From 44ad1c9811d2ebf540e601ea66b9bebf8ea82969 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 24 Oct 2023 15:04:13 +0000 Subject: [PATCH] [7.4.x] fix #10447 - consider marks in reverse mro order to give base classes priority (#11545) Co-authored-by: Ronny Pfannschmidt --- changelog/10447.bugfix.rst | 2 ++ src/_pytest/mark/structures.py | 4 +++- testing/test_mark.py | 37 +++++++++++++++++++++++++++++++++- 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 changelog/10447.bugfix.rst diff --git a/changelog/10447.bugfix.rst b/changelog/10447.bugfix.rst new file mode 100644 index 00000000000..fff94b28fb5 --- /dev/null +++ b/changelog/10447.bugfix.rst @@ -0,0 +1,2 @@ +markers are now considered in the reverse mro order to ensure base class markers are considered first +this resolves a regression. diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index 8dbff1dc93a..55620f0429e 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -373,7 +373,9 @@ def get_unpacked_marks( if not consider_mro: mark_lists = [obj.__dict__.get("pytestmark", [])] else: - mark_lists = [x.__dict__.get("pytestmark", []) for x in obj.__mro__] + mark_lists = [ + x.__dict__.get("pytestmark", []) for x in reversed(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 e2d1a40c38a..2767260df8c 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -1130,6 +1130,41 @@ class C(A, B): all_marks = get_unpacked_marks(C) - assert all_marks == [xfail("c").mark, xfail("a").mark, xfail("b").mark] + assert all_marks == [xfail("b").mark, xfail("a").mark, xfail("c").mark] assert get_unpacked_marks(C, consider_mro=False) == [xfail("c").mark] + + +# @pytest.mark.issue("https://github.com/pytest-dev/pytest/issues/10447") +def test_mark_fixture_order_mro(pytester: Pytester): + """This ensures we walk marks of the mro starting with the base classes + the action at a distance fixtures are taken as minimal example from a real project + + """ + foo = pytester.makepyfile( + """ + import pytest + + @pytest.fixture + def add_attr1(request): + request.instance.attr1 = object() + + + @pytest.fixture + def add_attr2(request): + request.instance.attr2 = request.instance.attr1 + + + @pytest.mark.usefixtures('add_attr1') + class Parent: + pass + + + @pytest.mark.usefixtures('add_attr2') + class TestThings(Parent): + def test_attrs(self): + assert self.attr1 == self.attr2 + """ + ) + result = pytester.runpytest(foo) + result.assert_outcomes(passed=1)