diff --git a/changelog/14329.bugfix.rst b/changelog/14329.bugfix.rst new file mode 100644 index 00000000000..59fab68bbb3 --- /dev/null +++ b/changelog/14329.bugfix.rst @@ -0,0 +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. diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 70d3a7457f6..c142fdcf1a8 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1677,7 +1677,9 @@ def _getautousenames(self, node: nodes.Node) -> Iterator[str]: def _getusefixturesnames(self, node: nodes.Item) -> Iterator[str]: """Return the names of usefixtures fixtures applicable to node.""" - for marker_node, mark in node.iter_markers_with_node(name="usefixtures"): + for marker_node, mark in reversed( + list(node.iter_markers_with_node(name="usefixtures")) + ): if not mark.args: marker_node.warn( PytestWarning( diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index 0fa6e8babba..1b43367b268 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -432,9 +432,7 @@ def get_unpacked_marks( if not consider_mro: mark_lists = [obj.__dict__.get("pytestmark", [])] else: - mark_lists = [ - x.__dict__.get("pytestmark", []) for x in reversed(obj.__mro__) - ] + mark_lists = [x.__dict__.get("pytestmark", []) for x in obj.__mro__] mark_list = [] for item in mark_lists: if isinstance(item, list): diff --git a/testing/test_mark.py b/testing/test_mark.py index 67219313183..c6254bf2935 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -651,6 +651,43 @@ def test_has_inherited(self): assert has_inherited_marker.kwargs == {"location": "class"} assert has_own.get_closest_marker("missing") is None + def test_mark_closest_with_parent(self, pytester: Pytester): + test_path = pytester.makepyfile( + """ + import pytest + + @pytest.mark.data(0) + class TestParent: + def get_data_mark(self, request: pytest.FixtureRequest): + getted_mark = request.node.get_closest_marker("data") + assert getted_mark + return getted_mark + + def test_case(self, request: pytest.FixtureRequest): + data_mark = self.get_data_mark(request) + assert data_mark.args[0] == 0 + + @pytest.mark.data(1) + def test_case_with_own_mark(self, request: pytest.FixtureRequest): + data_mark = self.get_data_mark(request) + assert data_mark.args[0] == 1 + + + @pytest.mark.data(2) + class TestChild(TestParent): + def test_case(self, request: pytest.FixtureRequest): + data_mark = self.get_data_mark(request) + assert data_mark.args[0] == 2 + + @pytest.mark.data(3) + def test_case_with_own_mark(self, request: pytest.FixtureRequest): + data_mark = self.get_data_mark(request) + assert data_mark.args[0] == 3 + """ + ) + result = pytester.runpytest(test_path) + result.assert_outcomes(passed=4) + def test_mark_with_wrong_marker(self, pytester: Pytester) -> None: reprec = pytester.inline_runsource( """ @@ -1229,7 +1266,7 @@ class C(A, B): all_marks = get_unpacked_marks(C) - assert all_marks == [xfail("b").mark, xfail("a").mark, xfail("c").mark] + assert all_marks == [xfail("c").mark, xfail("a").mark, xfail("b").mark] assert get_unpacked_marks(C, consider_mro=False) == [xfail("c").mark]