From 857098fe0f3558f9640276e6a9990888423fe55c 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 | 8 ++++++++ _pytest/python.py | 18 +++++++++++------- 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 bcb31cb88..a5fa03719 100644 --- a/_pytest/compat.py +++ b/_pytest/compat.py @@ -135,6 +135,14 @@ def getfuncargnames(function, is_method=False, cls=None): return arg_names +def get_default_arg_names(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 in (Parameter.POSITIONAL_OR_KEYWORD, 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 f9f17afd7..e06de1fc3 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -25,7 +25,7 @@ from _pytest.compat import ( 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, get_default_arg_names ) from _pytest.outcomes import fail from _pytest.mark.structures import transfer_markers @@ -789,6 +789,7 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): argnames, parameters = ParameterSet._for_parametrize( argnames, argvalues, self.function, self.config) del argvalues + default_arg_names = set(get_default_arg_names(self.function)) if scope is None: scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect) @@ -797,13 +798,16 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): 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 default_arg_names: + 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 000000000..0050e2af1 --- /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 f2732ef3b..1de38e3fb 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -616,6 +616,19 @@ class TestMetafunc(object): "*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