From 0ccb57904fea0a179ff4c2ae8195fc65d08d10eb Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 1 Jun 2025 22:14:02 +0300 Subject: [PATCH 1/2] python: a bit nicer error on duplicate parametrization Raising `ValueError` dumps all of the internal stacktrace to the user, which is not helpful. Raising `CollectError` is handled specially to just print the message. It would be nice to show source location or such - maybe another time. Fix #13457 --- changelog/13457.improvement.rst | 1 + src/_pytest/python.py | 4 +++- testing/python/metafunc.py | 12 ++++++++---- 3 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 changelog/13457.improvement.rst diff --git a/changelog/13457.improvement.rst b/changelog/13457.improvement.rst new file mode 100644 index 00000000000..3937384b322 --- /dev/null +++ b/changelog/13457.improvement.rst @@ -0,0 +1 @@ +The error message about duplicate parametrization no longer displays an internal stack trace. diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 1e085a80529..02b71767dc2 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -1079,7 +1079,9 @@ def setmulti( arg2scope = dict(self._arg2scope) for arg, val in zip(argnames, valset): if arg in params: - raise ValueError(f"duplicate parametrization of {arg!r}") + raise nodes.Collector.CollectError( + f"duplicate parametrization of {arg!r}" + ) params[arg] = val indices[arg] = param_index arg2scope[arg] = scope diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index e8b345aecc6..7ae26de3a18 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -82,11 +82,15 @@ def func(x, y): metafunc = self.Metafunc(func) metafunc.parametrize("x", [1, 2]) - pytest.raises(ValueError, lambda: metafunc.parametrize("x", [5, 6])) - pytest.raises(ValueError, lambda: metafunc.parametrize("x", [5, 6])) + with pytest.raises(pytest.Collector.CollectError): + metafunc.parametrize("x", [5, 6]) + with pytest.raises(pytest.Collector.CollectError): + metafunc.parametrize("x", [5, 6]) metafunc.parametrize("y", [1, 2]) - pytest.raises(ValueError, lambda: metafunc.parametrize("y", [5, 6])) - pytest.raises(ValueError, lambda: metafunc.parametrize("y", [5, 6])) + with pytest.raises(pytest.Collector.CollectError): + metafunc.parametrize("y", [5, 6]) + with pytest.raises(pytest.Collector.CollectError): + metafunc.parametrize("y", [5, 6]) with pytest.raises(TypeError, match="^ids must be a callable or an iterable$"): metafunc.parametrize("y", [5, 6], ids=42) # type: ignore[arg-type] From 30407eb7613bcafd8651beb047a8a2fc215409ab Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 2 Jun 2025 11:22:07 +0300 Subject: [PATCH 2/2] python: pinpoint nodeid in a couple of parametrization errors --- src/_pytest/python.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 02b71767dc2..82aab85a300 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -1073,6 +1073,7 @@ def setmulti( marks: Iterable[Mark | MarkDecorator], scope: Scope, param_index: int, + nodeid: str, ) -> CallSpec2: params = self.params.copy() indices = self.indices.copy() @@ -1080,7 +1081,7 @@ def setmulti( for arg, val in zip(argnames, valset): if arg in params: raise nodes.Collector.CollectError( - f"duplicate parametrization of {arg!r}" + f"{nodeid}: duplicate parametrization of {arg!r}" ) params[arg] = val indices[arg] = param_index @@ -1235,6 +1236,8 @@ def parametrize( It will also override any fixture-function defined scope, allowing to set a dynamic scope using test context or configuration. """ + nodeid = self.definition.nodeid + argnames, parametersets = ParameterSet._for_parametrize( argnames, argvalues, @@ -1246,7 +1249,7 @@ def parametrize( if "request" in argnames: fail( - "'request' is a reserved name and cannot be used in @pytest.mark.parametrize", + f"{nodeid}: 'request' is a reserved name and cannot be used in @pytest.mark.parametrize", pytrace=False, ) @@ -1341,6 +1344,7 @@ def parametrize( marks=param_set.marks, scope=scope_, param_index=param_index, + nodeid=nodeid, ) newcalls.append(newcallspec) self._calls = newcalls