Skip to content

Commit 79ed509

Browse files
committed
cleaned up, DAMPified, and added tests to ensure fixtures only add ther finalizer to a dependee fixture once
1 parent 51bfeb1 commit 79ed509

File tree

2 files changed

+45
-9
lines changed

2 files changed

+45
-9
lines changed

src/_pytest/fixtures.py

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -887,16 +887,13 @@ def finish(self, request):
887887

888888
def execute(self, request):
889889
for argname in self._dependee_fixture_argnames(request):
890+
if argname == "request":
891+
continue
890892
fixturedef = request._get_active_fixturedef(argname)
891-
if argname != "request":
892-
for fin in fixturedef._finalizers:
893-
if "request" in getattr(fin, "keywords", {}):
894-
if self == fin.keywords["request"]._fixturedef:
895-
break
896-
else:
897-
fixturedef.addfinalizer(
898-
functools.partial(self.finish, request=request)
899-
)
893+
if not self._will_be_finalized_by_fixture(fixturedef):
894+
fixturedef.addfinalizer(
895+
functools.partial(self.finish, request=request)
896+
)
900897

901898
my_cache_key = self.cache_key(request)
902899
cached_result = getattr(self, "cached_result", None)
@@ -916,6 +913,25 @@ def execute(self, request):
916913
hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
917914
return hook.pytest_fixture_setup(fixturedef=self, request=request)
918915

916+
def _will_be_finalized_by_fixture(self, fixturedef):
917+
"""Whether or not this fixture be finalized by the passed fixture.
918+
919+
Every ``:class:FixtureDef`` keeps a list of all the finishers (tear downs) of
920+
other ``:class:FixtureDef`` instances that it should run before running its own.
921+
Finishers are added to this list not by this ``:class:FixtureDef``, but by the
922+
other ``:class:FixtureDef`` instances. They tell this instance that it's
923+
responsible for tearing them down before it tears itself down.
924+
925+
This method allows a ``:class:FixtureDef`` to check if it has already told
926+
another ``:class:FixtureDef`` that the latter ``:class:FixtureDef`` is
927+
responsible for tearing down this ``:class:FixtureDef``.
928+
"""
929+
for finalizer in fixturedef._finalizers:
930+
if "request" in getattr(finalizer, "keywords", {}):
931+
if self == finalizer.keywords["request"]._fixturedef:
932+
return True
933+
return False
934+
919935
def _dependee_fixture_argnames(self, request):
920936
"""A list of argnames for fixtures that this fixture depends on.
921937

testing/python/fixtures.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4339,3 +4339,23 @@ def test_bug(value):
43394339
)
43404340
result = testdir.runpytest()
43414341
result.assert_outcomes(passed=10)
4342+
4343+
4344+
class TestFinalizerOnlyAddedOnce:
4345+
4346+
@pytest.fixture(scope="class", autouse=True)
4347+
def a(self):
4348+
pass
4349+
4350+
@pytest.fixture(scope="class", autouse=True)
4351+
def b(self, a):
4352+
pass
4353+
4354+
def test_a_will_finalize_b(self, request):
4355+
a = request._get_active_fixturedef("a")
4356+
b = request._get_active_fixturedef("b")
4357+
assert b._will_be_finalized_by_fixture(a)
4358+
4359+
def test_a_only_finishes_one(self, request):
4360+
a = request._get_active_fixturedef("a")
4361+
assert len(a._finalizers)

0 commit comments

Comments
 (0)