summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrett Cannon <bcannon@gmail.com>2008-09-02 01:25:16 (GMT)
committerBrett Cannon <bcannon@gmail.com>2008-09-02 01:25:16 (GMT)
commit1eaf0742d877fd9d84d6ed82a04bc33b027e9ad0 (patch)
tree1c1ee3a5dee04f5f4657b707e9d54ca2e28b1505
parent86533776c291c031853609ceaeda96eb2808e4ee (diff)
downloadcpython-1eaf0742d877fd9d84d6ed82a04bc33b027e9ad0.zip
cpython-1eaf0742d877fd9d84d6ed82a04bc33b027e9ad0.tar.gz
cpython-1eaf0742d877fd9d84d6ed82a04bc33b027e9ad0.tar.bz2
Move test.test_support.catch_warning() to the warnings module, rename it
catch_warnings(), and clean up the API. While expanding the test suite, a bug was found where a warning about the 'line' argument to showwarning() was not letting functions with '*args' go without a warning. Closes issue 3602. Code review by Benjamin Peterson.
-rw-r--r--Doc/library/warnings.rst50
-rw-r--r--Lib/BaseHTTPServer.py10
-rw-r--r--Lib/asynchat.py8
-rwxr-xr-xLib/cgi.py13
-rw-r--r--Lib/httplib.py9
-rw-r--r--Lib/mimetools.py9
-rw-r--r--Lib/test/test_support.py67
-rw-r--r--Lib/test/test_warnings.py74
-rw-r--r--Lib/warnings.py75
-rw-r--r--Misc/NEWS5
-rw-r--r--Python/_warnings.c14
11 files changed, 208 insertions, 126 deletions
diff --git a/Doc/library/warnings.rst b/Doc/library/warnings.rst
index 888cb84..ae3ab68 100644
--- a/Doc/library/warnings.rst
+++ b/Doc/library/warnings.rst
@@ -263,3 +263,53 @@ Available Functions
:func:`filterwarnings`, including that of the :option:`-W` command line options
and calls to :func:`simplefilter`.
+
+Available Classes
+-----------------
+
+.. class:: catch_warnings([record=False[, module=None]])
+
+ A context manager that guards the warnings filter from being permanentally
+ mutated. The manager returns an instance of :class:`WarningsRecorder`. The
+ *record* argument specifies whether warnings that would typically be
+ handled by :func:`showwarning` should instead be recorded by the
+ :class:`WarningsRecorder` instance. This argument is typically set when
+ testing for expected warnings behavior. The *module* argument may be a
+ module object that is to be used instead of the :mod:`warnings` module.
+ This argument should only be set when testing the :mod:`warnings` module
+ or some similar use-case.
+
+ Typical usage of the context manager is like so::
+
+ def fxn():
+ warn("fxn is deprecated", DeprecationWarning)
+ return "spam spam bacon spam"
+
+ # The function 'fxn' is known to raise a DeprecationWarning.
+ with catch_warnings() as w:
+ warnings.filterwarning('ignore', 'fxn is deprecated', DeprecationWarning)
+ fxn() # DeprecationWarning is temporarily suppressed.
+
+ .. note::
+
+ In Python 3.0, the arguments to the constructor for
+ :class:`catch_warnings` are keyword-only arguments.
+
+ .. versionadded:: 2.6
+
+
+.. class:: WarningsRecorder()
+
+ A subclass of :class:`list` that stores all warnings passed to
+ :func:`showwarning` when returned by a :class:`catch_warnings` context
+ manager created with its *record* argument set to ``True``. Each recorded
+ warning is represented by an object whose attributes correspond to the
+ arguments to :func:`showwarning`. As a convenience, a
+ :class:`WarningsRecorder` instance has the attributes of the last
+ recorded warning set on the :class:`WarningsRecorder` instance as well.
+
+ .. method:: reset()
+
+ Delete all recorded warnings.
+
+ .. versionadded:: 2.6
diff --git a/Lib/BaseHTTPServer.py b/Lib/BaseHTTPServer.py
index 0a6381e..acd8394 100644
--- a/Lib/BaseHTTPServer.py
+++ b/Lib/BaseHTTPServer.py
@@ -73,11 +73,11 @@ __all__ = ["HTTPServer", "BaseHTTPRequestHandler"]
import sys
import time
import socket # For gethostbyaddr()
-from test.test_support import catch_warning
-from warnings import filterwarnings
-with catch_warning(record=False):
- filterwarnings("ignore", ".*mimetools has been removed",
- DeprecationWarning)
+from warnings import filterwarnings, catch_warnings
+with catch_warnings():
+ if sys.py3kwarning:
+ filterwarnings("ignore", ".*mimetools has been removed",
+ DeprecationWarning)
import mimetools
import SocketServer
diff --git a/Lib/asynchat.py b/Lib/asynchat.py
index 121b467..a97de93 100644
--- a/Lib/asynchat.py
+++ b/Lib/asynchat.py
@@ -49,8 +49,9 @@ you - by calling your self.found_terminator() method.
import socket
import asyncore
from collections import deque
+from sys import py3kwarning
from test.test_support import catch_warning
-from warnings import filterwarnings
+from warnings import filterwarnings, catch_warnings
class async_chat (asyncore.dispatcher):
"""This is an abstract class. You must derive from this class, and add
@@ -218,8 +219,9 @@ class async_chat (asyncore.dispatcher):
# handle classic producer behavior
obs = self.ac_out_buffer_size
try:
- with catch_warning(record=False):
- filterwarnings("ignore", ".*buffer", DeprecationWarning)
+ with catch_warnings():
+ if py3kwarning:
+ filterwarnings("ignore", ".*buffer", DeprecationWarning)
data = buffer(first, 0, obs)
except TypeError:
data = first.more()
diff --git a/Lib/cgi.py b/Lib/cgi.py
index 3a795c9..dd11389 100755
--- a/Lib/cgi.py
+++ b/Lib/cgi.py
@@ -39,13 +39,14 @@ import sys
import os
import urllib
import UserDict
-from test.test_support import catch_warning
-from warnings import filterwarnings
-with catch_warning(record=False):
- filterwarnings("ignore", ".*mimetools has been removed",
- DeprecationWarning)
+from warnings import filterwarnings, catch_warnings
+with catch_warnings():
+ if sys.py3kwarning:
+ filterwarnings("ignore", ".*mimetools has been removed",
+ DeprecationWarning)
import mimetools
- filterwarnings("ignore", ".*rfc822 has been removed", DeprecationWarning)
+ if sys.py3kwarning:
+ filterwarnings("ignore", ".*rfc822 has been removed", DeprecationWarning)
import rfc822
try:
diff --git a/Lib/httplib.py b/Lib/httplib.py
index 62cd0c7..2830ad7 100644
--- a/Lib/httplib.py
+++ b/Lib/httplib.py
@@ -67,12 +67,13 @@ Req-sent-unread-response _CS_REQ_SENT <response_class>
"""
import socket
+from sys import py3kwarning
from urlparse import urlsplit
import warnings
-from test.test_support import catch_warning
-with catch_warning(record=False):
- warnings.filterwarnings("ignore", ".*mimetools has been removed",
- DeprecationWarning)
+with warnings.catch_warnings():
+ if py3kwarning:
+ warnings.filterwarnings("ignore", ".*mimetools has been removed",
+ DeprecationWarning)
import mimetools
try:
diff --git a/Lib/mimetools.py b/Lib/mimetools.py
index 097eda4..fc5a2a5 100644
--- a/Lib/mimetools.py
+++ b/Lib/mimetools.py
@@ -2,11 +2,12 @@
import os
+import sys
import tempfile
-from test.test_support import catch_warning
-from warnings import filterwarnings
-with catch_warning(record=False):
- filterwarnings("ignore", ".*rfc822 has been removed", DeprecationWarning)
+from warnings import filterwarnings, catch_warnings
+with catch_warnings(record=False):
+ if sys.py3kwarning:
+ filterwarnings("ignore", ".*rfc822 has been removed", DeprecationWarning)
import rfc822
from warnings import warnpy3k
diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py
index adcbdd1..695bd6d 100644
--- a/Lib/test/test_support.py
+++ b/Lib/test/test_support.py
@@ -18,7 +18,7 @@ __all__ = ["Error", "TestFailed", "TestSkipped", "ResourceDenied", "import_modul
"is_resource_enabled", "requires", "find_unused_port", "bind_port",
"fcmp", "have_unicode", "is_jython", "TESTFN", "HOST", "FUZZ",
"findfile", "verify", "vereq", "sortdict", "check_syntax_error",
- "open_urlresource", "WarningMessage", "catch_warning", "CleanImport",
+ "open_urlresource", "catch_warning", "CleanImport",
"EnvironmentVarGuard", "captured_output",
"captured_stdout", "TransientResource", "transient_internet",
"run_with_locale", "set_memlimit", "bigmemtest", "bigaddrspacetest",
@@ -381,71 +381,8 @@ def open_urlresource(url):
return open(fn)
-class WarningMessage(object):
- "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.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
def catch_warning(module=warnings, record=True):
- """Guard the warnings filter from being permanently changed and
- optionally record the details of any warnings that are issued.
-
- Use like this:
-
- with catch_warning() as w:
- warnings.warn("foo")
- assert str(w.message) == "foo"
- """
- original_filters = module.filters
- original_showwarning = module.showwarning
- if record:
- recorder = WarningRecorder()
- module.showwarning = recorder._showwarning
- else:
- recorder = None
- try:
- # Replace the filters with a copy of the original
- module.filters = module.filters[:]
- yield recorder
- finally:
- module.showwarning = original_showwarning
- module.filters = original_filters
+ return warnings.catch_warnings(record=record, module=module)
class CleanImport(object):
diff --git a/Lib/test/test_warnings.py b/Lib/test/test_warnings.py
index 7c1706a..1520bf2 100644
--- a/Lib/test/test_warnings.py
+++ b/Lib/test/test_warnings.py
@@ -79,20 +79,19 @@ class FilterTests(object):
"FilterTests.test_error")
def test_ignore(self):
- with test_support.catch_warning(self.module) as w:
+ with test_support.catch_warning(module=self.module) as w:
self.module.resetwarnings()
self.module.filterwarnings("ignore", category=UserWarning)
self.module.warn("FilterTests.test_ignore", UserWarning)
- self.assert_(not w.message)
+ self.assertEquals(len(w), 0)
def test_always(self):
- with test_support.catch_warning(self.module) as w:
+ with test_support.catch_warning(module=self.module) as w:
self.module.resetwarnings()
self.module.filterwarnings("always", category=UserWarning)
message = "FilterTests.test_always"
self.module.warn(message, UserWarning)
self.assert_(message, w.message)
- w.message = None # Reset.
self.module.warn(message, UserWarning)
self.assert_(w.message, message)
@@ -107,7 +106,7 @@ class FilterTests(object):
self.assertEquals(w.message, message)
w.reset()
elif x == 1:
- self.assert_(not w.message, "unexpected warning: " + str(w))
+ self.assert_(not len(w), "unexpected warning: " + str(w))
else:
raise ValueError("loop variant unhandled")
@@ -120,7 +119,7 @@ class FilterTests(object):
self.assertEquals(w.message, message)
w.reset()
self.module.warn(message, UserWarning)
- self.assert_(not w.message, "unexpected message: " + str(w))
+ self.assert_(not len(w), "unexpected message: " + str(w))
def test_once(self):
with test_support.catch_warning(self.module) as w:
@@ -133,10 +132,10 @@ class FilterTests(object):
w.reset()
self.module.warn_explicit(message, UserWarning, "test_warnings.py",
13)
- self.assert_(not w.message)
+ self.assertEquals(len(w), 0)
self.module.warn_explicit(message, UserWarning, "test_warnings2.py",
42)
- self.assert_(not w.message)
+ self.assertEquals(len(w), 0)
def test_inheritance(self):
with test_support.catch_warning(self.module) as w:
@@ -156,7 +155,7 @@ class FilterTests(object):
self.module.warn("FilterTests.test_ordering", UserWarning)
except UserWarning:
self.fail("order handling for actions failed")
- self.assert_(not w.message)
+ self.assertEquals(len(w), 0)
def test_filterwarnings(self):
# Test filterwarnings().
@@ -317,7 +316,6 @@ class WarnTests(unittest.TestCase):
None, Warning, None, 1, registry=42)
-
class CWarnTests(BaseTest, WarnTests):
module = c_warnings
@@ -377,7 +375,7 @@ class _WarningsTests(BaseTest):
self.failUnlessEqual(w.message, message)
w.reset()
self.module.warn_explicit(message, UserWarning, "file", 42)
- self.assert_(not w.message)
+ self.assertEquals(len(w), 0)
# Test the resetting of onceregistry.
self.module.onceregistry = {}
__warningregistry__ = {}
@@ -388,7 +386,7 @@ class _WarningsTests(BaseTest):
del self.module.onceregistry
__warningregistry__ = {}
self.module.warn_explicit(message, UserWarning, "file", 42)
- self.failUnless(not w.message)
+ self.assertEquals(len(w), 0)
finally:
self.module.onceregistry = original_registry
@@ -489,45 +487,45 @@ class PyWarningsDisplayTests(BaseTest, WarningsDisplayTests):
-class WarningsSupportTests(object):
- """Test the warning tools from test support module"""
+class CatchWarningTests(BaseTest):
- def test_catch_warning_restore(self):
+ """Test catch_warnings()."""
+
+ def test_catch_warnings_restore(self):
wmod = self.module
orig_filters = wmod.filters
orig_showwarning = wmod.showwarning
- with test_support.catch_warning(wmod):
+ with wmod.catch_warnings(record=True, module=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):
+ with wmod.catch_warnings(module=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):
+ def test_catch_warnings_recording(self):
wmod = self.module
- with test_support.catch_warning(wmod) as w:
- self.assertEqual(w.warnings, [])
+ with wmod.catch_warnings(module=wmod, record=True) as w:
+ self.assertEqual(w, [])
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")
+ self.assertEqual(str(w[0].message), "foo")
+ self.assertEqual(str(w[1].message), "bar")
w.reset()
- self.assertEqual(w.warnings, [])
+ self.assertEqual(w, [])
orig_showwarning = wmod.showwarning
- with test_support.catch_warning(wmod, record=False) as w:
+ with wmod.catch_warnings(module=wmod, record=False) as w:
self.assert_(w is None)
self.assert_(wmod.showwarning is orig_showwarning)
-
-class CWarningsSupportTests(BaseTest, WarningsSupportTests):
+class CCatchWarningTests(CatchWarningTests):
module = c_warnings
-class PyWarningsSupportTests(BaseTest, WarningsSupportTests):
+class PyCatchWarningTests(CatchWarningTests):
module = py_warnings
@@ -539,14 +537,24 @@ class ShowwarningDeprecationTests(BaseTest):
def bad_showwarning(message, category, filename, lineno, file=None):
pass
+ @staticmethod
+ def ok_showwarning(*args):
+ pass
+
def test_deprecation(self):
# message, category, filename, lineno[, file[, line]]
args = ("message", UserWarning, "file name", 42)
- with test_support.catch_warning(self.module):
+ with test_support.catch_warning(module=self.module):
self.module.filterwarnings("error", category=DeprecationWarning)
self.module.showwarning = self.bad_showwarning
self.assertRaises(DeprecationWarning, self.module.warn_explicit,
*args)
+ self.module.showwarning = self.ok_showwarning
+ try:
+ self.module.warn_explicit(*args)
+ except DeprecationWarning as exc:
+ self.fail('showwarning(*args) should not trigger a '
+ 'DeprecationWarning')
class CShowwarningDeprecationTests(ShowwarningDeprecationTests):
module = c_warnings
@@ -559,16 +567,14 @@ class PyShowwarningDeprecationTests(ShowwarningDeprecationTests):
def test_main():
py_warnings.onceregistry.clear()
c_warnings.onceregistry.clear()
- test_support.run_unittest(CFilterTests,
- PyFilterTests,
- CWarnTests,
- PyWarnTests,
+ test_support.run_unittest(CFilterTests, PyFilterTests,
+ CWarnTests, PyWarnTests,
CWCmdLineTests, PyWCmdLineTests,
_WarningsTests,
CWarningsDisplayTests, PyWarningsDisplayTests,
- CWarningsSupportTests, PyWarningsSupportTests,
+ CCatchWarningTests, PyCatchWarningTests,
CShowwarningDeprecationTests,
- PyShowwarningDeprecationTests,
+ PyShowwarningDeprecationTests,
)
diff --git a/Lib/warnings.py b/Lib/warnings.py
index 2e5c512..b699c43 100644
--- a/Lib/warnings.py
+++ b/Lib/warnings.py
@@ -272,7 +272,8 @@ def warn_explicit(message, category, filename, lineno,
fxn_code = showwarning.__func__.func_code
if fxn_code:
args = fxn_code.co_varnames[:fxn_code.co_argcount]
- if 'line' not in args:
+ CO_VARARGS = 0x4
+ if 'line' not in args and not fxn_code.co_flags & CO_VARARGS:
showwarning_msg = ("functions overriding warnings.showwarning() "
"must support the 'line' argument")
if message == showwarning_msg:
@@ -283,6 +284,78 @@ def warn_explicit(message, category, filename, lineno,
showwarning(message, category, filename, lineno)
+class WarningMessage(object):
+
+ """Holds the result of a single showwarning() call."""
+
+ _WARNING_DETAILS = ("message", "category", "filename", "lineno", "file",
+ "line")
+
+ def __init__(self, message, category, filename, lineno, file=None,
+ line=None):
+ local_values = locals()
+ for attr in self._WARNING_DETAILS:
+ setattr(self, attr, local_values[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 WarningsRecorder(list):
+
+ """Record the result of various showwarning() calls."""
+
+ # Explicitly stated arguments so as to not trigger DeprecationWarning
+ # about adding 'line'.
+ def showwarning(self, *args, **kwargs):
+ self.append(WarningMessage(*args, **kwargs))
+
+ def __getattr__(self, attr):
+ return getattr(self[-1], attr)
+
+ def reset(self):
+ del self[:]
+
+
+class catch_warnings(object):
+
+ """Guard the warnings filter from being permanently changed and optionally
+ record the details of any warnings that are issued.
+
+ Context manager returns an instance of warnings.WarningRecorder which is a
+ list of WarningMessage instances. Attributes on WarningRecorder are
+ redirected to the last created WarningMessage instance.
+
+ """
+
+ def __init__(self, record=False, module=None):
+ """Specify whether to record warnings and if an alternative module
+ should be used other than sys.modules['warnings'].
+
+ For compatibility with Python 3.0, please consider all arguments to be
+ keyword-only.
+
+ """
+ self._recorder = WarningsRecorder() if record else None
+ self._module = sys.modules['warnings'] if module is None else module
+
+ def __enter__(self):
+ self._filters = self._module.filters
+ self._module.filters = self._filters[:]
+ self._showwarning = self._module.showwarning
+ if self._recorder is not None:
+ self._recorder.reset() # In case the instance is being reused.
+ self._module.showwarning = self._recorder.showwarning
+ return self._recorder
+
+ def __exit__(self, *exc_info):
+ self._module.filters = self._filters
+ self._module.showwarning = self._showwarning
+
+
# filters contains a sequence of filter 5-tuples
# The components of the 5-tuple are:
# - an action: error, ignore, always, default, module, or once
diff --git a/Misc/NEWS b/Misc/NEWS
index 73bcf08..76df45c 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -53,6 +53,11 @@ C-API
Library
-------
+- Issue 3602: Moved test.test_support.catch_warning() to
+ warnings.catch_warnings() along with some API cleanup. Expanding the tests
+ for catch_warnings() also led to an improvement in the raising of a
+ DeprecationWarning related to warnings.warn_explicit().
+
- The deprecation warnings for the old camelCase threading API were removed.
- logging: fixed lack of use of encoding attribute specified on a stream.
diff --git a/Python/_warnings.c b/Python/_warnings.c
index 5ed8b55..331ad6c 100644
--- a/Python/_warnings.c
+++ b/Python/_warnings.c
@@ -1,4 +1,5 @@
#include "Python.h"
+#include "code.h" /* For DeprecationWarning about adding 'line'. */
#include "frameobject.h"
#define MODULE_NAME "_warnings"
@@ -416,11 +417,16 @@ warn_explicit(PyObject *category, PyObject *message,
/* A proper implementation of warnings.showwarning() should
have at least two default arguments. */
if ((defaults == NULL) || (PyTuple_Size(defaults) < 2)) {
- if (PyErr_WarnEx(PyExc_DeprecationWarning, msg, 1) < 0) {
- Py_DECREF(show_fxn);
- goto cleanup;
+ PyCodeObject *code = (PyCodeObject *)
+ PyFunction_GetCode(check_fxn);
+ if (!(code->co_flags & CO_VARARGS)) {
+ if (PyErr_WarnEx(PyExc_DeprecationWarning, msg, 1) <
+ 0) {
+ Py_DECREF(show_fxn);
+ goto cleanup;
+ }
}
- }
+ }
res = PyObject_CallFunctionObjArgs(show_fxn, message, category,
filename, lineno_obj,
NULL);