Accepting request 1057683 from devel:languages:python:pytest

- Update to 2.0.0:
  * Prior to version 2, we failed to correctly support true Pytest
    setup/teardown methods (i.e. setup_method and teardown_method) and
    these would not get copied to inner class scopes. This has been fixed.
    We still support old nose-style setup/teardown for now, despite them
    going away in Pytest 8.
  * Modernize codebase/project a bunch:
    + Dropped support for Python <3.6 (including 2.7)
    + Pytest support upgraded to support, and require, Pytest >=7.
    + Behavioral changes in Pytest internals have fixed a handful of
      sorta-bugs present in pytest-relaxed under Pytest versions 3 and 4.
- Dropped patches pytest-6.1-and-7.patch, pytest-relaxed-pr10.patch:
  * No longer required.
- Removed six from BuildRequires, is no longer used.

OBS-URL: https://build.opensuse.org/request/show/1057683
OBS-URL: https://build.opensuse.org/package/show/openSUSE:Factory/python-pytest-relaxed?expand=0&rev=11
This commit is contained in:
Dominique Leuenberger 2023-01-15 16:57:51 +00:00 committed by Git OBS Bridge
commit 2b0287129d
6 changed files with 26 additions and 412 deletions

View File

@ -1,218 +0,0 @@
Index: pytest-relaxed-1.1.5/pytest_relaxed/classes.py
===================================================================
--- pytest-relaxed-1.1.5.orig/pytest_relaxed/classes.py
+++ pytest-relaxed-1.1.5/pytest_relaxed/classes.py
@@ -4,7 +4,7 @@ import types
import six
from pytest import __version__ as pytest_version
-from pytest import Class, Instance, Module
+from pytest import Class, Module
# NOTE: don't see any other way to get access to pytest innards besides using
# the underscored name :(
@@ -12,6 +12,13 @@ from _pytest.python import PyCollector
pytest_version_info = tuple(map(int, pytest_version.split(".")[:3]))
+# https://docs.pytest.org/en/latest/deprecations.html#the-pytest-instance-collector
+# The pytest.Instance collector type has been removed in Pytest 7.0
+if pytest_version_info < (7, 0, 0):
+ from pytest import Instance
+else:
+ from pathlib import Path
+ Instance = object
# NOTE: these are defined here for reuse by both pytest's own machinery and our
# internal bits.
@@ -22,6 +29,47 @@ def istestclass(name):
def istestfunction(name):
return not (name.startswith("_") or name in ("setup", "teardown"))
+def _get_obj_rec(obj, parent_obj):
+ # Obtain parent attributes, etc not found on our obj (serves as both a
+ # useful identifier of "stuff added to an outer class" and a way of
+ # ensuring that we can override such attrs), and set them on obj
+ delta = set(dir(parent_obj)).difference(set(dir(obj)))
+ for name in delta:
+ value = getattr(parent_obj, name)
+ # Pytest's pytestmark attributes always get skipped, we don't want
+ # to spread that around where it's not wanted. (Besides, it can
+ # cause a lot of collection level warnings.)
+ if name == "pytestmark":
+ continue
+ # Classes get skipped; they'd always just be other 'inner' classes
+ # that we don't want to copy elsewhere.
+ if isinstance(value, six.class_types):
+ continue
+ # Methods may get skipped, or not, depending:
+ if isinstance(value, types.MethodType):
+ # If they look like tests, they get skipped; don't want to copy
+ # tests around!
+ if istestfunction(name):
+ continue
+ # Non-test == they're probably lifecycle methods
+ # (setup/teardown) or helpers (_do_thing). Rebind them to the
+ # target instance, otherwise the 'self' in the setup/helper is
+ # not the same 'self' as that in the actual test method it runs
+ # around or within!
+ # TODO: arguably, all setup or helper methods should become
+ # autouse class fixtures (see e.g. pytest docs under 'xunit
+ # setup on steroids')
+ func = six.get_method_function(value)
+ setattr(obj, name, six.create_bound_method(func, obj))
+ continue
+ # Same as above but for Pytest 7 which does
+ # collect methods as functions, and without the six wrapper.
+ if isinstance(value, types.FunctionType) and istestfunction(name):
+ continue
+ # Anything else should be some data-type attribute, which is copied
+ # verbatim / by-value.
+ setattr(obj, name, value)
+ return obj
# All other classes in here currently inherit from PyCollector, and it is what
# defines the default istestfunction/istestclass, so makes sense to inherit
@@ -50,7 +98,9 @@ class SpecModule(RelaxedMixin, Module):
@classmethod
def from_parent(cls, parent, fspath):
- if pytest_version_info >= (5, 4):
+ if pytest_version_info >= (7, 0):
+ return super(SpecModule, cls).from_parent(parent, path=Path(fspath))
+ elif pytest_version_info >= (5, 4):
return super(SpecModule, cls).from_parent(parent, fspath=fspath)
else:
return cls(parent=parent, fspath=fspath)
@@ -96,9 +146,7 @@ class SpecModule(RelaxedMixin, Module):
return collected
-# NOTE: no need to inherit from RelaxedMixin here as it doesn't do much by
-# its lonesome
-class SpecClass(Class):
+class SpecClass(RelaxedMixin, Class):
@classmethod
def from_parent(cls, parent, name):
@@ -110,16 +158,39 @@ class SpecClass(Class):
def collect(self):
items = super(SpecClass, self).collect()
collected = []
- # Replace Instance objects with SpecInstance objects that know how to
- # recurse into inner classes.
- # TODO: is this ever not a one-item list? Meh.
for item in items:
- item = SpecInstance.from_parent(item.parent, name=item.name)
- collected.append(item)
+ if pytest_version_info < (7, 0):
+ # Replace Instance objects with SpecInstance objects that know how to
+ # recurse into inner classes.
+ item = SpecInstance.from_parent(item.parent, name=item.name)
+ collected.append(item)
+ else:
+ # Pytest >= 7 collects the Functions in Class directly without Instance
+ # Replace any Class objects with SpecClass, and recurse into it.
+ if isinstance(item, Class):
+ subclass = SpecClass.from_parent(item.parent, name=item.name)
+ collected += subclass.collect()
+ else:
+ collected.append(item)
return collected
+ def _getobj(self):
+ # Regular object-making first
+ obj = super(SpecClass, self)._getobj()
+ # Then decorate it with our parent's extra attributes, allowing nested
+ # test classes to appear as an aggregate of parents' "scopes".
+ # NOTE: of course, skipping if we've gone out the top into a module etc
+ if (
+ pytest_version_info < (7, 0)
+ or not hasattr(self, "parent")
+ or not isinstance(self.parent, SpecClass)
+ ):
+ return obj
+ else:
+ return _get_obj_rec(obj, self.parent.obj)
class SpecInstance(RelaxedMixin, Instance):
+ # This is only instantiated in Pytest < 7
@classmethod
def from_parent(cls, parent, name):
@@ -141,61 +212,19 @@ class SpecInstance(RelaxedMixin, Instanc
or not isinstance(self.parent.parent, SpecInstance)
):
return obj
- parent_obj = self.parent.parent.obj
- # Obtain parent attributes, etc not found on our obj (serves as both a
- # useful identifier of "stuff added to an outer class" and a way of
- # ensuring that we can override such attrs), and set them on obj
- delta = set(dir(parent_obj)).difference(set(dir(obj)))
- for name in delta:
- value = getattr(parent_obj, name)
- # Pytest's pytestmark attributes always get skipped, we don't want
- # to spread that around where it's not wanted. (Besides, it can
- # cause a lot of collection level warnings.)
- if name == "pytestmark":
- continue
- # Classes get skipped; they'd always just be other 'inner' classes
- # that we don't want to copy elsewhere.
- if isinstance(value, six.class_types):
- continue
- # Functions (methods) may get skipped, or not, depending:
- if isinstance(value, types.MethodType):
- # If they look like tests, they get skipped; don't want to copy
- # tests around!
- if istestfunction(name):
- continue
- # Non-test == they're probably lifecycle methods
- # (setup/teardown) or helpers (_do_thing). Rebind them to the
- # target instance, otherwise the 'self' in the setup/helper is
- # not the same 'self' as that in the actual test method it runs
- # around or within!
- # TODO: arguably, all setup or helper methods should become
- # autouse class fixtures (see e.g. pytest docs under 'xunit
- # setup on steroids')
- func = six.get_method_function(value)
- setattr(obj, name, six.create_bound_method(func, obj))
- # Anything else should be some data-type attribute, which is copied
- # verbatim / by-value.
- else:
- setattr(obj, name, value)
- return obj
+ else:
+ return _get_obj_rec(obj, self.parent.parent.obj)
- # Stub for pytest >=3.0,<3.3 where _makeitem did not exist
- def makeitem(self, *args, **kwargs):
- return self._makeitem(*args, **kwargs)
- def _makeitem(self, name, obj):
- # More pytestmark skipping.
- if name == "pytestmark":
- return
- # NOTE: no need to modify collect() this time, just mutate item
- # creation. TODO: but if collect() is still public, let's move to that
- # sometime, if that'll work as well.
- superb = super(SpecInstance, self)
- attr = "_makeitem" if hasattr(superb, "_makeitem") else "makeitem"
- item = getattr(superb, attr)(name, obj)
- # Replace any Class objects with SpecClass; this will automatically
- # recurse.
- # TODO: can we unify this with SpecModule's same bits?
- if isinstance(item, Class):
- item = SpecClass.from_parent(item.parent, name=item.name)
- return item
+ def collect(self):
+ items = super(SpecInstance, self).collect()
+ collected = []
+ for item in items:
+ # Replace any Class objects with SpecClass, and recurse into it.
+ if isinstance(item, Class):
+ cls = SpecClass.from_parent(item.parent, name=item.name)
+ for item in cls.collect():
+ collected.append(item)
+ else:
+ collected.append(item)
+ return collected

