diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index 5449b17a1c6..599d252656b 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -211,26 +211,34 @@ def _for_parametrize( parameters = cls._parse_parametrize_parameters(argvalues, force_tuple) del argvalues - if parameters: - # Check all parameter sets have the correct number of values. - for param in parameters: - if len(param.values) != len(argnames): - msg = ( - '{nodeid}: in "parametrize" the number of names ({names_len}):\n' - " {names}\n" - "must be equal to the number of values ({values_len}):\n" - " {values}" - ) - fail( - msg.format( - nodeid=nodeid, - values=param.values, - names=argnames, - names_len=len(argnames), - values_len=len(param.values), - ), - pytrace=False, - ) + for param in parameters: + try: + values_len = len(param.values) + except TypeError: + fail( + f'{nodeid}: in "parametrize" cannot determine the number of ' + f"values: {param.values!r} is not iterable. This can happen when " + "a trailing comma in the argnames string causes a scalar " + "value to be parsed as a single-element tuple." + ) + return # never reached, but appease type checkers + if values_len != len(argnames): + msg = ( + '{nodeid}: in "parametrize" the number of names ({names_len}):\n' + " {names}\n" + "must be equal to the number of values ({values_len}):\n" + " {values}" + ) + fail( + msg.format( + nodeid=nodeid, + values=param.values, + names=argnames, + names_len=len(argnames), + values_len=len(param.values), + ), + pytrace=False, + ) else: # Empty parameter set (likely computed at runtime): create a single # parameter set with NOTSET values, with the "empty parameter set" mark applied to it.