5151from _pytest .config import hookimpl
5252from _pytest .config .argparsing import Parser
5353from _pytest .deprecated import check_ispytest
54- from _pytest .fixtures import FixtureDef
55- from _pytest .fixtures import FixtureRequest
54+ from _pytest .fixtures import _get_direct_parametrize_args
5655from _pytest .fixtures import FuncFixtureInfo
5756from _pytest .fixtures import get_scope_node
57+ from _pytest .fixtures import IdentityFixtureDef
5858from _pytest .main import Session
5959from _pytest .mark import ParameterSet
6060from _pytest .mark .structures import _HiddenParam
@@ -448,8 +448,6 @@ def _genfunctions(self, name: str, funcobj) -> Iterator[Function]:
448448 definition = FunctionDefinition .from_parent (self , name = name , callobj = funcobj )
449449 fixtureinfo = definition ._fixtureinfo
450450
451- # pytest_generate_tests impls call metafunc.parametrize() which fills
452- # metafunc._calls, the outcome of the hook.
453451 metafunc = Metafunc (
454452 definition = definition ,
455453 fixtureinfo = fixtureinfo ,
@@ -458,24 +456,34 @@ def _genfunctions(self, name: str, funcobj) -> Iterator[Function]:
458456 module = module ,
459457 _ispytest = True ,
460458 )
461- methods = []
459+
460+ def prune_dependency_tree_if_test_is_directly_parametrized (
461+ metafunc : Metafunc ,
462+ ) -> None :
463+ # Direct (those with `indirect=False`) parametrizations taking place in
464+ # module/class-specific `pytest_generate_tests` hooks, a.k.a dynamic direct
465+ # parametrizations using `metafunc.parametrize`, may have shadowed some
466+ # fixtures, making some fixtures no longer reachable. Update the dependency
467+ # tree to reflect what the item really needs.
468+ # Note that direct parametrizations using `@pytest.mark.parametrize` have
469+ # already been considered into making the closure using the `ignore_args`
470+ # arg to `getfixtureclosure`.
471+ if metafunc ._has_direct_parametrization :
472+ metafunc ._update_dependency_tree ()
473+
474+ methods = [prune_dependency_tree_if_test_is_directly_parametrized ]
462475 if hasattr (module , "pytest_generate_tests" ):
463476 methods .append (module .pytest_generate_tests )
464477 if cls is not None and hasattr (cls , "pytest_generate_tests" ):
465478 methods .append (cls ().pytest_generate_tests )
479+
480+ # pytest_generate_tests impls call metafunc.parametrize() which fills
481+ # metafunc._calls, the outcome of the hook.
466482 self .ihook .pytest_generate_tests .call_extra (methods , dict (metafunc = metafunc ))
467483
468484 if not metafunc ._calls :
469485 yield Function .from_parent (self , name = name , fixtureinfo = fixtureinfo )
470486 else :
471- metafunc ._recompute_direct_params_indices ()
472- # Direct parametrizations taking place in module/class-specific
473- # `metafunc.parametrize` calls may have shadowed some fixtures, so make sure
474- # we update what the function really needs a.k.a its fixture closure. Note that
475- # direct parametrizations using `@pytest.mark.parametrize` have already been considered
476- # into making the closure using `ignore_args` arg to `getfixtureclosure`.
477- fixtureinfo .prune_dependency_tree ()
478-
479487 for callspec in metafunc ._calls :
480488 subname = f"{ name } [{ callspec .id } ]" if callspec ._idlist else name
481489 yield Function .from_parent (
@@ -1108,12 +1116,8 @@ def id(self) -> str:
11081116 return "-" .join (self ._idlist )
11091117
11101118
1111- def get_direct_param_fixture_func (request : FixtureRequest ) -> Any :
1112- return request .param
1113-
1114-
11151119# Used for storing pseudo fixturedefs for direct parametrization.
1116- name2pseudofixturedef_key = StashKey [dict [str , FixtureDef [Any ]]]()
1120+ name2pseudofixturedef_key = StashKey [dict [str , IdentityFixtureDef [Any ]]]()
11171121
11181122
11191123@final
@@ -1162,6 +1166,9 @@ def __init__(
11621166
11631167 self ._params_directness : dict [str , Literal ["indirect" , "direct" ]] = {}
11641168
1169+ # Whether it's ever been directly parametrized, i.e. with `indirect=False`.
1170+ self ._has_direct_parametrization = False
1171+
11651172 def parametrize (
11661173 self ,
11671174 argnames : str | Sequence [str ],
@@ -1308,24 +1315,21 @@ def parametrize(
13081315 node = collector .session
13091316 else :
13101317 assert False , f"Unhandled missing scope: { scope } "
1311- default : dict [str , FixtureDef [Any ]] = {}
1318+ default : dict [str , IdentityFixtureDef [Any ]] = {}
13121319 name2pseudofixturedef = node .stash .setdefault (
13131320 name2pseudofixturedef_key , default
13141321 )
13151322 for argname in argnames :
13161323 if arg_directness [argname ] == "indirect" :
13171324 continue
1325+ self ._has_direct_parametrization = True
13181326 if name2pseudofixturedef is not None and argname in name2pseudofixturedef :
13191327 fixturedef = name2pseudofixturedef [argname ]
13201328 else :
1321- fixturedef = FixtureDef (
1322- config = self .config ,
1323- baseid = "" ,
1329+ fixturedef = IdentityFixtureDef (
1330+ config = self .definition .config ,
13241331 argname = argname ,
1325- func = get_direct_param_fixture_func ,
13261332 scope = scope_ ,
1327- params = None ,
1328- ids = None ,
13291333 _ispytest = True ,
13301334 )
13311335 if name2pseudofixturedef is not None :
@@ -1485,11 +1489,17 @@ def _validate_if_using_arg_names(
14851489 pytrace = False ,
14861490 )
14871491
1488- def _recompute_direct_params_indices (self ) -> None :
1489- for argname , param_type in self ._params_directness .items ():
1490- if param_type == "direct" :
1491- for i , callspec in enumerate (self ._calls ):
1492- callspec .indices [argname ] = i
1492+ def _update_dependency_tree (self ) -> None :
1493+ definition = self .definition
1494+ assert definition .parent is not None
1495+ fm = definition .parent .session ._fixturemanager
1496+ fixture_closure = fm .getfixtureclosure (
1497+ parentnode = definition ,
1498+ initialnames = definition ._fixtureinfo .initialnames ,
1499+ arg2fixturedefs = definition ._fixtureinfo .name2fixturedefs ,
1500+ ignore_args = _get_direct_parametrize_args (definition ),
1501+ )
1502+ definition ._fixtureinfo .names_closure [:] = fixture_closure
14931503
14941504
14951505def _find_parametrized_scope (
0 commit comments