recwarn code cleanup and output enhancements

* use f-strings more
* use pformat to ensure multi line print for longer-warning lists
  while keeping short ones small
This commit is contained in:
Ronny Pfannschmidt 2021-04-02 00:58:56 +02:00
parent 843e01824c
commit 1ddb0696d3
2 changed files with 28 additions and 33 deletions

View File

@ -1,6 +1,7 @@
"""Record warnings during test function execution.""" """Record warnings during test function execution."""
import re import re
import warnings import warnings
from pprint import pformat
from types import TracebackType from types import TracebackType
from typing import Any from typing import Any
from typing import Callable from typing import Callable
@ -142,10 +143,11 @@ def warns(
__tracebackhide__ = True __tracebackhide__ = True
if not args: if not args:
if kwargs: if kwargs:
msg = "Unexpected keyword arguments passed to pytest.warns: " argnames = ", ".join(sorted(kwargs))
msg += ", ".join(sorted(kwargs)) raise TypeError(
msg += "\nUse context-manager form instead?" f"Unexpected keyword arguments passed to pytest.warns: {argnames}"
raise TypeError(msg) "\nUse context-manager form instead?"
)
return WarningsChecker(expected_warning, match_expr=match, _ispytest=True) return WarningsChecker(expected_warning, match_expr=match, _ispytest=True)
else: else:
func = args[0] func = args[0]
@ -191,7 +193,7 @@ class WarningsRecorder(warnings.catch_warnings):
if issubclass(w.category, cls): if issubclass(w.category, cls):
return self._list.pop(i) return self._list.pop(i)
__tracebackhide__ = True __tracebackhide__ = True
raise AssertionError("%r not found in warning list" % cls) raise AssertionError(f"{cls!r} not found in warning list")
def clear(self) -> None: def clear(self) -> None:
"""Clear the list of recorded warnings.""" """Clear the list of recorded warnings."""
@ -202,7 +204,7 @@ class WarningsRecorder(warnings.catch_warnings):
def __enter__(self) -> "WarningsRecorder": # type: ignore def __enter__(self) -> "WarningsRecorder": # type: ignore
if self._entered: if self._entered:
__tracebackhide__ = True __tracebackhide__ = True
raise RuntimeError("Cannot enter %r twice" % self) raise RuntimeError(f"Cannot enter {self!r} twice")
_list = super().__enter__() _list = super().__enter__()
# record=True means it's None. # record=True means it's None.
assert _list is not None assert _list is not None
@ -218,7 +220,7 @@ class WarningsRecorder(warnings.catch_warnings):
) -> None: ) -> None:
if not self._entered: if not self._entered:
__tracebackhide__ = True __tracebackhide__ = True
raise RuntimeError("Cannot exit %r without entering first" % self) raise RuntimeError(f"Cannot exit {self!r} without entering first")
super().__exit__(exc_type, exc_val, exc_tb) super().__exit__(exc_type, exc_val, exc_tb)
@ -268,16 +270,17 @@ class WarningsChecker(WarningsRecorder):
__tracebackhide__ = True __tracebackhide__ = True
def found_str():
return pformat([record.message for record in self], indent=2)
# 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:
if not any(issubclass(r.category, self.expected_warning) for r in self): if not any(issubclass(r.category, self.expected_warning) for r in self):
__tracebackhide__ = True __tracebackhide__ = True
fail( fail(
"DID NOT WARN. No warnings of type {} were emitted. " f"DID NOT WARN. No warnings of type {self.expected_warning} were emitted.\n"
"The list of emitted warnings is: {}.".format( f"The list of emitted warnings is: {found_str()}."
self.expected_warning, [each.message for each in self]
)
) )
elif self.match_expr is not None: elif self.match_expr is not None:
for r in self: for r in self:
@ -286,11 +289,8 @@ class WarningsChecker(WarningsRecorder):
break break
else: else:
fail( fail(
"DID NOT WARN. No warnings of type {} matching" f"""\
" ('{}') were emitted. The list of emitted warnings" DID NOT WARN. No warnings of type {self.expected_warning} matching the regex were emitted.
" is: {}.".format( The regex is: {self.match_expr}
self.expected_warning, The list of emitted warnings is: {found_str()}"""
self.match_expr,
[each.message for each in self],
)
) )

View File

@ -1,4 +1,3 @@
import re
import warnings import warnings
from typing import Optional from typing import Optional
@ -263,7 +262,7 @@ class TestWarns:
with pytest.warns(RuntimeWarning): with pytest.warns(RuntimeWarning):
warnings.warn("user", UserWarning) warnings.warn("user", UserWarning)
excinfo.match( excinfo.match(
r"DID NOT WARN. No warnings of type \(.+RuntimeWarning.+,\) were emitted. " r"DID NOT WARN. No warnings of type \(.+RuntimeWarning.+,\) were emitted.\n"
r"The list of emitted warnings is: \[UserWarning\('user',?\)\]." r"The list of emitted warnings is: \[UserWarning\('user',?\)\]."
) )
@ -271,15 +270,15 @@ class TestWarns:
with pytest.warns(UserWarning): with pytest.warns(UserWarning):
warnings.warn("runtime", RuntimeWarning) warnings.warn("runtime", RuntimeWarning)
excinfo.match( excinfo.match(
r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) were emitted. " r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) were emitted.\n"
r"The list of emitted warnings is: \[RuntimeWarning\('runtime',?\)\]." r"The list of emitted warnings is: \[RuntimeWarning\('runtime',?\)]."
) )
with pytest.raises(pytest.fail.Exception) as excinfo: with pytest.raises(pytest.fail.Exception) as excinfo:
with pytest.warns(UserWarning): with pytest.warns(UserWarning):
pass pass
excinfo.match( excinfo.match(
r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) were emitted. " r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) were emitted.\n"
r"The list of emitted warnings is: \[\]." r"The list of emitted warnings is: \[\]."
) )
@ -289,18 +288,14 @@ class TestWarns:
warnings.warn("runtime", RuntimeWarning) warnings.warn("runtime", RuntimeWarning)
warnings.warn("import", ImportWarning) warnings.warn("import", ImportWarning)
message_template = ( messages = [each.message for each in warninfo]
"DID NOT WARN. No warnings of type {0} were emitted. " expected_str = (
"The list of emitted warnings is: {1}." f"DID NOT WARN. No warnings of type {warning_classes} were emitted.\n"
) f"The list of emitted warnings is: {messages}."
excinfo.match(
re.escape(
message_template.format(
warning_classes, [each.message for each in warninfo]
)
)
) )
assert str(excinfo.value) == expected_str
def test_record(self) -> None: def test_record(self) -> None:
with pytest.warns(UserWarning) as record: with pytest.warns(UserWarning) as record:
warnings.warn("user", UserWarning) warnings.warn("user", UserWarning)