Skip to content

Commit ae5849f

Browse files
Do the improvement
1 parent 9791e1d commit ae5849f

File tree

2 files changed

+56
-8
lines changed

2 files changed

+56
-8
lines changed

src/_pytest/python.py

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,12 +1136,12 @@ def setmulti(
11361136
id: str,
11371137
marks: Iterable[Union[Mark, MarkDecorator]],
11381138
scope: Scope,
1139-
param_index: int,
1139+
param_indices: Tuple[int, ...],
11401140
) -> "CallSpec2":
11411141
params = self.params.copy()
11421142
indices = self.indices.copy()
11431143
arg2scope = self._arg2scope.copy()
1144-
for arg, val in zip(argnames, valset):
1144+
for arg, val, param_index in zip(argnames, valset, param_indices):
11451145
if arg in params:
11461146
raise ValueError(f"duplicate {arg!r}")
11471147
params[arg] = val
@@ -1170,6 +1170,54 @@ def get_direct_param_fixture_func(request: FixtureRequest) -> Any:
11701170
return request.param
11711171

11721172

1173+
def resolve_values_indices_in_parametersets(
1174+
argnames: Sequence[str],
1175+
parametersets: Sequence[ParameterSet],
1176+
) -> List[Tuple[int, ...]]:
1177+
"""Resolve indices of the values in parameter sets. The index of a value is determined by
1178+
where the value first appears in the existing values of the argname. For example, given
1179+
``argnames`` and ``parametersets`` below, the result would be:
1180+
::
1181+
argnames = ["A", "B", "C"]
1182+
parametersets = [("a1", "b1", "c1"), ("a1", "b2", "c1"), ("a1", "b3", "c2")]
1183+
result = [(0, 0, 0), (0, 1, 0), (0, 2, 1)]
1184+
1185+
result is used in reordering tests to keep items using the same fixture close together.
1186+
1187+
:param argnames:
1188+
Argument names passed to ``metafunc.parametrize()``.
1189+
:param parametersets:
1190+
The parameter sets, each containing a set of values corresponding
1191+
to ``argnames``.
1192+
:returns:
1193+
List of tuples of indices, each tuple for a parameter set.
1194+
"""
1195+
indices = []
1196+
argname_value_indices_for_hashable_ones: Dict[str, Dict[object, int]] = defaultdict(
1197+
dict
1198+
)
1199+
argvalues_count: Dict[str, int] = defaultdict(lambda: 0)
1200+
for i, argname in enumerate(argnames):
1201+
argname_indices = []
1202+
for parameterset in parametersets:
1203+
value = parameterset.values[i]
1204+
try:
1205+
argname_indices.append(
1206+
argname_value_indices_for_hashable_ones[argname][value]
1207+
)
1208+
except KeyError: # New unique value
1209+
argname_value_indices_for_hashable_ones[argname][
1210+
value
1211+
] = argvalues_count[argname]
1212+
argname_indices.append(argvalues_count[argname])
1213+
argvalues_count[argname] += 1
1214+
except TypeError: # `value` is not hashable
1215+
argname_indices.append(argvalues_count[argname])
1216+
argvalues_count[argname] += 1
1217+
indices.append(argname_indices)
1218+
return list(cast(Iterable[Tuple[int]], zip(*indices)))
1219+
1220+
11731221
# Used for storing artificial fixturedefs for direct parametrization.
11741222
name2pseudofixturedef_key = StashKey[Dict[str, FixtureDef[Any]]]()
11751223

@@ -1324,7 +1372,9 @@ def parametrize(
13241372
ids = self._resolve_parameter_set_ids(
13251373
argnames, ids, parametersets, nodeid=self.definition.nodeid
13261374
)
1327-
1375+
param_indices_list = resolve_values_indices_in_parametersets(
1376+
argnames, parametersets
1377+
)
13281378
# Store used (possibly generated) ids with parametrize Marks.
13291379
if _param_mark and _param_mark._param_ids_from and generated_ids is None:
13301380
object.__setattr__(_param_mark._param_ids_from, "_param_ids_generated", ids)
@@ -1387,16 +1437,16 @@ def parametrize(
13871437
# of all calls.
13881438
newcalls = []
13891439
for callspec in self._calls or [CallSpec2()]:
1390-
for param_index, (param_id, param_set) in enumerate(
1391-
zip(ids, parametersets)
1440+
for param_id, param_set, param_indices in zip(
1441+
ids, parametersets, param_indices_list
13921442
):
13931443
newcallspec = callspec.setmulti(
13941444
argnames=argnames,
13951445
valset=param_set.values,
13961446
id=param_id,
13971447
marks=param_set.marks,
13981448
scope=scope_,
1399-
param_index=param_index,
1449+
param_indices=param_indices,
14001450
)
14011451
newcalls.append(newcallspec)
14021452
self._calls = newcalls

testing/python/metafunc.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -974,7 +974,6 @@ def test_parametrize_twoargs(self) -> None:
974974
assert metafunc._calls[1].params == dict(x=3, y=4)
975975
assert metafunc._calls[1].id == "3-4"
976976

977-
@pytest.mark.xfail(reason="Will pass upon merging PR#")
978977
def test_parametrize_with_duplicate_values(self) -> None:
979978
metafunc = self.Metafunc(lambda x, y: None)
980979
metafunc.parametrize(("x", "y"), [(1, 2), (3, 4), (1, 5), (2, 2)])
@@ -1018,7 +1017,6 @@ def test3(arg1):
10181017
]
10191018
)
10201019

1021-
@pytest.mark.xfail(reason="Will pass upon merging PR#")
10221020
def test_high_scoped_parametrize_with_duplicate_values_reordering(
10231021
self, pytester: Pytester
10241022
) -> None:

0 commit comments

Comments
 (0)