@@ -5198,3 +5198,110 @@ def test_something(self, request, app):
5198
5198
)
5199
5199
result = pytester .runpytest ("-v" )
5200
5200
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