Skip to content

Commit d2ba7fd

Browse files
Finalize changes
1 parent 3bc20b6 commit d2ba7fd

File tree

2 files changed

+73
-96
lines changed

2 files changed

+73
-96
lines changed

src/_pytest/fixtures.py

Lines changed: 11 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@
6565
from _pytest.pathlib import bestrelpath
6666
from _pytest.scope import HIGH_SCOPES
6767
from _pytest.scope import Scope
68-
from _pytest.stash import StashKey
6968

7069

7170
if TYPE_CHECKING:
@@ -149,66 +148,6 @@ def get_scope_node(
149148
assert_never(scope)
150149

151150

152-
def resolve_unique_values_and_their_indices_in_parametersets(
153-
argnames: Sequence[str],
154-
parametersets: Sequence[ParameterSet],
155-
) -> Tuple[Dict[str, List[object]], List[Tuple[int]]]:
156-
"""Resolve unique values and their indices in parameter sets. The index of a value
157-
is determined by when it appears in the possible values for the first time.
158-
For example, given ``argnames`` and ``parametersets`` below, the result would be:
159-
160-
::
161-
162-
argnames = ["A", "B", "C"]
163-
parametersets = [("a1", "b1", "c1"), ("a1", "b2", "c1"), ("a1", "b3", "c2")]
164-
result[0] = {"A": ["a1"], "B": ["b1", "b2", "b3"], "C": ["c1", "c2"]}
165-
result[1] = [(0, 0, 0), (0, 1, 0), (0, 2, 1)]
166-
167-
result is used in reordering `indirect`ly parametrized with multiple
168-
parameters or directly parametrized tests to keep items using the same fixture or
169-
pseudo-fixture values respectively, close together.
170-
171-
:param argnames:
172-
Argument names passed to ``parametrize()``.
173-
:param parametersets:
174-
The parameter sets, each containing a set of values corresponding
175-
to ``argnames``.
176-
:returns:
177-
Tuple of unique parameter values and their indices in parametersets.
178-
"""
179-
indices = []
180-
argname_value_indices_for_hashable_ones: Dict[str, Dict[object, int]] = defaultdict(
181-
dict
182-
)
183-
argvalues_count: Dict[str, int] = defaultdict(lambda: 0)
184-
unique_values: Dict[str, List[object]] = defaultdict(list)
185-
for i, argname in enumerate(argnames):
186-
argname_indices = []
187-
for parameterset in parametersets:
188-
value = parameterset.values[i]
189-
try:
190-
argname_indices.append(
191-
argname_value_indices_for_hashable_ones[argname][value]
192-
)
193-
except KeyError: # New unique value
194-
argname_value_indices_for_hashable_ones[argname][
195-
value
196-
] = argvalues_count[argname]
197-
argname_indices.append(argvalues_count[argname])
198-
argvalues_count[argname] += 1
199-
unique_values[argname].append(value)
200-
except TypeError: # `value` is not hashable
201-
argname_indices.append(argvalues_count[argname])
202-
argvalues_count[argname] += 1
203-
unique_values[argname].append(value)
204-
indices.append(argname_indices)
205-
return unique_values, list(zip(*indices))
206-
207-
208-
# Used for storing artificial fixturedefs for direct parametrization.
209-
name2pseudofixturedef_key = StashKey[Dict[str, "FixtureDef[Any]"]]()
210-
211-
212151
def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]:
213152
"""Return fixturemarker or None if it doesn't exist or raised
214153
exceptions."""
@@ -352,15 +291,9 @@ def fix_cache_order(
352291
item: nodes.Item,
353292
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]],
354293
items_by_argkey: Dict[Scope, Dict[FixtureArgKey, "Deque[nodes.Item]"]],
355-
ignore: Set[Optional[FixtureArgKey]],
356-
current_scope: Scope,
357294
) -> None:
358295
for scope in HIGH_SCOPES:
359-
if current_scope < scope:
360-
continue
361296
for key in argkeys_cache[scope].get(item, []):
362-
if key in ignore:
363-
continue
364297
items_by_argkey[scope][key].appendleft(item)
365298

366299

