Skip to content

Commit 86608c3

Browse files
committed
Hard error when setting a mark on a fixture function
Deprecated feature scheduled for removal in pytest 9. Part of #13893.
1 parent a6f3ec7 commit 86608c3

File tree

6 files changed

+70
-81
lines changed

6 files changed

+70
-81
lines changed

doc/en/deprecations.rst

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -367,23 +367,6 @@ conflicts (such as :class:`pytest.File` now taking ``path`` instead of
367367
``fspath``, as :ref:`outlined above <node-ctor-fspath-deprecation>`), a
368368
deprecation warning is now raised.
369369

370-
Applying a mark to a fixture function
371-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
372-
373-
.. deprecated:: 7.4
374-
375-
Applying a mark to a fixture function never had any effect, but it is a common user error.
376-
377-
.. code-block:: python
378-
379-
@pytest.mark.usefixtures("clean_database")
380-
@pytest.fixture
381-
def user() -> User: ...
382-
383-
Users expected in this case that the ``usefixtures`` mark would have its intended effect of using the ``clean_database`` fixture when ``user`` was invoked, when in fact it has no effect at all.
384-
385-
Now pytest will issue a warning when it encounters this problem, and will raise an error in the future versions.
386-
387370

388371
The ``yield_fixture`` function/decorator
389372
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -403,6 +386,24 @@ an appropriate period of deprecation has passed.
403386

404387
Some breaking changes which could not be deprecated are also listed.
405388

389+
Applying a mark to a fixture function
390+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
391+
392+
.. deprecated:: 7.4
393+
.. versionremoved:: 9.0
394+
395+
Applying a mark to a fixture function never had any effect, but it is a common user error.
396+
397+
.. code-block:: python
398+
399+
@pytest.mark.usefixtures("clean_database")
400+
@pytest.fixture
401+
def user() -> User: ...
402+
403+
Users expected in this case that the ``usefixtures`` mark would have its intended effect of using the ``clean_database`` fixture when ``user`` was invoked, when in fact it has no effect at all.
404+
405+
Now pytest will issue a warning when it encounters this problem, and will raise an error in the future versions.
406+
406407
.. _legacy-path-hooks-deprecated:
407408

408409
``py.path.local`` arguments for hooks replaced with ``pathlib.Path``

src/_pytest/deprecated.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,6 @@
5656
"#configuring-hook-specs-impls-using-markers",
5757
)
5858

59-
MARKED_FIXTURE = PytestRemovedIn9Warning(
60-
"Marks applied to fixtures have no effect\n"
61-
"See docs: https://docs.pytest.org/en/stable/deprecations.html#applying-a-mark-to-a-fixture-function"
62-
)
63-
6459
MONKEYPATCH_LEGACY_NAMESPACE_PACKAGES = PytestRemovedIn10Warning(
6560
"monkeypatch.syspath_prepend() called with pkg_resources legacy namespace packages detected.\n"
6661
"Legacy namespace packages (using pkg_resources.declare_namespace) are deprecated.\n"

src/_pytest/fixtures.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@
5353
from _pytest.config import ExitCode
5454
from _pytest.config.argparsing import Parser
5555
from _pytest.deprecated import check_ispytest
56-
from _pytest.deprecated import MARKED_FIXTURE
5756
from _pytest.deprecated import YIELD_FIXTURE
5857
from _pytest.main import Session
5958
from _pytest.mark import Mark
@@ -1236,7 +1235,10 @@ def __call__(self, function: FixtureFunction) -> FixtureFunctionDefinition:
12361235
)
12371236

12381237
if hasattr(function, "pytestmark"):
1239-
warnings.warn(MARKED_FIXTURE, stacklevel=2)
1238+
fail(
1239+
"Marks cannot be applied to fixtures.\n"
1240+
"See docs: https://docs.pytest.org/en/stable/deprecations.html#applying-a-mark-to-a-fixture-function"
1241+
)
12401242

