Issue #10775: assertRaises, assertRaisesRegex, assertWarns, and assertWarnsRegex now accept a keyword argument 'msg' when used as context managers. Initial patch by Winston Ewert.

This commit is contained in:
Ezio Melotti 2011-05-06 15:01:41 +03:00
parent 136148e97d
commit b4dc2502ad
5 changed files with 158 additions and 45 deletions

View File

@ -860,10 +860,11 @@ Test cases
| <TestCase.assertNotIsInstance>` | | | | <TestCase.assertNotIsInstance>` | | |
+-----------------------------------------+-----------------------------+---------------+ +-----------------------------------------+-----------------------------+---------------+
All the assert methods (except :meth:`assertRaises`, All the assert methods accept a *msg* argument that, if specified, is used
:meth:`assertRaisesRegex`, :meth:`assertWarns`, :meth:`assertWarnsRegex`) as the error message on failure (see also :data:`longMessage`).
accept a *msg* argument that, if specified, is used as the error message on Note that the *msg* keyword argument can be passed to :meth:`assertRaises`,
failure (see also :data:`longMessage`). :meth:`assertRaisesRegex`, :meth:`assertWarns`, :meth:`assertWarnsRegex`
only when they are used as a context manager.
.. method:: assertEqual(first, second, msg=None) .. method:: assertEqual(first, second, msg=None)
@ -957,7 +958,7 @@ Test cases
+---------------------------------------------------------+--------------------------------------+------------+ +---------------------------------------------------------+--------------------------------------+------------+
.. method:: assertRaises(exception, callable, *args, **kwds) .. method:: assertRaises(exception, callable, *args, **kwds)
assertRaises(exception) assertRaises(exception, msg=None)
Test that an exception is raised when *callable* is called with any Test that an exception is raised when *callable* is called with any
positional or keyword arguments that are also passed to positional or keyword arguments that are also passed to
@ -966,12 +967,16 @@ Test cases
To catch any of a group of exceptions, a tuple containing the exception To catch any of a group of exceptions, a tuple containing the exception
classes may be passed as *exception*. classes may be passed as *exception*.
If only the *exception* argument is given, returns a context manager so If only the *exception* and possibly the *msg* arguments are given,
that the code under test can be written inline rather than as a function:: return a context manager so that the code under test can be written
inline rather than as a function::
with self.assertRaises(SomeException): with self.assertRaises(SomeException):
do_something() 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 The context manager will store the caught exception object in its
:attr:`exception` attribute. This can be useful if the intention :attr:`exception` attribute. This can be useful if the intention
is to perform additional checks on the exception raised:: is to perform additional checks on the exception raised::
@ -988,9 +993,12 @@ Test cases
.. versionchanged:: 3.2 .. versionchanged:: 3.2
Added the :attr:`exception` attribute. 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) .. method:: assertRaisesRegex(exception, regex, callable, *args, **kwds)
assertRaisesRegex(exception, regex) assertRaisesRegex(exception, regex, msg=None)
Like :meth:`assertRaises` but also tests that *regex* matches Like :meth:`assertRaises` but also tests that *regex* matches
on the string representation of the raised exception. *regex* may be on the string representation of the raised exception. *regex* may be
@ -1007,12 +1015,16 @@ Test cases
.. versionadded:: 3.1 .. versionadded:: 3.1
under the name ``assertRaisesRegexp``. under the name ``assertRaisesRegexp``.
.. versionchanged:: 3.2 .. versionchanged:: 3.2
Renamed to :meth:`assertRaisesRegex`. Renamed to :meth:`assertRaisesRegex`.
.. versionchanged:: 3.3
Added the *msg* keyword argument when used as a context manager.
.. method:: assertWarns(warning, callable, *args, **kwds) .. method:: assertWarns(warning, callable, *args, **kwds)
assertWarns(warning) assertWarns(warning, msg=None)
Test that a warning is triggered when *callable* is called with any Test that a warning is triggered when *callable* is called with any
positional or keyword arguments that are also passed to positional or keyword arguments that are also passed to
@ -1021,12 +1033,16 @@ Test cases
To catch any of a group of warnings, a tuple containing the warning To catch any of a group of warnings, a tuple containing the warning
classes may be passed as *warnings*. classes may be passed as *warnings*.
If only the *warning* argument is given, returns a context manager so If only the *warning* and possibly the *msg* arguments are given,
that the code under test can be written inline rather than as a function:: returns a context manager so that the code under test can be written
inline rather than as a function::
with self.assertWarns(SomeWarning): with self.assertWarns(SomeWarning):
do_something() 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 The context manager will store the caught warning object in its
:attr:`warning` attribute, and the source line which triggered the :attr:`warning` attribute, and the source line which triggered the
warnings in the :attr:`filename` and :attr:`lineno` attributes. warnings in the :attr:`filename` and :attr:`lineno` attributes.
@ -1044,9 +1060,12 @@ Test cases
.. versionadded:: 3.2 .. versionadded:: 3.2
.. versionchanged:: 3.3
Added the *msg* keyword argument when used as a context manager.
.. method:: assertWarnsRegex(warning, regex, callable, *args, **kwds) .. 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 Like :meth:`assertWarns` but also tests that *regex* matches on the
message of the triggered warning. *regex* may be a regular expression message of the triggered warning. *regex* may be a regular expression
@ -1064,6 +1083,8 @@ Test cases
.. versionadded:: 3.2 .. 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: There are also other methods used to perform more specific checks, such as:

