Skip to content

Commit

Permalink
Finalize changes
Browse files Browse the repository at this point in the history
  • Loading branch information
sadra-barikbin committed Jul 15, 2023
1 parent 3bc20b6 commit d2ba7fd
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 96 deletions.
99 changes: 11 additions & 88 deletions src/_pytest/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@
from _pytest.pathlib import bestrelpath
from _pytest.scope import HIGH_SCOPES
from _pytest.scope import Scope
from _pytest.stash import StashKey


if TYPE_CHECKING:
Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -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)


Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -1585,19 +1512,15 @@ def getfixtureclosure(
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).

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(
Expand Down
70 changes: 62 additions & 8 deletions src/_pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
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
Expand All @@ -65,8 +64,6 @@
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
Expand All @@ -83,6 +80,7 @@
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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit d2ba7fd

Please sign in to comment.