diff --git a/Lib/test/string_tests.py b/Lib/test/string_tests.py index db0571fa117..7cfe65be35e 100644 --- a/Lib/test/string_tests.py +++ b/Lib/test/string_tests.py @@ -715,8 +715,7 @@ class CommonTest(unittest.TestCase): EQ("bobobXbobob", "bobobobXbobobob", "replace", "bobob", "bob") EQ("BOBOBOB", "BOBOBOB", "replace", "bob", "bobby") - # Silence Py3k warning - with test_support.check_warnings(): + with test_support.check_py3k_warnings(): ba = buffer('a') bb = buffer('b') EQ("bbc", "abc", "replace", ba, bb) diff --git a/Lib/test/test_ascii_formatd.py b/Lib/test/test_ascii_formatd.py index 77759fe07bc..50e7340ca87 100644 --- a/Lib/test/test_ascii_formatd.py +++ b/Lib/test/test_ascii_formatd.py @@ -17,14 +17,11 @@ class FormatDeprecationTests(unittest.TestCase): PyOS_ascii_formatd = pythonapi.PyOS_ascii_formatd buf = create_string_buffer(' ' * 100) - with check_warnings() as w: - warnings.simplefilter('default') + with check_warnings(): PyOS_ascii_formatd(byref(buf), sizeof(buf), '%+.10f', c_double(10.0)) self.assertEqual(buf.value, '+10.0000000000') - self.assertEqual(w.category, DeprecationWarning) - class FormatTests(unittest.TestCase): # ensure that, for the restricted set of format codes, # %-formatting returns the same values os PyOS_ascii_formatd diff --git a/Lib/test/test_fileio.py b/Lib/test/test_fileio.py index 5589d050b09..505794117f5 100644 --- a/Lib/test/test_fileio.py +++ b/Lib/test/test_fileio.py @@ -390,7 +390,7 @@ class OtherFileTests(unittest.TestCase): self.assertRaises(TypeError, _FileIO, "1", 0, 0) def testWarnings(self): - with check_warnings() as w: + with check_warnings(quiet=True) as w: self.assertEqual(w.warnings, []) self.assertRaises(TypeError, _FileIO, []) self.assertEqual(w.warnings, []) diff --git a/Lib/test/test_index.py b/Lib/test/test_index.py index 1a7bd01d632..84d83fba22a 100644 --- a/Lib/test/test_index.py +++ b/Lib/test/test_index.py @@ -279,16 +279,14 @@ class OverflowTestCase(unittest.TestCase): def test_getitem(self): self._getitem_helper(object) - # Silence Py3k warning - with test_support.check_warnings(): + with test_support.check_py3k_warnings(): self._getslice_helper_deprecated(object) def test_getitem_classic(self): class Empty: pass # XXX This test fails (see bug #7532) #self._getitem_helper(Empty) - # Silence Py3k warning - with test_support.check_warnings(): + with test_support.check_py3k_warnings(): self._getslice_helper_deprecated(Empty) def test_sequence_repeat(self): @@ -308,8 +306,7 @@ def test_main(): XRangeTestCase, OverflowTestCase, ) - # Silence Py3k warning - with test_support.check_warnings(): + with test_support.check_py3k_warnings(): test_support.run_unittest( ClassicSeqDeprecatedTestCase, NewSeqDeprecatedTestCase, diff --git a/Lib/test/test_random.py b/Lib/test/test_random.py index b4998b9f81c..94c20ceb9b5 100644 --- a/Lib/test/test_random.py +++ b/Lib/test/test_random.py @@ -53,8 +53,7 @@ class TestBasicOps(unittest.TestCase): state3 = self.gen.getstate() # s/b distinct from state2 self.assertNotEqual(state2, state3) - # Silence py3k warnings - with test_support.check_warnings(): + with test_support.check_py3k_warnings(quiet=True): self.assertRaises(TypeError, self.gen.jumpahead) # needs an arg self.assertRaises(TypeError, self.gen.jumpahead, "ick") # wrong type self.assertRaises(TypeError, self.gen.jumpahead, 2.3) # wrong type diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py index db575332dd4..0824bb3b0d3 100644 --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -16,6 +16,7 @@ import warnings import unittest import importlib import UserDict +import re __all__ = ["Error", "TestFailed", "ResourceDenied", "import_module", "verbose", "use_resources", "max_memuse", "record_original_stdout", @@ -23,8 +24,8 @@ __all__ = ["Error", "TestFailed", "ResourceDenied", "import_module", "is_resource_enabled", "requires", "find_unused_port", "bind_port", "fcmp", "have_unicode", "is_jython", "TESTFN", "HOST", "FUZZ", "SAVEDCWD", "temp_cwd", "findfile", "sortdict", "check_syntax_error", - "open_urlresource", "check_warnings", "CleanImport", - "EnvironmentVarGuard", "captured_output", + "open_urlresource", "check_warnings", "check_py3k_warnings", + "CleanImport", "EnvironmentVarGuard", "captured_output", "captured_stdout", "TransientResource", "transient_internet", "run_with_locale", "set_memlimit", "bigmemtest", "bigaddrspacetest", "BasicTestRunner", "run_unittest", "run_doctest", "threading_setup", @@ -488,22 +489,103 @@ class WarningsRecorder(object): entry to the warnings.catch_warnings() context manager. """ def __init__(self, warnings_list): - self.warnings = warnings_list + self._warnings = warnings_list + self._last = 0 def __getattr__(self, attr): - if self.warnings: - return getattr(self.warnings[-1], attr) + if len(self._warnings) > self._last: + return getattr(self._warnings[-1], attr) elif attr in warnings.WarningMessage._WARNING_DETAILS: return None raise AttributeError("%r has no attribute %r" % (self, attr)) + @property + def warnings(self): + return self._warnings[self._last:] + def reset(self): - del self.warnings[:] + self._last = len(self._warnings) + + +def _filterwarnings(filters, quiet=False): + """Catch the warnings, then check if all the expected + warnings have been raised and re-raise unexpected warnings. + If 'quiet' is True, only re-raise the unexpected warnings. + """ + # Clear the warning registry of the calling module + # in order to re-raise the warnings. + frame = sys._getframe(2) + registry = frame.f_globals.get('__warningregistry__') + if registry: + registry.clear() + with warnings.catch_warnings(record=True) as w: + # Disable filters, to record all warnings. Because + # test_warnings swap the module, we need to look up + # in the sys.modules dictionary. + sys.modules['warnings'].resetwarnings() + yield WarningsRecorder(w) + # Filter the recorded warnings + reraise = [warning.message for warning in w] + missing = [] + for msg, cat in filters: + seen = False + for exc in reraise[:]: + message = str(exc) + # Filter out the matching messages + if (re.match(msg, message, re.I) and + issubclass(exc.__class__, cat)): + seen = True + reraise.remove(exc) + if not seen and not quiet: + # This filter caught nothing + missing.append((msg, cat.__name__)) + for exc in reraise: + raise AssertionError("unhandled warning %r" % exc) + for filter in missing: + raise AssertionError("filter (%r, %s) did not caught any warning" % + filter) + @contextlib.contextmanager -def check_warnings(): - with warnings.catch_warnings(record=True) as w: - yield WarningsRecorder(w) +def check_warnings(*filters, **kwargs): + """Context manager to silence warnings. + + Accept 2-tuples as positional arguments: + ("message regexp", WarningCategory) + + Optional argument: + - if 'quiet' is True, it does not fail if a filter catches nothing + (default False) + + Without argument, it defaults to: + check_warnings(("", Warning), quiet=False) + """ + if not filters: + filters = (("", Warning),) + return _filterwarnings(filters, kwargs.get('quiet')) + + +@contextlib.contextmanager +def check_py3k_warnings(*filters, **kwargs): + """Context manager to silence py3k warnings. + + Accept 2-tuples as positional arguments: + ("message regexp", WarningCategory) + + Optional argument: + - if 'quiet' is True, it does not fail if a filter catches nothing + (default False) + + Without argument, it defaults to: + check_py3k_warnings(("", DeprecationWarning), quiet=False) + """ + if sys.py3kwarning: + if not filters: + filters = (("", DeprecationWarning),) + else: + # It should not raise any py3k warning + filters = () + return _filterwarnings(filters, kwargs.get('quiet')) class CleanImport(object): @@ -735,7 +817,6 @@ _4G = 4 * _1G MAX_Py_ssize_t = sys.maxsize def set_memlimit(limit): - import re global max_memuse global real_max_memuse sizes = { diff --git a/Lib/test/test_unicode.py b/Lib/test/test_unicode.py index 5eb331a73cb..a859c0faa7e 100644 --- a/Lib/test/test_unicode.py +++ b/Lib/test/test_unicode.py @@ -512,8 +512,7 @@ class UnicodeTest( ) if not sys.platform.startswith('java'): - # Silence Py3k warning - with test_support.check_warnings(): + with test_support.check_py3k_warnings(): buf = buffer('character buffers are decoded to unicode') self.assertEqual( unicode( diff --git a/Misc/NEWS b/Misc/NEWS index 8e982cbdcda..abf113e98ed 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -138,6 +138,9 @@ Extension Modules Tests ----- +- Issue #7849: Now the utility ``check_warnings`` verifies if the warnings are + effectively raised. A new utility ``check_py3k_warnings`` is available. + - The four path modules (genericpath, macpath, ntpath, posixpath) share a common TestCase for some tests: test_genericpath.CommonTest.