From 143c703b3f071fc7cd9716490af2cdad58378656 Mon Sep 17 00:00:00 2001 From: Andres Date: Mon, 11 Mar 2024 13:56:17 -0300 Subject: [PATCH 1/6] fix overriden/extended fixtures --- src/_pytest/fixtures.py | 17 ++++++++++++++++- testing/test_mark.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 48429a023..538a0ee5e 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1505,6 +1505,20 @@ class FixtureManager: # to re-discover fixturedefs again for each fixturename # (discovering matching fixtures for a given name/node is expensive). + def dependent_fixtures_argnames( + fixture_defs: Sequence[FixtureDef[Any]], + ) -> List[str]: + last_fixture = fixture_defs[-1] + # Initialize with the argnames of the last fixture + dependent_argnames = list(last_fixture.argnames) + for arg in fixture_defs: + if arg.argname in last_fixture.argnames: + # Add new argument names maintaining order and avoiding duplicates + for argname in arg.argnames: + if argname not in dependent_argnames: + dependent_argnames.append(argname) + return dependent_argnames + fixturenames_closure = list(initialnames) arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]] = {} @@ -1519,7 +1533,8 @@ class FixtureManager: fixturedefs = self.getfixturedefs(argname, parentnode) if fixturedefs: arg2fixturedefs[argname] = fixturedefs - for arg in fixturedefs[-1].argnames: + argnames = dependent_fixtures_argnames(fixturedefs) + for arg in argnames: if arg not in fixturenames_closure: fixturenames_closure.append(arg) diff --git a/testing/test_mark.py b/testing/test_mark.py index 2896afa45..bbce30e8d 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -447,6 +447,35 @@ def test_parametrized_with_kwargs(pytester: Pytester) -> None: assert result.ret == 0 +def test_parametrize_overriden_extended_fixture(pytester: Pytester) -> None: + """Overriden fixtures must pass over dependend fixtures for parameterization (#12091)""" + py_file = pytester.makepyfile( + """\ + import pytest + + @pytest.fixture + def param() -> int: + return 1 + + @pytest.fixture + def main(param: int) -> int: + return sum(range(param + 1)) + + + class TestFoo: + @pytest.fixture + def main(self, main: int) -> int: + return main + + @pytest.mark.parametrize("param", [2]) + def test_foo(self, main: int) -> None: + assert main == 3 + """ + ) + result = pytester.runpytest(py_file) + assert result.ret == 0 + + def test_parametrize_iterator(pytester: Pytester) -> None: """`parametrize` should work with generators (#5354).""" py_file = pytester.makepyfile( From a56e25154b0754b46f0b4d17b42f6c26779901e0 Mon Sep 17 00:00:00 2001 From: Andres Date: Wed, 13 Mar 2024 11:09:42 -0300 Subject: [PATCH 2/6] update tests --- testing/test_mark.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/testing/test_mark.py b/testing/test_mark.py index bbce30e8d..cd3fde406 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -449,6 +449,21 @@ def test_parametrized_with_kwargs(pytester: Pytester) -> None: def test_parametrize_overriden_extended_fixture(pytester: Pytester) -> None: """Overriden fixtures must pass over dependend fixtures for parameterization (#12091)""" + pytester.makeconftest( + """ + import pytest + + @pytest.fixture + def not_needed(): + assert False, "Should not be called!" + + @pytest.fixture + def main(foo): + assert False, "Should not be called!" + + """ + ) + py_file = pytester.makepyfile( """\ import pytest From c0daf2830e9ad18301fbf86d7299a1b616b7d23c Mon Sep 17 00:00:00 2001 From: Andres Date: Mon, 18 Mar 2024 14:01:10 -0300 Subject: [PATCH 3/6] fix dependent_fixtures_argnames --- src/_pytest/fixtures.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 538a0ee5e..a6209ad80 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1508,15 +1508,18 @@ class FixtureManager: def dependent_fixtures_argnames( fixture_defs: Sequence[FixtureDef[Any]], ) -> List[str]: - last_fixture = fixture_defs[-1] # Initialize with the argnames of the last fixture - dependent_argnames = list(last_fixture.argnames) - for arg in fixture_defs: - if arg.argname in last_fixture.argnames: - # Add new argument names maintaining order and avoiding duplicates - for argname in arg.argnames: + dependent_argnames = list(fixture_defs[-1].argnames) + # Iterate over the list in reverse order, skipping the last element already processed. + for index, current_fixture in enumerate( + reversed(fixture_defs[:-1]), start=1 + ): + if current_fixture.argname in fixture_defs[-index].argnames: + for argname in current_fixture.argnames: if argname not in dependent_argnames: dependent_argnames.append(argname) + else: + break return dependent_argnames fixturenames_closure = list(initialnames) From ec7a47ee9178ac1bc0e590e12b300c03db08eb2c Mon Sep 17 00:00:00 2001 From: Andres Date: Mon, 18 Mar 2024 16:47:20 -0300 Subject: [PATCH 4/6] update tests --- testing/python/fixtures.py | 48 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 1e22270e5..1fb5db2d8 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -1785,6 +1785,54 @@ class TestFixtureManagerParseFactories: result = pytester.runpytest("foo") result.stdout.fnmatch_lines(["*passed*"]) + def test_get_fixture_clousure_override_conftest_module_and_class( + self, pytester: Pytester + ): + pytester.makepyfile( + """\ + import pytest + + @pytest.fixture + def hello(param, hello): + return "module" + class TestClass(object): + @pytest.fixture + def hello(self, hello): + return "class" + + @pytest.mark.parametrize("param", ["foo"]) + def test_hello(self, item, hello, fm): + print(item) + clousurelist, _ = fm.getfixtureclosure(item, ("hello",), {}) + assert clousurelist[0] == ["hello", "param", "request"] + """ + ) + reprec = pytester.inline_run("-s") + reprec.assertoutcome(passed=1) + + def test_get_fixture_clousure_override_module_and_class(self, pytester: Pytester): + pytester.makepyfile( + """\ + import pytest + + @pytest.fixture + def hello(param): + return "module" + class TestClass(object): + @pytest.fixture + def hello(self, hello): + return "class" + @pytest.mark.parametrize("param", ["foo"]) + def test_hello(self, item, hello, fm): + print(item) + clousurelist, _ = fm.getfixtureclosure(item, ("hello",), {}) + print(clousurelist) + assert clousurelist == ["hello", "param"] + """ + ) + reprec = pytester.inline_run("-s") + reprec.assertoutcome(passed=1) + class TestAutouseDiscovery: @pytest.fixture From 5646d425a9837a3aa324fc535199b6905496abcd Mon Sep 17 00:00:00 2001 From: Andres Date: Mon, 18 Mar 2024 16:47:49 -0300 Subject: [PATCH 5/6] fmt --- testing/python/fixtures.py | 1 - 1 file changed, 1 deletion(-) diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 1fb5db2d8..d378ebef1 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -1799,7 +1799,6 @@ class TestFixtureManagerParseFactories: @pytest.fixture def hello(self, hello): return "class" - @pytest.mark.parametrize("param", ["foo"]) def test_hello(self, item, hello, fm): print(item) From 1ffafefc1fd68f3bc9a8cf6f0fb49ffbe5bf12ef Mon Sep 17 00:00:00 2001 From: Andres Date: Mon, 18 Mar 2024 17:48:37 -0300 Subject: [PATCH 6/6] fix test --- testing/python/fixtures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index d378ebef1..306b74725 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -1803,7 +1803,7 @@ class TestFixtureManagerParseFactories: def test_hello(self, item, hello, fm): print(item) clousurelist, _ = fm.getfixtureclosure(item, ("hello",), {}) - assert clousurelist[0] == ["hello", "param", "request"] + assert clousurelist == ["hello", "param", "request"] """ ) reprec = pytester.inline_run("-s")