21
21
from typing import Iterable
22
22
from typing import Iterator
23
23
from typing import List
24
+ from typing import Mapping
24
25
from typing import MutableMapping
25
26
from typing import NoReturn
26
27
from typing import Optional
28
+ from typing import OrderedDict
27
29
from typing import overload
28
30
from typing import Sequence
29
31
from typing import Set
76
78
77
79
78
80
if TYPE_CHECKING :
79
- from typing import Deque
80
-
81
81
from _pytest .main import Session
82
82
from _pytest .python import CallSpec2
83
83
from _pytest .python import Function
@@ -161,6 +161,12 @@ def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]:
161
161
)
162
162
163
163
164
+ # Algorithm for sorting on a per-parametrized resource setup basis.
165
+ # It is called for Session scope first and performs sorting
166
+ # down to the lower scopes such as to minimize number of "high scope"
167
+ # setups and teardowns.
168
+
169
+
164
170
@dataclasses .dataclass (frozen = True )
165
171
class FixtureArgKey :
166
172
argname : str
@@ -169,97 +175,95 @@ class FixtureArgKey:
169
175
item_cls : Optional [type ]
170
176
171
177
172
- def get_parametrized_fixture_keys (
178
+ _V = TypeVar ("_V" )
179
+ OrderedSet = Dict [_V , None ]
180
+
181
+
182
+ def get_parametrized_fixture_argkeys (
173
183
item : nodes .Item , scope : Scope
174
184
) -> Iterator [FixtureArgKey ]:
175
185
"""Return list of keys for all parametrized arguments which match
176
186
the specified scope."""
177
187
assert scope is not Scope .Function
188
+
178
189
try :
179
190
callspec : CallSpec2 = item .callspec # type: ignore[attr-defined]
180
191
except AttributeError :
181
192
return
193
+
194
+ item_cls = None
195
+ if scope is Scope .Session :
196
+ scoped_item_path = None
197
+ elif scope is Scope .Package :
198
+ # Package key = module's directory.
199
+ scoped_item_path = item .path .parent
200
+ elif scope is Scope .Module :
201
+ scoped_item_path = item .path
202
+ elif scope is Scope .Class :
203
+ scoped_item_path = item .path
204
+ item_cls = item .cls # type: ignore[attr-defined]
205
+ else :
206
+ assert_never (scope )
207
+
182
208
for argname in callspec .indices :
183
209
if callspec ._arg2scope [argname ] != scope :
184
210
continue
185
-
186
- item_cls = None
187
- if scope is Scope .Session :
188
- scoped_item_path = None
189
- elif scope is Scope .Package :
190
- # Package key = module's directory.
191
- scoped_item_path = item .path .parent
192
- elif scope is Scope .Module :
193
- scoped_item_path = item .path
194
- elif scope is Scope .Class :
195
- scoped_item_path = item .path
196
- item_cls = item .cls # type: ignore[attr-defined]
197
- else :
198
- assert_never (scope )
199
-
200
211
param_index = callspec .indices [argname ]
201
212
yield FixtureArgKey (argname , param_index , scoped_item_path , item_cls )
202
213
203
214
204
- # Algorithm for sorting on a per-parametrized resource setup basis.
205
- # It is called for Session scope first and performs sorting
206
- # down to the lower scopes such as to minimize number of "high scope"
207
- # setups and teardowns.
208
-
209
-
210
215
def reorder_items (items : Sequence [nodes .Item ]) -> List [nodes .Item ]:
211
- argkeys_cache : Dict [Scope , Dict [nodes .Item , Dict [FixtureArgKey , None ]]] = {}
212
- items_by_argkey : Dict [Scope , Dict [FixtureArgKey , Deque [nodes .Item ]]] = {}
216
+ argkeys_by_item : Dict [Scope , Dict [nodes .Item , OrderedSet [FixtureArgKey ]]] = {}
217
+ items_by_argkey : Dict [
218
+ Scope , Dict [FixtureArgKey , OrderedDict [nodes .Item , None ]]
219
+ ] = {}
213
220
for scope in HIGH_SCOPES :
214
- scoped_argkeys_cache = argkeys_cache [scope ] = {}
215
- scoped_items_by_argkey = items_by_argkey [scope ] = defaultdict (deque )
221
+ scoped_argkeys_by_item = argkeys_by_item [scope ] = {}
222
+ scoped_items_by_argkey = items_by_argkey [scope ] = defaultdict (OrderedDict )
216
223
for item in items :
217
- keys = dict .fromkeys (get_parametrized_fixture_keys (item , scope ), None )
218
- if keys :
219
- scoped_argkeys_cache [item ] = keys
220
- for key in keys :
221
- scoped_items_by_argkey [key ].append (item )
222
- items_dict = dict .fromkeys (items , None )
224
+ argkeys = dict .fromkeys (get_parametrized_fixture_argkeys (item , scope ))
225
+ if argkeys :
226
+ scoped_argkeys_by_item [item ] = argkeys
227
+ for argkey in argkeys :
228
+ scoped_items_by_argkey [argkey ][item ] = None
229
+
230
+ items_set = dict .fromkeys (items )
223
231
return list (
224
- reorder_items_atscope (items_dict , argkeys_cache , items_by_argkey , Scope .Session )
232
+ reorder_items_atscope (
233
+ items_set , argkeys_by_item , items_by_argkey , Scope .Session
234
+ )
225
235
)
226
236
227
237
228
- def fix_cache_order (
229
- item : nodes .Item ,
230
- argkeys_cache : Dict [Scope , Dict [nodes .Item , Dict [FixtureArgKey , None ]]],
231
- items_by_argkey : Dict [Scope , Dict [FixtureArgKey , "Deque[nodes.Item]" ]],
232
- ) -> None :
233
- for scope in HIGH_SCOPES :
234
- for key in argkeys_cache [scope ].get (item , []):
235
- items_by_argkey [scope ][key ].appendleft (item )
236
-
237
-
238
238
def reorder_items_atscope (
239
- items : Dict [nodes .Item , None ],
240
- argkeys_cache : Dict [Scope , Dict [nodes .Item , Dict [FixtureArgKey , None ]]],
241
- items_by_argkey : Dict [Scope , Dict [FixtureArgKey , "Deque[nodes.Item]" ]],
239
+ items : OrderedSet [nodes .Item ],
240
+ argkeys_by_item : Mapping [Scope , Mapping [nodes .Item , OrderedSet [FixtureArgKey ]]],
241
+ items_by_argkey : Mapping [
242
+ Scope , Mapping [FixtureArgKey , OrderedDict [nodes .Item , None ]]
243
+ ],
242
244
scope : Scope ,
243
- ) -> Dict [nodes .Item , None ]:
245
+ ) -> OrderedSet [nodes .Item ]:
244
246
if scope is Scope .Function or len (items ) < 3 :
245
247
return items
246
- ignore : Set [Optional [FixtureArgKey ]] = set ()
247
- items_deque = deque (items )
248
- items_done : Dict [nodes .Item , None ] = {}
248
+
249
249
scoped_items_by_argkey = items_by_argkey [scope ]
250
- scoped_argkeys_cache = argkeys_cache [scope ]
250
+ scoped_argkeys_by_item = argkeys_by_item [scope ]
251
+
252
+ ignore : Set [FixtureArgKey ] = set ()
253
+ items_deque = deque (items )
254
+ items_done : OrderedSet [nodes .Item ] = {}
251
255
while items_deque :
252
- no_argkey_group : Dict [nodes .Item , None ] = {}
256
+ no_argkey_items : OrderedSet [nodes .Item ] = {}
253
257
slicing_argkey = None
254
258
while items_deque :
255
259
item = items_deque .popleft ()
256
- if item in items_done or item in no_argkey_group :
260
+ if item in items_done or item in no_argkey_items :
257
261
continue
258
262
argkeys = dict .fromkeys (
259
- ( k for k in scoped_argkeys_cache .get (item , []) if k not in ignore ), None
263
+ k for k in scoped_argkeys_by_item .get (item , ()) if k not in ignore
260
264
)
261
265
if not argkeys :
262
- no_argkey_group [item ] = None
266
+ no_argkey_items [item ] = None
263
267
else :
264
268
slicing_argkey , _ = argkeys .popitem ()
265
269
# We don't have to remove relevant items from later in the
@@ -268,16 +272,23 @@ def reorder_items_atscope(
268
272
i for i in scoped_items_by_argkey [slicing_argkey ] if i in items
269
273
]
270
274
for i in reversed (matching_items ):
271
- fix_cache_order (i , argkeys_cache , items_by_argkey )
272
275
items_deque .appendleft (i )
276
+ # Fix items_by_argkey order.
277
+ for other_scope in HIGH_SCOPES :
278
+ other_scoped_items_by_argkey = items_by_argkey [other_scope ]
279
+ for argkey in argkeys_by_item [other_scope ].get (i , ()):
280
+ other_scoped_items_by_argkey [argkey ][i ] = None
281
+ other_scoped_items_by_argkey [argkey ].move_to_end (
282
+ i , last = False
283
+ )
273
284
break
274
- if no_argkey_group :
275
- no_argkey_group = reorder_items_atscope (
276
- no_argkey_group , argkeys_cache , items_by_argkey , scope .next_lower ()
285
+ if no_argkey_items :
286
+ reordered_no_argkey_items = reorder_items_atscope (
287
+ no_argkey_items , argkeys_by_item , items_by_argkey , scope .next_lower ()
277
288
)
278
- for item in no_argkey_group :
279
- items_done [ item ] = None
280
- ignore .add (slicing_argkey )
289
+ items_done . update ( reordered_no_argkey_items )
290
+ if slicing_argkey is not None :
291
+ ignore .add (slicing_argkey )
281
292
return items_done
282
293
283
294
0 commit comments