@@ -274,11 +274,43 @@ def get_direct_param_fixture_func(request):
274
274
return request .param
275
275
276
276
277
+ @attr .s (slots = True )
277
278
class 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 )
282
314
283
315
284
316
class FixtureRequest (FuncargnamesCompatAttr ):
@@ -1033,11 +1065,12 @@ def getfixtureinfo(self, node, func, cls, funcargs=True):
1033
1065
usefixtures = flatten (
1034
1066
mark .args for mark in node .iter_markers (name = "usefixtures" )
1035
1067
)
1036
- initialnames = argnames
1037
- initialnames = tuple (usefixtures ) + initialnames
1068
+ initialnames = tuple (usefixtures ) + argnames
1038
1069
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 )
1041
1074
1042
1075
def pytest_plugin_registered (self , plugin ):
1043
1076
nodeid = None
@@ -1085,6 +1118,12 @@ def merge(otherlist):
1085
1118
fixturenames_closure .append (arg )
1086
1119
1087
1120
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
+
1088
1127
arg2fixturedefs = {}
1089
1128
lastlen = - 1
1090
1129
while lastlen != len (fixturenames_closure ):
@@ -1106,7 +1145,7 @@ def sort_by_scope(arg_name):
1106
1145
return fixturedefs [- 1 ].scopenum
1107
1146
1108
1147
fixturenames_closure .sort (key = sort_by_scope )
1109
- return fixturenames_closure , arg2fixturedefs
1148
+ return initialnames , fixturenames_closure , arg2fixturedefs
1110
1149
1111
1150
def pytest_generate_tests (self , metafunc ):
1112
1151
for argname in metafunc .fixturenames :
0 commit comments