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.pathlib import bestrelpath
|
||||||
from _pytest.scope import HIGH_SCOPES
|
from _pytest.scope import HIGH_SCOPES
|
||||||
from _pytest.scope import Scope
|
from _pytest.scope import Scope
|
||||||
from _pytest.stash import StashKey
|
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -149,66 +148,6 @@ def get_scope_node(
|
||||||
assert_never(scope)
|
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"]:
|
def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]:
|
||||||
"""Return fixturemarker or None if it doesn't exist or raised
|
"""Return fixturemarker or None if it doesn't exist or raised
|
||||||
exceptions."""
|
exceptions."""
|
||||||
|
@ -352,15 +291,9 @@ def fix_cache_order(
|
||||||
item: nodes.Item,
|
item: nodes.Item,
|
||||||
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]],
|
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]],
|
||||||
items_by_argkey: Dict[Scope, Dict[FixtureArgKey, "Deque[nodes.Item]"]],
|
items_by_argkey: Dict[Scope, Dict[FixtureArgKey, "Deque[nodes.Item]"]],
|
||||||
ignore: Set[Optional[FixtureArgKey]],
|
|
||||||
current_scope: Scope,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
for scope in HIGH_SCOPES:
|
for scope in HIGH_SCOPES:
|
||||||
if current_scope < scope:
|
|
||||||
continue
|
|
||||||
for key in argkeys_cache[scope].get(item, []):
|
for key in argkeys_cache[scope].get(item, []):
|
||||||
if key in ignore:
|
|
||||||
continue
|
|
||||||
items_by_argkey[scope][key].appendleft(item)
|
items_by_argkey[scope][key].appendleft(item)
|
||||||
|
|
||||||
|
|
||||||
|
@ -404,11 +337,17 @@ def reorder_items_atscope(
|
||||||
else:
|
else:
|
||||||
slicing_argkey, _ = argkeys.popitem()
|
slicing_argkey, _ = argkeys.popitem()
|
||||||
# deque because they'll just be ignored.
|
# deque because they'll just be ignored.
|
||||||
unique_matching_items = dict.fromkeys(scoped_items_by_argkey[slicing_argkey])
|
unique_matching_items = dict.fromkeys(
|
||||||
for i in reversed(unique_matching_items if sys.version_info.minor > 7 else list(unique_matching_items)):
|
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:
|
if i not in items:
|
||||||
continue
|
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)
|
items_deque.appendleft(i)
|
||||||
break
|
break
|
||||||
if no_argkey_group:
|
if no_argkey_group:
|
||||||
|
@ -447,18 +386,6 @@ class FuncFixtureInfo:
|
||||||
name2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]]
|
name2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]]
|
||||||
name2num_fixturedefs_used: Dict[str, int]
|
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:
|
class FixtureRequest:
|
||||||
"""A request for a fixture from a test or fixture function.
|
"""A request for a fixture from a test or fixture function.
|
||||||
|
@ -1585,8 +1512,8 @@ class FixtureManager:
|
||||||
ignore_args: Sequence[str] = (),
|
ignore_args: Sequence[str] = (),
|
||||||
) -> Tuple[List[str], Dict[str, List[FixtureDef[Any]]]]:
|
) -> Tuple[List[str], Dict[str, List[FixtureDef[Any]]]]:
|
||||||
# Collect the closure of all fixtures, starting with the given
|
# Collect the closure of all fixtures, starting with the given
|
||||||
# fixturenames as the initial set. As we have to visit all
|
# initialnames as the initial set. As we have to visit all
|
||||||
# factory definitions anyway, we also return an arg2fixturedefs
|
# factory definitions anyway, we also populate arg2fixturedefs
|
||||||
# mapping so that the caller can reuse it and does not have
|
# mapping so that the caller can reuse it and does not have
|
||||||
# to re-discover fixturedefs again for each fixturename
|
# to re-discover fixturedefs again for each fixturename
|
||||||
# (discovering matching fixtures for a given name/node is expensive).
|
# (discovering matching fixtures for a given name/node is expensive).
|
||||||
|
@ -1594,10 +1521,6 @@ class FixtureManager:
|
||||||
parentid = parentnode.nodeid
|
parentid = parentnode.nodeid
|
||||||
fixturenames_closure: Dict[str, int] = {}
|
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_fixturedefs_used: Dict[str, int] = defaultdict(lambda: 0)
|
||||||
arg2num_def_used_in_path: 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(
|
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 import TerminalWriter
|
||||||
from _pytest._io.saferepr import saferepr
|
from _pytest._io.saferepr import saferepr
|
||||||
from _pytest.compat import ascii_escaped
|
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_default_arg_names
|
||||||
from _pytest.compat import get_real_func
|
from _pytest.compat import get_real_func
|
||||||
from _pytest.compat import getimfunc
|
from _pytest.compat import getimfunc
|
||||||
|
@ -65,8 +64,6 @@ from _pytest.fixtures import FixtureManager
|
||||||
from _pytest.fixtures import FixtureRequest
|
from _pytest.fixtures import FixtureRequest
|
||||||
from _pytest.fixtures import FuncFixtureInfo
|
from _pytest.fixtures import FuncFixtureInfo
|
||||||
from _pytest.fixtures import get_scope_node
|
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.main import Session
|
||||||
from _pytest.mark import MARK_GEN
|
from _pytest.mark import MARK_GEN
|
||||||
from _pytest.mark import ParameterSet
|
from _pytest.mark import ParameterSet
|
||||||
|
@ -83,6 +80,7 @@ from _pytest.pathlib import ImportPathMismatchError
|
||||||
from _pytest.pathlib import parts
|
from _pytest.pathlib import parts
|
||||||
from _pytest.pathlib import visit
|
from _pytest.pathlib import visit
|
||||||
from _pytest.scope import Scope
|
from _pytest.scope import Scope
|
||||||
|
from _pytest.stash import StashKey
|
||||||
from _pytest.warning_types import PytestCollectionWarning
|
from _pytest.warning_types import PytestCollectionWarning
|
||||||
from _pytest.warning_types import PytestReturnNotNoneWarning
|
from _pytest.warning_types import PytestReturnNotNoneWarning
|
||||||
from _pytest.warning_types import PytestUnhandledCoroutineWarning
|
from _pytest.warning_types import PytestUnhandledCoroutineWarning
|
||||||
|
@ -1151,11 +1149,8 @@ class CallSpec2:
|
||||||
and stored in item.callspec.
|
and stored in item.callspec.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# arg name -> arg value which will be passed to the parametrized test
|
# arg name -> arg value which will be passed to a fixture or pseudo-fixture
|
||||||
# function (direct parameterization).
|
# of the same name. (indirect or direct parametrization respectively)
|
||||||
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).
|
|
||||||
params: Dict[str, object] = dataclasses.field(default_factory=dict)
|
params: Dict[str, object] = dataclasses.field(default_factory=dict)
|
||||||
# arg name -> arg index.
|
# arg name -> arg index.
|
||||||
indices: Dict[str, int] = dataclasses.field(default_factory=dict)
|
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
|
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
|
@final
|
||||||
class Metafunc:
|
class Metafunc:
|
||||||
"""Objects passed to the :hook:`pytest_generate_tests` hook.
|
"""Objects passed to the :hook:`pytest_generate_tests` hook.
|
||||||
|
|
Loading…
Reference in New Issue