Implement unpacking nested pytest.param objects

- Merge nested ids (if parent pytest.param don't have id)
- Merge nested marks with parent
This commit is contained in:
Vladimir Gorkavenko 2021-10-31 11:57:48 +04:00
parent 1824349f74
commit ebfdb5fe28
4 changed files with 95 additions and 5 deletions

View File

@ -334,6 +334,7 @@ Virgil Dupras
Vitaly Lashmanov Vitaly Lashmanov
Vlad Dragos Vlad Dragos
Vlad Radziuk Vlad Radziuk
Vladimir Gorkavenko
Vladyslav Rachek Vladyslav Rachek
Volodymyr Piskun Volodymyr Piskun
Wei Lin Wei Lin

View File

@ -0,0 +1,17 @@
Now ``pytest`` do unpack nested ``pytest.param`` values and merge their ids and marks.
If parent ``pytest.param`` has an id - it rewrites nested ids.
For example, now such a construction is possible::
@pytest.mark.parametrize(
"a,b,c", [
pytest.param(
pytest.param(1, id="one", marks=pytest.mark.one),
pytest.param(2, id="two", marks=pytest.mark.two),
pytest.param(3, id="three", marks=pytest.mark.three),
marks=pytest.mark.full
)
]
)
Earlier in this case the values ``a, b, c`` in test are a ``ParameterSet`` objects

View File

@ -1126,9 +1126,21 @@ class Metafunc:
# Create the new calls: if we are parametrize() multiple times (by applying the decorator # Create the new calls: if we are parametrize() multiple times (by applying the decorator
# more than once) then we accumulate those calls generating the cartesian product # more than once) then we accumulate those calls generating the cartesian product
# of all calls. # of all calls.
# If we parametrize the test using values that contained nested pytest.param objects,
# then we unpack them values and merge marks.
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(zip(ids, parameters)): for param_index, (param_id, param_set) in enumerate(zip(ids, parameters)):
normalized_values = []
merged_marks = list(param_set.marks) if param_set.marks else []
for param_value in param_set.values:
if not isinstance(param_value, ParameterSet):
normalized_values.append(param_value)
else:
normalized_values.append(*param_value.values)
if param_value.marks:
merged_marks.append(*param_value.marks)
param_set = ParameterSet(values=tuple(normalized_values), marks=merged_marks, id=None)
newcallspec = callspec.setmulti( newcallspec = callspec.setmulti(
valtypes=arg_values_types, valtypes=arg_values_types,
argnames=argnames, argnames=argnames,
@ -1385,10 +1397,15 @@ def _idvalset(
return parameterset.id return parameterset.id
id = None if ids is None or idx >= len(ids) else ids[idx] id = None if ids is None or idx >= len(ids) else ids[idx]
if id is None: if id is None:
this_id = [ this_id = []
_idval(val, argname, idx, idfn, nodeid=nodeid, config=config) for val, argname in zip(parameterset.values, argnames):
for val, argname in zip(parameterset.values, argnames) to_get_idval = val
] if isinstance(val, ParameterSet):
if val.id:
this_id.append(val.id)
continue
to_get_idval = (val.values[0] if len(val.values) == 1 else val.values)
this_id.append(_idval(to_get_idval, argname, idx, idfn, nodeid=nodeid, config=config))
return "-".join(this_id) return "-".join(this_id)
else: else:
return _ascii_escaped_by_config(id, config) return _ascii_escaped_by_config(id, config)

View File

@ -1,6 +1,6 @@
import os import os
import sys import sys
from typing import List from typing import List, NamedTuple, Tuple, Any
from typing import Optional from typing import Optional
from unittest import mock from unittest import mock
@ -344,6 +344,61 @@ def test_parametrize_with_module(pytester: Pytester) -> None:
assert passed[0].nodeid.split("::")[-1] == expected_id assert passed[0].nodeid.split("::")[-1] == expected_id
@pytest.fixture()
def pytester_internal_test_values():
pytest.internal_test_values = [] # type: ignore[attr-defined]
yield pytest.internal_test_values # type: ignore[attr-defined]
del pytest.internal_test_values # type: ignore[attr-defined]
def test_parametrize_with_nested_paramsets(pytester: Pytester, pytester_internal_test_values: List[Any]) -> None:
"""Test parametrize with nested pytest.param objects in value"""
case = NamedTuple(
"case",
[
("expected_test_id", str),
("expected_value", Tuple[Any, ...]),
("expected_marks", Tuple[str, ...]),
],
)
cases = (
case("1-nested_2-3", (1, 2, 3), ("parametrize",)),
case("one_two_three", (1, 2, 3), ("parametrize",)),
case("1-nested_2-tuple_value", (1, 2, (3, 3)), ("parametrize",)),
case("1-2-3", (1, 2, 3), ("parametrize", "a", "b", "c", "all",)),
)
pytester.makepyfile(
"""
import pytest
@pytest.mark.parametrize(
"a, b, c",
[
(1, pytest.param(2, id="nested_2"), pytest.param(3)),
pytest.param(1, pytest.param(2, id="nested_2"), pytest.param(3), id="one_two_three"),
(1, pytest.param(2, id="nested_2"), pytest.param((3, 3), id="tuple_value")),
pytest.param(
pytest.param(1, marks=pytest.mark.a),
pytest.param(2, marks=pytest.mark.b),
pytest.param(3, marks=pytest.mark.c),
marks=pytest.mark.all
),
]
)
def test_func(a, b, c):
pytest.internal_test_values.append((a, b, c))
"""
)
rec = pytester.inline_run()
items = [x.item for x in rec.getcalls("pytest_itemcollected")]
passed, _, _ = rec.listoutcomes()
for i, case_ in enumerate(cases):
markers = {m.name for m in (items[i].iter_markers() or ())}
expected_id = "test_func[" + case_.expected_test_id + "]"
assert markers == set(case_.expected_marks)
assert passed[i].nodeid.split("::")[-1] == expected_id
assert pytester_internal_test_values[i] == case_.expected_value
@pytest.mark.parametrize( @pytest.mark.parametrize(
("expr", "expected_error"), ("expr", "expected_error"),
[ [