2121import os
2222from pathlib import Path
2323import re
24+ import textwrap
2425import types
2526from typing import Any
2627from typing import final
@@ -107,6 +108,12 @@ def pytest_addoption(parser: Parser) -> None:
107108 help = "Disable string escape non-ASCII characters, might cause unwanted "
108109 "side effects(use at your own risk)" ,
109110 )
111+ parser .addini (
112+ "strict_parametrization_ids" ,
113+ type = "bool" ,
114+ default = False ,
115+ help = "Emit an error if non-unique parameter set IDs are detected" ,
116+ )
110117
111118
112119def pytest_generate_tests (metafunc : Metafunc ) -> None :
@@ -878,8 +885,8 @@ class IdMaker:
878885 # Optionally, explicit IDs for ParameterSets by index.
879886 ids : Sequence [object | None ] | None
880887 # Optionally, the pytest config.
881- # Used for controlling ASCII escaping, and for calling the
882- # :hook:`pytest_make_parametrize_id` hook.
888+ # Used for controlling ASCII escaping, determining parametrization ID
889+ # strictness, and for calling the :hook:`pytest_make_parametrize_id` hook.
883890 config : Config | None
884891 # Optionally, the ID of the node being parametrized.
885892 # Used only for clearer error messages.
@@ -892,6 +899,9 @@ def make_unique_parameterset_ids(self) -> list[str | _HiddenParam]:
892899 """Make a unique identifier for each ParameterSet, that may be used to
893900 identify the parametrization in a node ID.
894901
902+ If strict_parametrization_ids is enabled, and duplicates are detected,
903+ raises CollectError. Otherwise makes the IDs unique as follows:
904+
895905 Format is <prm_1_token>-...-<prm_n_token>[counter], where prm_x_token is
896906 - user-provided id, if given
897907 - else an id derived from the value, applicable for certain types
@@ -904,6 +914,33 @@ def make_unique_parameterset_ids(self) -> list[str | _HiddenParam]:
904914 if len (resolved_ids ) != len (set (resolved_ids )):
905915 # Record the number of occurrences of each ID.
906916 id_counts = Counter (resolved_ids )
917+
918+ if self ._strict_parametrization_ids_enabled ():
919+ parameters = ", " .join (self .argnames )
920+ parametersets = ", " .join (
921+ [saferepr (list (param .values )) for param in self .parametersets ]
922+ )
923+ ids = ", " .join (
924+ id if id is not HIDDEN_PARAM else "<hidden>" for id in resolved_ids
925+ )
926+ duplicates = ", " .join (
927+ id if id is not HIDDEN_PARAM else "<hidden>"
928+ for id , count in id_counts .items ()
929+ if count > 1
930+ )
931+ msg = textwrap .dedent (f"""
932+ Duplicate parametrization IDs detected, but strict_parametrization_ids is set.
933+
934+ Test name: { self .nodeid }
935+ Parameters: { parameters }
936+ Parameter sets: { parametersets }
937+ IDs: { ids }
938+ Duplicates: { duplicates }
939+
940+ You can fix this problem using `@pytest.mark.parametrize(..., ids=...)` or `pytest.param(..., id=...)`.
941+ """ ).strip () # noqa: E501
942+ raise nodes .Collector .CollectError (msg )
943+
907944 # Map the ID to its next suffix.
908945 id_suffixes : dict [str , int ] = defaultdict (int )
909946 # Suffix non-unique IDs to make them unique.
@@ -925,6 +962,11 @@ def make_unique_parameterset_ids(self) -> list[str | _HiddenParam]:
925962 )
926963 return resolved_ids
927964
965+ def _strict_parametrization_ids_enabled (self ) -> bool :
966+ if self .config :
967+ return bool (self .config .getini ("strict_parametrization_ids" ))
968+ return False
969+
928970 def _resolve_ids (self ) -> Iterable [str | _HiddenParam ]:
929971 """Resolve IDs for all ParameterSets (may contain duplicates)."""
930972 for idx , parameterset in enumerate (self .parametersets ):
0 commit comments