Move scheduling of fixture finalization so it isn't rescheduled if the fixture value is cached.
This commit is contained in:
parent
348e6de102
commit
baed905c0b
|
@ -538,6 +538,8 @@ class FixtureRequest(abc.ABC):
|
||||||
:raises pytest.FixtureLookupError:
|
:raises pytest.FixtureLookupError:
|
||||||
If the given fixture could not be found.
|
If the given fixture could not be found.
|
||||||
"""
|
"""
|
||||||
|
# Note: This is called during setup for evaluating fixtures defined via
|
||||||
|
# function arguments as well.
|
||||||
fixturedef = self._get_active_fixturedef(argname)
|
fixturedef = self._get_active_fixturedef(argname)
|
||||||
assert fixturedef.cached_result is not None, (
|
assert fixturedef.cached_result is not None, (
|
||||||
f'The fixture value for "{argname}" is not available. '
|
f'The fixture value for "{argname}" is not available. '
|
||||||
|
@ -574,9 +576,8 @@ class FixtureRequest(abc.ABC):
|
||||||
"""Create a SubRequest based on "self" and call the execute method
|
"""Create a SubRequest based on "self" and call the execute method
|
||||||
of the given FixtureDef object.
|
of the given FixtureDef object.
|
||||||
|
|
||||||
This will force the FixtureDef object to throw away any previous
|
If the FixtureDef has cached the result it will do nothing, otherwise it will
|
||||||
results and compute a new fixture value, which will be stored into
|
setup and run the fixture, cache the value, and schedule a finalizer for it.
|
||||||
the FixtureDef object itself.
|
|
||||||
"""
|
"""
|
||||||
# prepare a subrequest object before calling fixture function
|
# prepare a subrequest object before calling fixture function
|
||||||
# (latter managed by fixturedef)
|
# (latter managed by fixturedef)
|
||||||
|
@ -641,11 +642,8 @@ class FixtureRequest(abc.ABC):
|
||||||
|
|
||||||
# Check if a higher-level scoped fixture accesses a lower level one.
|
# Check if a higher-level scoped fixture accesses a lower level one.
|
||||||
subrequest._check_scope(argname, self._scope, scope)
|
subrequest._check_scope(argname, self._scope, scope)
|
||||||
try:
|
# Make sure the fixture value is cached, running it if it isn't
|
||||||
# Call the fixture function.
|
|
||||||
fixturedef.execute(request=subrequest)
|
fixturedef.execute(request=subrequest)
|
||||||
finally:
|
|
||||||
self._schedule_finalizers(fixturedef, subrequest)
|
|
||||||
|
|
||||||
def _schedule_finalizers(
|
def _schedule_finalizers(
|
||||||
self, fixturedef: "FixtureDef[object]", subrequest: "SubRequest"
|
self, fixturedef: "FixtureDef[object]", subrequest: "SubRequest"
|
||||||
|
@ -1055,7 +1053,9 @@ class FixtureDef(Generic[FixtureValue]):
|
||||||
self.cached_result = None
|
self.cached_result = None
|
||||||
self._finalizers.clear()
|
self._finalizers.clear()
|
||||||
|
|
||||||
|
# Note: the return value is entirely unused, no tests depend on it
|
||||||
def execute(self, request: SubRequest) -> FixtureValue:
|
def execute(self, request: SubRequest) -> FixtureValue:
|
||||||
|
finalizer = functools.partial(self.finish, request=request)
|
||||||
# Get required arguments and register our own finish()
|
# Get required arguments and register our own finish()
|
||||||
# with their finalization.
|
# with their finalization.
|
||||||
for argname in self.argnames:
|
for argname in self.argnames:
|
||||||
|
@ -1063,7 +1063,7 @@ class FixtureDef(Generic[FixtureValue]):
|
||||||
if argname != "request":
|
if argname != "request":
|
||||||
# PseudoFixtureDef is only for "request".
|
# PseudoFixtureDef is only for "request".
|
||||||
assert isinstance(fixturedef, FixtureDef)
|
assert isinstance(fixturedef, FixtureDef)
|
||||||
fixturedef.addfinalizer(functools.partial(self.finish, request=request))
|
fixturedef.addfinalizer(finalizer)
|
||||||
|
|
||||||
my_cache_key = self.cache_key(request)
|
my_cache_key = self.cache_key(request)
|
||||||
if self.cached_result is not None:
|
if self.cached_result is not None:
|
||||||
|
@ -1073,6 +1073,7 @@ class FixtureDef(Generic[FixtureValue]):
|
||||||
if my_cache_key is cache_key:
|
if my_cache_key is cache_key:
|
||||||
if self.cached_result[2] is not None:
|
if self.cached_result[2] is not None:
|
||||||
exc = self.cached_result[2]
|
exc = self.cached_result[2]
|
||||||
|
# this would previously trigger adding a finalizer. Should it?
|
||||||
raise exc
|
raise exc
|
||||||
else:
|
else:
|
||||||
result = self.cached_result[0]
|
result = self.cached_result[0]
|
||||||
|
@ -1083,7 +1084,15 @@ class FixtureDef(Generic[FixtureValue]):
|
||||||
assert self.cached_result is None
|
assert self.cached_result is None
|
||||||
|
|
||||||
ihook = request.node.ihook
|
ihook = request.node.ihook
|
||||||
|
try:
|
||||||
|
# Setup the fixture, run the code in it, and cache the value
|
||||||
|
# in self.cached_result
|
||||||
result = ihook.pytest_fixture_setup(fixturedef=self, request=request)
|
result = ihook.pytest_fixture_setup(fixturedef=self, request=request)
|
||||||
|
finally:
|
||||||
|
# schedule our finalizer, even if the setup failed
|
||||||
|
request.node.addfinalizer(finalizer)
|
||||||
|
|
||||||
|
# note: unused
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def cache_key(self, request: SubRequest) -> object:
|
def cache_key(self, request: SubRequest) -> object:
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
last_executed = ""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def fixture_1():
|
||||||
|
global last_executed
|
||||||
|
assert last_executed == ""
|
||||||
|
last_executed = "autouse_setup"
|
||||||
|
yield
|
||||||
|
assert last_executed == "noautouse_teardown"
|
||||||
|
last_executed = "autouse_teardown"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def fixture_2():
|
||||||
|
global last_executed
|
||||||
|
assert last_executed == "autouse_setup"
|
||||||
|
last_executed = "noautouse_setup"
|
||||||
|
yield
|
||||||
|
assert last_executed == "run_test"
|
||||||
|
last_executed = "noautouse_teardown"
|
||||||
|
|
||||||
|
|
||||||
|
def test_autouse_fixture_teardown_order(fixture_1, fixture_2):
|
||||||
|
global last_executed
|
||||||
|
assert last_executed == "noautouse_setup"
|
||||||
|
last_executed = "run_test"
|
||||||
|
|
||||||
|
|
||||||
|
def test_2(fixture_1):
|
||||||
|
pass
|
Loading…
Reference in New Issue