Skip to content

Commit 5d467d3

Browse files
committed
Tweaks
1 parent 450c9ed commit 5d467d3

4 files changed

Lines changed: 31 additions & 26 deletions

File tree

changelog/14329.bugfix.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
Now the markers are considered in the order from near to far, through the mro. `get_closes_marker("usefixtures")` will also return the nearest mark, but the processing of the `usefixtures` goes from the far mark to the near one.
1+
Fixed :func:`request.node.get_closest_marker() <_pytest.nodes.Node.get_closest_marker>` (and :func:`iter_markers() <_pytest.nodes.Node.iter_markers>`) traversing MRO in the wrong order (farthest to closest).
2+
This could also affect :func:`pytest.mark.usefixtures` usage.

src/_pytest/fixtures.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1682,6 +1682,9 @@ def _getautousenames(self, node: nodes.Node) -> Iterator[str]:
16821682

16831683
def _getusefixturesnames(self, node: nodes.Item) -> Iterator[str]:
16841684
"""Return the names of usefixtures fixtures applicable to node."""
1685+
# Reverse order (fartest to closest) is more natural for usefixtures,
1686+
# e.g. want a module-level usefixture to be requested before a class one,
1687+
# a parent class' before a child's, etc.
16851688
for marker_node, mark in reversed(
16861689
list(node.iter_markers_with_node(name="usefixtures"))
16871690
):

src/_pytest/nodes.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,8 @@ def add_marker(self, marker: str | MarkDecorator, append: bool = True) -> None:
330330
def iter_markers(self, name: str | None = None) -> Iterator[Mark]:
331331
"""Iterate over all markers of the node.
332332
333+
The markers are returned from closest to farthest.
334+
333335
:param name: If given, filter the results by the name attribute.
334336
:returns: An iterator of the markers of the node.
335337
"""
@@ -340,6 +342,8 @@ def iter_markers_with_node(
340342
) -> Iterator[tuple[Node, Mark]]:
341343
"""Iterate over all markers of the node.
342344
345+
The markers are returned from closest to farthest.
346+
343347
:param name: If given, filter the results by the name attribute.
344348
:returns: An iterator of (node, mark) tuples.
345349
"""

testing/test_mark.py

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -651,41 +651,38 @@ def test_has_inherited(self):
651651
assert has_inherited_marker.kwargs == {"location": "class"}
652652
assert has_own.get_closest_marker("missing") is None
653653

654-
def test_mark_closest_with_parent(self, pytester: Pytester):
655-
test_path = pytester.makepyfile(
654+
def test_mark_closest_mro(self, pytester: Pytester) -> None:
655+
"""Marks should be collected from MRO from nearest to furthest (#14329)."""
656+
pytester.makepyfile(
656657
"""
657658
import pytest
658659
659-
@pytest.mark.data(0)
660-
class TestParent:
661-
def get_data_mark(self, request: pytest.FixtureRequest):
662-
getted_mark = request.node.get_closest_marker("data")
663-
assert getted_mark
664-
return getted_mark
665660
666-
def test_case(self, request: pytest.FixtureRequest):
667-
data_mark = self.get_data_mark(request)
668-
assert data_mark.args[0] == 0
661+
@pytest.mark.foo(0)
662+
class TestParent:
663+
def test_only_class(self, request):
664+
assert request.node.get_closest_marker("foo").args[0] == 0
665+
assert [mark.args[0] for mark in request.node.iter_markers("foo")] == [0]
669666
670-
@pytest.mark.data(1)
671-
def test_case_with_own_mark(self, request: pytest.FixtureRequest):
672-
data_mark = self.get_data_mark(request)
673-
assert data_mark.args[0] == 1
667+
@pytest.mark.foo(1)
668+
def test_function_and_class(self, request):
669+
assert request.node.get_closest_marker("foo").args[0] == 1
670+
assert [mark.args[0] for mark in request.node.iter_markers("foo")] == [1, 0]
674671
675672
676-
@pytest.mark.data(2)
673+
@pytest.mark.foo(2)
677674
class TestChild(TestParent):
678-
def test_case(self, request: pytest.FixtureRequest):
679-
data_mark = self.get_data_mark(request)
680-
assert data_mark.args[0] == 2
681-
682-
@pytest.mark.data(3)
683-
def test_case_with_own_mark(self, request: pytest.FixtureRequest):
684-
data_mark = self.get_data_mark(request)
685-
assert data_mark.args[0] == 3
675+
def test_only_class(self, request):
676+
assert request.node.get_closest_marker("foo").args[0] == 2
677+
assert [mark.args[0] for mark in request.node.iter_markers("foo")] == [2, 0]
678+
679+
@pytest.mark.foo(3)
680+
def test_function_and_class(self, request):
681+
assert request.node.get_closest_marker("foo").args[0] == 3
682+
assert [mark.args[0] for mark in request.node.iter_markers("foo")] == [3, 2, 0]
686683
"""
687684
)
688-
result = pytester.runpytest(test_path)
685+
result = pytester.runpytest()
689686
result.assert_outcomes(passed=4)
690687

691688
def test_mark_with_wrong_marker(self, pytester: Pytester) -> None:

0 commit comments

Comments
 (0)