Remove support for applying marks to values in parametrize

Fix #3082
This commit is contained in:
Bruno Oliveira 2018-12-20 16:13:43 -02:00
parent d888d5c933
commit c378cb4793
7 changed files with 110 additions and 141 deletions

View File

@ -0,0 +1,3 @@
Removed support for applying marks directly to values in ``@pytest.mark.parametrize``. Use ``pytest.param`` instead.
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#marks-in-pytest-mark-parametrize>`__ on information on how to update your code.

View File

@ -7,6 +7,11 @@ This page lists all pytest features that are currently deprecated or have been r
The objective is to give users a clear rationale why a certain feature has been removed, and what alternatives
should be used instead.
.. contents::
:depth: 3
:local:
Deprecated Features
-------------------
@ -81,40 +86,6 @@ As part of a large :ref:`marker-revamp`, :meth:`_pytest.nodes.Node.get_marker` i
:ref:`the documentation <update marker code>` on tips on how to update your code.
marks in ``pytest.mark.parametrize``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.2
Applying marks to values of a ``pytest.mark.parametrize`` call is now deprecated. For example:
.. code-block:: python
@pytest.mark.parametrize(
"a, b", [(3, 9), pytest.mark.xfail(reason="flaky")(6, 36), (10, 100)]
)
def test_foo(a, b):
...
This code applies the ``pytest.mark.xfail(reason="flaky")`` mark to the ``(6, 36)`` value of the above parametrization
call.
This was considered hard to read and understand, and also its implementation presented problems to the code preventing
further internal improvements in the marks architecture.
To update the code, use ``pytest.param``:
.. code-block:: python
@pytest.mark.parametrize(
"a, b",
[(3, 9), pytest.param((6, 36), marks=pytest.mark.xfail(reason="flaky")), (10, 100)],
)
def test_foo(a, b):
...
Result log (``--result-log``)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -145,6 +116,55 @@ collection.
This issue should affect only advanced plugins who create new collection types, so if you see this warning
message please contact the authors so they can change the code.
marks in ``pytest.mark.parametrize``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*Removed in version 4.0.*
Applying marks to values of a ``pytest.mark.parametrize`` call is now deprecated. For example:
.. code-block:: python
@pytest.mark.parametrize(
"a, b",
[
(3, 9),
pytest.mark.xfail(reason="flaky")(6, 36),
(10, 100),
(20, 200),
(40, 400),
(50, 500),
],
)
def test_foo(a, b):
...
This code applies the ``pytest.mark.xfail(reason="flaky")`` mark to the ``(6, 36)`` value of the above parametrization
call.
This was considered hard to read and understand, and also its implementation presented problems to the code preventing
further internal improvements in the marks architecture.
To update the code, use ``pytest.param``:
.. code-block:: python
@pytest.mark.parametrize(
"a, b",
[
(3, 9),
pytest.param(6, 36, marks=pytest.mark.xfail(reason="flaky")),
(10, 100),
(20, 200),
(40, 400),
(50, 500),
],
)
def test_foo(a, b):
...
``pytest_funcarg__`` prefix
~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -54,12 +54,6 @@ MARK_INFO_ATTRIBUTE = RemovedInPytest4Warning(
"Docs: https://docs.pytest.org/en/latest/mark.html#updating-code"
)
MARK_PARAMETERSET_UNPACKING = RemovedInPytest4Warning(
"Applying marks directly to parameters is deprecated,"
" please use pytest.param(..., marks=...) instead.\n"
"For more details, see: https://docs.pytest.org/en/latest/parametrize.html"
)
RAISES_EXEC = PytestDeprecationWarning(
"raises(..., 'code(as_a_string)') is deprecated, use the context manager form or use `exec()` directly\n\n"
"See https://docs.pytest.org/en/latest/deprecations.html#raises-warns-exec"

View File

@ -13,7 +13,6 @@ from ..compat import getfslineno
from ..compat import MappingMixin
from ..compat import NOTSET
from ..deprecated import MARK_INFO_ATTRIBUTE
from ..deprecated import MARK_PARAMETERSET_UNPACKING
from _pytest.outcomes import fail
@ -82,39 +81,23 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
return cls(values, marks, id_)
@classmethod
def extract_from(cls, parameterset, belonging_definition, legacy_force_tuple=False):
def extract_from(cls, parameterset, force_tuple=False):
"""
:param parameterset:
a legacy style parameterset that may or may not be a tuple,
and may or may not be wrapped into a mess of mark objects
:param legacy_force_tuple:
:param force_tuple:
enforce tuple wrapping so single argument tuple values
don't get decomposed and break tests
:param belonging_definition: the item that we will be extracting the parameters from.
"""
if isinstance(parameterset, cls):
return parameterset
if not isinstance(parameterset, MarkDecorator) and legacy_force_tuple:
if force_tuple:
return cls.param(parameterset)
newmarks = []
argval = parameterset
while isinstance(argval, MarkDecorator):
newmarks.append(
MarkDecorator(Mark(argval.markname, argval.args[:-1], argval.kwargs))
)
argval = argval.args[-1]
assert not isinstance(argval, ParameterSet)
if legacy_force_tuple:
argval = (argval,)
if newmarks and belonging_definition is not None:
belonging_definition.warn(MARK_PARAMETERSET_UNPACKING)
return cls(argval, marks=newmarks, id=None)
else:
return cls(parameterset, marks=[], id=None)
@classmethod
def _for_parametrize(cls, argnames, argvalues, func, config, function_definition):
@ -124,12 +107,7 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
else:
force_tuple = False
parameters = [
ParameterSet.extract_from(
x,
legacy_force_tuple=force_tuple,
belonging_definition=function_definition,
)
for x in argvalues
ParameterSet.extract_from(x, force_tuple=force_tuple) for x in argvalues
]
del argvalues
@ -137,11 +115,21 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
# check all parameter sets have the correct number of values
for param in parameters:
if len(param.values) != len(argnames):
raise ValueError(
'In "parametrize" the number of values ({}) must be '
"equal to the number of names ({})".format(
param.values, argnames
)
msg = (
'{nodeid}: in "parametrize" the number of names ({names_len}):\n'
" {names}\n"
"must be equal to the number of values ({values_len}):\n"
" {values}"
)
fail(
msg.format(
nodeid=function_definition.nodeid,
values=param.values,
names=argnames,
names_len=len(argnames),
values_len=len(param.values),
),
pytrace=False,
)
else:
# empty parameter set (likely computed at runtime): create a single

View File

@ -244,13 +244,6 @@ class TestClass(object):
"ignore:usage of Generator.Function is deprecated, please use pytest.Function instead"
)
class TestFunction(object):
@pytest.fixture
def ignore_parametrized_marks_args(self):
"""Provides arguments to pytester.runpytest() to ignore the warning about marks being applied directly
to parameters.
"""
return ("-W", "ignore:Applying marks directly to parameters")
def test_getmodulecollector(self, testdir):
item = testdir.getitem("def test_func(): pass")
modcol = item.getparent(pytest.Module)
@ -472,7 +465,6 @@ class TestFunction(object):
rec = testdir.inline_run()
rec.assertoutcome(passed=1)
@pytest.mark.filterwarnings("ignore:Applying marks directly to parameters")
def test_parametrize_with_mark(self, testdir):
items = testdir.getitems(
"""
@ -480,7 +472,7 @@ class TestFunction(object):
@pytest.mark.foo
@pytest.mark.parametrize('arg', [
1,
pytest.mark.bar(pytest.mark.baz(2))
pytest.param(2, marks=[pytest.mark.baz, pytest.mark.bar])
])
def test_function(arg):
pass
@ -558,37 +550,37 @@ class TestFunction(object):
assert colitems[2].name == "test2[a-c]"
assert colitems[3].name == "test2[b-c]"
def test_parametrize_skipif(self, testdir, ignore_parametrized_marks_args):
def test_parametrize_skipif(self, testdir):
testdir.makepyfile(
"""
import pytest
m = pytest.mark.skipif('True')
@pytest.mark.parametrize('x', [0, 1, m(2)])
@pytest.mark.parametrize('x', [0, 1, pytest.param(2, marks=m)])
def test_skip_if(x):
assert x < 2
"""
)
result = testdir.runpytest(*ignore_parametrized_marks_args)
result = testdir.runpytest()
result.stdout.fnmatch_lines("* 2 passed, 1 skipped in *")
def test_parametrize_skip(self, testdir, ignore_parametrized_marks_args):
def test_parametrize_skip(self, testdir):
testdir.makepyfile(
"""
import pytest
m = pytest.mark.skip('')
@pytest.mark.parametrize('x', [0, 1, m(2)])
@pytest.mark.parametrize('x', [0, 1, pytest.param(2, marks=m)])
def test_skip(x):
assert x < 2
"""
)
result = testdir.runpytest(*ignore_parametrized_marks_args)
result = testdir.runpytest()
result.stdout.fnmatch_lines("* 2 passed, 1 skipped in *")
def test_parametrize_skipif_no_skip(self, testdir, ignore_parametrized_marks_args):
def test_parametrize_skipif_no_skip(self, testdir):
testdir.makepyfile(
"""
import pytest
@ -600,40 +592,40 @@ class TestFunction(object):
assert x < 2
"""
)
result = testdir.runpytest(*ignore_parametrized_marks_args)
result = testdir.runpytest()
result.stdout.fnmatch_lines("* 1 failed, 2 passed in *")
def test_parametrize_xfail(self, testdir, ignore_parametrized_marks_args):
def test_parametrize_xfail(self, testdir):
testdir.makepyfile(
"""
import pytest
m = pytest.mark.xfail('True')
@pytest.mark.parametrize('x', [0, 1, m(2)])
@pytest.mark.parametrize('x', [0, 1, pytest.param(2, marks=m)])
def test_xfail(x):
assert x < 2
"""
)
result = testdir.runpytest(*ignore_parametrized_marks_args)
result = testdir.runpytest()
result.stdout.fnmatch_lines("* 2 passed, 1 xfailed in *")
def test_parametrize_passed(self, testdir, ignore_parametrized_marks_args):
def test_parametrize_passed(self, testdir):
testdir.makepyfile(
"""
import pytest
m = pytest.mark.xfail('True')
@pytest.mark.parametrize('x', [0, 1, m(2)])
@pytest.mark.parametrize('x', [0, 1, pytest.param(2, marks=m)])
def test_xfail(x):
pass
"""
)
result = testdir.runpytest(*ignore_parametrized_marks_args)
result = testdir.runpytest()
result.stdout.fnmatch_lines("* 2 passed, 1 xpassed in *")
def test_parametrize_xfail_passed(self, testdir, ignore_parametrized_marks_args):
def test_parametrize_xfail_passed(self, testdir):
testdir.makepyfile(
"""
import pytest
@ -645,7 +637,7 @@ class TestFunction(object):
pass
"""
)
result = testdir.runpytest(*ignore_parametrized_marks_args)
result = testdir.runpytest()
result.stdout.fnmatch_lines("* 3 passed in *")
def test_function_original_name(self, testdir):

View File

@ -1372,7 +1372,6 @@ class TestMetafuncFunctionalAuto(object):
assert output.count("preparing foo-3") == 1
@pytest.mark.filterwarnings("ignore:Applying marks directly to parameters")
@pytest.mark.issue308
class TestMarkersWithParametrization(object):
def test_simple_mark(self, testdir):
@ -1382,7 +1381,7 @@ class TestMarkersWithParametrization(object):
@pytest.mark.foo
@pytest.mark.parametrize(("n", "expected"), [
(1, 2),
pytest.mark.bar((1, 3)),
pytest.param(1, 3, marks=pytest.mark.bar),
(2, 3),
])
def test_increment(n, expected):
@ -1402,7 +1401,7 @@ class TestMarkersWithParametrization(object):
@pytest.mark.parametrize(("n", "expected"), [
(1, 2),
pytest.mark.foo((2, 3)),
pytest.param(2, 3, marks=pytest.mark.foo),
(3, 4),
])
def test_increment(n, expected):
@ -1442,7 +1441,7 @@ class TestMarkersWithParametrization(object):
@pytest.mark.parametrize(("n", "expected"), [
(1, 2),
pytest.mark.xfail((1, 3)),
pytest.param(1, 3, marks=pytest.mark.xfail),
(2, 3),
])
def test_increment(n, expected):
@ -1459,7 +1458,7 @@ class TestMarkersWithParametrization(object):
@pytest.mark.parametrize("n", [
2,
pytest.mark.xfail(3),
pytest.param(3, marks=pytest.mark.xfail),
4,
])
def test_isEven(n):
@ -1475,7 +1474,7 @@ class TestMarkersWithParametrization(object):
@pytest.mark.parametrize(("n", "expected"), [
(1, 2),
pytest.mark.xfail("True")((1, 3)),
pytest.param(1, 3, marks=pytest.mark.xfail("True")),
(2, 3),
])
def test_increment(n, expected):
@ -1491,7 +1490,7 @@ class TestMarkersWithParametrization(object):
@pytest.mark.parametrize(("n", "expected"), [
(1, 2),
pytest.mark.xfail(reason="some bug")((1, 3)),
pytest.param(1, 3, marks=pytest.mark.xfail(reason="some bug")),
(2, 3),
])
def test_increment(n, expected):
@ -1507,7 +1506,7 @@ class TestMarkersWithParametrization(object):
@pytest.mark.parametrize(("n", "expected"), [
(1, 2),
pytest.mark.xfail("True", reason="some bug")((1, 3)),
pytest.param(1, 3, marks=pytest.mark.xfail("True", reason="some bug")),
(2, 3),
])
def test_increment(n, expected):
@ -1522,9 +1521,11 @@ class TestMarkersWithParametrization(object):
s = """
import pytest
m = pytest.mark.xfail("sys.version_info > (0, 0, 0)", reason="some bug", strict={strict})
@pytest.mark.parametrize(("n", "expected"), [
(1, 2),
pytest.mark.xfail("sys.version_info > (0, 0, 0)", reason="some bug", strict={strict})((2, 3)),
pytest.param(2, 3, marks=m),
(3, 4),
])
def test_increment(n, expected):
@ -1548,7 +1549,7 @@ class TestMarkersWithParametrization(object):
failingTestData = [(1, 3),
(2, 2)]
testData = passingTestData + [pytest.mark.xfail(d)
testData = passingTestData + [pytest.param(*d, marks=pytest.mark.xfail)
for d in failingTestData]
metafunc.parametrize(("n", "expected"), testData)

View File

@ -10,7 +10,6 @@ import six
import pytest
from _pytest.mark import EMPTY_PARAMETERSET_OPTION
from _pytest.mark import MarkGenerator as Mark
from _pytest.mark import ParameterSet
from _pytest.mark import transfer_markers
from _pytest.nodes import Collector
from _pytest.nodes import Node
@ -477,8 +476,10 @@ def test_parametrized_collect_with_wrong_args(testdir):
result = testdir.runpytest(py_file)
result.stdout.fnmatch_lines(
[
'E ValueError: In "parametrize" the number of values ((1, 2, 3)) '
"must be equal to the number of names (['foo', 'bar'])"
'test_parametrized_collect_with_wrong_args.py::test_func: in "parametrize" the number of names (2):',
" ['foo', 'bar']",
"must be equal to the number of values (3):",
" (1, 2, 3)",
]
)
@ -1042,36 +1043,6 @@ class TestKeywordSelection(object):
assert_test_is_not_selected("()")
@pytest.mark.parametrize(
"argval, expected",
[
(
pytest.mark.skip()((1, 2)),
ParameterSet(values=(1, 2), marks=[pytest.mark.skip], id=None),
),
(
pytest.mark.xfail(pytest.mark.skip()((1, 2))),
ParameterSet(
values=(1, 2), marks=[pytest.mark.xfail, pytest.mark.skip], id=None
),
),
],
)
@pytest.mark.filterwarnings("default")
def test_parameterset_extractfrom(argval, expected):
from _pytest.deprecated import MARK_PARAMETERSET_UNPACKING
warn_called = []
class DummyItem:
def warn(self, warning):
warn_called.append(warning)
extracted = ParameterSet.extract_from(argval, belonging_definition=DummyItem())
assert extracted == expected
assert warn_called == [MARK_PARAMETERSET_UNPACKING]
def test_legacy_transfer():
class FakeModule(object):
pytestmark = []