diff --git a/AUTHORS b/AUTHORS index 1dbef3d5d..e355f01e0 100644 --- a/AUTHORS +++ b/AUTHORS @@ -23,6 +23,7 @@ Andras Tim Andrea Cimatoribus Andreas Zeidler Andrey Paramonov +Andrzej Klajnert Andrzej Ostrowski Andy Freeland Anthon van der Neut diff --git a/changelog/570.bugfix.rst b/changelog/570.bugfix.rst new file mode 100644 index 000000000..980491e28 --- /dev/null +++ b/changelog/570.bugfix.rst @@ -0,0 +1,2 @@ +Fixed long standing issue where fixture scope was not respected when indirect fixtures were used during +parametrization. diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index d5f9ad2d3..9a904758f 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -859,7 +859,7 @@ class FixtureDef: if argname != "request": fixturedef.addfinalizer(functools.partial(self.finish, request=request)) - my_cache_key = request.param_index + my_cache_key = self.cache_key(request) cached_result = getattr(self, "cached_result", None) if cached_result is not None: result, cache_key, err = cached_result @@ -877,6 +877,9 @@ class FixtureDef: hook = self._fixturemanager.session.gethookproxy(request.node.fspath) return hook.pytest_fixture_setup(fixturedef=self, request=request) + def cache_key(self, request): + return request.param_index if not hasattr(request, "param") else request.param + def __repr__(self): return "".format( self.argname, self.scope, self.baseid @@ -913,7 +916,7 @@ def pytest_fixture_setup(fixturedef, request): kwargs[argname] = result fixturefunc = resolve_fixture_function(fixturedef, request) - my_cache_key = request.param_index + my_cache_key = fixturedef.cache_key(request) try: result = call_fixture_func(fixturefunc, request, kwargs) except TEST_OUTCOME: diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 1f383e752..d998e9726 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -449,7 +449,8 @@ class TestFillFixtures: "*ERROR at setup of test_lookup_error*", " def test_lookup_error(unknown):*", "E fixture 'unknown' not found", - "> available fixtures:*a_fixture,*b_fixture,*c_fixture,*d_fixture*monkeypatch,*", # sorted + "> available fixtures:*a_fixture,*b_fixture,*c_fixture,*d_fixture*monkeypatch,*", + # sorted "> use 'py*test --fixtures *' for help on them.", "*1 error*", ] @@ -4009,3 +4010,55 @@ def test_fixture_named_request(testdir): " *test_fixture_named_request.py:5", ] ) + + +def test_indirect_fixture_does_not_break_scope(testdir): + """Ensure that fixture scope is respected when using indirect fixtures (#570)""" + testdir.makepyfile( + """ + import pytest + + instantiated = [] + + @pytest.fixture(scope="session") + def fixture_1(request): + instantiated.append(("fixture_1", request.param)) + + + @pytest.fixture(scope="session") + def fixture_2(request): + instantiated.append(("fixture_2", request.param)) + + + scenarios = [ + ("A", "a1"), + ("A", "a2"), + ("B", "b1"), + ("B", "b2"), + ("C", "c1"), + ("C", "c2"), + ] + + @pytest.mark.parametrize( + "fixture_1,fixture_2", scenarios, indirect=["fixture_1", "fixture_2"] + ) + def test_create_fixtures(fixture_1, fixture_2): + pass + + + def test_check_fixture_instantiations(): + assert instantiated == [ + ('fixture_1', 'A'), + ('fixture_2', 'a1'), + ('fixture_2', 'a2'), + ('fixture_1', 'B'), + ('fixture_2', 'b1'), + ('fixture_2', 'b2'), + ('fixture_1', 'C'), + ('fixture_2', 'c1'), + ('fixture_2', 'c2'), + ] + """ + ) + result = testdir.runpytest() + result.assert_outcomes(passed=7)