├── .gitignore ├── Makefile ├── VERSION ├── encoding_test.py ├── pypi.do ├── readme.rst ├── rednose-local.xml.do ├── rednose.dist ├── rednose.py ├── rednose.xml.template ├── sample_test.py ├── setup.py └── test.do /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.egg-info 3 | build 4 | dist 5 | .* 6 | 0inst 7 | *.stamp 8 | *-local.xml 9 | gup 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | redo=0install run --command=redo-ifchange http://gfxmonk.net/dist/0install/redo.xml 2 | 3 | default: test 4 | 5 | %: phony 6 | ${redo} $@ 7 | 8 | .PHONY: phony default 9 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.4.3 -------------------------------------------------------------------------------- /encoding_test.py: -------------------------------------------------------------------------------- 1 | # vim: fileencoding=utf-8: 2 | 3 | #NOTE: this file does *not* import unicode_literals, 4 | # so the assertion message is actually just utf-8 bytes 5 | 6 | def test(): 7 | assert False, "ä" 8 | -------------------------------------------------------------------------------- /pypi.do: -------------------------------------------------------------------------------- 1 | exec >&2 2 | redo-ifchange setup.py 3 | ./setup.py register sdist upload 4 | -------------------------------------------------------------------------------- /readme.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Unmaintained repository 3 | ========= 4 | 5 | The official repo for rednose is now https://github.com/JBKahn/rednose, please go there for the latest code! 6 | -------------------------------------------------------------------------------- /rednose-local.xml.do: -------------------------------------------------------------------------------- 1 | exec >&2 2 | set -eu 3 | 0install run http://gfxmonk.net/dist/0install/0local.xml rednose.xml.template 4 | mv "$1" "$3" 5 | -------------------------------------------------------------------------------- /rednose.dist: -------------------------------------------------------------------------------- 1 | setup.py 2 | rednose.py 3 | -------------------------------------------------------------------------------- /rednose.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2009, Tim Cuthbertson # All rights reserved. 2 | # 3 | # Redistribution and use in source and binary forms, with or without 4 | # modification, are permitted provided that the following conditions 5 | # are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright 8 | # notice, this list of conditions and the following disclaimer. 9 | # * Redistributions in binary form must reproduce the above 10 | # copyright notice, this list of conditions and the following 11 | # disclaimer in the documentation and/or other materials provided 12 | # with the distribution. 13 | # * Neither the name of the organisation nor the names of its 14 | # contributors may be used to endorse or promote products derived 15 | # from this software without specific prior written permission. 16 | # 17 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 20 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 21 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 24 | # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 25 | # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 27 | # WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | # POSSIBILITY OF SUCH DAMAGE. 29 | 30 | from __future__ import print_function 31 | import os 32 | import sys 33 | import linecache 34 | import re 35 | 36 | import nose 37 | import termstyle 38 | 39 | PY3 = sys.version_info[0] >= 3 40 | if PY3: 41 | to_unicode = str 42 | else: 43 | def to_unicode(s): 44 | try: 45 | return unicode(s) 46 | except UnicodeDecodeError: 47 | s = str(s) 48 | try: 49 | # try utf-8, the most likely case 50 | return unicode(s, 'UTF-8') 51 | except UnicodeDecodeError: 52 | # Can't decode, just use `repr` 53 | return unicode(repr(s)) 54 | 55 | 56 | failure = 'FAILED' 57 | error = 'ERROR' 58 | success = 'passed' 59 | skip = 'skipped' 60 | expected_failure = 'expected failure' 61 | unexpected_success = 'unexpected success' 62 | line_length = 77 63 | 64 | 65 | class RedNose(nose.plugins.Plugin): 66 | env_opt = 'NOSE_REDNOSE' 67 | env_opt_color = 'NOSE_REDNOSE_COLOR' 68 | 69 | def __init__(self, *args): 70 | super(RedNose, self).__init__(*args) 71 | self.enabled = False 72 | 73 | def options(self, parser, env=os.environ): 74 | rednose_on = bool(env.get(self.env_opt, False)) 75 | rednose_color = env.get(self.env_opt_color, 'auto') 76 | 77 | parser.add_option( 78 | "--rednose", 79 | action="store_true", 80 | default=rednose_on, 81 | dest="rednose", 82 | help="enable colour output (alternatively, set $%s=1)" % (self.env_opt,) 83 | ) 84 | parser.add_option( 85 | "--no-color", 86 | action="store_false", 87 | dest="rednose", 88 | help="disable colour output" 89 | ) 90 | parser.add_option( 91 | "--force-color", 92 | action="store_const", 93 | dest='rednose_color', 94 | default=rednose_color, 95 | const='force', 96 | help="force colour output when not using a TTY (alternatively, set $%s=force)" % (self.env_opt_color,) 97 | ) 98 | parser.add_option( 99 | "--immediate", 100 | action="store_true", 101 | default=False, 102 | help="print errors and failures as they happen, as well as at the end" 103 | ) 104 | parser.add_option( 105 | "--full-file-path", 106 | action="store_true", 107 | default=False, 108 | help="print the full file path as opposed to the one relative to your directory (default)" 109 | ) 110 | 111 | def configure(self, options, conf): 112 | if options.rednose: 113 | self.enabled = True 114 | termstyle_init = { 115 | 'force': termstyle.enable, 116 | 'off': termstyle.disable 117 | }.get(options.rednose_color, termstyle.auto) 118 | termstyle_init() 119 | 120 | self.immediate = options.immediate 121 | self.verbose = options.verbosity >= 2 122 | self.full_file_path = options.full_file_path 123 | 124 | def prepareTestResult(self, result): # noqa 125 | """Required to prevent others from monkey patching the add methods.""" 126 | return result 127 | 128 | def prepareTestRunner(self, runner): # noqa 129 | return ColourTestRunner(stream=runner.stream, descriptions=runner.descriptions, verbosity=runner.verbosity, config=runner.config, immediate=self.immediate, use_relative_path=not self.full_file_path) 130 | 131 | def setOutputStream(self, stream): # noqa 132 | self.stream = stream 133 | if os.name == 'nt': 134 | import colorama 135 | self.stream = colorama.initialise.wrap_stream(stream, convert=True, strip=False, autoreset=False, wrap=True) 136 | 137 | 138 | class ColourTestRunner(nose.core.TextTestRunner): 139 | 140 | def __init__(self, stream, descriptions, verbosity, config, immediate, use_relative_path): 141 | super(ColourTestRunner, self).__init__(stream=stream, descriptions=descriptions, verbosity=verbosity, config=config) 142 | self.immediate = immediate 143 | self.use_relative_path = use_relative_path 144 | 145 | def _makeResult(self): # noqa 146 | return ColourTextTestResult(self.stream, self.descriptions, self.verbosity, self.config, immediate=self.immediate, use_relative_path=self.use_relative_path) 147 | 148 | 149 | class ColourTextTestResult(nose.result.TextTestResult): 150 | """ 151 | A test result class that prints colour formatted text results to the stream. 152 | """ 153 | 154 | def __init__(self, stream, descriptions, verbosity, config, errorClasses=None, immediate=False, use_relative_path=False): # noqa 155 | super(ColourTextTestResult, self).__init__(stream=stream, descriptions=descriptions, verbosity=verbosity, config=config, errorClasses=errorClasses) 156 | self.has_test_ids = config.options.enable_plugin_id 157 | if self.has_test_ids: 158 | self.ids = self.get_test_ids(self.config.options.testIdFile) 159 | self.total = 0 160 | self.immediate = immediate 161 | self.use_relative_path = use_relative_path 162 | self.test_failures_and_exceptions = [] 163 | self.error = self.success = self.failure = self.skip = self.expected_failure = self.unexpected_success = 0 164 | self.verbose = config.verbosity >= 2 165 | self.short_status_map = { 166 | failure: 'F', 167 | error: 'E', 168 | skip: '-', 169 | expected_failure: "X", 170 | unexpected_success: "U", 171 | success: '.', 172 | } 173 | 174 | def get_test_ids(self, test_id_file): 175 | """Returns a mapping of test to id if one exists, else an empty dictionary.""" 176 | try: 177 | with open(test_id_file, 'rb') as fh: 178 | try: 179 | from cPickle import load 180 | except ImportError: 181 | from pickle import load 182 | data = load(fh) 183 | return {address: _id for _id, address in data["ids"].items()} 184 | except IOError: 185 | return {} 186 | 187 | def printSummary(self, start, stop): # noqa 188 | """Summarize all tests - the number of failures, errors and successes.""" 189 | self._line(termstyle.black) 190 | self._out("%s test%s run in %0.3f seconds" % (self.total, self._plural(self.total), stop - start)) 191 | if self.total > self.success: 192 | self._outln(". ") 193 | 194 | additionals = [ 195 | {"color": termstyle.red, "count": self.failure, "message": "%s FAILED"}, 196 | {"color": termstyle.yellow, "count": self.error, "message": "%s error%s" % ("%s", self._plural(self.error))}, 197 | {"color": termstyle.blue, "count": self.skip, "message": "%s skipped"}, 198 | {"color": termstyle.green, "count": self.expected_failure, "message": "%s expected_failures"}, 199 | {"color": termstyle.cyan, "count": self.unexpected_success, "message": "%s unexpected_successes"}, 200 | ] 201 | 202 | additionals_to_print = [ 203 | additional["color"](additional["message"] % (additional["count"])) for additional in additionals if additional["count"] > 0 204 | ] 205 | 206 | self._out(', '.join(additionals_to_print)) 207 | 208 | self._out(termstyle.green(" (%s test%s passed)" % (self.success, self._plural(self.success)))) 209 | self._outln() 210 | 211 | def _plural(self, num): 212 | return '' if num == 1 else 's' 213 | 214 | def _line(self, color=termstyle.reset, char='-'): 215 | """ 216 | Print a line of separator characters (default '-') in the given colour (default black). 217 | """ 218 | self._outln(color(char * line_length)) 219 | 220 | def _print_test(self, type_, color): 221 | self.total += 1 222 | if self.verbose: 223 | self._outln(color(type_)) 224 | else: 225 | short_ = self.short_status_map.get(type_, ".") 226 | self._out(color(short_)) 227 | if self.total % line_length == 0: 228 | self._outln() 229 | 230 | def _out(self, msg='', newline=False): 231 | self.stream.write(msg) 232 | if newline: 233 | self.stream.write('\n') 234 | 235 | def _outln(self, msg=''): 236 | self._out(msg=msg, newline=True) 237 | 238 | def _generate_and_add_test_report(self, type_, test, err): 239 | report = self._report_test(len(self.test_failures_and_exceptions), type_, test, err) 240 | self.test_failures_and_exceptions.append(report) 241 | 242 | def addFailure(self, test, err): # noqa 243 | self.failure += 1 244 | self._print_test(failure, termstyle.red) 245 | self._generate_and_add_test_report(failure, test, err) 246 | 247 | def addError(self, test, err): # noqa 248 | self.error += 1 249 | self._print_test(error, termstyle.yellow) 250 | self._generate_and_add_test_report(error, test, err) 251 | 252 | def addSuccess(self, test): # noqa 253 | self.success += 1 254 | self._print_test(success, termstyle.green) 255 | 256 | def addSkip(self, test, err): # noqa 257 | self.skip += 1 258 | self._print_test(skip, termstyle.blue) 259 | 260 | def addExpectedFailure(self, test, err): # noqa 261 | self.expected_failure += 1 262 | self._print_test(expected_failure, termstyle.green) 263 | 264 | def addUnexpectedSuccess(self, test): # noqa 265 | self.unexpected_success += 1 266 | self._print_test(unexpected_success, termstyle.cyan) 267 | 268 | def _report_test(self, report_index_num, type_, test, err): # noqa 269 | """report the results of a single (failing or errored) test""" 270 | if type_ == failure: 271 | color = termstyle.red 272 | else: 273 | color = termstyle.yellow 274 | 275 | exc_type, exc_instance, exc_trace = err 276 | 277 | colored_error_text = [ 278 | ''.join(self.format_traceback(exc_trace)), 279 | self._format_exception_message(exc_type, exc_instance, color) 280 | ] 281 | 282 | if type_ == failure: 283 | self.failures.append((test, colored_error_text)) 284 | flavour = "FAIL" 285 | else: 286 | self.errors.append((test, colored_error_text)) 287 | flavour = "ERROR" 288 | 289 | if self.immediate: 290 | self._outln() 291 | self.printErrorList(flavour, [(test, colored_error_text)], self.immediate) 292 | 293 | if self.has_test_ids: 294 | test_id = self.ids.get(test.address(), self.total) 295 | else: 296 | test_id = report_index_num + 1 297 | return (test_id, flavour, test, colored_error_text) 298 | 299 | def format_traceback(self, tb): 300 | ret = [termstyle.default(" Traceback (most recent call last):")] 301 | 302 | current_trace = tb 303 | while current_trace is not None: 304 | line = self._format_traceback_line(current_trace) 305 | if line is not None: 306 | ret.append(line) 307 | current_trace = current_trace.tb_next 308 | return '\n'.join(ret) 309 | 310 | def _format_traceback_line(self, tb): 311 | """ 312 | Formats the file / lineno / function line of a traceback element. 313 | 314 | Returns None is the line is not relevent to the user i.e. inside the test runner. 315 | """ 316 | if self._is_relevant_tb_level(tb): 317 | return None 318 | 319 | f = tb.tb_frame 320 | filename = f.f_code.co_filename 321 | lineno = tb.tb_lineno 322 | linecache.checkcache(filename) 323 | function_name = f.f_code.co_name 324 | 325 | line_contents = linecache.getline(filename, lineno, f.f_globals).strip() 326 | 327 | return " %s line %s in %s\n %s" % ( 328 | termstyle.blue(self._relative_path(filename) if self.use_relative_path else filename), 329 | termstyle.bold(termstyle.cyan(lineno)), 330 | termstyle.cyan(function_name), 331 | line_contents 332 | ) 333 | 334 | def _format_exception_message(self, exception_type, exception_instance, message_color): 335 | """Returns a colorized formatted exception message.""" 336 | orig_message_lines = to_unicode(exception_instance).splitlines() 337 | 338 | if len(orig_message_lines) == 0: 339 | return '' 340 | exception_message = orig_message_lines[0] 341 | 342 | message_lines = [message_color(' ', termstyle.bold(message_color(exception_type.__name__)), ": ") + message_color(exception_message)] 343 | for line in orig_message_lines[1:]: 344 | match = re.match('^---.* begin captured stdout.*----$', line) 345 | if match: 346 | message_color = termstyle.magenta 347 | message_lines.append('') 348 | line = ' ' + line 349 | message_lines.append(message_color(line)) 350 | return '\n'.join(message_lines) 351 | 352 | def _relative_path(self, path): 353 | """ 354 | Returns the relative path of a file to the current working directory. 355 | 356 | If path is a child of the current working directory, the relative 357 | path is returned surrounded. 358 | If path is not a child of the working directory, path is returned 359 | """ 360 | try: 361 | here = os.path.abspath(os.path.realpath(os.getcwd())) 362 | fullpath = os.path.abspath(os.path.realpath(path)) 363 | except OSError: 364 | return path 365 | if fullpath.startswith(here): 366 | return fullpath[len(here) + 1:] 367 | return path 368 | 369 | def printErrors(self): # noqa 370 | if not self.verbose: 371 | self._outln() 372 | if self.immediate: 373 | self._outln() 374 | for x in range(0, 4): 375 | self._outln() 376 | 377 | self._outln(termstyle.green("TEST RESULT OUTPUT:")) 378 | 379 | for (test_id, flavour, test, coloured_output_lines) in (self.test_failures_and_exceptions): 380 | self._printError(flavour=flavour, test=test, coloured_output_lines=coloured_output_lines, test_id=test_id) 381 | 382 | def _printError(self, flavour, test, coloured_output_lines, test_id, is_mid_test=False): # noqa 383 | if flavour == "FAIL": 384 | color = termstyle.red 385 | else: 386 | color = termstyle.yellow 387 | 388 | self._outln(color(self.separator1)) 389 | self._outln(color("%s) %s: %s" % (test_id, flavour, self.getDescription(test)))) 390 | self._outln(color(self.separator2)) 391 | 392 | for err_line in coloured_output_lines: 393 | self._outln("%s" % err_line) 394 | 395 | if is_mid_test: 396 | self._outln(color(self.separator2)) 397 | -------------------------------------------------------------------------------- /rednose.xml.template: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | rednose 5 | coloured output for nosetests 6 | https://github.com/gfxmonk/rednose 7 | 8 | 9 | ========= 10 | rednose 11 | ========= 12 | 13 | rednose is a `nosetests`_ 14 | plugin for adding colour (and readability) to nosetest console results. 15 | 16 | Installation: 17 | ------------- 18 | :: 19 | 20 | easy_install rednose 21 | 22 | or from the source:: 23 | 24 | ./setup.py develop 25 | 26 | Usage: 27 | ------ 28 | :: 29 | 30 | nosetests --rednose 31 | 32 | or:: 33 | 34 | export NOSE_REDNOSE=1 35 | nosetests 36 | 37 | Rednose by default uses auto-colouring, which will only use 38 | colour if you're running it on a terminal (i.e not piping it 39 | to a file). To control colouring, use one of:: 40 | 41 | nosetests --rednose --force-color 42 | nosetests --no-color 43 | 44 | (you can also control this by setting the environment variable NOSE_REDNOSE_COLOR to 'force' or 'no') 45 | 46 | .. _nosetests: http://somethingaboutorange.com/mrl/projects/nose/ 47 | 48 | 61 | 62 |
63 |

