25
25
from typing import Any
26
26
from typing import final
27
27
from typing import Literal
28
+ from typing import NoReturn
28
29
from typing import TYPE_CHECKING
29
30
import warnings
30
31
56
57
from _pytest .fixtures import get_scope_node
57
58
from _pytest .main import Session
58
59
from _pytest .mark import ParameterSet
60
+ from _pytest .mark .structures import _HiddenParam
59
61
from _pytest .mark .structures import get_unpacked_marks
62
+ from _pytest .mark .structures import HIDDEN_PARAM
60
63
from _pytest .mark .structures import Mark
61
64
from _pytest .mark .structures import MarkDecorator
62
65
from _pytest .mark .structures import normalize_mark_list
@@ -473,7 +476,7 @@ def _genfunctions(self, name: str, funcobj) -> Iterator[Function]:
473
476
fixtureinfo .prune_dependency_tree ()
474
477
475
478
for callspec in metafunc ._calls :
476
- subname = f"{ name } [{ callspec .id } ]"
479
+ subname = f"{ name } [{ callspec .id } ]" if callspec . _idlist else name
477
480
yield Function .from_parent (
478
481
self ,
479
482
name = subname ,
@@ -884,7 +887,7 @@ class IdMaker:
884
887
# Used only for clearer error messages.
885
888
func_name : str | None
886
889
887
- def make_unique_parameterset_ids (self ) -> list [str ]:
890
+ def make_unique_parameterset_ids (self ) -> list [str | _HiddenParam ]:
888
891
"""Make a unique identifier for each ParameterSet, that may be used to
889
892
identify the parametrization in a node ID.
890
893
@@ -905,6 +908,8 @@ def make_unique_parameterset_ids(self) -> list[str]:
905
908
# Suffix non-unique IDs to make them unique.
906
909
for index , id in enumerate (resolved_ids ):
907
910
if id_counts [id ] > 1 :
911
+ if id is HIDDEN_PARAM :
912
+ self ._complain_multiple_hidden_parameter_sets ()
908
913
suffix = ""
909
914
if id and id [- 1 ].isdigit ():
910
915
suffix = "_"
@@ -919,15 +924,21 @@ def make_unique_parameterset_ids(self) -> list[str]:
919
924
)
920
925
return resolved_ids
921
926
922
- def _resolve_ids (self ) -> Iterable [str ]:
927
+ def _resolve_ids (self ) -> Iterable [str | _HiddenParam ]:
923
928
"""Resolve IDs for all ParameterSets (may contain duplicates)."""
924
929
for idx , parameterset in enumerate (self .parametersets ):
925
930
if parameterset .id is not None :
926
931
# 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 )
928
936
elif self .ids and idx < len (self .ids ) and self .ids [idx ] is not None :
929
937
# 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 )
931
942
else :
932
943
# ID not provided - generate it.
933
944
yield "-" .join (
@@ -1001,12 +1012,7 @@ def _idval_from_value_required(self, val: object, idx: int) -> str:
1001
1012
return id
1002
1013
1003
1014
# 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 ()
1010
1016
msg = (
1011
1017
f"{ prefix } ids contains unsupported value { saferepr (val )} (type: { type (val )!r} ) at index { idx } . "
1012
1018
"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:
1019
1025
and the index of the ParameterSet."""
1020
1026
return str (argname ) + str (idx )
1021
1027
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
+
1022
1043
1023
1044
@final
1024
1045
@dataclasses .dataclass (frozen = True )
@@ -1047,7 +1068,7 @@ def setmulti(
1047
1068
* ,
1048
1069
argnames : Iterable [str ],
1049
1070
valset : Iterable [object ],
1050
- id : str ,
1071
+ id : str | _HiddenParam ,
1051
1072
marks : Iterable [Mark | MarkDecorator ],
1052
1073
scope : Scope ,
1053
1074
param_index : int ,
@@ -1065,7 +1086,7 @@ def setmulti(
1065
1086
params = params ,
1066
1087
indices = indices ,
1067
1088
_arg2scope = arg2scope ,
1068
- _idlist = [* self ._idlist , id ],
1089
+ _idlist = self . _idlist if id is HIDDEN_PARAM else [* self ._idlist , id ],
1069
1090
marks = [* self .marks , * normalize_mark_list (marks )],
1070
1091
)
1071
1092
@@ -1190,6 +1211,11 @@ def parametrize(
1190
1211
They are mapped to the corresponding index in ``argvalues``.
1191
1212
``None`` means to use the auto-generated id.
1192
1213
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
+
1193
1219
If it is a callable it will be called for each entry in
1194
1220
``argvalues``, and the return value is used as part of the
1195
1221
auto-generated id for the whole set (where parts are joined with
@@ -1322,7 +1348,7 @@ def _resolve_parameter_set_ids(
1322
1348
ids : Iterable [object | None ] | Callable [[Any ], object | None ] | None ,
1323
1349
parametersets : Sequence [ParameterSet ],
1324
1350
nodeid : str ,
1325
- ) -> list [str ]:
1351
+ ) -> list [str | _HiddenParam ]:
1326
1352
"""Resolve the actual ids for the given parameter sets.
1327
1353
1328
1354
:param argnames:
0 commit comments