parent
24ef7c98e8
commit
d8c783268c
|
@ -0,0 +1 @@
|
||||||
|
Consider the full mro when getting marks from classes.
|
|
@ -355,12 +355,29 @@ class MarkDecorator:
|
||||||
return self.with_args(*args, **kwargs)
|
return self.with_args(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def get_unpacked_marks(obj: object) -> Iterable[Mark]:
|
def get_unpacked_marks(
|
||||||
|
obj: object | type,
|
||||||
|
consider_mro: bool = True,
|
||||||
|
) -> List[Mark]:
|
||||||
"""Obtain the unpacked marks that are stored on an object."""
|
"""Obtain the unpacked marks that are stored on an object."""
|
||||||
mark_list = getattr(obj, "pytestmark", [])
|
if isinstance(obj, type):
|
||||||
if not isinstance(mark_list, list):
|
if not consider_mro:
|
||||||
mark_list = [mark_list]
|
mark_lists = [obj.__dict__.get("pytestmark", [])]
|
||||||
return normalize_mark_list(mark_list)
|
else:
|
||||||
|
mark_lists = [x.__dict__.get("pytestmark", []) for x in obj.__mro__]
|
||||||
|
mark_list = []
|
||||||
|
for item in mark_lists:
|
||||||
|
if isinstance(item, list):
|
||||||
|
mark_list.extend(item)
|
||||||
|
else:
|
||||||
|
mark_list.append(item)
|
||||||
|
else:
|
||||||
|
mark_attribute = getattr(obj, "pytestmark", [])
|
||||||
|
if isinstance(mark_attribute, list):
|
||||||
|
mark_list = mark_attribute
|
||||||
|
else:
|
||||||
|
mark_list = [mark_attribute]
|
||||||
|
return list(normalize_mark_list(mark_list))
|
||||||
|
|
||||||
|
|
||||||
def normalize_mark_list(
|
def normalize_mark_list(
|
||||||
|
@ -388,7 +405,7 @@ def store_mark(obj, mark: Mark) -> None:
|
||||||
assert isinstance(mark, Mark), mark
|
assert isinstance(mark, Mark), mark
|
||||||
# Always reassign name to avoid updating pytestmark in a reference that
|
# Always reassign name to avoid updating pytestmark in a reference that
|
||||||
# was only borrowed.
|
# was only borrowed.
|
||||||
obj.pytestmark = [*get_unpacked_marks(obj), mark]
|
obj.pytestmark = [*get_unpacked_marks(obj, consider_mro=False), mark]
|
||||||
|
|
||||||
|
|
||||||
# Typing for builtin pytest marks. This is cheating; it gives builtin marks
|
# Typing for builtin pytest marks. This is cheating; it gives builtin marks
|
||||||
|
|
|
@ -1109,3 +1109,26 @@ def test_marker_expr_eval_failure_handling(pytester: Pytester, expr) -> None:
|
||||||
result = pytester.runpytest(foo, "-m", expr)
|
result = pytester.runpytest(foo, "-m", expr)
|
||||||
result.stderr.fnmatch_lines([expected])
|
result.stderr.fnmatch_lines([expected])
|
||||||
assert result.ret == ExitCode.USAGE_ERROR
|
assert result.ret == ExitCode.USAGE_ERROR
|
||||||
|
|
||||||
|
|
||||||
|
def test_mark_mro():
|
||||||
|
@pytest.mark.xfail("a")
|
||||||
|
class A:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@pytest.mark.xfail("b")
|
||||||
|
class B:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@pytest.mark.xfail("c")
|
||||||
|
class C(A, B):
|
||||||
|
pass
|
||||||
|
|
||||||
|
from _pytest.mark.structures import get_unpacked_marks
|
||||||
|
|
||||||
|
all_marks = list(get_unpacked_marks(C))
|
||||||
|
|
||||||
|
nk = [(x.name, x.args[0]) for x in all_marks]
|
||||||
|
assert nk == [("xfail", "c"), ("xfail", "a"), ("xfail", "b")]
|
||||||
|
|
||||||
|
assert list(get_unpacked_marks(C, consider_mro=False)) == []
|
||||||
|
|
Loading…
Reference in New Issue