Skip to content

Commit 23ea04f

Browse files
authored
Merge pull request #4860 from nicoddemus/getfixturevalue-cleanup-1895
getfixturevalue does not correctly declare dependency with the calling fixture
2 parents 35c85f0 + c334adc commit 23ea04f

File tree

3 files changed

+57
-5
lines changed

3 files changed

+57
-5
lines changed

changelog/1895.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix bug where fixtures requested dynamically via ``request.getfixturevalue()`` might be teardown
2+
before the requesting fixture.

src/_pytest/fixtures.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -585,11 +585,13 @@ def _compute_fixture_value(self, fixturedef):
585585
# call the fixture function
586586
fixturedef.execute(request=subrequest)
587587
finally:
588-
# if fixture function failed it might have registered finalizers
589-
self.session._setupstate.addfinalizer(
590-
functools.partial(fixturedef.finish, request=subrequest),
591-
subrequest.node,
592-
)
588+
self._schedule_finalizers(fixturedef, subrequest)
589+
590+
def _schedule_finalizers(self, fixturedef, subrequest):
591+
# if fixture function failed it might have registered finalizers
592+
self.session._setupstate.addfinalizer(
593+
functools.partial(fixturedef.finish, request=subrequest), subrequest.node
594+
)
593595

594596
def _check_scope(self, argname, invoking_scope, requested_scope):
595597
if argname == "request":
@@ -659,6 +661,16 @@ def __repr__(self):
659661
def addfinalizer(self, finalizer):
660662
self._fixturedef.addfinalizer(finalizer)
661663

664+
def _schedule_finalizers(self, fixturedef, subrequest):
665+
# if the executing fixturedef was not explicitly requested in the argument list (via
666+
# getfixturevalue inside the fixture call) then ensure this fixture def will be finished
667+
# first
668+
if fixturedef.argname not in self.funcargnames:
669+
fixturedef.addfinalizer(
670+
functools.partial(self._fixturedef.finish, request=self)
671+
)
672+
super(SubRequest, self)._schedule_finalizers(fixturedef, subrequest)
673+
662674

663675
scopes = "session package module class function".split()
664676
scopenum_function = scopes.index("function")

testing/python/fixture.py renamed to testing/python/fixtures.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,44 @@ def test_func(something):
562562
reprec = testdir.inline_run()
563563
reprec.assertoutcome(passed=1)
564564

565+
def test_getfixturevalue_teardown(self, testdir):
566+
"""
567+
Issue #1895
568+
569+
`test_inner` requests `inner` fixture, which in turn requests `resource`
570+
using `getfixturevalue`. `test_func` then requests `resource`.
571+
572+
`resource` is teardown before `inner` because the fixture mechanism won't consider
573+
`inner` dependent on `resource` when it is used via `getfixturevalue`: `test_func`
574+
will then cause the `resource`'s finalizer to be called first because of this.
575+
"""
576+
testdir.makepyfile(
577+
"""
578+
import pytest
579+
580+
@pytest.fixture(scope='session')
581+
def resource():
582+
r = ['value']
583+
yield r
584+
r.pop()
585+
586+
@pytest.fixture(scope='session')
587+
def inner(request):
588+
resource = request.getfixturevalue('resource')
589+
assert resource == ['value']
590+
yield
591+
assert resource == ['value']
592+
593+
def test_inner(inner):
594+
pass
595+
596+
def test_func(resource):
597+
pass
598+
"""
599+
)
600+
result = testdir.runpytest()
601+
result.stdout.fnmatch_lines("* 2 passed in *")
602+
565603
@pytest.mark.parametrize("getfixmethod", ("getfixturevalue", "getfuncargvalue"))
566604
def test_getfixturevalue(self, testdir, getfixmethod):
567605
item = testdir.getitem(

0 commit comments

Comments
 (0)