diff --git a/.travis.yml b/.travis.yml index 2392754..8fc5429 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,9 @@ language: python dist: xenial python: - - 2.7 - - 3.5 - 3.6 - 3.7 - 3.8 - - pypy - pypy3 install: - pip install tox-travis coveralls diff --git a/CHANGES.rst b/CHANGES.rst index 9acc96b..02d12e3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,10 +2,18 @@ Change log for gocept.pytestlayer ================================= -6.4 (unreleased) +7.0 (unreleased) ================ -- Nothing changed yet. +Backwards imcompatible changes +------------------------------ + +- Drop support for Python 2.7 and 3.5 and ``pytest < 5.0``. (#8) + +Features +-------- + +- Support ``pytest >= 6.0``. (#8) 6.3 (2020-05-15) diff --git a/README.rst b/README.rst index 49a9e49..1616cc8 100644 --- a/README.rst +++ b/README.rst @@ -18,9 +18,8 @@ The gocept.pytestlayer distribution Integration of zope.testrunner-style test layers into the `py.test`_ framework -This package is compatible with Python versions 2.7 and 3.5 - 3.8 including -PyPy implementation. (To run its tests successfully you should use at least -Python 2.7.4 because of a bug in earlier Python 2.7 versions.) +This package is compatible with Python versions 3.6 - 3.8 including +PyPy3. .. _`py.test` : http://pytest.org diff --git a/setup.py b/setup.py index 684185b..45abc8d 100644 --- a/setup.py +++ b/setup.py @@ -5,20 +5,14 @@ setup( name='gocept.pytestlayer', - version='6.4.dev0', + version='7.0.dev0', python_requires=', '.join([ - '>=2.7', - '!=3.0.*', - '!=3.1.*', - '!=3.2.*', - '!=3.3.*', - '!=3.4.*', + '>=3.6', ]), install_requires=[ 'pytest', 'setuptools', - 'six', 'zope.dottedname', ], @@ -55,10 +49,7 @@ Natural Language :: English 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 diff --git a/src/gocept/pytestlayer/_compat.py b/src/gocept/pytestlayer/_compat.py index ab69589..b0cb2a9 100644 --- a/src/gocept/pytestlayer/_compat.py +++ b/src/gocept/pytestlayer/_compat.py @@ -1,23 +1,17 @@ -import six +# copied from Python 2.7 source code as plone testing uses __bases__ +def _searchbases(cls, accum): + # Simulate the "classic class" search order. + if cls in accum: + return + accum.append(cls) + for base in cls.__bases__: + _searchbases(base, accum) - -if six.PY2: - from inspect import getmro -else: - # copied from Python 2.7 source code as plone testing uses __bases__ - def _searchbases(cls, accum): - # Simulate the "classic class" search order. - if cls in accum: - return - accum.append(cls) - for base in cls.__bases__: - _searchbases(base, accum) - - def getmro(cls): - "Return tuple of base classes in method resolution order." - if hasattr(cls, "__mro__"): - return cls.__mro__ - else: - result = [] - _searchbases(cls, result) - return tuple(result) +def getmro(cls): + "Return tuple of base classes in method resolution order." + if hasattr(cls, "__mro__"): + return cls.__mro__ + else: + result = [] + _searchbases(cls, result) + return tuple(result) diff --git a/src/gocept/pytestlayer/fixture.py b/src/gocept/pytestlayer/fixture.py index 920ae1b..79bc454 100644 --- a/src/gocept/pytestlayer/fixture.py +++ b/src/gocept/pytestlayer/fixture.py @@ -2,7 +2,6 @@ import imp import pytest import re -import six import time import zope.dottedname.resolve @@ -26,7 +25,7 @@ def timer(request, text): yield if verbose: time_taken = time.time() - start - reporter.write("{0:.3f}".format(time_taken), green=1, bold=1) + reporter.write(f"{time_taken:.3f}", green=1, bold=1) reporter.write_line(" seconds.") @@ -36,7 +35,7 @@ def setup_layer(layer, request): layer_name = get_layer_name(layer) if hasattr(layer, 'setUp'): print(layer_name) - with timer(request, "Set up {0} in ".format(layer_name)): + with timer(request, f"Set up {layer_name} in "): layer.setUp() state.current.add(layer) @@ -46,7 +45,7 @@ def teardown_layer(layer, request): state = request.session.zopelayer_state layer_name = get_layer_name(layer) if hasattr(layer, 'tearDown'): - with timer(request, "Tear down {0} in ".format(layer_name)): + with timer(request, f"Tear down {layer_name} in "): layer.tearDown() state.current.remove(layer) @@ -137,7 +136,7 @@ def create(*layers, **kw): ns = {} for layer in layers: - if isinstance(layer, six.string_types): + if isinstance(layer, str): layer = zope.dottedname.resolve.resolve(layer) ns.update(_create_single(layer, **kw)) return ns @@ -215,9 +214,9 @@ def raise_if_bad_layer(layer): if not hasattr(layer, '__bases__'): raise RuntimeError( - "The layer {0} has no __bases__ attribute." + "The layer {layer!r} has no __bases__ attribute." " Layers may be of two sorts: class or instance with __bases__" - " attribute.".format(repr(layer)) + " attribute." ) diff --git a/src/gocept/pytestlayer/layered.py b/src/gocept/pytestlayer/layered.py index 9f1808c..acd976f 100644 --- a/src/gocept/pytestlayer/layered.py +++ b/src/gocept/pytestlayer/layered.py @@ -10,22 +10,26 @@ def collect(self): suite = self.obj() for item, layer in walk_suite(suite): fixture.parsefactories(self.parent, layer) - yield LayeredTestCaseInstance(item, self, layer) + yield LayeredTestCaseInstance.from_parent( + parent=self, obj=item, layer=layer) class LayeredTestCaseInstance(pytest.Collector): - def __init__(self, obj, parent, layer): + @classmethod + def from_parent(cls, parent, obj, layer, **kw): testname = repr(obj) # fantastic doctest API :( - super(pytest.Collector, self).__init__(testname, parent=parent) + instance = super(pytest.Collector, cls).from_parent( + parent=parent, name=testname) # store testcase instance and layer # to pass them to function - self.obj = obj - self.layer = layer - self.extra_keyword_matches.update(fixture.get_keywords(layer)) + instance.obj = obj + instance.layer = layer + instance.extra_keyword_matches.update(fixture.get_keywords(layer)) + return instance def collect(self): - yield LayeredTestCaseFunction('runTest', parent=self) + yield LayeredTestCaseFunction.from_parent(parent=self, name='runTest') def reportinfo(self): pass @@ -33,16 +37,19 @@ def reportinfo(self): class LayeredTestCaseFunction(_pytest.unittest.TestCaseFunction): - def __init__(self, name, parent): + @classmethod + def from_parent(cls, parent, name, **kw): description = get_description(parent) keywords = get_keywords(description) - super(LayeredTestCaseFunction, self).__init__( - name, parent=parent, - keywords=keywords + function = super(LayeredTestCaseFunction, cls).from_parent( + parent=parent, + name=name, + keywords=keywords, ) - self.layer = self.parent.layer - self.tc_description = description - self._testcase = self.parent.obj + function.layer = function.parent.layer + function.tc_description = description + function._testcase = function.parent.obj + return function def setup(self): # This is actually set in the base class, but as we want to modify diff --git a/src/gocept/pytestlayer/plugin.py b/src/gocept/pytestlayer/plugin.py index 0e67951..83dc867 100644 --- a/src/gocept/pytestlayer/plugin.py +++ b/src/gocept/pytestlayer/plugin.py @@ -16,7 +16,8 @@ def pytest_pycollect_makeitem(collector, name, obj): suite = query_testsuite(obj) if suite is not None: - return layered.LayeredTestSuite(name, parent=collector) + return layered.LayeredTestSuite.from_parent( + parent=collector, name=name) else: layer = query_layer(obj) if layer is not None: diff --git a/tox.ini b/tox.ini index 6e6818f..12c328a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,13 +1,10 @@ [tox] minversion = 2.4 envlist = - py27, - py35, - py36, - py37, - py38, - pypy, - pypy3, + py36 + py37 + py38 + pypy3 coverage-report [testenv] @@ -18,11 +15,10 @@ setenv = COVERAGE_FILE=.coverage.{envname} extras = test deps = - py{27,py}: pytest < 5.0 - py{35,36,37,38,py3}: pytest >= 5.4.2 + pytest >= 5.4.2 pytest-cov pytest-flake8 - py{35,36,37,38,py3}: pytest-remove-stale-bytecode + pytest-remove-stale-bytecode [testenv:coverage-report] deps = coverage