update comments, and remove test relying on internal functionality

This commit is contained in:
jakkdl 2024-03-26 15:44:13 +01:00
parent 358513b8a0
commit 1eebb78ee7
3 changed files with 20 additions and 45 deletions

View File

@ -1 +1 @@
Fix subfixtures adding their finalizer multiple times to parent fixtures, causing incorrect teardown ordering in some instances. Fix fixtures adding their finalizer multiple times to fixtures they request, causing unreliable and non-intuitive teardown ordering in some instances.

View File

@ -1035,19 +1035,25 @@ class FixtureDef(Generic[FixtureValue]):
raise BaseExceptionGroup(msg, exceptions[::-1]) raise BaseExceptionGroup(msg, exceptions[::-1])
def execute(self, request: SubRequest) -> FixtureValue: def execute(self, request: SubRequest) -> FixtureValue:
# Ensure arguments (parent fixtures) are loaded. """Return the value of this fixture, executing it if not cached."""
# If their cache has been invalidated it will finish itself and subfixtures, # Ensure dependent fixtures that this fixture requests are loaded.
# which will set our self.cached_result = None. # This needs to be done before checking if we have a cached value, since
# If/when parent fixture parametrization is included in our cache key this # if a dependent fixture has their cache invalidated, e.g. due to
# can be moved after checking our cache key and not require saving in a list. # parametrization, they finalize themselves and fixtures depending on it
parent_fixtures_to_add_finalizer = [] # (which will likely include this fixture) setting `self.cached_result = None`.
# See #4871
requested_fixtures_that_should_finalize_us = []
for argname in self.argnames: for argname in self.argnames:
fixturedef = request._get_active_fixturedef(argname) fixturedef = request._get_active_fixturedef(argname)
# Saves requested fixtures in a list so we later can add our finalizer
# to them, ensuring that if a requested fixture gets torn down we get torn
# down first. This is generally handled by SetupState, but still currently
# needed when this fixture is not parametrized but depends on a parametrized
# fixture.
if not isinstance(fixturedef, PseudoFixtureDef): if not isinstance(fixturedef, PseudoFixtureDef):
# save fixture as one to add our finalizer to, if we're not cached requested_fixtures_that_should_finalize_us.append(fixturedef)
# resolves #12135
parent_fixtures_to_add_finalizer.append(fixturedef)
# Check for (and return) cached value/exception.
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:
cache_key = self.cached_result[1] cache_key = self.cached_result[1]
@ -1065,9 +1071,11 @@ class FixtureDef(Generic[FixtureValue]):
self.finish(request) self.finish(request)
assert self.cached_result is None assert self.cached_result is None
# Add finalizer to requested fixtures we saved previously.
# We make sure to do this after checking for cached value to avoid
# adding our finalizer multiple times. (#12135)
finalizer = functools.partial(self.finish, request=request) finalizer = functools.partial(self.finish, request=request)
# add finalizer to parent fixtures for parent_fixture in requested_fixtures_that_should_finalize_us:
for parent_fixture in parent_fixtures_to_add_finalizer:
parent_fixture.addfinalizer(finalizer) parent_fixture.addfinalizer(finalizer)
ihook = request.node.ihook ihook = request.node.ihook

View File

@ -4753,39 +4753,6 @@ def test_scoped_fixture_teardown_order(pytester: Pytester) -> None:
assert result.ret == 0 assert result.ret == 0
def test_subfixture_not_queue_multiple_finalizers(pytester: Pytester) -> None:
"""Make sure subfixtures don't needlessly requeue their finalizer multiple times in
parent fixture.
Regression test for #12135
"""
pytester.makepyfile(
"""
import pytest
def _assert_len(request):
assert len(request._get_active_fixturedef('fixture_1')._finalizers) == 1
@pytest.fixture(scope="module")
def fixture_1():
...
@pytest.fixture(scope="module")
def fixture_2(request, fixture_1):
yield
def test_1(request, fixture_2):
_assert_len(request)
def test_2(request, fixture_2):
_assert_len(request)
"""
)
result = pytester.runpytest()
assert result.ret == 0
def test_subfixture_teardown_order(pytester: Pytester) -> None: def test_subfixture_teardown_order(pytester: Pytester) -> None:
""" """
Make sure fixtures don't re-register their finalization in parent fixtures multiple Make sure fixtures don't re-register their finalization in parent fixtures multiple