From a482ffee4296c01975bd004184371581eeed086d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 13 Mar 2020 09:44:59 -0300 Subject: [PATCH] Revert "[parametrize] enforce explicit argnames declaration (#6330)" This reverts commit 9e262038c84a99d1353551e8cbb32f46362b58b4. Fix #6909 --- changelog/6909.bugfix.rst | 3 ++ doc/en/example/parametrize.rst | 3 -- src/_pytest/fixtures.py | 16 ++++------- src/_pytest/python.py | 35 ----------------------- testing/python/collect.py | 2 +- testing/python/metafunc.py | 51 ---------------------------------- 6 files changed, 9 insertions(+), 101 deletions(-) create mode 100644 changelog/6909.bugfix.rst diff --git a/changelog/6909.bugfix.rst b/changelog/6909.bugfix.rst new file mode 100644 index 00000000000..32edc4974c2 --- /dev/null +++ b/changelog/6909.bugfix.rst @@ -0,0 +1,3 @@ +Revert the change introduced by `#6330 `_, which required all arguments to ``@pytest.mark.parametrize`` to be explicitly defined in the function signature. + +The intention of the original change was to remove what was expected to be an unintended/surprising behavior, but it turns out many people relied on it, so the restriction has been reverted. diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index df558d1bae6..14e6537adaa 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -402,9 +402,6 @@ The result of this test will be successful: .. 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/fixtures.py b/src/_pytest/fixtures.py index 638ed89b183..82a148127d3 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1,5 +1,6 @@ import functools import inspect +import itertools import sys import warnings from collections import defaultdict @@ -1276,8 +1277,10 @@ def getfixtureinfo(self, node, func, cls, funcargs=True): else: argnames = () - usefixtures = get_use_fixtures_for_node(node) - initialnames = usefixtures + argnames + usefixtures = itertools.chain.from_iterable( + mark.args for mark in node.iter_markers(name="usefixtures") + ) + initialnames = tuple(usefixtures) + argnames fm = node.session._fixturemanager initialnames, names_closure, arg2fixturedefs = fm.getfixtureclosure( initialnames, node, ignore_args=self._get_direct_parametrize_args(node) @@ -1474,12 +1477,3 @@ def _matchfactories(self, fixturedefs, nodeid): for fixturedef in fixturedefs: if nodes.ischildnode(fixturedef.baseid, nodeid): yield fixturedef - - -def get_use_fixtures_for_node(node) -> Tuple[str, ...]: - """Returns the names of all the usefixtures() marks on the given node""" - return tuple( - str(name) - for mark in node.iter_markers(name="usefixtures") - for name in mark.args - ) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index ef3ebf79133..b03d89a1f6e 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -936,8 +936,6 @@ def parametrize( arg_values_types = self._resolve_arg_value_types(argnames, indirect) - self._validate_explicit_parameters(argnames, indirect) - # Use any already (possibly) generated ids with parametrize Marks. if _param_mark and _param_mark._param_ids_from: generated_ids = _param_mark._param_ids_from._param_ids_generated @@ -1110,39 +1108,6 @@ def _validate_if_using_arg_names( pytrace=False, ) - def _validate_explicit_parameters( - self, - argnames: typing.Sequence[str], - indirect: Union[bool, typing.Sequence[str]], - ) -> None: - """ - The argnames in *parametrize* should either be declared explicitly via - indirect list or in the function signature - - :param List[str] argnames: list of argument names passed to ``parametrize()``. - :param indirect: same ``indirect`` parameter of ``parametrize()``. - :raise ValueError: if validation fails - """ - if isinstance(indirect, bool): - parametrized_argnames = [] if indirect else argnames - else: - parametrized_argnames = [arg for arg in argnames if arg not in indirect] - - if not parametrized_argnames: - return - - funcargnames = _pytest.compat.getfuncargnames(self.function) - usefixtures = fixtures.get_use_fixtures_for_node(self.definition) - - for arg in parametrized_argnames: - if arg not in funcargnames and arg not in usefixtures: - func_name = self.function.__name__ - msg = ( - 'In function "{func_name}":\n' - 'Parameter "{arg}" should be declared explicitly via indirect or in function itself' - ).format(func_name=func_name, arg=arg) - fail(msg, pytrace=False) - 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/collect.py b/testing/python/collect.py index 047d5f18e15..496a22b0504 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -463,7 +463,7 @@ def fix3(): return '3' @pytest.mark.parametrize('fix2', ['2']) - def test_it(fix1, fix2): + def test_it(fix1): assert fix1 == '21' assert not fix3_instantiated """ diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 4e657727c32..4d41910982b 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -36,9 +36,6 @@ def __init__(self, names): class DefinitionMock(python.FunctionDefinition): obj = attr.ib() - def listchain(self): - return [] - names = fixtures.getfuncargnames(func) fixtureinfo = FuncFixtureInfoMock(names) # type: Any definition = DefinitionMock._create(func) # type: Any @@ -1902,51 +1899,3 @@ def test_converted_to_str(a, b): "*= 6 passed in *", ] ) - - def test_parametrize_explicit_parameters_func(self, testdir: Testdir) -> None: - 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) - result.stdout.fnmatch_lines( - [ - '*In function "test_without_arg"*', - '*Parameter "arg" should be declared explicitly via indirect or in function itself*', - ] - ) - - def test_parametrize_explicit_parameters_method(self, testdir: Testdir) -> None: - 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) - result.stdout.fnmatch_lines( - [ - '*In function "test_without_argument"*', - '*Parameter "argument" should be declared explicitly via indirect or in function itself*', - ] - )