@@ -404,11 +337,17 @@ def reorder_items_atscope(
404337
else:
405338
slicing_argkey, _ = argkeys.popitem()
406339
# deque because they'll just be ignored.
407-
unique_matching_items = dict.fromkeys(scoped_items_by_argkey[slicing_argkey])
408-
for i in reversed(unique_matching_items if sys.version_info.minor > 7 else list(unique_matching_items)):
340+
unique_matching_items = dict.fromkeys(
341+
scoped_items_by_argkey[slicing_argkey]
342+
)
343+
for i in reversed(
344+
unique_matching_items
345+
if sys.version_info.minor > 7
346+
else list(unique_matching_items)
347+
):
409348
if i not in items:
410349
continue
411-
fix_cache_order(i, argkeys_cache, items_by_argkey, ignore, scope)
350+
fix_cache_order(i, argkeys_cache, items_by_argkey)
412351
items_deque.appendleft(i)
413352
break
414353
if no_argkey_group:
@@ -447,18 +386,6 @@ class FuncFixtureInfo:
447386
name2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]]
448387
name2num_fixturedefs_used: Dict[str, int]
449388

450-
def prune_dependency_tree(self) -> None:
451-
"""Recompute names_closure from initialnames and name2fixturedefs.
452-
453-
Can only reduce names_closure, which means that the new closure will
454-
always be a subset of the old one. The order is preserved.
455-
456-
This method is needed because dynamic direct parametrization may shadow
457-
some of the fixtures that were included in the originally built dependency
458-
tree. In this way the dependency tree can get pruned, and the closure
459-
of argnames may get reduced.
460-
"""
461-
462389

463390
class FixtureRequest:
464391
"""A request for a fixture from a test or fixture function.
@@ -1585,19 +1512,15 @@ def getfixtureclosure(
15851512
ignore_args: Sequence[str] = (),
15861513
) -> Tuple[List[str], Dict[str, List[FixtureDef[Any]]]]:
15871514
# Collect the closure of all fixtures, starting with the given
1588-
# fixturenames as the initial set. As we have to visit all
1589-
# factory definitions anyway, we also return an arg2fixturedefs
1515+
# initialnames as the initial set. As we have to visit all
1516+
# factory definitions anyway, we also populate arg2fixturedefs
15901517
# mapping so that the caller can reuse it and does not have
15911518
# to re-discover fixturedefs again for each fixturename
15921519
# (discovering matching fixtures for a given name/node is expensive).
15931520

15941521
parentid = parentnode.nodeid
15951522
fixturenames_closure: Dict[str, int] = {}
15961523

1597-
# At this point, fixturenames_closure contains what we call "initialnames",
1598-
# which is a set of fixturenames the function immediately requests. We
1599-
# need to return it as well, so save this.
1600-
16011524
arg2num_fixturedefs_used: Dict[str, int] = defaultdict(lambda: 0)
16021525
arg2num_def_used_in_path: Dict[str, int] = defaultdict(lambda: 0)
16031526
nodes_in_fixture_tree: Deque[Tuple[str, bool]] = deque(

src/_pytest/python.py

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
from _pytest._io import TerminalWriter
4141
from _pytest._io.saferepr import saferepr
4242
from _pytest.compat import ascii_escaped
43-
from _pytest.compat import assert_never
4443
from _pytest.compat import get_default_arg_names
4544
from _pytest.compat import get_real_func
4645
from _pytest.compat import getimfunc
@@ -65,8 +64,6 @@
6564
from _pytest.fixtures import FixtureRequest
6665
from _pytest.fixtures import FuncFixtureInfo
6766
from _pytest.fixtures import get_scope_node
68-
from _pytest.fixtures import name2pseudofixturedef_key
69-
from _pytest.fixtures import resolve_unique_values_and_their_indices_in_parametersets
7067
from _pytest.main import Session
7168
from _pytest.mark import MARK_GEN
7269
from _pytest.mark import ParameterSet
@@ -83,6 +80,7 @@
8380
from _pytest.pathlib import parts
8481
from _pytest.pathlib import visit
8582
from _pytest.scope import Scope
83+
from _pytest.stash import StashKey
8684
from _pytest.warning_types import PytestCollectionWarning
8785
from _pytest.warning_types import PytestReturnNotNoneWarning
8886
from _pytest.warning_types import PytestUnhandledCoroutineWarning
@@ -1151,11 +1149,8 @@ class CallSpec2:
11511149
and stored in item.callspec.
11521150
"""
11531151

