Index: Misc/ACKS =================================================================== --- Misc/ACKS (revision 88422) +++ Misc/ACKS (working copy) @@ -260,6 +260,7 @@ Tim Everett Paul Everitt David Everly +Winston Ewert Greg Ewing Martijn Faassen Clovis Fabricio Index: Doc/library/unittest.rst =================================================================== --- Doc/library/unittest.rst (revision 88422) +++ Doc/library/unittest.rst (working copy) @@ -967,6 +967,9 @@ with self.assertRaises(SomeException): do_something() + When used as a context manager, assertRaises supports an additional keyword + parameter, msg, which customizes the failure message. + 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:: @@ -1022,6 +1025,9 @@ with self.assertWarns(SomeWarning): do_something() + When used as a context manager, assertWarns supports an additional keyword + parameter, msg, which customizes the failure message. + 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. Index: Lib/unittest/test/test_assertions.py =================================================================== --- Lib/unittest/test/test_assertions.py (revision 88422) +++ Lib/unittest/test/test_assertions.py (working copy) @@ -92,6 +92,34 @@ else: self.fail("assertRaises() didn't let exception pass through") + self.assertRefuseParameters('assertRaises', ValueError, None, 2) + self.assertRefuseParameters('assertRaises', ValueError, foobar = 2) + self.assertRefuseParameters('assertRaisesRegex', + ValueError, 'here', None, 2) + self.assertRefuseParameters('assertRaisesRegex', + ValueError, 'here', foobar = 2) + + def assertRefuseParameters(self, name, *args, **kwargs): + """ checks that a context manager assertion refuses the parameters given """ + method = getattr(self, name) + complaint = ("arguments should not be passed to %s() when used " + "as a context manager" % name) + try: + with method(*args, **kwargs): + pass + except self.failureException as e: + self.assertEquals(complaint, str(e)) + else: + self.fails(name + "() didn't fail") + + def testAssertWarns(self): + self.assertRefuseParameters('assertWarns', UserWarning, None, 2) + self.assertRefuseParameters('assertWarns', UserWarning, foobar = 2) + self.assertRefuseParameters('assertWarnsRegex', + UserWarning, 'here', None, 2) + self.assertRefuseParameters('assertWarnsRegex', + UserWarning, 'here', foobar = 2) + def testAssertNotRegex(self): self.assertNotRegex('Ala ma kota', r'r+') try: @@ -144,17 +172,13 @@ # this used to cause a UnicodeDecodeError constructing msg self.testableTrue._formatMessage(one, '\uFFFD') - def assertMessages(self, methodName, args, errors): - def getMethod(i): + def assertMessagesEx(self, callback, errors): + for i, expected_regex in enumerate(errors): useTestableFalse = i < 2 if useTestableFalse: test = self.testableFalse else: test = self.testableTrue - return getattr(test, methodName) - - for i, expected_regex in enumerate(errors): - testMethod = getMethod(i) kwargs = {} withMsg = i % 2 if withMsg: @@ -162,8 +186,15 @@ with self.assertRaisesRegex(self.failureException, expected_regex=expected_regex): - testMethod(*args, **kwargs) + callback(test, kwargs) + def assertMessages(self, methodName, args, errors): + def callback(test, kwargs): + testMethod = getattr(test, methodName) + testMethod(*args, **kwargs) + + self.assertMessagesEx(callback, errors) + def testAssertTrue(self): self.assertMessages('assertTrue', (False,), ["^False is not true$", "^oops$", "^False is not true$", @@ -284,3 +315,46 @@ ["^unexpectedly identical: None$", "^oops$", "^unexpectedly identical: None$", "^unexpectedly identical: None : oops$"]) + + def testAssertRaises(self): + def missing_error(test, kwargs): + with test.assertRaises(KeyError, **kwargs): + pass + + self.assertMessagesEx(missing_error, + ['^KeyError not raised$', + '^oops$', + '^KeyError not raised$', + '^KeyError not raised : oops$']) + + def testAssertRaisesRegex(self): + def incorrect_error(test, kwargs): + with test.assertRaisesRegex(KeyError, 'missing', **kwargs): + raise KeyError('here') + + self.assertMessagesEx(incorrect_error, + ['^"missing" does not match "\'here\'"$', + '^oops$', + '^"missing" does not match "\'here\'"$', + '^"missing" does not match "\'here\'" : oops$']) + + def testAssertWarns(self): + def missing_warning(test, kwargs): + with test.assertWarns(UserWarning, **kwargs): + pass + + self.assertMessagesEx(missing_warning, + ['^UserWarning not triggered$', + '^oops$', + '^UserWarning not triggered$', + '^UserWarning not triggered : oops$']) + + def testAssertWarnsRegex(self): + def wrong_warning(test, kwargs): + with test.assertWarnsRegex(UserWarning, 'missing', **kwargs): + warnings.warn('here') + self.assertMessagesEx(wrong_warning, + ['^"missing" does not match "here"$', + '^oops$', + '^"missing" does not match "here"$', + '^"missing" does not match "here" : oops$']) Index: Lib/unittest/case.py =================================================================== --- Lib/unittest/case.py (revision 88422) +++ Lib/unittest/case.py (working copy) @@ -100,13 +100,12 @@ raise _UnexpectedSuccess return wrapper - class _AssertRaisesBaseContext(object): def __init__(self, expected, test_case, callable_obj=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,8 +116,25 @@ 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): + """ run the callable or enter the context depending on parameteres """ + if callable_obj is None: + self.msg = kwargs.pop('msg', None) + if args or kwargs: + self._raiseFailure('arguments should not be passed to ' + + name + '() when used as a context manager') + return self + with self: + callable_obj(*args, **kwargs) + + + class _AssertRaisesContext(_AssertRaisesBaseContext): """A context manager used to implement TestCase.assertRaises* methods.""" @@ -132,10 +148,10 @@ except AttributeError: exc_name = str(self.expected) if self.obj_name: - raise self.failureException("{0} not raised by {1}" + self._raiseFailure("{0} not raised by {1}" .format(exc_name, self.obj_name)) else: - raise self.failureException("{0} not raised" + self._raiseFailure("{0} not raised" .format(exc_name)) if not issubclass(exc_type, self.expected): # let unexpected exceptions pass through @@ -147,7 +163,7 @@ expected_regex = self.expected_regex if not expected_regex.search(str(exc_value)): - raise self.failureException('"%s" does not match "%s"' % + self._raiseFailure('"%s" does not match "%s"' % (expected_regex.pattern, str(exc_value))) return True @@ -192,13 +208,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._raiseFailure('"%s" does not match "%s"' % (self.expected_regex.pattern, str(first_matching))) if self.obj_name: - raise self.failureException("{0} not triggered by {1}" + self._raiseFailure("{0} not triggered by {1}" .format(exc_name, self.obj_name)) else: - raise self.failureException("{0} not triggered" + self._raiseFailure("{0} not triggered" .format(exc_name)) @@ -543,21 +559,23 @@ except UnicodeDecodeError: return '%s : %s' % (safe_repr(standardMsg), safe_repr(msg)) - - def assertRaises(self, excClass, callableObj=None, *args, **kwargs): + def assertRaises(self, excClass, callable_obj=None, *args, **kwargs): """Fail unless an exception of class excClass is thrown - 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 exception is thrown, it will not be caught, and the test case will be deemed to have suffered an error, exactly as for an unexpected 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.assertRaises(SomeException): + with self.assertRaises(SomeException, msg = 'should throw'): do_something() + The optional msg keyword parameter customizes the failure message. + It can only be used when using assertRaises 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:: @@ -567,26 +585,26 @@ the_exception = cm.exception self.assertEqual(the_exception.error_code, 3) """ - context = _AssertRaisesContext(excClass, self, callableObj) - if callableObj is None: - return context - with context: - callableObj(*args, **kwargs) + context = _AssertRaisesContext(excClass, self, callable_obj) + return context.handle('assertRaises', callable_obj, 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): + with self.assertWarns(SomeWarning, msg = 'should warn'): do_something() + The optional msg keyword parameter customizes the failure message. + It can only be used when using assertWarns 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 @@ -599,10 +617,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. @@ -1117,16 +1132,16 @@ expected_regex: Regex (re pattern object or string) expected to be found in error message. callable_obj: Function to be called. + msg: Optional message to use on 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): """Asserts that the message in a triggered warning matches a regexp. @@ -1139,15 +1154,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 to use on 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."""