2121from typing import Iterable
2222from typing import Iterator
2323from typing import List
24+ from typing import Mapping
2425from typing import MutableMapping
2526from typing import NoReturn
2627from typing import Optional
@@ -161,6 +162,12 @@ def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]:
161162 )
162163
163164
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+
164171@dataclasses .dataclass (frozen = True )
165172class FixtureArgKey :
166173 argname : str
@@ -169,97 +176,91 @@ class FixtureArgKey:
169176 item_cls : Optional [type ]
170177
171178
172- def get_parametrized_fixture_keys (
179+ _V = TypeVar ("_V" )
180+ OrderedSet = Dict [_V , None ]
181+
182+
183+ def get_parametrized_fixture_argkeys (
173184 item : nodes .Item , scope : Scope
174185) -> Iterator [FixtureArgKey ]:
175186 """Return list of keys for all parametrized arguments which match
176187 the specified scope."""
177188 assert scope is not Scope .Function
189+
178190 try :
179191 callspec : CallSpec2 = item .callspec # type: ignore[attr-defined]
180192 except AttributeError :
181193 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+
182209 for argname in callspec .indices :
183210 if callspec ._arg2scope [argname ] != scope :
184211 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-
200212 param_index = callspec .indices [argname ]
201213 yield FixtureArgKey (argname , param_index , scoped_item_path , item_cls )
202214
203215
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-
210216def 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 ]]] = {}
212218 items_by_argkey : Dict [Scope , Dict [FixtureArgKey , Deque [nodes .Item ]]] = {}
213219 for scope in HIGH_SCOPES :
214- scoped_argkeys_cache = argkeys_cache [scope ] = {}
220+ scoped_argkeys_by_item = argkeys_by_item [scope ] = {}
215221 scoped_items_by_argkey = items_by_argkey [scope ] = defaultdict (deque )
216222 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 )
223230 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+ )
225234 )
226235
227236
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-
238237def 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]" ]],
242241 scope : Scope ,
243- ) -> Dict [nodes .Item , None ]:
242+ ) -> OrderedSet [nodes .Item ]:
244243 if scope is Scope .Function or len (items ) < 3 :
245244 return items
246- ignore : Set [Optional [FixtureArgKey ]] = set ()
247- items_deque = deque (items )
248- items_done : Dict [nodes .Item , None ] = {}
245+
249246 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 ] = {}
251252 while items_deque :
252- no_argkey_group : Dict [nodes .Item , None ] = {}
253+ no_argkey_items : OrderedSet [nodes .Item ] = {}
253254 slicing_argkey = None
254255 while items_deque :
255256 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 :
257258 continue
258259 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
260261 )
261262 if not argkeys :
262- no_argkey_group [item ] = None
263+ no_argkey_items [item ] = None
263264 else :
264265 slicing_argkey , _ = argkeys .popitem ()
265266 # We don't have to remove relevant items from later in the
@@ -268,16 +269,20 @@ def reorder_items_atscope(
268269 i for i in scoped_items_by_argkey [slicing_argkey ] if i in items
269270 ]
270271 for i in reversed (matching_items ):
271- fix_cache_order (i , argkeys_cache , items_by_argkey )
272272 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 )
273278 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 ()
277282 )
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 )
281286 return items_done
282287
283288
0 commit comments