diff --git a/lib/subunit/INSTALL b/lib/subunit/INSTALL index 2a053d8ad6d..eeea734f6a9 100644 --- a/lib/subunit/INSTALL +++ b/lib/subunit/INSTALL @@ -14,7 +14,7 @@ Dependencies * Python for the filters * 'testtools' (On Debian and Ubuntu systems the 'python-testtools' package, the testtools package on pypi, or https://launchpad.net/testtools) for - the extended test API which permits attachments. Version 0.9.11 or newer is + the extended test API which permits attachments. Version 0.9.23 or newer is required. Of particular note, http://testtools.python-hosting.com/ is not the testtools you want. * A C compiler for the C bindings diff --git a/lib/subunit/MANIFEST.in b/lib/subunit/MANIFEST.in index 7c449cf7e7f..eb989816283 100644 --- a/lib/subunit/MANIFEST.in +++ b/lib/subunit/MANIFEST.in @@ -16,6 +16,5 @@ exclude missing prune perl exclude py-compile prune shell -prune python/iso8601 exclude stamp-h1 include NEWS diff --git a/lib/subunit/Makefile.am b/lib/subunit/Makefile.am index 310c0426ae5..da1602037ed 100644 --- a/lib/subunit/Makefile.am +++ b/lib/subunit/Makefile.am @@ -35,6 +35,7 @@ EXTRA_DIST = \ python/subunit/tests/test_test_protocol.py \ python/subunit/tests/test_test_results.py \ runtests.py \ + setup.py \ shell/README \ shell/share/subunit.sh \ shell/subunit-ui.patch \ @@ -48,8 +49,10 @@ include_subunitdir = $(includedir)/subunit dist_bin_SCRIPTS = \ filters/subunit-filter \ filters/subunit-ls \ + filters/subunit-notify \ filters/subunit-stats \ filters/subunit-tags \ + filters/subunit2csv \ filters/subunit2gtk \ filters/subunit2junitxml \ filters/subunit2pyunit \ diff --git a/lib/subunit/NEWS b/lib/subunit/NEWS index f28ec5a6df9..081dc5dbfcc 100644 --- a/lib/subunit/NEWS +++ b/lib/subunit/NEWS @@ -5,12 +5,25 @@ subunit release notes NEXT (In development) --------------------- +0.0.9 +----- + BUG FIXES ~~~~~~~~~ +* All the source files are now included in the distribution tarball. + (Arfrever Frehtes Taifersar Arahesis, Robert Collins, #996275) + * ``python/subunit/tests/test_run.py`` and ``python/subunit/filters.py`` were not included in the 0.0.8 tarball. (Robert Collins) +* Test ids which include non-ascii unicode characters are now supported. + (Robert Collins, #1029866) + +* The ``failfast`` option to ``subunit.run`` will now work. The dependency on + testtools has been raised to 0.9.23 to permit this. + (Robert Collins, #1090582) + 0.0.8 ----- diff --git a/lib/subunit/README b/lib/subunit/README index 4818a057bff..103a32cdf9b 100644 --- a/lib/subunit/README +++ b/lib/subunit/README @@ -43,6 +43,7 @@ A number of useful things can be done easily with subunit: Subunit supplies the following filters: * tap2subunit - convert perl's TestAnythingProtocol to subunit. + * subunit2csv - convert a subunit stream to csv. * subunit2pyunit - convert a subunit stream to pyunit test results. * subunit2gtk - show a subunit stream in GTK. * subunit2junitxml - convert a subunit stream to JUnit's XML format. @@ -214,3 +215,15 @@ to indicate a test that errored in some expected fashion (also know as "TODO" tests in some frameworks). uxsuccess is used to indicate and unexpected success where a test though to be failing actually passes. It is complementary to xfail. + +Hacking on subunit +------------------ + +Releases +======== + +* Update versions in configure.ac and python/subunit/__init__.py. +* Make PyPI and regular tarball releases. Upload the regular one to LP, the + PyPI one to PyPI. +* Push a tagged commit. + diff --git a/lib/subunit/configure.ac b/lib/subunit/configure.ac index 223b3c9b499..cf21d554808 100644 --- a/lib/subunit/configure.ac +++ b/lib/subunit/configure.ac @@ -1,6 +1,6 @@ m4_define([SUBUNIT_MAJOR_VERSION], [0]) m4_define([SUBUNIT_MINOR_VERSION], [0]) -m4_define([SUBUNIT_MICRO_VERSION], [8]) +m4_define([SUBUNIT_MICRO_VERSION], [9]) m4_define([SUBUNIT_VERSION], m4_defn([SUBUNIT_MAJOR_VERSION]).m4_defn([SUBUNIT_MINOR_VERSION]).m4_defn([SUBUNIT_MICRO_VERSION])) AC_PREREQ([2.59]) diff --git a/lib/subunit/python/subunit/__init__.py b/lib/subunit/python/subunit/__init__.py index 6015c0e68ca..42dcf297e4b 100644 --- a/lib/subunit/python/subunit/__init__.py +++ b/lib/subunit/python/subunit/__init__.py @@ -147,6 +147,19 @@ from testtools import testresult from subunit import chunked, details, iso8601, test_results +# same format as sys.version_info: "A tuple containing the five components of +# the version number: major, minor, micro, releaselevel, and serial. All +# values except releaselevel are integers; the release level is 'alpha', +# 'beta', 'candidate', or 'final'. The version_info value corresponding to the +# Python version 2.0 is (2, 0, 0, 'final', 0)." Additionally we use a +# releaselevel of 'dev' for unreleased under-development code. +# +# If the releaselevel is 'alpha' then the major/minor/micro components are not +# established at this point, and setup.py will use a version of next-$(revno). +# If the releaselevel is 'final', then the tarball will be major.minor.micro. +# Otherwise it is major.minor.micro~$(revno). + +__version__ = (0, 0, 9, 'final', 0) PROGRESS_SET = 0 PROGRESS_CUR = 1 @@ -636,6 +649,8 @@ class TestProtocolClient(testresult.TestResult): to subunit.Content objects. """ self._addOutcome("error", test, error=error, details=details) + if self.failfast: + self.stop() def addExpectedFailure(self, test, error=None, details=None): """Report an expected failure in test test. @@ -666,6 +681,8 @@ class TestProtocolClient(testresult.TestResult): to subunit.Content objects. """ self._addOutcome("failure", test, error=error, details=details) + if self.failfast: + self.stop() def _addOutcome(self, outcome, test, error=None, details=None, error_permitted=True): @@ -685,7 +702,7 @@ class TestProtocolClient(testresult.TestResult): :param error_permitted: If True then one and only one of error or details must be supplied. If False then error must not be supplied and details is still optional. """ - self._stream.write(_b("%s: %s" % (outcome, test.id()))) + self._stream.write(_b("%s: " % outcome) + self._test_id(test)) if error_permitted: if error is None and details is None: raise ValueError @@ -730,11 +747,19 @@ class TestProtocolClient(testresult.TestResult): """ self._addOutcome("uxsuccess", test, details=details, error_permitted=False) + if self.failfast: + self.stop() + + def _test_id(self, test): + result = test.id() + if type(result) is not bytes: + result = result.encode('utf8') + return result def startTest(self, test): """Mark a test as starting its test run.""" super(TestProtocolClient, self).startTest(test) - self._stream.write(_b("test: %s\n" % test.id())) + self._stream.write(_b("test: ") + self._test_id(test) + _b("\n")) self._stream.flush() def stopTest(self, test): diff --git a/lib/subunit/python/subunit/run.py b/lib/subunit/python/subunit/run.py index ca5fe5c17e0..b5ccea449d1 100755 --- a/lib/subunit/python/subunit/run.py +++ b/lib/subunit/python/subunit/run.py @@ -34,13 +34,22 @@ from testtools.run import ( class SubunitTestRunner(object): - def __init__(self, stream=sys.stdout): - self.stream = stream + def __init__(self, verbosity=None, failfast=None, buffer=None, stream=None): + """Create a TestToolsTestRunner. + + :param verbosity: Ignored. + :param failfast: Stop running tests at the first failure. + :param buffer: Ignored. + """ + self.failfast = failfast + self.stream = stream or sys.stdout def run(self, test): "Run the given test case or test suite." result = TestProtocolClient(self.stream) result = AutoTimingTestResultDecorator(result) + if self.failfast is not None: + result.failfast = self.failfast test(result) return result @@ -70,6 +79,6 @@ class SubunitTestProgram(TestProgram): if __name__ == '__main__': stream = get_default_formatter() - runner = SubunitTestRunner(stream) + runner = SubunitTestRunner SubunitTestProgram(module=None, argv=sys.argv, testRunner=runner, stdout=sys.stdout) diff --git a/lib/subunit/python/subunit/test_results.py b/lib/subunit/python/subunit/test_results.py index c00a2d3e970..91c9bbdc1e4 100644 --- a/lib/subunit/python/subunit/test_results.py +++ b/lib/subunit/python/subunit/test_results.py @@ -78,6 +78,13 @@ class TestResultDecorator(object): def addUnexpectedSuccess(self, test, details=None): return self.decorated.addUnexpectedSuccess(test, details=details) + def _get_failfast(self): + return getattr(self.decorated, 'failfast', False) + + def _set_failfast(self, value): + self.decorated.failfast = value + failfast = property(_get_failfast, _set_failfast) + def progress(self, offset, whence): return self.decorated.progress(offset, whence) diff --git a/lib/subunit/python/subunit/tests/test_test_protocol.py b/lib/subunit/python/subunit/tests/test_test_protocol.py index ec6830d03b8..7831ba16cdf 100644 --- a/lib/subunit/python/subunit/tests/test_test_protocol.py +++ b/lib/subunit/python/subunit/tests/test_test_protocol.py @@ -18,7 +18,7 @@ import datetime import unittest import os -from testtools import skipIf, TestCase, TestResult +from testtools import PlaceHolder, skipIf, TestCase, TestResult from testtools.compat import _b, _u, BytesIO from testtools.content import Content, TracebackContent, text_content from testtools.content_type import ContentType @@ -1133,6 +1133,7 @@ class TestTestProtocolClient(unittest.TestCase): def setUp(self): self.io = BytesIO() self.protocol = subunit.TestProtocolClient(self.io) + self.unicode_test = PlaceHolder(_u('\u2603')) self.test = TestTestProtocolClient("test_start_test") self.sample_details = {'something':Content( ContentType('text', 'plain'), lambda:[_b('serialised\nform')])} @@ -1145,6 +1146,12 @@ class TestTestProtocolClient(unittest.TestCase): self.protocol.startTest(self.test) self.assertEqual(self.io.getvalue(), _b("test: %s\n" % self.test.id())) + def test_start_test_unicode_id(self): + """Test startTest on a TestProtocolClient.""" + self.protocol.startTest(self.unicode_test) + expected = _b("test: ") + _u('\u2603').encode('utf8') + _b("\n") + self.assertEqual(expected, self.io.getvalue()) + def test_stop_test(self): # stopTest doesn't output anything. self.protocol.stopTest(self.test) @@ -1156,6 +1163,12 @@ class TestTestProtocolClient(unittest.TestCase): self.assertEqual( self.io.getvalue(), _b("successful: %s\n" % self.test.id())) + def test_add_outcome_unicode_id(self): + """Test addSuccess on a TestProtocolClient.""" + self.protocol.addSuccess(self.unicode_test) + expected = _b("successful: ") + _u('\u2603').encode('utf8') + _b("\n") + self.assertEqual(expected, self.io.getvalue()) + def test_add_success_details(self): """Test addSuccess on a TestProtocolClient with details.""" self.protocol.addSuccess(self.test, details=self.sample_details) diff --git a/lib/subunit/python/subunit/tests/test_test_results.py b/lib/subunit/python/subunit/tests/test_test_results.py index 236dfa22e51..ff74b9a818f 100644 --- a/lib/subunit/python/subunit/tests/test_test_results.py +++ b/lib/subunit/python/subunit/tests/test_test_results.py @@ -61,6 +61,7 @@ class TimeCapturingResult(unittest.TestResult): def __init__(self): super(TimeCapturingResult, self).__init__() self._calls = [] + self.failfast = False def time(self, a_datetime): self._calls.append(a_datetime) @@ -198,6 +199,11 @@ class TestAutoTimingTestResultDecorator(unittest.TestCase): self.assertEqual(3, len(self.decorated._calls)) self.assertNotEqual(None, self.decorated._calls[2]) + def test_set_failfast_True(self): + self.assertFalse(self.decorated.failfast) + self.result.failfast = True + self.assertTrue(self.decorated.failfast) + class TestTagCollapsingDecorator(TestCase): diff --git a/lib/subunit/runtests.py b/lib/subunit/runtests.py index 691e3b34da8..8ecc6cd3fb4 100755 --- a/lib/subunit/runtests.py +++ b/lib/subunit/runtests.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python # -*- Mode: python -*- # # Copyright (C) 2004 Canonical.com diff --git a/lib/subunit/setup.py b/lib/subunit/setup.py index a78eb999d7c..1a0b192b1b6 100755 --- a/lib/subunit/setup.py +++ b/lib/subunit/setup.py @@ -9,7 +9,7 @@ except ImportError: else: extra = { 'install_requires': [ - 'testtools>=0.9.11', + 'testtools>=0.9.23', ] } @@ -38,6 +38,7 @@ setup( long_description=open('README').read(), classifiers=[ 'Intended Audience :: Developers', + 'Programming Language :: Python :: 3', 'Programming Language :: Python', 'Topic :: Software Development :: Testing', ], @@ -45,7 +46,7 @@ setup( author='Robert Collins', author_email='subunit-dev@lists.launchpad.net', url='http://launchpad.net/subunit', - packages=['subunit'], + packages=['subunit', 'subunit.tests'], package_dir={'subunit': 'python/subunit'}, scripts = [ 'filters/subunit2gtk',