Skip to content

closes: 10865 Fix muted exception #11804

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,7 @@ Vivaan Verma
Vlad Dragos
Vlad Radziuk
Vladyslav Rachek
Volodymyr Kochetkov
Volodymyr Piskun
Wei Lin
Wil Cooley
Expand Down
2 changes: 2 additions & 0 deletions changelog/10865.improvement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:func:`pytest.warns` now validates that warning object's ``message`` is of type `str` -- currently in Python it is possible to pass other types than `str` when creating `Warning` instances, however this causes an exception when :func:`warnings.filterwarnings` is used to filter those warnings. See `CPython #103577 <https://github.com/python/cpython/issues/103577>`__ for a discussion.
While this can be considered a bug in CPython, we decided to put guards in pytest as the error message produced without this check in place is confusing.
11 changes: 11 additions & 0 deletions src/_pytest/recwarn.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,3 +329,14 @@ def found_str():
module=w.__module__,
source=w.source,
)
# Check warnings has valid argument type (#10865).
wrn: warnings.WarningMessage
for wrn in self:
self._validate_message(wrn)

@staticmethod
def _validate_message(wrn: Any) -> None:
if not isinstance(msg := wrn.message.args[0], str):
raise TypeError(
f"Warning message must be str, got {msg!r} (type {type(msg).__name__})"
)
27 changes: 27 additions & 0 deletions testing/test_recwarn.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# mypy: allow-untyped-defs
import sys
from typing import List
from typing import Optional
from typing import Type
Expand Down Expand Up @@ -477,3 +478,29 @@ def test_catch_warning_within_raise(self) -> None:
with pytest.raises(ValueError, match="some exception"):
warnings.warn("some warning", category=FutureWarning)
raise ValueError("some exception")


def test_raise_type_error_on_non_string_warning() -> None:
"""Check pytest.warns validates warning messages are strings (#10865)."""
with pytest.raises(TypeError, match="Warning message must be str"):
with pytest.warns(UserWarning):
warnings.warn(1) # type: ignore


def test_no_raise_type_error_on_string_warning() -> None:
"""Check pytest.warns validates warning messages are strings (#10865)."""
with pytest.warns(UserWarning):
warnings.warn("Warning")


@pytest.mark.skipif(
hasattr(sys, "pypy_version_info"),
reason="Not for pypy",
)
def test_raise_type_error_on_non_string_warning_cpython() -> None:
# Check that we get the same behavior with the stdlib, at least if filtering
# (see https://github.com/python/cpython/issues/103577 for details)
with pytest.raises(TypeError):
with warnings.catch_warnings():
warnings.filterwarnings("ignore", "test")
warnings.warn(1) # type: ignore