New Upstream Release - python-rednose

Ready changes

Summary

Merged new upstream version: 1.3.0 (was: 0.4.1).

Resulting package

Built on 2022-12-30T19:46 (took 4m11s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases python3-rednose

Lintian Result

Diff

diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 6fb73b5..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-*.pyc
-*.egg-info
-build
-dist
-.*
-0inst
-*.stamp
-*-local.xml
diff --git a/LICENCE b/LICENCE
new file mode 100644
index 0000000..0b69c6d
--- /dev/null
+++ b/LICENCE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Joseph Kahn
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..b8fa171
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,2 @@
+include *.rst
+include LICENCE
diff --git a/Makefile b/Makefile
deleted file mode 100644
index a8d683a..0000000
--- a/Makefile
+++ /dev/null
@@ -1,8 +0,0 @@
-redo=0install run --command=redo-ifchange http://gfxmonk.net/dist/0install/redo.xml
-
-default: test
-
-%: phony
-	${redo} $@
-
-.PHONY: phony default
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..3d1d3fe
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,79 @@
+Metadata-Version: 1.1
+Name: rednose
+Version: 1.3.0
+Summary: coloured output for nosetests
+Home-page: https://github.com/JBKahn/rednose
+Author: UNKNOWN
+Author-email: UNKNOWN
+License: MIT
+Description-Content-Type: UNKNOWN
+Description: =========
+        rednose
+        =========
+        
+        rednose is a `nosetests`_
+        plugin for adding colour (and readability) to nosetest console results.
+        
+        .. image:: rednose_example.png
+        	:scale: 50 %
+        	:align: center
+        
+        Installation:
+        -------------
+        ::
+        
+        	pip install rednose
+        	
+        or from the source::
+        
+        	python setup.py install
+        
+        Rednose officially supports Python 2.7, 3.4, and 3.5.
+        
+        Usage:
+        ------
+        ::
+        
+        	nosetests --rednose
+        
+        or::
+        
+        	export NOSE_REDNOSE=1
+        	nosetests
+        
+        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::
+        
+        	nosetests --rednose --force-color
+        	nosetests --no-color
+        
+        (you can also control this by setting the environment variable NOSE_REDNOSE_COLOR to 'force' or 'no')
+        
+        Rednose by default prints file paths relative to the working
+        directory. If you want the full path in the traceback then
+        use::
+        
+        	nosetests --rednose --full-file-path
+        
+        Rednose by default prints error style formating for skipped tests,
+        to supress this use::
+        
+        	nosetests --rednose --hide-skips
+        
+        Rednose supports printing the test results mid run as well as at
+        the end, to enable it use::
+        
+        	nosetests --rednose --immediate
+        
+        .. _nosetests: http://somethingaboutorange.com/mrl/projects/nose/
+        
+Keywords: test nosetests nose nosetest output colour console
+Platform: UNKNOWN
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 3
+Classifier: Development Status :: 4 - Beta
+Classifier: Intended Audience :: Developers
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: Software Development :: Testing
diff --git a/VERSION b/VERSION
deleted file mode 100644
index 44bb5d1..0000000
--- a/VERSION
+++ /dev/null
@@ -1 +0,0 @@
-0.4.1
\ No newline at end of file
diff --git a/debian/changelog b/debian/changelog
index 00a0445..2053e9f 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+python-rednose (1.3.0-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Fri, 30 Dec 2022 19:42:31 -0000
+
 python-rednose (0.4.1-3) unstable; urgency=medium
 
   [ Ondřej Nový ]
diff --git a/pypi.do b/pypi.do
deleted file mode 100644
index ee5165c..0000000
--- a/pypi.do
+++ /dev/null
@@ -1,3 +0,0 @@
-exec >&2
-redo-ifchange setup.py
-./setup.py register sdist upload
diff --git a/readme.rst b/readme.rst
index c761fab..1b86596 100644
--- a/readme.rst
+++ b/readme.rst
@@ -5,15 +5,21 @@ rednose
 rednose is a `nosetests`_
 plugin for adding colour (and readability) to nosetest console results.
 
+.. image:: rednose_example.png
+	:scale: 50 %
+	:align: center
+
 Installation:
 -------------
 ::
 
-	easy_install rednose
+	pip install rednose
 	
 or from the source::
 
-	./setup.py develop
+	python setup.py install
+
+Rednose officially supports Python 2.7, 3.4, and 3.5.
 
 Usage:
 ------
@@ -35,4 +41,20 @@ to a file). To control colouring, use one of::
 
 (you can also control this by setting the environment variable NOSE_REDNOSE_COLOR to 'force' or 'no')
 
+Rednose by default prints file paths relative to the working
+directory. If you want the full path in the traceback then
+use::
+
+	nosetests --rednose --full-file-path
+
+Rednose by default prints error style formating for skipped tests,
+to supress this use::
+
+	nosetests --rednose --hide-skips
+
+Rednose supports printing the test results mid run as well as at
+the end, to enable it use::
+
+	nosetests --rednose --immediate
+
 .. _nosetests: http://somethingaboutorange.com/mrl/projects/nose/
diff --git a/rednose-local.xml.do b/rednose-local.xml.do
deleted file mode 100644
index efaf40a..0000000
--- a/rednose-local.xml.do
+++ /dev/null
@@ -1,4 +0,0 @@
-exec >&2
-set -eu
-0install run http://gfxmonk.net/dist/0install/0local.xml rednose.xml.template
-mv "$1" "$3"
diff --git a/rednose.dist b/rednose.dist
deleted file mode 100644
index 34d43f1..0000000
--- a/rednose.dist
+++ /dev/null
@@ -1,2 +0,0 @@
-setup.py
-rednose.py
diff --git a/rednose.egg-info/PKG-INFO b/rednose.egg-info/PKG-INFO
new file mode 100644
index 0000000..3d1d3fe
--- /dev/null
+++ b/rednose.egg-info/PKG-INFO
@@ -0,0 +1,79 @@
+Metadata-Version: 1.1
+Name: rednose
+Version: 1.3.0
+Summary: coloured output for nosetests
+Home-page: https://github.com/JBKahn/rednose
+Author: UNKNOWN
+Author-email: UNKNOWN
+License: MIT
+Description-Content-Type: UNKNOWN
+Description: =========
+        rednose
+        =========
+        
+        rednose is a `nosetests`_
+        plugin for adding colour (and readability) to nosetest console results.
+        
+        .. image:: rednose_example.png
+        	:scale: 50 %
+        	:align: center
+        
+        Installation:
+        -------------
+        ::
+        
+        	pip install rednose
+        	
+        or from the source::
+        
+        	python setup.py install
+        
+        Rednose officially supports Python 2.7, 3.4, and 3.5.
+        
+        Usage:
+        ------
+        ::
+        
+        	nosetests --rednose
+        
+        or::
+        
+        	export NOSE_REDNOSE=1
+        	nosetests
+        
+        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::
+        
+        	nosetests --rednose --force-color
+        	nosetests --no-color
+        
+        (you can also control this by setting the environment variable NOSE_REDNOSE_COLOR to 'force' or 'no')
+        
+        Rednose by default prints file paths relative to the working
+        directory. If you want the full path in the traceback then
+        use::
+        
+        	nosetests --rednose --full-file-path
+        
+        Rednose by default prints error style formating for skipped tests,
+        to supress this use::
+        
+        	nosetests --rednose --hide-skips
+        
+        Rednose supports printing the test results mid run as well as at
+        the end, to enable it use::
+        
+        	nosetests --rednose --immediate
+        
+        .. _nosetests: http://somethingaboutorange.com/mrl/projects/nose/
+        
+Keywords: test nosetests nose nosetest output colour console
+Platform: UNKNOWN
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 3
+Classifier: Development Status :: 4 - Beta
+Classifier: Intended Audience :: Developers
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: Software Development :: Testing
diff --git a/rednose.egg-info/SOURCES.txt b/rednose.egg-info/SOURCES.txt
new file mode 100644
index 0000000..577ff13
--- /dev/null
+++ b/rednose.egg-info/SOURCES.txt
@@ -0,0 +1,18 @@
+LICENCE
+MANIFEST.in
+readme.rst
+rednose.py
+setup.py
+rednose.egg-info/PKG-INFO
+rednose.egg-info/SOURCES.txt
+rednose.egg-info/dependency_links.txt
+rednose.egg-info/entry_points.txt
+rednose.egg-info/requires.txt
+rednose.egg-info/top_level.txt
+test_files/__init__.py
+test_files/basic_test_suite.py
+test_files/class_test_failure.py
+test_files/encoding_test.py
+test_files/encoding_test_with_literals.py
+test_files/new_tests.py
+test_files/sample_test.py
\ No newline at end of file
diff --git a/rednose.egg-info/dependency_links.txt b/rednose.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/rednose.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/rednose.egg-info/entry_points.txt b/rednose.egg-info/entry_points.txt
new file mode 100644
index 0000000..c491946
--- /dev/null
+++ b/rednose.egg-info/entry_points.txt
@@ -0,0 +1,3 @@
+[nose.plugins.0.10]
+NOSETESTS_PLUGINS = rednose:RedNose
+
diff --git a/rednose.egg-info/requires.txt b/rednose.egg-info/requires.txt
new file mode 100644
index 0000000..295efac
--- /dev/null
+++ b/rednose.egg-info/requires.txt
@@ -0,0 +1,3 @@
+setuptools
+termstyle>=0.1.7
+colorama
diff --git a/rednose.egg-info/top_level.txt b/rednose.egg-info/top_level.txt
new file mode 100644
index 0000000..f8eab0e
--- /dev/null
+++ b/rednose.egg-info/top_level.txt
@@ -0,0 +1,2 @@
+rednose
+test_files
diff --git a/rednose.py b/rednose.py
index 1ff892a..7f0f15f 100644
--- a/rednose.py
+++ b/rednose.py
@@ -1,9 +1,9 @@
 # Copyright (c) 2009, Tim Cuthbertson # All rights reserved.
-# 
+#
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions
 # are met:
-# 
+#
 #     * Redistributions of source code must retain the above copyright
 #       notice, this list of conditions and the following disclaimer.
 #     * Redistributions in binary form must reproduce the above
@@ -13,7 +13,7 @@
 #     * Neither the name of the organisation nor the names of its
 #       contributors may be used to endorse or promote products derived
 #       from this software without specific prior written permission.
-# 
+#
 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
@@ -32,356 +32,430 @@ import os
 import sys
 import linecache
 import re
-import time
 
 import nose
-
 import termstyle
 
+PY3 = sys.version_info[0] >= 3
+if PY3:
+    to_unicode = str
+else:
+    def to_unicode(s):
+        try:
+            return unicode(s)
+        except UnicodeDecodeError:
+            s = str(s)
+            try:
+                # try utf-8, the most likely case
+                return unicode(s, 'UTF-8')
+            except UnicodeDecodeError:
+                # Can't decode, just use `repr`
+                return unicode(repr(s))
+
+
 failure = 'FAILED'
 error = 'ERROR'
 success = 'passed'
 skip = 'skipped'
+expected_failure = 'expected failure'
+unexpected_success = 'unexpected success'
 line_length = 77
 
-PY3 = sys.version_info[0] >= 3
-if PY3:
-	to_unicode = str
-else:
-	def to_unicode(s):
-		try:
-			return unicode(s)
-		except UnicodeDecodeError:
-			return unicode(repr(str(s)))
-
-BLACKLISTED_WRITERS = [
-	'nose[\\/]result\\.pyc?$',
-	'unittest[\\/]runner\\.pyc?$'
-]
-REDNOSE_DEBUG = False
-
 
 class RedNose(nose.plugins.Plugin):
-	env_opt = 'NOSE_REDNOSE'
-	env_opt_color = 'NOSE_REDNOSE_COLOR'
-	score = 199  # just under the `coverage` module
-
-	def __init__(self, *args):
-		super(RedNose, self).__init__(*args)
-		self.reports = []
-		self.error = self.success = self.failure = self.skip = 0
-		self.total = 0
-		self.stream = None
-		self.verbose = False
-		self.enabled = False
-		self.tree = False
-
-	def options(self, parser, env=os.environ):
-		global REDNOSE_DEBUG
-		rednose_on = bool(env.get(self.env_opt, False))
-		rednose_color = env.get(self.env_opt_color, 'auto')
-		REDNOSE_DEBUG = bool(env.get('REDNOSE_DEBUG', False))
-
-		parser.add_option(
-			"--rednose",
-			action="store_true",
-			default=rednose_on,
-			dest="rednose",
-			help="enable colour output (alternatively, set $%s=1)" % (self.env_opt,)
-		)
-		parser.add_option(
-			"--no-color",
-			action="store_false",
-			dest="rednose",
-			help="disable colour output"
-		)
-		parser.add_option(
-			"--force-color",
-			action="store_const",
-			dest='rednose_color',
-			default=rednose_color,
-			const='force',
-			help="force colour output when not using a TTY (alternatively, set $%s=force)" % (self.env_opt_color,)
-		)
-		parser.add_option(
-			"--immediate",
-			action="store_true",
-			default=False,
-			help="print errors and failures as they happen, as well as at the end"
-		)
-
-	def configure(self, options, conf):
-		if options.rednose:
-			self.enabled = True
-			termstyle_init = {
-				'force': termstyle.enable,
-				'off': termstyle.disable
-			}.get(options.rednose_color, termstyle.auto)
-			termstyle_init()
-
-			self.immediate = options.immediate
-			self.verbose = options.verbosity >= 2
-
-	def begin(self):
-		self.start_time = time.time()
-		self._in_test = False
-
-	def _format_test_name(self, test):
-		return test.shortDescription() or to_unicode(test)
-
-	def prepareTestResult(self, result):
-		result.stream = FilteringStream(self.stream, BLACKLISTED_WRITERS)
-
-	def beforeTest(self, test):
-		self._in_test = True
-		if self.verbose:
-			self._out(self._format_test_name(test) + ' ... ')
-
-	def afterTest(self, test):
-		if self._in_test:
-			self.addSkip()
-
-	def _print_test(self, type_, color):
-		self.total += 1
-		if self.verbose:
-			self._outln(color(type_))
-		else:
-			if type_ == failure:
-				short_ = 'F'
-			elif type_ == error:
-				short_ = 'X'
-			elif type_ == skip:
-				short_ = '-'
-			else:
-				short_ = '.'
-			self._out(color(short_))
-			if self.total % line_length == 0:
-				self._outln()
-		self._in_test = False
-
-	def _add_report(self, report):
-		failure_type, test, err = report
-		self.reports.append(report)
-		if self.immediate:
-			self._outln()
-			self._report_test(len(self.reports), *report)
-
-	def addFailure(self, test, err):
-		self.failure += 1
-		self._add_report((failure, test, err))
-		self._print_test(failure, termstyle.red)
-
-	def addError(self, test, err):
-		if err[0].__name__ == 'SkipTest':
-			self.addSkip(test, err)
-			return
-		self.error += 1
-		self._add_report((error, test, err))
-		self._print_test(error, termstyle.yellow)
-
-	def addSuccess(self, test):
-		self.success += 1
-		self._print_test(success, termstyle.green)
-
-	def addSkip(self, test=None, err=None):
-		self.skip += 1
-		self._print_test(skip, termstyle.blue)
-
-	def setOutputStream(self, stream):
-		self.stream = stream
-
-	def report(self, stream):
-		"""report on all registered failures and errors"""
-		self._outln()
-		if self.immediate:
-			for x in range(0, 5):
-				self._outln()
-		report_num = 0
-		if len(self.reports) > 0:
-			for report_num, report in enumerate(self.reports):
-				self._report_test(report_num + 1, *report)
-			self._outln()
-
-		self._summarize()
-
-	def _summarize(self):
-		"""summarize all tests - the number of failures, errors and successes"""
-		self._line(termstyle.black)
-		self._out("%s test%s run in %0.1f seconds" % (
-			self.total,
-			self._plural(self.total),
-			time.time() - self.start_time))
-		if self.total > self.success:
-			self._outln(". ")
-			additionals = []
-			if self.failure > 0:
-				additionals.append(termstyle.red("%s FAILED" % (
-					self.failure,)))
-			if self.error > 0:
-				additionals.append(termstyle.yellow("%s error%s" % (
-					self.error,
-					self._plural(self.error) )))
-			if self.skip > 0:
-				additionals.append(termstyle.blue("%s skipped" % (
-					self.skip)))
-			self._out(', '.join(additionals))
-				
-		self._out(termstyle.green(" (%s test%s passed)" % (
-			self.success,
-			self._plural(self.success) )))
-		self._outln()
-
-	def _report_test(self, report_num, type_, test, err):
-		"""report the results of a single (failing or errored) test"""
-		self._line(termstyle.black)
-		self._out("%s) " % (report_num))
-		if type_ == failure:
-			color = termstyle.red
-			self._outln(color('FAIL: %s' % (self._format_test_name(test),)))
-		else:
-			color = termstyle.yellow
-			self._outln(color('ERROR: %s' % (self._format_test_name(test),)))
-
-		exc_type, exc_instance, exc_trace = err
-
-		self._outln()
-		self._outln(self._fmt_traceback(exc_trace))
-		self._out(color('   ', termstyle.bold(color(exc_type.__name__)), ": "))
-		self._outln(self._fmt_message(exc_instance, color))
-		self._outln()
-
-	def _relative_path(self, path):
-		"""
-		If path is a child of the current working directory, the relative
-		path is returned surrounded by bold xterm escape sequences.
-		If path is not a child of the working directory, path is returned
-		"""
-		try:
-			here = os.path.abspath(os.path.realpath(os.getcwd()))
-			fullpath = os.path.abspath(os.path.realpath(path))
-		except OSError:
-			return path
-		if fullpath.startswith(here):
-			return termstyle.bold(fullpath[len(here)+1:])
-		return path
-
-	def _file_line(self, tb):
-		"""formats the file / lineno / function line of a traceback element"""
-		prefix = "file://"
-		prefix = ""
-
-		f = tb.tb_frame
-		if '__unittest' in f.f_globals:
-			# this is the magical flag that prevents unittest internal
-			# code from junking up the stacktrace
-			return None
-
-		filename = f.f_code.co_filename
-		lineno = tb.tb_lineno
-		linecache.checkcache(filename)
-		function_name = f.f_code.co_name
-
-		line_contents = linecache.getline(filename, lineno, f.f_globals).strip()
-
-		return "    %s line %s in %s\n      %s" % (
-			termstyle.blue(prefix, self._relative_path(filename)),
-			lineno,
-			termstyle.cyan(function_name),
-			line_contents)
-
-	def _fmt_traceback(self, trace):
-		"""format a traceback"""
-		ret = []
-		ret.append(termstyle.default("   Traceback (most recent call last):"))
-		current_trace = trace
-		while current_trace is not None:
-			line = self._file_line(current_trace)
-			if line is not None:
-				ret.append(line)
-			current_trace = current_trace.tb_next
-		return '\n'.join(ret)
-	
-	def _fmt_message(self, exception, color):
-		orig_message_lines = to_unicode(exception).splitlines()
-
-		if len(orig_message_lines) == 0:
-			return ''
-		message_lines = [color(orig_message_lines[0])]
-		for line in orig_message_lines[1:]:
-			match = re.match('^---.* begin captured stdout.*----$', line)
-			if match:
-				color = None
-				message_lines.append('')
-			line = '   ' + line
-			message_lines.append(color(line) if color is not None else line)
-		return '\n'.join(message_lines)
-
-	def _out(self, msg='', newline=False):
-		self.stream.write(msg)
-		if newline:
-			self.stream.write('\n')
-
-	def _outln(self, msg=''):
-		self._out(msg, True)
-
-	def _plural(self, num):
-		return '' if num == 1 else 's'
-
-	def _line(self, color=termstyle.reset, char='-'):
-		"""
-		print a line of separator characters (default '-')
-		in the given colour (default black)
-		"""
-		self._outln(color(char * line_length))
-
-
-import traceback
-import sys
-
-
-class FilteringStream(object):
-	"""
-	A wrapper for a stream that will filter
-	calls to `write` and `writeln` to ignore calls
-	from blacklisted callers
-	(implemented as a regex on their filename, according
-	to traceback.extract_stack())
-
-	It's super hacky, but there seems to be no other way
-	to suppress nose's default output
-	"""
-	def __init__(self, stream, excludes):
-		self.__stream = stream
-		self.__excludes = list(map(re.compile, excludes))
-
-	def __should_filter(self):
-		try:
-			stack = traceback.extract_stack(limit=3)[0]
-			filename = stack[0]
-			pattern_matches_filename = lambda pattern: pattern.search(filename)
-			should_filter = any(map(pattern_matches_filename, self.__excludes))
-			if REDNOSE_DEBUG:
-				print >> sys.stderr, "REDNOSE_DEBUG: got write call from %s, should_filter = %s" % (
-						filename, should_filter)
-			return should_filter
-		except StandardError as e:
-			if REDNOSE_DEBUG:
-				print("\nError in rednose filtering: %s" % (e,), file=sys.stderr)
-				traceback.print_exc(sys.stderr)
-			return False
-
-	def write(self, *a):
-		if self.__should_filter():
-			return
-		return self.__stream.write(*a)
-
-	def writeln(self, *a):
-		if self.__should_filter():
-			return
-		return self.__stream.writeln(*a)
-
-	# pass non-known methods through to self.__stream
-	def __getattr__(self, name):
-		if REDNOSE_DEBUG:
-			print("REDNOSE_DEBUG: getting attr %s" % (name,), file=sys.stderr)
-		return getattr(self.__stream, name)
+    # In order to color multiprocess output it has to have a higher score.
+    score = 1001
+    env_opt = 'NOSE_REDNOSE'
+    env_opt_color = 'NOSE_REDNOSE_COLOR'
+    env_opt_hide_skips = 'NOSE_REDNOSE_HIDE_SKIPS'
+
+    def __init__(self, *args):
+        super(RedNose, self).__init__(*args)
+        self.enabled = False
+
+    def options(self, parser, env=os.environ):
+        rednose_on_env_value = env.get(self.env_opt, False)
+        rednose_hide_skips_env_value = env.get(self.env_opt_hide_skips, False)
+
+        rednose_on = bool(rednose_on_env_value) if rednose_on_env_value not in ['false', '0'] else False
+        rednose_hide_skips = bool(rednose_hide_skips_env_value) if rednose_hide_skips_env_value not in ['false', '0'] else False
+        rednose_color = env.get(self.env_opt_color, 'auto')
+
+        parser.add_option(
+            "--rednose",
+            action="store_true",
+            default=rednose_on,
+            dest="rednose",
+            help="enable colour output (alternatively, set $%s=1)" % (self.env_opt,)
+        )
+        parser.add_option(
+            "--no-color",
+            action="store_false",
+            dest="rednose",
+            help="disable colour output"
+        )
+        parser.add_option(
+            "--force-color",
+            action="store_const",
+            dest='rednose_color',
+            default=rednose_color,
+            const='force',
+            help="force colour output when not using a TTY (alternatively, set $%s=force)" % (self.env_opt_color,)
+        )
+        parser.add_option(
+            "--immediate",
+            action="store_true",
+            default=False,
+            help="print errors and failures as they happen, as well as at the end"
+        )
+        parser.add_option(
+            "--full-file-path",
+            action="store_true",
+            default=False,
+            help="print the full file path as opposed to the one relative to your directory (default)"
+        )
+        parser.add_option(
+            "--hide-skips",
+            action="store_true",
+            default=rednose_hide_skips,
+            help="Hide the error printing for skip cases (default is to show them)"
+        )
+
+    def configure(self, options, conf):
+        if options.rednose:
+            self.enabled = True
+            termstyle_init = {
+                'force': termstyle.enable,
+                'off': termstyle.disable
+            }.get(options.rednose_color, termstyle.auto)
+            termstyle_init()
+
+            self.immediate = options.immediate
+            self.verbose = options.verbosity >= 2
+            self.full_file_path = options.full_file_path
+            self.hide_skips = options.hide_skips
+
+    def prepareTestResult(self, result):  # noqa
+        """Required to prevent others from monkey patching the add methods."""
+        return result
+
+    def prepareTestRunner(self, runner):  # noqa
+        stream = runner.stream
+        if os.name == 'nt':
+            import colorama
+            stream = colorama.initialise.wrap_stream(stream, convert=True, strip=False, autoreset=False, wrap=True)
+        return ColourTestRunner(stream=stream, descriptions=runner.descriptions, verbosity=runner.verbosity, config=runner.config, immediate=self.immediate, use_relative_path=not self.full_file_path, hide_skips=self.hide_skips)
+
+
+class ColourTestRunner(nose.core.TextTestRunner):
+
+    def __init__(self, stream, descriptions, verbosity, config, immediate, use_relative_path, hide_skips):
+        super(ColourTestRunner, self).__init__(stream=stream, descriptions=descriptions, verbosity=verbosity, config=config)
+        self.immediate = immediate
+        self.use_relative_path = use_relative_path
+        self.hide_skips = hide_skips
+
+    def _makeResult(self):  # noqa
+        return ColourTextTestResult(self.stream, self.descriptions, self.verbosity, self.config, immediate=self.immediate, use_relative_path=self.use_relative_path, hide_skips=self.hide_skips)
+
+
+class ColourTextTestResult(nose.result.TextTestResult):
+    """
+    A test result class that prints colour formatted text results to the stream.
+    """
+
+    def __init__(self, stream, descriptions, verbosity, config, errorClasses=None, immediate=False, use_relative_path=False, hide_skips=False):  # noqa
+        super(ColourTextTestResult, self).__init__(stream=stream, descriptions=descriptions, verbosity=verbosity, config=config, errorClasses=errorClasses)
+        self.has_test_ids = getattr(config.options, "enable_plugin_id", False)
+        if self.has_test_ids:
+            self.ids = self.get_test_ids(self.config.options.testIdFile)
+        self.total = 0
+        self.immediate = immediate
+        self.use_relative_path = use_relative_path
+        self.hide_skips = hide_skips
+        self.test_failures_and_exceptions = []
+        self.error = self.success = self.failure = self.skip = self.expected_failure = self.unexpected_success = 0
+        self.verbose = config.verbosity >= 2
+        self.short_status_map = {
+            failure: 'F',
+            error: 'E',
+            skip: '-',
+            expected_failure: "X",
+            unexpected_success: "U",
+            success: '.',
+        }
+        self.skips = []
+
+    def get_test_ids(self, test_id_file):
+        """Returns a mapping of test to id if one exists, else an empty dictionary."""
+        try:
+            with open(test_id_file, 'rb') as fh:
+                try:
+                    from cPickle import load
+                except ImportError:
+                    from pickle import load
+                data = load(fh)
+
+            return dict((address, _id) for _id, address in data["ids"].items())
+        except (IOError, ValueError):
+            return {}
+
+    def printSummary(self, start, stop):  # noqa
+        """Summarize all tests - the number of failures, errors and successes."""
+        self._line(termstyle.black)
+        self._out("%s test%s run in %0.3f seconds" % (self.total, self._plural(self.total), stop - start))
+        if self.total > self.success:
+            self._outln(". ")
+
+            additionals = [
+                {"color": termstyle.red, "count": self.failure, "message": "%s FAILED"},
+                {"color": termstyle.yellow, "count": self.error, "message": "%s error%s" % ("%s", self._plural(self.error))},
+                {"color": termstyle.blue, "count": self.skip, "message": "%s skipped"},
+                {"color": termstyle.green, "count": self.expected_failure, "message": "%s expected_failures"},
+                {"color": termstyle.cyan, "count": self.unexpected_success, "message": "%s unexpected_successes"},
+            ]
+
+            additionals_to_print = [
+                additional["color"](additional["message"] % (additional["count"])) for additional in additionals if additional["count"] > 0
+            ]
+
+            self._out(', '.join(additionals_to_print))
+
+        self._out(termstyle.green(" (%s test%s passed)" % (self.success, self._plural(self.success))))
+        self._outln()
+
+    def _plural(self, num):
+        return '' if num == 1 else 's'
+
+    def _line(self, color=termstyle.reset, char='-'):
+        """
+        Print a line of separator characters (default '-') in the given colour (default black).
+        """
+        self._outln(color(char * line_length))
+
+    def _print_test(self, test, type_, color):
+        self.total += 1
+        if self.verbose:
+            self._outln(color(type_))
+        else:
+            short_ = self.short_status_map.get(type_, ".")
+            self._out(color(short_))
+            if self.total % line_length == 0:
+                self._outln()
+
+    def _out(self, msg='', newline=False):
+        try:
+            self.stream.write(msg)
+            self.stream.flush()
+        except UnicodeEncodeError:
+            self.stream.write(msg.encode('utf-8'))
+            self.stream.flush()
+        if newline:
+            self.stream.write('\n')
+
+    def _outln(self, msg=''):
+        self._out(msg=msg, newline=True)
+
+    def _generate_and_add_test_report(self, type_, test, err):
+        report = self._report_test(len(self.test_failures_and_exceptions), type_, test, err)
+        self.test_failures_and_exceptions.append(report)
+
+    def addFailure(self, test, err):  # noqa
+        self.failure += 1
+        self._print_test(test, failure, termstyle.red)
+        self._generate_and_add_test_report(failure, test, err)
+
+    def addError(self, test, err):  # noqa
+        if err[0].__name__ == 'SkipTest':
+            self.addSkip(test, err)
+            return
+        self.error += 1
+        self._print_test(test, error, termstyle.yellow)
+        self._generate_and_add_test_report(error, test, err)
+
+    def addSuccess(self, test):  # noqa
+        self.success += 1
+        self._print_test(test, success, termstyle.green)
+
+    def addSkip(self, test, err):  # noqa
+        if isinstance(err, Exception):
+            err = (err.__class__, err, None)
+        elif self.verbose:
+            skip_message = "#{test_id} {test_location} ... ".format(
+                test_id=self._get_id(test),
+                test_location=getattr(test.context, "__file__", getattr(test.context, "__module__", None))
+            )
+            self._out(termstyle.reset(skip_message))
+        self.skip += 1
+        self._print_test(test, skip, termstyle.blue)
+        if not self.hide_skips:
+            self._generate_and_add_test_report(skip, test, err)
+
+    def addExpectedFailure(self, test, err):  # noqa
+        self.expected_failure += 1
+        self._print_test(test, expected_failure, termstyle.green)
+
+    def addUnexpectedSuccess(self, test):  # noqa
+        self.unexpected_success += 1
+        self._print_test(test, unexpected_success, termstyle.cyan)
+
+    def _report_test(self, report_index_num, type_, test, err):  # noqa
+        """report the results of a single (failing or errored) test"""
+        exc_type, exc_instance, exc_trace = err
+
+        if type_ == failure:
+            color = termstyle.red
+        elif type_ == skip:
+            color = termstyle.blue
+            exc_type = nose.SkipTest
+        else:
+            color = termstyle.yellow
+
+        colored_error_text = [
+            ''.join(self.format_traceback(exc_trace)),
+            self._format_exception_message(exc_type, exc_instance, color)
+        ]
+
+        if type_ == failure:
+            self.failures.append((test, colored_error_text))
+            flavour = "FAIL"
+        elif type_ == skip:
+            self.skips.append((test, colored_error_text))
+            flavour = "SKIP"
+        else:
+            self.errors.append((test, colored_error_text))
+            flavour = "ERROR"
+
+        test_id = self._get_id(test)
+
+        if self.immediate:
+            self._outln()
+            self._printError(flavour, test, colored_error_text, test_id, True)
+
+        return (test_id, flavour, test, colored_error_text)
+
+    def _get_id(self, test):
+        report_index_num = len(self.test_failures_and_exceptions)
+        if self.has_test_ids:
+            try:
+                test_id = self.ids.get(test.address(), self.total)
+            except AttributeError:
+                test_id = report_index_num + 1
+        else:
+            test_id = report_index_num + 1
+        return test_id
+
+    def format_traceback(self, tb):
+        if tb is not None:
+            ret = [termstyle.default("   Traceback (most recent call last):")]
+        else:
+            ret = [termstyle.default("   No Traceback")]
+
+        current_trace = tb
+        while current_trace is not None:
+            line = self._format_traceback_line(current_trace)
+            if line is not None:
+                ret.append(line)
+            current_trace = current_trace.tb_next
+        return '\n'.join(ret)
+
+    def _format_traceback_line(self, tb):
+        """
+        Formats the file / lineno / function line of a traceback element.
+
+        Returns None is the line is not relevent to the user i.e. inside the test runner.
+        """
+        if self._is_relevant_tb_level(tb):
+            return None
+
+        f = tb.tb_frame
+        filename = f.f_code.co_filename
+        lineno = tb.tb_lineno
+        linecache.checkcache(filename)
+        function_name = f.f_code.co_name
+
+        line_contents = linecache.getline(filename, lineno, f.f_globals).strip()
+
+        return "    %s line %s in %s\n      %s" % (
+            termstyle.blue(self._relative_path(filename) if self.use_relative_path else filename),
+            termstyle.bold(termstyle.cyan(lineno)),
+            termstyle.cyan(function_name),
+            line_contents
+        )
+
+    def _format_exception_message(self, exception_type, exception_instance, message_color):
+        """Returns a colorized formatted exception message."""
+        orig_message_lines = to_unicode(exception_instance).splitlines()
+
+        if len(orig_message_lines) == 0:
+            return ''
+        exception_message = orig_message_lines[0]
+
+        message_lines = [message_color('   ', termstyle.bold(message_color(exception_type.__name__)), ": ") + message_color(exception_message)]
+        for line in orig_message_lines[1:]:
+            match = re.match('^---.* begin captured stdout.*----$', line)
+            if match:
+                message_color = termstyle.magenta
+                message_lines.append('')
+            line = '   ' + line
+            message_lines.append(message_color(line))
+        return '\n'.join(message_lines)
+
+    def _relative_path(self, path):
+        """
+        Returns the relative path of a file to the current working directory.
+
+        If path is a child of the current working directory, the relative
+        path is returned surrounded.
+        If path is not a child of the working directory, path is returned
+        """
+        try:
+            here = os.path.abspath(os.path.realpath(os.getcwd()))
+            fullpath = os.path.abspath(os.path.realpath(path))
+        except OSError:
+            return path
+        if fullpath.startswith(here):
+            return fullpath[len(here) + 1:]
+        return path
+
+    def printErrors(self):  # noqa
+        if not self.verbose:
+            self._outln()
+        if self.immediate:
+            self._outln()
+            for x in range(0, 4):
+                self._outln()
+
+            self._outln(termstyle.green("TEST RESULT OUTPUT:"))
+
+        for (test_id, flavour, test, coloured_output_lines) in (self.test_failures_and_exceptions):
+            self._printError(flavour=flavour, test=test, coloured_output_lines=coloured_output_lines, test_id=test_id)
+
+        # Copied from the parent function.
+        self._outln()
+        for cls in self.errorClasses.keys():
+            storage, label, isfail = self.errorClasses[cls]
+            if isfail:
+                self.printErrorList(label, storage)
+        # Might get patched into a result with no config
+        if hasattr(self, 'config'):
+            self.config.plugins.report(self.stream)
+
+    def _printError(self, flavour, test, coloured_output_lines, test_id, is_mid_test=False):  # noqa
+        if flavour == "FAIL":
+            color = termstyle.red
+        elif flavour == "SKIP":
+            color = termstyle.blue
+
+        else:
+            color = termstyle.yellow
+
+        self._outln(color(self.separator1))
+        self._outln(color("%s) %s: %s" % (test_id, flavour, self.getDescription(test))))
+        self._outln(color(self.separator2))
+
+        for err_line in coloured_output_lines:
+            self._outln("%s" % err_line)
+
+        if is_mid_test:
+            self._outln(color(self.separator2))
diff --git a/rednose.xml.template b/rednose.xml.template
deleted file mode 100644
index eb5fa88..0000000
--- a/rednose.xml.template
+++ /dev/null
@@ -1,117 +0,0 @@
-<?xml version="1.0"?>
-<?xml-stylesheet type='text/xsl' href='interface.xsl'?>
-<interface xmlns="http://zero-install.sourceforge.net/2004/injector/interface">
-	<name>rednose</name>
-	<summary>coloured output for nosetests</summary>
-	<feed-for interface="http://gfxmonk.net/dist/0install/rednose.xml"/>
-	<description>
-=========
-rednose
-=========
-
-rednose is a `nosetests`_
-plugin for adding colour (and readability) to nosetest console results.
-
-Installation:
--------------
-::
-
-	easy_install rednose
-	
-or from the source::
-
-	./setup.py develop
-
-Usage:
-------
-::
-
-	nosetests --rednose
-
-or::
-
-	export NOSE_REDNOSE=1
-	nosetests
-
-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::
-
-	nosetests --rednose --force-color
-	nosetests --no-color
-
-(you can also control this by setting the environment variable NOSE_REDNOSE_COLOR to 'force' or 'no')
-
-.. _nosetests: http://somethingaboutorange.com/mrl/projects/nose/
-	</description>
-	<pypi-extra xmlns="http://gfxmonk.net/dist/0install"><![CDATA[
-	classifiers=[
-		"License :: OSI Approved :: BSD License",
-		"Programming Language :: Python",
-		"Programming Language :: Python :: 3",
-		"Development Status :: 4 - Beta",
-		"Intended Audience :: Developers",
-		"Topic :: Software Development :: Libraries :: Python Modules",
-		"Topic :: Software Development :: Testing",
-	],
-	keywords='test nosetests nose nosetest output colour console',
-	license='BSD',
-		]]></pypi-extra>
-	<rich-description xmlns="http://gfxmonk.net/dist/0install">
-		<div xmlns="http://www.w3.org/1999/xhtml">
-			<h1 id="rednose">rednose</h1>
-			<p>rednose is a <a href="http://somethingaboutorange.com/mrl/projects/nose/">nosetests</a> plugin for adding colour (and readability) to nosetest console results.</p>
-			<h2 id="installation">Installation:</h2>
-			<pre>
-				<code>easy_install rednose
-</code>
-			</pre>
-			<p>or from the source:</p>
-			<pre>
-				<code>./setup.py develop
-</code>
-			</pre>
-			<h2 id="usage">Usage:</h2>
-			<pre>
-				<code>nosetests --rednose
-</code>
-			</pre>
-			<p>or:</p>
-			<pre>
-				<code>export NOSE_REDNOSE=1
-nosetests
-</code>
-			</pre>
-			<p>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:</p>
-			<pre>
-				<code>nosetests --rednose --force-color
-nosetests --no-color
-</code>
-			</pre>
-			<p>(you can also control this by setting the environment variable NOSE_REDNOSE_COLOR to 'force' or 'no')</p>
-		</div>
-	</rich-description>
-	<group>
-		<environment name="NOSETESTS_PLUGINS" value="rednose/RedNose"/>
-		<environment name="NOSE_REDNOSE" value="1"/>
-		<environment insert="" name="PYTHONPATH"/>
-		<command name="run">
-			<runner interface="http://gfxmonk.net/dist/0install/nosetests-runner.xml"/>
-		</command>
-		<command name="core">
-			<runner interface="http://gfxmonk.net/dist/0install/nosetests-runner.xml" command="core"/>
-		</command>
-		<command name="test">
-			<runner interface="http://gfxmonk.net/dist/0install/nosetests-runner.xml" command="core"/>
-			<arg>-v</arg>
-		</command>
-		<requires interface="http://repo.roscidus.com/python/python"/>
-		<requires interface="http://gfxmonk.net/dist/0install/python-termstyle.xml">
-			<version not-before="0.1.7"/>
-		</requires>
-		<implementation version="{version}" released="{date}">
-			<manifest-digest/>
-			<archive href="{archive}"/>
-		</implementation>
-	</group>
-</interface>
diff --git a/sample_test.py b/sample_test.py
deleted file mode 100644
index a0f6308..0000000
--- a/sample_test.py
+++ /dev/null
@@ -1,30 +0,0 @@
-# vim: set fileencoding=utf-8 :
-from __future__ import print_function
-from __future__ import unicode_literals
-import unittest
-
-def delay_fail(f):
-	f() # fail it!
-
-class SomeTest(unittest.TestCase):
-	def test_fail(self):
-		print("oh noes, it's gonna blow!")
-		delay_fail(lambda: self.fail('no dice'))
-	
-	def test_success(self):
-		self.assertEqual(True, True)
-	
-	def test_error(self):
-		raise RuntimeError("things went south\nand here's a second line!")
-
-	def test_utf8(self):
-		self.assertEqual('café', 'abc')
-	
-	def test_skip(self):
-		import nose
-		raise nose.SkipTest
-	
-	def test_with_long_description(self):
-		"""It's got a long description, you see?"""
-		self.fail()
-
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..8bfd5a1
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,4 @@
+[egg_info]
+tag_build = 
+tag_date = 0
+
diff --git a/setup.py b/setup.py
index 34cded4..27aade0 100755
--- a/setup.py
+++ b/setup.py
@@ -1,29 +1,34 @@
-#!/usr/bin/env python
+from os import path
+from setuptools import setup, find_packages
 
-## NOTE: ##
-## this setup.py was generated by zero2pypi:
-## http://gfxmonk.net/dist/0install/zero2pypi.xml
 
-from setuptools import *
+def read(fname):
+    try:
+        return open(path.join(path.dirname(__file__), fname)).read()
+    except IOError:
+        return """A module for making nose pretty using colors."""
+
 setup(
-	packages = find_packages(exclude=['test', 'test.*']),
-	description='coloured output for nosetests',
-	entry_points={'nose.plugins.0.10': ['NOSETESTS_PLUGINS = rednose:RedNose']},
-	install_requires=['setuptools', 'python-termstyle >=0.1.7'],
-	long_description="\n**Note**: This package has been built automatically by\n`zero2pypi <http://gfxmonk.net/dist/0install/zero2pypi.xml>`_.\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",
-	name='rednose',
-	py_modules=['rednose'],
-	url='http://gfxmonk.net/dist/0install/rednose.xml',
-	version='0.4.1',
-classifiers=[
-		"License :: OSI Approved :: BSD License",
-		"Programming Language :: Python",
-		"Programming Language :: Python :: 3",
-		"Development Status :: 4 - Beta",
-		"Intended Audience :: Developers",
-		"Topic :: Software Development :: Libraries :: Python Modules",
-		"Topic :: Software Development :: Testing",
-	],
-	keywords='test nosetests nose nosetest output colour console',
-	license='BSD',
+    packages=find_packages(exclude=['test', 'test.*']),
+    description='coloured output for nosetests',
+    entry_points={'nose.plugins.0.10': ['NOSETESTS_PLUGINS = rednose:RedNose']},
+    install_requires=['setuptools', 'termstyle >=0.1.7', 'colorama'],
+    tests_require=['six==1.10.0'],
+    long_description=read('README.rst'),
+    name='rednose',
+    py_modules=['rednose'],
+    url='https://github.com/JBKahn/rednose',
+    version='1.3.0',
+    classifiers=[
+        "License :: OSI Approved :: MIT License",
+        "Programming Language :: Python",
+        "Programming Language :: Python :: 3",
+        "Development Status :: 4 - Beta",
+        "Intended Audience :: Developers",
+        "Topic :: Software Development :: Libraries :: Python Modules",
+        "Topic :: Software Development :: Testing",
+    ],
+    keywords='test nosetests nose nosetest output colour console',
+    license='MIT',
+    test_suite='test_files.new_tests',
 )
diff --git a/test.do b/test.do
deleted file mode 100644
index 12bdf43..0000000
--- a/test.do
+++ /dev/null
@@ -1,6 +0,0 @@
-exec >&2
-redo-ifchange rednose-local.xml
-0launch http://0install.net/2008/interfaces/0test.xml \
-	rednose-local.xml \
-	http://repo.roscidus.com/python/python \
-	2.6,2.8 3.0,4
diff --git a/test_files/__init__.py b/test_files/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/test_files/basic_test_suite.py b/test_files/basic_test_suite.py
new file mode 100644
index 0000000..33f3324
--- /dev/null
+++ b/test_files/basic_test_suite.py
@@ -0,0 +1,6 @@
+import unittest
+
+
+class TC(unittest.TestCase):
+    def runTest(self):  # noqa
+        raise ValueError("I hate fancy stuff")
diff --git a/test_files/class_test_failure.py b/test_files/class_test_failure.py
new file mode 100644
index 0000000..098b4b1
--- /dev/null
+++ b/test_files/class_test_failure.py
@@ -0,0 +1,34 @@
+from __future__ import print_function
+import unittest
+
+
+def setup_module():
+    raise unittest.SkipTest('RESI specific Nonius libs not present')
+    print(__name__, ': setup_module() ~~~~~~~~~~~~~~~~~~~~~~')
+
+
+def teardown_module():
+    print(__name__, ': teardown_module() ~~~~~~~~~~~~~~~~~~~')
+
+
+class SomeSkippedTest(unittest.TestCase):
+
+    @classmethod
+    def setup_class(cls):
+        print(__name__, ': TestClass.setup_class() ----------')
+
+    @classmethod
+    def teardown_class(cls):
+        print(__name__, ': TestClass.teardown_class() -------')
+
+    def setup(self):
+        print(__name__, ': TestClass.setup()  - - - - - - - -')
+
+    def teardown(self):
+        print(__name__, ': TestClass.teardown() - - - - - - -')
+
+    def test_method_1(self):
+        print(__name__, ': TestClass.test_method_1()')
+
+    def test_method_2(self):
+        print(__name__, ': TestClass.test_method_2()')
diff --git a/encoding_test.py b/test_files/encoding_test.py
similarity index 55%
rename from encoding_test.py
rename to test_files/encoding_test.py
index b9bde14..1674aaa 100644
--- a/encoding_test.py
+++ b/test_files/encoding_test.py
@@ -1,7 +1,8 @@
 # vim: fileencoding=utf-8:
 
-#NOTE: this file does *not* import unicode_literals,
+# NOTE: this file does *not* import unicode_literals,
 # so the assertion message is actually just utf-8 bytes
 
+
 def test():
-	assert False, "ä"
+    assert False, "ä"
diff --git a/test_files/encoding_test_with_literals.py b/test_files/encoding_test_with_literals.py
new file mode 100644
index 0000000..7d80cdc
--- /dev/null
+++ b/test_files/encoding_test_with_literals.py
@@ -0,0 +1,9 @@
+# vim: fileencoding=utf-8:
+from __future__ import unicode_literals
+
+import unittest
+
+
+class EncodingTest(unittest.TestCase):
+    def test_utf8(self):
+        self.assertEqual('café', 'abc')
diff --git a/test_files/new_tests.py b/test_files/new_tests.py
new file mode 100644
index 0000000..288b197
--- /dev/null
+++ b/test_files/new_tests.py
@@ -0,0 +1,324 @@
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+import unittest
+
+import nose
+from nose.plugins import PluginTester, testid
+from six import PY2, PY3
+
+from rednose import RedNose
+
+
+try:
+    from unittest import skip, skipUnless
+except ImportError:
+    def skip(f):
+        return lambda self: None
+
+    def skipUnless(condition, reason):  # noqa
+        if condition:
+            return lambda x: x
+        else:
+            return lambda x: None
+
+
+class TestRedNoseWithId(PluginTester, unittest.TestCase):
+    activate = '--rednose'
+    plugins = [RedNose(), testid.TestId()]
+    args = ['--force-color', '--with-id']
+    env = {}
+
+    def test_colored_result(self):
+        expected_lines = [
+            '\x1b[33mE\x1b[0m',
+            '\x1b[33m======================================================================\x1b[0m',
+            '\x1b[33m1) ERROR: runTest (test_files.basic_test_suite.TC)\x1b[0m',
+            '\x1b[33m----------------------------------------------------------------------\x1b[0m',
+            '\x1b[0m   Traceback (most recent call last):\x1b[0m',
+            '    \x1b[34mtest_files/basic_test_suite.py\x1b[0m line \x1b[1m\x1b[36m6\x1b[0m\x1b[0m in \x1b[36mrunTest\x1b[0m',
+            '      raise ValueError("I hate fancy stuff")',
+            '\x1b[33m   \x1b[33m\x1b[1m\x1b[33mValueError\x1b[0m\x1b[0m\x1b[33m: \x1b[0m\x1b[33mI hate fancy stuff\x1b[0m',
+            '',
+            '\x1b[30m-----------------------------------------------------------------------------\x1b[0m',
+            '1 test run in',
+            '\x1b[33m1 error\x1b[0m\x1b[32m (0 tests passed)\x1b[0m',
+            '',
+        ]
+        for expected_line, actual_line in zip(expected_lines, str(self.output).split("\n")):
+            if expected_line not in actual_line:
+                print(expected_line)
+                print(actual_line)
+                print(self.output)
+            self.assertTrue(expected_line in actual_line)
+
+    def makeSuite(self):  # noqa
+        from test_files.basic_test_suite import TC
+        return [TC('runTest')]
+
+
+class TestRedNose(PluginTester, unittest.TestCase):
+    activate = '--rednose'
+    plugins = [RedNose()]
+    args = ['--force-color']
+    env = {}
+
+    def test_colored_result(self):
+        expected_lines = [
+            '\x1b[33mE\x1b[0m',
+            '\x1b[33m======================================================================\x1b[0m',
+            '\x1b[33m1) ERROR: runTest (test_files.basic_test_suite.TC)\x1b[0m',
+            '\x1b[33m----------------------------------------------------------------------\x1b[0m',
+            '\x1b[0m   Traceback (most recent call last):\x1b[0m',
+            '    \x1b[34mtest_files/basic_test_suite.py\x1b[0m line \x1b[1m\x1b[36m6\x1b[0m\x1b[0m in \x1b[36mrunTest\x1b[0m',
+            '      raise ValueError("I hate fancy stuff")',
+            '\x1b[33m   \x1b[33m\x1b[1m\x1b[33mValueError\x1b[0m\x1b[0m\x1b[33m: \x1b[0m\x1b[33mI hate fancy stuff\x1b[0m',
+            '',
+            '\x1b[30m-----------------------------------------------------------------------------\x1b[0m',
+            '1 test run in',
+            '\x1b[33m1 error\x1b[0m\x1b[32m (0 tests passed)\x1b[0m',
+            '',
+        ]
+        for expected_line, actual_line in zip(expected_lines, str(self.output).split("\n")):
+            if expected_line not in actual_line:
+                print(expected_line)
+                print(actual_line)
+                print(self.output)
+            self.assertTrue(expected_line in actual_line)
+
+    def makeSuite(self):  # noqa
+        from test_files.basic_test_suite import TC
+        return [TC('runTest')]
+
+
+@skipUnless(sys.version_info >= (2, 7), "python 2.6 not supported")
+class TestRedNoseSkipInClass(PluginTester, unittest.TestCase):
+    activate = '--rednose'
+    plugins = [RedNose()]
+    args = ['--force-color']
+    env = {}
+    suitepath = os.path.join(os.getcwd(), 'test_files', 'class_test_failure.py')
+
+    def test_colored_result(self):
+        expected_lines = [
+            '\x1b[34m-\x1b[0m',
+            '\x1b[34m======================================================================\x1b[0m',
+            "\x1b[34m1) SKIP: test suite for <module 'test_files.class_test_failure' from '{0}/test_files/class_test_failure.py".format(os.getcwd()),
+            '\x1b[34m----------------------------------------------------------------------\x1b[0m',
+            '\x1b[0m   Traceback (most recent call last):\x1b[0m',
+            '    \x1b[34m{0}/suite.py\x1b[0m line \x1b[1m\x1b[36m'.format(nose.__path__[0]),
+            '      self.setUp()',
+            '    \x1b[34m{0}/suite.py\x1b[0m line \x1b[1m\x1b[36m'.format(nose.__path__[0]),
+            '      self.setupContext(ancestor)',
+            '    \x1b[34m{0}/suite.py\x1b[0m line \x1b[1m\x1b[36m'.format(nose.__path__[0]),
+            '      try_run(context, names)',
+            '    \x1b[34m{0}/util.py\x1b[0m line \x1b[1m\x1b[36m'.format(nose.__path__[0]),
+            '      return func()',
+            '    \x1b[34mtest_files/class_test_failure.py\x1b[0m line \x1b[1m\x1b[36m6\x1b[0m\x1b[0m in \x1b[36msetup_module\x1b[0m',
+            "      raise unittest.SkipTest('RESI specific Nonius libs not present')",
+            '\x1b[34m   \x1b[34m\x1b[1m\x1b[34mSkipTest\x1b[0m\x1b[0m\x1b[34m: \x1b[0m\x1b[34mRESI specific Nonius libs not present\x1b[0m',
+            '',
+            '\x1b[30m-----------------------------------------------------------------------------\x1b[0m',
+            '1 test run in ',
+            '\x1b[34m1 skipped\x1b[0m\x1b[32m (0 tests passed)\x1b[0m',
+            '',
+        ]
+        for expected_line, actual_line in zip(expected_lines, str(self.output).split("\n")):
+            if expected_line not in actual_line:
+                print(expected_line)
+                print(actual_line)
+                print(self.output)
+            self.assertTrue(expected_line in actual_line)
+
+
+@skipUnless(sys.version_info >= (2, 7), "python 2.6 not supported")
+class TestRedNoseSampleTests(PluginTester, unittest.TestCase):
+    activate = '--rednose'
+    plugins = [RedNose()]
+    args = ['--force-color']
+    env = {}
+    suitepath = os.path.join(os.getcwd(), 'test_files', 'sample_test.py')
+
+    def test_colored_result(self):
+        expected_lines = [
+            '\x1b[33mE\x1b[0m\x1b[31mF\x1b[0m\x1b[34m-\x1b[0m\x1b[34m-\x1b[0m\x1b[32m.\x1b[0m\x1b[31mF\x1b[0m',
+            '\x1b[33m======================================================================\x1b[0m',
+            '\x1b[33m1) ERROR: test_error (test_files.sample_test.SomeTest)\x1b[0m',
+            '\x1b[33m----------------------------------------------------------------------\x1b[0m',
+            '\x1b[0m   Traceback (most recent call last):\x1b[0m',
+            '    \x1b[34mtest_files/sample_test.py\x1b[0m line \x1b[1m\x1b[36m20\x1b[0m\x1b[0m in \x1b[36mtest_error\x1b[0m',
+            '      raise RuntimeError("things went south\\nand here\'s a second line!")',
+            '\x1b[33m   \x1b[33m\x1b[1m\x1b[33mRuntimeError\x1b[0m\x1b[0m\x1b[33m: \x1b[0m\x1b[33mthings went south\x1b[0m',
+            "\x1b[33m   and here's a second line!\x1b[0m",
+            '\x1b[31m======================================================================\x1b[0m',
+            '\x1b[31m2) FAIL: test_fail (test_files.sample_test.SomeTest)\x1b[0m',
+            '\x1b[31m----------------------------------------------------------------------\x1b[0m',
+            '\x1b[0m   Traceback (most recent call last):\x1b[0m',
+            '    \x1b[34mtest_files/sample_test.py\x1b[0m line \x1b[1m\x1b[36m14\x1b[0m\x1b[0m in \x1b[36mtest_fail\x1b[0m',
+            "      delay_fail(lambda: self.fail('no dice'))",
+            '    \x1b[34mtest_files/sample_test.py\x1b[0m line \x1b[1m\x1b[36m8\x1b[0m\x1b[0m in \x1b[36mdelay_fail\x1b[0m',
+            '      f()  # fail it!',
+            '    \x1b[34mtest_files/sample_test.py\x1b[0m line \x1b[1m\x1b[36m14\x1b[0m\x1b[0m in \x1b[36m<lambda>\x1b[0m',
+            "      delay_fail(lambda: self.fail('no dice'))",
+            '\x1b[31m   \x1b[31m\x1b[1m\x1b[31mAssertionError\x1b[0m\x1b[0m\x1b[31m: \x1b[0m\x1b[31mno dice\x1b[0m',
+            '\x1b[34m======================================================================\x1b[0m',
+            '\x1b[34m3) SKIP: test_skip (test_files.sample_test.SomeTest)\x1b[0m',
+            '\x1b[34m----------------------------------------------------------------------\x1b[0m',
+            '\x1b[0m   No Traceback\x1b[0m',
+            '',
+            '\x1b[34m======================================================================\x1b[0m',
+            '\x1b[34m4) SKIP: test_skip_with_reason (test_files.sample_test.SomeTest)\x1b[0m',
+            '\x1b[34m----------------------------------------------------------------------\x1b[0m',
+            '\x1b[0m   No Traceback\x1b[0m',
+            "\x1b[34m   \x1b[34m\x1b[1m\x1b[34mSkipTest\x1b[0m\x1b[0m\x1b[34m: \x1b[0m\x1b[34mLook at me, I'm skipping for a reason!!\x1b[0m",
+            '\x1b[31m======================================================================\x1b[0m',
+            "\x1b[31m5) FAIL: It's got a long description, you see?.\x1b[0m",
+            '\x1b[31m----------------------------------------------------------------------\x1b[0m',
+            '\x1b[0m   Traceback (most recent call last):\x1b[0m',
+            '    \x1b[34mtest_files/sample_test.py\x1b[0m line \x1b[1m\x1b[36m32\x1b[0m\x1b[0m in \x1b[36mtest_with_long_description\x1b[0m',
+            '      self.fail()', '\x1b[31m   \x1b[31m\x1b[1m\x1b[31mAssertionError\x1b[0m\x1b[0m\x1b[31m: \x1b[0m\x1b[31mNone\x1b[0m',
+            '',
+            "\x1b[34m6) SKIP: test suite for <class 'test_files.sample_test.TestBug'>\x1b[0m",
+            '\x1b[34m----------------------------------------------------------------------\x1b[0m',
+            '\x1b[0m   Traceback (most recent call last):\x1b[0m',
+            'nose/suite.py\x1b[0m line',
+            '      self.setUp()',
+            'nose/suite.py\x1b[0m line',
+            '      self.setupContext(ancestor)',
+            'nose/suite.py\x1b[0m line',
+            '      try_run(context, names)',
+            'nose/util.py\x1b[0m line',
+            '      return func()',
+            '    \x1b[34mtest_files/sample_test.py\x1b[0m line \x1b[1m\x1b[36m39\x1b[0m\x1b[0m in \x1b[36msetUpClass\x1b[0m',
+            '      raise nose.SkipTest("SKIPPING!")',
+            '\x1b[34m   \x1b[34m\x1b[1m\x1b[34mSkipTest\x1b[0m\x1b[0m\x1b[34m: \x1b[0m\x1b[34mSKIPPING!\x1b[0m',
+            '',
+            '\x1b[30m-----------------------------------------------------------------------------\x1b[0m',
+            '7 tests run in ',
+            '\x1b[31m2 FAILED\x1b[0m, \x1b[33m1 error\x1b[0m, \x1b[34m3 skipped\x1b[0m\x1b[32m (1 test passed)\x1b[0m',
+            ''
+        ]
+        if PY2:
+            import sys
+            if sys.version_info[1] == 6:
+                expected_lines = expected_lines[:8] + expected_lines[10:]
+
+        for expected_line, actual_line in zip(expected_lines, str(self.output).split("\n")):
+            if expected_line not in actual_line:
+                print(expected_line)
+                print(actual_line)
+                print(self.output)
+            self.assertTrue(expected_line in actual_line)
+
+
+class TestRedNoseEncoding(PluginTester, unittest.TestCase):
+    activate = '--rednose'
+    plugins = [RedNose()]
+    args = ['--force-color']
+    env = {}
+    suitepath = os.path.join(os.getcwd(), 'test_files', 'encoding_test.py')
+
+    def setUp(self):
+        import sys
+        self.old_encoding = sys.getdefaultencoding()
+        if PY2:
+            reload(sys)
+            sys.setdefaultencoding('utf8')
+        super(TestRedNoseEncoding, self).setUp()
+
+    def tearDown(self):
+        import sys
+        if PY2:
+            reload(sys)
+            sys.setdefaultencoding(self.old_encoding)
+        super(TestRedNoseEncoding, self).tearDown()
+
+    def test_colored_result(self):
+        expected_lines = [
+            '\x1b[31mF\x1b[0m',
+            '\x1b[31m======================================================================\x1b[0m',
+            '\x1b[31m1) FAIL: test_files.encoding_test.test\x1b[0m',
+            '\x1b[31m----------------------------------------------------------------------\x1b[0m',
+            '\x1b[0m   Traceback (most recent call last):\x1b[0m',
+            '    \x1b[34m{0}/case.py\x1b[0m line \x1b[1m\x1b[36m'.format(nose.__path__[0]),
+            '      self.test(*self.arg)',
+            '    \x1b[34mtest_files/encoding_test.py\x1b[0m line \x1b[1m\x1b[36m8\x1b[0m\x1b[0m in \x1b[36mtest\x1b[0m',
+            '      assert False, "\xc3\xa4"',
+            '\x1b[31m   \x1b[31m\x1b[1m\x1b[31mAssertionError\x1b[0m\x1b[0m\x1b[31m: \x1b[0m\x1b[31m\xc3\xa4\x1b[0m',
+            '',
+            '\x1b[30m-----------------------------------------------------------------------------\x1b[0m',
+            '1 test run in ',
+            '\x1b[31m1 FAILED\x1b[0m\x1b[32m (0 tests passed)\x1b[0m',
+            ''
+        ]
+
+        if PY3:
+            pass
+            expected_lines[8] = '      assert False, "ä"'
+            expected_lines[9] = '\x1b[31m   \x1b[31m\x1b[1m\x1b[31mAssertionError\x1b[0m\x1b[0m\x1b[31m: \x1b[0m\x1b[31mä\x1b[0m'
+
+        for expected_line, actual_line in zip(expected_lines, str(self.output).split("\n")):
+            if expected_line not in actual_line:
+                print(expected_line)
+                print(actual_line)
+                print(self.output)
+            self.assertTrue(expected_line in actual_line)
+
+
+class TestRedNoseEncodingWithLiterals(PluginTester, unittest.TestCase):
+    activate = '--rednose'
+    plugins = [RedNose()]
+    args = ['--force-color']
+    env = {}
+    suitepath = os.path.join(os.getcwd(), 'test_files', 'encoding_test_with_literals.py')
+
+    def setUp(self):
+        import sys
+        self.old_encoding = sys.getdefaultencoding()
+        if PY2:
+            reload(sys)
+            sys.setdefaultencoding('utf8')
+        super(TestRedNoseEncodingWithLiterals, self).setUp()
+
+    def tearDown(self):
+        import sys
+        if PY2:
+            reload(sys)
+            sys.setdefaultencoding(self.old_encoding)
+        super(TestRedNoseEncodingWithLiterals, self).tearDown()
+
+    def test_colored_result(self):
+        expected_lines = [
+            '\x1b[31mF\x1b[0m',
+            '\x1b[31m======================================================================\x1b[0m',
+            '\x1b[31m1) FAIL: test_utf8 (test_files.encoding_test_with_literals.EncodingTest)\x1b[0m',
+            '\x1b[31m----------------------------------------------------------------------\x1b[0m',
+            '\x1b[0m   Traceback (most recent call last):\x1b[0m',
+            '    \x1b[34mtest_files/encoding_test_with_literals.py\x1b[0m line \x1b[1m\x1b[36m9\x1b[0m\x1b[0m in \x1b[36mtest_utf8\x1b[0m',
+            "      self.assertEqual('caf\xc3\xa9', 'abc')",
+            "\x1b[31m   \x1b[31m\x1b[1m\x1b[31mAssertionError\x1b[0m\x1b[0m\x1b[31m: \x1b[0m\x1b[31mu'caf\\xe9' != u'abc'\x1b[0m",
+            '\x1b[31m   - caf\xc3\xa9\x1b[0m',
+            '\x1b[31m   + abc\x1b[0m',
+            '',
+            '\x1b[30m-----------------------------------------------------------------------------\x1b[0m',
+            '1 test run in ',
+            '\x1b[31m1 FAILED\x1b[0m\x1b[32m (0 tests passed)\x1b[0m',
+            ''
+        ]
+
+        if PY3:
+            expected_lines[6] = "      self.assertEqual('café', 'abc')"
+            expected_lines[7] = "\x1b[31m   \x1b[31m\x1b[1m\x1b[31mAssertionError\x1b[0m\x1b[0m\x1b[31m: \x1b[0m\x1b[31m'café' != 'abc'\x1b[0m"
+            expected_lines[8] = "\x1b[31m   - café\x1b[0m"
+        elif PY2:
+            import sys
+            if sys.version_info[1] == 6:
+                expected_lines = expected_lines[:8] + expected_lines[10:]
+
+        for expected_line, actual_line in zip(expected_lines, str(self.output).split("\n")):
+            if expected_line not in actual_line:
+                print(expected_line)
+                print(actual_line)
+                print(self.output)
+            self.assertTrue(expected_line in actual_line)
diff --git a/test_files/sample_test.py b/test_files/sample_test.py
new file mode 100644
index 0000000..dbeda24
--- /dev/null
+++ b/test_files/sample_test.py
@@ -0,0 +1,42 @@
+# vim: set fileencoding=utf-8 :
+from __future__ import print_function
+from __future__ import unicode_literals
+import unittest
+
+
+def delay_fail(f):
+    f()  # fail it!
+
+
+class SomeTest(unittest.TestCase):
+    def test_fail(self):
+        print("oh noes, it's gonna blow!")
+        delay_fail(lambda: self.fail('no dice'))
+
+    def test_success(self):
+        self.assertEqual(True, True)
+
+    def test_error(self):
+        raise RuntimeError("things went south\nand here's a second line!")
+
+    def test_skip(self):
+        import nose
+        raise nose.SkipTest
+
+    def test_skip_with_reason(self):
+        import nose
+        raise nose.SkipTest("Look at me, I'm skipping for a reason!!")
+
+    def test_with_long_description(self):
+        """It's got a long description, you see?."""
+        self.fail()
+
+
+class TestBug(unittest.TestCase):
+    @classmethod
+    def setUpClass(cls):
+        import nose
+        raise nose.SkipTest("SKIPPING!")
+
+    def test_bug(self):
+        pass

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/rednose-1.3.0.egg-info/PKG-INFO
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/rednose-1.3.0.egg-info/dependency_links.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/rednose-1.3.0.egg-info/entry_points.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/rednose-1.3.0.egg-info/requires.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/rednose-1.3.0.egg-info/top_level.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/test_files/__init__.py
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/test_files/basic_test_suite.py
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/test_files/class_test_failure.py
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/test_files/encoding_test.py
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/test_files/encoding_test_with_literals.py
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/test_files/new_tests.py
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/test_files/sample_test.py

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/rednose-0.4.1.egg-info/PKG-INFO
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/rednose-0.4.1.egg-info/dependency_links.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/rednose-0.4.1.egg-info/entry_points.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/rednose-0.4.1.egg-info/requires.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/rednose-0.4.1.egg-info/top_level.txt

Control files: lines which differ (wdiff format)

  • Depends: python3-nose, python3-colorama, python3-pkg-resources, python3-termstyle, python3:any

More details

Full run details