Skip to content

Commit c378cb4

Browse files
committed
Remove support for applying marks to values in parametrize
Fix pytest-dev#3082
1 parent d888d5c commit c378cb4

File tree

7 files changed

+110
-141
lines changed

7 files changed

+110
-141
lines changed

changelog/3082.removal.rst

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

doc/en/deprecations.rst

Lines changed: 54 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ This page lists all pytest features that are currently deprecated or have been r
77
The objective is to give users a clear rationale why a certain feature has been removed, and what alternatives
88
should be used instead.
99

10+
.. contents::
11+
:depth: 3
12+
:local:
13+
14+
1015
Deprecated Features
1116
-------------------
1217

@@ -81,40 +86,6 @@ As part of a large :ref:`marker-revamp`, :meth:`_pytest.nodes.Node.get_marker` i
8186
:ref:`the documentation <update marker code>` on tips on how to update your code.
8287

8388

84-
marks in ``pytest.mark.parametrize``
85-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
86-
87-
.. deprecated:: 3.2
88-
89-
Applying marks to values of a ``pytest.mark.parametrize`` call is now deprecated. For example:
90-
91-
.. code-block:: python
92-
93-
@pytest.mark.parametrize(
94-
"a, b", [(3, 9), pytest.mark.xfail(reason="flaky")(6, 36), (10, 100)]
95-
)
96-
def test_foo(a, b):
97-
...
98-
99-
This code applies the ``pytest.mark.xfail(reason="flaky")`` mark to the ``(6, 36)`` value of the above parametrization
100-
call.
101-
102-
This was considered hard to read and understand, and also its implementation presented problems to the code preventing
103-
further internal improvements in the marks architecture.
104-
105-
To update the code, use ``pytest.param``:
106-
107-
.. code-block:: python
108-
109-
@pytest.mark.parametrize(
110-
"a, b",
111-
[(3, 9), pytest.param((6, 36), marks=pytest.mark.xfail(reason="flaky")), (10, 100)],
112-
)
113-
def test_foo(a, b):
114-
...
115-
116-
117-
11889
Result log (``--result-log``)
11990
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
12091

@@ -145,6 +116,55 @@ collection.
145116
This issue should affect only advanced plugins who create new collection types, so if you see this warning
146117
message please contact the authors so they can change the code.
147118

119+
120+
marks in ``pytest.mark.parametrize``
121+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
122+
123+
*Removed in version 4.0.*
124+
125+
Applying marks to values of a ``pytest.mark.parametrize`` call is now deprecated. For example:
126+
127+
.. code-block:: python
128+
129+
@pytest.mark.parametrize(
130+
"a, b",
131+
[
132+
(3, 9),
133+
pytest.mark.xfail(reason="flaky")(6, 36),
134+
(10, 100),
135+
(20, 200),
136+
(40, 400),
137+
(50, 500),
138+
],
139+
)
140+
def test_foo(a, b):
141+
...
142+
143+
This code applies the ``pytest.mark.xfail(reason="flaky")`` mark to the ``(6, 36)`` value of the above parametrization
144+
call.
145+
146+
This was considered hard to read and understand, and also its implementation presented problems to the code preventing
147+
further internal improvements in the marks architecture.
148+
149+
To update the code, use ``pytest.param``:
150+
151+
.. code-block:: python
152+
153+
@pytest.mark.parametrize(
154+
"a, b",
155+
[
156+
(3, 9),
157+
pytest.param(6, 36, marks=pytest.mark.xfail(reason="flaky")),
158+
(10, 100),
159+
(20, 200),
160+
(40, 400),
161+
(50, 500),
162+
],
163+
)
164+
def test_foo(a, b):
165+
...
166+
167+
148168
``pytest_funcarg__`` prefix
149169
~~~~~~~~~~~~~~~~~~~~~~~~~~~
150170

src/_pytest/deprecated.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,6 @@
5454
"Docs: https://docs.pytest.org/en/latest/mark.html#updating-code"
5555
)
5656

