Skip to content

Commit d8c7832

Browse files
fix #7792: consider marks from the mro
closes #9105 as superseeded
1 parent 24ef7c9 commit d8c7832

File tree

3 files changed

+47
-6
lines changed

3 files changed

+47
-6
lines changed

changelog/7792.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Consider the full mro when getting marks from classes.

src/_pytest/mark/structures.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -355,12 +355,29 @@ def __call__(self, *args: object, **kwargs: object):
355355
return self.with_args(*args, **kwargs)
356356

357357

358-
def get_unpacked_marks(obj: object) -> Iterable[Mark]:
358+
def get_unpacked_marks(
359+
obj: object | type,
360+
consider_mro: bool = True,
361+
) -> List[Mark]:
359362
"""Obtain the unpacked marks that are stored on an object."""
360-
mark_list = getattr(obj, "pytestmark", [])
361-
if not isinstance(mark_list, list):
362-
mark_list = [mark_list]
363-
return normalize_mark_list(mark_list)
363+
if isinstance(obj, type):
364+
if not consider_mro:
365+
mark_lists = [obj.__dict__.get("pytestmark", [])]
366+
else:
367+
mark_lists = [x.__dict__.get("pytestmark", []) for x in obj.__mro__]
368+
mark_list = []
369+
for item in mark_lists:
370+
if isinstance(item, list):
371+
mark_list.extend(item)
372+
else:
373+
mark_list.append(item)
374+
else:
375+
mark_attribute = getattr(obj, "pytestmark", [])
376+
if isinstance(mark_attribute, list):
377+
mark_list = mark_attribute
378+
else:
379+
mark_list = [mark_attribute]
380+
return list(normalize_mark_list(mark_list))
364381

365382

366383
def normalize_mark_list(
@@ -388,7 +405,7 @@ def store_mark(obj, mark: Mark) -> None:
388405
assert isinstance(mark, Mark), mark
389406
# Always reassign name to avoid updating pytestmark in a reference that
390407
# was only borrowed.
391-
obj.pytestmark = [*get_unpacked_marks(obj), mark]
408+
obj.pytestmark = [*get_unpacked_marks(obj, consider_mro=False), mark]
392409

393410

394411
# Typing for builtin pytest marks. This is cheating; it gives builtin marks

testing/test_mark.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,3 +1109,26 @@ def test_foo():
11091109
result = pytester.runpytest(foo, "-m", expr)
11101110
result.stderr.fnmatch_lines([expected])
11111111
assert result.ret == ExitCode.USAGE_ERROR
1112+
1113+
1114+
def test_mark_mro():
1115+
@pytest.mark.xfail("a")
1116+
class A:
1117+
pass
1118+
1119+
@pytest.mark.xfail("b")
1120+
class B:
1121+
pass
1122+
1123+
@pytest.mark.xfail("c")
1124+
class C(A, B):
1125+
pass
1126+
1127+
from _pytest.mark.structures import get_unpacked_marks
1128+
1129+
all_marks = list(get_unpacked_marks(C))
1130+
1131+
nk = [(x.name, x.args[0]) for x in all_marks]
1132+
assert nk == [("xfail", "c"), ("xfail", "a"), ("xfail", "b")]
1133+
1134+
assert list(get_unpacked_marks(C, consider_mro=False)) == []

0 commit comments

Comments
 (0)