summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/main.yml6
-rw-r--r--NEWS18
-rw-r--r--configure.ac2
-rw-r--r--pyproject.toml5
-rw-r--r--python/subunit/__init__.py26
-rw-r--r--python/subunit/details.py8
-rwxr-xr-xpython/subunit/filter_scripts/subunit2csv.py2
-rwxr-xr-xpython/subunit/filter_scripts/subunit2disk.py2
-rwxr-xr-xpython/subunit/filter_scripts/subunit2gtk.py19
-rwxr-xr-xpython/subunit/filter_scripts/subunit2junitxml.py2
-rwxr-xr-xpython/subunit/filter_scripts/subunit_1to2.py2
-rwxr-xr-xpython/subunit/filter_scripts/subunit_2to1.py2
-rwxr-xr-xpython/subunit/filter_scripts/subunit_filter.py2
-rwxr-xr-xpython/subunit/filter_scripts/subunit_notify.py2
-rwxr-xr-xpython/subunit/filter_scripts/subunit_output.py2
-rwxr-xr-xpython/subunit/filter_scripts/subunit_stats.py2
-rwxr-xr-xpython/subunit/filter_scripts/subunit_tags.py2
-rwxr-xr-xpython/subunit/filter_scripts/tap2subunit.py2
-rw-r--r--python/subunit/iso8601.py21
-rwxr-xr-xpython/subunit/run.py7
-rw-r--r--python/subunit/tests/__init__.py1
-rwxr-xr-xpython/subunit/tests/sample-script.py2
-rwxr-xr-xpython/subunit/tests/sample-two-script.py2
-rw-r--r--python/subunit/tests/test_chunked.py7
-rw-r--r--python/subunit/tests/test_details.py4
-rw-r--r--python/subunit/tests/test_subunit_filter.py13
-rw-r--r--python/subunit/tests/test_subunit_stats.py8
-rw-r--r--python/subunit/tests/test_test_protocol.py215
-rw-r--r--python/subunit/tests/test_test_results.py17
-rw-r--r--python/subunit/v2.py33
-rw-r--r--setup.cfg3
-rwxr-xr-xsetup.py28
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
diff --git a/NEWS b/NEWS
index 73f660a..a5c58b1 100644
--- a/NEWS
+++ b/NEWS
@@ -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
-
diff --git a/setup.py b/setup.py
index f734098..54ba083 100755
--- a/setup.py
+++ b/setup.py
@@ -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.*",
)