Kaydet (Commit) d2e09383 authored tarafından Nick Coghlan's avatar Nick Coghlan

Issue #3781: Final cleanup of warnings.catch_warnings and its usage in the test…

Issue #3781: Final cleanup of warnings.catch_warnings and its usage in the test suite. Closes issue w.r.t. 2.6 (R: Brett Cannon)
üst 9fa5a282
...@@ -291,18 +291,26 @@ The :mod:`test.test_support` module defines the following functions: ...@@ -291,18 +291,26 @@ The :mod:`test.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(module=warnings, record=True) .. function:: check_warnings()
Return a context manager that guards the warnings filter from being A convenience wrapper for ``warnings.catch_warnings()`` that makes
permanently changed and optionally alters the :func:`showwarning` it easier to test that a warning was correctly raised with a single
function to record the details of any warnings that are issued in the assertion. It is approximately equivalent to calling
managed context. Details of the most recent call to :func:`showwarning` ``warnings.catch_warnings(record=True)``.
are saved directly on the context manager, while details of previous
warnings can be retrieved from the ``warnings`` list. The main difference is that on entry to the context manager, a
:class:`WarningRecorder` instance is returned instead of a simple list.
The underlying warnings list is available via the recorder object's
:attr:`warnings` attribute, while the attributes of the last raised
warning are also accessible directly on the object. If no warning has
been raised, then the latter attributes will all be :const:`None`.
A :meth:`reset` method is also provided on the recorder object. This
method simply clears the warning list.
The context manager is used like this:: The context manager is used like this::
with catch_warning() as w: with check_warnings() as w:
warnings.simplefilter("always") warnings.simplefilter("always")
warnings.warn("foo") warnings.warn("foo")
assert str(w.message) == "foo" assert str(w.message) == "foo"
...@@ -310,15 +318,8 @@ The :mod:`test.test_support` module defines the following functions: ...@@ -310,15 +318,8 @@ The :mod:`test.test_support` module defines the following functions:
assert str(w.message) == "bar" assert str(w.message) == "bar"
assert str(w.warnings[0].message) == "foo" assert str(w.warnings[0].message) == "foo"
assert str(w.warnings[1].message) == "bar" assert str(w.warnings[1].message) == "bar"
w.reset()
By default, the real :mod:`warnings` module is affected - the ability assert len(w.warnings) == 0
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 .. versionadded:: 2.6
...@@ -366,4 +367,10 @@ The :mod:`test.test_support` module defines the following classes: ...@@ -366,4 +367,10 @@ The :mod:`test.test_support` module defines the following classes:
Temporarily unset the environment variable ``envvar``. Temporarily unset the environment variable ``envvar``.
.. class:: WarningsRecorder()
Class used to record warnings for unit tests. See documentation of
:func:`check_warnings` above for more details.
.. versionadded:: 2.6
...@@ -163,9 +163,9 @@ ImportWarning can also be enabled explicitly in Python code using:: ...@@ -163,9 +163,9 @@ ImportWarning can also be enabled explicitly in Python code using::
Temporarily Suppressing Warnings Temporarily Suppressing Warnings
-------------------------------- --------------------------------
If you are using code that you know will raise a warning, such some deprecated If you are using code that you know will raise a warning, such as a deprecated
function, but do not want to see the warning, then suppress the warning using function, but do not want to see the warning, then it is possible to suppress
the :class:`catch_warnings` context manager:: the warning using the :class:`catch_warnings` context manager::
import warnings import warnings
...@@ -216,7 +216,15 @@ the warning has been cleared. ...@@ -216,7 +216,15 @@ the warning has been cleared.
Once the context manager exits, the warnings filter is restored to its state Once the context manager exits, the warnings filter is restored to its state
when the context was entered. This prevents tests from changing the warnings when the context was entered. This prevents tests from changing the warnings
filter in unexpected ways between tests and leading to indeterminate test filter in unexpected ways between tests and leading to indeterminate test
results. results. The :func:`showwarning` function in the module is also restored to
its original value.
When testing multiple operations that raise the same kind of warning, it
is important to test them in a manner that confirms each operation is raising
a new warning (e.g. set warnings to be raised as exceptions and check the
operations raise exceptions, check that the length of the warning list
continues to increase after each operation, or else delete the previous
entries from the warnings list before each new operation).
.. _warning-functions: .. _warning-functions:
...@@ -330,16 +338,18 @@ Available Context Managers ...@@ -330,16 +338,18 @@ Available Context Managers
.. class:: catch_warnings([\*, record=False, module=None]) .. class:: catch_warnings([\*, record=False, module=None])
A context manager that copies and, upon exit, restores the warnings filter. A context manager that copies and, upon exit, restores the warnings filter
If the *record* argument is False (the default) the context manager returns and the :func:`showwarning` function.
:class:`None`. If *record* is true, a list is returned that is populated If the *record* argument is :const:`False` (the default) the context manager
with objects as seen by a custom :func:`showwarning` function (which also returns :class:`None` on entry. If *record* is :const:`True`, a list is
suppresses output to ``sys.stdout``). Each object has attributes with the returned that is progressively populated with objects as seen by a custom
same names as the arguments to :func:`showwarning`. :func:`showwarning` function (which also suppresses output to ``sys.stdout``).
Each object in the list has attributes with the same names as the arguments to
:func:`showwarning`.
The *module* argument takes a module that will be used instead of the The *module* argument takes a module that will be used instead of the
module returned when you import :mod:`warnings` whose filter will be module returned when you import :mod:`warnings` whose filter will be
protected. This arguments exists primarily for testing the :mod:`warnings` protected. This argument exists primarily for testing the :mod:`warnings`
module itself. module itself.
.. note:: .. note::
......
...@@ -5,7 +5,7 @@ import shutil ...@@ -5,7 +5,7 @@ import shutil
import sys import sys
import py_compile import py_compile
import warnings import warnings
from test.test_support import unlink, TESTFN, unload, run_unittest from test.test_support import unlink, TESTFN, unload, run_unittest, check_warnings
def remove_files(name): def remove_files(name):
...@@ -279,17 +279,17 @@ class RelativeImport(unittest.TestCase): ...@@ -279,17 +279,17 @@ class RelativeImport(unittest.TestCase):
check_relative() check_relative()
# Check relative fails with only __package__ wrong # Check relative fails with only __package__ wrong
ns = dict(__package__='foo', __name__='test.notarealmodule') ns = dict(__package__='foo', __name__='test.notarealmodule')
with warnings.catch_warnings(record=True) as w: with check_warnings() as w:
check_absolute() check_absolute()
self.assert_('foo' in str(w[-1].message)) self.assert_('foo' in str(w.message))
self.assertEqual(w[-1].category, RuntimeWarning) self.assertEqual(w.category, RuntimeWarning)
self.assertRaises(SystemError, check_relative) self.assertRaises(SystemError, check_relative)
# Check relative fails with __package__ and __name__ wrong # Check relative fails with __package__ and __name__ wrong
ns = dict(__package__='foo', __name__='notarealpkg.notarealmodule') ns = dict(__package__='foo', __name__='notarealpkg.notarealmodule')
with warnings.catch_warnings(record=True) as w: with check_warnings() as w:
check_absolute() check_absolute()
self.assert_('foo' in str(w[-1].message)) self.assert_('foo' in str(w.message))
self.assertEqual(w[-1].category, RuntimeWarning) self.assertEqual(w.category, RuntimeWarning)
self.assertRaises(SystemError, check_relative) self.assertRaises(SystemError, check_relative)
# Check both fail with package set to a non-string # Check both fail with package set to a non-string
ns = dict(__package__=object()) ns = dict(__package__=object())
......
import unittest import unittest
import sys import sys
from test.test_support import CleanImport, TestSkipped, run_unittest from test.test_support import (check_warnings, CleanImport,
TestSkipped, run_unittest)
import warnings import warnings
from contextlib import nested from contextlib import nested
...@@ -8,15 +9,22 @@ from contextlib import nested ...@@ -8,15 +9,22 @@ from contextlib import nested
if not sys.py3kwarning: if not sys.py3kwarning:
raise TestSkipped('%s must be run with the -3 flag' % __name__) raise TestSkipped('%s must be run with the -3 flag' % __name__)
def reset_module_registry(module):
try:
registry = module.__warningregistry__
except AttributeError:
pass
else:
registry.clear()
class TestPy3KWarnings(unittest.TestCase): class TestPy3KWarnings(unittest.TestCase):
def assertWarning(self, _, warning, expected_message): def assertWarning(self, _, warning, expected_message):
self.assertEqual(str(warning[-1].message), expected_message) self.assertEqual(str(warning.message), expected_message)
def test_backquote(self): def test_backquote(self):
expected = 'backquote not supported in 3.x; use repr()' expected = 'backquote not supported in 3.x; use repr()'
with warnings.catch_warnings(record=True) as w: with check_warnings() as w:
exec "`2`" in {} exec "`2`" in {}
self.assertWarning(None, w, expected) self.assertWarning(None, w, expected)
...@@ -27,55 +35,71 @@ class TestPy3KWarnings(unittest.TestCase): ...@@ -27,55 +35,71 @@ class TestPy3KWarnings(unittest.TestCase):
exec expr in {'f' : f} exec expr in {'f' : f}
expected = "assignment to True or False is forbidden in 3.x" expected = "assignment to True or False is forbidden in 3.x"
with warnings.catch_warnings(record=True) as w: with check_warnings() as w:
safe_exec("True = False") safe_exec("True = False")
self.assertWarning(None, w, expected) self.assertWarning(None, w, expected)
w.reset()
safe_exec("False = True") safe_exec("False = True")
self.assertWarning(None, w, expected) self.assertWarning(None, w, expected)
w.reset()
try: try:
safe_exec("obj.False = True") safe_exec("obj.False = True")
except NameError: pass except NameError: pass
self.assertWarning(None, w, expected) self.assertWarning(None, w, expected)
w.reset()
try: try:
safe_exec("obj.True = False") safe_exec("obj.True = False")
except NameError: pass except NameError: pass
self.assertWarning(None, w, expected) self.assertWarning(None, w, expected)
w.reset()
safe_exec("def False(): pass") safe_exec("def False(): pass")
self.assertWarning(None, w, expected) self.assertWarning(None, w, expected)
w.reset()
safe_exec("def True(): pass") safe_exec("def True(): pass")
self.assertWarning(None, w, expected) self.assertWarning(None, w, expected)
w.reset()
safe_exec("class False: pass") safe_exec("class False: pass")
self.assertWarning(None, w, expected) self.assertWarning(None, w, expected)
w.reset()
safe_exec("class True: pass") safe_exec("class True: pass")
self.assertWarning(None, w, expected) self.assertWarning(None, w, expected)
w.reset()
safe_exec("def f(True=43): pass") safe_exec("def f(True=43): pass")
self.assertWarning(None, w, expected) self.assertWarning(None, w, expected)
w.reset()
safe_exec("def f(False=None): pass") safe_exec("def f(False=None): pass")
self.assertWarning(None, w, expected) self.assertWarning(None, w, expected)
w.reset()
safe_exec("f(False=True)") safe_exec("f(False=True)")
self.assertWarning(None, w, expected) self.assertWarning(None, w, expected)
w.reset()
safe_exec("f(True=1)") safe_exec("f(True=1)")
self.assertWarning(None, w, expected) self.assertWarning(None, w, expected)
def test_type_inequality_comparisons(self): def test_type_inequality_comparisons(self):
expected = 'type inequality comparisons not supported in 3.x' expected = 'type inequality comparisons not supported in 3.x'
with warnings.catch_warnings(record=True) as w: with check_warnings() as w:
self.assertWarning(int < str, w, expected) self.assertWarning(int < str, w, expected)
w.reset()
self.assertWarning(type < object, w, expected) self.assertWarning(type < object, w, expected)
def test_object_inequality_comparisons(self): def test_object_inequality_comparisons(self):
expected = 'comparing unequal types not supported in 3.x' expected = 'comparing unequal types not supported in 3.x'
with warnings.catch_warnings(record=True) as w: with check_warnings() as w:
self.assertWarning(str < [], w, expected) self.assertWarning(str < [], w, expected)
w.reset()
self.assertWarning(object() < (1, 2), w, expected) self.assertWarning(object() < (1, 2), w, expected)
def test_dict_inequality_comparisons(self): def test_dict_inequality_comparisons(self):
expected = 'dict inequality comparisons not supported in 3.x' expected = 'dict inequality comparisons not supported in 3.x'
with warnings.catch_warnings(record=True) as w: with check_warnings() as w:
self.assertWarning({} < {2:3}, w, expected) self.assertWarning({} < {2:3}, w, expected)
w.reset()
self.assertWarning({} <= {}, w, expected) self.assertWarning({} <= {}, w, expected)
w.reset()
self.assertWarning({} > {2:3}, w, expected) self.assertWarning({} > {2:3}, w, expected)
w.reset()
self.assertWarning({2:3} >= {}, w, expected) self.assertWarning({2:3} >= {}, w, expected)
def test_cell_inequality_comparisons(self): def test_cell_inequality_comparisons(self):
...@@ -86,8 +110,9 @@ class TestPy3KWarnings(unittest.TestCase): ...@@ -86,8 +110,9 @@ class TestPy3KWarnings(unittest.TestCase):
return g return g
cell0, = f(0).func_closure cell0, = f(0).func_closure
cell1, = f(1).func_closure cell1, = f(1).func_closure
with warnings.catch_warnings(record=True) as w: with check_warnings() as w:
self.assertWarning(cell0 == cell1, w, expected) self.assertWarning(cell0 == cell1, w, expected)
w.reset()
self.assertWarning(cell0 < cell1, w, expected) self.assertWarning(cell0 < cell1, w, expected)
def test_code_inequality_comparisons(self): def test_code_inequality_comparisons(self):
...@@ -96,10 +121,13 @@ class TestPy3KWarnings(unittest.TestCase): ...@@ -96,10 +121,13 @@ class TestPy3KWarnings(unittest.TestCase):
pass pass
def g(x): def g(x):
pass pass
with warnings.catch_warnings(record=True) as w: with check_warnings() as w:
self.assertWarning(f.func_code < g.func_code, w, expected) self.assertWarning(f.func_code < g.func_code, w, expected)
w.reset()
self.assertWarning(f.func_code <= g.func_code, w, expected) self.assertWarning(f.func_code <= g.func_code, w, expected)
w.reset()
self.assertWarning(f.func_code >= g.func_code, w, expected) self.assertWarning(f.func_code >= g.func_code, w, expected)
w.reset()
self.assertWarning(f.func_code > g.func_code, w, expected) self.assertWarning(f.func_code > g.func_code, w, expected)
def test_builtin_function_or_method_comparisons(self): def test_builtin_function_or_method_comparisons(self):
...@@ -107,10 +135,13 @@ class TestPy3KWarnings(unittest.TestCase): ...@@ -107,10 +135,13 @@ class TestPy3KWarnings(unittest.TestCase):
'inequality comparisons not supported in 3.x') 'inequality comparisons not supported in 3.x')
func = eval func = eval
meth = {}.get meth = {}.get
with warnings.catch_warnings(record=True) as w: with check_warnings() as w:
self.assertWarning(func < meth, w, expected) self.assertWarning(func < meth, w, expected)
w.reset()
self.assertWarning(func > meth, w, expected) self.assertWarning(func > meth, w, expected)
w.reset()
self.assertWarning(meth <= func, w, expected) self.assertWarning(meth <= func, w, expected)
w.reset()
self.assertWarning(meth >= func, w, expected) self.assertWarning(meth >= func, w, expected)
def test_sort_cmp_arg(self): def test_sort_cmp_arg(self):
...@@ -118,15 +149,18 @@ class TestPy3KWarnings(unittest.TestCase): ...@@ -118,15 +149,18 @@ class TestPy3KWarnings(unittest.TestCase):
lst = range(5) lst = range(5)
cmp = lambda x,y: -1 cmp = lambda x,y: -1
with warnings.catch_warnings(record=True) as w: with check_warnings() as w:
self.assertWarning(lst.sort(cmp=cmp), w, expected) self.assertWarning(lst.sort(cmp=cmp), w, expected)
w.reset()
self.assertWarning(sorted(lst, cmp=cmp), w, expected) self.assertWarning(sorted(lst, cmp=cmp), w, expected)
w.reset()
self.assertWarning(lst.sort(cmp), w, expected) self.assertWarning(lst.sort(cmp), w, expected)
w.reset()
self.assertWarning(sorted(lst, cmp), w, expected) self.assertWarning(sorted(lst, cmp), w, expected)
def test_sys_exc_clear(self): def test_sys_exc_clear(self):
expected = 'sys.exc_clear() not supported in 3.x; use except clauses' expected = 'sys.exc_clear() not supported in 3.x; use except clauses'
with warnings.catch_warnings(record=True) as w: with check_warnings() as w:
self.assertWarning(sys.exc_clear(), w, expected) self.assertWarning(sys.exc_clear(), w, expected)
def test_methods_members(self): def test_methods_members(self):
...@@ -135,17 +169,17 @@ class TestPy3KWarnings(unittest.TestCase): ...@@ -135,17 +169,17 @@ class TestPy3KWarnings(unittest.TestCase):
__methods__ = ['a'] __methods__ = ['a']
__members__ = ['b'] __members__ = ['b']
c = C() c = C()
with warnings.catch_warnings(record=True) as w: with check_warnings() as w:
self.assertWarning(dir(c), w, expected) self.assertWarning(dir(c), w, expected)
def test_softspace(self): def test_softspace(self):
expected = 'file.softspace not supported in 3.x' expected = 'file.softspace not supported in 3.x'
with file(__file__) as f: with file(__file__) as f:
with warnings.catch_warnings(record=True) as w: with check_warnings() as w:
self.assertWarning(f.softspace, w, expected) self.assertWarning(f.softspace, w, expected)
def set(): def set():
f.softspace = 0 f.softspace = 0
with warnings.catch_warnings(record=True) as w: with check_warnings() as w:
self.assertWarning(set(), w, expected) self.assertWarning(set(), w, expected)
def test_slice_methods(self): def test_slice_methods(self):
...@@ -161,59 +195,60 @@ class TestPy3KWarnings(unittest.TestCase): ...@@ -161,59 +195,60 @@ class TestPy3KWarnings(unittest.TestCase):
expected = "in 3.x, __{0}slice__ has been removed; use __{0}item__" expected = "in 3.x, __{0}slice__ has been removed; use __{0}item__"
for obj in (Spam(), Egg()): for obj in (Spam(), Egg()):
with warnings.catch_warnings(record=True) as w: with check_warnings() as w:
self.assertWarning(obj[1:2], w, expected.format('get')) self.assertWarning(obj[1:2], w, expected.format('get'))
w.reset()
del obj[3:4] del obj[3:4]
self.assertWarning(None, w, expected.format('del')) self.assertWarning(None, w, expected.format('del'))
w.reset()
obj[4:5] = "eggs" obj[4:5] = "eggs"
self.assertWarning(None, w, expected.format('set')) self.assertWarning(None, w, expected.format('set'))
def test_tuple_parameter_unpacking(self): def test_tuple_parameter_unpacking(self):
expected = "tuple parameter unpacking has been removed in 3.x" expected = "tuple parameter unpacking has been removed in 3.x"
with warnings.catch_warnings(record=True) as w: with check_warnings() as w:
exec "def f((a, b)): pass" exec "def f((a, b)): pass"
self.assertWarning(None, w, expected) self.assertWarning(None, w, expected)
def test_buffer(self): def test_buffer(self):
expected = 'buffer() not supported in 3.x' expected = 'buffer() not supported in 3.x'
with warnings.catch_warnings(record=True) as w: with check_warnings() as w:
self.assertWarning(buffer('a'), w, expected) self.assertWarning(buffer('a'), w, expected)
def test_file_xreadlines(self): def test_file_xreadlines(self):
expected = ("f.xreadlines() not supported in 3.x, " expected = ("f.xreadlines() not supported in 3.x, "
"try 'for line in f' instead") "try 'for line in f' instead")
with file(__file__) as f: with file(__file__) as f:
with warnings.catch_warnings(record=True) as w: with check_warnings() as w:
self.assertWarning(f.xreadlines(), w, expected) self.assertWarning(f.xreadlines(), w, expected)
def test_hash_inheritance(self): def test_hash_inheritance(self):
with warnings.catch_warnings(record=True) as w: with check_warnings() as w:
# With object as the base class # With object as the base class
class WarnOnlyCmp(object): class WarnOnlyCmp(object):
def __cmp__(self, other): pass def __cmp__(self, other): pass
self.assertEqual(len(w), 1) self.assertEqual(len(w.warnings), 1)
self.assertWarning(None, w, self.assertWarning(None, w,
"Overriding __cmp__ blocks inheritance of __hash__ in 3.x") "Overriding __cmp__ blocks inheritance of __hash__ in 3.x")
del w[:] w.reset()
class WarnOnlyEq(object): class WarnOnlyEq(object):
def __eq__(self, other): pass def __eq__(self, other): pass
self.assertEqual(len(w), 1) self.assertEqual(len(w.warnings), 1)
self.assertWarning(None, w, self.assertWarning(None, w,
"Overriding __eq__ blocks inheritance of __hash__ in 3.x") "Overriding __eq__ blocks inheritance of __hash__ in 3.x")
del w[:] w.reset()
class WarnCmpAndEq(object): class WarnCmpAndEq(object):
def __cmp__(self, other): pass def __cmp__(self, other): pass
def __eq__(self, other): pass def __eq__(self, other): pass
self.assertEqual(len(w), 2) self.assertEqual(len(w.warnings), 2)
self.assertWarning(None, w[:1], self.assertWarning(None, w.warnings[0],
"Overriding __cmp__ blocks inheritance of __hash__ in 3.x") "Overriding __cmp__ blocks inheritance of __hash__ in 3.x")
self.assertWarning(None, w, self.assertWarning(None, w,
"Overriding __eq__ blocks inheritance of __hash__ in 3.x") "Overriding __eq__ blocks inheritance of __hash__ in 3.x")
del w[:] w.reset()
class NoWarningOnlyHash(object): class NoWarningOnlyHash(object):
def __hash__(self): pass def __hash__(self): pass
self.assertEqual(len(w), 0) self.assertEqual(len(w.warnings), 0)
del w[:]
# With an intermediate class in the heirarchy # With an intermediate class in the heirarchy
class DefinesAllThree(object): class DefinesAllThree(object):
def __cmp__(self, other): pass def __cmp__(self, other): pass
...@@ -221,28 +256,28 @@ class TestPy3KWarnings(unittest.TestCase): ...@@ -221,28 +256,28 @@ class TestPy3KWarnings(unittest.TestCase):
def __hash__(self): pass def __hash__(self): pass
class WarnOnlyCmp(DefinesAllThree): class WarnOnlyCmp(DefinesAllThree):
def __cmp__(self, other): pass def __cmp__(self, other): pass
self.assertEqual(len(w), 1) self.assertEqual(len(w.warnings), 1)
self.assertWarning(None, w, self.assertWarning(None, w,
"Overriding __cmp__ blocks inheritance of __hash__ in 3.x") "Overriding __cmp__ blocks inheritance of __hash__ in 3.x")
del w[:] w.reset()
class WarnOnlyEq(DefinesAllThree): class WarnOnlyEq(DefinesAllThree):
def __eq__(self, other): pass def __eq__(self, other): pass
self.assertEqual(len(w), 1) self.assertEqual(len(w.warnings), 1)
self.assertWarning(None, w, self.assertWarning(None, w,
"Overriding __eq__ blocks inheritance of __hash__ in 3.x") "Overriding __eq__ blocks inheritance of __hash__ in 3.x")
del w[:] w.reset()
class WarnCmpAndEq(DefinesAllThree): class WarnCmpAndEq(DefinesAllThree):
def __cmp__(self, other): pass def __cmp__(self, other): pass
def __eq__(self, other): pass def __eq__(self, other): pass
self.assertEqual(len(w), 2) self.assertEqual(len(w.warnings), 2)
self.assertWarning(None, w[:1], self.assertWarning(None, w.warnings[0],
"Overriding __cmp__ blocks inheritance of __hash__ in 3.x") "Overriding __cmp__ blocks inheritance of __hash__ in 3.x")
self.assertWarning(None, w, self.assertWarning(None, w,
"Overriding __eq__ blocks inheritance of __hash__ in 3.x") "Overriding __eq__ blocks inheritance of __hash__ in 3.x")
del w[:] w.reset()
class NoWarningOnlyHash(DefinesAllThree): class NoWarningOnlyHash(DefinesAllThree):
def __hash__(self): pass def __hash__(self): pass
self.assertEqual(len(w), 0) self.assertEqual(len(w.warnings), 0)
class TestStdlibRemovals(unittest.TestCase): class TestStdlibRemovals(unittest.TestCase):
...@@ -283,6 +318,9 @@ class TestStdlibRemovals(unittest.TestCase): ...@@ -283,6 +318,9 @@ class TestStdlibRemovals(unittest.TestCase):
"""Make sure the specified module, when imported, raises a """Make sure the specified module, when imported, raises a
DeprecationWarning and specifies itself in the message.""" DeprecationWarning and specifies itself in the message."""
with nested(CleanImport(module_name), warnings.catch_warnings()): with nested(CleanImport(module_name), warnings.catch_warnings()):
# XXX: This is not quite enough for extension modules - those
# won't rerun their init code even with CleanImport.
# You can see this easily by running the whole test suite with -3
warnings.filterwarnings("error", ".+ removed", warnings.filterwarnings("error", ".+ removed",
DeprecationWarning, __name__) DeprecationWarning, __name__)
try: try:
...@@ -320,12 +358,15 @@ class TestStdlibRemovals(unittest.TestCase): ...@@ -320,12 +358,15 @@ class TestStdlibRemovals(unittest.TestCase):
def dumbo(where, names, args): pass def dumbo(where, names, args): pass
for path_mod in ("ntpath", "macpath", "os2emxpath", "posixpath"): for path_mod in ("ntpath", "macpath", "os2emxpath", "posixpath"):
mod = __import__(path_mod) mod = __import__(path_mod)
with warnings.catch_warnings(record=True) as w: reset_module_registry(mod)
with check_warnings() as w:
mod.walk("crashers", dumbo, None) mod.walk("crashers", dumbo, None)
self.assertEquals(str(w[-1].message), msg) self.assertEquals(str(w.message), msg)
def test_commands_members(self): def test_commands_members(self):
import commands import commands
# commands module tests may have already triggered this warning
reset_module_registry(commands)
members = {"mk2arg" : 2, "mkarg" : 1, "getstatus" : 1} members = {"mk2arg" : 2, "mkarg" : 1, "getstatus" : 1}
for name, arg_count in members.items(): for name, arg_count in members.items():
with warnings.catch_warnings(): with warnings.catch_warnings():
...@@ -335,6 +376,8 @@ class TestStdlibRemovals(unittest.TestCase): ...@@ -335,6 +376,8 @@ class TestStdlibRemovals(unittest.TestCase):
def test_reduce_move(self): def test_reduce_move(self):
from operator import add from operator import add
# reduce tests may have already triggered this warning
reset_module_registry(unittest)
with warnings.catch_warnings(): with warnings.catch_warnings():
warnings.filterwarnings("error", "reduce") warnings.filterwarnings("error", "reduce")
self.assertRaises(DeprecationWarning, reduce, add, range(10)) self.assertRaises(DeprecationWarning, reduce, add, range(10))
...@@ -342,6 +385,8 @@ class TestStdlibRemovals(unittest.TestCase): ...@@ -342,6 +385,8 @@ class TestStdlibRemovals(unittest.TestCase):
def test_mutablestring_removal(self): def test_mutablestring_removal(self):
# UserString.MutableString has been removed in 3.0. # UserString.MutableString has been removed in 3.0.
import UserString import UserString
# UserString tests may have already triggered this warning
reset_module_registry(UserString)
with warnings.catch_warnings(): with warnings.catch_warnings():
warnings.filterwarnings("error", ".*MutableString", warnings.filterwarnings("error", ".*MutableString",
DeprecationWarning) DeprecationWarning)
...@@ -349,7 +394,7 @@ class TestStdlibRemovals(unittest.TestCase): ...@@ -349,7 +394,7 @@ class TestStdlibRemovals(unittest.TestCase):
def test_main(): def test_main():
with warnings.catch_warnings(): with check_warnings():
warnings.simplefilter("always") warnings.simplefilter("always")
run_unittest(TestPy3KWarnings, run_unittest(TestPy3KWarnings,
TestStdlibRemovals) TestStdlibRemovals)
......
...@@ -66,35 +66,35 @@ class ReadWriteTests(unittest.TestCase): ...@@ -66,35 +66,35 @@ class ReadWriteTests(unittest.TestCase):
class TestWarnings(unittest.TestCase): class TestWarnings(unittest.TestCase):
def has_warned(self, w): def has_warned(self, w):
self.assertEqual(w[-1].category, RuntimeWarning) self.assertEqual(w.category, RuntimeWarning)
def test_byte_max(self): def test_byte_max(self):
with warnings.catch_warnings(record=True) as w: with test_support.check_warnings() as w:
ts.T_BYTE = CHAR_MAX+1 ts.T_BYTE = CHAR_MAX+1
self.has_warned(w) self.has_warned(w)
def test_byte_min(self): def test_byte_min(self):
with warnings.catch_warnings(record=True) as w: with test_support.check_warnings() as w:
ts.T_BYTE = CHAR_MIN-1 ts.T_BYTE = CHAR_MIN-1
self.has_warned(w) self.has_warned(w)
def test_ubyte_max(self): def test_ubyte_max(self):
with warnings.catch_warnings(record=True) as w: with test_support.check_warnings() as w:
ts.T_UBYTE = UCHAR_MAX+1 ts.T_UBYTE = UCHAR_MAX+1
self.has_warned(w) self.has_warned(w)
def test_short_max(self): def test_short_max(self):
with warnings.catch_warnings(record=True) as w: with test_support.check_warnings() as w:
ts.T_SHORT = SHRT_MAX+1 ts.T_SHORT = SHRT_MAX+1
self.has_warned(w) self.has_warned(w)
def test_short_min(self): def test_short_min(self):
with warnings.catch_warnings(record=True) as w: with test_support.check_warnings() as w:
ts.T_SHORT = SHRT_MIN-1 ts.T_SHORT = SHRT_MIN-1
self.has_warned(w) self.has_warned(w)
def test_ushort_max(self): def test_ushort_max(self):
with warnings.catch_warnings(record=True) as w: with test_support.check_warnings() as w:
ts.T_USHORT = USHRT_MAX+1 ts.T_USHORT = USHRT_MAX+1
self.has_warned(w) self.has_warned(w)
......
...@@ -8,7 +8,7 @@ import warnings ...@@ -8,7 +8,7 @@ import warnings
class TestUntestedModules(unittest.TestCase): class TestUntestedModules(unittest.TestCase):
def test_at_least_import_untested_modules(self): def test_at_least_import_untested_modules(self):
with warnings.catch_warnings(record=True): with warnings.catch_warnings():
import CGIHTTPServer import CGIHTTPServer
import aifc import aifc
import audiodev import audiodev
......
...@@ -18,7 +18,7 @@ __all__ = ["Error", "TestFailed", "TestSkipped", "ResourceDenied", "import_modul ...@@ -18,7 +18,7 @@ __all__ = ["Error", "TestFailed", "TestSkipped", "ResourceDenied", "import_modul
"is_resource_enabled", "requires", "find_unused_port", "bind_port", "is_resource_enabled", "requires", "find_unused_port", "bind_port",
"fcmp", "have_unicode", "is_jython", "TESTFN", "HOST", "FUZZ", "fcmp", "have_unicode", "is_jython", "TESTFN", "HOST", "FUZZ",
"findfile", "verify", "vereq", "sortdict", "check_syntax_error", "findfile", "verify", "vereq", "sortdict", "check_syntax_error",
"open_urlresource", "CleanImport", "open_urlresource", "check_warnings", "CleanImport",
"EnvironmentVarGuard", "captured_output", "EnvironmentVarGuard", "captured_output",
"captured_stdout", "TransientResource", "transient_internet", "captured_stdout", "TransientResource", "transient_internet",
"run_with_locale", "set_memlimit", "bigmemtest", "bigaddrspacetest", "run_with_locale", "set_memlimit", "bigmemtest", "bigaddrspacetest",
...@@ -381,6 +381,29 @@ def open_urlresource(url): ...@@ -381,6 +381,29 @@ def open_urlresource(url):
return open(fn) return open(fn)
class WarningsRecorder(object):
"""Convenience wrapper for the warnings list returned on
entry to the warnings.catch_warnings() context manager.
"""
def __init__(self, warnings_list):
self.warnings = warnings_list
def __getattr__(self, attr):
if self.warnings:
return getattr(self.warnings[-1], attr)
elif attr in warnings.WarningMessage._WARNING_DETAILS:
return None
raise AttributeError("%r has no attribute %r" % (self, attr))
def reset(self):
del self.warnings[:]
@contextlib.contextmanager
def check_warnings():
with warnings.catch_warnings(record=True) as w:
yield WarningsRecorder(w)
class CleanImport(object): 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.
......
...@@ -60,16 +60,16 @@ class SymtableTest(unittest.TestCase): ...@@ -60,16 +60,16 @@ class SymtableTest(unittest.TestCase):
def check(w, msg): def check(w, msg):
self.assertEqual(str(w.message), msg) self.assertEqual(str(w.message), msg)
sym = self.top.lookup("glob") sym = self.top.lookup("glob")
with warnings.catch_warnings(record=True) as w: with test_support.check_warnings() as w:
warnings.simplefilter("always", DeprecationWarning) warnings.simplefilter("always", DeprecationWarning)
self.assertFalse(sym.is_vararg()) self.assertFalse(sym.is_vararg())
check(w[-1].message, "is_vararg() is obsolete and will be removed") check(w, "is_vararg() is obsolete and will be removed")
w.reset()
self.assertFalse(sym.is_keywordarg()) self.assertFalse(sym.is_keywordarg())
check(w[-1].message, check(w, "is_keywordarg() is obsolete and will be removed")
"is_keywordarg() is obsolete and will be removed") w.reset()
self.assertFalse(sym.is_in_tuple()) self.assertFalse(sym.is_in_tuple())
check(w[-1].message, check(w, "is_in_tuple() is obsolete and will be removed")
"is_in_tuple() is obsolete and will be removed")
def test_type(self): def test_type(self):
self.assertEqual(self.top.get_type(), "module") self.assertEqual(self.top.get_type(), "module")
......
...@@ -517,10 +517,12 @@ class CatchWarningTests(BaseTest): ...@@ -517,10 +517,12 @@ class CatchWarningTests(BaseTest):
wmod = self.module wmod = self.module
orig_filters = wmod.filters orig_filters = wmod.filters
orig_showwarning = wmod.showwarning orig_showwarning = wmod.showwarning
with wmod.catch_warnings(record=True, module=wmod): # Ensure both showwarning and filters are restored when recording
with wmod.catch_warnings(module=wmod, record=True):
wmod.filters = wmod.showwarning = object() wmod.filters = wmod.showwarning = object()
self.assert_(wmod.filters is orig_filters) self.assert_(wmod.filters is orig_filters)
self.assert_(wmod.showwarning is orig_showwarning) self.assert_(wmod.showwarning is orig_showwarning)
# Same test, but with recording disabled
with wmod.catch_warnings(module=wmod, record=False): with wmod.catch_warnings(module=wmod, record=False):
wmod.filters = wmod.showwarning = object() wmod.filters = wmod.showwarning = object()
self.assert_(wmod.filters is orig_filters) self.assert_(wmod.filters is orig_filters)
...@@ -528,9 +530,10 @@ class CatchWarningTests(BaseTest): ...@@ -528,9 +530,10 @@ class CatchWarningTests(BaseTest):
def test_catch_warnings_recording(self): def test_catch_warnings_recording(self):
wmod = self.module wmod = self.module
# Ensure warnings are recorded when requested
with wmod.catch_warnings(module=wmod, record=True) as w: with wmod.catch_warnings(module=wmod, record=True) as w:
self.assertEqual(w, []) self.assertEqual(w, [])
self.assertRaises(AttributeError, getattr, w, 'message') self.assert_(type(w) is list)
wmod.simplefilter("always") wmod.simplefilter("always")
wmod.warn("foo") wmod.warn("foo")
self.assertEqual(str(w[-1].message), "foo") self.assertEqual(str(w[-1].message), "foo")
...@@ -540,11 +543,61 @@ class CatchWarningTests(BaseTest): ...@@ -540,11 +543,61 @@ class CatchWarningTests(BaseTest):
self.assertEqual(str(w[1].message), "bar") self.assertEqual(str(w[1].message), "bar")
del w[:] del w[:]
self.assertEqual(w, []) self.assertEqual(w, [])
# Ensure warnings are not recorded when not requested
orig_showwarning = wmod.showwarning orig_showwarning = wmod.showwarning
with wmod.catch_warnings(module=wmod, record=False) as w: with wmod.catch_warnings(module=wmod, record=False) as w:
self.assert_(w is None) self.assert_(w is None)
self.assert_(wmod.showwarning is orig_showwarning) self.assert_(wmod.showwarning is orig_showwarning)
def test_catch_warnings_reentry_guard(self):
wmod = self.module
# Ensure catch_warnings is protected against incorrect usage
x = wmod.catch_warnings(module=wmod, record=True)
self.assertRaises(RuntimeError, x.__exit__)
with x:
self.assertRaises(RuntimeError, x.__enter__)
# Same test, but with recording disabled
x = wmod.catch_warnings(module=wmod, record=False)
self.assertRaises(RuntimeError, x.__exit__)
with x:
self.assertRaises(RuntimeError, x.__enter__)
def test_catch_warnings_defaults(self):
wmod = self.module
orig_filters = wmod.filters
orig_showwarning = wmod.showwarning
# Ensure default behaviour is not to record warnings
with wmod.catch_warnings(module=wmod) as w:
self.assert_(w is None)
self.assert_(wmod.showwarning is orig_showwarning)
self.assert_(wmod.filters is not orig_filters)
self.assert_(wmod.filters is orig_filters)
if wmod is sys.modules['warnings']:
# Ensure the default module is this one
with wmod.catch_warnings() as w:
self.assert_(w is None)
self.assert_(wmod.showwarning is orig_showwarning)
self.assert_(wmod.filters is not orig_filters)
self.assert_(wmod.filters is orig_filters)
def test_check_warnings(self):
# Explicit tests for the test_support convenience wrapper
wmod = self.module
if wmod is sys.modules['warnings']:
with test_support.check_warnings() 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, [])
class CCatchWarningTests(CatchWarningTests): class CCatchWarningTests(CatchWarningTests):
module = c_warnings module = c_warnings
......
...@@ -331,8 +331,21 @@ class catch_warnings(object): ...@@ -331,8 +331,21 @@ class catch_warnings(object):
""" """
self._record = record self._record = record
self._module = sys.modules['warnings'] if module is None else module self._module = sys.modules['warnings'] if module is None else module
self._entered = False
def __repr__(self):
args = []
if self._record:
args.append("record=True")
if self._module is not sys.modules['warnings']:
args.append("module=%r" % self._module)
name = type(self).__name__
return "%s(%s)" % (name, ", ".join(args))
def __enter__(self): def __enter__(self):
if self._entered:
raise RuntimeError("Cannot enter %r twice" % self)
self._entered = True
self._filters = self._module.filters self._filters = self._module.filters
self._module.filters = self._filters[:] self._module.filters = self._filters[:]
self._showwarning = self._module.showwarning self._showwarning = self._module.showwarning
...@@ -346,6 +359,8 @@ class catch_warnings(object): ...@@ -346,6 +359,8 @@ class catch_warnings(object):
return None return None
def __exit__(self, *exc_info): def __exit__(self, *exc_info):
if not self._entered:
raise RuntimeError("Cannot exit %r without entering first" % self)
self._module.filters = self._filters self._module.filters = self._filters
self._module.showwarning = self._showwarning self._module.showwarning = self._showwarning
......
...@@ -79,9 +79,13 @@ Library ...@@ -79,9 +79,13 @@ Library
- Issue #3811: The Unicode database was updated to 5.1. - Issue #3811: The Unicode database was updated to 5.1.
- Issue #3781: Further warnings.catch_warnings() cleanup to prevent
silent misbehaviour when a single instance is nested in multiple
with statements, or when the methods are invoked in the wrong order.
- Issue #3809: Fixed spurious 'test.blah' file left behind by test_logging. - Issue #3809: Fixed spurious 'test.blah' file left behind by test_logging.
- Issue 3781: Clean up the API for warnings.catch_warnings() by having it - Issue #3781: Clean up the API for warnings.catch_warnings() by having it
return a list or None rather than a custom object. return a list or None rather than a custom object.
- Issue #1638033: Cookie.Morsel gained the httponly attribute. - Issue #1638033: Cookie.Morsel gained the httponly attribute.
...@@ -142,6 +146,10 @@ Extension Modules ...@@ -142,6 +146,10 @@ Extension Modules
Tests Tests
----- -----
- Issue #3781: Add test.test_support.check_warnings() as a convenience
wrapper for warnings.catch_warnings() that makes it easier to check
that expected warning messages are being reported.
- Issue #3796: Some tests functions were not enabled in test_float. - Issue #3796: Some tests functions were not enabled in test_float.
- Issue #3768: Move test_py3kwarn over to the new API for catch_warnings(). - Issue #3768: Move test_py3kwarn over to the new API for catch_warnings().
......
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