From bb878f9ae6fc350ce492f5349b54defd503ad0e8 Mon Sep 17 00:00:00 2001 From: Vladyslav Rachek Date: Sun, 8 Dec 2019 11:17:04 +0100 Subject: [PATCH] Force explicit declaration of args in parametrize Every argname used in `parametrize` either must be declared explicitly in the python test function, or via `indirect` list References https://github.com/pytest-dev/pytest/issues/5712 TODO: as of now, ValueError occurs during collection phase. Maybe we want it to appear during other phase? --- AUTHORS | 1 + doc/en/example/parametrize.rst | 3 ++- src/_pytest/python.py | 31 ++++++++++++++++++++++++++++ testing/python/metafunc.py | 37 ++++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 6e2f472fe4a..6252be8ddae 100644 --- a/AUTHORS +++ b/AUTHORS @@ -265,6 +265,7 @@ Vidar T. Fauske Virgil Dupras Vitaly Lashmanov Vlad Dragos +Vladyslav Rachek Volodymyr Piskun Wei Lin Wil Cooley diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index 7230f2b00db..5c765e83b02 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -396,7 +396,8 @@ The result of this test will be successful: ========================== no tests ran in 0.12s =========================== -.. regendoc:wipe +Note, that each argument in `parametrize` list should be explicitly declared in corresponding +python test function or via `indirect`. Parametrizing test methods through per-class configuration -------------------------------------------------------------- diff --git a/src/_pytest/python.py b/src/_pytest/python.py index d787638c9f1..c69653fc9fc 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -974,6 +974,7 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None, scope=None) scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect) self._validate_if_using_arg_names(argnames, indirect) + self._validate_explicit_parameters(argnames, indirect) arg_values_types = self._resolve_arg_value_types(argnames, indirect) @@ -1096,6 +1097,36 @@ def _validate_if_using_arg_names(self, argnames, indirect): pytrace=False, ) + def _validate_explicit_parameters(self, argnames, indirect): + """ + The argnames in *parametrize* should either be declared explicitly via + indirect list or explicitly in the function + + :param List[str] argnames: list of argument names passed to ``parametrize()``. + :param indirect: same ``indirect`` parameter of ``parametrize()``. + :raise ValueError: if validation fails + """ + func_name = self.function.__name__ + if type(indirect) is bool and indirect is True: + return + parametrized_argnames = list() + funcargnames = _pytest.compat.getfuncargnames(self.function) + if type(indirect) is list: + for arg in argnames: + if arg not in indirect: + parametrized_argnames.append(arg) + elif indirect is False: + parametrized_argnames = argnames + for arg in parametrized_argnames: + # reference: https://docs.python.org/3/library/inspect.html#inspect.getfullargspec + # this will contain args, vargargs, and everything python function can have + if arg not in funcargnames: + raise ValueError( + f'In function "{func_name}":\n' + f'Parameter "{arg}" should be declared explicitly via indirect\n' + f"or in function itself" + ) + def _find_parametrized_scope(argnames, arg2fixturedefs, indirect): """Find the most appropriate scope for a parametrized call based on its arguments. diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 65855f724ac..0fb1452adf3 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -1780,3 +1780,40 @@ def test_foo(a): ) result = testdir.runpytest() result.assert_outcomes(passed=1) + + def test_parametrize_explicit_parameters_func(self, testdir): + testdir.makepyfile( + """ + import pytest + + + @pytest.fixture + def fixture(arg): + return arg + + @pytest.mark.parametrize("arg", ["baz"]) + def test_without_arg(fixture): + assert "baz" == fixture + """ + ) + result = testdir.runpytest() + result.assert_outcomes(error=1) + + def test_parametrize_explicit_parameters_method(self, testdir): + testdir.makepyfile( + """ + import pytest + + class Test: + @pytest.fixture + def test_fixture(self, argument): + return argument + + @pytest.mark.parametrize("argument", ["foobar"]) + def test_without_argument(self, test_fixture): + assert "foobar" == test_fixture + """ + ) + result = testdir.runpytest() + result.assert_outcomes(error=1) + # assert result.ret == ExitCode.INTERRUPTED # ???