Kaydet (Commit) 38469e27 authored tarafından 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 itto test_warnings.
üst 3d0b9f09
......@@ -291,19 +291,34 @@ The :mod:`test.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. Details of the most recent call to :func:`showwarning`
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"
assert w.last == "foo"
warnings.warn("bar")
assert w.last == "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).
.. versionadded:: 2.6
......@@ -334,8 +349,6 @@ The :mod:`test.test_support` module defines the following classes:
attributes on the exception is :exc:`ResourceDenied` raised.
.. versionadded:: 2.6
.. class:: EnvironmentVarGuard()
Class used to temporarily set or unset environment variables. Instances can be
......@@ -352,3 +365,5 @@ The :mod:`test.test_support` module defines the following classes:
.. method:: EnvironmentVarGuard.unset(envvar)
Temporarily unset the environment variable ``envvar``.
......@@ -26,41 +26,30 @@ class TestPy3KWarnings(unittest.TestCase):
with catch_warning() as w:
safe_exec("True = False")
self.assertWarning(None, w, expected)
with catch_warning() as w:
safe_exec("False = True")
self.assertWarning(None, w, expected)
with catch_warning() as w:
try:
safe_exec("obj.False = True")
except NameError: pass
self.assertWarning(None, w, expected)
with catch_warning() as w:
try:
safe_exec("obj.True = False")
except NameError: pass
self.assertWarning(None, w, expected)
with catch_warning() as w:
safe_exec("def False(): pass")
self.assertWarning(None, w, expected)
with catch_warning() as w:
safe_exec("def True(): pass")
self.assertWarning(None, w, expected)
with catch_warning() as w:
safe_exec("class False: pass")
self.assertWarning(None, w, expected)
with catch_warning() as w:
safe_exec("class True: pass")
self.assertWarning(None, w, expected)
with catch_warning() as w:
safe_exec("def f(True=43): pass")
self.assertWarning(None, w, expected)
with catch_warning() as w:
safe_exec("def f(False=None): pass")
self.assertWarning(None, w, expected)
with catch_warning() as w:
safe_exec("f(False=True)")
self.assertWarning(None, w, expected)
with catch_warning() as w:
safe_exec("f(True=1)")
self.assertWarning(None, w, expected)
......@@ -69,25 +58,20 @@ class TestPy3KWarnings(unittest.TestCase):
expected = 'type inequality comparisons not supported in 3.x'
with catch_warning() as w:
self.assertWarning(int < str, w, expected)
with catch_warning() as w:
self.assertWarning(type < object, w, expected)
def test_object_inequality_comparisons(self):
expected = 'comparing unequal types not supported in 3.x'
with catch_warning() as w:
self.assertWarning(str < [], w, expected)
with catch_warning() as w:
self.assertWarning(object() < (1, 2), w, expected)
def test_dict_inequality_comparisons(self):
expected = 'dict inequality comparisons not supported in 3.x'
with catch_warning() as w:
self.assertWarning({} < {2:3}, w, expected)
with catch_warning() as w:
self.assertWarning({} <= {}, w, expected)
with catch_warning() as w:
self.assertWarning({} > {2:3}, w, expected)
with catch_warning() as w:
self.assertWarning({2:3} >= {}, w, expected)
def test_cell_inequality_comparisons(self):
......@@ -100,7 +84,6 @@ class TestPy3KWarnings(unittest.TestCase):
cell1, = f(1).func_closure
with catch_warning() as w:
self.assertWarning(cell0 == cell1, w, expected)
with catch_warning() as w:
self.assertWarning(cell0 < cell1, w, expected)
def test_code_inequality_comparisons(self):
......@@ -111,11 +94,8 @@ class TestPy3KWarnings(unittest.TestCase):
pass
with catch_warning() as w:
self.assertWarning(f.func_code < g.func_code, w, expected)
with catch_warning() as w:
self.assertWarning(f.func_code <= g.func_code, w, expected)
with catch_warning() as w:
self.assertWarning(f.func_code >= g.func_code, w, expected)
with catch_warning() as w:
self.assertWarning(f.func_code > g.func_code, w, expected)
def test_builtin_function_or_method_comparisons(self):
......@@ -125,11 +105,8 @@ class TestPy3KWarnings(unittest.TestCase):
meth = {}.get
with catch_warning() as w:
self.assertWarning(func < meth, w, expected)
with catch_warning() as w:
self.assertWarning(func > meth, w, expected)
with catch_warning() as w:
self.assertWarning(meth <= func, w, expected)
with catch_warning() as w:
self.assertWarning(meth >= func, w, expected)
def assertWarning(self, _, warning, expected_message):
......@@ -142,11 +119,8 @@ class TestPy3KWarnings(unittest.TestCase):
with catch_warning() as w:
self.assertWarning(lst.sort(cmp=cmp), w, expected)
with catch_warning() as w:
self.assertWarning(sorted(lst, cmp=cmp), w, expected)
with catch_warning() as w:
self.assertWarning(lst.sort(cmp), w, expected)
with catch_warning() as w:
self.assertWarning(sorted(lst, cmp), w, expected)
def test_sys_exc_clear(self):
......@@ -229,7 +203,7 @@ class TestStdlibRemovals(unittest.TestCase):
"""Make sure the specified module, when imported, raises a
DeprecationWarning and specifies itself in the message."""
with CleanImport(module_name):
with catch_warning(record=False) as w:
with catch_warning(record=False):
warnings.filterwarnings("error", ".+ removed",
DeprecationWarning)
try:
......@@ -290,7 +264,7 @@ class TestStdlibRemovals(unittest.TestCase):
def test_main():
with catch_warning(record=True):
with catch_warning():
warnings.simplefilter("always")
run_unittest(TestPy3KWarnings,
TestStdlibRemovals)
......
......@@ -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.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" % (
......
......@@ -383,36 +383,49 @@ def open_urlresource(url):
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:
......@@ -420,13 +433,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
......@@ -436,7 +453,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:
......
......@@ -488,6 +488,49 @@ 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 test_support.catch_warning(wmod):
wmod.filters = wmod.showwarning = object()
self.assert_(wmod.filters is orig_filters)
self.assert_(wmod.showwarning is orig_showwarning)
with test_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 test_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 test_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
class ShowwarningDeprecationTests(BaseTest):
"""Test the deprecation of the old warnings.showwarning() API works."""
......@@ -513,7 +556,6 @@ class PyShowwarningDeprecationTests(ShowwarningDeprecationTests):
module = py_warnings
def test_main():
py_warnings.onceregistry.clear()
c_warnings.onceregistry.clear()
......@@ -524,6 +566,7 @@ def test_main():
CWCmdLineTests, PyWCmdLineTests,
_WarningsTests,
CWarningsDisplayTests, PyWarningsDisplayTests,
CWarningsSupportTests, PyWarningsSupportTests,
CShowwarningDeprecationTests,
PyShowwarningDeprecationTests,
)
......
......@@ -82,6 +82,14 @@ Library
redundant ":443" port number designation when the connection is using the
default https port (443).
Tests
-----
- test.test_support.catch_warning now keeps track of all warnings it sees
and is now better documented. Explicit unit tests for this context manager
have been added to test_warnings.
Build
-----
......
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