@@ -5198,3 +5198,110 @@ def test_something(self, request, app):
51985198 )
51995199 result = pytester .runpytest ("-v" )
52005200 result .assert_outcomes (passed = 1 )
5201+
5202+
5203+ def test_fixture_closure_handles_circular_dependencies (pytester : Pytester ) -> None :
5204+ """Test that getfixtureclosure properly handles circular dependencies.
5205+
5206+ The test will error in the runtest phase due to the fixture loop,
5207+ but the closure computation still completes.
5208+ """
5209+ pytester .makepyfile (
5210+ """
5211+ import pytest
5212+
5213+ # Direct circular dependency.
5214+ @pytest.fixture
5215+ def fix_a(fix_b): pass
5216+
5217+ @pytest.fixture
5218+ def fix_b(fix_a): pass
5219+
5220+ # Indirect circular dependency through multiple fixtures.
5221+ @pytest.fixture
5222+ def fix_x(fix_y): pass
5223+
5224+ @pytest.fixture
5225+ def fix_y(fix_z): pass
5226+
5227+ @pytest.fixture
5228+ def fix_z(fix_x): pass
5229+
5230+ def test_circular_deps(fix_a, fix_x):
5231+ pass
5232+ """
5233+ )
5234+ items , _hookrec = pytester .inline_genitems ()
5235+ assert isinstance (items [0 ], Function )
5236+ assert items [0 ].fixturenames == ["fix_a" , "fix_x" , "fix_b" , "fix_y" , "fix_z" ]
5237+
5238+
5239+ def test_fixture_closure_handles_diamond_dependencies (pytester : Pytester ) -> None :
5240+ """Test that getfixtureclosure properly handles diamond dependencies."""
5241+ pytester .makepyfile (
5242+ """
5243+ import pytest
5244+
5245+ @pytest.fixture
5246+ def db(): pass
5247+
5248+ @pytest.fixture
5249+ def user(db): pass
5250+
5251+ @pytest.fixture
5252+ def session(db): pass
5253+
5254+ @pytest.fixture
5255+ def app(user, session): pass
5256+
5257+ def test_diamond_deps(request, app):
5258+ assert request.node.fixturenames == ["request", "app", "user", "session", "db"]
5259+ assert request.fixturenames == ["request", "app", "user", "session", "db"]
5260+ """
5261+ )
5262+ result = pytester .runpytest ("-v" )
5263+ result .assert_outcomes (passed = 1 )
5264+
5265+
5266+ @pytest .mark .xfail (reason = "not currently handled correctly" )
5267+ def test_fixture_closure_with_complex_override_and_shared_deps (
5268+ pytester : Pytester ,
5269+ ) -> None :
5270+ """Test that shared dependencies in override chains are processed only once."""
5271+ pytester .makeconftest (
5272+ """
5273+ import pytest
5274+
5275+ @pytest.fixture
5276+ def db(): pass
5277+
5278+ @pytest.fixture
5279+ def cache(): pass
5280+
5281+ @pytest.fixture
5282+ def settings(): pass
5283+
5284+ @pytest.fixture
5285+ def app(db, cache, settings): pass
5286+ """
5287+ )
5288+ pytester .makepyfile (
5289+ """
5290+ import pytest
5291+
5292+ # Override app, but also directly use cache and settings.
5293+ # This creates multiple paths to the same fixtures.
5294+ @pytest.fixture
5295+ def app(app, cache, settings): pass
5296+
5297+ class TestClass:
5298+ # Another override that uses both app and cache.
5299+ @pytest.fixture
5300+ def app(self, app, cache): pass
5301+
5302+ def test_shared_deps(self, request, app):
5303+ assert request.node.fixturenames == ["request", "app", "db", "cache", "settings"]
5304+ """
5305+ )
5306+ result = pytester .runpytest ("-v" )
5307+ result .assert_outcomes (passed = 1 )
0 commit comments