Emit unmatched warnings from pytest.warns()

This commit is contained in:
Reagan Lee 2023-06-30 15:29:02 -07:00 committed by Zac Hatfield-Dodds
parent a50ea1b8e7
commit 9279ea2882
4 changed files with 71 additions and 4 deletions

View File

@ -311,6 +311,7 @@ Raphael Pierzina
Rafal Semik Rafal Semik
Raquel Alegre Raquel Alegre
Ravi Chandra Ravi Chandra
Reagan Lee
Robert Holt Robert Holt
Roberto Aldera Roberto Aldera
Roberto Polli Roberto Polli

View File

@ -0,0 +1 @@
Set :func:`warns` to re-emit unmatched warnings when the context closes

View File

@ -149,6 +149,10 @@ def warns( # noqa: F811
This could be achieved in the same way as with exceptions, see This could be achieved in the same way as with exceptions, see
:ref:`parametrizing_conditional_raising` for an example. :ref:`parametrizing_conditional_raising` for an example.
.. note::
Unlike the stdlib :func:`warnings.catch_warnings` context manager,
unmatched warnings will be re-emitted when the context closes.
""" """
__tracebackhide__ = True __tracebackhide__ = True
if not args: if not args:
@ -290,6 +294,32 @@ class WarningsChecker(WarningsRecorder):
def found_str(): def found_str():
return pformat([record.message for record in self], indent=2) return pformat([record.message for record in self], indent=2)
def re_emit() -> None:
for r in self:
if matches(r):
continue
assert issubclass(r.message.__class__, Warning)
warnings.warn_explicit(
str(r.message),
r.message.__class__,
r.filename,
r.lineno,
module=r.__module__,
source=r.source,
)
def matches(warning) -> bool:
if self.expected_warning is not None:
if issubclass(warning.category, self.expected_warning):
if self.match_expr is not None:
if re.compile(self.match_expr).search(str(warning.message)):
return True
return False
return True
return False
# only check if we're not currently handling an exception # only check if we're not currently handling an exception
if exc_type is None and exc_val is None and exc_tb is None: if exc_type is None and exc_val is None and exc_tb is None:
if self.expected_warning is not None: if self.expected_warning is not None:
@ -303,6 +333,7 @@ class WarningsChecker(WarningsRecorder):
for r in self: for r in self:
if issubclass(r.category, self.expected_warning): if issubclass(r.category, self.expected_warning):
if re.compile(self.match_expr).search(str(r.message)): if re.compile(self.match_expr).search(str(r.message)):
re_emit()
break break
else: else:
fail( fail(
@ -311,3 +342,5 @@ DID NOT WARN. No warnings of type {self.expected_warning} matching the regex wer
Regex: {self.match_expr} Regex: {self.match_expr}
Emitted warnings: {found_str()}""" Emitted warnings: {found_str()}"""
) )
else:
re_emit()

View File

@ -376,6 +376,8 @@ class TestWarns:
warnings.warn("value must be 42", UserWarning) warnings.warn("value must be 42", UserWarning)
def test_one_from_multiple_warns(self) -> None: def test_one_from_multiple_warns(self) -> None:
with pytest.raises(pytest.fail.Exception):
with pytest.warns(UserWarning, match=r"aaa"):
with pytest.warns(UserWarning, match=r"aaa"): with pytest.warns(UserWarning, match=r"aaa"):
warnings.warn("cccccccccc", UserWarning) warnings.warn("cccccccccc", UserWarning)
warnings.warn("bbbbbbbbbb", UserWarning) warnings.warn("bbbbbbbbbb", UserWarning)
@ -403,3 +405,33 @@ class TestWarns:
with pytest.warns(UserWarning, foo="bar"): # type: ignore with pytest.warns(UserWarning, foo="bar"): # type: ignore
pass pass
assert "Unexpected keyword arguments" in str(excinfo.value) assert "Unexpected keyword arguments" in str(excinfo.value)
def test_re_emit_single(self) -> None:
with pytest.warns(DeprecationWarning):
with pytest.warns(UserWarning):
warnings.warn("user warning", UserWarning)
warnings.warn("some deprecation warning", DeprecationWarning)
def test_re_emit_multiple(self) -> None:
with pytest.warns(UserWarning):
warnings.warn("first warning", UserWarning)
warnings.warn("second warning", UserWarning)
def test_re_emit_match_single(self) -> None:
with pytest.warns(DeprecationWarning):
with pytest.warns(UserWarning, match="user warning"):
warnings.warn("user warning", UserWarning)
warnings.warn("some deprecation warning", DeprecationWarning)
def test_re_emit_match_multiple(self) -> None:
# with pytest.warns(UserWarning):
with pytest.warns(UserWarning, match="user warning"):
warnings.warn("first user warning", UserWarning)
warnings.warn("second user warning", UserWarning)
def test_re_emit_non_match_single(self) -> None:
# with pytest.warns(UserWarning):
with pytest.warns(UserWarning, match="v2 warning"):
with pytest.warns(UserWarning, match="v1 warning"):
warnings.warn("v1 warning", UserWarning)
warnings.warn("non-matching v2 warning", UserWarning)