View File

@ -106,7 +106,7 @@ class _AssertRaisesBaseContext(object):
def __init__(self, expected, test_case, callable_obj=None, def __init__(self, expected, test_case, callable_obj=None,
expected_regex=None): expected_regex=None):
self.expected = expected self.expected = expected
self.failureException = test_case.failureException self.test_case = test_case
if callable_obj is not None: if callable_obj is not None:
try: try:
self.obj_name = callable_obj.__name__ self.obj_name = callable_obj.__name__
@ -117,6 +117,24 @@ class _AssertRaisesBaseContext(object):
if isinstance(expected_regex, (bytes, str)): if isinstance(expected_regex, (bytes, str)):
expected_regex = re.compile(expected_regex) expected_regex = re.compile(expected_regex)
self.expected_regex = 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): class _AssertRaisesContext(_AssertRaisesBaseContext):
@ -132,11 +150,10 @@ class _AssertRaisesContext(_AssertRaisesBaseContext):
except AttributeError: except AttributeError:
exc_name = str(self.expected) exc_name = str(self.expected)
if self.obj_name: if self.obj_name:
raise self.failureException("{0} not raised by {1}" self._raiseFailure("{} not raised by {}".format(exc_name,
.format(exc_name, self.obj_name)) self.obj_name))
else: else:
raise self.failureException("{0} not raised" self._raiseFailure("{} not raised".format(exc_name))
.format(exc_name))
if not issubclass(exc_type, self.expected): if not issubclass(exc_type, self.expected):
# let unexpected exceptions pass through # let unexpected exceptions pass through
return False return False
@ -147,8 +164,8 @@ class _AssertRaisesContext(_AssertRaisesBaseContext):
expected_regex = self.expected_regex expected_regex = self.expected_regex
if not expected_regex.search(str(exc_value)): if not expected_regex.search(str(exc_value)):
raise self.failureException('"%s" does not match "%s"' % self._raiseFailure('"{}" does not match "{}"'.format(
(expected_regex.pattern, str(exc_value))) expected_regex.pattern, str(exc_value)))
return True return True
@ -192,14 +209,13 @@ class _AssertWarnsContext(_AssertRaisesBaseContext):
return return
# Now we simply try to choose a helpful failure message # Now we simply try to choose a helpful failure message
if first_matching is not None: if first_matching is not None:
raise self.failureException('"%s" does not match "%s"' % self._raiseFailure('"{}" does not match "{}"'.format(
(self.expected_regex.pattern, str(first_matching))) self.expected_regex.pattern, str(first_matching)))
if self.obj_name: if self.obj_name:
raise self.failureException("{0} not triggered by {1}" self._raiseFailure("{} not triggered by {}".format(exc_name,
.format(exc_name, self.obj_name)) self.obj_name))
else: else:
raise self.failureException("{0} not triggered" self._raiseFailure("{} not triggered".format(exc_name))
.format(exc_name))
class _TypeEqualityDict(object): class _TypeEqualityDict(object):
@ -547,7 +563,6 @@ class TestCase(object):
except UnicodeDecodeError: except UnicodeDecodeError:
return '%s : %s' % (safe_repr(standardMsg), safe_repr(msg)) return '%s : %s' % (safe_repr(standardMsg), safe_repr(msg))
def assertRaises(self, excClass, callableObj=None, *args, **kwargs): def assertRaises(self, excClass, callableObj=None, *args, **kwargs):
"""Fail unless an exception of class excClass is thrown """Fail unless an exception of class excClass is thrown
by callableObj when invoked with arguments args and keyword by callableObj when invoked with arguments args and keyword
@ -562,6 +577,9 @@ class TestCase(object):
with self.assertRaises(SomeException): with self.assertRaises(SomeException):
do_something() 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 context manager keeps a reference to the exception as
the 'exception' attribute. This allows you to inspect the the 'exception' attribute. This allows you to inspect the
exception after the assertion:: exception after the assertion::
@ -572,25 +590,25 @@ class TestCase(object):
self.assertEqual(the_exception.error_code, 3) self.assertEqual(the_exception.error_code, 3)
""" """
context = _AssertRaisesContext(excClass, self, callableObj) context = _AssertRaisesContext(excClass, self, callableObj)
if callableObj is None: return context.handle('assertRaises', callableObj, args, kwargs)
return context
with context:
callableObj(*args, **kwargs)
def assertWarns(self, expected_warning, callable_obj=None, *args, **kwargs): def assertWarns(self, expected_warning, callable_obj=None, *args, **kwargs):
"""Fail unless a warning of class warnClass is triggered """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 arguments kwargs. If a different type of warning is
triggered, it will not be handled: depending on the other triggered, it will not be handled: depending on the other
warning filtering rules in effect, it might be silenced, printed warning filtering rules in effect, it might be silenced, printed
out, or raised as an exception. 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:: context object used like this::
with self.assertWarns(SomeWarning): with self.assertWarns(SomeWarning):
do_something() 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 The context manager keeps a reference to the first matching
warning as the 'warning' attribute; similarly, the 'filename' warning as the 'warning' attribute; similarly, the 'filename'
and 'lineno' attributes give you information about the line and 'lineno' attributes give you information about the line
@ -603,10 +621,7 @@ class TestCase(object):
self.assertEqual(the_warning.some_attribute, 147) self.assertEqual(the_warning.some_attribute, 147)
""" """
context = _AssertWarnsContext(expected_warning, self, callable_obj) context = _AssertWarnsContext(expected_warning, self, callable_obj)
if callable_obj is None: return context.handle('assertWarns', callable_obj, args, kwargs)
return context
with context:
callable_obj(*args, **kwargs)
def _getAssertEqualityFunc(self, first, second): def _getAssertEqualityFunc(self, first, second):
"""Get a detailed comparison function for the types of the two args. """Get a detailed comparison function for the types of the two args.
@ -1083,15 +1098,15 @@ class TestCase(object):
expected_regex: Regex (re pattern object or string) expected expected_regex: Regex (re pattern object or string) expected
to be found in error message. to be found in error message.
callable_obj: Function to be called. 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. args: Extra args.
kwargs: Extra kwargs. kwargs: Extra kwargs.
""" """
context = _AssertRaisesContext(expected_exception, self, callable_obj, context = _AssertRaisesContext(expected_exception, self, callable_obj,
expected_regex) expected_regex)
if callable_obj is None:
return context return context.handle('assertRaisesRegex', callable_obj, args, kwargs)
with context:
callable_obj(*args, **kwargs)
def assertWarnsRegex(self, expected_warning, expected_regex, def assertWarnsRegex(self, expected_warning, expected_regex,
callable_obj=None, *args, **kwargs): callable_obj=None, *args, **kwargs):
@ -1105,15 +1120,14 @@ class TestCase(object):
expected_regex: Regex (re pattern object or string) expected expected_regex: Regex (re pattern object or string) expected
to be found in error message. to be found in error message.
callable_obj: Function to be called. 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. args: Extra args.
kwargs: Extra kwargs. kwargs: Extra kwargs.
""" """
context = _AssertWarnsContext(expected_warning, self, callable_obj, context = _AssertWarnsContext(expected_warning, self, callable_obj,
expected_regex) expected_regex)
if callable_obj is None: return context.handle('assertWarnsRegex', callable_obj, args, kwargs)
return context
with context:
callable_obj(*args, **kwargs)
def assertRegex(self, text, expected_regex, msg=None): def assertRegex(self, text, expected_regex, msg=None):
"""Fail the test unless the text matches the regular expression.""" """Fail the test unless the text matches the regular expression."""