57-
MARK_PARAMETERSET_UNPACKING = RemovedInPytest4Warning(
58-
"Applying marks directly to parameters is deprecated,"
59-
" please use pytest.param(..., marks=...) instead.\n"
60-
"For more details, see: https://docs.pytest.org/en/latest/parametrize.html"
61-
)
62-
6357
RAISES_EXEC = PytestDeprecationWarning(
6458
"raises(..., 'code(as_a_string)') is deprecated, use the context manager form or use `exec()` directly\n\n"
6559
"See https://docs.pytest.org/en/latest/deprecations.html#raises-warns-exec"

src/_pytest/mark/structures.py

Lines changed: 21 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
from ..compat import MappingMixin
1414
from ..compat import NOTSET
1515
from ..deprecated import MARK_INFO_ATTRIBUTE
16-
from ..deprecated import MARK_PARAMETERSET_UNPACKING
1716
from _pytest.outcomes import fail
1817

1918

@@ -82,39 +81,23 @@ def param(cls, *values, **kw):
8281
return cls(values, marks, id_)
8382

8483
@classmethod
85-
def extract_from(cls, parameterset, belonging_definition, legacy_force_tuple=False):
84+
def extract_from(cls, parameterset, force_tuple=False):
8685
"""
8786
:param parameterset:
8887
a legacy style parameterset that may or may not be a tuple,
8988
and may or may not be wrapped into a mess of mark objects
9089
91-
:param legacy_force_tuple:
90+
:param force_tuple:
9291
enforce tuple wrapping so single argument tuple values
9392
don't get decomposed and break tests
94-
95-
:param belonging_definition: the item that we will be extracting the parameters from.
9693
"""
9794

9895
if isinstance(parameterset, cls):
9996
return parameterset
100-
if not isinstance(parameterset, MarkDecorator) and legacy_force_tuple:
97+
if force_tuple:
10198
return cls.param(parameterset)
102-
103-
newmarks = []
104-
argval = parameterset
105-
while isinstance(argval, MarkDecorator):
106-
newmarks.append(
107-
MarkDecorator(Mark(argval.markname, argval.args[:-1], argval.kwargs))
108-
)
109-
argval = argval.args[-1]
110-
assert not isinstance(argval, ParameterSet)
111-
if legacy_force_tuple:
112-
argval = (argval,)
113-
114-
if newmarks and belonging_definition is not None:
115-
belonging_definition.warn(MARK_PARAMETERSET_UNPACKING)
116-
117-
return cls(argval, marks=newmarks, id=None)
99+
else:
100+
return cls(parameterset, marks=[], id=None)
118101

119102
@classmethod
120103
def _for_parametrize(cls, argnames, argvalues, func, config, function_definition):
@@ -124,24 +107,29 @@ def _for_parametrize(cls, argnames, argvalues, func, config, function_definition
124107
else:
125108
force_tuple = False
126109
parameters = [
127-
ParameterSet.extract_from(
128-
x,
129-
legacy_force_tuple=force_tuple,
130-
belonging_definition=function_definition,
131-
)
132-
for x in argvalues
110+
ParameterSet.extract_from(x, force_tuple=force_tuple) for x in argvalues
133111
]
134112
del argvalues
135113

136114
if parameters:
137115
# check all parameter sets have the correct number of values
138116
for param in parameters:
139117
if len(param.values) != len(argnames):
140-
raise ValueError(
141-
'In "parametrize" the number of values ({}) must be '
142-
"equal to the number of names ({})".format(
143-
param.values, argnames
144-
)
118+
msg = (
119+
'{nodeid}: in "parametrize" the number of names ({names_len}):\n'
120+
" {names}\n"
121+
"must be equal to the number of values ({values_len}):\n"
122+
" {values}"
123+
)
124+
fail(
125+
msg.format(
126+
nodeid=function_definition.nodeid,
127+
values=param.values,
128+
names=argnames,
129+
names_len=len(argnames),
130+
values_len=len(param.values),
131+
),
132+
pytrace=False,
145133
)
146134
else:
147135
# empty parameter set (likely computed at runtime): create a single

testing/python/collect.py

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -244,13 +244,6 @@ def prop(self):
244244
"ignore:usage of Generator.Function is deprecated, please use pytest.Function instead"
245245
)
246246
class TestFunction(object):
247-
@pytest.fixture
248-
def ignore_parametrized_marks_args(self):
249-
"""Provides arguments to pytester.runpytest() to ignore the warning about marks being applied directly
250-
to parameters.
251-
"""
252-
return ("-W", "ignore:Applying marks directly to parameters")
253-
254247
def test_getmodulecollector(self, testdir):
255248
item = testdir.getitem("def test_func(): pass")
256249
modcol = item.getparent(pytest.Module)
@@ -472,15 +465,14 @@ def test_it(fix1):
472465
rec = testdir.inline_run()
473466
rec.assertoutcome(passed=1)
474467

475-
@pytest.mark.filterwarnings("ignore:Applying marks directly to parameters")
476468
def test_parametrize_with_mark(self, testdir):
477469
items = testdir.getitems(
478470
"""
479471
import pytest
480472
@pytest.mark.foo
481473
@pytest.mark.parametrize('arg', [
482474
1,
483-
pytest.mark.bar(pytest.mark.baz(2))
475+
pytest.param(2, marks=[pytest.mark.baz, pytest.mark.bar])
484476
])
485477
def test_function(arg):
486478
pass
@@ -558,37 +550,37 @@ def test2(self, x, y):
558550
assert colitems[2].name == "test2[a-c]"
559551
assert colitems[3].name == "test2[b-c]"
560552

561-
def test_parametrize_skipif(self, testdir, ignore_parametrized_marks_args):
553+
def test_parametrize_skipif(self, testdir):
562554
testdir.makepyfile(
563555
"""
564556
import pytest
565557
566558
m = pytest.mark.skipif('True')
567559
568-
@pytest.mark.parametrize('x', [0, 1, m(2)])
560+
@pytest.mark.parametrize('x', [0, 1, pytest.param(2, marks=m)])
569561
def test_skip_if(x):
570562
assert x < 2
571563
"""
572564
)
573-
result = testdir.runpytest(*ignore_parametrized_marks_args)
565+
result = testdir.runpytest()
574566
result.stdout.fnmatch_lines("* 2 passed, 1 skipped in *")
575567

576-
def test_parametrize_skip(self, testdir, ignore_parametrized_marks_args):
568+
def test_parametrize_skip(self, testdir):
577569
testdir.makepyfile(
578570
"""
579571
import pytest
580572
581573
m = pytest.mark.skip('')
582574
583-
@pytest.mark.parametrize('x', [0, 1, m(2)])
575+
@pytest.mark.parametrize('x', [0, 1, pytest.param(2, marks=m)])
584576
def test_skip(x):
585577
assert x < 2
586578
"""
587579
)
588-
result = testdir.runpytest(*ignore_parametrized_marks_args)
580+
result = testdir.runpytest()
589581
result.stdout.fnmatch_lines("* 2 passed, 1 skipped in *")
590582

591-
def test_parametrize_skipif_no_skip(self, testdir, ignore_parametrized_marks_args):
583+
def test_parametrize_skipif_no_skip(self, testdir):
592584
testdir.makepyfile(
593585
"""
594586
import pytest
@@ -600,40 +592,40 @@ def test_skipif_no_skip(x):
600592
assert x < 2
601593
"""
602594
)
603-
result = testdir.runpytest(*ignore_parametrized_marks_args)
595+
result = testdir.runpytest()
604596
result.stdout.fnmatch_lines("* 1 failed, 2 passed in *")
605597

606-
def test_parametrize_xfail(self, testdir, ignore_parametrized_marks_args):
598+
def test_parametrize_xfail(self, testdir):
607599
testdir.makepyfile(
608600
"""
609601
import pytest
610602
611603
m = pytest.mark.xfail('True')
612604
613-
@pytest.mark.parametrize('x', [0, 1, m(2)])
605+
@pytest.mark.parametrize('x', [0, 1, pytest.param(2, marks=m)])
614606
def test_xfail(x):
615607
assert x < 2
616608
"""
617609
)
618-
result = testdir.runpytest(*ignore_parametrized_marks_args)
610+
result = testdir.runpytest()
619611
result.stdout.fnmatch_lines("* 2 passed, 1 xfailed in *")
620612

621-
def test_parametrize_passed(self, testdir, ignore_parametrized_marks_args):
613+
def test_parametrize_passed(self, testdir):
622614
testdir.makepyfile(
623615
"""
624616
import pytest
625617
626618
m = pytest.mark.xfail('True')
627619
628-
@pytest.mark.parametrize('x', [0, 1, m(2)])
620+
@pytest.mark.parametrize('x', [0, 1, pytest.param(2, marks=m)])
629621
def test_xfail(x):
630622
pass
631623
"""
632624
)
633-
result = testdir.runpytest(*ignore_parametrized_marks_args)
625+
result = testdir.runpytest()
634626
result.stdout.fnmatch_lines("* 2 passed, 1 xpassed in *")
635627

636-
def test_parametrize_xfail_passed(self, testdir, ignore_parametrized_marks_args):
628+
def test_parametrize_xfail_passed(self, testdir):
637629
testdir.makepyfile(
638630
"""
639631
import pytest
@@ -645,7 +637,7 @@ def test_passed(x):
645637
pass
646638
"""
647639
)
648-
result = testdir.runpytest(*ignore_parametrized_marks_args)
640+
result = testdir.runpytest()
649641
result.stdout.fnmatch_lines("* 3 passed in *")
650642

651643
def test_function_original_name(self, testdir):

0 commit comments

Comments
 (0)