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
Vlad Dragos
Vlad Radziuk
Vladimir Gorkavenko
Vladyslav Rachek
Volodymyr Piskun
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
# more than once) then we accumulate those calls generating the cartesian product
# 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 = []
for callspec in self._calls or [CallSpec2()]:
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(
valtypes=arg_values_types,
argnames=argnames,
@ -1385,10 +1397,15 @@ def _idvalset(
return parameterset.id
id = None if ids is None or idx >= len(ids) else ids[idx]
if id is None:
this_id = [
_idval(val, argname, idx, idfn, nodeid=nodeid, config=config)
for val, argname in zip(parameterset.values, argnames)
]
this_id = []
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)
else:
return _ascii_escaped_by_config(id, config)

View File

@ -1,6 +1,6 @@
import os
import sys
from typing import List
from typing import List, NamedTuple, Tuple, Any
from typing import Optional
from unittest import mock
@ -344,6 +344,61 @@ def test_parametrize_with_module(pytester: Pytester) -> None:
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(
("expr", "expected_error"),
[