diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py index 225a1431ca8..befad619465 100644 --- a/Lib/unittest/case.py +++ b/Lib/unittest/case.py @@ -129,37 +129,42 @@ class _BaseTestCaseContext: msg = self.test_case._formatMessage(self.msg, standardMsg) raise self.test_case.failureException(msg) -def _sentinel(*args, **kwargs): - raise AssertionError('Should never be called') - class _AssertRaisesBaseContext(_BaseTestCaseContext): - def __init__(self, expected, test_case, callable_obj=_sentinel, - expected_regex=None): + def __init__(self, expected, test_case, expected_regex=None): _BaseTestCaseContext.__init__(self, test_case) self.expected = expected self.test_case = test_case - if callable_obj is not _sentinel: - try: - self.obj_name = callable_obj.__name__ - except AttributeError: - self.obj_name = str(callable_obj) - else: - self.obj_name = None if expected_regex is not None: expected_regex = re.compile(expected_regex) self.expected_regex = expected_regex + self.obj_name = None self.msg = None - def handle(self, name, callable_obj, args, kwargs): + def handle(self, name, args, kwargs): """ - If callable_obj is _sentinel, assertRaises/Warns is being used as a + If args is empty, assertRaises/Warns is being used as a context manager, so check for a 'msg' kwarg and return self. - If callable_obj is not _sentinel, call it passing args and kwargs. + If args is not empty, call a callable passing positional and keyword + arguments. """ - if callable_obj is _sentinel: + if args and args[0] is None: + warnings.warn("callable is None", + DeprecationWarning, 3) + args = () + if not args: self.msg = kwargs.pop('msg', None) + if kwargs: + warnings.warn('%r is an invalid keyword argument for ' + 'this function' % next(iter(kwargs)), + DeprecationWarning, 3) return self + + callable_obj, *args = args + try: + self.obj_name = callable_obj.__name__ + except AttributeError: + self.obj_name = str(callable_obj) with self: callable_obj(*args, **kwargs) @@ -676,15 +681,15 @@ class TestCase(object): except UnicodeDecodeError: return '%s : %s' % (safe_repr(standardMsg), safe_repr(msg)) - def assertRaises(self, excClass, callableObj=_sentinel, *args, **kwargs): - """Fail unless an exception of class excClass is raised - by callableObj when invoked with arguments args and keyword - arguments kwargs. If a different type of exception is + def assertRaises(self, expected_exception, *args, **kwargs): + """Fail unless an exception of class expected_exception is raised + by the callable when invoked with specified positional and + keyword arguments. If a different type of exception is raised, 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, will return a + If called with the callable and arguments omitted, will return a context object used like this:: with self.assertRaises(SomeException): @@ -702,18 +707,18 @@ class TestCase(object): the_exception = cm.exception self.assertEqual(the_exception.error_code, 3) """ - context = _AssertRaisesContext(excClass, self, callableObj) - return context.handle('assertRaises', callableObj, args, kwargs) + context = _AssertRaisesContext(expected_exception, self) + return context.handle('assertRaises', args, kwargs) - def assertWarns(self, expected_warning, callable_obj=_sentinel, *args, **kwargs): + def assertWarns(self, expected_warning, *args, **kwargs): """Fail unless a warning of class warnClass is triggered - by callable_obj when invoked with arguments args and keyword - arguments kwargs. If a different type of warning is + by the callable when invoked with specified positional and + keyword arguments. 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 callable_obj omitted, will return a + If called with the callable and arguments omitted, will return a context object used like this:: with self.assertWarns(SomeWarning): @@ -733,8 +738,8 @@ class TestCase(object): the_warning = cm.warning self.assertEqual(the_warning.some_attribute, 147) """ - context = _AssertWarnsContext(expected_warning, self, callable_obj) - return context.handle('assertWarns', callable_obj, args, kwargs) + context = _AssertWarnsContext(expected_warning, self) + return context.handle('assertWarns', args, kwargs) def assertLogs(self, logger=None, level=None): """Fail unless a log message of level *level* or higher is emitted @@ -1221,26 +1226,23 @@ class TestCase(object): self.fail(self._formatMessage(msg, standardMsg)) def assertRaisesRegex(self, expected_exception, expected_regex, - callable_obj=_sentinel, *args, **kwargs): + *args, **kwargs): """Asserts that the message in a raised exception matches a regex. Args: expected_exception: Exception class expected to be raised. expected_regex: Regex (re pattern object or string) expected to be found in error message. - callable_obj: Function to be called. + args: Function to be called and extra positional args. + kwargs: Extra kwargs. 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) - - return context.handle('assertRaisesRegex', callable_obj, args, kwargs) + context = _AssertRaisesContext(expected_exception, self, expected_regex) + return context.handle('assertRaisesRegex', args, kwargs) def assertWarnsRegex(self, expected_warning, expected_regex, - callable_obj=_sentinel, *args, **kwargs): + *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 @@ -1250,15 +1252,13 @@ class TestCase(object): expected_warning: Warning class expected to be triggered. expected_regex: Regex (re pattern object or string) expected to be found in error message. - callable_obj: Function to be called. + args: Function to be called and extra positional args. + kwargs: Extra kwargs. 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) - return context.handle('assertWarnsRegex', callable_obj, args, kwargs) + context = _AssertWarnsContext(expected_warning, self, expected_regex) + return context.handle('assertWarnsRegex', 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_case.py b/Lib/unittest/test/test_case.py index 75bb0d6014d..a05cc648900 100644 --- a/Lib/unittest/test/test_case.py +++ b/Lib/unittest/test/test_case.py @@ -1146,7 +1146,7 @@ test case with self.assertRaises(self.failureException): self.assertRaises(ExceptionMock, lambda: 0) # Failure when the function is None - with self.assertRaises(TypeError): + with self.assertWarns(DeprecationWarning): self.assertRaises(ExceptionMock, None) # Failure when another exception is raised with self.assertRaises(ExceptionMock): @@ -1172,6 +1172,15 @@ test case with self.assertRaises(self.failureException): with self.assertRaises(ExceptionMock): pass + # Custom message + with self.assertRaisesRegex(self.failureException, 'foobar'): + with self.assertRaises(ExceptionMock, msg='foobar'): + pass + # Invalid keyword argument + with self.assertWarnsRegex(DeprecationWarning, 'foobar'), \ + self.assertRaises(AssertionError): + with self.assertRaises(ExceptionMock, foobar=42): + pass # Failure when another exception is raised with self.assertRaises(ExceptionMock): self.assertRaises(ValueError, Stub) @@ -1185,7 +1194,7 @@ test case self.assertRaisesRegex(ExceptionMock, re.compile('expect$'), Stub) self.assertRaisesRegex(ExceptionMock, 'expect$', Stub) - with self.assertRaises(TypeError): + with self.assertWarns(DeprecationWarning): self.assertRaisesRegex(ExceptionMock, 'expect$', None) def testAssertNotRaisesRegex(self): @@ -1197,6 +1206,15 @@ test case self.failureException, '^Exception not raised by $', self.assertRaisesRegex, Exception, 'x', lambda: None) + # Custom message + with self.assertRaisesRegex(self.failureException, 'foobar'): + with self.assertRaisesRegex(Exception, 'expect', msg='foobar'): + pass + # Invalid keyword argument + with self.assertWarnsRegex(DeprecationWarning, 'foobar'), \ + self.assertRaises(AssertionError): + with self.assertRaisesRegex(Exception, 'expect', foobar=42): + pass def testAssertRaisesRegexInvalidRegex(self): # Issue 20145. @@ -1255,7 +1273,7 @@ test case with self.assertRaises(self.failureException): self.assertWarns(RuntimeWarning, lambda: 0) # Failure when the function is None - with self.assertRaises(TypeError): + with self.assertWarns(DeprecationWarning): self.assertWarns(RuntimeWarning, None) # Failure when another warning is triggered with warnings.catch_warnings(): @@ -1295,6 +1313,15 @@ test case with self.assertRaises(self.failureException): with self.assertWarns(RuntimeWarning): pass + # Custom message + with self.assertRaisesRegex(self.failureException, 'foobar'): + with self.assertWarns(RuntimeWarning, msg='foobar'): + pass + # Invalid keyword argument + with self.assertWarnsRegex(DeprecationWarning, 'foobar'), \ + self.assertRaises(AssertionError): + with self.assertWarns(RuntimeWarning, foobar=42): + pass # Failure when another warning is triggered with warnings.catch_warnings(): # Force default filter (in case tests are run with -We) @@ -1319,7 +1346,7 @@ test case self.assertWarnsRegex(RuntimeWarning, "o+", lambda: 0) # Failure when the function is None - with self.assertRaises(TypeError): + with self.assertWarns(DeprecationWarning): self.assertWarnsRegex(RuntimeWarning, "o+", None) # Failure when another warning is triggered with warnings.catch_warnings(): @@ -1357,6 +1384,15 @@ test case with self.assertRaises(self.failureException): with self.assertWarnsRegex(RuntimeWarning, "o+"): pass + # Custom message + with self.assertRaisesRegex(self.failureException, 'foobar'): + with self.assertWarnsRegex(RuntimeWarning, 'o+', msg='foobar'): + pass + # Invalid keyword argument + with self.assertWarnsRegex(DeprecationWarning, 'foobar'), \ + self.assertRaises(AssertionError): + with self.assertWarnsRegex(RuntimeWarning, 'o+', foobar=42): + pass # Failure when another warning is triggered with warnings.catch_warnings(): # Force default filter (in case tests are run with -We) diff --git a/Misc/NEWS b/Misc/NEWS index 5e8ba2224a1..502d80f8780 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -76,7 +76,8 @@ Library usernames and passwords to UTF8. - Issue #24134: assertRaises(), assertRaisesRegex(), assertWarns() and - assertWarnsRegex() checks are not longer successful if the callable is None. + assertWarnsRegex() checks now emits a deprecation warning when callable is + None or keyword arguments except msg is passed in the context manager mode. - Issue #24018: Add a collections.abc.Generator abstract base class. Contributed by Stefan Behnel.