fixtures: minor cleanups to reorder_items
This makes some minor clarity and performance improvements to the code.
This commit is contained in:
parent
98021838fd
commit
1eee63a891
|
@ -21,6 +21,7 @@ from typing import Generic
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
from typing import List
|
from typing import List
|
||||||
|
from typing import Mapping
|
||||||
from typing import MutableMapping
|
from typing import MutableMapping
|
||||||
from typing import NoReturn
|
from typing import NoReturn
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
@ -161,6 +162,12 @@ def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Algorithm for sorting on a per-parametrized resource setup basis.
|
||||||
|
# It is called for Session scope first and performs sorting
|
||||||
|
# down to the lower scopes such as to minimize number of "high scope"
|
||||||
|
# setups and teardowns.
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass(frozen=True)
|
@dataclasses.dataclass(frozen=True)
|
||||||
class FixtureArgKey:
|
class FixtureArgKey:
|
||||||
argname: str
|
argname: str
|
||||||
|
@ -169,19 +176,21 @@ class FixtureArgKey:
|
||||||
item_cls: Optional[type]
|
item_cls: Optional[type]
|
||||||
|
|
||||||
|
|
||||||
def get_parametrized_fixture_keys(
|
_V = TypeVar("_V")
|
||||||
|
OrderedSet = Dict[_V, None]
|
||||||
|
|
||||||
|
|
||||||
|
def get_parametrized_fixture_argkeys(
|
||||||
item: nodes.Item, scope: Scope
|
item: nodes.Item, scope: Scope
|
||||||
) -> Iterator[FixtureArgKey]:
|
) -> Iterator[FixtureArgKey]:
|
||||||
"""Return list of keys for all parametrized arguments which match
|
"""Return list of keys for all parametrized arguments which match
|
||||||
the specified scope."""
|
the specified scope."""
|
||||||
assert scope is not Scope.Function
|
assert scope is not Scope.Function
|
||||||
|
|
||||||
try:
|
try:
|
||||||
callspec: CallSpec2 = item.callspec # type: ignore[attr-defined]
|
callspec: CallSpec2 = item.callspec # type: ignore[attr-defined]
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return
|
return
|
||||||
for argname in callspec.indices:
|
|
||||||
if callspec._arg2scope[argname] != scope:
|
|
||||||
continue
|
|
||||||
|
|
||||||
item_cls = None
|
item_cls = None
|
||||||
if scope is Scope.Session:
|
if scope is Scope.Session:
|
||||||
|
@ -197,69 +206,61 @@ def get_parametrized_fixture_keys(
|
||||||
else:
|
else:
|
||||||
assert_never(scope)
|
assert_never(scope)
|
||||||
|
|
||||||
|
for argname in callspec.indices:
|
||||||
|
if callspec._arg2scope[argname] != scope:
|
||||||
|
continue
|
||||||
param_index = callspec.indices[argname]
|
param_index = callspec.indices[argname]
|
||||||
yield FixtureArgKey(argname, param_index, scoped_item_path, item_cls)
|
yield FixtureArgKey(argname, param_index, scoped_item_path, item_cls)
|
||||||
|
|
||||||
|
|
||||||
# Algorithm for sorting on a per-parametrized resource setup basis.
|
|
||||||
# It is called for Session scope first and performs sorting
|
|
||||||
# down to the lower scopes such as to minimize number of "high scope"
|
|
||||||
# setups and teardowns.
|
|
||||||
|
|
||||||
|
|
||||||
def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]:
|
def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]:
|
||||||
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]] = {}
|
argkeys_by_item: Dict[Scope, Dict[nodes.Item, OrderedSet[FixtureArgKey]]] = {}
|
||||||
items_by_argkey: Dict[Scope, Dict[FixtureArgKey, Deque[nodes.Item]]] = {}
|
items_by_argkey: Dict[Scope, Dict[FixtureArgKey, Deque[nodes.Item]]] = {}
|
||||||
for scope in HIGH_SCOPES:
|
for scope in HIGH_SCOPES:
|
||||||
scoped_argkeys_cache = argkeys_cache[scope] = {}
|
scoped_argkeys_by_item = argkeys_by_item[scope] = {}
|
||||||
scoped_items_by_argkey = items_by_argkey[scope] = defaultdict(deque)
|
scoped_items_by_argkey = items_by_argkey[scope] = defaultdict(deque)
|
||||||
for item in items:
|
for item in items:
|
||||||
keys = dict.fromkeys(get_parametrized_fixture_keys(item, scope), None)
|
argkeys = dict.fromkeys(get_parametrized_fixture_argkeys(item, scope))
|
||||||
if keys:
|
if argkeys:
|
||||||
scoped_argkeys_cache[item] = keys
|
scoped_argkeys_by_item[item] = argkeys
|
||||||
for key in keys:
|
for argkey in argkeys:
|
||||||
scoped_items_by_argkey[key].append(item)
|
scoped_items_by_argkey[argkey].append(item)
|
||||||
items_dict = dict.fromkeys(items, None)
|
|
||||||
|
items_set = dict.fromkeys(items)
|
||||||
return list(
|
return list(
|
||||||
reorder_items_atscope(items_dict, argkeys_cache, items_by_argkey, Scope.Session)
|
reorder_items_atscope(
|
||||||
|
items_set, argkeys_by_item, items_by_argkey, Scope.Session
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def fix_cache_order(
|
|
||||||
item: nodes.Item,
|
|
||||||
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]],
|
|
||||||
items_by_argkey: Dict[Scope, Dict[FixtureArgKey, "Deque[nodes.Item]"]],
|
|
||||||
) -> None:
|
|
||||||
for scope in HIGH_SCOPES:
|
|
||||||
for key in argkeys_cache[scope].get(item, []):
|
|
||||||
items_by_argkey[scope][key].appendleft(item)
|
|
||||||
|
|
||||||
|
|
||||||
def reorder_items_atscope(
|
def reorder_items_atscope(
|
||||||
items: Dict[nodes.Item, None],
|
items: OrderedSet[nodes.Item],
|
||||||
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]],
|
argkeys_by_item: Mapping[Scope, Mapping[nodes.Item, OrderedSet[FixtureArgKey]]],
|
||||||
items_by_argkey: Dict[Scope, Dict[FixtureArgKey, "Deque[nodes.Item]"]],
|
items_by_argkey: Mapping[Scope, Mapping[FixtureArgKey, "Deque[nodes.Item]"]],
|
||||||
scope: Scope,
|
scope: Scope,
|
||||||
) -> Dict[nodes.Item, None]:
|
) -> OrderedSet[nodes.Item]:
|
||||||
if scope is Scope.Function or len(items) < 3:
|
if scope is Scope.Function or len(items) < 3:
|
||||||
return items
|
return items
|
||||||
ignore: Set[Optional[FixtureArgKey]] = set()
|
|
||||||
items_deque = deque(items)
|
|
||||||
items_done: Dict[nodes.Item, None] = {}
|
|
||||||
scoped_items_by_argkey = items_by_argkey[scope]
|
scoped_items_by_argkey = items_by_argkey[scope]
|
||||||
scoped_argkeys_cache = argkeys_cache[scope]
|
scoped_argkeys_by_item = argkeys_by_item[scope]
|
||||||
|
|
||||||
|
ignore: Set[FixtureArgKey] = set()
|
||||||
|
items_deque = deque(items)
|
||||||
|
items_done: OrderedSet[nodes.Item] = {}
|
||||||
while items_deque:
|
while items_deque:
|
||||||
no_argkey_group: Dict[nodes.Item, None] = {}
|
no_argkey_items: OrderedSet[nodes.Item] = {}
|
||||||
slicing_argkey = None
|
slicing_argkey = None
|
||||||
while items_deque:
|
while items_deque:
|
||||||
item = items_deque.popleft()
|
item = items_deque.popleft()
|
||||||
if item in items_done or item in no_argkey_group:
|
if item in items_done or item in no_argkey_items:
|
||||||
continue
|
continue
|
||||||
argkeys = dict.fromkeys(
|
argkeys = dict.fromkeys(
|
||||||
(k for k in scoped_argkeys_cache.get(item, []) if k not in ignore), None
|
k for k in scoped_argkeys_by_item.get(item, ()) if k not in ignore
|
||||||
)
|
)
|
||||||
if not argkeys:
|
if not argkeys:
|
||||||
no_argkey_group[item] = None
|
no_argkey_items[item] = None
|
||||||
else:
|
else:
|
||||||
slicing_argkey, _ = argkeys.popitem()
|
slicing_argkey, _ = argkeys.popitem()
|
||||||
# We don't have to remove relevant items from later in the
|
# We don't have to remove relevant items from later in the
|
||||||
|
@ -268,15 +269,19 @@ def reorder_items_atscope(
|
||||||
i for i in scoped_items_by_argkey[slicing_argkey] if i in items
|
i for i in scoped_items_by_argkey[slicing_argkey] if i in items
|
||||||
]
|
]
|
||||||
for i in reversed(matching_items):
|
for i in reversed(matching_items):
|
||||||
fix_cache_order(i, argkeys_cache, items_by_argkey)
|
|
||||||
items_deque.appendleft(i)
|
items_deque.appendleft(i)
|
||||||
|
# Fix items_by_argkey order.
|
||||||
|
for other_scope in HIGH_SCOPES:
|
||||||
|
other_scoped_items_by_argkey = items_by_argkey[other_scope]
|
||||||
|
for argkey in argkeys_by_item[other_scope].get(i, ()):
|
||||||
|
other_scoped_items_by_argkey[argkey].appendleft(i)
|
||||||
break
|
break
|
||||||
if no_argkey_group:
|
if no_argkey_items:
|
||||||
no_argkey_group = reorder_items_atscope(
|
reordered_no_argkey_items = reorder_items_atscope(
|
||||||
no_argkey_group, argkeys_cache, items_by_argkey, scope.next_lower()
|
no_argkey_items, argkeys_by_item, items_by_argkey, scope.next_lower()
|
||||||
)
|
)
|
||||||
for item in no_argkey_group:
|
items_done.update(reordered_no_argkey_items)
|
||||||
items_done[item] = None
|
if slicing_argkey is not None:
|
||||||
ignore.add(slicing_argkey)
|
ignore.add(slicing_argkey)
|
||||||
return items_done
|
return items_done
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue