mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
Make test.test_support.catch_warnings more robust as discussed on python-dev. Also add explicit tests for it to test_warnings. (forward port of r64910 from trunk)
This commit is contained in:
parent
628b1b3659
commit
b130493834
4 changed files with 114 additions and 42 deletions
|
@ -211,7 +211,7 @@ This module defines the following exceptions:
|
|||
Subclass of :exc:`TestSkipped`. Raised when a resource (such as a network
|
||||
connection) is not available. Raised by the :func:`requires` function.
|
||||
|
||||
The :mod:`test.test_support` module defines the following constants:
|
||||
The :mod:`test.support` module defines the following constants:
|
||||
|
||||
|
||||
.. data:: verbose
|
||||
|
@ -278,20 +278,34 @@ The :mod:`test.support` module defines the following functions:
|
|||
This will run all tests defined in the named module.
|
||||
|
||||
|
||||
.. function:: catch_warning(record=True)
|
||||
.. function:: catch_warning(module=warnings, record=True)
|
||||
|
||||
Return a context manager that guards the warnings filter from being
|
||||
permanently changed and records the data of the last warning that has been
|
||||
issued. The ``record`` argument specifies whether any raised warnings are
|
||||
captured by the object returned by :func:`warnings.catch_warning` or allowed
|
||||
to propagate as normal.
|
||||
permanently changed and optionally alters the :func:`showwarning`
|
||||
function to record the details of any warnings that are issued in the
|
||||
managed context. Attributes of the most recent warning are saved
|
||||
directly on the context manager, while details of previous warnings
|
||||
can be retrieved from the ``warnings`` list.
|
||||
|
||||
The context manager is typically used like this::
|
||||
The context manager is used like this::
|
||||
|
||||
with catch_warning() as w:
|
||||
warnings.simplefilter("always")
|
||||
warnings.warn("foo")
|
||||
assert str(w.message) == "foo"
|
||||
warnings.warn("bar")
|
||||
assert str(w.message) == "bar"
|
||||
assert str(w.warnings[0].message) == "foo"
|
||||
assert str(w.warnings[1].message) == "bar"
|
||||
|
||||
By default, the real :mod:`warnings` module is affected - the ability
|
||||
to select a different module is provided for the benefit of the
|
||||
:mod:`warnings` module's own unit tests.
|
||||
The ``record`` argument specifies whether or not the :func:`showwarning`
|
||||
function is replaced. Note that recording the warnings in this fashion
|
||||
also prevents them from being written to sys.stderr. If set to ``False``,
|
||||
the standard handling of warning messages is left in place (however, the
|
||||
original handling is still restored at the end of the block).
|
||||
|
||||
.. function:: captured_stdout()
|
||||
|
||||
|
@ -331,3 +345,5 @@ The :mod:`test.support` module defines the following classes:
|
|||
.. method:: EnvironmentVarGuard.unset(envvar)
|
||||
|
||||
Temporarily unset the environment variable ``envvar``.
|
||||
|
||||
|
||||
|
|
|
@ -368,36 +368,49 @@ def open_urlresource(url, *args, **kw):
|
|||
|
||||
|
||||
class WarningMessage(object):
|
||||
"Holds the result of the latest showwarning() call"
|
||||
def __init__(self):
|
||||
self.message = None
|
||||
self.category = None
|
||||
self.filename = None
|
||||
self.lineno = None
|
||||
|
||||
def _showwarning(self, message, category, filename, lineno, file=None,
|
||||
line=None):
|
||||
self.message = message
|
||||
self.category = category
|
||||
self.filename = filename
|
||||
self.lineno = lineno
|
||||
self.line = line
|
||||
|
||||
def reset(self):
|
||||
self._showwarning(*((None,)*6))
|
||||
"Holds the result of a single showwarning() call"
|
||||
_WARNING_DETAILS = "message category filename lineno line".split()
|
||||
def __init__(self, message, category, filename, lineno, line=None):
|
||||
for attr in self._WARNING_DETAILS:
|
||||
setattr(self, attr, locals()[attr])
|
||||
self._category_name = category.__name__ if category else None
|
||||
|
||||
def __str__(self):
|
||||
return ("{message : %r, category : %r, filename : %r, lineno : %s, "
|
||||
"line : %r}" % (self.message,
|
||||
self.category.__name__ if self.category else None,
|
||||
"line : %r}" % (self.message, self._category_name,
|
||||
self.filename, self.lineno, self.line))
|
||||
|
||||
class WarningRecorder(object):
|
||||
"Records the result of any showwarning calls"
|
||||
def __init__(self):
|
||||
self.warnings = []
|
||||
self._set_last(None)
|
||||
|
||||
def _showwarning(self, message, category, filename, lineno,
|
||||
file=None, line=None):
|
||||
wm = WarningMessage(message, category, filename, lineno, line)
|
||||
self.warnings.append(wm)
|
||||
self._set_last(wm)
|
||||
|
||||
def _set_last(self, last_warning):
|
||||
if last_warning is None:
|
||||
for attr in WarningMessage._WARNING_DETAILS:
|
||||
setattr(self, attr, None)
|
||||
else:
|
||||
for attr in WarningMessage._WARNING_DETAILS:
|
||||
setattr(self, attr, getattr(last_warning, attr))
|
||||
|
||||
def reset(self):
|
||||
self.warnings = []
|
||||
self._set_last(None)
|
||||
|
||||
def __str__(self):
|
||||
return '[%s]' % (', '.join(map(str, self.warnings)))
|
||||
|
||||
@contextlib.contextmanager
|
||||
def catch_warning(module=warnings, record=True):
|
||||
"""
|
||||
Guard the warnings filter from being permanently changed and record the
|
||||
data of the last warning that has been issued.
|
||||
"""Guard the warnings filter from being permanently changed and
|
||||
optionally record the details of any warnings that are issued.
|
||||
|
||||
Use like this:
|
||||
|
||||
|
@ -405,13 +418,17 @@ def catch_warning(module=warnings, record=True):
|
|||
warnings.warn("foo")
|
||||
assert str(w.message) == "foo"
|
||||
"""
|
||||
original_filters = module.filters[:]
|
||||
original_filters = module.filters
|
||||
original_showwarning = module.showwarning
|
||||
if record:
|
||||
warning_obj = WarningMessage()
|
||||
module.showwarning = warning_obj._showwarning
|
||||
recorder = WarningRecorder()
|
||||
module.showwarning = recorder._showwarning
|
||||
else:
|
||||
recorder = None
|
||||
try:
|
||||
yield warning_obj if record else None
|
||||
# Replace the filters with a copy of the original
|
||||
module.filters = module.filters[:]
|
||||
yield recorder
|
||||
finally:
|
||||
module.showwarning = original_showwarning
|
||||
module.filters = original_filters
|
||||
|
@ -421,7 +438,7 @@ class CleanImport(object):
|
|||
"""Context manager to force import to return a new module reference.
|
||||
|
||||
This is useful for testing module-level behaviours, such as
|
||||
the emission of a DepreciationWarning on import.
|
||||
the emission of a DeprecationWarning on import.
|
||||
|
||||
Use like this:
|
||||
|
||||
|
|
|
@ -35,12 +35,9 @@ def with_warning_restore(func):
|
|||
@wraps(func)
|
||||
def decorator(*args, **kw):
|
||||
with catch_warning():
|
||||
# Grrr, we need this function to warn every time. Without removing
|
||||
# the warningregistry, running test_tarfile then test_struct would fail
|
||||
# on 64-bit platforms.
|
||||
globals = func.__globals__
|
||||
if '__warningregistry__' in globals:
|
||||
del globals['__warningregistry__']
|
||||
# We need this function to warn every time, so stick an
|
||||
# unqualifed 'always' at the head of the filter list
|
||||
warnings.simplefilter("always")
|
||||
warnings.filterwarnings("error", category=DeprecationWarning)
|
||||
return func(*args, **kw)
|
||||
return decorator
|
||||
|
@ -53,7 +50,7 @@ def deprecated_err(func, *args):
|
|||
pass
|
||||
except DeprecationWarning:
|
||||
if not PY_STRUCT_OVERFLOW_MASKING:
|
||||
raise TestFailed("%s%s expected to raise struct.error" % (
|
||||
raise TestFailed("%s%s expected to raise DeprecationWarning" % (
|
||||
func.__name__, args))
|
||||
else:
|
||||
raise TestFailed("%s%s did not raise error" % (
|
||||
|
|
|
@ -487,6 +487,47 @@ class CWarningsDisplayTests(BaseTest, WarningsDisplayTests):
|
|||
class PyWarningsDisplayTests(BaseTest, WarningsDisplayTests):
|
||||
module = py_warnings
|
||||
|
||||
class WarningsSupportTests(object):
|
||||
"""Test the warning tools from test support module"""
|
||||
|
||||
def test_catch_warning_restore(self):
|
||||
wmod = self.module
|
||||
orig_filters = wmod.filters
|
||||
orig_showwarning = wmod.showwarning
|
||||
with support.catch_warning(wmod):
|
||||
wmod.filters = wmod.showwarning = object()
|
||||
self.assert_(wmod.filters is orig_filters)
|
||||
self.assert_(wmod.showwarning is orig_showwarning)
|
||||
with support.catch_warning(wmod, record=False):
|
||||
wmod.filters = wmod.showwarning = object()
|
||||
self.assert_(wmod.filters is orig_filters)
|
||||
self.assert_(wmod.showwarning is orig_showwarning)
|
||||
|
||||
def test_catch_warning_recording(self):
|
||||
wmod = self.module
|
||||
with support.catch_warning(wmod) as w:
|
||||
self.assertEqual(w.warnings, [])
|
||||
wmod.simplefilter("always")
|
||||
wmod.warn("foo")
|
||||
self.assertEqual(str(w.message), "foo")
|
||||
wmod.warn("bar")
|
||||
self.assertEqual(str(w.message), "bar")
|
||||
self.assertEqual(str(w.warnings[0].message), "foo")
|
||||
self.assertEqual(str(w.warnings[1].message), "bar")
|
||||
w.reset()
|
||||
self.assertEqual(w.warnings, [])
|
||||
orig_showwarning = wmod.showwarning
|
||||
with support.catch_warning(wmod, record=False) as w:
|
||||
self.assert_(w is None)
|
||||
self.assert_(wmod.showwarning is orig_showwarning)
|
||||
|
||||
|
||||
class CWarningsSupportTests(BaseTest, WarningsSupportTests):
|
||||
module = c_warnings
|
||||
|
||||
class PyWarningsSupportTests(BaseTest, WarningsSupportTests):
|
||||
module = py_warnings
|
||||
|
||||
|
||||
def test_main():
|
||||
py_warnings.onceregistry.clear()
|
||||
|
@ -498,6 +539,7 @@ def test_main():
|
|||
CWCmdLineTests, PyWCmdLineTests,
|
||||
_WarningsTests,
|
||||
CWarningsDisplayTests, PyWarningsDisplayTests,
|
||||
CWarningsSupportTests, PyWarningsSupportTests,
|
||||
)
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue