From a8eb81994e4f392de15ff19189bb0ed0fd4f0dc2 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 1 Jul 2023 16:04:22 -0400 Subject: [PATCH] Reverse order on marks from MRO Fixes #10447 --- changelog/10447.bugfix.rst | 1 + src/_pytest/mark/structures.py | 6 ++++-- testing/test_mark.py | 37 +++++++++++++++++++++++++++++++++- 3 files changed, 41 insertions(+), 3 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..5b3e29efd16 --- /dev/null +++ b/changelog/10447.bugfix.rst @@ -0,0 +1 @@ +When resolving marks from a class hierarchy MRO, reverse the order so that more basic classes' marks will be processed ahead of ancestral classes. diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index bc10d3b90ef..ce8e8c291e5 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -372,9 +372,11 @@ def get_unpacked_marks( """ if isinstance(obj, type): if not consider_mro: - mark_lists = [obj.__dict__.get("pytestmark", [])] + mark_lists: Iterable[Any] = [obj.__dict__.get("pytestmark", [])] else: - mark_lists = [x.__dict__.get("pytestmark", []) for x in obj.__mro__] + mark_lists = reversed( + [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 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)