Skip to content

Commit 0fae45b

Browse files
authored
Merge pull request #9660 from pytest-dev/backport-9646-to-7.0.x
2 parents 6684110 + 37d434f commit 0fae45b

File tree

3 files changed

+49
-27
lines changed

3 files changed

+49
-27
lines changed

changelog/9643.bugfix.rst

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Delay issuing a :class:`~pytest.PytestWarning` about diamond inheritance involving :class:`~pytest.Item` and
2+
:class:`~pytest.Collector` so it can be filtered using :ref:`standard warning filters <warnings>`.

src/_pytest/nodes.py

+31-14
Original file line numberDiff line numberDiff line change
@@ -656,20 +656,6 @@ class Item(Node):
656656

657657
nextitem = None
658658

659-
def __init_subclass__(cls) -> None:
660-
problems = ", ".join(
661-
base.__name__ for base in cls.__bases__ if issubclass(base, Collector)
662-
)
663-
if problems:
664-
warnings.warn(
665-
f"{cls.__name__} is an Item subclass and should not be a collector, "
666-
f"however its bases {problems} are collectors.\n"
667-
"Please split the Collectors and the Item into separate node types.\n"
668-
"Pytest Doc example: https://docs.pytest.org/en/latest/example/nonpython.html\n"
669-
"example pull request on a plugin: https://github.com/asmeurer/pytest-flakes/pull/40/",
670-
PytestWarning,
671-
)
672-
673659
def __init__(
674660
self,
675661
name,
@@ -697,6 +683,37 @@ def __init__(
697683
#: for this test.
698684
self.user_properties: List[Tuple[str, object]] = []
699685

686+
self._check_item_and_collector_diamond_inheritance()
687+
688+
def _check_item_and_collector_diamond_inheritance(self) -> None:
689+
"""
690+
Check if the current type inherits from both File and Collector
691+
at the same time, emitting a warning accordingly (#8447).
692+
"""
693+
cls = type(self)
694+
695+
# We inject an attribute in the type to avoid issuing this warning
696+
# for the same class more than once, which is not helpful.
697+
# It is a hack, but was deemed acceptable in order to avoid
698+
# flooding the user in the common case.
699+
attr_name = "_pytest_diamond_inheritance_warning_shown"
700+
if getattr(cls, attr_name, False):
701+
return
702+
setattr(cls, attr_name, True)
703+
704+
problems = ", ".join(
705+
base.__name__ for base in cls.__bases__ if issubclass(base, Collector)
706+
)
707+
if problems:
708+
warnings.warn(
709+
f"{cls.__name__} is an Item subclass and should not be a collector, "
710+
f"however its bases {problems} are collectors.\n"
711+
"Please split the Collectors and the Item into separate node types.\n"
712+
"Pytest Doc example: https://docs.pytest.org/en/latest/example/nonpython.html\n"
713+
"example pull request on a plugin: https://github.com/asmeurer/pytest-flakes/pull/40/",
714+
PytestWarning,
715+
)
716+
700717
def runtest(self) -> None:
701718
"""Run the test case for this item.
702719

testing/test_nodes.py

+16-13
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import re
2+
import warnings
13
from pathlib import Path
24
from typing import cast
35
from typing import List
@@ -58,30 +60,31 @@ def test_subclassing_both_item_and_collector_deprecated(
5860
request, tmp_path: Path
5961
) -> None:
6062
"""
61-
Verifies we warn on diamond inheritance
62-
as well as correctly managing legacy inheritance ctors with missing args
63-
as found in plugins
63+
Verifies we warn on diamond inheritance as well as correctly managing legacy
64+
inheritance constructors with missing args as found in plugins.
6465
"""
6566

66-
with pytest.warns(
67-
PytestWarning,
68-
match=(
69-
"(?m)SoWrong is an Item subclass and should not be a collector, however its bases File are collectors.\n"
70-
"Please split the Collectors and the Item into separate node types.\n.*"
71-
),
72-
):
67+
# We do not expect any warnings messages to issued during class definition.
68+
with warnings.catch_warnings():
69+
warnings.simplefilter("error")
7370

7471
class SoWrong(nodes.Item, nodes.File):
7572
def __init__(self, fspath, parent):
7673
"""Legacy ctor with legacy call # don't wana see"""
7774
super().__init__(fspath, parent)
7875

79-
with pytest.warns(
80-
PytestWarning, match=".*SoWrong.* not using a cooperative constructor.*"
81-
):
76+
with pytest.warns(PytestWarning) as rec:
8277
SoWrong.from_parent(
8378
request.session, fspath=legacy_path(tmp_path / "broken.txt")
8479
)
80+
messages = [str(x.message) for x in rec]
81+
assert any(
82+
re.search(".*SoWrong.* not using a cooperative constructor.*", x)
83+
for x in messages
84+
)
85+
assert any(
86+
re.search("(?m)SoWrong .* should not be a collector", x) for x in messages
87+
)
8588

8689

8790
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)