diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 0151a4d9c..b839ee37f 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1541,6 +1541,23 @@ 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]: + # Initialize with the argnames of the last fixture + 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) arg2fixturedefs: dict[str, Sequence[FixtureDef[Any]]] = {} @@ -1555,7 +1572,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/python/fixtures.py b/testing/python/fixtures.py index bc091bb1f..663acccd9 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -1787,6 +1787,53 @@ 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 == ["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 diff --git a/testing/test_mark.py b/testing/test_mark.py index 89eef7920..82a2886b5 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -499,6 +499,50 @@ 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)""" + 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 + + @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(