Skip to content

Commit

Permalink
cleaned up, DAMPified, and added tests to ensure fixtures only add th…
Browse files Browse the repository at this point in the history
…er finalizer to a dependee fixture once
  • Loading branch information
SalmonMode authored and blueyed committed Jan 25, 2020
1 parent ff05460 commit 6db4724
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 9 deletions.
34 changes: 25 additions & 9 deletions src/_pytest/fixtures.py
Expand Up @@ -888,16 +888,13 @@ def finish(self, request):

def execute(self, request):
for argname in self._dependee_fixture_argnames(request):
if argname == "request":
continue
fixturedef = request._get_active_fixturedef(argname)
if argname != "request":
for fin in fixturedef._finalizers:
if "request" in getattr(fin, "keywords", {}):
if self == fin.keywords["request"]._fixturedef:
break
else:
fixturedef.addfinalizer(
functools.partial(self.finish, request=request)
)
if not self._will_be_finalized_by_fixture(fixturedef):
fixturedef.addfinalizer(
functools.partial(self.finish, request=request)
)

my_cache_key = self.cache_key(request)
cached_result = getattr(self, "cached_result", None)
Expand All @@ -917,6 +914,25 @@ def execute(self, request):
hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
return hook.pytest_fixture_setup(fixturedef=self, request=request)

def _will_be_finalized_by_fixture(self, fixturedef):
"""Whether or not this fixture be finalized by the passed fixture.
Every ``:class:FixtureDef`` keeps a list of all the finishers (tear downs) of
other ``:class:FixtureDef`` instances that it should run before running its own.
Finishers are added to this list not by this ``:class:FixtureDef``, but by the
other ``:class:FixtureDef`` instances. They tell this instance that it's
responsible for tearing them down before it tears itself down.
This method allows a ``:class:FixtureDef`` to check if it has already told
another ``:class:FixtureDef`` that the latter ``:class:FixtureDef`` is
responsible for tearing down this ``:class:FixtureDef``.
"""
for finalizer in fixturedef._finalizers:
if "request" in getattr(finalizer, "keywords", {}):
if self == finalizer.keywords["request"]._fixturedef:
return True
return False

def _dependee_fixture_argnames(self, request):
"""A list of argnames for fixtures that this fixture depends on.
Expand Down
20 changes: 20 additions & 0 deletions testing/python/fixtures.py
Expand Up @@ -4374,3 +4374,23 @@ def test_suffix(fix_combined):
)
result = testdir.runpytest("-vv", str(p1))
assert result.ret == 0


class TestFinalizerOnlyAddedOnce:

@pytest.fixture(scope="class", autouse=True)
def a(self):
pass

@pytest.fixture(scope="class", autouse=True)
def b(self, a):
pass

def test_a_will_finalize_b(self, request):
a = request._get_active_fixturedef("a")
b = request._get_active_fixturedef("b")
assert b._will_be_finalized_by_fixture(a)

def test_a_only_finishes_one(self, request):
a = request._get_active_fixturedef("a")
assert len(a._finalizers)

0 comments on commit 6db4724

Please sign in to comment.