From 68361d937bc3bafee8605513a37ef7c8a858c4ef Mon Sep 17 00:00:00 2001 From: Sadra Barikbin Date: Thu, 3 Aug 2023 12:15:36 +0330 Subject: [PATCH 1/6] Fix the typo and update test --- src/_pytest/python.py | 2 +- testing/python/metafunc.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index c0c16d4d0d8..b991c2947ea 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -1516,7 +1516,7 @@ def _find_parametrized_scope( if all_arguments_are_fixtures: fixturedefs = arg2fixturedefs or {} used_scopes = [ - fixturedef[0]._scope + fixturedef[-1]._scope for name, fixturedef in fixturedefs.items() if name in argnames ] diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index bb4ae9d9af3..98c61d4fced 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -151,6 +151,7 @@ class DummyFixtureDef: module_fix=[DummyFixtureDef(Scope.Module)], class_fix=[DummyFixtureDef(Scope.Class)], func_fix=[DummyFixtureDef(Scope.Function)], + mixed_fix=[DummyFixtureDef(Scope.Module), DummyFixtureDef(Scope.Class)], ), ) @@ -187,6 +188,7 @@ def find_scope(argnames, indirect): ) == Scope.Module ) + assert find_scope(["mixed_fix"], indirect=True) == Scope.Class def test_parametrize_and_id(self) -> None: def func(x, y): From 05283a48fbf26b2dc72e799340adec917c8efaca Mon Sep 17 00:00:00 2001 From: Sadra Barikbin Date: Sat, 5 Aug 2023 00:59:09 +0330 Subject: [PATCH 2/6] Add changelog entry and a test --- changelog/11234.bugfix.rst | 1 + src/_pytest/fixtures.py | 2 +- testing/python/metafunc.py | 60 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 changelog/11234.bugfix.rst diff --git a/changelog/11234.bugfix.rst b/changelog/11234.bugfix.rst new file mode 100644 index 00000000000..54dfafad24c --- /dev/null +++ b/changelog/11234.bugfix.rst @@ -0,0 +1 @@ +Fixed the case that when there are multiple fixturedefs for a param, _find_parametrized_scope picks the farthest one, while it should pick the nearest one. diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 00c2a8ef432..6e99ec18895 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -492,7 +492,7 @@ def node(self): node: Optional[Union[nodes.Item, nodes.Collector]] = self._pyfuncitem elif scope is Scope.Package: # FIXME: _fixturedef is not defined on FixtureRequest (this class), - # but on FixtureRequest (a subclass). + # but on SubRequest (a subclass). node = get_scope_package(self._pyfuncitem, self._fixturedef) # type: ignore[attr-defined] else: node = get_scope_node(self._pyfuncitem, scope) diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 98c61d4fced..6a2d79bf024 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -1505,6 +1505,66 @@ def test_it(x): pass result = pytester.runpytest() assert result.ret == 0 + def test_reordering_with_scopeless_and_just_indirect_parametrization( + self, pytester: Pytester + ) -> None: + pytester.makeconftest( + """ + import pytest + + @pytest.fixture(scope="package") + def fixture1(): + pass + """ + ) + pytester.makepyfile( + """ + import pytest + + @pytest.fixture(scope="module") + def fixture0(): + pass + + @pytest.fixture(scope="module") + def fixture1(fixture0): + pass + + @pytest.mark.parametrize("fixture1", [0], indirect=True) + def test_0(fixture1): + pass + + @pytest.fixture(scope="module") + def fixture(): + pass + + @pytest.mark.parametrize("fixture", [0], indirect=True) + def test_1(fixture): + pass + + def test_2(): + pass + + class Test: + @pytest.fixture(scope="class") + def fixture(self): + pass + + @pytest.mark.parametrize("fixture", [0], indirect=True) + def test_3(self, fixture): + pass + """ + ) + result = pytester.runpytest("-v") + assert result.ret == 0 + result.stdout.fnmatch_lines( + [ + "*test_0*", + "*test_1*", + "*test_2*", + "*test_3*", + ] + ) + class TestMetafuncFunctionalAuto: """Tests related to automatically find out the correct scope for From 4fdd3d31db0e9f34fa2fc03d6e6520faa1c41e77 Mon Sep 17 00:00:00 2001 From: Sadra Barikbin Date: Sat, 5 Aug 2023 12:59:44 +0330 Subject: [PATCH 3/6] Fix a tiny typo --- src/_pytest/python.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index b991c2947ea..ae42e390f56 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -1682,7 +1682,7 @@ class Function(PyobjMixin, nodes.Item): :param config: The pytest Config object. :param callspec: - If given, this is function has been parametrized and the callspec contains + If given, this function has been parametrized and the callspec contains meta information about the parametrization. :param callobj: If given, the object which will be called when the Function is invoked, From 6ca11f77194438b7ee3e2e409761e03db17f9dc4 Mon Sep 17 00:00:00 2001 From: Sadra Barikbin Date: Thu, 3 Aug 2023 12:15:36 +0330 Subject: [PATCH 4/6] python: fix scope assignment for indirect parameter sets Previously, when assigning a scope for a fully-indirect parameter set, when there are multiple fixturedefs for a param (i.e. same-name fixture chain), the highest scope was used, but it should be the lowest scope, since that's the effective scope of the fixture. --- changelog/11277.bugfix.rst | 2 ++ src/_pytest/fixtures.py | 2 +- src/_pytest/python.py | 4 +-- testing/python/metafunc.py | 62 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 changelog/11277.bugfix.rst diff --git a/changelog/11277.bugfix.rst b/changelog/11277.bugfix.rst new file mode 100644 index 00000000000..43370561e3b --- /dev/null +++ b/changelog/11277.bugfix.rst @@ -0,0 +1,2 @@ +Fixed a bug that when there are multiple fixtures for an indirect parameter, +the scope of the highest-scope fixture is picked for the parameter set, instead of that of the one with the narrowest scope. diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 00c2a8ef432..6e99ec18895 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -492,7 +492,7 @@ def node(self): node: Optional[Union[nodes.Item, nodes.Collector]] = self._pyfuncitem elif scope is Scope.Package: # FIXME: _fixturedef is not defined on FixtureRequest (this class), - # but on FixtureRequest (a subclass). + # but on SubRequest (a subclass). node = get_scope_package(self._pyfuncitem, self._fixturedef) # type: ignore[attr-defined] else: node = get_scope_node(self._pyfuncitem, scope) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index c0c16d4d0d8..ae42e390f56 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -1516,7 +1516,7 @@ def _find_parametrized_scope( if all_arguments_are_fixtures: fixturedefs = arg2fixturedefs or {} used_scopes = [ - fixturedef[0]._scope + fixturedef[-1]._scope for name, fixturedef in fixturedefs.items() if name in argnames ] @@ -1682,7 +1682,7 @@ class Function(PyobjMixin, nodes.Item): :param config: The pytest Config object. :param callspec: - If given, this is function has been parametrized and the callspec contains + If given, this function has been parametrized and the callspec contains meta information about the parametrization. :param callobj: If given, the object which will be called when the Function is invoked, diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index bb4ae9d9af3..6a2d79bf024 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -151,6 +151,7 @@ class DummyFixtureDef: module_fix=[DummyFixtureDef(Scope.Module)], class_fix=[DummyFixtureDef(Scope.Class)], func_fix=[DummyFixtureDef(Scope.Function)], + mixed_fix=[DummyFixtureDef(Scope.Module), DummyFixtureDef(Scope.Class)], ), ) @@ -187,6 +188,7 @@ def find_scope(argnames, indirect): ) == Scope.Module ) + assert find_scope(["mixed_fix"], indirect=True) == Scope.Class def test_parametrize_and_id(self) -> None: def func(x, y): @@ -1503,6 +1505,66 @@ def test_it(x): pass result = pytester.runpytest() assert result.ret == 0 + def test_reordering_with_scopeless_and_just_indirect_parametrization( + self, pytester: Pytester + ) -> None: + pytester.makeconftest( + """ + import pytest + + @pytest.fixture(scope="package") + def fixture1(): + pass + """ + ) + pytester.makepyfile( + """ + import pytest + + @pytest.fixture(scope="module") + def fixture0(): + pass + + @pytest.fixture(scope="module") + def fixture1(fixture0): + pass + + @pytest.mark.parametrize("fixture1", [0], indirect=True) + def test_0(fixture1): + pass + + @pytest.fixture(scope="module") + def fixture(): + pass + + @pytest.mark.parametrize("fixture", [0], indirect=True) + def test_1(fixture): + pass + + def test_2(): + pass + + class Test: + @pytest.fixture(scope="class") + def fixture(self): + pass + + @pytest.mark.parametrize("fixture", [0], indirect=True) + def test_3(self, fixture): + pass + """ + ) + result = pytester.runpytest("-v") + assert result.ret == 0 + result.stdout.fnmatch_lines( + [ + "*test_0*", + "*test_1*", + "*test_2*", + "*test_3*", + ] + ) + class TestMetafuncFunctionalAuto: """Tests related to automatically find out the correct scope for From aec2ee3107d81abd2be867fdb177f3c9d7c5ecb9 Mon Sep 17 00:00:00 2001 From: Sadra Barikbin Date: Sun, 6 Aug 2023 16:04:21 +0330 Subject: [PATCH 5/6] Apply the comment --- testing/python/metafunc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 6a2d79bf024..4c066a89d79 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -1546,7 +1546,7 @@ def test_2(): class Test: @pytest.fixture(scope="class") - def fixture(self): + def fixture(self, fixture): pass @pytest.mark.parametrize("fixture", [0], indirect=True) From 92db45434459add9d54c7094ca7c83cde0cc3a1d Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 6 Aug 2023 16:39:51 +0300 Subject: [PATCH 6/6] Delete 11234.bugfix.rst --- changelog/11234.bugfix.rst | 1 - 1 file changed, 1 deletion(-) delete mode 100644 changelog/11234.bugfix.rst diff --git a/changelog/11234.bugfix.rst b/changelog/11234.bugfix.rst deleted file mode 100644 index 54dfafad24c..00000000000 --- a/changelog/11234.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed the case that when there are multiple fixturedefs for a param, _find_parametrized_scope picks the farthest one, while it should pick the nearest one.