[Python-checkins] r64910 - in python/trunk: Doc/library/test.rst Lib/test/test_py3kwarn.py Lib/test/test_struct.py Lib/test/test_support.py Lib/test/test_warnings.py Misc/NEWS

nick.coghlan python-checkins at python.org
Sun Jul 13 14:23:48 CEST 2008


Author: nick.coghlan
Date: Sun Jul 13 14:23:47 2008
New Revision: 64910

Log:
Make test.test_support.catch_warnings more robust as discussed on python-dev. Also add explicit tests for itto test_warnings.

Modified:
   python/trunk/Doc/library/test.rst
   python/trunk/Lib/test/test_py3kwarn.py
   python/trunk/Lib/test/test_struct.py
   python/trunk/Lib/test/test_support.py
   python/trunk/Lib/test/test_warnings.py
   python/trunk/Misc/NEWS

Modified: python/trunk/Doc/library/test.rst
==============================================================================
--- python/trunk/Doc/library/test.rst	(original)
+++ python/trunk/Doc/library/test.rst	Sun Jul 13 14:23:47 2008
@@ -291,19 +291,34 @@
    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 @@
    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 @@
 .. method:: EnvironmentVarGuard.unset(envvar)
 
    Temporarily unset the environment variable ``envvar``.
+
+

Modified: python/trunk/Lib/test/test_py3kwarn.py
==============================================================================
--- python/trunk/Lib/test/test_py3kwarn.py	(original)
+++ python/trunk/Lib/test/test_py3kwarn.py	Sun Jul 13 14:23:47 2008
@@ -26,41 +26,30 @@
         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 @@
         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 @@
         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 @@
             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 @@
         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 @@
 
         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 @@
         """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 @@
 
 
 def test_main():
-    with catch_warning(record=True):
+    with catch_warning():
         warnings.simplefilter("always")
         run_unittest(TestPy3KWarnings,
                      TestStdlibRemovals)

Modified: python/trunk/Lib/test/test_struct.py
==============================================================================
--- python/trunk/Lib/test/test_struct.py	(original)
+++ python/trunk/Lib/test/test_struct.py	Sun Jul 13 14:23:47 2008
@@ -35,12 +35,9 @@
     @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 @@
         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" % (

Modified: python/trunk/Lib/test/test_support.py
==============================================================================
--- python/trunk/Lib/test/test_support.py	(original)
+++ python/trunk/Lib/test/test_support.py	Sun Jul 13 14:23:47 2008
@@ -383,36 +383,49 @@
 
 
 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 @@
             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 @@
     """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:
 

Modified: python/trunk/Lib/test/test_warnings.py
==============================================================================
--- python/trunk/Lib/test/test_warnings.py	(original)
+++ python/trunk/Lib/test/test_warnings.py	Sun Jul 13 14:23:47 2008
@@ -488,6 +488,49 @@
     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 @@
     module = py_warnings
 
 
-
 def test_main():
     py_warnings.onceregistry.clear()
     c_warnings.onceregistry.clear()
@@ -524,6 +566,7 @@
                                 CWCmdLineTests, PyWCmdLineTests,
                                 _WarningsTests,
                                 CWarningsDisplayTests, PyWarningsDisplayTests,
+                                CWarningsSupportTests, PyWarningsSupportTests,
                                 CShowwarningDeprecationTests,
                                 PyShowwarningDeprecationTests,
                              )

Modified: python/trunk/Misc/NEWS
==============================================================================
--- python/trunk/Misc/NEWS	(original)
+++ python/trunk/Misc/NEWS	Sun Jul 13 14:23:47 2008
@@ -82,6 +82,14 @@
   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
 -----
 


More information about the Python-checkins mailing list