[Python-checkins] cpython: Issue #10775: assertRaises, assertRaisesRegex, assertWarns, and

ezio.melotti python-checkins at python.org
Fri May 6 14:02:49 CEST 2011


http://hg.python.org/cpython/rev/8fc801ca9ea1
changeset:   69873:8fc801ca9ea1
parent:      69871:e781292fb65b
user:        Ezio Melotti
date:        Fri May 06 15:01:41 2011 +0300
summary:
  Issue #10775: assertRaises, assertRaisesRegex, assertWarns, and assertWarnsRegex now accept a keyword argument 'msg' when used as context managers.  Initial patch by Winston Ewert.

files:
  Doc/library/unittest.rst             |  45 ++++++--
  Lib/unittest/case.py                 |  80 +++++++++------
  Lib/unittest/test/test_assertions.py |  73 ++++++++++++++
  Misc/ACKS                            |   1 +
  Misc/NEWS                            |   4 +
  5 files changed, 158 insertions(+), 45 deletions(-)


diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst
--- a/Doc/library/unittest.rst
+++ b/Doc/library/unittest.rst
@@ -860,10 +860,11 @@
    | <TestCase.assertNotIsInstance>`         |                             |               |
    +-----------------------------------------+-----------------------------+---------------+
 
-   All the assert methods (except :meth:`assertRaises`,
-   :meth:`assertRaisesRegex`, :meth:`assertWarns`, :meth:`assertWarnsRegex`)
-   accept a *msg* argument that, if specified, is used as the error message on
-   failure (see also :data:`longMessage`).
+   All the assert methods accept a *msg* argument that, if specified, is used
+   as the error message on failure (see also :data:`longMessage`).
+   Note that the *msg* keyword argument can be passed to :meth:`assertRaises`,
+   :meth:`assertRaisesRegex`, :meth:`assertWarns`, :meth:`assertWarnsRegex`
+   only when they are used as a context manager.
 
    .. method:: assertEqual(first, second, msg=None)
 
@@ -957,7 +958,7 @@
    +---------------------------------------------------------+--------------------------------------+------------+
 
    .. method:: assertRaises(exception, callable, *args, **kwds)
-               assertRaises(exception)
+               assertRaises(exception, msg=None)
 
       Test that an exception is raised when *callable* is called with any
       positional or keyword arguments that are also passed to
@@ -966,12 +967,16 @@
       To catch any of a group of exceptions, a tuple containing the exception
       classes may be passed as *exception*.
 
-      If only the *exception* argument is given, returns a context manager so
-      that the code under test can be written inline rather than as a function::
+      If only the *exception* and possibly the *msg* arguments are given,
+      return a context manager so that the code under test can be written
+      inline rather than as a function::
 
          with self.assertRaises(SomeException):
              do_something()
 
+      When used as a context manager, :meth:`assertRaises` accepts the
+      additional keyword argument *msg*.
+
       The context manager will store the caught exception object in its
       :attr:`exception` attribute.  This can be useful if the intention
       is to perform additional checks on the exception raised::
@@ -988,9 +993,12 @@
       .. versionchanged:: 3.2
          Added the :attr:`exception` attribute.
 
+      .. versionchanged:: 3.3
+         Added the *msg* keyword argument when used as a context manager.
+
 
    .. method:: assertRaisesRegex(exception, regex, callable, *args, **kwds)
-               assertRaisesRegex(exception, regex)
+               assertRaisesRegex(exception, regex, msg=None)
 
       Like :meth:`assertRaises` but also tests that *regex* matches
       on the string representation of the raised exception.  *regex* may be
@@ -1007,12 +1015,16 @@
 
       .. versionadded:: 3.1
          under the name ``assertRaisesRegexp``.
+
       .. versionchanged:: 3.2
          Renamed to :meth:`assertRaisesRegex`.
 
+      .. versionchanged:: 3.3
+         Added the *msg* keyword argument when used as a context manager.
+
 
    .. method:: assertWarns(warning, callable, *args, **kwds)
-               assertWarns(warning)
+               assertWarns(warning, msg=None)
 
       Test that a warning is triggered when *callable* is called with any
       positional or keyword arguments that are also passed to
@@ -1021,12 +1033,16 @@
       To catch any of a group of warnings, a tuple containing the warning
       classes may be passed as *warnings*.
 
-      If only the *warning* argument is given, returns a context manager so
-      that the code under test can be written inline rather than as a function::
+      If only the *warning* and possibly the *msg* arguments are given,
+      returns a context manager so that the code under test can be written
+      inline rather than as a function::
 
          with self.assertWarns(SomeWarning):
              do_something()
 
+      When used as a context manager, :meth:`assertRaises` accepts the
+      additional keyword argument *msg*.
+
       The context manager will store the caught warning object in its
       :attr:`warning` attribute, and the source line which triggered the
       warnings in the :attr:`filename` and :attr:`lineno` attributes.
@@ -1044,9 +1060,12 @@
 
       .. versionadded:: 3.2
 
+      .. versionchanged:: 3.3
+         Added the *msg* keyword argument when used as a context manager.
+
 
    .. method:: assertWarnsRegex(warning, regex, callable, *args, **kwds)
-               assertWarnsRegex(warning, regex)
+               assertWarnsRegex(warning, regex, msg=None)
 
       Like :meth:`assertWarns` but also tests that *regex* matches on the
       message of the triggered warning.  *regex* may be a regular expression
@@ -1064,6 +1083,8 @@
 
       .. versionadded:: 3.2
 
+      .. versionchanged:: 3.3
+         Added the *msg* keyword argument when used as a context manager.
 
 
    There are also other methods used to perform more specific checks, such as:
diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py
--- a/Lib/unittest/case.py
+++ b/Lib/unittest/case.py
@@ -104,9 +104,9 @@
 class _AssertRaisesBaseContext(object):
 
     def __init__(self, expected, test_case, callable_obj=None,
-                  expected_regex=None):
+                 expected_regex=None):
         self.expected = expected
-        self.failureException = test_case.failureException
+        self.test_case = test_case
         if callable_obj is not None:
             try:
                 self.obj_name = callable_obj.__name__
@@ -117,6 +117,24 @@
         if isinstance(expected_regex, (bytes, str)):
             expected_regex = re.compile(expected_regex)
         self.expected_regex = expected_regex
+        self.msg = None
+
+    def _raiseFailure(self, standardMsg):
+        msg = self.test_case._formatMessage(self.msg, standardMsg)
+        raise self.test_case.failureException(msg)
+
+    def handle(self, name, callable_obj, args, kwargs):
+        """
+        If callable_obj is None, assertRaises/Warns is being used as a
+        context manager, so check for a 'msg' kwarg and return self.
+        If callable_obj is not None, call it passing args and kwargs.
+        """
+        if callable_obj is None:
+            self.msg = kwargs.pop('msg', None)
+            return self
+        with self:
+            callable_obj(*args, **kwargs)
+
 
 
 class _AssertRaisesContext(_AssertRaisesBaseContext):
@@ -132,11 +150,10 @@
             except AttributeError:
                 exc_name = str(self.expected)
             if self.obj_name:
-                raise self.failureException("{0} not raised by {1}"
-                    .format(exc_name, self.obj_name))
+                self._raiseFailure("{} not raised by {}".format(exc_name,
+                                                                self.obj_name))
             else:
-                raise self.failureException("{0} not raised"
-                    .format(exc_name))
+                self._raiseFailure("{} not raised".format(exc_name))
         if not issubclass(exc_type, self.expected):
             # let unexpected exceptions pass through
             return False
@@ -147,8 +164,8 @@
 
         expected_regex = self.expected_regex
         if not expected_regex.search(str(exc_value)):
-            raise self.failureException('"%s" does not match "%s"' %
-                     (expected_regex.pattern, str(exc_value)))
+            self._raiseFailure('"{}" does not match "{}"'.format(
+                     expected_regex.pattern, str(exc_value)))
         return True
 
 
@@ -192,14 +209,13 @@
             return
         # Now we simply try to choose a helpful failure message
         if first_matching is not None:
-            raise self.failureException('"%s" does not match "%s"' %
-                     (self.expected_regex.pattern, str(first_matching)))
+            self._raiseFailure('"{}" does not match "{}"'.format(
+                     self.expected_regex.pattern, str(first_matching)))
         if self.obj_name:
-            raise self.failureException("{0} not triggered by {1}"
-                .format(exc_name, self.obj_name))
+            self._raiseFailure("{} not triggered by {}".format(exc_name,
+                                                               self.obj_name))
         else:
-            raise self.failureException("{0} not triggered"
-                .format(exc_name))
+            self._raiseFailure("{} not triggered".format(exc_name))
 
 
 class _TypeEqualityDict(object):
@@ -547,7 +563,6 @@
         except UnicodeDecodeError:
             return  '%s : %s' % (safe_repr(standardMsg), safe_repr(msg))
 
-
     def assertRaises(self, excClass, callableObj=None, *args, **kwargs):
         """Fail unless an exception of class excClass is thrown
            by callableObj when invoked with arguments args and keyword
@@ -562,6 +577,9 @@
                 with self.assertRaises(SomeException):
                     do_something()
 
+           An optional keyword argument 'msg' can be provided when assertRaises
+           is used as a context object.
+
            The context manager keeps a reference to the exception as
            the 'exception' attribute. This allows you to inspect the
            exception after the assertion::
@@ -572,25 +590,25 @@
                self.assertEqual(the_exception.error_code, 3)
         """
         context = _AssertRaisesContext(excClass, self, callableObj)
-        if callableObj is None:
-            return context
-        with context:
-            callableObj(*args, **kwargs)
+        return context.handle('assertRaises', callableObj, args, kwargs)
 
     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
+           by callable_obj 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
+           If called with callable_obj omitted or None, will return a
            context object used like this::
 
                 with self.assertWarns(SomeWarning):
                     do_something()
 
+           An optional keyword argument 'msg' can be provided when assertWarns
+           is used as a context object.
+
            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
@@ -603,10 +621,7 @@
                self.assertEqual(the_warning.some_attribute, 147)
         """
         context = _AssertWarnsContext(expected_warning, self, callable_obj)
-        if callable_obj is None:
-            return context
-        with context:
-            callable_obj(*args, **kwargs)
+        return context.handle('assertWarns', callable_obj, args, kwargs)
 
     def _getAssertEqualityFunc(self, first, second):
         """Get a detailed comparison function for the types of the two args.
@@ -1083,15 +1098,15 @@
             expected_regex: Regex (re pattern object or string) expected
                     to be found in error message.
             callable_obj: Function to be called.
+            msg: Optional message used in case of failure. Can only be used
+                    when assertRaisesRegex is used as a context manager.
             args: Extra args.
             kwargs: Extra kwargs.
         """
         context = _AssertRaisesContext(expected_exception, self, callable_obj,
                                        expected_regex)
-        if callable_obj is None:
-            return context
-        with context:
-            callable_obj(*args, **kwargs)
+
+        return context.handle('assertRaisesRegex', callable_obj, args, kwargs)
 
     def assertWarnsRegex(self, expected_warning, expected_regex,
                          callable_obj=None, *args, **kwargs):
@@ -1105,15 +1120,14 @@
             expected_regex: Regex (re pattern object or string) expected
                     to be found in error message.
             callable_obj: Function to be called.
+            msg: Optional message used in case of failure. Can only be used
+                    when assertWarnsRegex is used as a context manager.
             args: Extra args.
             kwargs: Extra kwargs.
         """
         context = _AssertWarnsContext(expected_warning, self, callable_obj,
                                       expected_regex)
-        if callable_obj is None:
-            return context
-        with context:
-            callable_obj(*args, **kwargs)
+        return context.handle('assertWarnsRegex', callable_obj, args, kwargs)
 
     def assertRegex(self, text, expected_regex, msg=None):
         """Fail the test unless the text matches the regular expression."""
diff --git a/Lib/unittest/test/test_assertions.py b/Lib/unittest/test/test_assertions.py
--- a/Lib/unittest/test/test_assertions.py
+++ b/Lib/unittest/test/test_assertions.py
@@ -1,6 +1,7 @@
 import datetime
 import warnings
 import unittest
+from itertools import product
 
 
 class Test_Assertions(unittest.TestCase):
@@ -145,6 +146,14 @@
         self.testableTrue._formatMessage(one, '\uFFFD')
 
     def assertMessages(self, methodName, args, errors):
+        """
+        Check that methodName(*args) raises the correct error messages.
+        errors should be a list of 4 regex that match the error when:
+          1) longMessage = False and no msg passed;
+          2) longMessage = False and msg passed;
+          3) longMessage = True and no msg passed;
+          4) longMessage = True and msg passed;
+        """
         def getMethod(i):
             useTestableFalse  = i < 2
             if useTestableFalse:
@@ -284,3 +293,67 @@
                             ["^unexpectedly identical: None$", "^oops$",
                              "^unexpectedly identical: None$",
                              "^unexpectedly identical: None : oops$"])
+
+
+    def assertMessagesCM(self, methodName, args, func, errors):
+        """
+        Check that the correct error messages are raised while executing:
+          with method(*args):
+              func()
+        *errors* should be a list of 4 regex that match the error when:
+          1) longMessage = False and no msg passed;
+          2) longMessage = False and msg passed;
+          3) longMessage = True and no msg passed;
+          4) longMessage = True and msg passed;
+        """
+        p = product((self.testableFalse, self.testableTrue),
+                    ({}, {"msg": "oops"}))
+        for (cls, kwargs), err in zip(p, errors):
+            method = getattr(cls, methodName)
+            with self.assertRaisesRegex(cls.failureException, err):
+                with method(*args, **kwargs) as cm:
+                    func()
+
+    def testAssertRaises(self):
+        self.assertMessagesCM('assertRaises', (TypeError,), lambda: None,
+                              ['^TypeError not raised$', '^oops$',
+                               '^TypeError not raised$',
+                               '^TypeError not raised : oops$'])
+
+    def testAssertRaisesRegex(self):
+        # test error not raised
+        self.assertMessagesCM('assertRaisesRegex', (TypeError, 'unused regex'),
+                              lambda: None,
+                              ['^TypeError not raised$', '^oops$',
+                               '^TypeError not raised$',
+                               '^TypeError not raised : oops$'])
+        # test error raised but with wrong message
+        def raise_wrong_message():
+            raise TypeError('foo')
+        self.assertMessagesCM('assertRaisesRegex', (TypeError, 'regex'),
+                              raise_wrong_message,
+                              ['^"regex" does not match "foo"$', '^oops$',
+                               '^"regex" does not match "foo"$',
+                               '^"regex" does not match "foo" : oops$'])
+
+    def testAssertWarns(self):
+        self.assertMessagesCM('assertWarns', (UserWarning,), lambda: None,
+                              ['^UserWarning not triggered$', '^oops$',
+                               '^UserWarning not triggered$',
+                               '^UserWarning not triggered : oops$'])
+
+    def testAssertWarnsRegex(self):
+        # test error not raised
+        self.assertMessagesCM('assertWarnsRegex', (UserWarning, 'unused regex'),
+                              lambda: None,
+                              ['^UserWarning not triggered$', '^oops$',
+                               '^UserWarning not triggered$',
+                               '^UserWarning not triggered : oops$'])
+        # test warning raised but with wrong message
+        def raise_wrong_message():
+            warnings.warn('foo')
+        self.assertMessagesCM('assertWarnsRegex', (UserWarning, 'regex'),
+                              raise_wrong_message,
+                              ['^"regex" does not match "foo"$', '^oops$',
+                               '^"regex" does not match "foo"$',
+                               '^"regex" does not match "foo" : oops$'])
diff --git a/Misc/ACKS b/Misc/ACKS
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -269,6 +269,7 @@
 Tim Everett
 Paul Everitt
 David Everly
+Winston Ewert
 Greg Ewing
 Martijn Faassen
 Clovis Fabricio
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -140,6 +140,10 @@
 Library
 -------
 
+- Issue #10775: assertRaises, assertRaisesRegex, assertWarns, and
+  assertWarnsRegex now accept a keyword argument 'msg' when used as context
+  managers.  Initial patch by Winston Ewert.
+
 - Issue #10684: shutil.move used to delete a folder on case insensitive
   filesystems when the source and destination name where the same except
   for the case.

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list