From 4c62401e4fbcf4942d16053d3e37d8b5f5ad3b23 Mon Sep 17 00:00:00 2001 From: Anton Zhilin Date: Mon, 24 Feb 2025 14:14:56 +0300 Subject: [PATCH 1/4] Prevent parametrize with scope from breaking fixture dependencies --- changelog/13248.bugfix.rst | 2 ++ src/_pytest/fixtures.py | 7 ++++++- testing/python/fixtures.py | 24 ++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 changelog/13248.bugfix.rst diff --git a/changelog/13248.bugfix.rst b/changelog/13248.bugfix.rst new file mode 100644 index 00000000000..2ebb102fd07 --- /dev/null +++ b/changelog/13248.bugfix.rst @@ -0,0 +1,2 @@ +Fixed an issue where passing a ``scope`` in :py:func:`Metafunc.parametrize ` with ``indirect=True`` +could result in other fixtures being unable to depend on the parametrized fixture. diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index dcd06c3b40a..bb50b014dd1 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -605,7 +605,12 @@ def _get_active_fixturedef( param_index = 0 scope = fixturedef._scope self._check_fixturedef_without_param(fixturedef) - self._check_scope(fixturedef, scope) + # The parametrize invocation scope only controls caching behavior while + # allowing wider-scoped fixtures to keep depending on the parametrized + # fixture. Scope control is enforced for parametrized fixtures + # by recreating the whole fixture tree on parameter change. + # Hence `fixturedef._scope`, not `scope`. + self._check_scope(fixturedef, fixturedef._scope) subrequest = SubRequest( self, scope, param, param_index, fixturedef, _ispytest=True ) diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index dc69781095b..b66b0ffa277 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -5009,3 +5009,27 @@ def test_result(): ) result = pytester.runpytest() assert result.ret == 0 + + +def test_parametrized_fixture_scope_allowed(pytester: Pytester) -> None: + """ + Make sure scope from parametrize does not affect fixture's ability to be + depended upon. + + Regression test for #13248 + """ + pytester.makepyfile( + """ + import pytest + + @pytest.fixture(scope="session") + def my_fixture(request): + return getattr(request, "param", None) + + @pytest.mark.parametrize("my_fixture", ["a"], indirect=True, scope="function") + def test_foo(my_fixture): + pass + """ + ) + result = pytester.runpytest() + result.assert_outcomes(passed=1) From 5e32d0b7a9f9663d2767ded4c1851ae3fac44707 Mon Sep 17 00:00:00 2001 From: Anton Zhilin Date: Mon, 24 Feb 2025 14:16:06 +0300 Subject: [PATCH 2/4] Add myself to AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index e670571566a..e5c19cdca0d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -43,6 +43,7 @@ Anthony Shaw Anthony Sottile Anton Grinevich Anton Lodder +Anton Zhilin Antony Lee Arel Cordero Arias Emmanuel From aa874f12fddabb4b752851fdfd59288ed423f4be Mon Sep 17 00:00:00 2001 From: Anton Zhilin Date: Mon, 24 Feb 2025 15:33:15 +0300 Subject: [PATCH 3/4] Fix the regression test --- testing/python/fixtures.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index b66b0ffa277..f804415af80 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -5026,8 +5026,12 @@ def test_parametrized_fixture_scope_allowed(pytester: Pytester) -> None: def my_fixture(request): return getattr(request, "param", None) + @pytest.fixture(scope="session") + def another_fixture(my_fixture): + return my_fixture + @pytest.mark.parametrize("my_fixture", ["a"], indirect=True, scope="function") - def test_foo(my_fixture): + def test_foo(another_fixture): pass """ ) From 1d0b2a72d0d2a6f377b594f2a5ecfda0a78179ab Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 1 Mar 2025 10:40:05 -0300 Subject: [PATCH 4/4] Assert fixture value inside the test --- testing/python/fixtures.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index f804415af80..c8d1eb23838 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -5030,9 +5030,9 @@ def my_fixture(request): def another_fixture(my_fixture): return my_fixture - @pytest.mark.parametrize("my_fixture", ["a"], indirect=True, scope="function") + @pytest.mark.parametrize("my_fixture", ["a value"], indirect=True, scope="function") def test_foo(another_fixture): - pass + assert another_fixture == "a value" """ ) result = pytester.runpytest()