From 73bfbcbdfb6225031b63db25f4425db27bac9845 Mon Sep 17 00:00:00 2001 From: Michael Howitz Date: Tue, 28 Mar 2023 08:49:42 +0200 Subject: [PATCH] Drop support for Python < 3.7 (#146) * Bumped version for breaking release. * Drop support for Python 2.7, 3.5, 3.6. * Configuring for pure-python * Drop support for Python 2.7 up to 3.6. * Improve resource warnings. Fixes #144. --- .github/workflows/tests.yml | 16 +-- .meta.toml | 14 +- CHANGES.rst | 4 +- setup.cfg | 4 +- setup.py | 11 +- src/zope/testrunner/coverage.py | 4 +- src/zope/testrunner/debug.py | 3 +- src/zope/testrunner/digraph.py | 4 +- src/zope/testrunner/eggsupport.py | 1 - src/zope/testrunner/feature.py | 2 +- src/zope/testrunner/find.py | 58 +++----- src/zope/testrunner/formatter.py | 86 +++++------ src/zope/testrunner/garbagecollection.py | 4 +- src/zope/testrunner/layer.py | 4 +- src/zope/testrunner/listing.py | 2 +- src/zope/testrunner/options.py | 1 - src/zope/testrunner/process.py | 4 +- src/zope/testrunner/profiling.py | 4 +- src/zope/testrunner/refcount.py | 5 +- src/zope/testrunner/runner.py | 79 ++++------- src/zope/testrunner/shuffle.py | 13 +- src/zope/testrunner/tb_format.py | 43 +++--- src/zope/testrunner/tests/test_doctest.py | 133 +++++------------- src/zope/testrunner/tests/test_find.py | 2 +- src/zope/testrunner/tests/test_runner.py | 32 ++--- src/zope/testrunner/tests/test_subunit.py | 37 +---- .../testrunner/tests/test_threadsupport.py | 2 +- .../testrunner/tests/testrunner-colors.rst | 6 +- .../testrunner-debugging-import-failure.rst | 4 +- .../testrunner-debugging-layer-setup.rst | 10 +- .../testrunner-debugging-nonprintable-exc.rst | 7 +- .../tests/testrunner-edge-cases.rst | 2 +- .../testrunner/tests/testrunner-errors.rst | 20 +-- .../tests/testrunner-ex/gc-after-test.py | 15 +- .../testrunner/tests/testrunner-ex/leak.py | 2 +- .../testrunner-ex/sample2/stdstreamstest.py | 2 +- .../tests/testrunner-ex/samplelayers.py | 24 ++-- src/zope/testrunner/tests/testrunner-gc.rst | 4 +- .../tests/testrunner-layers-cantfind.rst | 3 +- .../tests/testrunner-layers-ntd.rst | 10 +- .../tests/testrunner-report-skipped.rst | 3 +- .../tests/testrunner-subunit-v2.rst | 32 ++--- .../testrunner/tests/testrunner-subunit.rst | 20 +-- .../tests/testrunner-unexpected-success.rst | 5 +- .../testrunner/tests/testrunner-wo-source.rst | 25 ++-- src/zope/testrunner/threadsupport.py | 6 +- tox.ini | 8 +- 47 files changed, 285 insertions(+), 495 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a11f5ec..1f774e6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,33 +22,23 @@ jobs: config: # [Python version, tox env] - ["3.9", "lint"] - - ["2.7", "py27"] - - ["3.5", "py35"] - - ["3.6", "py36"] - ["3.7", "py37"] - ["3.8", "py38"] - ["3.9", "py39"] - ["3.10", "py310"] - ["3.11", "py311"] - - ["pypy-2.7", "pypy"] - - ["pypy-3.7", "pypy3"] + - ["pypy-3.9", "pypy3"] - ["3.9", "docs"] - ["3.9", "coverage"] - - ["2.7", "py27-subunit"] - - ["3.5", "py35-subunit"] - - ["3.6", "py36-subunit"] - ["3.7", "py37-subunit"] - ["3.8", "py38-subunit"] - ["3.9", "py39-subunit"] - ["3.10", "py310-subunit"] - ["3.11", "py311-subunit"] - - ["pypy-2.7", "pypy-subunit"] - - ["pypy-3.7", "pypy3-subunit"] + - ["pypy-3.9", "pypy3-subunit"] exclude: - { os: ["windows", "windows-latest"], config: ["3.9", "lint"] } - { os: ["windows", "windows-latest"], config: ["3.9", "docs"] } - - { os: ["windows", "windows-latest"], config: ["3.9", "coverage"] } - - { os: ["windows", "windows-latest"], config: ["pypy-2.7", "pypy-subunit"] } runs-on: ${{ matrix.os[1] }} if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name @@ -76,7 +66,7 @@ jobs: - name: Coverage if: matrix.config[1] == 'coverage' run: | - pip install coveralls coverage-python-version + pip install coveralls coveralls --service=github env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.meta.toml b/.meta.toml index dc7a3b1..fcd8a38 100644 --- a/.meta.toml +++ b/.meta.toml @@ -2,11 +2,10 @@ # https://github.com/zopefoundation/meta/tree/master/config/pure-python [meta] template = "pure-python" -commit-id = "180c99ed3def7da6e173a5a496cad6484eadd044" +commit-id = "1514f236" [python] with-pypy = true -with-legacy-python = true with-docs = true with-sphinx-doctests = false with-windows = true @@ -42,7 +41,7 @@ additional-config = [ [tox] additional-envlist = [ - "py{27,35,36,37,38,39,310,311,py,py3}-subunit", + "py{37,38,39,310,311,py3}-subunit", ] testenv-deps = [ ] @@ -54,17 +53,10 @@ use-flake8 = true [github-actions] additional-config = [ - "- [\"2.7\", \"py27-subunit\"]", - "- [\"3.5\", \"py35-subunit\"]", - "- [\"3.6\", \"py36-subunit\"]", "- [\"3.7\", \"py37-subunit\"]", "- [\"3.8\", \"py38-subunit\"]", "- [\"3.9\", \"py39-subunit\"]", "- [\"3.10\", \"py310-subunit\"]", "- [\"3.11\", \"py311-subunit\"]", - "- [\"pypy-2.7\", \"pypy-subunit\"]", - "- [\"pypy-3.7\", \"pypy3-subunit\"]", - ] -additional-exclude = [ - "- { os: [\"windows\", \"windows-latest\"], config: [\"pypy-2.7\", \"pypy-subunit\"] }", + "- [\"pypy-3.9\", \"pypy3-subunit\"]", ] diff --git a/CHANGES.rst b/CHANGES.rst index 1990497..6761950 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,10 +2,10 @@ zope.testrunner Changelog =========================== -5.7 (unreleased) +6.0 (unreleased) ================ -- Nothing changed yet. +- Drop support for Python 2.7, 3.5, 3.6. 5.6 (2022-12-09) diff --git a/setup.cfg b/setup.cfg index 979375a..d287e4d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ # Generated from: # https://github.com/zopefoundation/meta/tree/master/config/pure-python [bdist_wheel] -universal = 1 +universal = 0 [flake8] doctests = 1 @@ -29,7 +29,7 @@ ignore = force_single_line = True combine_as_imports = True sections = FUTURE,STDLIB,THIRDPARTY,ZOPE,FIRSTPARTY,LOCALFOLDER -known_third_party = six, docutils, pkg_resources +known_third_party = six, docutils, pkg_resources, pytz known_zope = known_first_party = default_section = ZOPE diff --git a/setup.py b/setup.py index ffaa24a..61b0134 100644 --- a/setup.py +++ b/setup.py @@ -23,11 +23,10 @@ from setuptools.command.test import test -version = '5.7.dev0' +version = '6.0.dev0' INSTALL_REQUIRES = [ 'setuptools', - 'six', 'zope.exceptions', 'zope.interface', ] @@ -133,7 +132,7 @@ def read(*names): description='Zope testrunner script.', long_description=long_description, author='Zope Foundation and Contributors', - author_email='zope-dev@zope.org', + author_email='zope-dev@zope.dev', packages=[ "zope", "zope.testrunner", @@ -147,11 +146,7 @@ def read(*names): "License :: OSI Approved :: Zope Public License", "Operating System :: OS Independent", "Programming Language :: Python", - "Programming Language :: Python :: 2", - "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", @@ -163,7 +158,7 @@ def read(*names): "Topic :: Software Development :: Testing", ], namespace_packages=['zope'], - python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*', + python_requires='>=3.7', install_requires=INSTALL_REQUIRES, tests_require=TESTS_REQUIRE, extras_require=EXTRAS_REQUIRE, diff --git a/src/zope/testrunner/coverage.py b/src/zope/testrunner/coverage.py index 7d1ce44..3f4cc9f 100644 --- a/src/zope/testrunner/coverage.py +++ b/src/zope/testrunner/coverage.py @@ -83,7 +83,7 @@ def stop(self): self.started = False -class TestIgnore(object): +class TestIgnore: def __init__(self, directories): self._test_dirs = [self._filenameFormat(d[0]) + os.path.sep @@ -130,7 +130,7 @@ class Coverage(zope.testrunner.feature.Feature): directory = None def __init__(self, runner): - super(Coverage, self).__init__(runner) + super().__init__(runner) self.active = bool(runner.options.coverage) def global_setup(self): diff --git a/src/zope/testrunner/debug.py b/src/zope/testrunner/debug.py index 5091682..af19036 100644 --- a/src/zope/testrunner/debug.py +++ b/src/zope/testrunner/debug.py @@ -14,7 +14,6 @@ """Debug functions """ -from __future__ import print_function import doctest import io @@ -85,4 +84,4 @@ def print_doctest_location(err): filename = err.test.filename if filename.endswith('.pyc'): filename = filename[:-1] - print("> %s(%s)_()" % (filename, err.test.lineno+err.example.lineno+1)) + print("> {}({})_()".format(filename, err.test.lineno+err.example.lineno+1)) diff --git a/src/zope/testrunner/digraph.py b/src/zope/testrunner/digraph.py index 3878257..087e09f 100644 --- a/src/zope/testrunner/digraph.py +++ b/src/zope/testrunner/digraph.py @@ -17,7 +17,7 @@ from itertools import count -class DiGraph(object): +class DiGraph: """Directed graph. A directed graph is a set of nodes together with a @@ -186,7 +186,7 @@ def sccs(self, trivial=False): visits.extend(self._neighbors.get(node, ())) -class _TarjanState(object): +class _TarjanState: """representation of a node's processing state.""" __slots__ = "stacked dfs low".split() diff --git a/src/zope/testrunner/eggsupport.py b/src/zope/testrunner/eggsupport.py index 15b92d7..8f02853 100644 --- a/src/zope/testrunner/eggsupport.py +++ b/src/zope/testrunner/eggsupport.py @@ -1,6 +1,5 @@ """ Add unit and functional testing support to setuptools-driven eggs. """ -from __future__ import print_function from setuptools.command.test import ScanningLoader from setuptools.command.test import test as BaseCommand diff --git a/src/zope/testrunner/feature.py b/src/zope/testrunner/feature.py index 0992232..0d5f15d 100644 --- a/src/zope/testrunner/feature.py +++ b/src/zope/testrunner/feature.py @@ -20,7 +20,7 @@ @zope.interface.implementer(zope.testrunner.interfaces.IFeature) -class Feature(object): +class Feature: """A base class implementing no-op methods for the IFeature interface.""" active = False diff --git a/src/zope/testrunner/find.py b/src/zope/testrunner/find.py index 6dfe3da..94f9746 100644 --- a/src/zope/testrunner/find.py +++ b/src/zope/testrunner/find.py @@ -19,8 +19,6 @@ import sys import unittest -import six - import zope.testrunner.debug import zope.testrunner.feature import zope.testrunner.layer @@ -102,14 +100,13 @@ class StartUpFailure(unittest.TestCase): >>> from zope.testrunner.interfaces import EndRun >>> try: #doctest: +ELLIPSIS - ... try: # try...except...finally doesn't work in Python 2.4 ... # Needed to prevent the result from starting with '...' ... print("Result:") ... StartUpFailure(options, None, exc_info) - ... except EndRun: + ... except EndRun: ... print("EndRun raised") ... finally: - ... sys.stdin = old_stdin + ... sys.stdin = old_stdin Result: Exception: something bad happened during import ... @@ -141,7 +138,7 @@ def __init__(self, options, module, exc_info): zope.testrunner.debug.post_mortem(exc_info) self.module = module self.exc_info = exc_info - super(StartUpFailure, self).__init__() + super().__init__() def shortDescription(self): return 'StartUpFailure: import errors in %s.' % self.module @@ -153,7 +150,12 @@ def runTest(self): if self.exc_info is None or any(x is None for x in self.exc_info): self.fail("could not import %s" % self.module) else: - six.reraise(*self.exc_info) + try: + _, value, tb = self.exc_info + raise value.with_traceback(tb) + finally: + value = None + tb = None def find_tests(options, found_suites=None): @@ -232,9 +234,7 @@ def find_suites(options, accept=None): "Module %s does not define any tests" % module_name) - if isinstance(suite, unittest.TestSuite): - check_suite(suite, module_name) - else: + if not isinstance(suite, unittest.TestSuite): # We extract the error message expression into a # local variable because we want the `raise` # statement to fit on a single line, to make the @@ -356,8 +356,7 @@ def test_dirs(options, seen): yield p, package break else: - for dpath in options.test_path: - yield dpath + yield from options.test_path def walk_with_symlinks(options, dir): @@ -371,8 +370,7 @@ def walk_with_symlinks(options, dir): for d in dirs: p = os.path.join(dirpath, d) if os.path.islink(p): - for sdirpath, sdirs, sfiles in walk_with_symlinks(options, p): - yield (sdirpath, sdirs, sfiles) + yield from walk_with_symlinks(options, p) compiled_suffixes = '.pyc', '.pyo' @@ -451,16 +449,16 @@ def tests_from_suite(suite, options, dlevel=1, level = getattr(suite, 'level', dlevel) layer = getattr(suite, 'layer', dlayer) - if not isinstance(layer, six.string_types): + if not isinstance(layer, str): layer = name_from_layer(layer) if isinstance(suite, unittest.TestSuite): for possible_suite in suite: - for r in tests_from_suite(possible_suite, options, level, layer, - accept=accept, - seen_test_ids=seen_test_ids, - duplicated_test_ids=duplicated_test_ids): - yield r + yield from tests_from_suite( + possible_suite, options, level, layer, + accept=accept, + seen_test_ids=seen_test_ids, + duplicated_test_ids=duplicated_test_ids) elif isinstance(suite, StartUpFailure): yield (suite, None) else: @@ -475,26 +473,6 @@ def tests_from_suite(suite, options, dlevel=1, yield (suite, layer) -def check_suite(suite, module_name): - - """Check for bad tests in a test suite. - - "Bad tests" are those that do not inherit from unittest.TestCase. - - Note that this function is pointless on Python 2.5, because unittest itself - checks for this in TestSuite.addTest. It is, however, useful on earlier - Pythons. - """ - for x in suite: - if isinstance(x, unittest.TestSuite): - check_suite(x, module_name) - elif not isinstance(x, unittest.TestCase): - raise TypeError( - "Invalid test, %r,\nin test_suite from %s" - % (x, module_name) - ) - - _layer_name_cache = {} diff --git a/src/zope/testrunner/formatter.py b/src/zope/testrunner/formatter.py index f1bedd8..1c4f554 100644 --- a/src/zope/testrunner/formatter.py +++ b/src/zope/testrunner/formatter.py @@ -13,20 +13,14 @@ ############################################################################## """Output formatting. """ -from __future__ import print_function - - -try: - from collections.abc import MutableMapping -except ImportError: - from collections import MutableMapping - import doctest +import io import os import re import sys import tempfile import traceback +from collections.abc import MutableMapping from contextlib import contextmanager from datetime import datetime from datetime import timedelta @@ -34,12 +28,6 @@ from zope.testrunner.exceptions import DocTestFailureException -try: - unicode -except NameError: - unicode = str - - doctest_template = """ File "%s", line %s, in %s @@ -51,7 +39,7 @@ """ -class OutputFormatter(object): +class OutputFormatter: """Test runner output formatter.""" # Implementation note: be careful about printing stuff to sys.stderr. @@ -93,7 +81,7 @@ def getShortDescription(self, test, room): else: pre = s[:pos+2] post = s[-w:] - s = "%s...%s" % (pre, post) + s = "{}...{}".format(pre, post) else: w = room - 4 s = '... ' + s[-w:] @@ -230,10 +218,10 @@ def refcounts(self, rc, prev): def detailed_refcounts(self, track, rc, prev): """Report a change in reference counts, with extra detail.""" - print((" sum detail refcount=%-8d" - " sys refcount=%-8d" - " change=%-6d" - % (track.n, rc, rc - prev))) + print(" sum detail refcount=%-8d" + " sys refcount=%-8d" + " change=%-6d" + % (track.n, rc, rc - prev)) track.output() def start_set_up(self, layer_name): @@ -439,16 +427,14 @@ def tigetnum(attr, default=None): try: import curses except ImportError: - # avoid reimporting a broken module in python 2.3 + # avoid reimporting a broken module sys.modules['curses'] = None else: # If sys.stdout is not a real file object (e.g. in unit tests that # use various wrappers), you get an error, different depending on # Python version: - expected_exceptions = (curses.error, TypeError, AttributeError) - if sys.version_info >= (3,): - import io - expected_exceptions += (io.UnsupportedOperation, ) + expected_exceptions = ( + curses.error, TypeError, AttributeError, io.UnsupportedOperation) try: curses.setupterm() except expected_exceptions: @@ -544,7 +530,7 @@ def color_code(self, color): prefix_code = code break color_code = self.colorcodes[color] - return '\033[%s%sm' % (prefix_code, color_code) + return '\033[{}{}m'.format(prefix_code, color_code) def color(self, what): """Pick a named color from the color scheme""" @@ -576,10 +562,10 @@ def test_skipped(self, test, reason): The next output operation should be stop_test(). """ if self.verbose > 2: - s = " (%sskipped: %s%s)" % ( + s = " ({}skipped: {}{})".format( self.color('skipped'), reason, self.color('info')) elif self.verbose > 1: - s = " (%sskipped%s)" % ( + s = " ({}skipped{})".format( self.color('skipped'), self.color('info')) else: return @@ -621,7 +607,7 @@ def format_seconds(self, n_seconds, normal='normal'): """Format a time in seconds.""" if n_seconds >= 60: n_minutes, n_seconds = divmod(n_seconds, 60) - return "%s minutes %s seconds" % ( + return "{} minutes {} seconds".format( self.colorize('number', '%d' % n_minutes, normal), self.colorize('number', '%.3f' % n_seconds, normal)) else: @@ -764,7 +750,7 @@ def print_colorized_traceback(self, formatted_traceback): print() -class FakeTest(object): +class FakeTest: """A fake test object that only has an id.""" failureException = None @@ -799,7 +785,7 @@ def id(self): testtools = None -class _RunnableDecorator(object): +class _RunnableDecorator: """Permit controlling the runnable annotation on tests. This decorates a StreamResult, adding a setRunnable context manager to @@ -831,7 +817,7 @@ def status(self, **kwargs): self.decorated.status(**kwargs) -class _SortedDict(MutableMapping, object): +class _SortedDict(MutableMapping): """A dict that always returns items in sorted order. This differs from collections.OrderedDict in that it returns items in @@ -862,7 +848,7 @@ def __len__(self): return len(self._dict) -class SubunitOutputFormatter(object): +class SubunitOutputFormatter: """A subunit output formatter. This output formatter generates subunit-compatible output (see @@ -966,11 +952,11 @@ def _emit_failure(self, failure_id, tag, exc_info): def _enter_layer(self, layer_name): """Tell subunit that we are entering a layer.""" - self._subunit.tags(['zope:layer:%s' % (layer_name,)], []) + self._subunit.tags(['zope:layer:{}'.format(layer_name)], []) def _exit_layer(self, layer_name): """Tell subunit that we are exiting a layer.""" - self._subunit.tags([], ['zope:layer:%s' % (layer_name,)]) + self._subunit.tags([], ['zope:layer:{}'.format(layer_name)]) def info(self, message): """Print an informative message.""" @@ -993,7 +979,7 @@ def error(self, message): """Report an error.""" # XXX: Mostly used for user errors, sometimes used for errors in the # test framework, sometimes used to record layer setUp failure (!!!). - self._stream.write('%s\n' % (message,)) + self._stream.write('{}\n'.format(message)) def error_with_banner(self, message): """Report an error with a big ASCII banner.""" @@ -1087,7 +1073,7 @@ def garbage(self, garbage): # summary information for the whole suite. However, there's no event # on output formatters for "everything is really finished, honest". -- # jml, 2010-02-14 - details = {'garbage': text_content(unicode(garbage))} + details = {'garbage': text_content(str(garbage))} self._emit_fake_test(self.TAG_GARBAGE, self.TAG_GARBAGE, details) def test_garbage(self, test, garbage): @@ -1104,7 +1090,7 @@ def test_garbage(self, test, garbage): self._subunit.startTest(test) self._subunit.tags([self.TAG_GARBAGE], []) self._subunit.addError( - test, details={'garbage': text_content(unicode(garbage))}) + test, details={'garbage': text_content(str(garbage))}) self._subunit.stopTest(test) def test_threads(self, test, new_threads): @@ -1117,7 +1103,7 @@ def test_threads(self, test, new_threads): self._subunit.startTest(test) self._subunit.tags([self.TAG_THREADS], []) self._subunit.addError( - test, details={'threads': text_content(unicode(new_threads))}) + test, details={'threads': text_content(str(new_threads))}) self._subunit.stopTest(test) def test_cycles(self, test, cycles): @@ -1151,7 +1137,7 @@ def start_set_up(self, layer_name): The next output operation should be stop_set_up(). """ - test = FakeTest('%s:setUp' % (layer_name,)) + test = FakeTest('{}:setUp'.format(layer_name)) now = self._emit_timestamp() with self._subunit.setRunnable(False): self._subunit.startTest(test) @@ -1165,7 +1151,7 @@ def stop_set_up(self, seconds): """ layer_name, start_time = self._last_layer self._last_layer = None - test = FakeTest('%s:setUp' % (layer_name,)) + test = FakeTest('{}:setUp'.format(layer_name)) self._emit_timestamp(start_time + timedelta(seconds=seconds)) with self._subunit.setRunnable(False): self._subunit.addSuccess(test) @@ -1175,7 +1161,7 @@ def stop_set_up(self, seconds): def layer_failure(self, failure_type, exc_info): layer_name, start_time = self._last_layer self._emit_failure( - '%s:%s' % (layer_name, failure_type), self.TAG_LAYER, exc_info) + '{}:{}'.format(layer_name, failure_type), self.TAG_LAYER, exc_info) def start_tear_down(self, layer_name): """Report that we're tearing down a layer. @@ -1187,7 +1173,7 @@ def start_tear_down(self, layer_name): The next output operation should be stop_tear_down() or tear_down_not_supported(). """ - test = FakeTest('%s:tearDown' % (layer_name,)) + test = FakeTest('{}:tearDown'.format(layer_name)) self._exit_layer(layer_name) now = self._emit_timestamp() with self._subunit.setRunnable(False): @@ -1202,7 +1188,7 @@ def stop_tear_down(self, seconds): """ layer_name, start_time = self._last_layer self._last_layer = None - test = FakeTest('%s:tearDown' % (layer_name,)) + test = FakeTest('{}:tearDown'.format(layer_name)) self._emit_timestamp(start_time + timedelta(seconds=seconds)) with self._subunit.setRunnable(False): self._subunit.addSuccess(test) @@ -1215,7 +1201,7 @@ def tear_down_not_supported(self): """ layer_name, start_time = self._last_layer self._last_layer = None - test = FakeTest('%s:tearDown' % (layer_name,)) + test = FakeTest('{}:tearDown'.format(layer_name)) self._emit_timestamp() with self._subunit.setRunnable(False): self._subunit.addSkip(test, 'tearDown not supported') @@ -1259,19 +1245,19 @@ def _exc_info_to_details(self, exc_info): formatter = OutputFormatter(None) traceback = formatter.format_traceback(exc_info) - # We have no idea if the traceback is a unicode object or a + # We have no idea if the traceback is a str object or a # bytestring with non-ASCII characters. We had best be careful when # handling it. if isinstance(traceback, bytes): # Assume the traceback was UTF-8-encoded, but still be careful. - unicode_tb = traceback.decode('utf-8', 'replace') + str_tb = traceback.decode('utf-8', 'replace') else: - unicode_tb = traceback + str_tb = traceback return _SortedDict({ 'traceback': Content( self.TRACEBACK_CONTENT_TYPE, - lambda: [unicode_tb.encode('utf8')]), + lambda: [str_tb.encode('utf8')]), }) def _add_std_streams_to_details(self, details, stdout, stderr): @@ -1339,7 +1325,7 @@ def error(self, message): # XXX: Mostly used for user errors, sometimes used for errors in the # test framework, sometimes used to record layer setUp failure (!!!). self._subunit.status( - file_name='error', file_bytes=unicode(message).encode('utf-8'), + file_name='error', file_bytes=str(message).encode('utf-8'), eof=True, mime_type=repr(self.PLAIN_TEXT)) def _emit_exists(self, test): diff --git a/src/zope/testrunner/garbagecollection.py b/src/zope/testrunner/garbagecollection.py index d9561ce..ac872b7 100644 --- a/src/zope/testrunner/garbagecollection.py +++ b/src/zope/testrunner/garbagecollection.py @@ -23,7 +23,7 @@ class Threshold(zope.testrunner.feature.Feature): def __init__(self, runner): - super(Threshold, self).__init__(runner) + super().__init__(runner) self.threshold = self.runner.options.gc self.active = bool(self.threshold) @@ -55,7 +55,7 @@ class Debug(zope.testrunner.feature.Feature): """Manages garbage collection debug flags.""" def __init__(self, runner): - super(Debug, self).__init__(runner) + super().__init__(runner) self.flags = self.runner.options.gc_option self.active = bool(self.flags) diff --git a/src/zope/testrunner/layer.py b/src/zope/testrunner/layer.py index 7f0a647..7a5df0f 100644 --- a/src/zope/testrunner/layer.py +++ b/src/zope/testrunner/layer.py @@ -16,11 +16,11 @@ import unittest -class UnitTests(object): +class UnitTests: """A layer for gathering all unit tests.""" -class EmptyLayer(object): +class EmptyLayer: """An empty layer to start spreading out subprocesses.""" __bases__ = () diff --git a/src/zope/testrunner/listing.py b/src/zope/testrunner/listing.py index b7c9046..4f184f7 100644 --- a/src/zope/testrunner/listing.py +++ b/src/zope/testrunner/listing.py @@ -21,7 +21,7 @@ class Listing(zope.testrunner.feature.Feature): """Lists all tests in the report instead of running the tests.""" def __init__(self, runner): - super(Listing, self).__init__(runner) + super().__init__(runner) self.active = bool(runner.options.list_tests) def global_setup(self): diff --git a/src/zope/testrunner/options.py b/src/zope/testrunner/options.py index 3d250f4..b1db0a9 100644 --- a/src/zope/testrunner/options.py +++ b/src/zope/testrunner/options.py @@ -13,7 +13,6 @@ ############################################################################## """Command-line option parsing """ -from __future__ import print_function import argparse import os diff --git a/src/zope/testrunner/process.py b/src/zope/testrunner/process.py index 1780be8..3ee1d06 100644 --- a/src/zope/testrunner/process.py +++ b/src/zope/testrunner/process.py @@ -13,7 +13,6 @@ ############################################################################## """Subprocess support. """ -from __future__ import print_function import sys @@ -24,7 +23,7 @@ class SubProcess(zope.testrunner.feature.Feature): """Lists all tests in the report instead of running the tests.""" def __init__(self, runner): - super(SubProcess, self).__init__(runner) + super().__init__(runner) self.active = bool(runner.options.resume_layer) def global_setup(self): @@ -50,5 +49,4 @@ def report(self): for test, exc_info in self.runner.errors: print(' '.join(str(test).strip().split('\n')), file=self.original_stderr) - # You need to flush in Python 3, and it doesn't hurt in Python 2: self.original_stderr.flush() diff --git a/src/zope/testrunner/profiling.py b/src/zope/testrunner/profiling.py index 887d3f5..ea65358 100644 --- a/src/zope/testrunner/profiling.py +++ b/src/zope/testrunner/profiling.py @@ -25,7 +25,7 @@ available_profilers = {} -class CProfiler(object): +class CProfiler: """cProfiler""" def __init__(self, filepath): self.filepath = filepath @@ -52,7 +52,7 @@ def loadStats(self, prof_glob): class Profiling(zope.testrunner.feature.Feature): def __init__(self, runner): - super(Profiling, self).__init__(runner) + super().__init__(runner) self.active = bool(self.runner.options.profile) self.profiler = self.runner.options.profile diff --git a/src/zope/testrunner/refcount.py b/src/zope/testrunner/refcount.py index f153fdd..f9092ee 100644 --- a/src/zope/testrunner/refcount.py +++ b/src/zope/testrunner/refcount.py @@ -13,14 +13,13 @@ ############################################################################## """Support for tracking reference counts. """ -from __future__ import print_function import gc import sys import types -class TrackRefs(object): +class TrackRefs: """Object to track reference counts across test runs.""" def __init__(self): @@ -103,4 +102,4 @@ def type_or_class_title(t): module = getattr(t, '__module__', '__builtin__') if module == '__builtin__': return t.__name__ - return "%s.%s" % (module, t.__name__) + return "{}.{}".format(module, t.__name__) diff --git a/src/zope/testrunner/runner.py b/src/zope/testrunner/runner.py index 693d733..c43134c 100644 --- a/src/zope/testrunner/runner.py +++ b/src/zope/testrunner/runner.py @@ -13,13 +13,13 @@ ############################################################################## """Test execution """ -from __future__ import print_function import errno import gc import io import os import pprint +import queue import re import subprocess import sys @@ -29,8 +29,7 @@ import unittest import warnings from contextlib import contextmanager - -from six import StringIO +from io import StringIO import zope.testrunner import zope.testrunner._doctest @@ -62,13 +61,6 @@ from .util import uses_refcounts -try: - import Queue # Python 2 -except ImportError: - # Python 3 - import queue as Queue # Python 3 - - class UnexpectedSuccess(Exception): pass @@ -85,14 +77,14 @@ def __init__(self, reason, stderr): self.stderr = stderr def __str__(self): - return '%s: %s' % (self.reason, self.stderr) + return '{}: {}'.format(self.reason, self.stderr) class CanNotTearDown(Exception): "Couldn't tear down a test" -class Runner(object): +class Runner: """The test runner. It is the central point of this package and responsible for finding and @@ -156,8 +148,8 @@ def ordered_layers(self): # but only if this is not in the subprocess yield (name_from_layer(EmptyLayer), EmptyLayer, EmptySuite()) - layer_names = dict([(layer_from_name(layer_name), layer_name) - for layer_name in self.tests_by_layer_name]) + layer_names = {layer_from_name(layer_name): layer_name + for layer_name in self.tests_by_layer_name} for layer in order_by_bases(layer_names): layer_name = layer_names[layer] yield layer_name, layer, self.tests_by_layer_name[layer_name] @@ -412,11 +404,8 @@ def run_tests(options, tests, name, failures, errors, skipped, import_errors): output.stop_tests() failures.extend(result.failures) n_failures = len(result.failures) - if hasattr(result, 'unexpectedSuccesses'): - # Python versions prior to 2.7 do not have the concept of - # unexpectedSuccesses. - failures.extend(result.unexpectedSuccesses) - n_failures += len(result.unexpectedSuccesses) + failures.extend(result.unexpectedSuccesses) + n_failures += len(result.unexpectedSuccesses) skipped.extend(result.skipped) errors.extend(result.errors) output.summary(n_tests=result.testsRun, @@ -463,7 +452,7 @@ def run_layer(options, layer_name, layer, tests, setup_layers, output = options.output gathered = [] gather_layers(layer, gathered) - needed = dict([(ly, 1) for ly in gathered]) + needed = {ly: 1 for ly in gathered} if options.resume_number != 0: output.info("Running %s tests:" % layer_name) tear_down_unneeded(options, needed, setup_layers, errors) @@ -490,7 +479,7 @@ class SetUpLayerFailure(unittest.TestCase): subunit_label = 'setUp' def __init__(self, layer): - super(SetUpLayerFailure, self).__init__() + super().__init__() self.layer = layer def runTest(self): @@ -505,7 +494,7 @@ class TearDownLayerFailure(unittest.TestCase): subunit_label = 'tearDown' def __init__(self, layer): - super(TearDownLayerFailure, self).__init__() + super().__init__() self.layer = layer def runTest(self): @@ -532,11 +521,6 @@ def spawn_layer_in_subprocess(result, script_parts, options, features, args.extend(options.original_testrunner_args[1:]) - # this is because of a bug in Python (http://www.python.org/sf/900092) - if (options.profile == 'hotshot' - and sys.version_info[:3] <= (2, 4, 1)): - args.insert(1, '-O') - debugargs = args # save them before messing up for windows if sys.platform.startswith('win'): args = args[0] + ' ' + ' '.join([ @@ -575,7 +559,7 @@ def reader_thread(f, buf): if not line: break result.write(line) - except IOError as e: + except OSError as e: if e.errno == errno.EINTR: # If the subprocess dies before we finish reading its # output, a SIGCHLD signal can interrupt the reading. @@ -664,7 +648,7 @@ def _get_output_buffer(stream): return stream -class AbstractSubprocessResult(object): +class AbstractSubprocessResult: """A result of a subprocess layer run.""" num_ran = 0 @@ -691,7 +675,7 @@ class ImmediateSubprocessResult(AbstractSubprocessResult): """Sends complete output to queue.""" def __init__(self, layer_name, queue): - super(ImmediateSubprocessResult, self).__init__(layer_name, queue) + super().__init__(layer_name, queue) self.stream = _get_output_buffer(sys.stdout) def write(self, out): @@ -730,7 +714,7 @@ def resume_tests(script_parts, options, features, layers, failures, errors, elif (options.verbose > 1 and not options.subunit and not options.subunit_v2): result_factory = KeepaliveSubprocessResult - stdout_queue = Queue.Queue() + stdout_queue = queue.Queue() else: result_factory = DeferredSubprocessResult resume_number = int(options.processes > 1) @@ -767,7 +751,7 @@ def resume_tests(script_parts, options, features, layers, failures, errors, previous_output = output try: layer_name, output = stdout_queue.get(False) - except Queue.Empty: + except queue.Empty: break if layer_name != last_layer_intermediate_output: # Clarify what layer is reporting activity. @@ -908,20 +892,17 @@ def testTearDown(self): def _makeBufferedStdStream(self): """Make a buffered stream to replace a standard stream.""" - if sys.version_info[0] >= 3: - # The returned stream needs to have a 'buffer' attribute, since - # some tests may expect that to exist on standard streams, and a - # 'getvalue' method for the convenience of _restoreStdStreams. - # This requires some care. - class BufferedStandardStream(io.TextIOWrapper): - def getvalue(self): - return self.buffer.getvalue().decode( - encoding=self.encoding, errors=self.errors) - - return BufferedStandardStream( - io.BytesIO(), newline='\n', write_through=True) - else: - return StringIO() + # The returned stream needs to have a 'buffer' attribute, since + # some tests may expect that to exist on standard streams, and a + # 'getvalue' method for the convenience of _restoreStdStreams. + # This requires some care. + class BufferedStandardStream(io.TextIOWrapper): + def getvalue(self): + return self.buffer.getvalue().decode( + encoding=self.encoding, errors=self.errors) + + return BufferedStandardStream( + io.BytesIO(), newline='\n', write_through=True) def _setUpStdStreams(self): """Set up buffered standard streams, if requested.""" @@ -1054,8 +1035,6 @@ def stopTest(self, test): cycles = [[repr_lines(o) for o in c] for c in g.sccs()] del gc.garbage[:] g = obj = None # avoid to hold cyclic garbage - # Python 2: avoid to hold cyclic garbage - o = c = None # noqa: F841 gc.set_debug(gc_opts) gccount = gc.collect() \ if uses_refcounts and self.options.gc_after_test else 0 @@ -1114,7 +1093,7 @@ def layer_sort_key(layer): Based on the reverse MRO ordering in order to put layers with shared base layers next to each other. """ - seen = set([]) + seen = set() key = [] # Note: we cannot reuse gather_layers() here because it uses a @@ -1193,7 +1172,7 @@ def repr_lines(obj, max_width=75, max_lines=5): oi = pprint.pformat(obj) except Exception: # unprintable - oi = "%s instance at 0x%x" % (obj.__class__, id(obj)) + oi = "{} instance at 0x{:x}".format(obj.__class__, id(obj)) # limit cmps = oi.split("\n", max_lines) if len(cmps) > max_lines: diff --git a/src/zope/testrunner/shuffle.py b/src/zope/testrunner/shuffle.py index 194459c..2734c65 100644 --- a/src/zope/testrunner/shuffle.py +++ b/src/zope/testrunner/shuffle.py @@ -16,7 +16,6 @@ import math import random -import sys import time import zope.testrunner.feature @@ -26,7 +25,7 @@ class Shuffle(zope.testrunner.feature.Feature): """Take the tests found so far and shuffle them.""" def __init__(self, runner): - super(Shuffle, self).__init__(runner) + super().__init__(runner) self.active = runner.options.shuffle self.seed = runner.options.shuffle_seed if self.seed is None: @@ -37,9 +36,8 @@ def __init__(self, runner): def global_setup(self): rng = random.Random(self.seed) - if sys.version_info >= (3, 2): - # in case somebody tries to use a string as the seed - rng.seed(self.seed, version=1) + # in case somebody tries to use a string as the seed + rng.seed(self.seed, version=1) # Be careful to shuffle the layers in a deterministic order! for layer, suite in sorted(self.runner.tests_by_layer_name.items()): # Test suites cannot be modified through a public API. We thus @@ -59,9 +57,8 @@ def global_setup(self): floor = math.floor for i in reversed(range(1, len(tests))): # Pick an element in tests[:i+1] with which to exchange - # tests[i]. math.floor returns a float on Python 2, so we - # need int() until we drop Python 2 support. - j = int(floor(rng.random() * (i + 1))) + # tests[i]. + j = floor(rng.random() * (i + 1)) tests[i], tests[j] = tests[j], tests[i] self.runner.tests_by_layer_name[layer] = suite.__class__(tests) diff --git a/src/zope/testrunner/tb_format.py b/src/zope/testrunner/tb_format.py index bbd76b0..0834ce3 100644 --- a/src/zope/testrunner/tb_format.py +++ b/src/zope/testrunner/tb_format.py @@ -22,30 +22,25 @@ import zope.testrunner.feature -try: - _iter_chain = traceback._iter_chain -except AttributeError: - # Python 3.5 - def _iter_chain(exc, custom_tb=None, seen=None): - if seen is None: - seen = set() - seen.add(exc) - its = [] - context = exc.__context__ - cause = exc.__cause__ - if cause is not None and cause not in seen: - its.append(_iter_chain(cause, False, seen)) - its.append([(traceback._cause_message, None)]) - elif (context is not None and - not exc.__suppress_context__ and - context not in seen): - its.append(_iter_chain(context, None, seen)) - its.append([(traceback._context_message, None)]) - its.append([(exc, custom_tb or exc.__traceback__)]) - # itertools.chain is in an extension module and may be unavailable - for it in its: - for x in it: - yield x +def _iter_chain(exc, custom_tb=None, seen=None): + if seen is None: + seen = set() + seen.add(exc) + its = [] + context = exc.__context__ + cause = exc.__cause__ + if cause is not None and cause not in seen: + its.append(_iter_chain(cause, False, seen)) + its.append([(traceback._cause_message, None)]) + elif (context is not None and + not exc.__suppress_context__ and + context not in seen): + its.append(_iter_chain(context, None, seen)) + its.append([(traceback._context_message, None)]) + its.append([(exc, custom_tb or exc.__traceback__)]) + # itertools.chain is in an extension module and may be unavailable + for it in its: + yield from it def format_exception(t, v, tb, limit=None, chain=None): diff --git a/src/zope/testrunner/tests/test_doctest.py b/src/zope/testrunner/tests/test_doctest.py index b4ce41d..4760c74 100644 --- a/src/zope/testrunner/tests/test_doctest.py +++ b/src/zope/testrunner/tests/test_doctest.py @@ -13,10 +13,10 @@ ############################################################################## """Test harness for the test runner itself. """ -from __future__ import print_function import doctest import gc +import io import os import re import sys @@ -31,21 +31,10 @@ # because it s...s to maintain just one if sys.platform == 'win32': checker = renormalizing.RENormalizing([ - # 2.5 changed the way pdb reports exceptions - (re.compile(r":"), - r'exceptions.\1Error:'), - - # rewrite pdb prompt to ... the current location - # windows, py2.4 pdb seems not to put the '>' on doctest locations - # therefore we cut it here - (re.compile('^> doctest[^\n]+->None$', re.M), '...->None'), - - # rewrite pdb prompt to ... the current location - (re.compile('^> [^\n]+->None$', re.M), '> ...->None'), + # rewrite pdb prompt for coverage runs: + (re.compile('->None'), ''), (re.compile(r""), (r'?')), - (re.compile(r":"), - r'exceptions.\1Error:'), # testtools content formatter is used to mime-encode # tracebacks when the SubunitOutputFormatter is used, and the @@ -97,40 +86,14 @@ re.MULTILINE), r''), # (re.compile('^> [^\n]+->None$', re.M), '> ...->None'), - (re.compile('import pdb; pdb'), 'Pdb()'), # Py 2.3 - - # Python 3 exceptions are from the builtins module - (re.compile(r'builtins\.(SyntaxError|TypeError)'), - r'exceptions.\1'), - - # Python 3.6 introduces ImportError subclasses - (re.compile(r'ModuleNotFoundError:'), 'ImportError:'), - - # Python 3.3 has better exception messages - (re.compile("ImportError: No module named '(?:[^']*[.])?([^'.]*)'"), - r'ImportError: No module named \1'), - - # PyPy has different exception messages too - (re.compile("ImportError: No module named " - "(?:[a-zA-Z_0-9.]*[.])?([a-zA-Z_0-9]*)"), - r'ImportError: No module named \1'), - (re.compile("NameError: global name '([^']*)' is not defined"), - r"NameError: name '\1' is not defined"), - ]) else: # *nix checker = renormalizing.RENormalizing([ - # 2.5 changed the way pdb reports exceptions - (re.compile(r":"), - r'exceptions.\1Error:'), - - # rewrite pdb prompt to ... the current location - (re.compile('^> [^\n]+->None$', re.M), '> ...->None'), + # rewrite pdb prompt for coverage runs: + (re.compile('->None'), ''), (re.compile(r""), (r'?')), - (re.compile(r":"), - r'exceptions.\1Error:'), # this is a magic to put linefeeds into the doctest # on win it takes one step, linux is crazy about the same... @@ -169,67 +132,44 @@ r'(/__init__)?.py{\w+}", [^\n]+\n[^\n]+\n', re.MULTILINE), r''), - (re.compile('import pdb; pdb'), 'Pdb()'), # Py 2.3 - - # Python 3 exceptions are from the builtins module - (re.compile(r'builtins\.(SyntaxError|TypeError)'), - r'exceptions.\1'), - - # Python 3.6 introduces ImportError subclasses - (re.compile(r'ModuleNotFoundError:'), 'ImportError:'), - - # Python 3.3 has better exception messages - (re.compile("ImportError: No module named '(?:[^']*[.])?([^'.]*)'"), - r'ImportError: No module named \1'), - - # PyPy has different exception messages too - (re.compile("ImportError: No module named " - "(?:[a-zA-Z_0-9.]*[.])?([a-zA-Z_0-9]*)"), - r'ImportError: No module named \1'), - (re.compile("NameError: global name '([^']*)' is not defined"), - r"NameError: name '\1' is not defined"), - ]) -# On Python 3, monkey-patch doctest with our own _SpoofOut replacement. We +# Monkey-patch doctest with our own _SpoofOut replacement. We # need sys.stdout to be binary-capable so that we can pass through binary # output from test formatters cleanly, which is in particular required for # subunit. We don't expect to be able to do actual binary comparisons in # doctests, but that's OK. # See https://github.com/zopefoundation/zope.testrunner/pull/23 for the # background. -if sys.version_info[0] >= 3: - import io - - class _SpoofOut(io.TextIOWrapper): - def __init__(self): - super(_SpoofOut, self).__init__(io.BytesIO(), encoding='utf-8') - - def write(self, s): - super(_SpoofOut, self).write(s) - # Always flush immediately so that getvalue() never returns - # short results. - self.flush() - - def getvalue(self): - result = self.buffer.getvalue().decode('utf-8', 'replace') - # If anything at all was written, make sure there's a trailing - # newline. There's no way for the expected output to indicate - # that a trailing newline is missing. - if result and not result.endswith("\n"): - result += "\n" - # We're reading bytes, so we have to do universal newlines - # conversion by hand. - return result.replace(os.linesep, '\n') - - def truncate(self, size=None): - self.seek(size) - super(_SpoofOut, self).truncate() + +class _SpoofOut(io.TextIOWrapper): + def __init__(self): + super().__init__(io.BytesIO(), encoding='utf-8') + + def write(self, s): + super().write(s) + # Always flush immediately so that getvalue() never returns + # short results. + self.flush() + + def getvalue(self): + result = self.buffer.getvalue().decode('utf-8', 'replace') + # If anything at all was written, make sure there's a trailing + # newline. There's no way for the expected output to indicate + # that a trailing newline is missing. + if result and not result.endswith("\n"): + result += "\n" + # We're reading bytes, so we have to do universal newlines + # conversion by hand. + return result.replace(os.linesep, '\n') + + def truncate(self, size=None): + self.seek(size) + super().truncate() def setUp(test): - test.globs['print_function'] = print_function test.globs['saved-sys-info'] = ( sys.path[:], sys.argv[:], @@ -237,9 +177,8 @@ def setUp(test): ) if hasattr(gc, 'get_threshold'): test.globs['saved-gc-threshold'] = gc.get_threshold() - if sys.version_info[0] >= 3: - test.globs['saved-doctest-SpoofOut'] = doctest._SpoofOut - doctest._SpoofOut = _SpoofOut + test.globs['saved-doctest-SpoofOut'] = doctest._SpoofOut + doctest._SpoofOut = _SpoofOut test.globs['this_directory'] = os.path.split(__file__)[0] test.globs['testrunner_script'] = sys.argv[0] @@ -250,8 +189,7 @@ def tearDown(test): gc.set_threshold(*test.globs['saved-gc-threshold']) sys.modules.clear() sys.modules.update(test.globs['saved-sys-info'][2]) - if sys.version_info[0] >= 3: - doctest._SpoofOut = test.globs['saved-doctest-SpoofOut'] + doctest._SpoofOut = test.globs['saved-doctest-SpoofOut'] def test_suite(): @@ -397,8 +335,7 @@ def test_suite(): 'N.NNN seconds'), (re.compile(r'\(\d+[.]\d\d\d s\)'), '(N.NNN s)'), - # objects on cycle differ between PY2 and PY3 - # and different python 3 versions + # objects on cycle differ between different python versions (re.compile(r'\[\d+\]'), '[C]')]))) try: diff --git a/src/zope/testrunner/tests/test_find.py b/src/zope/testrunner/tests/test_find.py index 266b96c..00b44ed 100644 --- a/src/zope/testrunner/tests/test_find.py +++ b/src/zope/testrunner/tests/test_find.py @@ -34,7 +34,7 @@ class TestUniqueness(unittest.TestCase): """Test how the testrunner handles non-unique IDs.""" def setUp(self): - super(TestUniqueness, self).setUp() + super().setUp() suites = [ doctest.DocFileSuite('testrunner-ex/sampletests.rst'), doctest.DocFileSuite('testrunner-ex/sampletests.rst'), diff --git a/src/zope/testrunner/tests/test_runner.py b/src/zope/testrunner/tests/test_runner.py index aafc303..c94c6f2 100644 --- a/src/zope/testrunner/tests/test_runner.py +++ b/src/zope/testrunner/tests/test_runner.py @@ -35,10 +35,10 @@ def test_order_by_bases(self): # /|\ / # / | \ / # A1 A2 AB - class A(object): pass + class A: pass class A1(A): pass class A2(A): pass - class B(object): pass + class B: pass class AB(A, B): pass self.assertEqual(self.order(B, A1, A2, A1, AB, UnitTests), 'UnitTests, A1, A2, B, AB') @@ -49,9 +49,9 @@ class AB(A, B): pass self.assertEqual(self.sort_key(AB), 'B, A, AB') def test_order_by_bases_alphabetical_order(self): - class X(object): pass - class Y(object): pass - class Z(object): pass + class X: pass + class Y: pass + class Z: pass class A(Y): pass class B(X): pass class C(Z): pass @@ -69,7 +69,7 @@ def test_order_by_bases_diamond_hierarchy(self): # C E # \ / # F - class A(object): pass + class A: pass class B(A): pass class C(B): pass class D(A): pass @@ -92,7 +92,7 @@ def test_order_by_bases_shared_setup_trumps_alphabetical_order(self): # / \ \ # AAAABD \ MMMACF # ZZZABE - class A(object): pass + class A: pass class AB(A): pass class AC(A): pass class AAAABD(AB): pass @@ -140,9 +140,9 @@ def test_order_by_bases_reverse_tree(self): # B C # \ / # A - class F(object): pass - class E(object): pass - class D(object): pass + class F: pass + class E: pass + class D: pass class C(D, F): pass class B(D, E): pass class A(B, C): pass @@ -158,11 +158,11 @@ def test_order_by_bases_mro_is_complicated(self): # \ K3 / # \ |/ # ZZ - class A(object): pass - class B(object): pass - class C(object): pass - class D(object): pass - class E(object): pass + class A: pass + class B: pass + class C: pass + class D: pass + class E: pass class K1(A, B, C): pass class K2(D, B, E): pass class K3(D, A): pass @@ -230,7 +230,7 @@ def test_warnings_are_shown(self): class TestReprLines(unittest.TestCase): def test_unprintable(self): - class C(object): + class C: def __repr__(self): raise Exception diff --git a/src/zope/testrunner/tests/test_subunit.py b/src/zope/testrunner/tests/test_subunit.py index a45dc51..777ad68 100644 --- a/src/zope/testrunner/tests/test_subunit.py +++ b/src/zope/testrunner/tests/test_subunit.py @@ -21,12 +21,6 @@ from zope.testrunner import formatter -try: - unichr -except NameError: - unichr = chr # Python 3 - - try: import subunit subunit @@ -35,43 +29,26 @@ def test_suite(): return unittest.TestSuite() else: - class TestSubunitTracebackPrintingMixin(object): + class TestSubunitTracebackPrintingMixin: def makeByteStringFailure(self, text, encoding): try: - if sys.version_info[0] < 3: - # On Python 2, note that this deliberately throws a - # string of bytes instead of a unicode object. This - # simulates errors thrown by utf8-encoded doctests. - bytestr = text.encode(encoding) - self.fail(bytestr) - else: - # On Python 3, it's more accurate to just use the - # Unicode text directly. - self.fail(text) + # It's more accurate to just use the text directly. + self.fail(text) except self.failureException: return sys.exc_info() def test_print_failure_containing_utf8_bytestrings(self): - exc_info = self.makeByteStringFailure(unichr(6514), 'utf8') + exc_info = self.makeByteStringFailure(chr(6514), 'utf8') self.subunit_formatter.test_failure(self, 0, exc_info) assert b"AssertionError: \xe1\xa5\xb2" in self.output.getvalue() - # '\xe1\xa5\xb2'.decode('utf-8') == unichr(6514) + # '\xe1\xa5\xb2'.decode('utf-8') == chr(6514) def test_print_error_containing_utf8_bytestrings(self): - exc_info = self.makeByteStringFailure(unichr(6514), 'utf8') + exc_info = self.makeByteStringFailure(chr(6514), 'utf8') self.subunit_formatter.test_error(self, 0, exc_info) assert b"AssertionError: \xe1\xa5\xb2" in self.output.getvalue() - # '\xe1\xa5\xb2'.decode('utf-8') == unichr(6514) - - @unittest.skipIf( - sys.version_info[0] >= 3, - 'Tracebacks are always Unicode on Python 3') - def test_print_failure_containing_latin1_bytestrings(self): - exc_info = self.makeByteStringFailure(unichr(241), 'latin1') - self.subunit_formatter.test_failure(self, 0, exc_info) - assert b"AssertionError: \xef\xbf\xbd" in self.output.getvalue() - # '\xef\xbf\xbd'.decode('utf-8') = unichr(0xFFFD) + # '\xe1\xa5\xb2'.decode('utf-8') == chr(6514) class TestSubunitTracebackPrinting( TestSubunitTracebackPrintingMixin, unittest.TestCase): diff --git a/src/zope/testrunner/tests/test_threadsupport.py b/src/zope/testrunner/tests/test_threadsupport.py index d43527f..85b56ab 100644 --- a/src/zope/testrunner/tests/test_threadsupport.py +++ b/src/zope/testrunner/tests/test_threadsupport.py @@ -22,7 +22,7 @@ from ..threadsupport import enumerate -class ThreadMixin(object): +class ThreadMixin: """test thread.""" def __init__(self): self.lock = Lock() diff --git a/src/zope/testrunner/tests/testrunner-colors.rst b/src/zope/testrunner/tests/testrunner-colors.rst index 2283ebe..ebf7fbf 100644 --- a/src/zope/testrunner/tests/testrunner-colors.rst +++ b/src/zope/testrunner/tests/testrunner-colors.rst @@ -97,7 +97,7 @@ A failed test run highlights the failures in red: {red} File "testrunner-ex/sample2/sampletests_e.py", line 24, in g{normal} {red} x = y + 1 # noqa: F821{normal} {red} - __traceback_info__: I don't know what Y should be.{normal} - {red} NameError: global name 'y' is not defined{normal} + {red} NameError: name 'y' is not defined{normal} @@ -112,7 +112,7 @@ A failed test run highlights the failures in red: {normal} File "{boldblue}testrunner-ex/sample2/sampletests_e.py{normal}", line {boldred}24{normal}, in {boldcyan}g{normal} {cyan} x = y + 1 # noqa: F821{normal} {red} - __traceback_info__: I don't know what Y should be.{normal} - {red}NameError: global name 'y' is not defined{normal} + {red}NameError: name 'y' is not defined{normal} @@ -132,7 +132,7 @@ A failed test run highlights the failures in red: {red} f(){normal} {red} File "", line 2, in f{normal} {red} return x{normal} - {red} NameError: global name 'x' is not defined{normal} + {red} NameError: name 'x' is not defined{normal} diff --git a/src/zope/testrunner/tests/testrunner-debugging-import-failure.rst b/src/zope/testrunner/tests/testrunner-debugging-import-failure.rst index 3e774cf..e1a47e6 100644 --- a/src/zope/testrunner/tests/testrunner-debugging-import-failure.rst +++ b/src/zope/testrunner/tests/testrunner-debugging-import-failure.rst @@ -10,7 +10,7 @@ Post-mortem debugging also works when there is an import failure. ... with open(os.path.join(dir, filename), 'w') as f: ... f.write(body) ... try: - ... # Need to do this on Python 3.3 after creating new modules + ... # Need to do this after creating new modules: ... import importlib; importlib.invalidate_caches() ... except (ImportError, AttributeError): ... pass @@ -71,5 +71,3 @@ Post-mortem debugging also works when the test suite is invalid: EndRun raised >>> shutil.rmtree(tdir) - - diff --git a/src/zope/testrunner/tests/testrunner-debugging-layer-setup.rst b/src/zope/testrunner/tests/testrunner-debugging-layer-setup.rst index 881a72a..cbeb8f5 100644 --- a/src/zope/testrunner/tests/testrunner-debugging-layer-setup.rst +++ b/src/zope/testrunner/tests/testrunner-debugging-layer-setup.rst @@ -9,7 +9,7 @@ setup. ... with open(os.path.join(dir, filename), 'w') as f: ... f.write(body) ... try: - ... # Need to do this on Python 3.3 after creating new modules + ... # Need to do this after creating new modules: ... import importlib; importlib.invalidate_caches() ... except (ImportError, AttributeError): ... pass @@ -32,7 +32,7 @@ setup. ... suite = doctest.DocTestSuite() ... suite.layer = Layer ... return suite - ... + ... ... ''') >>> class Input: @@ -86,7 +86,7 @@ a subprocess: ... def setUp(self): ... x = 1 ... raise ValueError - ... + ... ... def a_test(): ... """ ... >>> None @@ -97,7 +97,7 @@ a subprocess: ... suite2 = doctest.DocTestSuite() ... suite2.layer = Layer2 ... return unittest.TestSuite((suite1, suite2)) - ... + ... ... ''') >>> import sys @@ -136,5 +136,3 @@ a subprocess: True >>> shutil.rmtree(tdir) - - diff --git a/src/zope/testrunner/tests/testrunner-debugging-nonprintable-exc.rst b/src/zope/testrunner/tests/testrunner-debugging-nonprintable-exc.rst index 8825a65..383e5dc 100644 --- a/src/zope/testrunner/tests/testrunner-debugging-nonprintable-exc.rst +++ b/src/zope/testrunner/tests/testrunner-debugging-nonprintable-exc.rst @@ -10,7 +10,7 @@ Post-mortem debugging also works when the exception cannot be printed ... with open(os.path.join(dir, filename), 'w') as f: ... f.write(body) ... try: - ... # Need to do this on Python 3.3 after creating new modules + ... # Need to do this on after creating new modules: ... import importlib; importlib.invalidate_caches() ... except (ImportError, AttributeError): ... pass @@ -18,11 +18,10 @@ Post-mortem debugging also works when the exception cannot be printed >>> write_file('tests.py', ... r''' ... import unittest - ... import six ... ... class MyTest(unittest.TestCase): ... def test(self): - ... self.assertEqual(six.u('a'), six.b('\xc4\x85').decode('UTF-8')) + ... self.assertEqual('a', b'\xc4\x85'.decode('UTF-8')) ... ''') >>> class Input: @@ -53,5 +52,3 @@ Post-mortem debugging also works when the exception cannot be printed False >>> shutil.rmtree(tdir) - - diff --git a/src/zope/testrunner/tests/testrunner-edge-cases.rst b/src/zope/testrunner/tests/testrunner-edge-cases.rst index aedbff8..75cd615 100644 --- a/src/zope/testrunner/tests/testrunner-edge-cases.rst +++ b/src/zope/testrunner/tests/testrunner-edge-cases.rst @@ -29,7 +29,7 @@ affecting the Python path: Module: sampletestsf Traceback (most recent call last): - ImportError: No module named sampletestsf + ModuleNotFoundError: No module named 'sampletestsf' ... >>> sys.path.append(directory_with_tests) diff --git a/src/zope/testrunner/tests/testrunner-errors.rst b/src/zope/testrunner/tests/testrunner-errors.rst index b00b804..092d187 100644 --- a/src/zope/testrunner/tests/testrunner-errors.rst +++ b/src/zope/testrunner/tests/testrunner-errors.rst @@ -49,7 +49,7 @@ be read only): File "testrunner-ex/sample2/sampletests_e.py", line 24, in g x = y + 1 # noqa: F821 - __traceback_info__: I don't know what Y should be. - NameError: global name 'y' is not defined + NameError: name 'y' is not defined @@ -62,7 +62,7 @@ be read only): File "testrunner-ex/sample2/sampletests_e.py", line 24, in g x = y + 1 # noqa: F821 - __traceback_info__: I don't know what Y should be. - NameError: global name 'y' is not defined + NameError: name 'y' is not defined @@ -82,7 +82,7 @@ be read only): f() File "", line 2, in f return x - NameError: global name 'x' is not defined + NameError: name 'x' is not defined @@ -135,7 +135,7 @@ there'll be a summary of the errors at the end of the test: File "testrunner-ex/sample2/sampletests_e.py", line 24, in g x = y + 1 # noqa: F821 - __traceback_info__: I don't know what Y should be. - NameError: global name 'y' is not defined + NameError: name 'y' is not defined ... @@ -149,7 +149,7 @@ there'll be a summary of the errors at the end of the test: File "testrunner-ex/sample2/sampletests_e.py", line 24, in g x = y + 1 # noqa: F821 - __traceback_info__: I don't know what Y should be. - NameError: global name 'y' is not defined + NameError: name 'y' is not defined ... @@ -169,7 +169,7 @@ there'll be a summary of the errors at the end of the test: f() File "", line 2, in f return x - NameError: global name 'x' is not defined + NameError: name 'x' is not defined . @@ -225,7 +225,7 @@ Similarly for progress output, the progress ticker will be interrupted: File "testrunner-ex/sample2/sampletests_e.py", line 24, in g x = y + 1 # noqa: F821 - __traceback_info__: I don't know what Y should be. - NameError: global name 'y' is not defined + NameError: name 'y' is not defined 2/47 (4.3%)\r \r @@ -242,7 +242,7 @@ Similarly for progress output, the progress ticker will be interrupted: File "testrunner-ex/sample2/sampletests_e.py", line 24, in g x = y + 1 # noqa: F821 - __traceback_info__: I don't know what Y should be. - NameError: global name 'y' is not defined + NameError: name 'y' is not defined 5/47 (10.6%)\r \r @@ -266,7 +266,7 @@ Similarly for progress output, the progress ticker will be interrupted: f() File "", line 2, in f return x - NameError: global name 'x' is not defined + NameError: name 'x' is not defined 8/47 (17.0%) @@ -826,7 +826,7 @@ Then run the tests: Traceback (most recent call last): File "testrunner-ex/sample2/sample21/sampletests_i.py", line 15, in ? import zope.testrunner.huh # noqa: F401 - ImportError: No module named huh + ModuleNotFoundError: No module named 'zope.testrunner.huh' Module: sample2.sample23.sampletests_i diff --git a/src/zope/testrunner/tests/testrunner-ex/gc-after-test.py b/src/zope/testrunner/tests/testrunner-ex/gc-after-test.py index 9a82674..864dea6 100644 --- a/src/zope/testrunner/tests/testrunner-ex/gc-after-test.py +++ b/src/zope/testrunner/tests/testrunner-ex/gc-after-test.py @@ -2,8 +2,6 @@ from unittest import TestCase from warnings import warn -from six import PY2 - class GcAfterTestTests(TestCase): def tearDown(self): @@ -42,14 +40,14 @@ def f(): f() -class _Cycle(object): +class _Cycle: """Auxiliary class creating a reference cycle.""" def __init__(self, **kw): self.self = self # create reference cycle self.__dict__.update(kw) -class _Resource(object): +class _Resource: """Auxiliary class emulating a resource.""" closed = False @@ -58,9 +56,6 @@ def close(self): def __del__(self): if not self.closed: - warn(ResourceWarning("not closed")) - - -if PY2: - class ResourceWarning(Warning): - pass + warn(ResourceWarning( + "not closed" + " - this is no error: testing ResourceWarning here")) diff --git a/src/zope/testrunner/tests/testrunner-ex/leak.py b/src/zope/testrunner/tests/testrunner-ex/leak.py index b118d35..c9e0af6 100644 --- a/src/zope/testrunner/tests/testrunner-ex/leak.py +++ b/src/zope/testrunner/tests/testrunner-ex/leak.py @@ -21,7 +21,7 @@ def __init__(self): self.x = 'x' -class Leakable(object): +class Leakable: def __init__(self): self.x = 'x' diff --git a/src/zope/testrunner/tests/testrunner-ex/sample2/stdstreamstest.py b/src/zope/testrunner/tests/testrunner-ex/sample2/stdstreamstest.py index cf2da03..cf2efbc 100644 --- a/src/zope/testrunner/tests/testrunner-ex/sample2/stdstreamstest.py +++ b/src/zope/testrunner/tests/testrunner-ex/sample2/stdstreamstest.py @@ -21,7 +21,7 @@ class Test(unittest.TestCase): def _getStreamBuffer(self, stream): - return stream.buffer if sys.version_info[0] >= 3 else stream + return stream.buffer def test_stdout_success(self): sys.stdout.write("stdout output on success\n") diff --git a/src/zope/testrunner/tests/testrunner-ex/samplelayers.py b/src/zope/testrunner/tests/testrunner-ex/samplelayers.py index f3c4a97..30f4b9c 100644 --- a/src/zope/testrunner/tests/testrunner-ex/samplelayers.py +++ b/src/zope/testrunner/tests/testrunner-ex/samplelayers.py @@ -27,14 +27,14 @@ class Layer1: def setUp(self): global layer if layer != self.base: - raise ValueError("Bad layer, %s, for %s." % (layer, self)) + raise ValueError("Bad layer, {}, for {}.".format(layer, self)) layer = self.layer setUp = classmethod(setUp) def tearDown(self): global layer if layer != self.layer: - raise ValueError("Bad layer, %s, for %s." % (layer, self)) + raise ValueError("Bad layer, {}, for {}.".format(layer, self)) layer = self.base tearDown = classmethod(tearDown) @@ -46,14 +46,14 @@ class Layerx: def setUp(self): global layerx if layerx != self.basex: - raise ValueError("Bad layerx, %s, for %s." % (layerx, self)) + raise ValueError("Bad layerx, {}, for {}.".format(layerx, self)) layerx = self.layerx setUp = classmethod(setUp) def tearDown(self): global layerx if layerx != self.layerx: - raise ValueError("Bad layerx, %s, for %s." % (layerx, self)) + raise ValueError("Bad layerx, {}, for {}.".format(layerx, self)) layerx = self.basex tearDown = classmethod(tearDown) @@ -77,22 +77,22 @@ class Layer111(Layerx, Layer11): def setUp(self): global layer if layer != self.base: - raise ValueError("Bad layer, %s, for %s." % (layer, self)) + raise ValueError("Bad layer, {}, for {}.".format(layer, self)) layer = self.layer global layerx if layerx != self.basex: - raise ValueError("Bad layerx, %s, for %s." % (layerx, self)) + raise ValueError("Bad layerx, {}, for {}.".format(layerx, self)) layerx = self.layerx setUp = classmethod(setUp) def tearDown(self): global layer if layer != self.layer: - raise ValueError("Bad layer, %s, for %s." % (layer, self)) + raise ValueError("Bad layer, {}, for {}.".format(layer, self)) layer = self.base global layerx if layerx != self.layerx: - raise ValueError("Bad layerx, %s, for %s." % (layerx, self)) + raise ValueError("Bad layerx, {}, for {}.".format(layerx, self)) layerx = self.basex tearDown = classmethod(tearDown) @@ -111,22 +111,22 @@ class Layer112(Layerx, Layer11): def setUp(self): global layer if layer != self.base: - raise ValueError("Bad layer, %s, for %s." % (layer, self)) + raise ValueError("Bad layer, {}, for {}.".format(layer, self)) layer = self.layer global layerx if layerx != self.basex: - raise ValueError("Bad layerx, %s, for %s." % (layerx, self)) + raise ValueError("Bad layerx, {}, for {}.".format(layerx, self)) layerx = self.layerx setUp = classmethod(setUp) def tearDown(self): global layer if layer != self.layer: - raise ValueError("Bad layer, %s, for %s." % (layer, self)) + raise ValueError("Bad layer, {}, for {}.".format(layer, self)) layer = self.base global layerx if layerx != self.layerx: - raise ValueError("Bad layerx, %s, for %s." % (layerx, self)) + raise ValueError("Bad layerx, {}, for {}.".format(layerx, self)) layerx = self.basex tearDown = classmethod(tearDown) diff --git a/src/zope/testrunner/tests/testrunner-gc.rst b/src/zope/testrunner/tests/testrunner-gc.rst index 15cdd5f..bea3305 100644 --- a/src/zope/testrunner/tests/testrunner-gc.rst +++ b/src/zope/testrunner/tests/testrunner-gc.rst @@ -66,7 +66,7 @@ values: Specifying more than 3 ``--gc`` options is not allowed: - >>> from six import StringIO + >>> from io import StringIO >>> out = StringIO() >>> stdout = sys.stdout >>> sys.stdout = out @@ -92,7 +92,7 @@ in the library documentation for the gc module. The output statistics are written to standard error. - >>> from six import StringIO + >>> from io import StringIO >>> err = StringIO() >>> stderr = sys.stderr >>> sys.stderr = err diff --git a/src/zope/testrunner/tests/testrunner-layers-cantfind.rst b/src/zope/testrunner/tests/testrunner-layers-cantfind.rst index 22f995f..fa28e51 100644 --- a/src/zope/testrunner/tests/testrunner-layers-cantfind.rst +++ b/src/zope/testrunner/tests/testrunner-layers-cantfind.rst @@ -22,7 +22,7 @@ method out. >>> sys.stdout.close = lambda: None - >>> from six import StringIO + >>> from io import StringIO >>> orig_stderr = sys.stderr >>> sys.stderr = fake_stderr = StringIO() @@ -47,4 +47,3 @@ Cleanup >>> del sys.stdout.close >>> sys.stderr = orig_stderr - diff --git a/src/zope/testrunner/tests/testrunner-layers-ntd.rst b/src/zope/testrunner/tests/testrunner-layers-ntd.rst index cbd0403..7e65d09 100644 --- a/src/zope/testrunner/tests/testrunner-layers-ntd.rst +++ b/src/zope/testrunner/tests/testrunner-layers-ntd.rst @@ -213,8 +213,8 @@ that is run as a subprocess: ********************************************************************** --Return-- - > doctest.py(351)set_trace()->None - -> Pdb().set_trace() + > (3)?() + -> import pdb; pdb.set_trace() (Pdb) c ********************************************************************** @@ -222,8 +222,8 @@ that is run as a subprocess: ********************************************************************** --Return-- - > doctest.py(351)set_trace()->None - -> Pdb().set_trace() + > testrunner-ex/sample2/sampletests_ntds.py(NNN)f() + -> import pdb; pdb.set_trace() (Pdb) c ********************************************************************** @@ -244,7 +244,7 @@ If a test is run in a subprocess and it generates output on stderr (as stderrtest does), the output is ignored (but it doesn't cause a SubprocessError like it once did). - >>> from six import StringIO + >>> from io import StringIO >>> real_stderr = sys.stderr >>> sys.stderr = StringIO() diff --git a/src/zope/testrunner/tests/testrunner-report-skipped.rst b/src/zope/testrunner/tests/testrunner-report-skipped.rst index 3707304..a70d7b6 100644 --- a/src/zope/testrunner/tests/testrunner-report-skipped.rst +++ b/src/zope/testrunner/tests/testrunner-report-skipped.rst @@ -1,8 +1,7 @@ testrunner handling of skipped tests ==================================== -`unittest 2`_, which has been merged into Python 2.7 and Python 3.1, provides a -way to "skip" tests: +``unittest`` provides a way to "skip" tests, see http://docs.python.org/2/library/unittest.html#skipping-tests-and-expected-failures This feature is reported by the test runner. diff --git a/src/zope/testrunner/tests/testrunner-subunit-v2.rst b/src/zope/testrunner/tests/testrunner-subunit-v2.rst index f992d9e..43d498e 100644 --- a/src/zope/testrunner/tests/testrunner-subunit-v2.rst +++ b/src/zope/testrunner/tests/testrunner-subunit-v2.rst @@ -56,18 +56,12 @@ For easier doctesting, we use a helper that summarizes the output. >>> def subunit_summarize(func, *args, **kwargs): ... orig_stdout = sys.stdout ... try: - ... if sys.version_info[0] >= 3: - ... import io - ... sys.stdout = io.TextIOWrapper( - ... io.BytesIO(), encoding='utf-8') - ... else: - ... import StringIO - ... sys.stdout = StringIO.StringIO() + ... import io + ... sys.stdout = io.TextIOWrapper(io.BytesIO(), encoding='utf-8') ... ret = func(*args, **kwargs) ... sys.stdout.flush() ... buf = sys.stdout - ... if sys.version_info[0] >= 3: - ... buf = buf.detach() + ... buf = buf.detach() ... buf.seek(0) ... finally: ... sys.stdout = orig_stdout @@ -235,7 +229,7 @@ https://bugs.launchpad.net/subunit/+bug/1740158.) testrunner-ex/sample2/sampletests_e.py", Line NNN, in g x = y + 1 # noqa: F821 - __traceback_info__: I don't know what Y should be. - NameError: global name 'y' is not defined + NameError: name 'y' is not defined id=sample2.sampletests_e.eek status=fail tags=(zope:layer:zope.testrunner.layer.UnitTests) @@ -256,7 +250,7 @@ https://bugs.launchpad.net/subunit/+bug/1740158.) testrunner-ex/sample2/sampletests_e.py", Line NNN, in g x = y + 1 # noqa: F821 - __traceback_info__: I don't know what Y should be. - NameError: global name 'y' is not defined + NameError: name 'y' is not defined id=sample2.sampletests_e.Test.test3 status=fail tags=(zope:layer:zope.testrunner.layer.UnitTests) @@ -282,7 +276,7 @@ https://bugs.launchpad.net/subunit/+bug/1740158.) f() File "", Line NNN, in f return x - NameError: global name 'x' is not defined + NameError: name 'x' is not defined id=e_rst status=fail tags=(zope:layer:zope.testrunner.layer.UnitTests) id=zope.testrunner.layer.UnitTests:tearDown status=inprogress !runnable @@ -455,7 +449,7 @@ Let's run tests including a module with some bad syntax: Traceback (most recent call last): File "/home/benji/workspace/all-the-trunks/zope.testrunner/src/zope/testrunner/testrunner-ex/sample2/sample21/sampletests_i.py", line 16, in import zope.testrunner.huh # noqa: F401... - ImportError: No module named huh + ModuleNotFoundError: No module named 'zope.testrunner.huh' id=sample2.sample21.sampletests_i status=fail tags=(zope:import_error) id=sample2.sample23.sampletests_i status=inprogress @@ -613,7 +607,7 @@ Note that debugging doesn't work when running tests in a subprocess: id=sample3.sampletests_ntd.TestSomething.test_error1 traceback (text/x-traceback...) Traceback (most recent call last): - File "/usr/lib/python2.6/unittest.py", line 305, in debug + File "/usr/lib/python3.11/unittest.py", line 305, in debug getattr(self, self._testMethodName)() File "/home/jml/src/zope.testrunner/subunit-output-formatter/src/zope/testing/testrunner/testrunner-ex/sample3/sampletests_ntd.py", line 42, in test_error1 raise TypeError("Can we see errors") @@ -631,7 +625,7 @@ Note that debugging doesn't work when running tests in a subprocess: id=sample3.sampletests_ntd.TestSomething.test_error2 traceback (text/x-traceback...) Traceback (most recent call last): - File "/usr/lib/python2.6/unittest.py", line 305, in debug + File "/usr/lib/python3.11/unittest.py", line 305, in debug getattr(self, self._testMethodName)() File "/home/jml/src/zope.testrunner/subunit-output-formatter/src/zope/testing/testrunner/testrunner-ex/sample3/sampletests_ntd.py", line 45, in test_error2 raise TypeError("I hope so") @@ -649,11 +643,11 @@ Note that debugging doesn't work when running tests in a subprocess: id=sample3.sampletests_ntd.TestSomething.test_fail1 traceback (text/x-traceback...) Traceback (most recent call last): - File "/usr/lib/python2.6/unittest.py", line 305, in debug + File "/usr/lib/python3.11/unittest.py", line 305, in debug getattr(self, self._testMethodName)() File "/home/jml/src/zope.testrunner/subunit-output-formatter/src/zope/testing/testrunner/testrunner-ex/sample3/sampletests_ntd.py", line 48, in test_fail1 self.assertEqual(1, 2) - File "/usr/lib/python2.6/unittest.py", line 350, in failUnlessEqual + File "/usr/lib/python3.11/unittest.py", line 350, in failUnlessEqual (msg or '%r != %r' % (first, second)) AssertionError: 1 != 2 @@ -669,11 +663,11 @@ Note that debugging doesn't work when running tests in a subprocess: id=sample3.sampletests_ntd.TestSomething.test_fail2 traceback (text/x-traceback...) Traceback (most recent call last): - File "/usr/lib/python2.6/unittest.py", line 305, in debug + File "/usr/lib/python3.11/unittest.py", line 305, in debug getattr(self, self._testMethodName)() File "/home/jml/src/zope.testrunner/subunit-output-formatter/src/zope/testing/testrunner/testrunner-ex/sample3/sampletests_ntd.py", line 51, in test_fail2 self.assertEqual(1, 3) - File "/usr/lib/python2.6/unittest.py", line 350, in failUnlessEqual + File "/usr/lib/python3.11/unittest.py", line 350, in failUnlessEqual (msg or '%r != %r' % (first, second)) AssertionError: 1 != 3 diff --git a/src/zope/testrunner/tests/testrunner-subunit.rst b/src/zope/testrunner/tests/testrunner-subunit.rst index f895ede..9907f91 100644 --- a/src/zope/testrunner/tests/testrunner-subunit.rst +++ b/src/zope/testrunner/tests/testrunner-subunit.rst @@ -209,7 +209,7 @@ Errors are recorded in the subunit stream as MIME-encoded chunks of text. testrunner-ex/sample2/sampletests_e.py", Line NNN, in g x = y + 1 # noqa: F821 - __traceback_info__: I don't know what Y should be. - NameError: global name 'y' is not defined + NameError: name 'y' is not defined 0\r ] @@ -237,7 +237,7 @@ Errors are recorded in the subunit stream as MIME-encoded chunks of text. testrunner-ex/sample2/sampletests_e.py", Line NNN, in g x = y + 1 # noqa: F821 - __traceback_info__: I don't know what Y should be. - NameError: global name 'y' is not defined + NameError: name 'y' is not defined 0\r ] @@ -270,7 +270,7 @@ Errors are recorded in the subunit stream as MIME-encoded chunks of text. f() File "", Line NNN, in f return x - NameError: global name 'x' is not defined + NameError: name 'x' is not defined 0\r ] @@ -487,7 +487,7 @@ Let's run tests including a module with some bad syntax: Traceback (most recent call last): File "/home/benji/workspace/all-the-trunks/zope.testrunner/src/zope/testrunner/testrunner-ex/sample2/sample21/sampletests_i.py", line 16, in import zope.testrunner.huh # noqa: F401... - ImportError: No module named huh + ModuleNotFoundError: No module named 'zope.testrunner.huh' ] test: sample2.sample23.sampletests_i tags: zope:import_error @@ -703,7 +703,7 @@ Note that debugging doesn't work when running tests in a subprocess: 16A\r Traceback (most recent call last): - File "/usr/lib/python2.6/unittest.py", line 305, in debug + File "/usr/lib/python3.11/unittest.py", line 305, in debug getattr(self, self._testMethodName)() File "/home/jml/src/zope.testrunner/subunit-output-formatter/src/zope/testing/testrunner/testrunner-ex/sample3/sampletests_ntd.py", line 42, in test_error1 raise TypeError("Can we see errors") @@ -723,7 +723,7 @@ Note that debugging doesn't work when running tests in a subprocess: 15A\r Traceback (most recent call last): - File "/usr/lib/python2.6/unittest.py", line 305, in debug + File "/usr/lib/python3.11/unittest.py", line 305, in debug getattr(self, self._testMethodName)() File "/home/jml/src/zope.testrunner/subunit-output-formatter/src/zope/testing/testrunner/testrunner-ex/sample3/sampletests_ntd.py", line 45, in test_error2 raise TypeError("I hope so") @@ -743,11 +743,11 @@ Note that debugging doesn't work when running tests in a subprocess: 1C5\r Traceback (most recent call last): - File "/usr/lib/python2.6/unittest.py", line 305, in debug + File "/usr/lib/python3.11/unittest.py", line 305, in debug getattr(self, self._testMethodName)() File "/home/jml/src/zope.testrunner/subunit-output-formatter/src/zope/testing/testrunner/testrunner-ex/sample3/sampletests_ntd.py", line 48, in test_fail1 self.assertEqual(1, 2) - File "/usr/lib/python2.6/unittest.py", line 350, in failUnlessEqual + File "/usr/lib/python3.11/unittest.py", line 350, in failUnlessEqual (msg or '%r != %r' % (first, second)) AssertionError: 1 != 2 0\r @@ -765,11 +765,11 @@ Note that debugging doesn't work when running tests in a subprocess: 1C5\r Traceback (most recent call last): - File "/usr/lib/python2.6/unittest.py", line 305, in debug + File "/usr/lib/python3.11/unittest.py", line 305, in debug getattr(self, self._testMethodName)() File "/home/jml/src/zope.testrunner/subunit-output-formatter/src/zope/testing/testrunner/testrunner-ex/sample3/sampletests_ntd.py", line 51, in test_fail2 self.assertEqual(1, 3) - File "/usr/lib/python2.6/unittest.py", line 350, in failUnlessEqual + File "/usr/lib/python3.11/unittest.py", line 350, in failUnlessEqual (msg or '%r != %r' % (first, second)) AssertionError: 1 != 3 0\r diff --git a/src/zope/testrunner/tests/testrunner-unexpected-success.rst b/src/zope/testrunner/tests/testrunner-unexpected-success.rst index 6dbd72b..2395449 100644 --- a/src/zope/testrunner/tests/testrunner-unexpected-success.rst +++ b/src/zope/testrunner/tests/testrunner-unexpected-success.rst @@ -1,10 +1,7 @@ testrunner handling of unexpected success ========================================= -Python 2.7 introduced the concept of expectedFailures to unittest. -See http://www.voidspace.org.uk/python/articles/unittest2.shtml#more-skipping - -Although testrunner is currently not able to hande unexpected successes +Although testrunner is currently not able to handle unexpected successes correctly at least it does not report them as successes. diff --git a/src/zope/testrunner/tests/testrunner-wo-source.rst b/src/zope/testrunner/tests/testrunner-wo-source.rst index dd8ded2..09e4311 100644 --- a/src/zope/testrunner/tests/testrunner-wo-source.rst +++ b/src/zope/testrunner/tests/testrunner-wo-source.rst @@ -77,19 +77,18 @@ output is the same as when running with .py source above. The absence of "removing stale bytecode ..." messages shows that ``--usecompiled`` also implies ``--keepbytecode``: - >>> if sys.version_info >= (3, 2): - ... # PEP-3147: pyc files in __pycache__ directories cannot be - ... # imported; legacy source-less imports need to use the legacy - ... # layout - ... for root, dirs, files in os.walk(dst): - ... for f in files: - ... if f.endswith((".pyc", ".pyo")): - ... # "root/f" is "dirname/__pycache__/name.magic.ext" - ... dirname = os.path.dirname(os.path.abspath(root)) - ... namewmagic, ext = os.path.splitext(os.path.basename(f)) - ... newname = os.path.splitext(namewmagic)[0] + ext - ... os.rename(os.path.join(root, f), - ... os.path.join(dirname, newname)) + >>> # PEP-3147: pyc files in __pycache__ directories cannot be + ... # imported; legacy source-less imports need to use the legacy + ... # layout + ... for root, dirs, files in os.walk(dst): + ... for f in files: + ... if f.endswith((".pyc", ".pyo")): + ... # "root/f" is "dirname/__pycache__/name.magic.ext" + ... dirname = os.path.dirname(os.path.abspath(root)) + ... namewmagic, ext = os.path.splitext(os.path.basename(f)) + ... newname = os.path.splitext(namewmagic)[0] + ext + ... os.rename(os.path.join(root, f), + ... os.path.join(dirname, newname)) >>> testrunner.run_internal(mydefaults, ["test", "--usecompiled"]) Running tests at level 1 diff --git a/src/zope/testrunner/threadsupport.py b/src/zope/testrunner/threadsupport.py index a410493..11aabe1 100644 --- a/src/zope/testrunner/threadsupport.py +++ b/src/zope/testrunner/threadsupport.py @@ -31,12 +31,12 @@ def enumerate(): """return sequence of proxies for the currently running threads.""" running = set(current_frames()) - th_known = dict((t.ident, t) for t in threading.enumerate()) + th_known = {t.ident: t for t in threading.enumerate()} return [ThreadProxy(th_known[i] if i in th_known else DummyThread(i)) for i in running] -class ThreadProxy(object): +class ThreadProxy: """auxiliary class to provide ident based ``__eq__``.""" def __init__(self, thread): self.thread = thread @@ -51,7 +51,7 @@ def __getattr__(self, k): return getattr(self.thread, k) -class DummyThread(object): +class DummyThread: """auxiliary to represent a thread unknown to ``threading``.""" def __init__(self, ident): self.ident = ident diff --git a/tox.ini b/tox.ini index a1ac457..b910f00 100644 --- a/tox.ini +++ b/tox.ini @@ -4,19 +4,15 @@ minversion = 3.18 envlist = lint - py27 - py35 - py36 py37 py38 py39 py310 py311 - pypy pypy3 docs coverage - py{27,35,36,37,38,39,310,311,py,py3}-subunit + py{37,38,39,310,311,py3}-subunit [testenv] usedevelop = true @@ -67,7 +63,6 @@ allowlist_externals = mkdir deps = coverage - coverage-python-version commands = mkdir -p {toxinidir}/parts/htmlcov coverage run -m zope.testrunner --test-path=src {posargs:-vc} @@ -76,7 +71,6 @@ commands = [coverage:run] branch = True -plugins = coverage_python_version source = zope.testrunner [coverage:report]