Michael Foord avatar Michael Foord committed aaa1582

Adding assertWarns. Thanks to Mark Roddy

Comments (0)

Files changed (6)

 * `Article / Docs: New features in unittest
   <http://www.voidspace.org.uk/python/articles/unittest2.shtml>`_
 
-Thanks to Mark Roddy, there is also a version of for Python 2.3. This is
-maintained as a separate branch and is a separate download.
+Thanks to Mark Roddy, there is a distribution of unittest2 0.5.1 for Python 2.3.
+This is maintained as a separate branch and is a separate download.
 
 To avoid problems with ``pip`` installing the wrong distribution, the Python 2.3
 version of unittest2 can't be hosted on PyPI:
 --------------------------
 
 * Invoking `unit2` without args starts test discovery
+* Added `TestCase.assertWarns` and `TestCase.assertWarnsRegexp` context managers
 * Fix Python issue 9926. TestSuite subclasses that override __call__ are called
   correctly.
 * Removed unused `maxDiff` parameter from `TestCase.assertSequenceEqual`.
 NAME = 'unittest2'
 
 PACKAGES = ['unittest2', 'unittest2.test']
-SCRIPTS = ['unit2.py', 'unit2']
+SCRIPTS = ['unit2.py', 'unit2']#, 'unittestgui', 'unittestgui.py']
 
-DESCRIPTION = ('The new features in unittest for Python 2.7 backported to '
-               'Python 2.3+.')
+DESCRIPTION = ('The new features in unittest backported to '
+               'Python 2.4+.')
 
 URL = 'http://pypi.python.org/pypi/unittest2'
 
     'Intended Audience :: Developers',
     'License :: OSI Approved :: BSD License',
     'Programming Language :: Python',
-    'Programming Language :: Python :: 2.3',
     'Programming Language :: Python :: 2.4',
     'Programming Language :: Python :: 2.5',
     'Programming Language :: Python :: 2.6',
             '%s = unittest2:main_' % SCRIPT2,
         ],
     }
-    
+
     params['test_suite'] = 'unittest2.collector'
 
 setup(**params)

unittest2/case.py

     unorderable_list_difference
 )
 
-from unittest2.compatibility import wraps
+from unittest2.compatibility import wraps, with_context, catch_warnings
 
 __unittest = True
 
     return wrapper
 
 
-class _AssertRaisesContext(object):
-    """A context manager used to implement TestCase.assertRaises* methods."""
+class _AssertRaisesBaseContext(object):
 
-    def __init__(self, expected, test_case, expected_regexp=None):
+    def __init__(self, expected, test_case, callable_obj=None,
+                 expected_regexp=None):
         self.expected = expected
         self.failureException = test_case.failureException
+        if callable_obj is not None:
+            try:
+                self.obj_name = callable_obj.__name__
+            except AttributeError:
+                self.obj_name = str(callable_obj)
+        else:
+            self.obj_name = None
+        if isinstance(expected_regexp, basestring):
+            expected_regexp = re.compile(expected_regexp)
         self.expected_regexp = expected_regexp
 
+
+class _AssertRaisesContext(_AssertRaisesBaseContext):
+    """A context manager used to implement TestCase.assertRaises* methods."""
+
     def __enter__(self):
         return self
 
             return True
 
         expected_regexp = self.expected_regexp
-        if isinstance(expected_regexp, basestring):
-            expected_regexp = re.compile(expected_regexp)
         if not expected_regexp.search(str(exc_value)):
