@@ -260,8 +260,6 @@ def __init__(self, argnames, names_closure, name2fixturedefs):
260
260
self .name2fixturedefs = name2fixturedefs
261
261
262
262
263
-
264
-
265
263
class FixtureRequest (FuncargnamesCompatAttr ):
266
264
""" A request for a fixture from a test or fixture function.
267
265
@@ -276,34 +274,51 @@ def __init__(self, pyfuncitem):
276
274
self .fixturename = None
277
275
#: Scope string, one of "function", "class", "module", "session"
278
276
self .scope = "function"
279
- self ._funcargs = {}
280
- self ._fixturedefs = {}
277
+ # rename both attributes below because their key has changed; better an attribute error
278
+ # than subtle key misses; also backward incompatibility
279
+ self ._fixture_values = {} # (argname, scope) -> fixture value
280
+ self ._fixture_defs = {} # (argname, scope) -> FixtureDef
281
281
fixtureinfo = pyfuncitem ._fixtureinfo
282
282
self ._arg2fixturedefs = fixtureinfo .name2fixturedefs .copy ()
283
283
self ._arg2index = {}
284
- self .fixturenames = fixtureinfo .names_closure
285
284
self ._fixturemanager = pyfuncitem .session ._fixturemanager
286
285
286
+ @property
287
+ def fixturenames (self ):
288
+ # backward incompatible note: now a readonly property
289
+ return list (self ._pyfuncitem ._fixtureinfo .names_closure )
290
+
287
291
@property
288
292
def node (self ):
289
293
""" underlying collection node (depends on current request scope)"""
290
294
return self ._getscopeitem (self .scope )
291
295
292
296
293
- def _getnextfixturedef (self , argname ):
294
- fixturedefs = self ._arg2fixturedefs .get (argname , None )
297
+ def _getnextfixturedef (self , argname , scope ):
298
+ def trygetfixturedefs (argname ):
299
+ fixturedefs = self ._arg2fixturedefs .get (argname , None )
300
+ if fixturedefs is None :
301
+ fixturedefs = self ._arg2fixturedefs .get (argname + ':' + scope , None )
302
+ return fixturedefs
303
+
304
+ fixturedefs = trygetfixturedefs (argname )
295
305
if fixturedefs is None :
296
306
# we arrive here because of a a dynamic call to
297
307
# getfixturevalue(argname) usage which was naturally
298
308
# not known at parsing/collection time
299
- fixturedefs = self ._fixturemanager .getfixturedefs (
300
- argname , self ._pyfuncitem .parent .nodeid )
301
- self ._arg2fixturedefs [argname ] = fixturedefs
309
+ parentid = self ._pyfuncitem .parent .nodeid
310
+ fixturedefs = self ._fixturemanager .getfixturedefs (argname , parentid )
311
+ if fixturedefs :
312
+ self ._arg2fixturedefs [argname ] = fixturedefs
313
+ fixturedefs_by_argname = self ._fixturemanager .getfixturedefs_multiple_scopes (argname , parentid )
314
+ if fixturedefs_by_argname :
315
+ self ._arg2fixturedefs .update (fixturedefs_by_argname )
316
+ fixturedefs = trygetfixturedefs (argname )
302
317
# fixturedefs list is immutable so we maintain a decreasing index
303
- index = self ._arg2index .get (argname , 0 ) - 1
318
+ index = self ._arg2index .get (( argname , scope ) , 0 ) - 1
304
319
if fixturedefs is None or (- index > len (fixturedefs )):
305
320
raise FixtureLookupError (argname , self )
306
- self ._arg2index [argname ] = index
321
+ self ._arg2index [( argname , scope ) ] = index
307
322
return fixturedefs [index ]
308
323
309
324
@property
@@ -442,10 +457,10 @@ def getfuncargvalue(self, argname):
442
457
443
458
def _get_active_fixturedef (self , argname ):
444
459
try :
445
- return self ._fixturedefs [ argname ]
460
+ return self ._fixture_defs [( argname , self . scope ) ]
446
461
except KeyError :
447
462
try :
448
- fixturedef = self ._getnextfixturedef (argname )
463
+ fixturedef = self ._getnextfixturedef (argname , self . scope )
449
464
except FixtureLookupError :
450
465
if argname == "request" :
451
466
class PseudoFixtureDef :
@@ -456,8 +471,8 @@ class PseudoFixtureDef:
456
471
# remove indent to prevent the python3 exception
457
472
# from leaking into the call
458
473
result = self ._getfixturevalue (fixturedef )
459
- self ._funcargs [ argname ] = result
460
- self ._fixturedefs [ argname ] = fixturedef
474
+ self ._fixture_values [( argname , self . scope ) ] = result
475
+ self ._fixture_defs [( argname , self . scope ) ] = fixturedef
461
476
return fixturedef
462
477
463
478
def _get_fixturestack (self ):
@@ -578,11 +593,10 @@ def __init__(self, request, scope, param, param_index, fixturedef):
578
593
self ._fixturedef = fixturedef
579
594
self .addfinalizer = fixturedef .addfinalizer
580
595
self ._pyfuncitem = request ._pyfuncitem
581
- self ._funcargs = request ._funcargs
582
- self ._fixturedefs = request ._fixturedefs
596
+ self ._fixture_values = request ._fixture_values
597
+ self ._fixture_defs = request ._fixture_defs
583
598
self ._arg2fixturedefs = request ._arg2fixturedefs
584
599
self ._arg2index = request ._arg2index
585
- self .fixturenames = request .fixturenames
586
600
self ._fixturemanager = request ._fixturemanager
587
601
588
602
def __repr__ (self ):
@@ -622,7 +636,7 @@ def formatrepr(self):
622
636
fspath , lineno = getfslineno (function )
623
637
try :
624
638
lines , _ = inspect .getsourcelines (get_real_func (function ))
625
- except (IOError , IndexError ):
639
+ except (IOError , IndexError , TypeError ):
626
640
error_msg = "file %s, line %s: source code not available"
627
641
addline (error_msg % (fspath , lineno + 1 ))
628
642
else :
@@ -636,9 +650,9 @@ def formatrepr(self):
636
650
if msg is None :
637
651
fm = self .request ._fixturemanager
638
652
available = []
639
- for name , fixturedef in fm . _arg2fixturedefs . items ():
640
- parentid = self . request . _pyfuncitem . parent . nodeid
641
- faclist = list (fm ._matchfactories (fixturedef , parentid ))
653
+ parentid = self . request . _pyfuncitem . parent . nodeid
654
+ for name , fixturedefs in fm . _arg2fixturedefs . items ():
655
+ faclist = list (fm ._matchfactories (fixturedefs , parentid ))
642
656
if faclist :
643
657
available .append (name )
644
658
msg = "fixture %r not found" % (self .argname ,)
@@ -749,7 +763,7 @@ def execute(self, request):
749
763
assert not hasattr (self , "cached_result" )
750
764
751
765
ihook = self ._fixturemanager .session .ihook
752
- ihook .pytest_fixture_setup (fixturedef = self , request = request )
766
+ return ihook .pytest_fixture_setup (fixturedef = self , request = request )
753
767
754
768
def __repr__ (self ):
755
769
return ("<FixtureDef name=%r scope=%r baseid=%r >" %
@@ -984,10 +998,12 @@ def getfixtureclosure(self, fixturenames, parentnode):
984
998
985
999
parentid = parentnode .nodeid
986
1000
fixturenames_closure = self ._getautousenames (parentid )
1001
+
987
1002
def merge (otherlist ):
988
1003
for arg in otherlist :
989
1004
if arg not in fixturenames_closure :
990
1005
fixturenames_closure .append (arg )
1006
+
991
1007
merge (fixturenames )
992
1008
arg2fixturedefs = {}
993
1009
lastlen = - 1
@@ -1000,6 +1016,11 @@ def merge(otherlist):
1000
1016
if fixturedefs :
1001
1017
arg2fixturedefs [argname ] = fixturedefs
1002
1018
merge (fixturedefs [- 1 ].argnames )
1019
+ fixturedefs_by_argname = self .getfixturedefs_multiple_scopes (argname , parentid )
1020
+ if fixturedefs_by_argname :
1021
+ arg2fixturedefs .update (fixturedefs_by_argname )
1022
+ for fixturedefs in fixturedefs_by_argname .values ():
1023
+ merge (fixturedefs [- 1 ].argnames )
1003
1024
return fixturenames_closure , arg2fixturedefs
1004
1025
1005
1026
def pytest_generate_tests (self , metafunc ):
@@ -1018,7 +1039,7 @@ def pytest_generate_tests(self, metafunc):
1018
1039
indirect = True , scope = fixturedef .scope ,
1019
1040
ids = fixturedef .ids )
1020
1041
else :
1021
- continue # will raise FixtureLookupError at setup time
1042
+ continue # will raise FixtureLookupError at setup time
1022
1043
1023
1044
def pytest_collection_modifyitems (self , items ):
1024
1045
# separate parametrized setups
@@ -1057,25 +1078,43 @@ def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False):
1057
1078
msg = 'fixtures cannot have "pytest_funcarg__" prefix ' \
1058
1079
'and be decorated with @pytest.fixture:\n %s' % name
1059
1080
assert not name .startswith (self ._argprefix ), msg
1060
- fixturedef = FixtureDef (self , nodeid , name , obj ,
1061
- marker .scope , marker .params ,
1062
- unittest = unittest , ids = marker .ids )
1063
- faclist = self ._arg2fixturedefs .setdefault (name , [])
1064
- if fixturedef .has_location :
1065
- faclist .append (fixturedef )
1081
+
1082
+ def new_fixture_def (name , scope ):
1083
+ """Create and registers a new FixtureDef with given name and scope."""
1084
+ fixture_def = FixtureDef (self , nodeid , name , obj ,
1085
+ scope , marker .params ,
1086
+ unittest = unittest , ids = marker .ids )
1087
+
1088
+ faclist = self ._arg2fixturedefs .setdefault (name , [])
1089
+ if fixture_def .has_location :
1090
+ faclist .append (fixture_def )
1091
+ else :
1092
+ # fixturedefs with no location are at the front
1093
+ # so this inserts the current fixturedef after the
1094
+ # existing fixturedefs from external plugins but
1095
+ # before the fixturedefs provided in conftests.
1096
+ i = len ([f for f in faclist if not f .has_location ])
1097
+ faclist .insert (i , fixture_def )
1098
+ if marker .autouse :
1099
+ autousenames .append (name )
1100
+
1101
+ if marker .scope == 'invocation' :
1102
+ for new_scope in scopes :
1103
+ new_fixture_def (name + ':{0}' .format (new_scope ), new_scope )
1066
1104
else :
1067
- # fixturedefs with no location are at the front
1068
- # so this inserts the current fixturedef after the
1069
- # existing fixturedefs from external plugins but
1070
- # before the fixturedefs provided in conftests.
1071
- i = len ([f for f in faclist if not f .has_location ])
1072
- faclist .insert (i , fixturedef )
1073
- if marker .autouse :
1074
- autousenames .append (name )
1105
+ new_fixture_def (name , marker .scope )
1106
+
1075
1107
if autousenames :
1076
1108
self ._nodeid_and_autousenames .append ((nodeid or '' , autousenames ))
1077
1109
1078
1110
def getfixturedefs (self , argname , nodeid ):
1111
+ """
1112
+ Gets a list of fixtures which are applicable to the given node id.
1113
+
1114
+ :param str argname: name of the fixture to search for
1115
+ :param str nodeid: full node id of the requesting test.
1116
+ :return: list[FixtureDef]
1117
+ """
1079
1118
try :
1080
1119
fixturedefs = self ._arg2fixturedefs [argname ]
1081
1120
except KeyError :
@@ -1087,3 +1126,24 @@ def _matchfactories(self, fixturedefs, nodeid):
1087
1126
for fixturedef in fixturedefs :
1088
1127
if nodeid .startswith (fixturedef .baseid ):
1089
1128
yield fixturedef
1129
+
1130
+ def getfixturedefs_multiple_scopes (self , argname , nodeid ):
1131
+ """
1132
+ Gets multiple scoped fixtures which are applicable to the given nodeid. Multiple scoped
1133
+ fixtures are created by "invocation" scoped fixtures and have argnames in
1134
+ the form: "<argname>:<scope>" (for example "tmpdir:session").
1135
+
1136
+ :return: dict of "argname" -> [FixtureDef].
1137
+
1138
+ Arguments similar to ``getfixturedefs``.
1139
+ """
1140
+ prefix = argname + ':'
1141
+ fixturedefs_by_argname = dict ((k , v ) for k , v in self ._arg2fixturedefs .items ()
1142
+ if k .startswith (prefix ))
1143
+ if fixturedefs_by_argname :
1144
+ result = {}
1145
+ for argname , fixturedefs in fixturedefs_by_argname .items ():
1146
+ result [argname ] = tuple (self ._matchfactories (fixturedefs , nodeid ))
1147
+ return result
1148
+ else :
1149
+ return None
0 commit comments