blob: f8bc865ee82039f70258ab2fd9e809649ca7896a [file] [log] [blame]
Olivier Deprezf4ef2d02021-04-20 13:36:24 +02001"""Test case implementation"""
2
3import sys
4import functools
5import difflib
6import pprint
7import re
8import warnings
9import collections
10import contextlib
11import traceback
12import types
13
14from . import result
15from .util import (strclass, safe_repr, _count_diff_all_purpose,
16 _count_diff_hashable, _common_shorten_repr)
17
18__unittest = True
19
20_subtest_msg_sentinel = object()
21
22DIFF_OMITTED = ('\nDiff is %s characters long. '
23 'Set self.maxDiff to None to see it.')
24
25class SkipTest(Exception):
26 """
27 Raise this exception in a test to skip it.
28
29 Usually you can use TestCase.skipTest() or one of the skipping decorators
30 instead of raising this directly.
31 """
32
33class _ShouldStop(Exception):
34 """
35 The test should stop.
36 """
37
38class _UnexpectedSuccess(Exception):
39 """
40 The test was supposed to fail, but it didn't!
41 """
42
43
44class _Outcome(object):
45 def __init__(self, result=None):
46 self.expecting_failure = False
47 self.result = result
48 self.result_supports_subtests = hasattr(result, "addSubTest")
49 self.success = True
50 self.skipped = []
51 self.expectedFailure = None
52 self.errors = []
53
54 @contextlib.contextmanager
55 def testPartExecutor(self, test_case, isTest=False):
56 old_success = self.success
57 self.success = True
58 try:
59 yield
60 except KeyboardInterrupt:
61 raise
62 except SkipTest as e:
63 self.success = False
64 self.skipped.append((test_case, str(e)))
65 except _ShouldStop:
66 pass
67 except:
68 exc_info = sys.exc_info()
69 if self.expecting_failure:
70 self.expectedFailure = exc_info
71 else:
72 self.success = False
73 self.errors.append((test_case, exc_info))
74 # explicitly break a reference cycle:
75 # exc_info -> frame -> exc_info
76 exc_info = None
77 else:
78 if self.result_supports_subtests and self.success:
79 self.errors.append((test_case, None))
80 finally:
81 self.success = self.success and old_success
82
83
84def _id(obj):
85 return obj
86
87
88_module_cleanups = []
89def addModuleCleanup(function, /, *args, **kwargs):
90 """Same as addCleanup, except the cleanup items are called even if
91 setUpModule fails (unlike tearDownModule)."""
92 _module_cleanups.append((function, args, kwargs))
93
94
95def doModuleCleanups():
96 """Execute all module cleanup functions. Normally called for you after
97 tearDownModule."""
98 exceptions = []
99 while _module_cleanups:
100 function, args, kwargs = _module_cleanups.pop()
101 try:
102 function(*args, **kwargs)
103 except Exception as exc:
104 exceptions.append(exc)
105 if exceptions:
106 # Swallows all but first exception. If a multi-exception handler
107 # gets written we should use that here instead.
108 raise exceptions[0]
109
110
111def skip(reason):
112 """
113 Unconditionally skip a test.
114 """
115 def decorator(test_item):
116 if not isinstance(test_item, type):
117 @functools.wraps(test_item)
118 def skip_wrapper(*args, **kwargs):
119 raise SkipTest(reason)
120 test_item = skip_wrapper
121
122 test_item.__unittest_skip__ = True
123 test_item.__unittest_skip_why__ = reason
124 return test_item
125 if isinstance(reason, types.FunctionType):
126 test_item = reason
127 reason = ''
128 return decorator(test_item)
129 return decorator
130
131def skipIf(condition, reason):
132 """
133 Skip a test if the condition is true.
134 """
135 if condition:
136 return skip(reason)
137 return _id
138
139def skipUnless(condition, reason):
140 """
141 Skip a test unless the condition is true.
142 """
143 if not condition:
144 return skip(reason)
145 return _id
146
147def expectedFailure(test_item):
148 test_item.__unittest_expecting_failure__ = True
149 return test_item
150
151def _is_subtype(expected, basetype):
152 if isinstance(expected, tuple):
153 return all(_is_subtype(e, basetype) for e in expected)
154 return isinstance(expected, type) and issubclass(expected, basetype)
155
156class _BaseTestCaseContext:
157
158 def __init__(self, test_case):
159 self.test_case = test_case
160
161 def _raiseFailure(self, standardMsg):
162 msg = self.test_case._formatMessage(self.msg, standardMsg)
163 raise self.test_case.failureException(msg)
164
165class _AssertRaisesBaseContext(_BaseTestCaseContext):
166
167 def __init__(self, expected, test_case, expected_regex=None):
168 _BaseTestCaseContext.__init__(self, test_case)
169 self.expected = expected
170 self.test_case = test_case
171 if expected_regex is not None:
172 expected_regex = re.compile(expected_regex)
173 self.expected_regex = expected_regex
174 self.obj_name = None
175 self.msg = None
176
177 def handle(self, name, args, kwargs):
178 """
179 If args is empty, assertRaises/Warns is being used as a
180 context manager, so check for a 'msg' kwarg and return self.
181 If args is not empty, call a callable passing positional and keyword
182 arguments.
183 """
184 try:
185 if not _is_subtype(self.expected, self._base_type):
186 raise TypeError('%s() arg 1 must be %s' %
187 (name, self._base_type_str))
188 if not args:
189 self.msg = kwargs.pop('msg', None)
190 if kwargs:
191 raise TypeError('%r is an invalid keyword argument for '
192 'this function' % (next(iter(kwargs)),))
193 return self
194
195 callable_obj, *args = args
196 try:
197 self.obj_name = callable_obj.__name__
198 except AttributeError:
199 self.obj_name = str(callable_obj)
200 with self:
201 callable_obj(*args, **kwargs)
202 finally:
203 # bpo-23890: manually break a reference cycle
204 self = None
205
206
207class _AssertRaisesContext(_AssertRaisesBaseContext):
208 """A context manager used to implement TestCase.assertRaises* methods."""
209
210 _base_type = BaseException
211 _base_type_str = 'an exception type or tuple of exception types'
212
213 def __enter__(self):
214 return self
215
216 def __exit__(self, exc_type, exc_value, tb):
217 if exc_type is None:
218 try:
219 exc_name = self.expected.__name__
220 except AttributeError:
221 exc_name = str(self.expected)
222 if self.obj_name:
223 self._raiseFailure("{} not raised by {}".format(exc_name,
224 self.obj_name))
225 else:
226 self._raiseFailure("{} not raised".format(exc_name))
227 else:
228 traceback.clear_frames(tb)
229 if not issubclass(exc_type, self.expected):
230 # let unexpected exceptions pass through
231 return False
232 # store exception, without traceback, for later retrieval
233 self.exception = exc_value.with_traceback(None)
234 if self.expected_regex is None:
235 return True
236
237 expected_regex = self.expected_regex
238 if not expected_regex.search(str(exc_value)):
239 self._raiseFailure('"{}" does not match "{}"'.format(
240 expected_regex.pattern, str(exc_value)))
241 return True
242
243 __class_getitem__ = classmethod(types.GenericAlias)
244
245
246class _AssertWarnsContext(_AssertRaisesBaseContext):
247 """A context manager used to implement TestCase.assertWarns* methods."""
248
249 _base_type = Warning
250 _base_type_str = 'a warning type or tuple of warning types'
251
252 def __enter__(self):
253 # The __warningregistry__'s need to be in a pristine state for tests
254 # to work properly.
255 for v in sys.modules.values():
256 if getattr(v, '__warningregistry__', None):
257 v.__warningregistry__ = {}
258 self.warnings_manager = warnings.catch_warnings(record=True)
259 self.warnings = self.warnings_manager.__enter__()
260 warnings.simplefilter("always", self.expected)
261 return self
262
263 def __exit__(self, exc_type, exc_value, tb):
264 self.warnings_manager.__exit__(exc_type, exc_value, tb)
265 if exc_type is not None:
266 # let unexpected exceptions pass through
267 return
268 try:
269 exc_name = self.expected.__name__
270 except AttributeError:
271 exc_name = str(self.expected)
272 first_matching = None
273 for m in self.warnings:
274 w = m.message
275 if not isinstance(w, self.expected):
276 continue
277 if first_matching is None:
278 first_matching = w
279 if (self.expected_regex is not None and
280 not self.expected_regex.search(str(w))):
281 continue
282 # store warning for later retrieval
283 self.warning = w
284 self.filename = m.filename
285 self.lineno = m.lineno
286 return
287 # Now we simply try to choose a helpful failure message
288 if first_matching is not None:
289 self._raiseFailure('"{}" does not match "{}"'.format(
290 self.expected_regex.pattern, str(first_matching)))
291 if self.obj_name:
292 self._raiseFailure("{} not triggered by {}".format(exc_name,
293 self.obj_name))
294 else:
295 self._raiseFailure("{} not triggered".format(exc_name))
296
297
298
299class _OrderedChainMap(collections.ChainMap):
300 def __iter__(self):
301 seen = set()
302 for mapping in self.maps:
303 for k in mapping:
304 if k not in seen:
305 seen.add(k)
306 yield k
307
308
309class TestCase(object):
310 """A class whose instances are single test cases.
311
312 By default, the test code itself should be placed in a method named
313 'runTest'.
314
315 If the fixture may be used for many test cases, create as
316 many test methods as are needed. When instantiating such a TestCase
317 subclass, specify in the constructor arguments the name of the test method
318 that the instance is to execute.
319
320 Test authors should subclass TestCase for their own tests. Construction
321 and deconstruction of the test's environment ('fixture') can be
322 implemented by overriding the 'setUp' and 'tearDown' methods respectively.
323
324 If it is necessary to override the __init__ method, the base class
325 __init__ method must always be called. It is important that subclasses
326 should not change the signature of their __init__ method, since instances
327 of the classes are instantiated automatically by parts of the framework
328 in order to be run.
329
330 When subclassing TestCase, you can set these attributes:
331 * failureException: determines which exception will be raised when
332 the instance's assertion methods fail; test methods raising this
333 exception will be deemed to have 'failed' rather than 'errored'.
334 * longMessage: determines whether long messages (including repr of
335 objects used in assert methods) will be printed on failure in *addition*
336 to any explicit message passed.
337 * maxDiff: sets the maximum length of a diff in failure messages
338 by assert methods using difflib. It is looked up as an instance
339 attribute so can be configured by individual tests if required.
340 """
341
342 failureException = AssertionError
343
344 longMessage = True
345
346 maxDiff = 80*8
347
348 # If a string is longer than _diffThreshold, use normal comparison instead
349 # of difflib. See #11763.
350 _diffThreshold = 2**16
351
352 # Attribute used by TestSuite for classSetUp
353
354 _classSetupFailed = False
355
356 _class_cleanups = []
357
358 def __init__(self, methodName='runTest'):
359 """Create an instance of the class that will use the named test
360 method when executed. Raises a ValueError if the instance does
361 not have a method with the specified name.
362 """
363 self._testMethodName = methodName
364 self._outcome = None
365 self._testMethodDoc = 'No test'
366 try:
367 testMethod = getattr(self, methodName)
368 except AttributeError:
369 if methodName != 'runTest':
370 # we allow instantiation with no explicit method name
371 # but not an *incorrect* or missing method name
372 raise ValueError("no such test method in %s: %s" %
373 (self.__class__, methodName))
374 else:
375 self._testMethodDoc = testMethod.__doc__
376 self._cleanups = []
377 self._subtest = None
378
379 # Map types to custom assertEqual functions that will compare
380 # instances of said type in more detail to generate a more useful
381 # error message.
382 self._type_equality_funcs = {}
383 self.addTypeEqualityFunc(dict, 'assertDictEqual')
384 self.addTypeEqualityFunc(list, 'assertListEqual')
385 self.addTypeEqualityFunc(tuple, 'assertTupleEqual')
386 self.addTypeEqualityFunc(set, 'assertSetEqual')
387 self.addTypeEqualityFunc(frozenset, 'assertSetEqual')
388 self.addTypeEqualityFunc(str, 'assertMultiLineEqual')
389
390 def addTypeEqualityFunc(self, typeobj, function):
391 """Add a type specific assertEqual style function to compare a type.
392
393 This method is for use by TestCase subclasses that need to register
394 their own type equality functions to provide nicer error messages.
395
396 Args:
397 typeobj: The data type to call this function on when both values
398 are of the same type in assertEqual().
399 function: The callable taking two arguments and an optional
400 msg= argument that raises self.failureException with a
401 useful error message when the two arguments are not equal.
402 """
403 self._type_equality_funcs[typeobj] = function
404
405 def addCleanup(self, function, /, *args, **kwargs):
406 """Add a function, with arguments, to be called when the test is
407 completed. Functions added are called on a LIFO basis and are
408 called after tearDown on test failure or success.
409
410 Cleanup items are called even if setUp fails (unlike tearDown)."""
411 self._cleanups.append((function, args, kwargs))
412
413 @classmethod
414 def addClassCleanup(cls, function, /, *args, **kwargs):
415 """Same as addCleanup, except the cleanup items are called even if
416 setUpClass fails (unlike tearDownClass)."""
417 cls._class_cleanups.append((function, args, kwargs))
418
419 def setUp(self):
420 "Hook method for setting up the test fixture before exercising it."
421 pass
422
423 def tearDown(self):
424 "Hook method for deconstructing the test fixture after testing it."
425 pass
426
427 @classmethod
428 def setUpClass(cls):
429 "Hook method for setting up class fixture before running tests in the class."
430
431 @classmethod
432 def tearDownClass(cls):
433 "Hook method for deconstructing the class fixture after running all tests in the class."
434
435 def countTestCases(self):
436 return 1
437
438 def defaultTestResult(self):
439 return result.TestResult()
440
441 def shortDescription(self):
442 """Returns a one-line description of the test, or None if no
443 description has been provided.
444
445 The default implementation of this method returns the first line of
446 the specified test method's docstring.
447 """
448 doc = self._testMethodDoc
449 return doc.strip().split("\n")[0].strip() if doc else None
450
451
452 def id(self):
453 return "%s.%s" % (strclass(self.__class__), self._testMethodName)
454
455 def __eq__(self, other):
456 if type(self) is not type(other):
457 return NotImplemented
458
459 return self._testMethodName == other._testMethodName
460
461 def __hash__(self):
462 return hash((type(self), self._testMethodName))
463
464 def __str__(self):
465 return "%s (%s)" % (self._testMethodName, strclass(self.__class__))
466
467 def __repr__(self):
468 return "<%s testMethod=%s>" % \
469 (strclass(self.__class__), self._testMethodName)
470
471 def _addSkip(self, result, test_case, reason):
472 addSkip = getattr(result, 'addSkip', None)
473 if addSkip is not None:
474 addSkip(test_case, reason)
475 else:
476 warnings.warn("TestResult has no addSkip method, skips not reported",
477 RuntimeWarning, 2)
478 result.addSuccess(test_case)
479
480 @contextlib.contextmanager
481 def subTest(self, msg=_subtest_msg_sentinel, **params):
482 """Return a context manager that will return the enclosed block
483 of code in a subtest identified by the optional message and
484 keyword parameters. A failure in the subtest marks the test
485 case as failed but resumes execution at the end of the enclosed
486 block, allowing further test code to be executed.
487 """
488 if self._outcome is None or not self._outcome.result_supports_subtests:
489 yield
490 return
491 parent = self._subtest
492 if parent is None:
493 params_map = _OrderedChainMap(params)
494 else:
495 params_map = parent.params.new_child(params)
496 self._subtest = _SubTest(self, msg, params_map)
497 try:
498 with self._outcome.testPartExecutor(self._subtest, isTest=True):
499 yield
500 if not self._outcome.success:
501 result = self._outcome.result
502 if result is not None and result.failfast:
503 raise _ShouldStop
504 elif self._outcome.expectedFailure:
505 # If the test is expecting a failure, we really want to
506 # stop now and register the expected failure.
507 raise _ShouldStop
508 finally:
509 self._subtest = parent
510
511 def _feedErrorsToResult(self, result, errors):
512 for test, exc_info in errors:
513 if isinstance(test, _SubTest):
514 result.addSubTest(test.test_case, test, exc_info)
515 elif exc_info is not None:
516 if issubclass(exc_info[0], self.failureException):
517 result.addFailure(test, exc_info)
518 else:
519 result.addError(test, exc_info)
520
521 def _addExpectedFailure(self, result, exc_info):
522 try:
523 addExpectedFailure = result.addExpectedFailure
524 except AttributeError:
525 warnings.warn("TestResult has no addExpectedFailure method, reporting as passes",
526 RuntimeWarning)
527 result.addSuccess(self)
528 else:
529 addExpectedFailure(self, exc_info)
530
531 def _addUnexpectedSuccess(self, result):
532 try:
533 addUnexpectedSuccess = result.addUnexpectedSuccess
534 except AttributeError:
535 warnings.warn("TestResult has no addUnexpectedSuccess method, reporting as failure",
536 RuntimeWarning)
537 # We need to pass an actual exception and traceback to addFailure,
538 # otherwise the legacy result can choke.
539 try:
540 raise _UnexpectedSuccess from None
541 except _UnexpectedSuccess:
542 result.addFailure(self, sys.exc_info())
543 else:
544 addUnexpectedSuccess(self)
545
546 def _callSetUp(self):
547 self.setUp()
548
549 def _callTestMethod(self, method):
550 method()
551
552 def _callTearDown(self):
553 self.tearDown()
554
555 def _callCleanup(self, function, /, *args, **kwargs):
556 function(*args, **kwargs)
557
558 def run(self, result=None):
559 orig_result = result
560 if result is None:
561 result = self.defaultTestResult()
562 startTestRun = getattr(result, 'startTestRun', None)
563 if startTestRun is not None:
564 startTestRun()
565
566 result.startTest(self)
567
568 testMethod = getattr(self, self._testMethodName)
569 if (getattr(self.__class__, "__unittest_skip__", False) or
570 getattr(testMethod, "__unittest_skip__", False)):
571 # If the class or method was skipped.
572 try:
573 skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
574 or getattr(testMethod, '__unittest_skip_why__', ''))
575 self._addSkip(result, self, skip_why)
576 finally:
577 result.stopTest(self)
578 return
579 expecting_failure_method = getattr(testMethod,
580 "__unittest_expecting_failure__", False)
581 expecting_failure_class = getattr(self,
582 "__unittest_expecting_failure__", False)
583 expecting_failure = expecting_failure_class or expecting_failure_method
584 outcome = _Outcome(result)
585 try:
586 self._outcome = outcome
587
588 with outcome.testPartExecutor(self):
589 self._callSetUp()
590 if outcome.success:
591 outcome.expecting_failure = expecting_failure
592 with outcome.testPartExecutor(self, isTest=True):
593 self._callTestMethod(testMethod)
594 outcome.expecting_failure = False
595 with outcome.testPartExecutor(self):
596 self._callTearDown()
597
598 self.doCleanups()
599 for test, reason in outcome.skipped:
600 self._addSkip(result, test, reason)
601 self._feedErrorsToResult(result, outcome.errors)
602 if outcome.success:
603 if expecting_failure:
604 if outcome.expectedFailure:
605 self._addExpectedFailure(result, outcome.expectedFailure)
606 else:
607 self._addUnexpectedSuccess(result)
608 else:
609 result.addSuccess(self)
610 return result
611 finally:
612 result.stopTest(self)
613 if orig_result is None:
614 stopTestRun = getattr(result, 'stopTestRun', None)
615 if stopTestRun is not None:
616 stopTestRun()
617
618 # explicitly break reference cycles:
619 # outcome.errors -> frame -> outcome -> outcome.errors
620 # outcome.expectedFailure -> frame -> outcome -> outcome.expectedFailure
621 outcome.errors.clear()
622 outcome.expectedFailure = None
623
624 # clear the outcome, no more needed
625 self._outcome = None
626
627 def doCleanups(self):
628 """Execute all cleanup functions. Normally called for you after
629 tearDown."""
630 outcome = self._outcome or _Outcome()
631 while self._cleanups:
632 function, args, kwargs = self._cleanups.pop()
633 with outcome.testPartExecutor(self):
634 self._callCleanup(function, *args, **kwargs)
635
636 # return this for backwards compatibility
637 # even though we no longer use it internally
638 return outcome.success
639
640 @classmethod
641 def doClassCleanups(cls):
642 """Execute all class cleanup functions. Normally called for you after
643 tearDownClass."""
644 cls.tearDown_exceptions = []
645 while cls._class_cleanups:
646 function, args, kwargs = cls._class_cleanups.pop()
647 try:
648 function(*args, **kwargs)
649 except Exception:
650 cls.tearDown_exceptions.append(sys.exc_info())
651
652 def __call__(self, *args, **kwds):
653 return self.run(*args, **kwds)
654
655 def debug(self):
656 """Run the test without collecting errors in a TestResult"""
657 self.setUp()
658 getattr(self, self._testMethodName)()
659 self.tearDown()
660 while self._cleanups:
661 function, args, kwargs = self._cleanups.pop(-1)
662 function(*args, **kwargs)
663
664 def skipTest(self, reason):
665 """Skip this test."""
666 raise SkipTest(reason)
667
668 def fail(self, msg=None):
669 """Fail immediately, with the given message."""
670 raise self.failureException(msg)
671
672 def assertFalse(self, expr, msg=None):
673 """Check that the expression is false."""
674 if expr:
675 msg = self._formatMessage(msg, "%s is not false" % safe_repr(expr))
676 raise self.failureException(msg)
677
678 def assertTrue(self, expr, msg=None):
679 """Check that the expression is true."""
680 if not expr:
681 msg = self._formatMessage(msg, "%s is not true" % safe_repr(expr))
682 raise self.failureException(msg)
683
684 def _formatMessage(self, msg, standardMsg):
685 """Honour the longMessage attribute when generating failure messages.
686 If longMessage is False this means:
687 * Use only an explicit message if it is provided
688 * Otherwise use the standard message for the assert
689
690 If longMessage is True:
691 * Use the standard message
692 * If an explicit message is provided, plus ' : ' and the explicit message
693 """
694 if not self.longMessage:
695 return msg or standardMsg
696 if msg is None:
697 return standardMsg
698 try:
699 # don't switch to '{}' formatting in Python 2.X
700 # it changes the way unicode input is handled
701 return '%s : %s' % (standardMsg, msg)
702 except UnicodeDecodeError:
703 return '%s : %s' % (safe_repr(standardMsg), safe_repr(msg))
704
705 def assertRaises(self, expected_exception, *args, **kwargs):
706 """Fail unless an exception of class expected_exception is raised
707 by the callable when invoked with specified positional and
708 keyword arguments. If a different type of exception is
709 raised, it will not be caught, and the test case will be
710 deemed to have suffered an error, exactly as for an
711 unexpected exception.
712
713 If called with the callable and arguments omitted, will return a
714 context object used like this::
715
716 with self.assertRaises(SomeException):
717 do_something()
718
719 An optional keyword argument 'msg' can be provided when assertRaises
720 is used as a context object.
721
722 The context manager keeps a reference to the exception as
723 the 'exception' attribute. This allows you to inspect the
724 exception after the assertion::
725
726 with self.assertRaises(SomeException) as cm:
727 do_something()
728 the_exception = cm.exception
729 self.assertEqual(the_exception.error_code, 3)
730 """
731 context = _AssertRaisesContext(expected_exception, self)
732 try:
733 return context.handle('assertRaises', args, kwargs)
734 finally:
735 # bpo-23890: manually break a reference cycle
736 context = None
737
738 def assertWarns(self, expected_warning, *args, **kwargs):
739 """Fail unless a warning of class warnClass is triggered
740 by the callable when invoked with specified positional and
741 keyword arguments. If a different type of warning is
742 triggered, it will not be handled: depending on the other
743 warning filtering rules in effect, it might be silenced, printed
744 out, or raised as an exception.
745
746 If called with the callable and arguments omitted, will return a
747 context object used like this::
748
749 with self.assertWarns(SomeWarning):
750 do_something()
751
752 An optional keyword argument 'msg' can be provided when assertWarns
753 is used as a context object.
754
755 The context manager keeps a reference to the first matching
756 warning as the 'warning' attribute; similarly, the 'filename'
757 and 'lineno' attributes give you information about the line
758 of Python code from which the warning was triggered.
759 This allows you to inspect the warning after the assertion::
760
761 with self.assertWarns(SomeWarning) as cm:
762 do_something()
763 the_warning = cm.warning
764 self.assertEqual(the_warning.some_attribute, 147)
765 """
766 context = _AssertWarnsContext(expected_warning, self)
767 return context.handle('assertWarns', args, kwargs)
768
769 def assertLogs(self, logger=None, level=None):
770 """Fail unless a log message of level *level* or higher is emitted
771 on *logger_name* or its children. If omitted, *level* defaults to
772 INFO and *logger* defaults to the root logger.
773
774 This method must be used as a context manager, and will yield
775 a recording object with two attributes: `output` and `records`.
776 At the end of the context manager, the `output` attribute will
777 be a list of the matching formatted log messages and the
778 `records` attribute will be a list of the corresponding LogRecord
779 objects.
780
781 Example::
782
783 with self.assertLogs('foo', level='INFO') as cm:
784 logging.getLogger('foo').info('first message')
785 logging.getLogger('foo.bar').error('second message')
786 self.assertEqual(cm.output, ['INFO:foo:first message',
787 'ERROR:foo.bar:second message'])
788 """
789 # Lazy import to avoid importing logging if it is not needed.
790 from ._log import _AssertLogsContext
791 return _AssertLogsContext(self, logger, level)
792
793 def _getAssertEqualityFunc(self, first, second):
794 """Get a detailed comparison function for the types of the two args.
795
796 Returns: A callable accepting (first, second, msg=None) that will
797 raise a failure exception if first != second with a useful human
798 readable error message for those types.
799 """
800 #
801 # NOTE(gregory.p.smith): I considered isinstance(first, type(second))
802 # and vice versa. I opted for the conservative approach in case
803 # subclasses are not intended to be compared in detail to their super
804 # class instances using a type equality func. This means testing
805 # subtypes won't automagically use the detailed comparison. Callers
806 # should use their type specific assertSpamEqual method to compare
807 # subclasses if the detailed comparison is desired and appropriate.
808 # See the discussion in http://bugs.python.org/issue2578.
809 #
810 if type(first) is type(second):
811 asserter = self._type_equality_funcs.get(type(first))
812 if asserter is not None:
813 if isinstance(asserter, str):
814 asserter = getattr(self, asserter)
815 return asserter
816
817 return self._baseAssertEqual
818
819 def _baseAssertEqual(self, first, second, msg=None):
820 """The default assertEqual implementation, not type specific."""
821 if not first == second:
822 standardMsg = '%s != %s' % _common_shorten_repr(first, second)
823 msg = self._formatMessage(msg, standardMsg)
824 raise self.failureException(msg)
825
826 def assertEqual(self, first, second, msg=None):
827 """Fail if the two objects are unequal as determined by the '=='
828 operator.
829 """
830 assertion_func = self._getAssertEqualityFunc(first, second)
831 assertion_func(first, second, msg=msg)
832
833 def assertNotEqual(self, first, second, msg=None):
834 """Fail if the two objects are equal as determined by the '!='
835 operator.
836 """
837 if not first != second:
838 msg = self._formatMessage(msg, '%s == %s' % (safe_repr(first),
839 safe_repr(second)))
840 raise self.failureException(msg)
841
842 def assertAlmostEqual(self, first, second, places=None, msg=None,
843 delta=None):
844 """Fail if the two objects are unequal as determined by their
845 difference rounded to the given number of decimal places
846 (default 7) and comparing to zero, or by comparing that the
847 difference between the two objects is more than the given
848 delta.
849
850 Note that decimal places (from zero) are usually not the same
851 as significant digits (measured from the most significant digit).
852
853 If the two objects compare equal then they will automatically
854 compare almost equal.
855 """
856 if first == second:
857 # shortcut
858 return
859 if delta is not None and places is not None:
860 raise TypeError("specify delta or places not both")
861
862 diff = abs(first - second)
863 if delta is not None:
864 if diff <= delta:
865 return
866
867 standardMsg = '%s != %s within %s delta (%s difference)' % (
868 safe_repr(first),
869 safe_repr(second),
870 safe_repr(delta),
871 safe_repr(diff))
872 else:
873 if places is None:
874 places = 7
875
876 if round(diff, places) == 0:
877 return
878
879 standardMsg = '%s != %s within %r places (%s difference)' % (
880 safe_repr(first),
881 safe_repr(second),
882 places,
883 safe_repr(diff))
884 msg = self._formatMessage(msg, standardMsg)
885 raise self.failureException(msg)
886
887 def assertNotAlmostEqual(self, first, second, places=None, msg=None,
888 delta=None):
889 """Fail if the two objects are equal as determined by their
890 difference rounded to the given number of decimal places
891 (default 7) and comparing to zero, or by comparing that the
892 difference between the two objects is less than the given delta.
893
894 Note that decimal places (from zero) are usually not the same
895 as significant digits (measured from the most significant digit).
896
897 Objects that are equal automatically fail.
898 """
899 if delta is not None and places is not None:
900 raise TypeError("specify delta or places not both")
901 diff = abs(first - second)
902 if delta is not None:
903 if not (first == second) and diff > delta:
904 return
905 standardMsg = '%s == %s within %s delta (%s difference)' % (
906 safe_repr(first),
907 safe_repr(second),
908 safe_repr(delta),
909 safe_repr(diff))
910 else:
911 if places is None:
912 places = 7
913 if not (first == second) and round(diff, places) != 0:
914 return
915 standardMsg = '%s == %s within %r places' % (safe_repr(first),
916 safe_repr(second),
917 places)
918
919 msg = self._formatMessage(msg, standardMsg)
920 raise self.failureException(msg)
921
922 def assertSequenceEqual(self, seq1, seq2, msg=None, seq_type=None):
923 """An equality assertion for ordered sequences (like lists and tuples).
924
925 For the purposes of this function, a valid ordered sequence type is one
926 which can be indexed, has a length, and has an equality operator.
927
928 Args:
929 seq1: The first sequence to compare.
930 seq2: The second sequence to compare.
931 seq_type: The expected datatype of the sequences, or None if no
932 datatype should be enforced.
933 msg: Optional message to use on failure instead of a list of
934 differences.
935 """
936 if seq_type is not None:
937 seq_type_name = seq_type.__name__
938 if not isinstance(seq1, seq_type):
939 raise self.failureException('First sequence is not a %s: %s'
940 % (seq_type_name, safe_repr(seq1)))
941 if not isinstance(seq2, seq_type):
942 raise self.failureException('Second sequence is not a %s: %s'
943 % (seq_type_name, safe_repr(seq2)))
944 else:
945 seq_type_name = "sequence"
946
947 differing = None
948 try:
949 len1 = len(seq1)
950 except (TypeError, NotImplementedError):
951 differing = 'First %s has no length. Non-sequence?' % (
952 seq_type_name)
953
954 if differing is None:
955 try:
956 len2 = len(seq2)
957 except (TypeError, NotImplementedError):
958 differing = 'Second %s has no length. Non-sequence?' % (
959 seq_type_name)
960
961 if differing is None:
962 if seq1 == seq2:
963 return
964
965 differing = '%ss differ: %s != %s\n' % (
966 (seq_type_name.capitalize(),) +
967 _common_shorten_repr(seq1, seq2))
968
969 for i in range(min(len1, len2)):
970 try:
971 item1 = seq1[i]
972 except (TypeError, IndexError, NotImplementedError):
973 differing += ('\nUnable to index element %d of first %s\n' %
974 (i, seq_type_name))
975 break
976
977 try:
978 item2 = seq2[i]
979 except (TypeError, IndexError, NotImplementedError):
980 differing += ('\nUnable to index element %d of second %s\n' %
981 (i, seq_type_name))
982 break
983
984 if item1 != item2:
985 differing += ('\nFirst differing element %d:\n%s\n%s\n' %
986 ((i,) + _common_shorten_repr(item1, item2)))
987 break
988 else:
989 if (len1 == len2 and seq_type is None and
990 type(seq1) != type(seq2)):
991 # The sequences are the same, but have differing types.
992 return
993
994 if len1 > len2:
995 differing += ('\nFirst %s contains %d additional '
996 'elements.\n' % (seq_type_name, len1 - len2))
997 try:
998 differing += ('First extra element %d:\n%s\n' %
999 (len2, safe_repr(seq1[len2])))
1000 except (TypeError, IndexError, NotImplementedError):
1001 differing += ('Unable to index element %d '
1002 'of first %s\n' % (len2, seq_type_name))
1003 elif len1 < len2:
1004 differing += ('\nSecond %s contains %d additional '
1005 'elements.\n' % (seq_type_name, len2 - len1))
1006 try:
1007 differing += ('First extra element %d:\n%s\n' %
1008 (len1, safe_repr(seq2[len1])))
1009 except (TypeError, IndexError, NotImplementedError):
1010 differing += ('Unable to index element %d '
1011 'of second %s\n' % (len1, seq_type_name))
1012 standardMsg = differing
1013 diffMsg = '\n' + '\n'.join(
1014 difflib.ndiff(pprint.pformat(seq1).splitlines(),
1015 pprint.pformat(seq2).splitlines()))
1016
1017 standardMsg = self._truncateMessage(standardMsg, diffMsg)
1018 msg = self._formatMessage(msg, standardMsg)
1019 self.fail(msg)
1020
1021 def _truncateMessage(self, message, diff):
1022 max_diff = self.maxDiff
1023 if max_diff is None or len(diff) <= max_diff:
1024 return message + diff
1025 return message + (DIFF_OMITTED % len(diff))
1026
1027 def assertListEqual(self, list1, list2, msg=None):
1028 """A list-specific equality assertion.
1029
1030 Args:
1031 list1: The first list to compare.
1032 list2: The second list to compare.
1033 msg: Optional message to use on failure instead of a list of
1034 differences.
1035
1036 """
1037 self.assertSequenceEqual(list1, list2, msg, seq_type=list)
1038
1039 def assertTupleEqual(self, tuple1, tuple2, msg=None):
1040 """A tuple-specific equality assertion.
1041
1042 Args:
1043 tuple1: The first tuple to compare.
1044 tuple2: The second tuple to compare.
1045 msg: Optional message to use on failure instead of a list of
1046 differences.
1047 """
1048 self.assertSequenceEqual(tuple1, tuple2, msg, seq_type=tuple)
1049
1050 def assertSetEqual(self, set1, set2, msg=None):
1051 """A set-specific equality assertion.
1052
1053 Args:
1054 set1: The first set to compare.
1055 set2: The second set to compare.
1056 msg: Optional message to use on failure instead of a list of
1057 differences.
1058
1059 assertSetEqual uses ducktyping to support different types of sets, and
1060 is optimized for sets specifically (parameters must support a
1061 difference method).
1062 """
1063 try:
1064 difference1 = set1.difference(set2)
1065 except TypeError as e:
1066 self.fail('invalid type when attempting set difference: %s' % e)
1067 except AttributeError as e:
1068 self.fail('first argument does not support set difference: %s' % e)
1069
1070 try:
1071 difference2 = set2.difference(set1)
1072 except TypeError as e:
1073 self.fail('invalid type when attempting set difference: %s' % e)
1074 except AttributeError as e:
1075 self.fail('second argument does not support set difference: %s' % e)
1076
1077 if not (difference1 or difference2):
1078 return
1079
1080 lines = []
1081 if difference1:
1082 lines.append('Items in the first set but not the second:')
1083 for item in difference1:
1084 lines.append(repr(item))
1085 if difference2:
1086 lines.append('Items in the second set but not the first:')
1087 for item in difference2:
1088 lines.append(repr(item))
1089
1090 standardMsg = '\n'.join(lines)
1091 self.fail(self._formatMessage(msg, standardMsg))
1092
1093 def assertIn(self, member, container, msg=None):
1094 """Just like self.assertTrue(a in b), but with a nicer default message."""
1095 if member not in container:
1096 standardMsg = '%s not found in %s' % (safe_repr(member),
1097 safe_repr(container))
1098 self.fail(self._formatMessage(msg, standardMsg))
1099
1100 def assertNotIn(self, member, container, msg=None):
1101 """Just like self.assertTrue(a not in b), but with a nicer default message."""
1102 if member in container:
1103 standardMsg = '%s unexpectedly found in %s' % (safe_repr(member),
1104 safe_repr(container))
1105 self.fail(self._formatMessage(msg, standardMsg))
1106
1107 def assertIs(self, expr1, expr2, msg=None):
1108 """Just like self.assertTrue(a is b), but with a nicer default message."""
1109 if expr1 is not expr2:
1110 standardMsg = '%s is not %s' % (safe_repr(expr1),
1111 safe_repr(expr2))
1112 self.fail(self._formatMessage(msg, standardMsg))
1113
1114 def assertIsNot(self, expr1, expr2, msg=None):
1115 """Just like self.assertTrue(a is not b), but with a nicer default message."""
1116 if expr1 is expr2:
1117 standardMsg = 'unexpectedly identical: %s' % (safe_repr(expr1),)
1118 self.fail(self._formatMessage(msg, standardMsg))
1119
1120 def assertDictEqual(self, d1, d2, msg=None):
1121 self.assertIsInstance(d1, dict, 'First argument is not a dictionary')
1122 self.assertIsInstance(d2, dict, 'Second argument is not a dictionary')
1123
1124 if d1 != d2:
1125 standardMsg = '%s != %s' % _common_shorten_repr(d1, d2)
1126 diff = ('\n' + '\n'.join(difflib.ndiff(
1127 pprint.pformat(d1).splitlines(),
1128 pprint.pformat(d2).splitlines())))
1129 standardMsg = self._truncateMessage(standardMsg, diff)
1130 self.fail(self._formatMessage(msg, standardMsg))
1131
1132 def assertDictContainsSubset(self, subset, dictionary, msg=None):
1133 """Checks whether dictionary is a superset of subset."""
1134 warnings.warn('assertDictContainsSubset is deprecated',
1135 DeprecationWarning)
1136 missing = []
1137 mismatched = []
1138 for key, value in subset.items():
1139 if key not in dictionary:
1140 missing.append(key)
1141 elif value != dictionary[key]:
1142 mismatched.append('%s, expected: %s, actual: %s' %
1143 (safe_repr(key), safe_repr(value),
1144 safe_repr(dictionary[key])))
1145
1146 if not (missing or mismatched):
1147 return
1148
1149 standardMsg = ''
1150 if missing:
1151 standardMsg = 'Missing: %s' % ','.join(safe_repr(m) for m in
1152 missing)
1153 if mismatched:
1154 if standardMsg:
1155 standardMsg += '; '
1156 standardMsg += 'Mismatched values: %s' % ','.join(mismatched)
1157
1158 self.fail(self._formatMessage(msg, standardMsg))
1159
1160
1161 def assertCountEqual(self, first, second, msg=None):
1162 """Asserts that two iterables have the same elements, the same number of
1163 times, without regard to order.
1164
1165 self.assertEqual(Counter(list(first)),
1166 Counter(list(second)))
1167
1168 Example:
1169 - [0, 1, 1] and [1, 0, 1] compare equal.
1170 - [0, 0, 1] and [0, 1] compare unequal.
1171
1172 """
1173 first_seq, second_seq = list(first), list(second)
1174 try:
1175 first = collections.Counter(first_seq)
1176 second = collections.Counter(second_seq)
1177 except TypeError:
1178 # Handle case with unhashable elements
1179 differences = _count_diff_all_purpose(first_seq, second_seq)
1180 else:
1181 if first == second:
1182 return
1183 differences = _count_diff_hashable(first_seq, second_seq)
1184
1185 if differences:
1186 standardMsg = 'Element counts were not equal:\n'
1187 lines = ['First has %d, Second has %d: %r' % diff for diff in differences]
1188 diffMsg = '\n'.join(lines)
1189 standardMsg = self._truncateMessage(standardMsg, diffMsg)
1190 msg = self._formatMessage(msg, standardMsg)
1191 self.fail(msg)
1192
1193 def assertMultiLineEqual(self, first, second, msg=None):
1194 """Assert that two multi-line strings are equal."""
1195 self.assertIsInstance(first, str, 'First argument is not a string')
1196 self.assertIsInstance(second, str, 'Second argument is not a string')
1197
1198 if first != second:
1199 # don't use difflib if the strings are too long
1200 if (len(first) > self._diffThreshold or
1201 len(second) > self._diffThreshold):
1202 self._baseAssertEqual(first, second, msg)
1203 firstlines = first.splitlines(keepends=True)
1204 secondlines = second.splitlines(keepends=True)
1205 if len(firstlines) == 1 and first.strip('\r\n') == first:
1206 firstlines = [first + '\n']
1207 secondlines = [second + '\n']
1208 standardMsg = '%s != %s' % _common_shorten_repr(first, second)
1209 diff = '\n' + ''.join(difflib.ndiff(firstlines, secondlines))
1210 standardMsg = self._truncateMessage(standardMsg, diff)
1211 self.fail(self._formatMessage(msg, standardMsg))
1212
1213 def assertLess(self, a, b, msg=None):
1214 """Just like self.assertTrue(a < b), but with a nicer default message."""
1215 if not a < b:
1216 standardMsg = '%s not less than %s' % (safe_repr(a), safe_repr(b))
1217 self.fail(self._formatMessage(msg, standardMsg))
1218
1219 def assertLessEqual(self, a, b, msg=None):
1220 """Just like self.assertTrue(a <= b), but with a nicer default message."""
1221 if not a <= b:
1222 standardMsg = '%s not less than or equal to %s' % (safe_repr(a), safe_repr(b))
1223 self.fail(self._formatMessage(msg, standardMsg))
1224
1225 def assertGreater(self, a, b, msg=None):
1226 """Just like self.assertTrue(a > b), but with a nicer default message."""
1227 if not a > b:
1228 standardMsg = '%s not greater than %s' % (safe_repr(a), safe_repr(b))
1229 self.fail(self._formatMessage(msg, standardMsg))
1230
1231 def assertGreaterEqual(self, a, b, msg=None):
1232 """Just like self.assertTrue(a >= b), but with a nicer default message."""
1233 if not a >= b:
1234 standardMsg = '%s not greater than or equal to %s' % (safe_repr(a), safe_repr(b))
1235 self.fail(self._formatMessage(msg, standardMsg))
1236
1237 def assertIsNone(self, obj, msg=None):
1238 """Same as self.assertTrue(obj is None), with a nicer default message."""
1239 if obj is not None:
1240 standardMsg = '%s is not None' % (safe_repr(obj),)
1241 self.fail(self._formatMessage(msg, standardMsg))
1242
1243 def assertIsNotNone(self, obj, msg=None):
1244 """Included for symmetry with assertIsNone."""
1245 if obj is None:
1246 standardMsg = 'unexpectedly None'
1247 self.fail(self._formatMessage(msg, standardMsg))
1248
1249 def assertIsInstance(self, obj, cls, msg=None):
1250 """Same as self.assertTrue(isinstance(obj, cls)), with a nicer
1251 default message."""
1252 if not isinstance(obj, cls):
1253 standardMsg = '%s is not an instance of %r' % (safe_repr(obj), cls)
1254 self.fail(self._formatMessage(msg, standardMsg))
1255
1256 def assertNotIsInstance(self, obj, cls, msg=None):
1257 """Included for symmetry with assertIsInstance."""
1258 if isinstance(obj, cls):
1259 standardMsg = '%s is an instance of %r' % (safe_repr(obj), cls)
1260 self.fail(self._formatMessage(msg, standardMsg))
1261
1262 def assertRaisesRegex(self, expected_exception, expected_regex,
1263 *args, **kwargs):
1264 """Asserts that the message in a raised exception matches a regex.
1265
1266 Args:
1267 expected_exception: Exception class expected to be raised.
1268 expected_regex: Regex (re.Pattern object or string) expected
1269 to be found in error message.
1270 args: Function to be called and extra positional args.
1271 kwargs: Extra kwargs.
1272 msg: Optional message used in case of failure. Can only be used
1273 when assertRaisesRegex is used as a context manager.
1274 """
1275 context = _AssertRaisesContext(expected_exception, self, expected_regex)
1276 return context.handle('assertRaisesRegex', args, kwargs)
1277
1278 def assertWarnsRegex(self, expected_warning, expected_regex,
1279 *args, **kwargs):
1280 """Asserts that the message in a triggered warning matches a regexp.
1281 Basic functioning is similar to assertWarns() with the addition
1282 that only warnings whose messages also match the regular expression
1283 are considered successful matches.
1284
1285 Args:
1286 expected_warning: Warning class expected to be triggered.
1287 expected_regex: Regex (re.Pattern object or string) expected
1288 to be found in error message.
1289 args: Function to be called and extra positional args.
1290 kwargs: Extra kwargs.
1291 msg: Optional message used in case of failure. Can only be used
1292 when assertWarnsRegex is used as a context manager.
1293 """
1294 context = _AssertWarnsContext(expected_warning, self, expected_regex)
1295 return context.handle('assertWarnsRegex', args, kwargs)
1296
1297 def assertRegex(self, text, expected_regex, msg=None):
1298 """Fail the test unless the text matches the regular expression."""
1299 if isinstance(expected_regex, (str, bytes)):
1300 assert expected_regex, "expected_regex must not be empty."
1301 expected_regex = re.compile(expected_regex)
1302 if not expected_regex.search(text):
1303 standardMsg = "Regex didn't match: %r not found in %r" % (
1304 expected_regex.pattern, text)
1305 # _formatMessage ensures the longMessage option is respected
1306 msg = self._formatMessage(msg, standardMsg)
1307 raise self.failureException(msg)
1308
1309 def assertNotRegex(self, text, unexpected_regex, msg=None):
1310 """Fail the test if the text matches the regular expression."""
1311 if isinstance(unexpected_regex, (str, bytes)):
1312 unexpected_regex = re.compile(unexpected_regex)
1313 match = unexpected_regex.search(text)
1314 if match:
1315 standardMsg = 'Regex matched: %r matches %r in %r' % (
1316 text[match.start() : match.end()],
1317 unexpected_regex.pattern,
1318 text)
1319 # _formatMessage ensures the longMessage option is respected
1320 msg = self._formatMessage(msg, standardMsg)
1321 raise self.failureException(msg)
1322
1323
1324 def _deprecate(original_func):
1325 def deprecated_func(*args, **kwargs):
1326 warnings.warn(
1327 'Please use {0} instead.'.format(original_func.__name__),
1328 DeprecationWarning, 2)
1329 return original_func(*args, **kwargs)
1330 return deprecated_func
1331
1332 # see #9424
1333 failUnlessEqual = assertEquals = _deprecate(assertEqual)
1334 failIfEqual = assertNotEquals = _deprecate(assertNotEqual)
1335 failUnlessAlmostEqual = assertAlmostEquals = _deprecate(assertAlmostEqual)
1336 failIfAlmostEqual = assertNotAlmostEquals = _deprecate(assertNotAlmostEqual)
1337 failUnless = assert_ = _deprecate(assertTrue)
1338 failUnlessRaises = _deprecate(assertRaises)
1339 failIf = _deprecate(assertFalse)
1340 assertRaisesRegexp = _deprecate(assertRaisesRegex)
1341 assertRegexpMatches = _deprecate(assertRegex)
1342 assertNotRegexpMatches = _deprecate(assertNotRegex)
1343
1344
1345
1346class FunctionTestCase(TestCase):
1347 """A test case that wraps a test function.
1348
1349 This is useful for slipping pre-existing test functions into the
1350 unittest framework. Optionally, set-up and tidy-up functions can be
1351 supplied. As with TestCase, the tidy-up ('tearDown') function will
1352 always be called if the set-up ('setUp') function ran successfully.
1353 """
1354
1355 def __init__(self, testFunc, setUp=None, tearDown=None, description=None):
1356 super(FunctionTestCase, self).__init__()
1357 self._setUpFunc = setUp
1358 self._tearDownFunc = tearDown
1359 self._testFunc = testFunc
1360 self._description = description
1361
1362 def setUp(self):
1363 if self._setUpFunc is not None:
1364 self._setUpFunc()
1365
1366 def tearDown(self):
1367 if self._tearDownFunc is not None:
1368 self._tearDownFunc()
1369
1370 def runTest(self):
1371 self._testFunc()
1372
1373 def id(self):
1374 return self._testFunc.__name__
1375
1376 def __eq__(self, other):
1377 if not isinstance(other, self.__class__):
1378 return NotImplemented
1379
1380 return self._setUpFunc == other._setUpFunc and \
1381 self._tearDownFunc == other._tearDownFunc and \
1382 self._testFunc == other._testFunc and \
1383 self._description == other._description
1384
1385 def __hash__(self):
1386 return hash((type(self), self._setUpFunc, self._tearDownFunc,
1387 self._testFunc, self._description))
1388
1389 def __str__(self):
1390 return "%s (%s)" % (strclass(self.__class__),
1391 self._testFunc.__name__)
1392
1393 def __repr__(self):
1394 return "<%s tec=%s>" % (strclass(self.__class__),
1395 self._testFunc)
1396
1397 def shortDescription(self):
1398 if self._description is not None:
1399 return self._description
1400 doc = self._testFunc.__doc__
1401 return doc and doc.split("\n")[0].strip() or None
1402
1403
1404class _SubTest(TestCase):
1405
1406 def __init__(self, test_case, message, params):
1407 super().__init__()
1408 self._message = message
1409 self.test_case = test_case
1410 self.params = params
1411 self.failureException = test_case.failureException
1412
1413 def runTest(self):
1414 raise NotImplementedError("subtests cannot be run directly")
1415
1416 def _subDescription(self):
1417 parts = []
1418 if self._message is not _subtest_msg_sentinel:
1419 parts.append("[{}]".format(self._message))
1420 if self.params:
1421 params_desc = ', '.join(
1422 "{}={!r}".format(k, v)
1423 for (k, v) in self.params.items())
1424 parts.append("({})".format(params_desc))
1425 return " ".join(parts) or '(<subtest>)'
1426
1427 def id(self):
1428 return "{} {}".format(self.test_case.id(), self._subDescription())
1429
1430 def shortDescription(self):
1431 """Returns a one-line description of the subtest, or None if no
1432 description has been provided.
1433 """
1434 return self.test_case.shortDescription()
1435
1436 def __str__(self):
1437 return "{} {}".format(self.test_case, self._subDescription())