-            raise self.failureException('"%s" does not match "%s"' %
+            raise self.failureException('%r does not match %r' %
                      (expected_regexp.pattern, str(exc_value)))
         return True
 
 
+class _AssertWarnsContext(_AssertRaisesBaseContext):
+    """A context manager used to implement TestCase.assertWarns* methods."""
+
+    def __enter__(self):
+        # The __warningregistry__'s need to be in a pristine state for tests
+        # to work properly.
+        for v in sys.modules.values():
+            if getattr(v, '__warningregistry__', None):
+                v.__warningregistry__ = {}
+        self.warnings_manager = catch_warnings(record=True)
+        self.warnings = self.warnings_manager.__enter__()
+        warnings.simplefilter("always", self.expected)
+        return self
+
+    def __exit__(self, exc_type, exc_value, tb):
+        self.warnings_manager.__exit__(exc_type, exc_value, tb)
+        if exc_type is not None:
+            # let unexpected exceptions pass through
+            return
+        try:
+            exc_name = self.expected.__name__
+        except AttributeError:
+            exc_name = str(self.expected)
+        first_matching = None
+        for m in self.warnings:
+            w = m.message
+            if not isinstance(w, self.expected):
+                continue
+            if first_matching is None:
+                first_matching = w
+            if (self.expected_regexp is not None and
+                not self.expected_regexp.search(str(w))):
+                continue
+            # store warning for later retrieval
+            self.warning = w
+            self.filename = m.filename
+            self.lineno = m.lineno
+            return
+        # Now we simply try to choose a helpful failure message
+        if first_matching is not None:
+            raise self.failureException('%r does not match %r' %
+                     (self.expected_regexp.pattern, str(first_matching)))
+        if self.obj_name:
+            raise self.failureException("%s not triggered by %s"
+                % (exc_name, self.obj_name))
+        else:
+            raise self.failureException("%s not triggered"
+                % exc_name )
+
+
 class _TypeEqualityDict(object):
 
     def __init__(self, testcase):
             excName = str(excClass)
         raise self.failureException, "%s not raised" % excName
 
+    def assertWarns(self, expected_warning, callable_obj=None, *args, **kwargs):
+        """Fail unless a warning of class warnClass is triggered
+           by callableObj when invoked with arguments args and keyword
+           arguments kwargs.  If a different type of warning is
+           triggered, it will not be handled: depending on the other
+           warning filtering rules in effect, it might be silenced, printed
+           out, or raised as an exception.
+
+           If called with callableObj omitted or None, will return a
+           context object used like this::
+
+                with self.assertWarns(SomeWarning):
+                    do_something()
+
+           The context manager keeps a reference to the first matching
+           warning as the 'warning' attribute; similarly, the 'filename'
+          and 'lineno' attributes give you information about the line
+           of Python code from which the warning was triggered.
+           This allows you to inspect the warning after the assertion::
+
+               with self.assertWarns(SomeWarning) as cm:
+                   do_something()
+               the_warning = cm.warning
+               self.assertEqual(the_warning.some_attribute, 147)
+        """
+        context = _AssertWarnsContext(expected_warning, self, callable_obj)
+        if callable_obj is None:
+            return context
+        context.__enter__()
+        try:
+            callable_obj(*args, **kwargs)
+        except:
+            if not context.__exit__(*sys.exc_info()):
+                raise
+            else:
+                return
+        else:
+            context.__exit__(None, None, None)
+
     def _getAssertEqualityFunc(self, first, second):
         """Get a detailed comparison function for the types of the two args.
 
                 excName = str(expected_exception)
             raise self.failureException, "%s not raised" % excName
 
+    def assertWarnsRegexp(self, expected_warning, expected_regexp,
+                          callable_obj=None, *args, **kwargs):
+        """Asserts that the message in a triggered warning matches a regexp.
+        Basic functioning is similar to assertWarns() with the addition
+        that only warnings whose messages also match the regular expression
+        are considered successful matches.
+
+        Args:
+            expected_warning: Warning class expected to be triggered.
+            expected_regexp: Regexp (re pattern object or string) expected
+                    to be found in error message.
+            callable_obj: Function to be called.
+            args: Extra args.
+            kwargs: Extra kwargs.
+        """
+        context = _AssertWarnsContext(expected_warning, self, callable_obj,expected_regexp)
+        if callable_obj is None:
+            return context
+        with_context(context, callable_obj, *args, **kwargs)
 
     def assertRegexpMatches(self, text, expected_regexp, msg=None):
         """Fail the test unless the text matches the regular expression."""

unittest2/compatibility.py

     relpath = _relpath_nt
 else:
     relpath = _relpath_posix
+
+
+def with_context(context, callableobj, *args, **kwargs):
+    """
+    Execute a callable utilizing a context object
+    in the same way that the 'with' statement would
+    """
+    context.__enter__()
+    try:
+        callableobj(*args, **kwargs)
+    except:
+        if not context.__exit__(*sys.exc_info()):
+            raise
+        else:
+            return
+    else:
+        context.__exit__(None, None, None)
+
+
+# copied from Python 2.6
+try:
+    from warnings import catch_warnings
+except ImportError:
+    class catch_warnings(object):
+        def __init__(self, record=False, module=None):
+            self._record = record
+            self._module = sys.modules['warnings']
+            self._entered = False
+    
+        def __repr__(self):
+            args = []
+            if self._record:
+                args.append("record=True")
+            name = type(self).__name__
+            return "%s(%s)" % (name, ", ".join(args))
+    
+        def __enter__(self):
+            if self._entered:
+                raise RuntimeError("Cannot enter %r twice" % self)
+            self._entered = True
+            self._filters = self._module.filters
+            self._module.filters = self._filters[:]
+            self._showwarning = self._module.showwarning
+            if self._record:
+                log = []
+                def showwarning(*args, **kwargs):
+                    log.append(WarningMessage(*args, **kwargs))
+                self._module.showwarning = showwarning
+                return log
+            else:
+                return None
+    
+        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.showwarning = self._showwarning
+
+    class WarningMessage(object):
+        _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 = None
+            if category.__name__:
+                self._category_name = category.__name__
+

unittest2/test/_test_unittest2_with.py

 from __future__ import with_statement
 
+
 import unittest2
-from unittest2.test.support import OldTestResult, catch_warnings
+from unittest2.test.support import OldTestResult
+from unittest2.compatibility import catch_warnings
 
 import warnings
+import inspect
+
 # needed to enable the deprecation warnings
 warnings.simplefilter('default')
 
             self.failUnlessRaises(TypeError, lambda _: 3.14 + u'spam')
             self.failIf(False)
 
+    def testAssertWarnsCallable(self):
+        def _runtime_warn():
+            warnings.warn("foo", RuntimeWarning)
+        # Success when the right warning is triggered, even several times
+        self.assertWarns(RuntimeWarning, _runtime_warn)
+        self.assertWarns(RuntimeWarning, _runtime_warn)
+        # A tuple of warning classes is accepted
+        self.assertWarns((DeprecationWarning, RuntimeWarning), _runtime_warn)
+        # *args and **kwargs also work
+        self.assertWarns(RuntimeWarning,
+                         warnings.warn, "foo", category=RuntimeWarning)
+        # Failure when no warning is triggered
+        with self.assertRaises(self.failureException):
+            self.assertWarns(RuntimeWarning, lambda: 0)
+        # Failure when another warning is triggered
+        with catch_warnings():
+            # Force default filter (in case tests are run with -We)
+            warnings.simplefilter("default", RuntimeWarning)
+            with self.assertRaises(self.failureException):
+                self.assertWarns(DeprecationWarning, _runtime_warn)
+        # Filters for other warnings are not modified
+        with catch_warnings():
+            warnings.simplefilter("error", RuntimeWarning)
+            with self.assertRaises(RuntimeWarning):
+                self.assertWarns(DeprecationWarning, _runtime_warn)
+
+    def testAssertWarnsContext(self):
+        # Believe it or not, it is preferrable to duplicate all tests above,
+        # to make sure the __warningregistry__ $@ is circumvented correctly.
+        def _runtime_warn():
+            warnings.warn("foo", RuntimeWarning)
+        _runtime_warn_lineno = inspect.getsourcelines(_runtime_warn)[1]
+        with self.assertWarns(RuntimeWarning) as cm:
+            _runtime_warn()
+        # A tuple of warning classes is accepted
+        with self.assertWarns((DeprecationWarning, RuntimeWarning)) as cm:
+            _runtime_warn()
+        # The context manager exposes various useful attributes
+        self.assertIsInstance(cm.warning, RuntimeWarning)
+        self.assertEqual(cm.warning.args[0], "foo")
+        self.assertIn("_test_unittest2_with.py", cm.filename)
+        self.assertEqual(cm.lineno, _runtime_warn_lineno + 1)
+        # Same with several warnings
+        with self.assertWarns(RuntimeWarning):
+            _runtime_warn()
+            _runtime_warn()
+        with self.assertWarns(RuntimeWarning):
+            warnings.warn("foo", category=RuntimeWarning)
+        # Failure when no warning is triggered
+        with self.assertRaises(self.failureException):
+            with self.assertWarns(RuntimeWarning):
+                pass
+        # Failure when another warning is triggered
+        with catch_warnings():
+            # Force default filter (in case tests are run with -We)
+            warnings.simplefilter("default", RuntimeWarning)
+            with self.assertRaises(self.failureException):
+                with self.assertWarns(DeprecationWarning):
+                    _runtime_warn()
+        # Filters for other warnings are not modified
+        with catch_warnings():
+            warnings.simplefilter("error", RuntimeWarning)
+            with self.assertRaises(RuntimeWarning):
+                with self.assertWarns(DeprecationWarning):
+                    _runtime_warn()
+
+    def testAssertWarnsRegexpCallable(self):
+        def _runtime_warn(msg):
+            warnings.warn(msg, RuntimeWarning)
+        self.assertWarnsRegexp(RuntimeWarning, "o+",
+                               _runtime_warn, "foox")
+        # Failure when no warning is triggered
+        with self.assertRaises(self.failureException):
+            self.assertWarnsRegexp(RuntimeWarning, "o+",
+                                   lambda: 0)
+        # Failure when another warning is triggered
+        with catch_warnings():
+            # Force default filter (in case tests are run with -We)
+            warnings.simplefilter("default", RuntimeWarning)
+            with self.assertRaises(self.failureException):
+                self.assertWarnsRegexp(DeprecationWarning, "o+",
+                                       _runtime_warn, "foox")
+        # Failure when message doesn't match
+        with self.assertRaises(self.failureException):
+            self.assertWarnsRegexp(RuntimeWarning, "o+",
+                                   _runtime_warn, "barz")
+        # A little trickier: we ask RuntimeWarnings to be raised, and then
+        # check for some of them.  It is implementation-defined whether
+        # non-matching RuntimeWarnings are simply re-raised, or produce a
+        # failureException.
+        with catch_warnings():
+            warnings.simplefilter("error", RuntimeWarning)
+            with self.assertRaises((RuntimeWarning, self.failureException)):
+                self.assertWarnsRegexp(RuntimeWarning, "o+",
+                                       _runtime_warn, "barz")
+
+    def testAssertWarnsRegexpContext(self):
+        # Same as above, but with assertWarnsRegexp as a context manager
+        def _runtime_warn(msg):
+            warnings.warn(msg, RuntimeWarning)
+        _runtime_warn_lineno = inspect.getsourcelines(_runtime_warn)[1]
+        with self.assertWarnsRegexp(RuntimeWarning, "o+") as cm:
+            _runtime_warn("foox")
+        self.assertIsInstance(cm.warning, RuntimeWarning)
+        self.assertEqual(cm.warning.args[0], "foox")
+        self.assertIn("_test_unittest2_with.py", cm.filename)
+        self.assertEqual(cm.lineno, _runtime_warn_lineno + 1)
+        # Failure when no warning is triggered
+        with self.assertRaises(self.failureException):
+            with self.assertWarnsRegexp(RuntimeWarning, "o+"):
+                pass
+        # Failure when another warning is triggered
+        with catch_warnings():
+            # Force default filter (in case tests are run with -We)
+            warnings.simplefilter("default", RuntimeWarning)
+            with self.assertRaises(self.failureException):
+                with self.assertWarnsRegexp(DeprecationWarning, "o+"):
+                    _runtime_warn("foox")
+        # Failure when message doesn't match
+        with self.assertRaises(self.failureException):
+            with self.assertWarnsRegexp(RuntimeWarning, "o+"):
+                _runtime_warn("barz")
+        # A little trickier: we ask RuntimeWarnings to be raised, and then
+        # check for some of them.  It is implementation-defined whether
+        # non-matching RuntimeWarnings are simply re-raised, or produce a
+        # failureException.
+        with catch_warnings():
+            warnings.simplefilter("error", RuntimeWarning)
+            with self.assertRaises((RuntimeWarning, self.failureException)):
+                with self.assertWarnsRegexp(RuntimeWarning, "o+"):
+                    _runtime_warn("barz")
+
 
 if __name__ == '__main__':
     unittest2.main()

unittest2/test/support.py

             except Exception, e:
                 self.fail("Problem hashing %s and %s: %s" % (obj_1, obj_2, e))
 
-
-
-# copied from Python 2.6
-try:
-    from warnings import catch_warnings
-except ImportError:
-    class catch_warnings(object):
-        def __init__(self, record=False, module=None):
-            self._record = record
-            self._module = sys.modules['warnings']
-            self._entered = False
-    
-        def __repr__(self):
-            args = []
-            if self._record:
-                args.append("record=True")
-            name = type(self).__name__
-            return "%s(%s)" % (name, ", ".join(args))
-    
-        def __enter__(self):
-            if self._entered:
-                raise RuntimeError("Cannot enter %r twice" % self)
-            self._entered = True
-            self._filters = self._module.filters
-            self._module.filters = self._filters[:]
-            self._showwarning = self._module.showwarning
-            if self._record:
-                log = []
-                def showwarning(*args, **kwargs):
-                    log.append(WarningMessage(*args, **kwargs))
-                self._module.showwarning = showwarning
-                return log
-            else:
-                return None
-    
-        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.showwarning = self._showwarning
-
-    class WarningMessage(object):
-        _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 = None
-            if category.__name__:
-                self._category_name = category.__name__
-
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.