12411243
fixture_definition = FixtureFunctionDefinition(
12421244
function=function, fixture_function_marker=self, _ispytest=True

src/_pytest/mark/structures.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
from ..compat import NotSetType
2727
from _pytest.config import Config
2828
from _pytest.deprecated import check_ispytest
29-
from _pytest.deprecated import MARKED_FIXTURE
3029
from _pytest.deprecated import PARAMETRIZE_NON_COLLECTION_ITERABLE
3130
from _pytest.outcomes import fail
3231
from _pytest.raises import AbstractRaises
@@ -409,7 +408,7 @@ def __call__(self, *args: object, **kwargs: object):
409408
if isinstance(func, staticmethod | classmethod):
410409
unwrapped_func = func.__func__
411410
if len(args) == 1 and (istestfunc(unwrapped_func) or is_class):
412-
store_mark(unwrapped_func, self.mark, stacklevel=3)
411+
store_mark(unwrapped_func, self.mark)
413412
return func
414413
return self.with_args(*args, **kwargs)
415414

@@ -464,7 +463,7 @@ def normalize_mark_list(
464463
yield mark_obj
465464

466465

467-
def store_mark(obj, mark: Mark, *, stacklevel: int = 2) -> None:
466+
def store_mark(obj, mark: Mark) -> None:
468467
"""Store a Mark on an object.
469468
470469
This is used to implement the Mark declarations/decorators correctly.
@@ -474,7 +473,10 @@ def store_mark(obj, mark: Mark, *, stacklevel: int = 2) -> None:
474473
from ..fixtures import getfixturemarker
475474

476475
if getfixturemarker(obj) is not None:
477-
warnings.warn(MARKED_FIXTURE, stacklevel=stacklevel)
476+
fail(
477+
"Marks cannot be applied to fixtures.\n"
478+
"See docs: https://docs.pytest.org/en/stable/deprecations.html#applying-a-mark-to-a-fixture-function"
479+
)
478480

479481
# Always reassign name to avoid updating pytestmark in a reference that
480482
# was only borrowed.

testing/deprecated_test.py

Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -107,56 +107,3 @@ def collect(self):
107107
parent=mod.parent,
108108
fspath=legacy_path("bla"),
109109
)
110-
111-
112-
def test_fixture_disallow_on_marked_functions():
113-
"""Test that applying @pytest.fixture to a marked function warns (#3364)."""
114-
with pytest.warns(
115-
pytest.PytestRemovedIn9Warning,
116-
match=r"Marks applied to fixtures have no effect",
117-
) as record:
118-
119-
@pytest.fixture
120-
@pytest.mark.parametrize("example", ["hello"])
121-
@pytest.mark.usefixtures("tmp_path")
122-
def foo():
123-
raise NotImplementedError()
124-
125-
# it's only possible to get one warning here because you're already prevented
126-
# from applying @fixture twice
127-
# ValueError("fixture is being applied more than once to the same function")
128-
assert len(record) == 1
129-
130-
131-
def test_fixture_disallow_marks_on_fixtures():
132-
"""Test that applying a mark to a fixture warns (#3364)."""
133-
with pytest.warns(
134-
pytest.PytestRemovedIn9Warning,
135-
match=r"Marks applied to fixtures have no effect",
136-
) as record:
137-
138-
@pytest.mark.parametrize("example", ["hello"])
139-
@pytest.mark.usefixtures("tmp_path")
140-
@pytest.fixture
141-
def foo():
142-
raise NotImplementedError()
143-
144-
assert len(record) == 2 # one for each mark decorator
145-
# should point to this file
146-
assert all(rec.filename == __file__ for rec in record)
147-
148-
149-
def test_fixture_disallowed_between_marks():
150-
"""Test that applying a mark to a fixture warns (#3364)."""
151-
with pytest.warns(
152-
pytest.PytestRemovedIn9Warning,
153-
match=r"Marks applied to fixtures have no effect",
154-
) as record:
155-
156-
@pytest.mark.parametrize("example", ["hello"])
157-
@pytest.fixture
158-
@pytest.mark.usefixtures("tmp_path")
159-
def foo():
160-
raise NotImplementedError()
161-
162-
assert len(record) == 2 # one for each mark decorator

testing/test_mark.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,3 +1302,45 @@ def test_staticmethod_wrapper_on_top(value: int):
13021302
)
13031303
result = pytester.runpytest()
13041304
result.assert_outcomes(passed=8)
1305+
1306+
1307+
def test_fixture_disallow_on_marked_functions() -> None:
1308+
"""Test that applying @pytest.fixture to a marked function errors (#3364)."""
1309+
with pytest.raises(
1310+
pytest.fail.Exception,
1311+
match=r"Marks cannot be applied to fixtures",
1312+
):
1313+
1314+
@pytest.fixture
1315+
@pytest.mark.parametrize("example", ["hello"])
1316+
@pytest.mark.usefixtures("tmp_path")
1317+
def foo():
1318+
raise NotImplementedError()
1319+
1320+
1321+
def test_fixture_disallow_marks_on_fixtures() -> None:
1322+
"""Test that applying a mark to a fixture errors (#3364)."""
1323+
with pytest.raises(
1324+
pytest.fail.Exception,
1325+
match=r"Marks cannot be applied to fixtures",
1326+
):
1327+
1328+
@pytest.mark.parametrize("example", ["hello"])
1329+
@pytest.mark.usefixtures("tmp_path")
1330+
@pytest.fixture
1331+
def foo():
1332+
raise NotImplementedError()
1333+
1334+
1335+
def test_fixture_disallowed_between_marks() -> None:
1336+
"""Test that applying a mark to a fixture errors (#3364)."""
1337+
with pytest.raises(
1338+
pytest.fail.Exception,
1339+
match=r"Marks cannot be applied to fixtures",
1340+
):
1341+
1342+
@pytest.mark.parametrize("example", ["hello"])
1343+
@pytest.fixture
1344+
@pytest.mark.usefixtures("tmp_path")
1345+
def foo():
1346+
raise NotImplementedError()

0 commit comments

Comments
 (0)