diff options
32 files changed, 218 insertions, 249 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bb8cbae..032fb33 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,8 +10,8 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9, 'pypy3'] - os: ["ubuntu-latest"] + python-version: ['3.6', '3.7', '3.8', '3.9', '3.10', 'pypy3'] + os: ['ubuntu-latest'] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} @@ -19,7 +19,7 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install Deps - run: sudo apt-get install check libcppunit-dev + run: sudo apt-get install check libcppunit-dev - name: Install package run: python -m pip install -U '.[test,docs]' - name: Build @@ -5,6 +5,24 @@ subunit release notes NEXT (In development) --------------------- +IMPROVEMENTS +~~~~~~~~~~~~ + +* Add support for Python 3.9 + (Thomas Grainger) + +* Add support for Python 3.10 + (Stephen Finucane) + +* Drop support for Python 2.7, 3.4, and 3.5 + (Stephen Finucane) + +BUGFIXES +~~~~~~~~ + +* Fix tests with testtools >= 2.5.0. + (Colin Watson) + 1.4.0 ----- diff --git a/configure.ac b/configure.ac index a06bd48..b5a59ee 100644 --- a/configure.ac +++ b/configure.ac @@ -24,7 +24,7 @@ AM_PROG_CC_C_O AC_PROG_INSTALL AC_PROG_LN_S AC_PROG_LIBTOOL -AM_PATH_PYTHON +AM_PATH_PYTHON([3.6]) AS_IF([test "$GCC" = "yes"], [ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d21b989 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,5 @@ +[build-system] +# These are the assumed default build requirements from pip: +# https://pip.pypa.io/en/stable/reference/pip/#pep-517-and-518-support +requires = ["setuptools>=43.0.0"] +build-backend = "setuptools.build_meta" diff --git a/python/subunit/__init__.py b/python/subunit/__init__.py index cf4692a..9d8bc7c 100644 --- a/python/subunit/__init__.py +++ b/python/subunit/__init__.py @@ -42,8 +42,7 @@ Twisted. See the ``TestProtocolServer`` parser class for more details. Subunit includes extensions to the Python ``TestResult`` protocol. These are all done in a compatible manner: ``TestResult`` objects that do not implement the extension methods will not cause errors to be raised, instead the extension -will either lose fidelity (for instance, folding expected failures to success -in Python versions < 2.7 or 3.1), or discard the extended data (for extra +will either lose fidelity, or discard the extended data (for extra details, tags, timestamping and progress markers). The test outcome methods ``addSuccess``, ``addError``, ``addExpectedFailure``, @@ -116,25 +115,20 @@ Utility modules * subunit.test_results contains TestResult helper classes. """ +from io import BytesIO +from io import StringIO +from io import UnsupportedOperation as _UnsupportedOperation import os import re import subprocess import sys import unittest -try: - from io import UnsupportedOperation as _UnsupportedOperation -except ImportError: - _UnsupportedOperation = AttributeError from extras import safe_hasattr from testtools import content, content_type, ExtendedToOriginalDecorator from testtools.content import TracebackContent from testtools.compat import _b, _u try: - from testtools.compat import BytesIO, StringIO -except ImportError: - from io import BytesIO, StringIO -try: from testtools.testresult.real import _StringException RemoteException = _StringException except ImportError: @@ -513,9 +507,7 @@ class TestProtocolServer(object): """ self.client = ExtendedToOriginalDecorator(client) if stream is None: - stream = sys.stdout - if sys.version_info > (3, 0): - stream = stream.buffer + stream = sys.stdout.buffer self._stream = stream self._forward_stream = forward_stream or DiscardStream() # state objects we can switch too @@ -821,7 +813,7 @@ class TestProtocolClient(testresult.TestResult): if parameters: self._stream.write(_b(";")) param_strs = [] - for param, value in parameters.items(): + for param, value in sorted(parameters.items()): param_strs.append("%s=%s" % (param, value)) self._stream.write(_b(",".join(param_strs))) self._stream.write(_b("\n%s\n" % name)) @@ -1292,11 +1284,7 @@ def _make_binary_on_windows(fileno): def _unwrap_text(stream): """Unwrap stream if it is a text stream to get the original buffer.""" exceptions = (_UnsupportedOperation, IOError) - if sys.version_info > (3, 0): - unicode_type = str - else: - unicode_type = unicode - exceptions += (ValueError,) + unicode_type = str try: # Read streams if type(stream.read(0)) is unicode_type: diff --git a/python/subunit/details.py b/python/subunit/details.py index 5105580..f231cf6 100644 --- a/python/subunit/details.py +++ b/python/subunit/details.py @@ -6,7 +6,7 @@ # license at the users choice. A copy of both licenses are available in the # project source as Apache-2.0 and BSD. You may not use this file except in # compliance with one of these two licences. -# +# # Unless required by applicable law or agreed to in writing, software # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -16,12 +16,10 @@ """Handlers for outcome details.""" +from io import BytesIO, StringIO + from testtools import content, content_type from testtools.compat import _b -try: - from testtools.compat import BytesIO, StringIO -except ImportError: - from io import BytesIO, StringIO from subunit import chunked diff --git a/python/subunit/filter_scripts/subunit2csv.py b/python/subunit/filter_scripts/subunit2csv.py index 0a8a0de..c8e48a5 100755 --- a/python/subunit/filter_scripts/subunit2csv.py +++ b/python/subunit/filter_scripts/subunit2csv.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # subunit: extensions to python unittest to get test results from subprocesses. # Copyright (C) 2009 Robert Collins <robertc@robertcollins.net> # diff --git a/python/subunit/filter_scripts/subunit2disk.py b/python/subunit/filter_scripts/subunit2disk.py index fea2f0e..da1c09c 100755 --- a/python/subunit/filter_scripts/subunit2disk.py +++ b/python/subunit/filter_scripts/subunit2disk.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # subunit: extensions to python unittest to get test results from subprocesses. # Copyright (C) 2013 Subunit Contributors # diff --git a/python/subunit/filter_scripts/subunit2gtk.py b/python/subunit/filter_scripts/subunit2gtk.py index 110f2f0..bcfd1e7 100755 --- a/python/subunit/filter_scripts/subunit2gtk.py +++ b/python/subunit/filter_scripts/subunit2gtk.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # subunit: extensions to python unittest to get test results from subprocesses. # Copyright (C) 2009 Robert Collins <robertc@robertcollins.net> # @@ -174,26 +174,15 @@ class GTKTestResult(unittest.TestResult): GObject.idle_add(self.update_counts) def addSkip(self, test, reason): - # addSkip is new in Python 2.7/3.1 - addSkip = getattr(super(GTKTestResult, self), 'addSkip', None) - if callable(addSkip): - addSkip(test, reason) + super(GTKTestResult, self).addSkip(test, reason) GObject.idle_add(self.update_counts) def addExpectedFailure(self, test, err): - # addExpectedFailure is new in Python 2.7/3.1 - addExpectedFailure = getattr(super(GTKTestResult, self), - 'addExpectedFailure', None) - if callable(addExpectedFailure): - addExpectedFailure(test, err) + super(GTKTestResult, self).addExpectedFailure(test, err) GObject.idle_add(self.update_counts) def addUnexpectedSuccess(self, test): - # addUnexpectedSuccess is new in Python 2.7/3.1 - addUnexpectedSuccess = getattr(super(GTKTestResult, self), - 'addUnexpectedSuccess', None) - if callable(addUnexpectedSuccess): - addUnexpectedSuccess(test) + super(GTKTestResult, self).addUnexpectedSuccess(test) GObject.idle_add(self.update_counts) def progress(self, offset, whence): diff --git a/python/subunit/filter_scripts/subunit2junitxml.py b/python/subunit/filter_scripts/subunit2junitxml.py index 89167b5..db85040 100755 --- a/python/subunit/filter_scripts/subunit2junitxml.py +++ b/python/subunit/filter_scripts/subunit2junitxml.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # subunit: extensions to python unittest to get test results from subprocesses. # Copyright (C) 2009 Robert Collins <robertc@robertcollins.net> # diff --git a/python/subunit/filter_scripts/subunit_1to2.py b/python/subunit/filter_scripts/subunit_1to2.py index d59447b..085c0fe 100755 --- a/python/subunit/filter_scripts/subunit_1to2.py +++ b/python/subunit/filter_scripts/subunit_1to2.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # subunit: extensions to python unittest to get test results from subprocesses. # Copyright (C) 2013 Robert Collins <robertc@robertcollins.net> # diff --git a/python/subunit/filter_scripts/subunit_2to1.py b/python/subunit/filter_scripts/subunit_2to1.py index ed9f26e..1f2716a 100755 --- a/python/subunit/filter_scripts/subunit_2to1.py +++ b/python/subunit/filter_scripts/subunit_2to1.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # subunit: extensions to python unittest to get test results from subprocesses. # Copyright (C) 2013 Robert Collins <robertc@robertcollins.net> # diff --git a/python/subunit/filter_scripts/subunit_filter.py b/python/subunit/filter_scripts/subunit_filter.py index 0f45b88..951672f 100755 --- a/python/subunit/filter_scripts/subunit_filter.py +++ b/python/subunit/filter_scripts/subunit_filter.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # subunit: extensions to python unittest to get test results from subprocesses. # Copyright (C) 200-2013 Robert Collins <robertc@robertcollins.net> # (C) 2009 Martin Pool diff --git a/python/subunit/filter_scripts/subunit_notify.py b/python/subunit/filter_scripts/subunit_notify.py index af04327..44ca758 100755 --- a/python/subunit/filter_scripts/subunit_notify.py +++ b/python/subunit/filter_scripts/subunit_notify.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # subunit: extensions to python unittest to get test results from subprocesses. # Copyright (C) 2010 Jelmer Vernooij <jelmer@samba.org> # diff --git a/python/subunit/filter_scripts/subunit_output.py b/python/subunit/filter_scripts/subunit_output.py index d377092..a587e87 100755 --- a/python/subunit/filter_scripts/subunit_output.py +++ b/python/subunit/filter_scripts/subunit_output.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # subunit: extensions to python unittest to get test results from subprocesses. # Copyright (C) 2013 Subunit Contributors # diff --git a/python/subunit/filter_scripts/subunit_stats.py b/python/subunit/filter_scripts/subunit_stats.py index a152250..53814d1 100755 --- a/python/subunit/filter_scripts/subunit_stats.py +++ b/python/subunit/filter_scripts/subunit_stats.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # subunit: extensions to python unittest to get test results from subprocesses. # Copyright (C) 2009 Robert Collins <robertc@robertcollins.net> # diff --git a/python/subunit/filter_scripts/subunit_tags.py b/python/subunit/filter_scripts/subunit_tags.py index e2b40d6..31b45cc 100755 --- a/python/subunit/filter_scripts/subunit_tags.py +++ b/python/subunit/filter_scripts/subunit_tags.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # subunit: extensions to python unittest to get test results from subprocesses. # Copyright (C) 2009 Robert Collins <robertc@robertcollins.net> # diff --git a/python/subunit/filter_scripts/tap2subunit.py b/python/subunit/filter_scripts/tap2subunit.py index 4263335..d39e8ce 100755 --- a/python/subunit/filter_scripts/tap2subunit.py +++ b/python/subunit/filter_scripts/tap2subunit.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # subunit: extensions to python unittest to get test results from subprocesses. # Copyright (C) 2009 Robert Collins <robertc@robertcollins.net> # diff --git a/python/subunit/iso8601.py b/python/subunit/iso8601.py index 07855d0..7d392d4 100644 --- a/python/subunit/iso8601.py +++ b/python/subunit/iso8601.py @@ -1,5 +1,5 @@ # Copyright (c) 2007 Michael Twomey -# +# # 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 @@ -7,10 +7,10 @@ # 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. @@ -31,7 +31,6 @@ datetime.datetime(2007, 1, 25, 12, 0, tzinfo=<iso8601.iso8601.Utc ...>) from datetime import datetime, timedelta, tzinfo import re -import sys __all__ = ["parse_date", "ParseError"] @@ -47,10 +46,6 @@ TIMEZONE_REGEX = re.compile(TIMEZONE_REGEX_PATTERN.encode('utf8')) zulu = "Z".encode('latin-1') minus = "-".encode('latin-1') -if sys.version_info < (3, 0): - bytes = str - - class ParseError(Exception): """Raised when there is a problem parsing a date string""" @@ -58,7 +53,7 @@ class ParseError(Exception): ZERO = timedelta(0) class Utc(tzinfo): """UTC - + """ def utcoffset(self, dt): return ZERO @@ -72,7 +67,7 @@ UTC = Utc() class FixedOffset(tzinfo): """Fixed offset in hours and minutes from UTC - + """ def __init__(self, offset_hours, offset_minutes, name): self.__offset = timedelta(hours=offset_hours, minutes=offset_minutes) @@ -86,13 +81,13 @@ class FixedOffset(tzinfo): def dst(self, dt): return ZERO - + def __repr__(self): return "<FixedOffset %r>" % self.__name def parse_timezone(tzstring, default_timezone=UTC): """Parses ISO 8601 time zone specs into tzinfo offsets - + """ if tzstring == zulu: return default_timezone @@ -111,7 +106,7 @@ def parse_timezone(tzstring, default_timezone=UTC): def parse_date(datestring, default_timezone=UTC): """Parses ISO 8601 dates into datetime objects - + The timezone is parsed from the date string. However it is quite common to have dates without a timezone (not strictly correct). In this case the default timezone specified in default_timezone is used. This is UTC by diff --git a/python/subunit/run.py b/python/subunit/run.py index 6b20351..20fe86c 100755 --- a/python/subunit/run.py +++ b/python/subunit/run.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python3 # # Simple subunit testrunner for python # Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007 @@ -136,10 +136,7 @@ def main(argv=None, stdout=None): if hasattr(stdout, 'fileno'): # Patch stdout to be unbuffered, so that pdb works well on 2.6/2.7. binstdout = io.open(stdout.fileno(), 'wb', 0) - if sys.version_info[0] > 2: - sys.stdout = io.TextIOWrapper(binstdout, encoding=sys.stdout.encoding) - else: - sys.stdout = binstdout + sys.stdout = io.TextIOWrapper(binstdout, encoding=sys.stdout.encoding) stdout = sys.stdout SubunitTestProgram(module=None, argv=argv, testRunner=runner, stdout=stdout, exit=False) diff --git a/python/subunit/tests/__init__.py b/python/subunit/tests/__init__.py index c6599f7..4c8b2ae 100644 --- a/python/subunit/tests/__init__.py +++ b/python/subunit/tests/__init__.py @@ -23,6 +23,7 @@ from testscenarios import generate_scenarios # Before the test module imports to avoid circularity. # For testing: different pythons have different str() implementations. _remote_exception_repr = "testtools.testresult.real._StringException" +_remote_exception_repr_chunked = "34\r\n" + _remote_exception_repr + ": boo qux\n0\r\n" _remote_exception_str = "Traceback (most recent call last):\ntesttools.testresult.real._StringException" _remote_exception_str_chunked = "57\r\n" + _remote_exception_str + ": boo qux\n0\r\n" diff --git a/python/subunit/tests/sample-script.py b/python/subunit/tests/sample-script.py index 91838f6..f89f6c1 100755 --- a/python/subunit/tests/sample-script.py +++ b/python/subunit/tests/sample-script.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys if sys.platform == "win32": import msvcrt, os diff --git a/python/subunit/tests/sample-two-script.py b/python/subunit/tests/sample-two-script.py index fc73dfc..a687601 100755 --- a/python/subunit/tests/sample-two-script.py +++ b/python/subunit/tests/sample-two-script.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys print("test old mcdonald") print("success old mcdonald") diff --git a/python/subunit/tests/test_chunked.py b/python/subunit/tests/test_chunked.py index 46cf150..21f4814 100644 --- a/python/subunit/tests/test_chunked.py +++ b/python/subunit/tests/test_chunked.py @@ -7,7 +7,7 @@ # license at the users choice. A copy of both licenses are available in the # project source as Apache-2.0 and BSD. You may not use this file except in # compliance with one of these two licences. -# +# # Unless required by applicable law or agreed to in writing, software # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -15,13 +15,10 @@ # limitations under that license. # +from io import BytesIO import unittest from testtools.compat import _b -try: - from testtools.compat import BytesIO -except ImportError: - from io import BytesIO import subunit.chunked diff --git a/python/subunit/tests/test_details.py b/python/subunit/tests/test_details.py index f3c70d2..b2c0440 100644 --- a/python/subunit/tests/test_details.py +++ b/python/subunit/tests/test_details.py @@ -17,10 +17,6 @@ import unittest from testtools.compat import _b -try: - from testtools.compat import StringIO -except ImportError: - from io import StringIO import subunit.tests from subunit import content, content_type, details diff --git a/python/subunit/tests/test_subunit_filter.py b/python/subunit/tests/test_subunit_filter.py index 307c3be..d14415a 100644 --- a/python/subunit/tests/test_subunit_filter.py +++ b/python/subunit/tests/test_subunit_filter.py @@ -17,21 +17,18 @@ """Tests for subunit.TestResultFilter.""" from datetime import datetime +from io import BytesIO import os import subprocess import sys -from subunit import iso8601 import unittest -from testtools import TestCase from testtools.compat import _b -try: - from testtools.compat import BytesIO -except ImportError: - from io import BytesIO +from testtools import TestCase from testtools.testresult.doubles import ExtendedTestResult, StreamResult import subunit +from subunit import iso8601 from subunit.test_results import make_tag_filter, TestResultFilter from subunit import ByteStreamToStreamResult, StreamResultToBytes @@ -300,10 +297,6 @@ xfail todo ('stopTest', 'foo - renamed')], [(ev[0], ev[1].id()) for ev in result._events]) - if sys.version_info < (2, 7): - # These tests require Python >=2.7. - del test_fixup_expected_failures, test_fixup_expected_errors, test_fixup_unexpected_success - class TestFilterCommand(TestCase): diff --git a/python/subunit/tests/test_subunit_stats.py b/python/subunit/tests/test_subunit_stats.py index 9faf24d..46be5c2 100644 --- a/python/subunit/tests/test_subunit_stats.py +++ b/python/subunit/tests/test_subunit_stats.py @@ -6,7 +6,7 @@ # license at the users choice. A copy of both licenses are available in the # project source as Apache-2.0 and BSD. You may not use this file except in # compliance with one of these two licences. -# +# # Unless required by applicable law or agreed to in writing, software # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -16,13 +16,11 @@ """Tests for subunit.TestResultStats.""" +from io import BytesIO +from io import StringIO import unittest from testtools.compat import _b -try: - from testtools.compat import BytesIO, StringIO -except ImportError: - from io import BytesIO, StringIO import subunit diff --git a/python/subunit/tests/test_test_protocol.py b/python/subunit/tests/test_test_protocol.py index faab93e..74cb1ce 100644 --- a/python/subunit/tests/test_test_protocol.py +++ b/python/subunit/tests/test_test_protocol.py @@ -16,16 +16,14 @@ import datetime import io +from io import BytesIO +from io import StringIO import os import tempfile import unittest from testtools import PlaceHolder, skipIf, TestCase, TestResult from testtools.compat import _b, _u -try: - from testtools.compat import BytesIO, StringIO -except ImportError: - from io import BytesIO, StringIO from testtools.content import Content, TracebackContent, text_content from testtools.content_type import ContentType try: @@ -40,12 +38,12 @@ except ImportError: Python27TestResult, ExtendedTestResult, ) -from testtools.matchers import Contains -from testtools.testcase import six +from testtools.matchers import Contains, Equals, MatchesAny import subunit from subunit.tests import ( _remote_exception_repr, + _remote_exception_repr_chunked, _remote_exception_str, _remote_exception_str_chunked, ) @@ -64,21 +62,13 @@ class TestHelpers(TestCase): fd, file_path = tempfile.mkstemp() self.addCleanup(os.remove, file_path) fake_file = os.fdopen(fd, 'r') - if six.PY3: - self.assertEqual(fake_file.buffer, - subunit._unwrap_text(fake_file)) - else: - self.assertEqual(fake_file, subunit._unwrap_text(fake_file)) + self.assertEqual(fake_file.buffer, subunit._unwrap_text(fake_file)) def test__unwrap_text_file_write_mode(self): fd, file_path = tempfile.mkstemp() self.addCleanup(os.remove, file_path) fake_file = os.fdopen(fd, 'w') - if six.PY3: - self.assertEqual(fake_file.buffer, - subunit._unwrap_text(fake_file)) - else: - self.assertEqual(fake_file, subunit._unwrap_text(fake_file)) + self.assertEqual(fake_file.buffer, subunit._unwrap_text(fake_file)) def test__unwrap_text_fileIO_read_mode(self): fd, file_path = tempfile.mkstemp() @@ -156,20 +146,14 @@ class TestTestProtocolServerPipe(unittest.TestCase): protocol.readFrom(pipe) bing = subunit.RemotedTestCase("bing crosby") an_error = subunit.RemotedTestCase("an error") - if six.PY3: - self.assertEqual(client.errors, - [(an_error, _remote_exception_repr + '\n')]) - self.assertEqual( - client.failures, - [(bing, _remote_exception_repr + ": " - + details_to_str({'traceback': text_content(traceback)}) + "\n")]) - else: - self.assertEqual(client.errors, - [(an_error, '_StringException\n')]) - self.assertEqual( - client.failures, - [(bing, "_StringException: " - + details_to_str({'traceback': text_content(traceback)}) + "\n")]) + self.assertEqual( + client.errors, [(an_error, _remote_exception_repr + '\n')], + ) + self.assertEqual( + client.failures, + [(bing, _remote_exception_repr + ": " + + details_to_str({'traceback': text_content(traceback)}) + "\n")], + ) self.assertEqual(client.testsRun, 3) def test_non_test_characters_forwarded_immediately(self): @@ -1023,14 +1007,9 @@ class TestRemotedTestCase(unittest.TestCase): "'A test description'>", "%r" % test) result = unittest.TestResult() test.run(result) - if six.PY3: - self.assertEqual([(test, _remote_exception_repr + ': ' + - "Cannot run RemotedTestCases.\n\n")], - result.errors) - else: - self.assertEqual([(test, "_StringException: " + - "Cannot run RemotedTestCases.\n\n")], - result.errors) + self.assertEqual([(test, _remote_exception_repr + ': ' + + "Cannot run RemotedTestCases.\n\n")], + result.errors) self.assertEqual(1, result.testsRun) another_test = subunit.RemotedTestCase("A test description") self.assertEqual(test, another_test) @@ -1194,6 +1173,11 @@ class TestIsolatedTestSuite(TestCase): self.assertEqual(self.SampleTestToIsolate.TEST, False) +# A number of these tests produce different output depending on the +# testtools version. testtools < 2.5.0 used traceback2, which incorrectly +# included the traceback header even for an exception with no traceback. +# testtools 2.5.0 switched to the Python 3 standard library's traceback +# module, which fixes this bug. See https://bugs.python.org/issue24695. class TestTestProtocolClient(TestCase): def setUp(self): @@ -1249,96 +1233,121 @@ class TestTestProtocolClient(TestCase): """Test addFailure on a TestProtocolClient.""" self.protocol.addFailure( self.test, subunit.RemoteError(_u("boo qux"))) - self.assertEqual( - self.io.getvalue(), - _b(('failure: %s [\n' + _remote_exception_str + ': boo qux\n]\n') - % self.test.id())) + self.assertThat(self.io.getvalue(), MatchesAny( + # testtools < 2.5.0 + Equals(_b(( + 'failure: %s [\n' + + _remote_exception_str + ': boo qux\n' + + ']\n') % self.test.id())), + # testtools >= 2.5.0 + Equals(_b(( + 'failure: %s [\n' + + _remote_exception_repr + ': boo qux\n' + + ']\n') % self.test.id())))) def test_add_failure_details(self): """Test addFailure on a TestProtocolClient with details.""" self.protocol.addFailure( self.test, details=self.sample_tb_details) - self.assertThat([ - _b(("failure: %s [ multipart\n" - "Content-Type: text/plain\n" - "something\n" - "F\r\nserialised\nform0\r\n" - "Content-Type: text/x-traceback;charset=utf8,language=python\n" - "traceback\n" + _remote_exception_str_chunked + - "]\n") % self.test.id()), - _b(("failure: %s [ multipart\n" - "Content-Type: text/plain\n" - "something\n" - "F\r\nserialised\nform0\r\n" - "Content-Type: text/x-traceback;language=python,charset=utf8\n" - "traceback\n" + _remote_exception_str_chunked + - "]\n") % self.test.id()), - ], - Contains(self.io.getvalue())), + self.assertThat(self.io.getvalue(), MatchesAny( + # testtools < 2.5.0 + Equals(_b(( + "failure: %s [ multipart\n" + "Content-Type: text/plain\n" + "something\n" + "F\r\nserialised\nform0\r\n" + "Content-Type: text/x-traceback;charset=utf8,language=python\n" + "traceback\n" + _remote_exception_str_chunked + + "]\n") % self.test.id())), + # testtools >= 2.5.0 + Equals(_b(( + "failure: %s [ multipart\n" + "Content-Type: text/plain\n" + "something\n" + "F\r\nserialised\nform0\r\n" + "Content-Type: text/x-traceback;charset=utf8,language=python\n" + "traceback\n" + _remote_exception_repr_chunked + + "]\n") % self.test.id())))) def test_add_error(self): """Test stopTest on a TestProtocolClient.""" self.protocol.addError( self.test, subunit.RemoteError(_u("phwoar crikey"))) - self.assertEqual( - self.io.getvalue(), - _b(('error: %s [\n' + - _remote_exception_str + ": phwoar crikey\n" - "]\n") % self.test.id())) + self.assertThat(self.io.getvalue(), MatchesAny( + # testtools < 2.5.0 + Equals(_b(( + 'error: %s [\n' + + _remote_exception_str + ": phwoar crikey\n" + "]\n") % self.test.id())), + # testtools >= 2.5.0 + Equals(_b(( + 'error: %s [\n' + + _remote_exception_repr + ": phwoar crikey\n" + "]\n") % self.test.id())))) def test_add_error_details(self): """Test stopTest on a TestProtocolClient with details.""" self.protocol.addError( self.test, details=self.sample_tb_details) - self.assertThat([ - _b(("error: %s [ multipart\n" - "Content-Type: text/plain\n" - "something\n" - "F\r\nserialised\nform0\r\n" - "Content-Type: text/x-traceback;charset=utf8,language=python\n" - "traceback\n" + _remote_exception_str_chunked + - "]\n") % self.test.id()), - _b(("error: %s [ multipart\n" - "Content-Type: text/plain\n" - "something\n" - "F\r\nserialised\nform0\r\n" - "Content-Type: text/x-traceback;language=python,charset=utf8\n" - "traceback\n" + _remote_exception_str_chunked + - "]\n") % self.test.id()), - ], - Contains(self.io.getvalue())), + self.assertThat(self.io.getvalue(), MatchesAny( + # testtools < 2.5.0 + Equals(_b(( + "error: %s [ multipart\n" + "Content-Type: text/plain\n" + "something\n" + "F\r\nserialised\nform0\r\n" + "Content-Type: text/x-traceback;charset=utf8,language=python\n" + "traceback\n" + _remote_exception_str_chunked + + "]\n") % self.test.id())), + # testtools >= 2.5.0 + Equals(_b(( + "error: %s [ multipart\n" + "Content-Type: text/plain\n" + "something\n" + "F\r\nserialised\nform0\r\n" + "Content-Type: text/x-traceback;charset=utf8,language=python\n" + "traceback\n" + _remote_exception_repr_chunked + + "]\n") % self.test.id())))) def test_add_expected_failure(self): """Test addExpectedFailure on a TestProtocolClient.""" self.protocol.addExpectedFailure( self.test, subunit.RemoteError(_u("phwoar crikey"))) - self.assertEqual( - self.io.getvalue(), - _b(('xfail: %s [\n' + - _remote_exception_str + ": phwoar crikey\n" - "]\n") % self.test.id())) + self.assertThat(self.io.getvalue(), MatchesAny( + # testtools < 2.5.0 + Equals(_b(( + 'xfail: %s [\n' + + _remote_exception_str + ": phwoar crikey\n" + "]\n") % self.test.id())), + # testtools >= 2.5.0 + Equals(_b(( + 'xfail: %s [\n' + + _remote_exception_repr + ": phwoar crikey\n" + "]\n") % self.test.id())))) def test_add_expected_failure_details(self): """Test addExpectedFailure on a TestProtocolClient with details.""" self.protocol.addExpectedFailure( self.test, details=self.sample_tb_details) - self.assertThat([ - _b(("xfail: %s [ multipart\n" - "Content-Type: text/plain\n" - "something\n" - "F\r\nserialised\nform0\r\n" - "Content-Type: text/x-traceback;charset=utf8,language=python\n" - "traceback\n" + _remote_exception_str_chunked + - "]\n") % self.test.id()), - _b(("xfail: %s [ multipart\n" - "Content-Type: text/plain\n" - "something\n" - "F\r\nserialised\nform0\r\n" - "Content-Type: text/x-traceback;language=python,charset=utf8\n" - "traceback\n" + _remote_exception_str_chunked + - "]\n") % self.test.id()), - ], - Contains(self.io.getvalue())), + self.assertThat(self.io.getvalue(), MatchesAny( + # testtools < 2.5.0 + Equals(_b(( + "xfail: %s [ multipart\n" + "Content-Type: text/plain\n" + "something\n" + "F\r\nserialised\nform0\r\n" + "Content-Type: text/x-traceback;charset=utf8,language=python\n" + "traceback\n" + _remote_exception_str_chunked + + "]\n") % self.test.id())), + # testtools >= 2.5.0 + Equals(_b(( + "xfail: %s [ multipart\n" + "Content-Type: text/plain\n" + "something\n" + "F\r\nserialised\nform0\r\n" + "Content-Type: text/x-traceback;charset=utf8,language=python\n" + "traceback\n" + _remote_exception_repr_chunked + + "]\n") % self.test.id())))) def test_add_skip(self): """Test addSkip on a TestProtocolClient.""" diff --git a/python/subunit/tests/test_test_results.py b/python/subunit/tests/test_test_results.py index add30bb..31fd0ac 100644 --- a/python/subunit/tests/test_test_results.py +++ b/python/subunit/tests/test_test_results.py @@ -16,18 +16,15 @@ import csv import datetime +from io import StringIO import sys import unittest from testtools import TestCase -try: - from testtools.compat import StringIO -except ImportError: - from io import StringIO from testtools.content import ( text_content, TracebackContent, - ) +) from testtools.testresult.doubles import ExtendedTestResult import subunit @@ -380,10 +377,7 @@ class TestByTestResultTests(testtools.TestCase): super(TestByTestResultTests, self).setUp() self.log = [] self.result = subunit.test_results.TestByTestResult(self.on_test) - if sys.version_info >= (3, 0): - self.result._now = iter(range(5)).__next__ - else: - self.result._now = iter(range(5)).next + self.result._now = iter(range(5)).__next__ def assertCalled(self, **kwargs): defaults = { @@ -539,10 +533,7 @@ class TestCsvResult(testtools.TestCase): def test_csv_output(self): stream = StringIO() result = subunit.test_results.CsvResult(stream) - if sys.version_info >= (3, 0): - result._now = iter(range(5)).__next__ - else: - result._now = iter(range(5)).next + result._now = iter(range(5)).__next__ result.startTestRun() result.startTest(self) result.addSuccess(self) diff --git a/python/subunit/v2.py b/python/subunit/v2.py index e8a31d6..2137165 100644 --- a/python/subunit/v2.py +++ b/python/subunit/v2.py @@ -15,10 +15,7 @@ # import codecs -utf_8_decode = codecs.utf_8_decode import datetime -from io import UnsupportedOperation -import os import select import struct import sys @@ -30,6 +27,8 @@ builtins = try_imports(['__builtin__', 'builtins']) import subunit import subunit.iso8601 as iso8601 +utf_8_decode = codecs.utf_8_decode + __all__ = [ 'ByteStreamToStreamResult', 'StreamResultToBytes', @@ -53,7 +52,6 @@ EPOCH = datetime.datetime.utcfromtimestamp(0).replace(tzinfo=iso8601.Utc()) NUL_ELEMENT = b'\0'[0] # Contains True for types for which 'nul in thing' falsely returns false. _nul_test_broken = {} -_PY3 = (sys.version_info >= (3,)) def has_nul(buffer_or_bytes): @@ -232,21 +230,18 @@ class StreamResultToBytes(object): # For now, simplest code: join, crc32, join, output content = b''.join(packet) data = content + struct.pack(FMT_32, zlib.crc32(content) & 0xffffffff) - if _PY3: - # On eventlet 0.17.3, GreenIO.write() can make partial write. - # Use a loop to ensure that all bytes are written. - # See also the eventlet issue: - # https://github.com/eventlet/eventlet/issues/248 - view = memoryview(data) - datalen = len(data) - offset = 0 - while offset < datalen: - written = self.output_stream.write(view[offset:]) - if written is None: - break - offset += written - else: - self.output_stream.write(data) + # On eventlet 0.17.3, GreenIO.write() can make partial write. + # Use a loop to ensure that all bytes are written. + # See also the eventlet issue: + # https://github.com/eventlet/eventlet/issues/248 + view = memoryview(data) + datalen = len(data) + offset = 0 + while offset < datalen: + written = self.output_stream.write(view[offset:]) + if written is None: + break + offset += written self.output_stream.flush() diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index b8a1655..0000000 --- a/setup.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[bdist_wheel] -universal = 1 - @@ -1,4 +1,5 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 + import os.path from setuptools import setup @@ -6,8 +7,9 @@ from setuptools import setup def _get_version_from_file(filename, start_of_line, split_marker): """Extract version from file, giving last matching value or None""" try: - return [x for x in open(filename) - if x.startswith(start_of_line)][-1].split(split_marker)[1].strip() + return [ + x for x in open(filename) if x.startswith(start_of_line) + ][-1].split(split_marker)[1].strip() except (IOError, IndexError): return None @@ -17,12 +19,14 @@ VERSION = ( _get_version_from_file('PKG-INFO', 'Version:', ':') # Must be a development checkout, so use the Makefile or _get_version_from_file('Makefile', 'VERSION', '=') - or "0.0") + or "0.0" +) relpath = os.path.dirname(__file__) if relpath: os.chdir(relpath) + setup( name='python-subunit', version=VERSION, @@ -32,13 +36,12 @@ setup( 'Intended Audience :: Developers', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Topic :: Software Development :: Testing', ], keywords='python test streaming', @@ -53,6 +56,11 @@ setup( packages=['subunit', 'subunit.tests', 'subunit.filter_scripts'], package_dir={'subunit': 'python/subunit'}, + python_requires=">=3.6", + install_requires=[ + 'extras', + 'testtools>=0.9.34', + ], entry_points={ 'console_scripts': [ 'subunit-1to2=subunit.filter_scripts.subunit_1to2:main', @@ -71,10 +79,6 @@ setup( 'tap2subunit=subunit.filter_scripts.tap2subunit:main', ] }, - install_requires=[ - 'extras', - 'testtools>=0.9.34', - ], tests_require=[ 'fixtures', 'hypothesis', @@ -82,8 +86,6 @@ setup( ], extras_require={ 'docs': ['docutils'], - 'test': ['fixtures', 'testscenarios'], - 'test:python_version!="3.2"': ['hypothesis'], + 'test': ['fixtures', 'testscenarios', 'hypothesis'], }, - python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", ) |