summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/unittest.rst45
-rw-r--r--Lib/unittest/case.py80
-rw-r--r--Lib/unittest/test/test_assertions.py73
-rw-r--r--Misc/ACKS1
-rw-r--r--Misc/NEWS4
5 files changed, 158 insertions, 45 deletions
diff --git a/Doc/library/unittest.rst b/Doc/library/unittest.rst
index a623f3c..191d5b9 100644
--- a/Doc/library/unittest.rst
+++ b/Doc/library/unittest.rst
@@ -860,10 +860,11 @@ Test cases
| <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 @@ Test cases
+---------------------------------------------------------+--------------------------------------+------------+
.. 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 @@ Test cases
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 @@ Test cases
.. 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 @@ Test cases
.. 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 @@ Test cases
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 @@ Test cases
.. 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 @@ Test cases
.. 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
index 881de6f..5b7c29d 100644
--- a/Lib/unittest/case.py
+++ b/Lib/unittest/case.py
@@ -104,9 +104,9 @@ def expectedFailure(func):
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 @@ class _AssertRaisesBaseContext(object):
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 @@ class _AssertRaisesContext(_AssertRaisesBaseContext):
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 @@ class _AssertRaisesContext(_AssertRaisesBaseContext):
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 @@ class _AssertWarnsContext(_AssertRaisesBaseContext):
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 @@ class TestCase(object):
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 @@ class TestCase(object):
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 @@ class TestCase(object):
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 @@ class TestCase(object):
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 @@ class TestCase(object):
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 @@ class TestCase(object):
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
index a1d20eb..d43fe5a 100644
--- 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 @@ class TestLongMessage(unittest.TestCase):
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 @@ class TestLongMessage(unittest.TestCase):
["^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
index 3516b1c..d296ea1 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -269,6 +269,7 @@ Carey Evans
Tim Everett
Paul Everitt
David Everly
+Winston Ewert
Greg Ewing
Martijn Faassen
Clovis Fabricio
diff --git a/Misc/NEWS b/Misc/NEWS
index 5cf9ac6..b0d6836 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -140,6 +140,10 @@ Core and Builtins
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.