1154-
# arg name -> arg value which will be passed to the parametrized test
1155-
# function (direct parameterization).
1156-
funcargs: Dict[str, object] = dataclasses.field(default_factory=dict)
1157-
# arg name -> arg value which will be passed to a fixture of the same name
1158-
# (indirect parametrization).
1152+
# arg name -> arg value which will be passed to a fixture or pseudo-fixture
1153+
# of the same name. (indirect or direct parametrization respectively)
11591154
params: Dict[str, object] = dataclasses.field(default_factory=dict)
11601155
# arg name -> arg index.
11611156
indices: Dict[str, int] = dataclasses.field(default_factory=dict)
@@ -1208,6 +1203,65 @@ def get_direct_param_fixture_func(request: FixtureRequest) -> Any:
12081203
return request.param
12091204

12101205

1206+
def resolve_unique_values_and_their_indices_in_parametersets(
1207+
argnames: Sequence[str],
1208+
parametersets: Sequence[ParameterSet],
1209+
) -> Tuple[Dict[str, List[object]], List[Tuple[int]]]:
1210+
"""Resolve unique values and represent parameter sets by values' indices. The index of
1211+
a value in a parameter set is determined by where the value appears in the existing values
1212+
of the argname for the first time. For example, given ``argnames`` and ``parametersets``
1213+
below, the result would be:
1214+
1215+
::
1216+
1217+
argnames = ["A", "B", "C"]
1218+
parametersets = [("a1", "b1", "c1"), ("a1", "b2", "c1"), ("a1", "b3", "c2")]
1219+
result[0] = {"A": ["a1"], "B": ["b1", "b2", "b3"], "C": ["c1", "c2"]}
1220+
result[1] = [(0, 0, 0), (0, 1, 0), (0, 2, 1)]
1221+
1222+
result is used in reordering tests to keep items using the same fixture close together.
1223+
1224+
:param argnames:
1225+
Argument names passed to ``parametrize()``.
1226+
:param parametersets:
1227+
The parameter sets, each containing a set of values corresponding
1228+
to ``argnames``.
1229+
:returns:
1230+
Tuple of unique parameter values and their indices in parametersets.
1231+
"""
1232+
indices = []
1233+
argname_value_indices_for_hashable_ones: Dict[str, Dict[object, int]] = defaultdict(
1234+
dict
1235+
)
1236+
argvalues_count: Dict[str, int] = defaultdict(lambda: 0)
1237+
unique_values: Dict[str, List[object]] = defaultdict(list)
1238+
for i, argname in enumerate(argnames):
1239+
argname_indices = []
1240+
for parameterset in parametersets:
1241+
value = parameterset.values[i]
1242+
try:
1243+
argname_indices.append(
1244+
argname_value_indices_for_hashable_ones[argname][value]
1245+
)
1246+
except KeyError: # New unique value
1247+
argname_value_indices_for_hashable_ones[argname][
1248+
value
1249+
] = argvalues_count[argname]
1250+
argname_indices.append(argvalues_count[argname])
1251+
argvalues_count[argname] += 1
1252+
unique_values[argname].append(value)
1253+
except TypeError: # `value` is not hashable
1254+
argname_indices.append(argvalues_count[argname])
1255+
argvalues_count[argname] += 1
1256+
unique_values[argname].append(value)
1257+
indices.append(argname_indices)
1258+
return unique_values, list(zip(*indices))
1259+
1260+
1261+
# Used for storing artificial fixturedefs for direct parametrization.
1262+
name2pseudofixturedef_key = StashKey[Dict[str, "FixtureDef[Any]"]]()
1263+
1264+
12111265
@final
12121266
class Metafunc:
12131267
"""Objects passed to the :hook:`pytest_generate_tests` hook.

0 commit comments

Comments
 (0)