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