From 6317aa0343629676ea71bdab7ccf0d1a3251e6c1 Mon Sep 17 00:00:00 2001 From: Brian Maissy Date: Wed, 4 Apr 2018 19:44:01 +0300 Subject: [PATCH] added indicative error when parametrizing an argument with a default value --- _pytest/compat.py | 9 +++++++++ _pytest/python.py | 17 ++++++++++------- changelog/3221.feature | 1 + testing/python/metafunc.py | 13 +++++++++++++ 4 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 changelog/3221.feature diff --git a/_pytest/compat.py b/_pytest/compat.py index 92df656564a..acae9f27527 100644 --- a/_pytest/compat.py +++ b/_pytest/compat.py @@ -127,6 +127,15 @@ def getfuncargnames(function, is_method=False, cls=None): return arg_names +def getdefaultargnames(function): + # Note: this code intentionally mirrors the code at the beginning of getfuncargnames, + # to get the arguments which were excluded from its result because they had default values + return tuple(p.name for p in signature(function).parameters.values() + if (p.kind is Parameter.POSITIONAL_OR_KEYWORD or + p.kind is Parameter.KEYWORD_ONLY) and + p.default is not Parameter.empty) + + if _PY3: STRING_TYPES = bytes, str UNICODE_TYPES = str, diff --git a/_pytest/python.py b/_pytest/python.py index f9f17afd794..ea4413e0c7a 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -25,7 +25,7 @@ isclass, isfunction, is_generator, ascii_escaped, REGEX_TYPE, STRING_TYPES, NoneType, NOTSET, get_real_func, getfslineno, safe_getattr, - safe_str, getlocation, enum, + safe_str, getlocation, enum, getdefaultargnames ) from _pytest.outcomes import fail from _pytest.mark.structures import transfer_markers @@ -797,13 +797,16 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None, valtypes = {} for arg in argnames: if arg not in self.fixturenames: - if isinstance(indirect, (tuple, list)): - name = 'fixture' if arg in indirect else 'argument' + if arg in getdefaultargnames(self.function): + raise ValueError("%r already takes an argument %r with a default value" % (self.function, arg)) else: - name = 'fixture' if indirect else 'argument' - raise ValueError( - "%r uses no %s %r" % ( - self.function, name, arg)) + if isinstance(indirect, (tuple, list)): + name = 'fixture' if arg in indirect else 'argument' + else: + name = 'fixture' if indirect else 'argument' + raise ValueError( + "%r uses no %s %r" % ( + self.function, name, arg)) if indirect is True: valtypes = dict.fromkeys(argnames, "params") diff --git a/changelog/3221.feature b/changelog/3221.feature new file mode 100644 index 00000000000..0050e2af150 --- /dev/null +++ b/changelog/3221.feature @@ -0,0 +1 @@ +Added a more indicative error message when parametrizing a function whose argument takes a default value. \ No newline at end of file diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index f2732ef3b9c..1de38e3fb17 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -616,6 +616,19 @@ def test_simple(x): "*uses no argument 'y'*", ]) + def test_parametrize_gives_indicative_error_on_function_with_default_argument(self, testdir): + testdir.makepyfile(""" + import pytest + + @pytest.mark.parametrize('x, y', [('a', 'b')]) + def test_simple(x, y=1): + assert len(x) == 1 + """) + result = testdir.runpytest("--collect-only") + result.stdout.fnmatch_lines([ + "*already takes an argument 'y' with a default value", + ]) + def test_addcalls_and_parametrize_indirect(self): def func(x, y): pass