@@ -1035,14 +1035,25 @@ def finish(self, request: SubRequest) -> None:
1035
1035
raise BaseExceptionGroup (msg , exceptions [::- 1 ])
1036
1036
1037
1037
def execute (self , request : SubRequest ) -> FixtureValue :
1038
- finalizer = functools .partial (self .finish , request = request )
1039
- # Get required arguments and register our own finish()
1040
- # with their finalization.
1038
+ """Return the value of this fixture, executing it if not cached."""
1039
+ # Ensure that the dependent fixtures requested by this fixture are loaded.
1040
+ # This needs to be done before checking if we have a cached value, since
1041
+ # if a dependent fixture has their cache invalidated, e.g. due to
1042
+ # parametrization, they finalize themselves and fixtures depending on it
1043
+ # (which will likely include this fixture) setting `self.cached_result = None`.
1044
+ # See #4871
1045
+ requested_fixtures_that_should_finalize_us = []
1041
1046
for argname in self .argnames :
1042
1047
fixturedef = request ._get_active_fixturedef (argname )
1048
+ # Saves requested fixtures in a list so we later can add our finalizer
1049
+ # to them, ensuring that if a requested fixture gets torn down we get torn
1050
+ # down first. This is generally handled by SetupState, but still currently
1051
+ # needed when this fixture is not parametrized but depends on a parametrized
1052
+ # fixture.
1043
1053
if not isinstance (fixturedef , PseudoFixtureDef ):
1044
- fixturedef . addfinalizer ( finalizer )
1054
+ requested_fixtures_that_should_finalize_us . append ( fixturedef )
1045
1055
1056
+ # Check for (and return) cached value/exception.
1046
1057
my_cache_key = self .cache_key (request )
1047
1058
if self .cached_result is not None :
1048
1059
cache_key = self .cached_result [1 ]
@@ -1060,6 +1071,13 @@ def execute(self, request: SubRequest) -> FixtureValue:
1060
1071
self .finish (request )
1061
1072
assert self .cached_result is None
1062
1073
1074
+ # Add finalizer to requested fixtures we saved previously.
1075
+ # We make sure to do this after checking for cached value to avoid
1076
+ # adding our finalizer multiple times. (#12135)
1077
+ finalizer = functools .partial (self .finish , request = request )
1078
+ for parent_fixture in requested_fixtures_that_should_finalize_us :
1079
+ parent_fixture .addfinalizer (finalizer )
1080
+
1063
1081
ihook = request .node .ihook
1064
1082
try :
1065
1083
# Setup the fixture, run the code in it, and cache the value
0 commit comments