1010from functools import partial
1111from textwrap import dedent
1212from typing import List
13+ from typing import Optional
1314from typing import Tuple
1415
1516import py
3637from _pytest .main import FSHookProxy
3738from _pytest .mark import MARK_GEN
3839from _pytest .mark .structures import get_unpacked_marks
40+ from _pytest .mark .structures import Mark
3941from _pytest .mark .structures import normalize_mark_list
4042from _pytest .outcomes import fail
4143from _pytest .outcomes import skip
@@ -122,7 +124,7 @@ def pytest_cmdline_main(config):
122124
123125def pytest_generate_tests (metafunc ):
124126 for marker in metafunc .definition .iter_markers (name = "parametrize" ):
125- metafunc .parametrize (* marker .args , ** marker .kwargs )
127+ metafunc .parametrize (* marker .args , ** marker .kwargs , _param_mark = marker )
126128
127129
128130def pytest_configure (config ):
@@ -915,7 +917,16 @@ def funcargnames(self):
915917 warnings .warn (FUNCARGNAMES , stacklevel = 2 )
916918 return self .fixturenames
917919
918- def parametrize (self , argnames , argvalues , indirect = False , ids = None , scope = None ):
920+ def parametrize (
921+ self ,
922+ argnames ,
923+ argvalues ,
924+ indirect = False ,
925+ ids = None ,
926+ scope = None ,
927+ * ,
928+ _param_mark : Optional [Mark ] = None
929+ ):
919930 """ Add new invocations to the underlying test function using the list
920931 of argvalues for the given argnames. Parametrization is performed
921932 during the collection phase. If you need to setup expensive resources
@@ -938,13 +949,21 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None, scope=None)
938949 function so that it can perform more expensive setups during the
939950 setup phase of a test rather than at collection time.
940951
941- :arg ids: list of string ids, or a callable.
942- If strings, each is corresponding to the argvalues so that they are
943- part of the test id. If None is given as id of specific test, the
944- automatically generated id for that argument will be used.
945- If callable, it should take one argument (a single argvalue) and return
946- a string or return None. If None, the automatically generated id for that
947- argument will be used.
952+ :arg ids: sequence of (or generator for) ids for ``argvalues``,
953+ or a callable to return part of the id for each argvalue.
954+
955+ With sequences (and generators like ``itertools.count()``) the
956+ returned ids should be of type ``string``, ``int``, ``float``,
957+ ``bool``, or ``None``.
958+ They are mapped to the corresponding index in ``argvalues``.
959+ ``None`` means to use the auto-generated id.
960+
961+ If it is a callable it will be called for each entry in
962+ ``argvalues``, and the return value is used as part of the
963+ auto-generated id for the whole set.
964+ This is useful to provide more specific ids for certain items, e.g.
965+ dates. Returning ``None`` will use an auto-generated id.
966+
948967 If no ids are provided they will be generated automatically from
949968 the argvalues.
950969
@@ -978,8 +997,16 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None, scope=None)
978997
979998 arg_values_types = self ._resolve_arg_value_types (argnames , indirect )
980999
1000+ if _param_mark and _param_mark ._param_ids_from :
1001+ generated_ids = _param_mark ._param_ids_from ._param_ids_generated
1002+ if generated_ids is not None :
1003+ ids = generated_ids
1004+
9811005 ids = self ._resolve_arg_ids (argnames , ids , parameters , item = self .definition )
9821006
1007+ if _param_mark and _param_mark ._param_ids_from and generated_ids is None :
1008+ object .__setattr__ (_param_mark ._param_ids_from , "_param_ids_generated" , ids )
1009+
9831010 scopenum = scope2index (
9841011 scope , descr = "parametrize() call in {}" .format (self .function .__name__ )
9851012 )
@@ -1014,26 +1041,47 @@ def _resolve_arg_ids(self, argnames, ids, parameters, item):
10141041 :rtype: List[str]
10151042 :return: the list of ids for each argname given
10161043 """
1017- from _pytest ._io .saferepr import saferepr
1018-
10191044 idfn = None
10201045 if callable (ids ):
10211046 idfn = ids
10221047 ids = None
10231048 if ids :
10241049 func_name = self .function .__name__
1025- if len (ids ) != len (parameters ):
1026- msg = "In {}: {} parameter sets specified, with different number of ids: {}"
1027- fail (msg .format (func_name , len (parameters ), len (ids )), pytrace = False )
1028- for id_value in ids :
1029- if id_value is not None and not isinstance (id_value , str ):
1030- msg = "In {}: ids must be list of strings, found: {} (type: {!r})"
1050+ ids = self ._validate_ids (ids , parameters , func_name )
1051+ ids = idmaker (argnames , parameters , idfn , ids , self .config , item = item )
1052+ return ids
1053+
1054+ def _validate_ids (self , ids , parameters , func_name ):
1055+ try :
1056+ len (ids )
1057+ except TypeError :
1058+ try :
1059+ it = iter (ids )
1060+ except TypeError :
1061+ raise TypeError ("ids must be a callable, sequence or generator" )
1062+ else :
1063+ import itertools
1064+
1065+ new_ids = list (itertools .islice (it , len (parameters )))
1066+ else :
1067+ new_ids = list (ids )
1068+
1069+ if len (new_ids ) != len (parameters ):
1070+ msg = "In {}: {} parameter sets specified, with different number of ids: {}"
1071+ fail (msg .format (func_name , len (parameters ), len (ids )), pytrace = False )
1072+ for idx , id_value in enumerate (new_ids ):
1073+ if id_value is not None :
1074+ if isinstance (id_value , (float , int , bool )):
1075+ new_ids [idx ] = str (id_value )
1076+ elif not isinstance (id_value , str ):
1077+ from _pytest ._io .saferepr import saferepr
1078+
1079+ msg = "In {}: ids must be list of string/float/int/bool, found: {} (type: {!r}) at index {}"
10311080 fail (
1032- msg .format (func_name , saferepr (id_value ), type (id_value )),
1081+ msg .format (func_name , saferepr (id_value ), type (id_value ), idx ),
10331082 pytrace = False ,
10341083 )
1035- ids = idmaker (argnames , parameters , idfn , ids , self .config , item = item )
1036- return ids
1084+ return new_ids
10371085
10381086 def _resolve_arg_value_types (self , argnames , indirect ):
10391087 """Resolves if each parametrized argument must be considered a parameter to a fixture or a "funcarg"
0 commit comments