View File

@ -1,6 +1,7 @@
import datetime import datetime
import warnings import warnings
import unittest import unittest
from itertools import product
class Test_Assertions(unittest.TestCase): class Test_Assertions(unittest.TestCase):
@ -145,6 +146,14 @@ class TestLongMessage(unittest.TestCase):
self.testableTrue._formatMessage(one, '\uFFFD') self.testableTrue._formatMessage(one, '\uFFFD')
def assertMessages(self, methodName, args, errors): 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): def getMethod(i):
useTestableFalse = i < 2 useTestableFalse = i < 2
if useTestableFalse: if useTestableFalse:
@ -284,3 +293,67 @@ class TestLongMessage(unittest.TestCase):
["^unexpectedly identical: None$", "^oops$", ["^unexpectedly identical: None$", "^oops$",
"^unexpectedly identical: None$", "^unexpectedly identical: None$",
"^unexpectedly identical: None : oops$"]) "^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$'])

View File

@ -269,6 +269,7 @@ Carey Evans
Tim Everett Tim Everett
Paul Everitt Paul Everitt
David Everly David Everly
Winston Ewert
Greg Ewing Greg Ewing
Martijn Faassen Martijn Faassen
Clovis Fabricio Clovis Fabricio

View File

@ -140,6 +140,10 @@ Core and Builtins
Library 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 - Issue #10684: shutil.move used to delete a folder on case insensitive
filesystems when the source and destination name where the same except filesystems when the source and destination name where the same except
for the case. for the case.