@@ -274,11 +274,43 @@ def get_direct_param_fixture_func(request):
274274 return request .param
275275
276276
277+ @attr .s (slots = True )
277278class FuncFixtureInfo (object ):
278- def __init__ (self , argnames , names_closure , name2fixturedefs ):
279- self .argnames = argnames
280- self .names_closure = names_closure
281- self .name2fixturedefs = name2fixturedefs
279+ # original function argument names
280+ argnames = attr .ib (type = tuple )
281+ # argnames that function immediately requires. These include argnames +
282+ # fixture names specified via usefixtures and via autouse=True in fixture
283+ # definitions.
284+ initialnames = attr .ib (type = tuple )
285+ names_closure = attr .ib (type = "List[str]" )
286+ name2fixturedefs = attr .ib (type = "List[str, List[FixtureDef]]" )
287+
288+ def prune_dependency_tree (self ):
289+ """Recompute names_closure from initialnames and name2fixturedefs
290+
291+ Can only reduce names_closure, which means that the new closure will
292+ always be a subset of the old one. The order is preserved.
293+
294+ This method is needed because direct parametrization may shadow some
295+ of the fixtures that were included in the originally built dependency
296+ tree. In this way the dependency tree can get pruned, and the closure
297+ of argnames may get reduced.
298+ """
299+ closure = set ()
300+ working_set = set (self .initialnames )
301+ while working_set :
302+ argname = working_set .pop ()
303+ # argname may be smth not included in the original names_closure,
304+ # in which case we ignore it. This currently happens with pseudo
305+ # FixtureDefs which wrap 'get_direct_param_fixture_func(request)'.
306+ # So they introduce the new dependency 'request' which might have
307+ # been missing in the original tree (closure).
308+ if argname not in closure and argname in self .names_closure :
309+ closure .add (argname )
310+ if argname in self .name2fixturedefs :
311+ working_set .update (self .name2fixturedefs [argname ][- 1 ].argnames )
312+
313+ self .names_closure [:] = sorted (closure , key = self .names_closure .index )
282314
283315
284316class FixtureRequest (FuncargnamesCompatAttr ):
@@ -1033,11 +1065,12 @@ def getfixtureinfo(self, node, func, cls, funcargs=True):
10331065 usefixtures = flatten (
10341066 mark .args for mark in node .iter_markers (name = "usefixtures" )
10351067 )
1036- initialnames = argnames
1037- initialnames = tuple (usefixtures ) + initialnames
1068+ initialnames = tuple (usefixtures ) + argnames
10381069 fm = node .session ._fixturemanager
1039- names_closure , arg2fixturedefs = fm .getfixtureclosure (initialnames , node )
1040- return FuncFixtureInfo (argnames , names_closure , arg2fixturedefs )
1070+ initialnames , names_closure , arg2fixturedefs = fm .getfixtureclosure (
1071+ initialnames , node
1072+ )
1073+ return FuncFixtureInfo (argnames , initialnames , names_closure , arg2fixturedefs )
10411074
10421075 def pytest_plugin_registered (self , plugin ):
10431076 nodeid = None
@@ -1085,6 +1118,12 @@ def merge(otherlist):
10851118 fixturenames_closure .append (arg )
10861119
10871120 merge (fixturenames )
1121+
1122+ # at this point, fixturenames_closure contains what we call "initialnames",
1123+ # which is a set of fixturenames the function immediately requests. We
1124+ # need to return it as well, so save this.
1125+ initialnames = tuple (fixturenames_closure )
1126+
10881127 arg2fixturedefs = {}
10891128 lastlen = - 1
10901129 while lastlen != len (fixturenames_closure ):
@@ -1106,7 +1145,7 @@ def sort_by_scope(arg_name):
11061145 return fixturedefs [- 1 ].scopenum
11071146
11081147 fixturenames_closure .sort (key = sort_by_scope )
1109- return fixturenames_closure , arg2fixturedefs
1148+ return initialnames , fixturenames_closure , arg2fixturedefs
11101149
11111150 def pytest_generate_tests (self , metafunc ):
11121151 for argname in metafunc .fixturenames :
0 commit comments