View File

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e39a7e5b14e14dfff0de0ad720dfffa740c128d599ab14cfac13f4deb34164a6
size 26786

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4b373cc754666ff60f5425a69cb414642c2a11ce7a46c8ce066a738e4092ca39
size 27040

View File

@ -1,175 +0,0 @@
From 3423a446a49548b80e13fa78fe1c3cbd21968457 Mon Sep 17 00:00:00 2001
From: Stanislav Levin <slev@altlinux.org>
Date: Thu, 7 May 2020 18:46:52 +0300
Subject: [PATCH 1/2] Fix deprecated direct constructors for Nodes
As of 5.4.0 Pytest emits deprecation warnings [0]:
```
PytestDeprecationWarning: direct construction of Spec* has been
deprecated, please use Spec*.from_parent.
```
[0]: https://docs.pytest.org/en/5.4.0/changelog.html#deprecations
Signed-off-by: Stanislav Levin <slev@altlinux.org>
---
pytest_relaxed/classes.py | 31 ++++++++++++++++++++++++++++---
pytest_relaxed/plugin.py | 2 +-
2 files changed, 29 insertions(+), 4 deletions(-)
diff --git a/pytest_relaxed/classes.py b/pytest_relaxed/classes.py
index 10615bc..26edf4e 100644
--- a/pytest_relaxed/classes.py
+++ b/pytest_relaxed/classes.py
@@ -3,12 +3,15 @@
import six
+from pytest import __version__ as pytest_version
from pytest import Class, Instance, Module
# NOTE: don't see any other way to get access to pytest innards besides using
# the underscored name :(
from _pytest.python import PyCollector
+pytest_version_info = tuple(map(int, pytest_version.split(".")[:3]))
+
# NOTE: these are defined here for reuse by both pytest's own machinery and our
# internal bits.
@@ -45,6 +48,13 @@ def istestfunction(self, obj, name):
class SpecModule(RelaxedMixin, Module):
+ @classmethod
+ def from_parent(cls, parent, fspath):
+ if pytest_version_info >= (5, 4):
+ return super(SpecModule, cls).from_parent(parent, fspath=fspath)
+ else:
+ return cls(parent=parent, fspath=fspath)
+
def _is_test_obj(self, test_func, obj, name):
# First run our super() test, which should be RelaxedMixin's.
good_name = getattr(super(SpecModule, self), test_func)(obj, name)
@@ -69,6 +79,7 @@ def collect(self):
# Get whatever our parent picked up as valid test items (given our
# relaxed name constraints above). It'll be nearly all module contents.
items = super(SpecModule, self).collect()
+
collected = []
for item in items:
# Replace Class objects with recursive SpecInstances (via
@@ -80,7 +91,7 @@ def collect(self):
# them to be handled by pytest's own unittest support) but since
# those are almost always in test_prefixed_filenames anyways...meh
if isinstance(item, Class):
- item = SpecClass(item.name, item.parent)
+ item = SpecClass.from_parent(item.parent, name=item.name)
collected.append(item)
return collected
@@ -89,6 +100,13 @@ def collect(self):
# its lonesome
class SpecClass(Class):
+ @classmethod
+ def from_parent(cls, parent, name):
+ if pytest_version_info >= (5, 4):
+ return super(SpecClass, cls).from_parent(parent, name=name)
+ else:
+ return cls(parent=parent, name=name)
+
def collect(self):
items = super(SpecClass, self).collect()
collected = []
@@ -96,13 +114,20 @@ def collect(self):
# recurse into inner classes.
# TODO: is this ever not a one-item list? Meh.
for item in items:
- item = SpecInstance(name=item.name, parent=item.parent)
+ item = SpecInstance.from_parent(item.parent, name=item.name)
collected.append(item)
return collected
class SpecInstance(RelaxedMixin, Instance):
+ @classmethod
+ def from_parent(cls, parent, name):
+ if pytest_version_info >= (5, 4):
+ return super(SpecInstance, cls).from_parent(parent, name=name)
+ else:
+ return cls(parent=parent, name=name)
+
def _getobj(self):
# Regular object-making first
obj = super(SpecInstance, self)._getobj()
@@ -172,5 +197,5 @@ def _makeitem(self, name, obj):
# recurse.
# TODO: can we unify this with SpecModule's same bits?
if isinstance(item, Class):
- item = SpecClass(item.name, item.parent)
+ item = SpecClass.from_parent(item.parent, name=item.name)
return item
diff --git a/pytest_relaxed/plugin.py b/pytest_relaxed/plugin.py
index 0f5a389..8f64358 100644
--- a/pytest_relaxed/plugin.py
+++ b/pytest_relaxed/plugin.py
@@ -28,7 +28,7 @@ def pytest_collect_file(path, parent):
):
# Then use our custom module class which performs modified
# function/class selection as well as class recursion
- return SpecModule(path, parent)
+ return SpecModule.from_parent(parent, fspath=path)
@pytest.mark.trylast # So we can be sure builtin terminalreporter exists
From 6360ba2cc46b597d20478aaee1d6bd1d73588a88 Mon Sep 17 00:00:00 2001
From: Stanislav Levin <slev@altlinux.org>
Date: Fri, 6 Dec 2019 16:13:06 +0300
Subject: [PATCH 2/2] Fixed expected colored statistics
As of 5.3.0 Pytest improved colored statistics of the outcome:
https://docs.pytest.org/en/5.3.0/changelog.html#improvements
Signed-off-by: Stanislav Levin <slev@altlinux.org>
---
tests/test_display.py | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/tests/test_display.py b/tests/test_display.py
index 5b7f9c8..2884c84 100644
--- a/tests/test_display.py
+++ b/tests/test_display.py
@@ -1,4 +1,5 @@
from pytest import skip
+from pytest import __version__ as pytest_version
# Load some fixtures we expose, without actually loading our entire plugin
from pytest_relaxed.fixtures import environ # noqa
@@ -8,6 +9,8 @@
# (May not be feasible if it has to assume something about how our collection
# works?) CLI option (99% sure we can hook into that as a plugin)?
+pytest_version_info = tuple(map(int, pytest_version.split(".")[:3]))
+
def _expect_regular_output(testdir):
output = testdir.runpytest().stdout.str()
@@ -225,7 +228,14 @@ def behavior_four(self):
assert "== FAILURES ==" in output
assert "AssertionError" in output
# Summary
- assert "== 1 failed, 4 passed, 1 skipped in " in output
+ if pytest_version_info >= (5, 3):
+ expected_out = (
+ "== \x1b[31m\x1b[1m1 failed\x1b[0m, \x1b[32m4 passed\x1b[0m, "
+ "\x1b[33m1 skipped\x1b[0m\x1b[31m in "
+ )
+ else:
+ expected_out = "== 1 failed, 4 passed, 1 skipped in "
+ assert expected_out in output
def test_nests_many_levels_deep_no_problem(self, testdir):
testdir.makepyfile(

View File

@ -1,3 +1,21 @@
-------------------------------------------------------------------
Wed Jan 11 01:52:18 UTC 2023 - Steve Kowalik <steven.kowalik@suse.com>
- Update to 2.0.0:
* Prior to version 2, we failed to correctly support true Pytest
setup/teardown methods (i.e. setup_method and teardown_method) and
these would not get copied to inner class scopes. This has been fixed.
We still support old nose-style setup/teardown for now, despite them
going away in Pytest 8.
* Modernize codebase/project a bunch:
+ Dropped support for Python <3.6 (including 2.7)
+ Pytest support upgraded to support, and require, Pytest >=7.
+ Behavioral changes in Pytest internals have fixed a handful of
sorta-bugs present in pytest-relaxed under Pytest versions 3 and 4.
- Dropped patches pytest-6.1-and-7.patch, pytest-relaxed-pr10.patch:
* No longer required.
- Removed six from BuildRequires, is no longer used.
-------------------------------------------------------------------
Tue Apr 12 19:41:01 UTC 2022 - Ben Greiner <code@bnavigator.de>

View File

@ -1,7 +1,7 @@
#
# spec file for package python-pytest-relaxed
#
# Copyright (c) 2022 SUSE LLC
# Copyright (c) 2023 SUSE LLC
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@ -16,27 +16,20 @@
#
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
Name: python-pytest-relaxed
Version: 1.1.5
Version: 2.0.0
Release: 0
Summary: Relaxed test discovery/organization for pytest
License: BSD-2-Clause
URL: https://github.com/bitprophet/pytest-relaxed
Source: https://files.pythonhosted.org/packages/source/p/pytest-relaxed/pytest-relaxed-%{version}.tar.gz
# PATCH-FIX-UPSTREAM pytest-relaxed-pr10.patch -- gh#bitprophet/pytest-relaxed#10
Patch0: pytest-relaxed-pr10.patch
# PATCH-FIX-UPSTREAM pytest-6.1-and-7.patch -- gh#bitprophet/pytest-relaxed#21 + gh#s-t-e-v-e-n-k/pytest-relaxed#1
Patch1: pytest-6.1-and-7.patch
BuildRequires: %{python_module decorator >= 4}
BuildRequires: %{python_module pytest}
BuildRequires: %{python_module pytest >= 7}
BuildRequires: %{python_module setuptools}
BuildRequires: %{python_module six}
BuildRequires: fdupes
BuildRequires: python-rpm-macros
Requires: python-decorator >= 4
Requires: python-pytest
Requires: python-six
Requires: python-pytest >= 7
BuildArch: noarch
%python_subpackages
@ -45,10 +38,6 @@ Relaxed test discovery/organization plugin for pytest from python-paramiko autho
%prep
%autosetup -p1 -n pytest-relaxed-%{version}
# do not hardcode deps
sed -i setup.py \
-e 's:pytest>=3,<5:pytest>=3:' \
-e 's:decorator>=4,<5:decorator>=4:'
%build
export LANG=en_US.UTF-8
@ -60,7 +49,7 @@ export LANG=en_US.UTF-8
%python_expand %fdupes %{buildroot}%{$python_sitelib}
%check
%pytest
%pytest tests
%files %{python_files}
%license LICENSE