Repository navigation
@pytest.mark.* priority seems wrong #10406
Copy link
Copy link
Open
Labels
topic: marksrelated to marks, either the general marks or builtinrelated to marks, either the general marks or builtintype: bugproblem that needs to be addressedproblem that needs to be addressed
Description
Originally asked about in #10391 and deemed to be a bug by @RonnyPfannschmidt
Suppose I have a test class for which I want to turn warnings into errors. However, some of the parametrizations I'm going to test will emit warnings and I just want to ignore them. I thought, I can write something like
import pytest
import warnings
@pytest.mark.filterwarnings("error")
class TestMarkClass:
@pytest.mark.parametrize("_", [pytest.param(None, marks=pytest.mark.filterwarnings("ignore"))])
def test_foo(self, _):
warnings.warn("foo")However, it seems that the class wide mark takes priority over the one in the parametrization. On the flip side, if I mark the individual tests like
class TestMarkMethods:
@pytest.mark.filterwarnings("error")
@pytest.mark.parametrize("_", [pytest.param(None, marks=pytest.mark.filterwarnings("ignore"))])
def test_bar(self, _):
warnings.warn("bar")
@pytest.mark.parametrize("_", [pytest.param(None, marks=pytest.mark.filterwarnings("ignore"))])
@pytest.mark.filterwarnings("error")
def test_baz(self, _):
warnings.warn("baz")the mark in the parametrization takes priority. Running everything together with -rA set gives
======================================= FAILURES =======================================
_____________________________ TestMarkClass.test_foo[None] _____________________________
Traceback (most recent call last):
File "/home/user/main.py", line 9, in test_foo
warnings.warn("foo")
UserWarning: foo
======================================== PASSES ========================================
=============================== short test summary info ================================
PASSED main.py::TestMarkMethods::test_bar[None]
PASSED main.py::TestMarkMethods::test_baz[None]
FAILED main.py::TestMarkClass::test_foo[None] - UserWarning: foo
============================= 1 failed, 2 passed in 0.68s ==============================
This order seems wrong to me. I would expect the marker in the parametrization takes the highest priority, followed by the marker on the test and lastly the marker on the class.
$ pytest --version
pytest 7.1.3Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
topic: marksrelated to marks, either the general marks or builtinrelated to marks, either the general marks or builtintype: bugproblem that needs to be addressedproblem that needs to be addressed
Activity
david1542 commentedon Oct 21, 2022
I can start working on it, if it's okay. Also, any additional information from the core maintainers would be great :)
RonnyPfannschmidt commentedon Oct 21, 2022
this one relates to the order in which collection parameterize drop the parameterize obtained markers into the marker list
its a little bit tricky, it would resolve automatically if the FunctionDefinition node was made "real"
david1542 commentedon Oct 21, 2022
@RonnyPfannschmidt Thanks :) Can you shed some more light on this maybe? Specifically, where the markers are merged. I found the
get_unpacked_marksandstore_mark, but it looks like they operate on the object level 🧐Sorry if my questions are too basic, I'm new to this project :)
RonnyPfannschmidt commentedon Oct 21, 2022
the markers are merged in the python function nodes
ojii commentedon Aug 13, 2024
I've run into the same/a similar issue with unexpected marker order. I would expect the order to either be "most test case specific -> least test case specific" or the reverse.
I've done some experimenting and I found that for function tests, the order is "parametrize level marks" -> "function level marks" -> "pytest_collection_modifyitems in conftest" -> "module level marks". For class tests, the order appears to be "method level marks" -> "parametrize level marks" -> "pytest_collection_modifyitems in conftest" -> "class level marks" -> "module level marks". I'd argue that is confusing.
Here's the code I used to test this:
conftest.py
test_markers.py
Colin-b commentedon Sep 27, 2024
Coming to this topic after encountering the same issue. Markers defined at test suite level (via
modifyitems) should be considered as the first declaration.I assume that you most likely import the test file (meaning you register module and class markers) and then execute modifyitems (and that would make sense in order to know the actual items upfront).
Without changing this behavior that some people might rely upon (that would be weird but you never know), what would you think about introducing a
pytestmarkatconftestlevel allowing to actually have marker(s) that would be the actual default for the test suite without the need to callmodifyitems, thus resolving the order of markers (test suite -> module -> class -> function).Thanks again
RonnyPfannschmidt commentedon Jul 24, 2026
Note
AI-assisted analysis (driven and reviewed by @RonnyPfannschmidt). Reproduced against current
main.Still reproduces
The original example still fails on
main:TestMarkClass::test_foofails (class-levelfilterwarnings("error")wins over the parametrizefilterwarnings("ignore")), while bothTestMarkMethodsvariants pass. I also checked it against #14630 (the MRO marker-ordering branch) — identical result, that PR does not address this issue (it fixes a different axis, see below).Root cause: two ordering inversions that cancel in one case but not the other
_pytest/warnings.pyapplies the marks like this:warnings.filterwarnings()inserts at the front of the filter list, so the last-applied filter wins. That means to make the closest mark win, this loop would need to iterate farthest-first. It iterates closest-first, so the farthest mark wins.Printing the actual
iter_markers("filterwarnings")order (closest → farthest):test_bar)error(decorator),ignore(param)ignoretest_foo)ignore(param),error(class)errorSo there are two separate bugs:
warnings.pyconsumes markers in the wrong direction — with front-insertion it should apply farthest-first so the closest mark ends up winning.iter_markersyields the parametrize/callspec mark after the function-decorator mark for aFunctionnode (python.pyextendsown_markerswith the decorator marks first, thencallspec.marks), i.e. the param mark comes out as farthest rather than closest.In the function case these two inversions cancel out, so it happens to pass. In the class case
iter_markersis already correct (param first), nothing cancels, and the bug surfaces.Implication for a fix
Fixing only
warnings.py(reverse the consumption order) repairs the class case but breaks the function case, becauseiter_markershas that pair backwards. A correct fix needs both:warnings.py: apply filters farthest-first (e.g.reversed(list(item.iter_markers(...)))).Function.iter_markers: yield the parametrize/callspec mark before the function-decorator mark.Step 2 is the sensitive one —
own_markersorder also influences parametrize id/naming, so the reordering likely belongs in the consumer (or in a closest-first iteration helper) rather than inown_markersitself. #14630 recently introduced a_iter_own_markers_closest_first()hook for exactly this kind of closest-first correction onClassnodes; a full fix here would extend that idea to theFunction/callspec axis and pair it with thewarnings.pychange.