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