diff --git a/changelog/7615.improvement.rst b/changelog/7615.improvement.rst new file mode 100644 index 000000000..fcf9a1a9b --- /dev/null +++ b/changelog/7615.improvement.rst @@ -0,0 +1 @@ +:meth:`Node.warn <_pytest.nodes.Node.warn>` now permits any subclass of :class:`Warning`, not just :class:`PytestWarning `. diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index dd58d5df9..27434fb6a 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -34,7 +34,6 @@ from _pytest.store import Store if TYPE_CHECKING: # Imported here due to circular import. from _pytest.main import Session - from _pytest.warning_types import PytestWarning from _pytest._code.code import _TracebackStyle @@ -198,27 +197,31 @@ class Node(metaclass=NodeMeta): def __repr__(self) -> str: return "<{} {}>".format(self.__class__.__name__, getattr(self, "name", None)) - def warn(self, warning: "PytestWarning") -> None: + def warn(self, warning: Warning) -> None: """Issue a warning for this Node. Warnings will be displayed after the test session, unless explicitly suppressed. :param Warning warning: - The warning instance to issue. Must be a subclass of PytestWarning. + The warning instance to issue. - :raises ValueError: If ``warning`` instance is not a subclass of PytestWarning. + :raises ValueError: If ``warning`` instance is not a subclass of Warning. Example usage: .. code-block:: python node.warn(PytestWarning("some message")) - """ - from _pytest.warning_types import PytestWarning + node.warn(UserWarning("some message")) - if not isinstance(warning, PytestWarning): + .. versionchanged:: 6.2 + Any subclass of :class:`Warning` is now accepted, rather than only + :class:`PytestWarning ` subclasses. + """ + # enforce type checks here to avoid getting a generic type error later otherwise. + if not isinstance(warning, Warning): raise ValueError( - "warning must be an instance of PytestWarning or subclass, got {!r}".format( + "warning must be an instance of Warning or subclass, got {!r}".format( warning ) ) diff --git a/testing/test_nodes.py b/testing/test_nodes.py index 627be9301..f3824c570 100644 --- a/testing/test_nodes.py +++ b/testing/test_nodes.py @@ -1,10 +1,12 @@ from typing import List +from typing import Type import py import pytest from _pytest import nodes from _pytest.pytester import Pytester +from _pytest.warning_types import PytestWarning @pytest.mark.parametrize( @@ -35,15 +37,33 @@ def test_node_from_parent_disallowed_arguments() -> None: nodes.Node.from_parent(None, config=None) # type: ignore[arg-type] -def test_std_warn_not_pytestwarning(pytester: Pytester) -> None: +@pytest.mark.parametrize( + "warn_type, msg", [(DeprecationWarning, "deprecated"), (PytestWarning, "pytest")] +) +def test_node_warn_is_no_longer_only_pytest_warnings( + pytester: Pytester, warn_type: Type[Warning], msg: str +) -> None: items = pytester.getitems( """ def test(): pass """ ) - with pytest.raises(ValueError, match=".*instance of PytestWarning.*"): - items[0].warn(UserWarning("some warning")) # type: ignore[arg-type] + with pytest.warns(warn_type, match=msg): + items[0].warn(warn_type(msg)) + + +def test_node_warning_enforces_warning_types(pytester: Pytester) -> None: + items = pytester.getitems( + """ + def test(): + pass + """ + ) + with pytest.raises( + ValueError, match="warning must be an instance of Warning or subclass" + ): + items[0].warn(Exception("ok")) # type: ignore[arg-type] def test__check_initialpaths_for_relpath() -> None: