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