diff --git a/changelog/6909.bugfix.rst b/changelog/6909.bugfix.rst new file mode 100644 index 000000000..32edc4974 --- /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 df558d1ba..14e6537ad 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 081e95a6d..22964770d 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 @@ -1277,8 +1278,10 @@ class FixtureManager: 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) @@ -1475,12 +1478,3 @@ class FixtureManager: 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 e26076179..6805a72fb 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -936,8 +936,6 @@ class Metafunc: 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 @@ class Metafunc: 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 047d5f18e..496a22b05 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -463,7 +463,7 @@ class TestFunction: 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 4e657727c..4d4191098 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -36,9 +36,6 @@ class TestMetafunc: 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 @@ class TestMarkersWithParametrization: "*= 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*', - ] - )