Commit b1304938 authored by Nick Coghlan's avatar Nick Coghlan

Make test.test_support.catch_warnings more robust as discussed on python-dev....

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)
parent 628b1b36
......@@ -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"
"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,
self.filename, self.lineno, self.line))
class WarningRecorder(object):
"Records the result of any showwarning calls"
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
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._showwarning(*((None,)*6))
self.warnings = []
self._set_last(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,
self.filename, self.lineno, self.line))
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,
)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment