Merge f86aa30530
into f426c0b35a
This commit is contained in:
commit
d2bd3bae13
|
@ -1054,12 +1054,12 @@ class CallSpec2:
|
||||||
id: str,
|
id: str,
|
||||||
marks: Iterable[Union[Mark, MarkDecorator]],
|
marks: Iterable[Union[Mark, MarkDecorator]],
|
||||||
scope: Scope,
|
scope: Scope,
|
||||||
param_index: int,
|
param_indices: Tuple[int, ...],
|
||||||
) -> "CallSpec2":
|
) -> "CallSpec2":
|
||||||
params = self.params.copy()
|
params = self.params.copy()
|
||||||
indices = self.indices.copy()
|
indices = self.indices.copy()
|
||||||
arg2scope = dict(self._arg2scope)
|
arg2scope = dict(self._arg2scope)
|
||||||
for arg, val in zip(argnames, valset):
|
for arg, val, param_index in zip(argnames, valset, param_indices):
|
||||||
if arg in params:
|
if arg in params:
|
||||||
raise ValueError(f"duplicate parametrization of {arg!r}")
|
raise ValueError(f"duplicate parametrization of {arg!r}")
|
||||||
params[arg] = val
|
params[arg] = val
|
||||||
|
@ -1088,7 +1088,57 @@ def get_direct_param_fixture_func(request: FixtureRequest) -> Any:
|
||||||
return request.param
|
return request.param
|
||||||
|
|
||||||
|
|
||||||
# Used for storing pseudo fixturedefs for direct parametrization.
|
def resolve_values_indices_in_parametersets(
|
||||||
|
argnames: Sequence[str],
|
||||||
|
parametersets: Sequence[ParameterSet],
|
||||||
|
) -> List[Tuple[int, ...]]:
|
||||||
|
"""Resolve indices of the values in parameter sets. The index of a value is determined by
|
||||||
|
where the value first appears in the existing values of the argname. In other words, given
|
||||||
|
a subset of the cross-product of some ordered sets, it substitues the values in the subset
|
||||||
|
members with their index in the respective sets. 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, 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 ``metafunc.parametrize()``.
|
||||||
|
:param parametersets:
|
||||||
|
The parameter sets, each containing a set of values corresponding
|
||||||
|
to ``argnames``.
|
||||||
|
:returns:
|
||||||
|
List of tuples of indices, each tuple for a parameter set.
|
||||||
|
"""
|
||||||
|
indices: List[List[int]] = []
|
||||||
|
argname_value_indices_for_hashable_ones: Dict[str, Dict[object, int]] = defaultdict(
|
||||||
|
dict
|
||||||
|
)
|
||||||
|
argvalues_count: Dict[str, int] = defaultdict(int)
|
||||||
|
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
|
||||||
|
except TypeError: # `value` is not hashable
|
||||||
|
argname_indices.append(argvalues_count[argname])
|
||||||
|
argvalues_count[argname] += 1
|
||||||
|
indices.append(argname_indices)
|
||||||
|
return list(zip(*indices))
|
||||||
|
|
||||||
|
|
||||||
|
# Used for storing artificial fixturedefs for direct parametrization.
|
||||||
name2pseudofixturedef_key = StashKey[Dict[str, FixtureDef[Any]]]()
|
name2pseudofixturedef_key = StashKey[Dict[str, FixtureDef[Any]]]()
|
||||||
|
|
||||||
|
|
||||||
|
@ -1243,13 +1293,15 @@ class Metafunc:
|
||||||
ids = self._resolve_parameter_set_ids(
|
ids = self._resolve_parameter_set_ids(
|
||||||
argnames, ids, parametersets, nodeid=self.definition.nodeid
|
argnames, ids, parametersets, nodeid=self.definition.nodeid
|
||||||
)
|
)
|
||||||
|
param_indices_list = resolve_values_indices_in_parametersets(
|
||||||
|
argnames, parametersets
|
||||||
|
)
|
||||||
# Store used (possibly generated) ids with parametrize Marks.
|
# Store used (possibly generated) ids with parametrize Marks.
|
||||||
if _param_mark and _param_mark._param_ids_from and generated_ids is None:
|
if _param_mark and _param_mark._param_ids_from and generated_ids is None:
|
||||||
object.__setattr__(_param_mark._param_ids_from, "_param_ids_generated", ids)
|
object.__setattr__(_param_mark._param_ids_from, "_param_ids_generated", ids)
|
||||||
|
|
||||||
# Add funcargs as fixturedefs to fixtureinfo.arg2fixturedefs by registering
|
# Add funcargs as fixturedefs to fixtureinfo.arg2fixturedefs by registering
|
||||||
# artificial "pseudo" FixtureDef's so that later at test execution time we can
|
# artificial FixtureDef's so that later at test execution time we can
|
||||||
# rely on a proper FixtureDef to exist for fixture setup.
|
# rely on a proper FixtureDef to exist for fixture setup.
|
||||||
node = None
|
node = None
|
||||||
# If we have a scope that is higher than function, we need
|
# If we have a scope that is higher than function, we need
|
||||||
|
@ -1305,8 +1357,8 @@ class Metafunc:
|
||||||
# of all calls.
|
# of all calls.
|
||||||
newcalls = []
|
newcalls = []
|
||||||
for callspec in self._calls or [CallSpec2()]:
|
for callspec in self._calls or [CallSpec2()]:
|
||||||
for param_index, (param_id, param_set) in enumerate(
|
for param_id, param_set, param_indices in zip(
|
||||||
zip(ids, parametersets)
|
ids, parametersets, param_indices_list
|
||||||
):
|
):
|
||||||
newcallspec = callspec.setmulti(
|
newcallspec = callspec.setmulti(
|
||||||
argnames=argnames,
|
argnames=argnames,
|
||||||
|
@ -1314,7 +1366,7 @@ class Metafunc:
|
||||||
id=param_id,
|
id=param_id,
|
||||||
marks=param_set.marks,
|
marks=param_set.marks,
|
||||||
scope=scope_,
|
scope=scope_,
|
||||||
param_index=param_index,
|
param_indices=param_indices,
|
||||||
)
|
)
|
||||||
newcalls.append(newcallspec)
|
newcalls.append(newcallspec)
|
||||||
self._calls = newcalls
|
self._calls = newcalls
|
||||||
|
|
|
@ -21,10 +21,12 @@ from _pytest import fixtures
|
||||||
from _pytest import python
|
from _pytest import python
|
||||||
from _pytest.compat import getfuncargnames
|
from _pytest.compat import getfuncargnames
|
||||||
from _pytest.compat import NOTSET
|
from _pytest.compat import NOTSET
|
||||||
|
from _pytest.mark import ParameterSet
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
from _pytest.pytester import Pytester
|
from _pytest.pytester import Pytester
|
||||||
from _pytest.python import Function
|
from _pytest.python import Function
|
||||||
from _pytest.python import IdMaker
|
from _pytest.python import IdMaker
|
||||||
|
from _pytest.python import resolve_values_indices_in_parametersets
|
||||||
from _pytest.scope import Scope
|
from _pytest.scope import Scope
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -985,6 +987,15 @@ class TestMetafunc:
|
||||||
assert metafunc._calls[1].params == dict(x=3, y=4)
|
assert metafunc._calls[1].params == dict(x=3, y=4)
|
||||||
assert metafunc._calls[1].id == "3-4"
|
assert metafunc._calls[1].id == "3-4"
|
||||||
|
|
||||||
|
def test_parametrize_with_duplicate_values(self) -> None:
|
||||||
|
metafunc = self.Metafunc(lambda x, y: None)
|
||||||
|
metafunc.parametrize(("x", "y"), [(1, 2), (3, 4), (1, 5), (2, 2)])
|
||||||
|
assert len(metafunc._calls) == 4
|
||||||
|
assert metafunc._calls[0].indices == dict(x=0, y=0)
|
||||||
|
assert metafunc._calls[1].indices == dict(x=1, y=1)
|
||||||
|
assert metafunc._calls[2].indices == dict(x=0, y=2)
|
||||||
|
assert metafunc._calls[3].indices == dict(x=2, y=0)
|
||||||
|
|
||||||
def test_high_scoped_parametrize_reordering(self, pytester: Pytester) -> None:
|
def test_high_scoped_parametrize_reordering(self, pytester: Pytester) -> None:
|
||||||
pytester.makepyfile(
|
pytester.makepyfile(
|
||||||
"""
|
"""
|
||||||
|
@ -1019,6 +1030,35 @@ class TestMetafunc:
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_high_scoped_parametrize_with_duplicate_values_reordering(
|
||||||
|
self, pytester: Pytester
|
||||||
|
) -> None:
|
||||||
|
pytester.makepyfile(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.fixture(scope='module')
|
||||||
|
def fixture1(request):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@pytest.fixture(scope='module')
|
||||||
|
def fixture2(request):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("fixture1, fixture2", [("a", 0), ("b", 1), ("a", 2)], indirect=True)
|
||||||
|
def test(fixture1, fixture2):
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = pytester.runpytest("--collect-only")
|
||||||
|
result.stdout.re_match_lines(
|
||||||
|
[
|
||||||
|
r" <Function test\[a-0\]>",
|
||||||
|
r" <Function test\[a-2\]>",
|
||||||
|
r" <Function test\[b-1\]>",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
def test_parametrize_multiple_times(self, pytester: Pytester) -> None:
|
def test_parametrize_multiple_times(self, pytester: Pytester) -> None:
|
||||||
pytester.makepyfile(
|
pytester.makepyfile(
|
||||||
"""
|
"""
|
||||||
|
@ -1650,6 +1690,16 @@ class TestMetafuncFunctional:
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_resolve_values_indices_in_parametersets(self, pytester: Pytester) -> None:
|
||||||
|
indices = resolve_values_indices_in_parametersets(
|
||||||
|
("a", "b"),
|
||||||
|
[
|
||||||
|
ParameterSet.extract_from((a, b))
|
||||||
|
for a, b in [(1, 1), (1, 2), (2, 3), ([2], 4), ([2], 1)]
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert indices == [(0, 0), (0, 1), (1, 2), (2, 3), (3, 0)]
|
||||||
|
|
||||||
|
|
||||||
class TestMetafuncFunctionalAuto:
|
class TestMetafuncFunctionalAuto:
|
||||||
"""Tests related to automatically find out the correct scope for
|
"""Tests related to automatically find out the correct scope for
|
||||||
|
|
Loading…
Reference in New Issue