rednose

64 |

rednose is a nosetests plugin for adding colour (and readability) to nosetest console results.

65 |

Installation:

66 |
 67 | 				easy_install rednose
 68 | 
 69 | 			
70 |

or from the source:

71 |
 72 | 				./setup.py develop
 73 | 
 74 | 			
75 |

Usage:

76 |
 77 | 				nosetests --rednose
 78 | 
 79 | 			
80 |

or:

81 |
 82 | 				export NOSE_REDNOSE=1
 83 | nosetests
 84 | 
 85 | 			
86 |

Rednose by default uses auto-colouring, which will only use colour if you're running it on a terminal (i.e not piping it to a file). To control colouring, use one of:

87 |
 88 | 				nosetests --rednose --force-color
 89 | nosetests --no-color
 90 | 
 91 | 			
92 |

(you can also control this by setting the environment variable NOSE_REDNOSE_COLOR to 'force' or 'no')

93 |
94 |
95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -v 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 |
120 | -------------------------------------------------------------------------------- /sample_test.py: -------------------------------------------------------------------------------- 1 | # vim: set fileencoding=utf-8 : 2 | from __future__ import print_function 3 | from __future__ import unicode_literals 4 | import unittest 5 | 6 | def delay_fail(f): 7 | f() # fail it! 8 | 9 | class SomeTest(unittest.TestCase): 10 | def test_fail(self): 11 | print("oh noes, it's gonna blow!") 12 | delay_fail(lambda: self.fail('no dice')) 13 | 14 | def test_success(self): 15 | self.assertEqual(True, True) 16 | 17 | def test_error(self): 18 | raise RuntimeError("things went south\nand here's a second line!") 19 | 20 | def test_utf8(self): 21 | self.assertEqual('café', 'abc') 22 | 23 | def test_skip(self): 24 | import nose 25 | raise nose.SkipTest 26 | 27 | def test_with_long_description(self): 28 | """It's got a long description, you see?""" 29 | self.fail() 30 | 31 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ## NOTE: ## 4 | ## this setup.py was generated by zero2pypi: 5 | ## http://gfxmonk.net/dist/0install/zero2pypi.xml 6 | 7 | from setuptools import * 8 | setup( 9 | packages = find_packages(exclude=['test', 'test.*']), 10 | description='coloured output for nosetests', 11 | entry_points={'nose.plugins.0.10': ['NOSETESTS_PLUGINS = rednose:RedNose']}, 12 | install_requires=['setuptools', 'python-termstyle >=0.1.7', 'colorama'], 13 | long_description="\n**Note**: This package has been built automatically by\n`zero2pypi `_.\nIf possible, you should use the zero-install feed instead:\nhttp://gfxmonk.net/dist/0install/rednose.xml\n\n----------------\n\n=========\nrednose\n=========\n\nrednose is a `nosetests`_\nplugin for adding colour (and readability) to nosetest console results.\n\nInstallation:\n-------------\n::\n\n\teasy_install rednose\n\t\nor from the source::\n\n\t./setup.py develop\n\nUsage:\n------\n::\n\n\tnosetests --rednose\n\nor::\n\n\texport NOSE_REDNOSE=1\n\tnosetests\n\nRednose by default uses auto-colouring, which will only use\ncolour if you're running it on a terminal (i.e not piping it\nto a file). To control colouring, use one of::\n\n\tnosetests --rednose --force-color\n\tnosetests --no-color\n\n(you can also control this by setting the environment variable NOSE_REDNOSE_COLOR to 'force' or 'no')\n\n.. _nosetests: http://somethingaboutorange.com/mrl/projects/nose/\n", 14 | name='rednose', 15 | py_modules=['rednose'], 16 | url='https://github.com/gfxmonk/rednose', 17 | version='0.4.3', 18 | classifiers=[ 19 | "License :: OSI Approved :: BSD License", 20 | "Programming Language :: Python", 21 | "Programming Language :: Python :: 3", 22 | "Development Status :: 4 - Beta", 23 | "Intended Audience :: Developers", 24 | "Topic :: Software Development :: Libraries :: Python Modules", 25 | "Topic :: Software Development :: Testing", 26 | ], 27 | keywords='test nosetests nose nosetest output colour console', 28 | license='BSD', 29 | ) 30 | -------------------------------------------------------------------------------- /test.do: -------------------------------------------------------------------------------- 1 | exec >&2 2 | redo-ifchange rednose-local.xml 3 | 0launch http://0install.net/2008/interfaces/0test.xml \ 4 | rednose-local.xml \ 5 | http://repo.roscidus.com/python/python \ 6 | 2.6,2.8 3.0,4 7 | --------------------------------------------------------------------------------