Skip to content

Commit

Permalink
Fix missing marks when inheritance from multiple classes
Browse files Browse the repository at this point in the history
  • Loading branch information
Oren Efraimov committed Sep 19, 2021
1 parent 60e995d commit 3be3a5d
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 0 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Expand Up @@ -104,6 +104,7 @@ Edison Gustavo Muenz
Edoardo Batini
Edson Tadeu M. Manoel
Eduardo Schettino
Efraimov Oren
Eli Boyarski
Elizaveta Shashkova
Éloi Rivard
Expand Down
5 changes: 5 additions & 0 deletions doc/en/changelog.rst
Expand Up @@ -31,6 +31,11 @@ with advance notice in the **Deprecations** section of releases.
pytest 6.2.5 (2021-08-29)
=========================

Bug Fixes
---------

- `#7792 <https://github.com/pytest-dev/pytest/issues/7792>`_: Fixing missing marks when inheritance from multiple classes.


Trivial/Internal Changes
------------------------
Expand Down
19 changes: 19 additions & 0 deletions src/_pytest/mark/structures.py
Expand Up @@ -4,6 +4,7 @@
from typing import Any
from typing import Callable
from typing import Collection
from typing import Dict
from typing import Iterable
from typing import Iterator
from typing import List
Expand Down Expand Up @@ -368,6 +369,24 @@ def get_unpacked_marks(obj) -> List[Mark]:
return normalize_mark_list(mark_list)


MARKERS_BY_CLASS_NAME: Dict[str, List[Mark]] = {}


def get_all_mro_marks(cls, module_name):
all_markers = getattr(cls, "pytestmark", [])
if cls is None:
return all_markers
if not isinstance(all_markers, list):
all_markers = [all_markers]
for parent_obj in cls.__mro__[::-1]:
full_class_name = f"{module_name}_{parent_obj.__name__}"
if not MARKERS_BY_CLASS_NAME.get(full_class_name):
MARKERS_BY_CLASS_NAME[full_class_name] = get_unpacked_marks(parent_obj)
all_markers.extend(MARKERS_BY_CLASS_NAME[full_class_name])
setattr(cls, "pytestmark", list({str(mark): mark for mark in all_markers}.values()))
return cls.pytestmark


def normalize_mark_list(mark_list: Iterable[Union[Mark, MarkDecorator]]) -> List[Mark]:
"""
Normalize an iterable of Mark or MarkDecorator objects into a list of marks
Expand Down
2 changes: 2 additions & 0 deletions src/_pytest/python.py
Expand Up @@ -60,6 +60,7 @@
from _pytest.main import Session
from _pytest.mark import MARK_GEN
from _pytest.mark import ParameterSet
from _pytest.mark.structures import get_all_mro_marks
from _pytest.mark.structures import get_unpacked_marks
from _pytest.mark.structures import Mark
from _pytest.mark.structures import MarkDecorator
Expand Down Expand Up @@ -1586,6 +1587,7 @@ def __init__(
# to a readonly property that returns FunctionDefinition.name.

self.keywords.update(self.obj.__dict__)
self.own_markers.extend(get_all_mro_marks(self.cls, self.module.__name__))
self.own_markers.extend(get_unpacked_marks(self.obj))
if callspec:
self.callspec = callspec
Expand Down
32 changes: 32 additions & 0 deletions testing/test_mark.py
Expand Up @@ -1127,3 +1127,35 @@ def test_foo():
result = pytester.runpytest(foo, "-m", expr)
result.stderr.fnmatch_lines([expected])
assert result.ret == ExitCode.USAGE_ERROR


@pytest.mark.parametrize(
"markers",
(
"mark1",
"mark1 and mark2",
"mark1 and mark2 and mark3",
"mark1 and mark2 and mark3 and mark4",
),
)
def test_markers_from_multiple_inheritances(pytester: Pytester, markers) -> None:
py_file = pytester.makepyfile(
"""
import pytest
@pytest.mark.mark1
class Base1:
pass
@pytest.mark.mark2
class Base2:
pass
@pytest.mark.mark3
class TestMultipleInheritances(Base1, Base2):
@pytest.mark.mark4
def test_multiple_inheritances(self):
pass
"""
)
result = pytester.inline_run(py_file, "-m", markers)
result.assertoutcome(passed=1)

0 comments on commit 3be3a5d

Please sign in to comment.