Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(249550)

Side by Side Diff: Lib/unittest/case.py

Issue 18937: add unittest assertion for logging
Patch Set: Created 5 years, 11 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « Doc/library/unittest.rst ('k') | Lib/unittest/test/test_case.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 """Test case implementation""" 1 """Test case implementation"""
2 2
3 import sys 3 import sys
4 import functools 4 import functools
5 import difflib 5 import difflib
6 import logging
6 import pprint 7 import pprint
7 import re 8 import re
8 import warnings 9 import warnings
9 import collections 10 import collections
10 import contextlib 11 import contextlib
11 12
12 from . import result 13 from . import result
13 from .util import (strclass, safe_repr, _count_diff_all_purpose, 14 from .util import (strclass, safe_repr, _count_diff_all_purpose,
14 _count_diff_hashable) 15 _count_diff_hashable)
15 16
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after
108 """ 109 """
109 if not condition: 110 if not condition:
110 return skip(reason) 111 return skip(reason)
111 return _id 112 return _id
112 113
113 def expectedFailure(test_item): 114 def expectedFailure(test_item):
114 test_item.__unittest_expecting_failure__ = True 115 test_item.__unittest_expecting_failure__ = True
115 return test_item 116 return test_item
116 117
117 118
118 class _AssertRaisesBaseContext(object): 119 class _BaseTestCaseContext:
120
121 def __init__(self, test_case):
122 self.test_case = test_case
123
124 def _raiseFailure(self, standardMsg):
125 msg = self.test_case._formatMessage(self.msg, standardMsg)
126 raise self.test_case.failureException(msg)
127
128
129 class _AssertRaisesBaseContext(_BaseTestCaseContext):
119 130
120 def __init__(self, expected, test_case, callable_obj=None, 131 def __init__(self, expected, test_case, callable_obj=None,
121 expected_regex=None): 132 expected_regex=None):
133 _BaseTestCaseContext.__init__(self, test_case)
122 self.expected = expected 134 self.expected = expected
123 self.test_case = test_case 135 self.test_case = test_case
124 if callable_obj is not None: 136 if callable_obj is not None:
125 try: 137 try:
126 self.obj_name = callable_obj.__name__ 138 self.obj_name = callable_obj.__name__
127 except AttributeError: 139 except AttributeError:
128 self.obj_name = str(callable_obj) 140 self.obj_name = str(callable_obj)
129 else: 141 else:
130 self.obj_name = None 142 self.obj_name = None
131 if isinstance(expected_regex, (bytes, str)): 143 if isinstance(expected_regex, (bytes, str)):
132 expected_regex = re.compile(expected_regex) 144 expected_regex = re.compile(expected_regex)
133 self.expected_regex = expected_regex 145 self.expected_regex = expected_regex
134 self.msg = None 146 self.msg = None
135
136 def _raiseFailure(self, standardMsg):
137 msg = self.test_case._formatMessage(self.msg, standardMsg)
138 raise self.test_case.failureException(msg)
139 147
140 def handle(self, name, callable_obj, args, kwargs): 148 def handle(self, name, callable_obj, args, kwargs):
141 """ 149 """
142 If callable_obj is None, assertRaises/Warns is being used as a 150 If callable_obj is None, assertRaises/Warns is being used as a
143 context manager, so check for a 'msg' kwarg and return self. 151 context manager, so check for a 'msg' kwarg and return self.
144 If callable_obj is not None, call it passing args and kwargs. 152 If callable_obj is not None, call it passing args and kwargs.
145 """ 153 """
146 if callable_obj is None: 154 if callable_obj is None:
147 self.msg = kwargs.pop('msg', None) 155 self.msg = kwargs.pop('msg', None)
148 return self 156 return self
149 with self: 157 with self:
150 callable_obj(*args, **kwargs) 158 callable_obj(*args, **kwargs)
151
152 159
153 160
154 class _AssertRaisesContext(_AssertRaisesBaseContext): 161 class _AssertRaisesContext(_AssertRaisesBaseContext):
155 """A context manager used to implement TestCase.assertRaises* methods.""" 162 """A context manager used to implement TestCase.assertRaises* methods."""
156 163
157 def __enter__(self): 164 def __enter__(self):
158 return self 165 return self
159 166
160 def __exit__(self, exc_type, exc_value, tb): 167 def __exit__(self, exc_type, exc_value, tb):
161 if exc_type is None: 168 if exc_type is None:
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after
223 return 230 return
224 # Now we simply try to choose a helpful failure message 231 # Now we simply try to choose a helpful failure message
225 if first_matching is not None: 232 if first_matching is not None:
226 self._raiseFailure('"{}" does not match "{}"'.format( 233 self._raiseFailure('"{}" does not match "{}"'.format(
227 self.expected_regex.pattern, str(first_matching))) 234 self.expected_regex.pattern, str(first_matching)))
228 if self.obj_name: 235 if self.obj_name:
229 self._raiseFailure("{} not triggered by {}".format(exc_name, 236 self._raiseFailure("{} not triggered by {}".format(exc_name,
230 self.obj_name)) 237 self.obj_name))
231 else: 238 else:
232 self._raiseFailure("{} not triggered".format(exc_name)) 239 self._raiseFailure("{} not triggered".format(exc_name))
240
241
242
243 _LoggingWatcher = collections.namedtuple("_LoggingWatcher",
244 ["records", "output"])
245
246
247 class _CapturingHandler(logging.Handler):
248 """
249 A logging handler capturing all (raw and formatted) logging output.
250 """
251
252 def __init__(self):
253 logging.Handler.__init__(self)
254 self.watcher = _LoggingWatcher([], [])
255
256 def flush(self):
257 pass
258
259 def emit(self, record):
260 self.watcher.records.append(record)
261 msg = self.format(record)
262 self.watcher.output.append(msg)
263
264
265
266 class _AssertLogsContext(_BaseTestCaseContext):
267 """A context manager used to implement TestCase.assertLogs()."""
268
269 LOGGING_FORMAT = "%(levelname)s:%(name)s:%(message)s"
270
271 def __init__(self, test_case, logger_name, level):
272 _BaseTestCaseContext.__init__(self, test_case)
273 self.logger_name = logger_name
274 if level:
275 self.level = logging._nameToLevel.get(level, level)
276 else:
277 self.level = logging.INFO
278 self.msg = None
279
280 def __enter__(self):
281 if isinstance(self.logger_name, logging.Logger):
282 logger = self.logger = self.logger_name
283 else:
284 logger = self.logger = logging.getLogger(self.logger_name)
285 formatter = logging.Formatter(self.LOGGING_FORMAT)
286 handler = _CapturingHandler()
287 handler.setFormatter(formatter)
288 self.watcher = handler.watcher
289 self.old_handlers = logger.handlers[:]
290 self.old_level = logger.level
291 self.old_propagate = logger.propagate
292 logger.handlers = [handler]
293 logger.setLevel(self.level)
294 logger.propagate = False
295 return handler.watcher
296
297 def __exit__(self, exc_type, exc_value, tb):
298 self.logger.handlers = self.old_handlers
299 self.logger.propagate = self.old_propagate
300 self.logger.setLevel(self.old_level)
301 if exc_type is not None:
302 # let unexpected exceptions pass through
303 return False
304 if len(self.watcher.records) == 0:
305 self._raiseFailure(
306 "no logs of level {} or higher triggered on {}"
307 .format(logging.getLevelName(self.level), self.logger.name))
233 308
234 309
235 class TestCase(object): 310 class TestCase(object):
236 """A class whose instances are single test cases. 311 """A class whose instances are single test cases.
237 312
238 By default, the test code itself should be placed in a method named 313 By default, the test code itself should be placed in a method named
239 'runTest'. 314 'runTest'.
240 315
241 If the fixture may be used for many test cases, create as 316 If the fixture may be used for many test cases, create as
242 many test methods as are needed. When instantiating such a TestCase 317 many test methods as are needed. When instantiating such a TestCase
(...skipping 393 matching lines...) Expand 10 before | Expand all | Expand 10 after
636 of Python code from which the warning was triggered. 711 of Python code from which the warning was triggered.
637 This allows you to inspect the warning after the assertion:: 712 This allows you to inspect the warning after the assertion::
638 713
639 with self.assertWarns(SomeWarning) as cm: 714 with self.assertWarns(SomeWarning) as cm:
640 do_something() 715 do_something()
641 the_warning = cm.warning 716 the_warning = cm.warning
642 self.assertEqual(the_warning.some_attribute, 147) 717 self.assertEqual(the_warning.some_attribute, 147)
643 """ 718 """
644 context = _AssertWarnsContext(expected_warning, self, callable_obj) 719 context = _AssertWarnsContext(expected_warning, self, callable_obj)
645 return context.handle('assertWarns', callable_obj, args, kwargs) 720 return context.handle('assertWarns', callable_obj, args, kwargs)
721
722 def assertLogs(self, logger=None, level=None):
723 """Fail unless a log message of level *level* or higher is emitted
724 on *logger_name* or its children. If omitted, *level* defaults to
725 INFO and *logger* defaults to the root logger.
726
727 This method must be used as a context manager, and will yield
728 a recording object with two attributes: `output` and `records`.
729 At the end of the context manager, the `output` attribute will
730 be a list of the matching formatted log messages and the
731 `records` attribute will be a list of the corresponding LogRecord
732 objects.
733
734 Example::
735
736 with self.assertLogs('foo', level='INFO') as cm:
737 logging.getLogger('foo').info('first message')
738 logging.getLogger('foo.bar').error('second message')
739 self.assertEqual(cm.output, ['INFO:foo:first message',
740 'ERROR:foo.bar:second message'])
741 """
742 return _AssertLogsContext(self, logger, level)
646 743
647 def _getAssertEqualityFunc(self, first, second): 744 def _getAssertEqualityFunc(self, first, second):
648 """Get a detailed comparison function for the types of the two args. 745 """Get a detailed comparison function for the types of the two args.
649 746
650 Returns: A callable accepting (first, second, msg=None) that will 747 Returns: A callable accepting (first, second, msg=None) that will
651 raise a failure exception if first != second with a useful human 748 raise a failure exception if first != second with a useful human
652 readable error message for those types. 749 readable error message for those types.
653 """ 750 """
654 # 751 #
655 # NOTE(gregory.p.smith): I considered isinstance(first, type(second)) 752 # NOTE(gregory.p.smith): I considered isinstance(first, type(second))
(...skipping 629 matching lines...) Expand 10 before | Expand all | Expand 10 after
1285 return "{} {}".format(self.test_case.id(), self._subDescription()) 1382 return "{} {}".format(self.test_case.id(), self._subDescription())
1286 1383
1287 def shortDescription(self): 1384 def shortDescription(self):
1288 """Returns a one-line description of the subtest, or None if no 1385 """Returns a one-line description of the subtest, or None if no
1289 description has been provided. 1386 description has been provided.
1290 """ 1387 """
1291 return self.test_case.shortDescription() 1388 return self.test_case.shortDescription()
1292 1389
1293 def __str__(self): 1390 def __str__(self):
1294 return "{} {}".format(self.test_case, self._subDescription()) 1391 return "{} {}".format(self.test_case, self._subDescription())
OLDNEW
« no previous file with comments | « Doc/library/unittest.rst ('k') | Lib/unittest/test/test_case.py » ('j') | no next file with comments »

RSS Feeds Recent Issues | This issue
This is Rietveld 894c83f36cb7+