diff --git a/AUTHORS b/AUTHORS index 83509281035..ad68b12bee7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -70,6 +70,7 @@ Benjamin Schubert Bernard Pratz Bo Wu Bob Ippolito +Brendan Cazier Brian Dorsey Brian Larsen Brian Maissy diff --git a/changelog/13907.improvement.rst b/changelog/13907.improvement.rst new file mode 100644 index 00000000000..d96eefaeffe --- /dev/null +++ b/changelog/13907.improvement.rst @@ -0,0 +1 @@ +Added a new attribute to the Callspec2 class with the stringified (either automatically or with an explicit `ids` parameter) ID of parametrization values. diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 257dd78f462..1a0bc4398fb 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -1113,6 +1113,8 @@ class CallSpec2: _idlist: Sequence[str] = dataclasses.field(default_factory=tuple) # Marks which will be applied to the item. marks: list[Mark] = dataclasses.field(default_factory=list) + # Parametrization mapping for each argname to an ID. + ids: dict[str | tuple[str, ...], str] = dataclasses.field(default_factory=dict) def setmulti( self, @@ -1142,6 +1144,14 @@ def setmulti( _arg2scope=arg2scope, _idlist=self._idlist if id is HIDDEN_PARAM else [*self._idlist, id], marks=[*self.marks, *normalize_mark_list(marks)], + ids=self.ids + if id is HIDDEN_PARAM + else { + **self.ids, + _argnames[0] + if len(_argnames := tuple(argnames)) == 1 + else _argnames: id, + }, ) def getparam(self, name: str) -> object: diff --git a/testing/test_mark.py b/testing/test_mark.py index 8d76ea310eb..f6c5d614940 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -1090,6 +1090,50 @@ def test_parameterset_for_parametrize_bad_markname(pytester: Pytester) -> None: test_parameterset_for_parametrize_marks(pytester, "bad") +def test_parametrization_callspec_ids(pytester: Pytester): + pytester.makepyfile( + """ +import pytest + +class CustomClass: + pass + +def custom_function(): + return None + +all_ids = [ + {("tuple_one", "tuple_two"): "t1a-t2a", "id_function": "11", "id_explicit": "one", "name": "first"}, + {("tuple_one", "tuple_two"): "t1a-t2a", "id_function": "11", "id_explicit": "one", "name": "2"}, + {("tuple_one", "tuple_two"): "t1a-t2a", "id_function": "11", "id_explicit": "one", "name": "3.5"}, + {("tuple_one", "tuple_two"): "t1a-t2a", "id_function": "11", "id_explicit": "one", "name": "True"}, + {("tuple_one", "tuple_two"): "t1a-t2a", "id_function": "11", "id_explicit": "one", "name": ""}, + {("tuple_one", "tuple_two"): "t1a-t2a", "id_function": "11", "id_explicit": "one", "name": "CustomClass"}, + {("tuple_one", "tuple_two"): "t1a-t2a", "id_function": "11", "id_explicit": "one", "name": "custom_function"}, + {("tuple_one", "tuple_two"): "t1b-t2b", "id_function": "11", "id_explicit": "one", "name": "first"}, + {("tuple_one", "tuple_two"): "t1b-t2b", "id_function": "11", "id_explicit": "one", "name": "2"}, + {("tuple_one", "tuple_two"): "t1b-t2b", "id_function": "11", "id_explicit": "one", "name": "3.5"}, + {("tuple_one", "tuple_two"): "t1b-t2b", "id_function": "11", "id_explicit": "one", "name": "True"}, + {("tuple_one", "tuple_two"): "t1b-t2b", "id_function": "11", "id_explicit": "one", "name": ""}, + {("tuple_one", "tuple_two"): "t1b-t2b", "id_function": "11", "id_explicit": "one", "name": "CustomClass"}, + {("tuple_one", "tuple_two"): "t1b-t2b", "id_function": "11", "id_explicit": "one", "name": "custom_function"} +] + +@pytest.mark.parametrize("name", ("first", 2, 3.5, True, lambda k: k, CustomClass, custom_function)) +@pytest.mark.parametrize("id_explicit", (1,), ids=("one",)) +@pytest.mark.parametrize("id_function", (1,), ids=lambda k: f"{k}{k}") +@pytest.mark.parametrize(("tuple_one", "tuple_two"), (("t1a", "t2a"), ("t1b", "t2b"))) +def test_more(name, id_explicit, id_function, tuple_one, tuple_two, request): + ids = request.node.callspec.ids + assert ids in all_ids +""" + ) + + reprec = pytester.inline_run() + passed, skipped, failed = reprec.countoutcomes() + assert passed == 14 + assert skipped == failed == 0 + + def test_mark_expressions_no_smear(pytester: Pytester) -> None: pytester.makepyfile( """