Skip to content

Commit

Permalink
Allow using warnings.warn() with Warnings
Browse files Browse the repository at this point in the history
`pytest.warns()` now allows using `warnings.warn()` with a `Warning`
instance, as is required by Python, with one exception. If the warning
used is a `UserWarning` that was created by passing it arguments and the
first argument was not a `str` then `pytest.raises()` still considers
that an error. This is because if an invalid type was used in
`warnings.warn()` then Python creates a `UserWarning` anyways and it
becomes impossible for `pytest` to figure out if that was done
automatically or not.
  • Loading branch information
eerovaher committed Feb 9, 2024
1 parent 898f8f0 commit b9d756f
Show file tree
Hide file tree
Showing 4 changed files with 24 additions and 5 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ Edison Gustavo Muenz
Edoardo Batini
Edson Tadeu M. Manoel
Eduardo Schettino
Eero Vaher
Eli Boyarski
Elizaveta Shashkova
Éloi Rivard
Expand Down
10 changes: 8 additions & 2 deletions changelog/10865.improvement.rst
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
: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.
:func:`pytest.warns` now validates that :func:`warnings.warn` was called with a
`str` or a `Warning`.
Currently in Python it is possible to use other types, 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.
13 changes: 12 additions & 1 deletion src/_pytest/recwarn.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,18 @@ def found_str():

@staticmethod
def _validate_message(wrn: Any) -> None:
if isinstance(wrn.message, Warning) and type(wrn.message) is not UserWarning:
# If the warning was of an incorrect type then Python creates a UserWarning.
# Any other warning must have been specified explicitly.
return
if not wrn.message.args:
# UserWarning() without arguments must have been specified explicitly.
return

Check warning on line 345 in src/_pytest/recwarn.py

View check run for this annotation

Codecov / codecov/patch

src/_pytest/recwarn.py#L345

Added line #L345 was not covered by tests
if not isinstance(msg := wrn.message.args[0], str):
# It is possible that UserWarning was explicitly specified, but its
# first argument was not a string, but that case can't be
# distinguished from an invalid type.
raise TypeError(
f"Warning message must be str, got {msg!r} (type {type(msg).__name__})"
"Warning must be str or Warning, got "
f"{msg!r} (type {type(msg).__name__})"
)
5 changes: 3 additions & 2 deletions testing/test_recwarn.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import List
from typing import Optional
from typing import Type
from typing import Union
import warnings

from _pytest.pytester import Pytester
Expand Down Expand Up @@ -483,7 +484,7 @@ def test_catch_warning_within_raise(self) -> None:
def test_raise_type_error_on_invalid_warning() -> None:
"""Check pytest.warns validates warning messages are strings (#10865) or
Warning instances (#11959)."""
with pytest.raises(TypeError, match="Warning message must be str"):
with pytest.raises(TypeError, match="Warning must be str or Warning"):
with pytest.warns(UserWarning):
warnings.warn(1) # type: ignore

Expand All @@ -496,7 +497,7 @@ def test_raise_type_error_on_invalid_warning() -> None:
pytest.param(Warning(), id="Warning"),
],
)
def test_no_raise_type_error_on_valid_warning(message: str | Warning) -> None:
def test_no_raise_type_error_on_valid_warning(message: Union[str, Warning]) -> None:
"""Check pytest.warns validates warning messages are strings (#10865) or
Warning instances (#11959)."""
with pytest.warns(Warning):
Expand Down

0 comments on commit b9d756f

Please sign in to comment.