[parametrize] enforce explicit argnames declaration (#6330)
Every argname used in `parametrize` either must be declared explicitly in the python test function, or via `indirect` list Fix #5712
This commit is contained in:
parent
39d9f7cff5
commit
9e262038c8
1
AUTHORS
1
AUTHORS
|
@ -274,6 +274,7 @@ Vidar T. Fauske
|
||||||
Virgil Dupras
|
Virgil Dupras
|
||||||
Vitaly Lashmanov
|
Vitaly Lashmanov
|
||||||
Vlad Dragos
|
Vlad Dragos
|
||||||
|
Vladyslav Rachek
|
||||||
Volodymyr Piskun
|
Volodymyr Piskun
|
||||||
Wei Lin
|
Wei Lin
|
||||||
Wil Cooley
|
Wil Cooley
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Now all arguments to ``@pytest.mark.parametrize`` need to be explicitly declared in the function signature or via ``indirect``.
|
||||||
|
Previously it was possible to omit an argument if a fixture with the same name existed, which was just an accident of implementation and was not meant to be a part of the API.
|
|
@ -398,6 +398,9 @@ The result of this test will be successful:
|
||||||
|
|
||||||
.. regendoc:wipe
|
.. 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
|
Parametrizing test methods through per-class configuration
|
||||||
--------------------------------------------------------------
|
--------------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import functools
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
import itertools
|
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
@ -1279,10 +1278,8 @@ class FixtureManager:
|
||||||
else:
|
else:
|
||||||
argnames = ()
|
argnames = ()
|
||||||
|
|
||||||
usefixtures = itertools.chain.from_iterable(
|
usefixtures = get_use_fixtures_for_node(node)
|
||||||
mark.args for mark in node.iter_markers(name="usefixtures")
|
initialnames = usefixtures + argnames
|
||||||
)
|
|
||||||
initialnames = tuple(usefixtures) + argnames
|
|
||||||
fm = node.session._fixturemanager
|
fm = node.session._fixturemanager
|
||||||
initialnames, names_closure, arg2fixturedefs = fm.getfixtureclosure(
|
initialnames, names_closure, arg2fixturedefs = fm.getfixtureclosure(
|
||||||
initialnames, node, ignore_args=self._get_direct_parametrize_args(node)
|
initialnames, node, ignore_args=self._get_direct_parametrize_args(node)
|
||||||
|
@ -1479,3 +1476,12 @@ class FixtureManager:
|
||||||
for fixturedef in fixturedefs:
|
for fixturedef in fixturedefs:
|
||||||
if nodes.ischildnode(fixturedef.baseid, nodeid):
|
if nodes.ischildnode(fixturedef.baseid, nodeid):
|
||||||
yield fixturedef
|
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
|
||||||
|
)
|
||||||
|
|
|
@ -955,6 +955,8 @@ class Metafunc:
|
||||||
|
|
||||||
arg_values_types = self._resolve_arg_value_types(argnames, indirect)
|
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.
|
# Use any already (possibly) generated ids with parametrize Marks.
|
||||||
if _param_mark and _param_mark._param_ids_from:
|
if _param_mark and _param_mark._param_ids_from:
|
||||||
generated_ids = _param_mark._param_ids_from._param_ids_generated
|
generated_ids = _param_mark._param_ids_from._param_ids_generated
|
||||||
|
@ -1105,6 +1107,37 @@ class Metafunc:
|
||||||
pytrace=False,
|
pytrace=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _validate_explicit_parameters(self, argnames, indirect):
|
||||||
|
"""
|
||||||
|
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) and indirect is True:
|
||||||
|
return
|
||||||
|
parametrized_argnames = list()
|
||||||
|
funcargnames = _pytest.compat.getfuncargnames(self.function)
|
||||||
|
if isinstance(indirect, Sequence):
|
||||||
|
for arg in argnames:
|
||||||
|
if arg not in indirect:
|
||||||
|
parametrized_argnames.append(arg)
|
||||||
|
elif indirect is False:
|
||||||
|
parametrized_argnames = argnames
|
||||||
|
|
||||||
|
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):
|
def _find_parametrized_scope(argnames, arg2fixturedefs, indirect):
|
||||||
"""Find the most appropriate scope for a parametrized call based on its arguments.
|
"""Find the most appropriate scope for a parametrized call based on its arguments.
|
||||||
|
|
|
@ -463,7 +463,7 @@ class TestFunction:
|
||||||
return '3'
|
return '3'
|
||||||
|
|
||||||
@pytest.mark.parametrize('fix2', ['2'])
|
@pytest.mark.parametrize('fix2', ['2'])
|
||||||
def test_it(fix1):
|
def test_it(fix1, fix2):
|
||||||
assert fix1 == '21'
|
assert fix1 == '21'
|
||||||
assert not fix3_instantiated
|
assert not fix3_instantiated
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -28,6 +28,9 @@ class TestMetafunc:
|
||||||
class DefinitionMock(python.FunctionDefinition):
|
class DefinitionMock(python.FunctionDefinition):
|
||||||
obj = attr.ib()
|
obj = attr.ib()
|
||||||
|
|
||||||
|
def listchain(self):
|
||||||
|
return []
|
||||||
|
|
||||||
names = fixtures.getfuncargnames(func)
|
names = fixtures.getfuncargnames(func)
|
||||||
fixtureinfo = FixtureInfo(names)
|
fixtureinfo = FixtureInfo(names)
|
||||||
definition = DefinitionMock._create(func)
|
definition = DefinitionMock._create(func)
|
||||||
|
@ -1877,3 +1880,51 @@ class TestMarkersWithParametrization:
|
||||||
"*= 6 passed in *",
|
"*= 6 passed in *",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
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.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*',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue