Finalize changes
This commit is contained in:
parent
3bc20b6750
commit
d2ba7fd505
|
@ -65,7 +65,6 @@ from _pytest.pathlib import absolutepath
|
|||
from _pytest.pathlib import bestrelpath
|
||||
from _pytest.scope import HIGH_SCOPES
|
||||
from _pytest.scope import Scope
|
||||
from _pytest.stash import StashKey
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -149,66 +148,6 @@ def get_scope_node(
|
|||
assert_never(scope)
|
||||
|
||||
|
||||
def resolve_unique_values_and_their_indices_in_parametersets(
|
||||
argnames: Sequence[str],
|
||||
parametersets: Sequence[ParameterSet],
|
||||
) -> Tuple[Dict[str, List[object]], List[Tuple[int]]]:
|
||||
"""Resolve unique values and their indices in parameter sets. The index of a value
|
||||
is determined by when it appears in the possible values for the first time.
|
||||
For example, given ``argnames`` and ``parametersets`` below, the result would be:
|
||||
|
||||
::
|
||||
|
||||
argnames = ["A", "B", "C"]
|
||||
parametersets = [("a1", "b1", "c1"), ("a1", "b2", "c1"), ("a1", "b3", "c2")]
|
||||
result[0] = {"A": ["a1"], "B": ["b1", "b2", "b3"], "C": ["c1", "c2"]}
|
||||
result[1] = [(0, 0, 0), (0, 1, 0), (0, 2, 1)]
|
||||
|
||||
result is used in reordering `indirect`ly parametrized with multiple
|
||||
parameters or directly parametrized tests to keep items using the same fixture or
|
||||
pseudo-fixture values respectively, close together.
|
||||
|
||||
:param argnames:
|
||||
Argument names passed to ``parametrize()``.
|
||||
:param parametersets:
|
||||
The parameter sets, each containing a set of values corresponding
|
||||
to ``argnames``.
|
||||
:returns:
|
||||
Tuple of unique parameter values and their indices in parametersets.
|
||||
"""
|
||||
indices = []
|
||||
argname_value_indices_for_hashable_ones: Dict[str, Dict[object, int]] = defaultdict(
|
||||
dict
|
||||
)
|
||||
argvalues_count: Dict[str, int] = defaultdict(lambda: 0)
|
||||
unique_values: Dict[str, List[object]] = defaultdict(list)
|
||||
for i, argname in enumerate(argnames):
|
||||
argname_indices = []
|
||||
for parameterset in parametersets:
|
||||
value = parameterset.values[i]
|
||||
try:
|
||||
argname_indices.append(
|
||||
argname_value_indices_for_hashable_ones[argname][value]
|
||||
)
|
||||
except KeyError: # New unique value
|
||||
argname_value_indices_for_hashable_ones[argname][
|
||||
value
|
||||
] = argvalues_count[argname]
|
||||
argname_indices.append(argvalues_count[argname])
|
||||
argvalues_count[argname] += 1
|
||||
unique_values[argname].append(value)
|
||||
except TypeError: # `value` is not hashable
|
||||
argname_indices.append(argvalues_count[argname])
|
||||
argvalues_count[argname] += 1
|
||||
unique_values[argname].append(value)
|
||||
indices.append(argname_indices)
|
||||
return unique_values, list(zip(*indices))
|
||||
|
||||
|
||||
# Used for storing artificial fixturedefs for direct parametrization.
|
||||
name2pseudofixturedef_key = StashKey[Dict[str, "FixtureDef[Any]"]]()
|
||||
|
||||
|
||||
def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]:
|
||||
"""Return fixturemarker or None if it doesn't exist or raised
|
||||
exceptions."""
|
||||
|
@ -352,15 +291,9 @@ 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]"]],
|
||||
ignore: Set[Optional[FixtureArgKey]],
|
||||
current_scope: Scope,
|
||||
) -> None:
|
||||
for scope in HIGH_SCOPES:
|
||||
if current_scope < scope:
|
||||
continue
|
||||
for key in argkeys_cache[scope].get(item, []):
|
||||
if key in ignore:
|
||||
continue
|
||||
items_by_argkey[scope][key].appendleft(item)
|
||||
|
||||
|
||||
|
@ -404,11 +337,17 @@ def reorder_items_atscope(
|
|||
else:
|
||||
slicing_argkey, _ = argkeys.popitem()
|
||||
# deque because they'll just be ignored.
|
||||
unique_matching_items = dict.fromkeys(scoped_items_by_argkey[slicing_argkey])
|
||||
for i in reversed(unique_matching_items if sys.version_info.minor > 7 else list(unique_matching_items)):
|
||||
unique_matching_items = dict.fromkeys(
|
||||
scoped_items_by_argkey[slicing_argkey]
|
||||
)
|
||||
for i in reversed(
|
||||
unique_matching_items
|
||||
if sys.version_info.minor > 7
|
||||
else list(unique_matching_items)
|
||||
):
|
||||
if i not in items:
|
||||
continue
|
||||
fix_cache_order(i, argkeys_cache, items_by_argkey, ignore, scope)
|
||||
fix_cache_order(i, argkeys_cache, items_by_argkey)
|
||||
items_deque.appendleft(i)
|
||||
break
|
||||
if no_argkey_group:
|
||||
|
@ -447,18 +386,6 @@ class FuncFixtureInfo:
|
|||
name2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]]
|
||||
name2num_fixturedefs_used: Dict[str, int]
|
||||
|
||||
def prune_dependency_tree(self) -> None:
|
||||
"""Recompute names_closure from initialnames and name2fixturedefs.
|
||||
|
||||
Can only reduce names_closure, which means that the new closure will
|
||||
always be a subset of the old one. The order is preserved.
|
||||
|
||||
This method is needed because dynamic direct parametrization may shadow
|
||||
some of the fixtures that were included in the originally built dependency
|
||||
tree. In this way the dependency tree can get pruned, and the closure
|
||||
of argnames may get reduced.
|
||||
"""
|
||||
|
||||
|
||||
class FixtureRequest:
|
||||
"""A request for a fixture from a test or fixture function.
|
||||
|
@ -1585,8 +1512,8 @@ class FixtureManager:
|
|||
ignore_args: Sequence[str] = (),
|
||||
) -> Tuple[List[str], Dict[str, List[FixtureDef[Any]]]]:
|
||||
# Collect the closure of all fixtures, starting with the given
|
||||
# fixturenames as the initial set. As we have to visit all
|
||||
# factory definitions anyway, we also return an arg2fixturedefs
|
||||
# initialnames as the initial set. As we have to visit all
|
||||
# factory definitions anyway, we also populate arg2fixturedefs
|
||||
# mapping so that the caller can reuse it and does not have
|
||||
# to re-discover fixturedefs again for each fixturename
|
||||
# (discovering matching fixtures for a given name/node is expensive).
|
||||
|
@ -1594,10 +1521,6 @@ class FixtureManager:
|
|||
parentid = parentnode.nodeid
|
||||
fixturenames_closure: Dict[str, int] = {}
|
||||
|
||||
# At this point, fixturenames_closure contains what we call "initialnames",
|
||||
# which is a set of fixturenames the function immediately requests. We
|
||||
# need to return it as well, so save this.
|
||||
|
||||
arg2num_fixturedefs_used: Dict[str, int] = defaultdict(lambda: 0)
|
||||
arg2num_def_used_in_path: Dict[str, int] = defaultdict(lambda: 0)
|
||||
nodes_in_fixture_tree: Deque[Tuple[str, bool]] = deque(
|
||||
|
|
|
@ -40,7 +40,6 @@ from _pytest._code.code import Traceback
|
|||
from _pytest._io import TerminalWriter
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest.compat import ascii_escaped
|
||||
from _pytest.compat import assert_never
|
||||
from _pytest.compat import get_default_arg_names
|
||||
from _pytest.compat import get_real_func
|
||||
from _pytest.compat import getimfunc
|
||||
|
@ -65,8 +64,6 @@ from _pytest.fixtures import FixtureManager
|
|||
from _pytest.fixtures import FixtureRequest
|
||||
from _pytest.fixtures import FuncFixtureInfo
|
||||
from _pytest.fixtures import get_scope_node
|
||||
from _pytest.fixtures import name2pseudofixturedef_key
|
||||
from _pytest.fixtures import resolve_unique_values_and_their_indices_in_parametersets
|
||||
from _pytest.main import Session
|
||||
from _pytest.mark import MARK_GEN
|
||||
from _pytest.mark import ParameterSet
|
||||
|
@ -83,6 +80,7 @@ from _pytest.pathlib import ImportPathMismatchError
|
|||
from _pytest.pathlib import parts
|
||||
from _pytest.pathlib import visit
|
||||
from _pytest.scope import Scope
|
||||
from _pytest.stash import StashKey
|
||||
from _pytest.warning_types import PytestCollectionWarning
|
||||
from _pytest.warning_types import PytestReturnNotNoneWarning
|
||||
from _pytest.warning_types import PytestUnhandledCoroutineWarning
|
||||
|
@ -1151,11 +1149,8 @@ class CallSpec2:
|
|||
and stored in item.callspec.
|
||||
"""
|
||||
|
||||
# arg name -> arg value which will be passed to the parametrized test
|
||||
# function (direct parameterization).
|
||||
funcargs: Dict[str, object] = dataclasses.field(default_factory=dict)
|
||||
# arg name -> arg value which will be passed to a fixture of the same name
|
||||
# (indirect parametrization).
|
||||
# arg name -> arg value which will be passed to a fixture or pseudo-fixture
|
||||
# of the same name. (indirect or direct parametrization respectively)
|
||||
params: Dict[str, object] = dataclasses.field(default_factory=dict)
|
||||
# arg name -> arg index.
|
||||
indices: Dict[str, int] = dataclasses.field(default_factory=dict)
|
||||
|
@ -1208,6 +1203,65 @@ def get_direct_param_fixture_func(request: FixtureRequest) -> Any:
|
|||
return request.param
|
||||
|
||||
|
||||
def resolve_unique_values_and_their_indices_in_parametersets(
|
||||
argnames: Sequence[str],
|
||||
parametersets: Sequence[ParameterSet],
|
||||
) -> Tuple[Dict[str, List[object]], List[Tuple[int]]]:
|
||||
"""Resolve unique values and represent parameter sets by values' indices. The index of
|
||||
a value in a parameter set is determined by where the value appears in the existing values
|
||||
of the argname for the first time. For example, given ``argnames`` and ``parametersets``
|
||||
below, the result would be:
|
||||
|
||||
::
|
||||
|
||||
argnames = ["A", "B", "C"]
|
||||
parametersets = [("a1", "b1", "c1"), ("a1", "b2", "c1"), ("a1", "b3", "c2")]
|
||||
result[0] = {"A": ["a1"], "B": ["b1", "b2", "b3"], "C": ["c1", "c2"]}
|
||||
result[1] = [(0, 0, 0), (0, 1, 0), (0, 2, 1)]
|
||||
|
||||
result is used in reordering tests to keep items using the same fixture close together.
|
||||
|
||||
:param argnames:
|
||||
Argument names passed to ``parametrize()``.
|
||||
:param parametersets:
|
||||
The parameter sets, each containing a set of values corresponding
|
||||
to ``argnames``.
|
||||
:returns:
|
||||
Tuple of unique parameter values and their indices in parametersets.
|
||||
"""
|
||||
indices = []
|
||||
argname_value_indices_for_hashable_ones: Dict[str, Dict[object, int]] = defaultdict(
|
||||
dict
|
||||
)
|
||||
argvalues_count: Dict[str, int] = defaultdict(lambda: 0)
|
||||
unique_values: Dict[str, List[object]] = defaultdict(list)
|
||||
for i, argname in enumerate(argnames):
|
||||
argname_indices = []
|
||||
for parameterset in parametersets:
|
||||
value = parameterset.values[i]
|
||||
try:
|
||||
argname_indices.append(
|
||||
argname_value_indices_for_hashable_ones[argname][value]
|
||||
)
|
||||
except KeyError: # New unique value
|
||||
argname_value_indices_for_hashable_ones[argname][
|
||||
value
|
||||
] = argvalues_count[argname]
|
||||
argname_indices.append(argvalues_count[argname])
|
||||
argvalues_count[argname] += 1
|
||||
unique_values[argname].append(value)
|
||||
except TypeError: # `value` is not hashable
|
||||
argname_indices.append(argvalues_count[argname])
|
||||
argvalues_count[argname] += 1
|
||||
unique_values[argname].append(value)
|
||||
indices.append(argname_indices)
|
||||
return unique_values, list(zip(*indices))
|
||||
|
||||
|
||||
# Used for storing artificial fixturedefs for direct parametrization.
|
||||
name2pseudofixturedef_key = StashKey[Dict[str, "FixtureDef[Any]"]]()
|
||||
|
||||
|
||||
@final
|
||||
class Metafunc:
|
||||
"""Objects passed to the :hook:`pytest_generate_tests` hook.
|
||||
|
|
Loading…
Reference in New Issue