2525from typing import Any
2626from typing import final
2727from typing import Literal
28+ from typing import NoReturn
2829from typing import TYPE_CHECKING
2930import warnings
3031
5657from _pytest .fixtures import get_scope_node
5758from _pytest .main import Session
5859from _pytest .mark import ParameterSet
60+ from _pytest .mark .structures import _HiddenParam
5961from _pytest .mark .structures import get_unpacked_marks
62+ from _pytest .mark .structures import HIDDEN_PARAM
6063from _pytest .mark .structures import Mark
6164from _pytest .mark .structures import MarkDecorator
6265from _pytest .mark .structures import normalize_mark_list
@@ -473,7 +476,7 @@ def _genfunctions(self, name: str, funcobj) -> Iterator[Function]:
473476 fixtureinfo .prune_dependency_tree ()
474477
475478 for callspec in metafunc ._calls :
476- subname = f"{ name } [{ callspec .id } ]"
479+ subname = f"{ name } [{ callspec .id } ]" if callspec . _idlist else name
477480 yield Function .from_parent (
478481 self ,
479482 name = subname ,
@@ -884,7 +887,7 @@ class IdMaker:
884887 # Used only for clearer error messages.
885888 func_name : str | None
886889
887- def make_unique_parameterset_ids (self ) -> list [str ]:
890+ def make_unique_parameterset_ids (self ) -> list [str | _HiddenParam ]:
888891 """Make a unique identifier for each ParameterSet, that may be used to
889892 identify the parametrization in a node ID.
890893
@@ -905,6 +908,8 @@ def make_unique_parameterset_ids(self) -> list[str]:
905908 # Suffix non-unique IDs to make them unique.
906909 for index , id in enumerate (resolved_ids ):
907910 if id_counts [id ] > 1 :
911+ if id is HIDDEN_PARAM :
912+ self ._complain_multiple_hidden_parameter_sets ()
908913 suffix = ""
909914 if id and id [- 1 ].isdigit ():
910915 suffix = "_"
@@ -919,15 +924,21 @@ def make_unique_parameterset_ids(self) -> list[str]:
919924 )
920925 return resolved_ids
921926
922- def _resolve_ids (self ) -> Iterable [str ]:
927+ def _resolve_ids (self ) -> Iterable [str | _HiddenParam ]:
923928 """Resolve IDs for all ParameterSets (may contain duplicates)."""
924929 for idx , parameterset in enumerate (self .parametersets ):
925930 if parameterset .id is not None :
926931 # ID provided directly - pytest.param(..., id="...")
927- yield _ascii_escaped_by_config (parameterset .id , self .config )
932+ if parameterset .id is HIDDEN_PARAM :
933+ yield HIDDEN_PARAM
934+ else :
935+ yield _ascii_escaped_by_config (parameterset .id , self .config )
928936 elif self .ids and idx < len (self .ids ) and self .ids [idx ] is not None :
929937 # ID provided in the IDs list - parametrize(..., ids=[...]).
930- yield self ._idval_from_value_required (self .ids [idx ], idx )
938+ if self .ids [idx ] is HIDDEN_PARAM :
939+ yield HIDDEN_PARAM
940+ else :
941+ yield self ._idval_from_value_required (self .ids [idx ], idx )
931942 else :
932943 # ID not provided - generate it.
933944 yield "-" .join (
@@ -1001,12 +1012,7 @@ def _idval_from_value_required(self, val: object, idx: int) -> str:
10011012 return id
10021013
10031014 # Fail.
1004- if self .func_name is not None :
1005- prefix = f"In { self .func_name } : "
1006- elif self .nodeid is not None :
1007- prefix = f"In { self .nodeid } : "
1008- else :
1009- prefix = ""
1015+ prefix = self ._make_error_prefix ()
10101016 msg = (
10111017 f"{ prefix } ids contains unsupported value { saferepr (val )} (type: { type (val )!r} ) at index { idx } . "
10121018 "Supported types are: str, bytes, int, float, complex, bool, enum, regex or anything with a __name__."
@@ -1019,6 +1025,21 @@ def _idval_from_argname(argname: str, idx: int) -> str:
10191025 and the index of the ParameterSet."""
10201026 return str (argname ) + str (idx )
10211027
1028+ def _complain_multiple_hidden_parameter_sets (self ) -> NoReturn :
1029+ fail (
1030+ f"{ self ._make_error_prefix ()} multiple instances of HIDDEN_PARAM "
1031+ "cannot be used in the same parametrize call, "
1032+ "because the tests names need to be unique."
1033+ )
1034+
1035+ def _make_error_prefix (self ) -> str :
1036+ if self .func_name is not None :
1037+ return f"In { self .func_name } : "
1038+ elif self .nodeid is not None :
1039+ return f"In { self .nodeid } : "
1040+ else :
1041+ return ""
1042+
10221043
10231044@final
10241045@dataclasses .dataclass (frozen = True )
@@ -1047,7 +1068,7 @@ def setmulti(
10471068 * ,
10481069 argnames : Iterable [str ],
10491070 valset : Iterable [object ],
1050- id : str ,
1071+ id : str | _HiddenParam ,
10511072 marks : Iterable [Mark | MarkDecorator ],
10521073 scope : Scope ,
10531074 param_index : int ,
@@ -1065,7 +1086,7 @@ def setmulti(
10651086 params = params ,
10661087 indices = indices ,
10671088 _arg2scope = arg2scope ,
1068- _idlist = [* self ._idlist , id ],
1089+ _idlist = self . _idlist if id is HIDDEN_PARAM else [* self ._idlist , id ],
10691090 marks = [* self .marks , * normalize_mark_list (marks )],
10701091 )
10711092
@@ -1190,6 +1211,11 @@ def parametrize(
11901211 They are mapped to the corresponding index in ``argvalues``.
11911212 ``None`` means to use the auto-generated id.
11921213
1214+ .. versionadded:: 8.4
1215+ :ref:`hidden-param` means to hide the parameter set
1216+ from the test name. Can only be used at most 1 time, as
1217+ test names need to be unique.
1218+
11931219 If it is a callable it will be called for each entry in
11941220 ``argvalues``, and the return value is used as part of the
11951221 auto-generated id for the whole set (where parts are joined with
@@ -1322,7 +1348,7 @@ def _resolve_parameter_set_ids(
13221348 ids : Iterable [object | None ] | Callable [[Any ], object | None ] | None ,
13231349 parametersets : Sequence [ParameterSet ],
13241350 nodeid : str ,
1325- ) -> list [str ]:
1351+ ) -> list [str | _HiddenParam ]:
13261352 """Resolve the actual ids for the given parameter sets.
13271353
13281354 :param argnames:
0 commit comments