Skip to content

Commit 2a480c5

Browse files
authored
Merge pull request #3367 from brianmaissy/feature/indicative_error_for_parametrize_with_default_argument
added indicative error when parametrizing an argument with a default …
2 parents f008460 + 857098f commit 2a480c5

File tree

4 files changed

+33
-7
lines changed

4 files changed

+33
-7
lines changed

_pytest/compat.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,14 @@ def getfuncargnames(function, is_method=False, cls=None):
135135
return arg_names
136136

137137

138+
def get_default_arg_names(function):
139+
# Note: this code intentionally mirrors the code at the beginning of getfuncargnames,
140+
# to get the arguments which were excluded from its result because they had default values
141+
return tuple(p.name for p in signature(function).parameters.values()
142+
if p.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY) and
143+
p.default is not Parameter.empty)
144+
145+
138146
if _PY3:
139147
STRING_TYPES = bytes, str
140148
UNICODE_TYPES = str,

_pytest/python.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
isclass, isfunction, is_generator, ascii_escaped,
2626
REGEX_TYPE, STRING_TYPES, NoneType, NOTSET,
2727
get_real_func, getfslineno, safe_getattr,
28-
safe_str, getlocation, enum,
28+
safe_str, getlocation, enum, get_default_arg_names
2929
)
3030
from _pytest.outcomes import fail
3131
from _pytest.mark.structures import transfer_markers
@@ -790,6 +790,7 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None,
790790
argnames, parameters = ParameterSet._for_parametrize(
791791
argnames, argvalues, self.function, self.config)
792792
del argvalues
793+
default_arg_names = set(get_default_arg_names(self.function))
793794

794795
if scope is None:
795796
scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect)
@@ -798,13 +799,16 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None,
798799
valtypes = {}
799800
for arg in argnames:
800801
if arg not in self.fixturenames:
801-
if isinstance(indirect, (tuple, list)):
802-
name = 'fixture' if arg in indirect else 'argument'
802+
if arg in default_arg_names:
803+
raise ValueError("%r already takes an argument %r with a default value" % (self.function, arg))
803804
else:
804-
name = 'fixture' if indirect else 'argument'
805-
raise ValueError(
806-
"%r uses no %s %r" % (
807-
self.function, name, arg))
805+
if isinstance(indirect, (tuple, list)):
806+
name = 'fixture' if arg in indirect else 'argument'
807+
else:
808+
name = 'fixture' if indirect else 'argument'
809+
raise ValueError(
810+
"%r uses no %s %r" % (
811+
self.function, name, arg))
808812

809813
if indirect is True:
810814
valtypes = dict.fromkeys(argnames, "params")

changelog/3221.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added a more indicative error message when parametrizing a function whose argument takes a default value.

testing/python/metafunc.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,19 @@ def test_simple(x):
616616
"*uses no argument 'y'*",
617617
])
618618

619+
def test_parametrize_gives_indicative_error_on_function_with_default_argument(self, testdir):
620+
testdir.makepyfile("""
621+
import pytest
622+
623+
@pytest.mark.parametrize('x, y', [('a', 'b')])
624+
def test_simple(x, y=1):
625+
assert len(x) == 1
626+
""")
627+
result = testdir.runpytest("--collect-only")
628+
result.stdout.fnmatch_lines([
629+
"*already takes an argument 'y' with a default value",
630+
])
631+
619632
def test_addcalls_and_parametrize_indirect(self):
620633
def func(x, y):
621634
pass

0 commit comments

Comments
 (0)