mirror of
https://github.com/python/cpython.git
synced 2025-09-27 02:39:58 +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
|
Subclass of :exc:`TestSkipped`. Raised when a resource (such as a network
|
||||||
connection) is not available. Raised by the :func:`requires` function.
|
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
|
.. 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.
|
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
|
Return a context manager that guards the warnings filter from being
|
||||||
permanently changed and records the data of the last warning that has been
|
permanently changed and optionally alters the :func:`showwarning`
|
||||||
issued. The ``record`` argument specifies whether any raised warnings are
|
function to record the details of any warnings that are issued in the
|
||||||
captured by the object returned by :func:`warnings.catch_warning` or allowed
|
managed context. Attributes of the most recent warning are saved
|
||||||
to propagate as normal.
|
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:
|
with catch_warning() as w:
|
||||||
|
warnings.simplefilter("always")
|
||||||
warnings.warn("foo")
|
warnings.warn("foo")
|
||||||
assert str(w.message) == "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()
|
.. function:: captured_stdout()
|
||||||
|
|
||||||
|
@ -331,3 +345,5 @@ The :mod:`test.support` module defines the following classes:
|
||||||
.. method:: EnvironmentVarGuard.unset(envvar)
|
.. method:: EnvironmentVarGuard.unset(envvar)
|
||||||
|
|
||||||
Temporarily unset the environment variable ``envvar``.
|
Temporarily unset the environment variable ``envvar``.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -368,36 +368,49 @@ def open_urlresource(url, *args, **kw):
|
||||||
|
|
||||||
|
|
||||||
class WarningMessage(object):
|
class WarningMessage(object):
|
||||||
"Holds the result of the latest showwarning() call"
|
"Holds the result of a single showwarning() call"
|
||||||
def __init__(self):
|
_WARNING_DETAILS = "message category filename lineno line".split()
|
||||||
self.message = None
|
def __init__(self, message, category, filename, lineno, line=None):
|
||||||
self.category = None
|
for attr in self._WARNING_DETAILS:
|
||||||
self.filename = None
|
setattr(self, attr, locals()[attr])
|
||||||
self.lineno = None
|
self._category_name = category.__name__ if category else 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))
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return ("{message : %r, category : %r, filename : %r, lineno : %s, "
|
return ("{message : %r, category : %r, filename : %r, lineno : %s, "
|
||||||
"line : %r}" % (self.message,
|
"line : %r}" % (self.message, self._category_name,
|
||||||
self.category.__name__ if self.category else None,
|
self.filename, self.lineno, self.line))
|
||||||
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
|
@contextlib.contextmanager
|
||||||
def catch_warning(module=warnings, record=True):
|
def catch_warning(module=warnings, record=True):
|
||||||
"""
|
"""Guard the warnings filter from being permanently changed and
|
||||||
Guard the warnings filter from being permanently changed and record the
|
optionally record the details of any warnings that are issued.
|
||||||
data of the last warning that has been issued.
|
|
||||||
|
|
||||||
Use like this:
|
Use like this:
|
||||||
|
|
||||||
|
@ -405,13 +418,17 @@ def catch_warning(module=warnings, record=True):
|
||||||
warnings.warn("foo")
|
warnings.warn("foo")
|
||||||
assert str(w.message) == "foo"
|
assert str(w.message) == "foo"
|
||||||
"""
|
"""
|
||||||
original_filters = module.filters[:]
|
original_filters = module.filters
|
||||||
original_showwarning = module.showwarning
|
original_showwarning = module.showwarning
|
||||||
if record:
|
if record:
|
||||||
warning_obj = WarningMessage()
|
recorder = WarningRecorder()
|
||||||
module.showwarning = warning_obj._showwarning
|
module.showwarning = recorder._showwarning
|
||||||
|
else:
|
||||||
|
recorder = None
|
||||||
try:
|
try:
|
||||||
yield warning_obj if record else None
|
# Replace the filters with a copy of the original
|
||||||
|
module.filters = module.filters[:]
|
||||||
|
yield recorder
|
||||||
finally:
|
finally:
|
||||||
module.showwarning = original_showwarning
|
module.showwarning = original_showwarning
|
||||||
module.filters = original_filters
|
module.filters = original_filters
|
||||||
|
@ -421,7 +438,7 @@ class CleanImport(object):
|
||||||
"""Context manager to force import to return a new module reference.
|
"""Context manager to force import to return a new module reference.
|
||||||
|
|
||||||
This is useful for testing module-level behaviours, such as
|
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:
|
Use like this:
|
||||||
|
|
||||||
|
|
|
@ -35,12 +35,9 @@ def with_warning_restore(func):
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def decorator(*args, **kw):
|
def decorator(*args, **kw):
|
||||||
with catch_warning():
|
with catch_warning():
|
||||||
# Grrr, we need this function to warn every time. Without removing
|
# We need this function to warn every time, so stick an
|
||||||
# the warningregistry, running test_tarfile then test_struct would fail
|
# unqualifed 'always' at the head of the filter list
|
||||||
# on 64-bit platforms.
|
warnings.simplefilter("always")
|
||||||
globals = func.__globals__
|
|
||||||
if '__warningregistry__' in globals:
|
|
||||||
del globals['__warningregistry__']
|
|
||||||
warnings.filterwarnings("error", category=DeprecationWarning)
|
warnings.filterwarnings("error", category=DeprecationWarning)
|
||||||
return func(*args, **kw)
|
return func(*args, **kw)
|
||||||
return decorator
|
return decorator
|
||||||
|
@ -53,7 +50,7 @@ def deprecated_err(func, *args):
|
||||||
pass
|
pass
|
||||||
except DeprecationWarning:
|
except DeprecationWarning:
|
||||||
if not PY_STRUCT_OVERFLOW_MASKING:
|
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))
|
func.__name__, args))
|
||||||
else:
|
else:
|
||||||
raise TestFailed("%s%s did not raise error" % (
|
raise TestFailed("%s%s did not raise error" % (
|
||||||
|
|
|
@ -487,6 +487,47 @@ class CWarningsDisplayTests(BaseTest, WarningsDisplayTests):
|
||||||
class PyWarningsDisplayTests(BaseTest, WarningsDisplayTests):
|
class PyWarningsDisplayTests(BaseTest, WarningsDisplayTests):
|
||||||
module = py_warnings
|
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():
|
def test_main():
|
||||||
py_warnings.onceregistry.clear()
|
py_warnings.onceregistry.clear()
|
||||||
|
@ -498,6 +539,7 @@ def test_main():
|
||||||
CWCmdLineTests, PyWCmdLineTests,
|
CWCmdLineTests, PyWCmdLineTests,
|
||||||
_WarningsTests,
|
_WarningsTests,
|
||||||
CWarningsDisplayTests, PyWarningsDisplayTests,
|
CWarningsDisplayTests, PyWarningsDisplayTests,
|
||||||
|
CWarningsSupportTests, PyWarningsSupportTests,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue