jpayne@69: """Test case implementation""" jpayne@69: jpayne@69: import sys jpayne@69: import functools jpayne@69: import difflib jpayne@69: import logging jpayne@69: import pprint jpayne@69: import re jpayne@69: import warnings jpayne@69: import collections jpayne@69: import contextlib jpayne@69: import traceback jpayne@69: import types jpayne@69: jpayne@69: from . import result jpayne@69: from .util import (strclass, safe_repr, _count_diff_all_purpose, jpayne@69: _count_diff_hashable, _common_shorten_repr) jpayne@69: jpayne@69: __unittest = True jpayne@69: jpayne@69: _subtest_msg_sentinel = object() jpayne@69: jpayne@69: DIFF_OMITTED = ('\nDiff is %s characters long. ' jpayne@69: 'Set self.maxDiff to None to see it.') jpayne@69: jpayne@69: class SkipTest(Exception): jpayne@69: """ jpayne@69: Raise this exception in a test to skip it. jpayne@69: jpayne@69: Usually you can use TestCase.skipTest() or one of the skipping decorators jpayne@69: instead of raising this directly. jpayne@69: """ jpayne@69: jpayne@69: class _ShouldStop(Exception): jpayne@69: """ jpayne@69: The test should stop. jpayne@69: """ jpayne@69: jpayne@69: class _UnexpectedSuccess(Exception): jpayne@69: """ jpayne@69: The test was supposed to fail, but it didn't! jpayne@69: """ jpayne@69: jpayne@69: jpayne@69: class _Outcome(object): jpayne@69: def __init__(self, result=None): jpayne@69: self.expecting_failure = False jpayne@69: self.result = result jpayne@69: self.result_supports_subtests = hasattr(result, "addSubTest") jpayne@69: self.success = True jpayne@69: self.skipped = [] jpayne@69: self.expectedFailure = None jpayne@69: self.errors = [] jpayne@69: jpayne@69: @contextlib.contextmanager jpayne@69: def testPartExecutor(self, test_case, isTest=False): jpayne@69: old_success = self.success jpayne@69: self.success = True jpayne@69: try: jpayne@69: yield jpayne@69: except KeyboardInterrupt: jpayne@69: raise jpayne@69: except SkipTest as e: jpayne@69: self.success = False jpayne@69: self.skipped.append((test_case, str(e))) jpayne@69: except _ShouldStop: jpayne@69: pass jpayne@69: except: jpayne@69: exc_info = sys.exc_info() jpayne@69: if self.expecting_failure: jpayne@69: self.expectedFailure = exc_info jpayne@69: else: jpayne@69: self.success = False jpayne@69: self.errors.append((test_case, exc_info)) jpayne@69: # explicitly break a reference cycle: jpayne@69: # exc_info -> frame -> exc_info jpayne@69: exc_info = None jpayne@69: else: jpayne@69: if self.result_supports_subtests and self.success: jpayne@69: self.errors.append((test_case, None)) jpayne@69: finally: jpayne@69: self.success = self.success and old_success jpayne@69: jpayne@69: jpayne@69: def _id(obj): jpayne@69: return obj jpayne@69: jpayne@69: jpayne@69: _module_cleanups = [] jpayne@69: def addModuleCleanup(function, /, *args, **kwargs): jpayne@69: """Same as addCleanup, except the cleanup items are called even if jpayne@69: setUpModule fails (unlike tearDownModule).""" jpayne@69: _module_cleanups.append((function, args, kwargs)) jpayne@69: jpayne@69: jpayne@69: def doModuleCleanups(): jpayne@69: """Execute all module cleanup functions. Normally called for you after jpayne@69: tearDownModule.""" jpayne@69: exceptions = [] jpayne@69: while _module_cleanups: jpayne@69: function, args, kwargs = _module_cleanups.pop() jpayne@69: try: jpayne@69: function(*args, **kwargs) jpayne@69: except Exception as exc: jpayne@69: exceptions.append(exc) jpayne@69: if exceptions: jpayne@69: # Swallows all but first exception. If a multi-exception handler jpayne@69: # gets written we should use that here instead. jpayne@69: raise exceptions[0] jpayne@69: jpayne@69: jpayne@69: def skip(reason): jpayne@69: """ jpayne@69: Unconditionally skip a test. jpayne@69: """ jpayne@69: def decorator(test_item): jpayne@69: if not isinstance(test_item, type): jpayne@69: @functools.wraps(test_item) jpayne@69: def skip_wrapper(*args, **kwargs): jpayne@69: raise SkipTest(reason) jpayne@69: test_item = skip_wrapper jpayne@69: jpayne@69: test_item.__unittest_skip__ = True jpayne@69: test_item.__unittest_skip_why__ = reason jpayne@69: return test_item jpayne@69: if isinstance(reason, types.FunctionType): jpayne@69: test_item = reason jpayne@69: reason = '' jpayne@69: return decorator(test_item) jpayne@69: return decorator jpayne@69: jpayne@69: def skipIf(condition, reason): jpayne@69: """ jpayne@69: Skip a test if the condition is true. jpayne@69: """ jpayne@69: if condition: jpayne@69: return skip(reason) jpayne@69: return _id jpayne@69: jpayne@69: def skipUnless(condition, reason): jpayne@69: """ jpayne@69: Skip a test unless the condition is true. jpayne@69: """ jpayne@69: if not condition: jpayne@69: return skip(reason) jpayne@69: return _id jpayne@69: jpayne@69: def expectedFailure(test_item): jpayne@69: test_item.__unittest_expecting_failure__ = True jpayne@69: return test_item jpayne@69: jpayne@69: def _is_subtype(expected, basetype): jpayne@69: if isinstance(expected, tuple): jpayne@69: return all(_is_subtype(e, basetype) for e in expected) jpayne@69: return isinstance(expected, type) and issubclass(expected, basetype) jpayne@69: jpayne@69: class _BaseTestCaseContext: jpayne@69: jpayne@69: def __init__(self, test_case): jpayne@69: self.test_case = test_case jpayne@69: jpayne@69: def _raiseFailure(self, standardMsg): jpayne@69: msg = self.test_case._formatMessage(self.msg, standardMsg) jpayne@69: raise self.test_case.failureException(msg) jpayne@69: jpayne@69: class _AssertRaisesBaseContext(_BaseTestCaseContext): jpayne@69: jpayne@69: def __init__(self, expected, test_case, expected_regex=None): jpayne@69: _BaseTestCaseContext.__init__(self, test_case) jpayne@69: self.expected = expected jpayne@69: self.test_case = test_case jpayne@69: if expected_regex is not None: jpayne@69: expected_regex = re.compile(expected_regex) jpayne@69: self.expected_regex = expected_regex jpayne@69: self.obj_name = None jpayne@69: self.msg = None jpayne@69: jpayne@69: def handle(self, name, args, kwargs): jpayne@69: """ jpayne@69: If args is empty, assertRaises/Warns is being used as a jpayne@69: context manager, so check for a 'msg' kwarg and return self. jpayne@69: If args is not empty, call a callable passing positional and keyword jpayne@69: arguments. jpayne@69: """ jpayne@69: try: jpayne@69: if not _is_subtype(self.expected, self._base_type): jpayne@69: raise TypeError('%s() arg 1 must be %s' % jpayne@69: (name, self._base_type_str)) jpayne@69: if not args: jpayne@69: self.msg = kwargs.pop('msg', None) jpayne@69: if kwargs: jpayne@69: raise TypeError('%r is an invalid keyword argument for ' jpayne@69: 'this function' % (next(iter(kwargs)),)) jpayne@69: return self jpayne@69: jpayne@69: callable_obj, *args = args jpayne@69: try: jpayne@69: self.obj_name = callable_obj.__name__ jpayne@69: except AttributeError: jpayne@69: self.obj_name = str(callable_obj) jpayne@69: with self: jpayne@69: callable_obj(*args, **kwargs) jpayne@69: finally: jpayne@69: # bpo-23890: manually break a reference cycle jpayne@69: self = None jpayne@69: jpayne@69: jpayne@69: class _AssertRaisesContext(_AssertRaisesBaseContext): jpayne@69: """A context manager used to implement TestCase.assertRaises* methods.""" jpayne@69: jpayne@69: _base_type = BaseException jpayne@69: _base_type_str = 'an exception type or tuple of exception types' jpayne@69: jpayne@69: def __enter__(self): jpayne@69: return self jpayne@69: jpayne@69: def __exit__(self, exc_type, exc_value, tb): jpayne@69: if exc_type is None: jpayne@69: try: jpayne@69: exc_name = self.expected.__name__ jpayne@69: except AttributeError: jpayne@69: exc_name = str(self.expected) jpayne@69: if self.obj_name: jpayne@69: self._raiseFailure("{} not raised by {}".format(exc_name, jpayne@69: self.obj_name)) jpayne@69: else: jpayne@69: self._raiseFailure("{} not raised".format(exc_name)) jpayne@69: else: jpayne@69: traceback.clear_frames(tb) jpayne@69: if not issubclass(exc_type, self.expected): jpayne@69: # let unexpected exceptions pass through jpayne@69: return False jpayne@69: # store exception, without traceback, for later retrieval jpayne@69: self.exception = exc_value.with_traceback(None) jpayne@69: if self.expected_regex is None: jpayne@69: return True jpayne@69: jpayne@69: expected_regex = self.expected_regex jpayne@69: if not expected_regex.search(str(exc_value)): jpayne@69: self._raiseFailure('"{}" does not match "{}"'.format( jpayne@69: expected_regex.pattern, str(exc_value))) jpayne@69: return True jpayne@69: jpayne@69: jpayne@69: class _AssertWarnsContext(_AssertRaisesBaseContext): jpayne@69: """A context manager used to implement TestCase.assertWarns* methods.""" jpayne@69: jpayne@69: _base_type = Warning jpayne@69: _base_type_str = 'a warning type or tuple of warning types' jpayne@69: jpayne@69: def __enter__(self): jpayne@69: # The __warningregistry__'s need to be in a pristine state for tests jpayne@69: # to work properly. jpayne@69: for v in sys.modules.values(): jpayne@69: if getattr(v, '__warningregistry__', None): jpayne@69: v.__warningregistry__ = {} jpayne@69: self.warnings_manager = warnings.catch_warnings(record=True) jpayne@69: self.warnings = self.warnings_manager.__enter__() jpayne@69: warnings.simplefilter("always", self.expected) jpayne@69: return self jpayne@69: jpayne@69: def __exit__(self, exc_type, exc_value, tb): jpayne@69: self.warnings_manager.__exit__(exc_type, exc_value, tb) jpayne@69: if exc_type is not None: jpayne@69: # let unexpected exceptions pass through jpayne@69: return jpayne@69: try: jpayne@69: exc_name = self.expected.__name__ jpayne@69: except AttributeError: jpayne@69: exc_name = str(self.expected) jpayne@69: first_matching = None jpayne@69: for m in self.warnings: jpayne@69: w = m.message jpayne@69: if not isinstance(w, self.expected): jpayne@69: continue jpayne@69: if first_matching is None: jpayne@69: first_matching = w jpayne@69: if (self.expected_regex is not None and jpayne@69: not self.expected_regex.search(str(w))): jpayne@69: continue jpayne@69: # store warning for later retrieval jpayne@69: self.warning = w jpayne@69: self.filename = m.filename jpayne@69: self.lineno = m.lineno jpayne@69: return jpayne@69: # Now we simply try to choose a helpful failure message jpayne@69: if first_matching is not None: jpayne@69: self._raiseFailure('"{}" does not match "{}"'.format( jpayne@69: self.expected_regex.pattern, str(first_matching))) jpayne@69: if self.obj_name: jpayne@69: self._raiseFailure("{} not triggered by {}".format(exc_name, jpayne@69: self.obj_name)) jpayne@69: else: jpayne@69: self._raiseFailure("{} not triggered".format(exc_name)) jpayne@69: jpayne@69: jpayne@69: jpayne@69: _LoggingWatcher = collections.namedtuple("_LoggingWatcher", jpayne@69: ["records", "output"]) jpayne@69: jpayne@69: jpayne@69: class _CapturingHandler(logging.Handler): jpayne@69: """ jpayne@69: A logging handler capturing all (raw and formatted) logging output. jpayne@69: """ jpayne@69: jpayne@69: def __init__(self): jpayne@69: logging.Handler.__init__(self) jpayne@69: self.watcher = _LoggingWatcher([], []) jpayne@69: jpayne@69: def flush(self): jpayne@69: pass jpayne@69: jpayne@69: def emit(self, record): jpayne@69: self.watcher.records.append(record) jpayne@69: msg = self.format(record) jpayne@69: self.watcher.output.append(msg) jpayne@69: jpayne@69: jpayne@69: jpayne@69: class _AssertLogsContext(_BaseTestCaseContext): jpayne@69: """A context manager used to implement TestCase.assertLogs().""" jpayne@69: jpayne@69: LOGGING_FORMAT = "%(levelname)s:%(name)s:%(message)s" jpayne@69: jpayne@69: def __init__(self, test_case, logger_name, level): jpayne@69: _BaseTestCaseContext.__init__(self, test_case) jpayne@69: self.logger_name = logger_name jpayne@69: if level: jpayne@69: self.level = logging._nameToLevel.get(level, level) jpayne@69: else: jpayne@69: self.level = logging.INFO jpayne@69: self.msg = None jpayne@69: jpayne@69: def __enter__(self): jpayne@69: if isinstance(self.logger_name, logging.Logger): jpayne@69: logger = self.logger = self.logger_name jpayne@69: else: jpayne@69: logger = self.logger = logging.getLogger(self.logger_name) jpayne@69: formatter = logging.Formatter(self.LOGGING_FORMAT) jpayne@69: handler = _CapturingHandler() jpayne@69: handler.setFormatter(formatter) jpayne@69: self.watcher = handler.watcher jpayne@69: self.old_handlers = logger.handlers[:] jpayne@69: self.old_level = logger.level jpayne@69: self.old_propagate = logger.propagate jpayne@69: logger.handlers = [handler] jpayne@69: logger.setLevel(self.level) jpayne@69: logger.propagate = False jpayne@69: return handler.watcher jpayne@69: jpayne@69: def __exit__(self, exc_type, exc_value, tb): jpayne@69: self.logger.handlers = self.old_handlers jpayne@69: self.logger.propagate = self.old_propagate jpayne@69: self.logger.setLevel(self.old_level) jpayne@69: if exc_type is not None: jpayne@69: # let unexpected exceptions pass through jpayne@69: return False jpayne@69: if len(self.watcher.records) == 0: jpayne@69: self._raiseFailure( jpayne@69: "no logs of level {} or higher triggered on {}" jpayne@69: .format(logging.getLevelName(self.level), self.logger.name)) jpayne@69: jpayne@69: jpayne@69: class _OrderedChainMap(collections.ChainMap): jpayne@69: def __iter__(self): jpayne@69: seen = set() jpayne@69: for mapping in self.maps: jpayne@69: for k in mapping: jpayne@69: if k not in seen: jpayne@69: seen.add(k) jpayne@69: yield k jpayne@69: jpayne@69: jpayne@69: class TestCase(object): jpayne@69: """A class whose instances are single test cases. jpayne@69: jpayne@69: By default, the test code itself should be placed in a method named jpayne@69: 'runTest'. jpayne@69: jpayne@69: If the fixture may be used for many test cases, create as jpayne@69: many test methods as are needed. When instantiating such a TestCase jpayne@69: subclass, specify in the constructor arguments the name of the test method jpayne@69: that the instance is to execute. jpayne@69: jpayne@69: Test authors should subclass TestCase for their own tests. Construction jpayne@69: and deconstruction of the test's environment ('fixture') can be jpayne@69: implemented by overriding the 'setUp' and 'tearDown' methods respectively. jpayne@69: jpayne@69: If it is necessary to override the __init__ method, the base class jpayne@69: __init__ method must always be called. It is important that subclasses jpayne@69: should not change the signature of their __init__ method, since instances jpayne@69: of the classes are instantiated automatically by parts of the framework jpayne@69: in order to be run. jpayne@69: jpayne@69: When subclassing TestCase, you can set these attributes: jpayne@69: * failureException: determines which exception will be raised when jpayne@69: the instance's assertion methods fail; test methods raising this jpayne@69: exception will be deemed to have 'failed' rather than 'errored'. jpayne@69: * longMessage: determines whether long messages (including repr of jpayne@69: objects used in assert methods) will be printed on failure in *addition* jpayne@69: to any explicit message passed. jpayne@69: * maxDiff: sets the maximum length of a diff in failure messages jpayne@69: by assert methods using difflib. It is looked up as an instance jpayne@69: attribute so can be configured by individual tests if required. jpayne@69: """ jpayne@69: jpayne@69: failureException = AssertionError jpayne@69: jpayne@69: longMessage = True jpayne@69: jpayne@69: maxDiff = 80*8 jpayne@69: jpayne@69: # If a string is longer than _diffThreshold, use normal comparison instead jpayne@69: # of difflib. See #11763. jpayne@69: _diffThreshold = 2**16 jpayne@69: jpayne@69: # Attribute used by TestSuite for classSetUp jpayne@69: jpayne@69: _classSetupFailed = False jpayne@69: jpayne@69: _class_cleanups = [] jpayne@69: jpayne@69: def __init__(self, methodName='runTest'): jpayne@69: """Create an instance of the class that will use the named test jpayne@69: method when executed. Raises a ValueError if the instance does jpayne@69: not have a method with the specified name. jpayne@69: """ jpayne@69: self._testMethodName = methodName jpayne@69: self._outcome = None jpayne@69: self._testMethodDoc = 'No test' jpayne@69: try: jpayne@69: testMethod = getattr(self, methodName) jpayne@69: except AttributeError: jpayne@69: if methodName != 'runTest': jpayne@69: # we allow instantiation with no explicit method name jpayne@69: # but not an *incorrect* or missing method name jpayne@69: raise ValueError("no such test method in %s: %s" % jpayne@69: (self.__class__, methodName)) jpayne@69: else: jpayne@69: self._testMethodDoc = testMethod.__doc__ jpayne@69: self._cleanups = [] jpayne@69: self._subtest = None jpayne@69: jpayne@69: # Map types to custom assertEqual functions that will compare jpayne@69: # instances of said type in more detail to generate a more useful jpayne@69: # error message. jpayne@69: self._type_equality_funcs = {} jpayne@69: self.addTypeEqualityFunc(dict, 'assertDictEqual') jpayne@69: self.addTypeEqualityFunc(list, 'assertListEqual') jpayne@69: self.addTypeEqualityFunc(tuple, 'assertTupleEqual') jpayne@69: self.addTypeEqualityFunc(set, 'assertSetEqual') jpayne@69: self.addTypeEqualityFunc(frozenset, 'assertSetEqual') jpayne@69: self.addTypeEqualityFunc(str, 'assertMultiLineEqual') jpayne@69: jpayne@69: def addTypeEqualityFunc(self, typeobj, function): jpayne@69: """Add a type specific assertEqual style function to compare a type. jpayne@69: jpayne@69: This method is for use by TestCase subclasses that need to register jpayne@69: their own type equality functions to provide nicer error messages. jpayne@69: jpayne@69: Args: jpayne@69: typeobj: The data type to call this function on when both values jpayne@69: are of the same type in assertEqual(). jpayne@69: function: The callable taking two arguments and an optional jpayne@69: msg= argument that raises self.failureException with a jpayne@69: useful error message when the two arguments are not equal. jpayne@69: """ jpayne@69: self._type_equality_funcs[typeobj] = function jpayne@69: jpayne@69: def addCleanup(*args, **kwargs): jpayne@69: """Add a function, with arguments, to be called when the test is jpayne@69: completed. Functions added are called on a LIFO basis and are jpayne@69: called after tearDown on test failure or success. jpayne@69: jpayne@69: Cleanup items are called even if setUp fails (unlike tearDown).""" jpayne@69: if len(args) >= 2: jpayne@69: self, function, *args = args jpayne@69: elif not args: jpayne@69: raise TypeError("descriptor 'addCleanup' of 'TestCase' object " jpayne@69: "needs an argument") jpayne@69: elif 'function' in kwargs: jpayne@69: function = kwargs.pop('function') jpayne@69: self, *args = args jpayne@69: import warnings jpayne@69: warnings.warn("Passing 'function' as keyword argument is deprecated", jpayne@69: DeprecationWarning, stacklevel=2) jpayne@69: else: jpayne@69: raise TypeError('addCleanup expected at least 1 positional ' jpayne@69: 'argument, got %d' % (len(args)-1)) jpayne@69: args = tuple(args) jpayne@69: jpayne@69: self._cleanups.append((function, args, kwargs)) jpayne@69: addCleanup.__text_signature__ = '($self, function, /, *args, **kwargs)' jpayne@69: jpayne@69: @classmethod jpayne@69: def addClassCleanup(cls, function, /, *args, **kwargs): jpayne@69: """Same as addCleanup, except the cleanup items are called even if jpayne@69: setUpClass fails (unlike tearDownClass).""" jpayne@69: cls._class_cleanups.append((function, args, kwargs)) jpayne@69: jpayne@69: def setUp(self): jpayne@69: "Hook method for setting up the test fixture before exercising it." jpayne@69: pass jpayne@69: jpayne@69: def tearDown(self): jpayne@69: "Hook method for deconstructing the test fixture after testing it." jpayne@69: pass jpayne@69: jpayne@69: @classmethod jpayne@69: def setUpClass(cls): jpayne@69: "Hook method for setting up class fixture before running tests in the class." jpayne@69: jpayne@69: @classmethod jpayne@69: def tearDownClass(cls): jpayne@69: "Hook method for deconstructing the class fixture after running all tests in the class." jpayne@69: jpayne@69: def countTestCases(self): jpayne@69: return 1 jpayne@69: jpayne@69: def defaultTestResult(self): jpayne@69: return result.TestResult() jpayne@69: jpayne@69: def shortDescription(self): jpayne@69: """Returns a one-line description of the test, or None if no jpayne@69: description has been provided. jpayne@69: jpayne@69: The default implementation of this method returns the first line of jpayne@69: the specified test method's docstring. jpayne@69: """ jpayne@69: doc = self._testMethodDoc jpayne@69: return doc and doc.split("\n")[0].strip() or None jpayne@69: jpayne@69: jpayne@69: def id(self): jpayne@69: return "%s.%s" % (strclass(self.__class__), self._testMethodName) jpayne@69: jpayne@69: def __eq__(self, other): jpayne@69: if type(self) is not type(other): jpayne@69: return NotImplemented jpayne@69: jpayne@69: return self._testMethodName == other._testMethodName jpayne@69: jpayne@69: def __hash__(self): jpayne@69: return hash((type(self), self._testMethodName)) jpayne@69: jpayne@69: def __str__(self): jpayne@69: return "%s (%s)" % (self._testMethodName, strclass(self.__class__)) jpayne@69: jpayne@69: def __repr__(self): jpayne@69: return "<%s testMethod=%s>" % \ jpayne@69: (strclass(self.__class__), self._testMethodName) jpayne@69: jpayne@69: def _addSkip(self, result, test_case, reason): jpayne@69: addSkip = getattr(result, 'addSkip', None) jpayne@69: if addSkip is not None: jpayne@69: addSkip(test_case, reason) jpayne@69: else: jpayne@69: warnings.warn("TestResult has no addSkip method, skips not reported", jpayne@69: RuntimeWarning, 2) jpayne@69: result.addSuccess(test_case) jpayne@69: jpayne@69: @contextlib.contextmanager jpayne@69: def subTest(self, msg=_subtest_msg_sentinel, **params): jpayne@69: """Return a context manager that will return the enclosed block jpayne@69: of code in a subtest identified by the optional message and jpayne@69: keyword parameters. A failure in the subtest marks the test jpayne@69: case as failed but resumes execution at the end of the enclosed jpayne@69: block, allowing further test code to be executed. jpayne@69: """ jpayne@69: if self._outcome is None or not self._outcome.result_supports_subtests: jpayne@69: yield jpayne@69: return jpayne@69: parent = self._subtest jpayne@69: if parent is None: jpayne@69: params_map = _OrderedChainMap(params) jpayne@69: else: jpayne@69: params_map = parent.params.new_child(params) jpayne@69: self._subtest = _SubTest(self, msg, params_map) jpayne@69: try: jpayne@69: with self._outcome.testPartExecutor(self._subtest, isTest=True): jpayne@69: yield jpayne@69: if not self._outcome.success: jpayne@69: result = self._outcome.result jpayne@69: if result is not None and result.failfast: jpayne@69: raise _ShouldStop jpayne@69: elif self._outcome.expectedFailure: jpayne@69: # If the test is expecting a failure, we really want to jpayne@69: # stop now and register the expected failure. jpayne@69: raise _ShouldStop jpayne@69: finally: jpayne@69: self._subtest = parent jpayne@69: jpayne@69: def _feedErrorsToResult(self, result, errors): jpayne@69: for test, exc_info in errors: jpayne@69: if isinstance(test, _SubTest): jpayne@69: result.addSubTest(test.test_case, test, exc_info) jpayne@69: elif exc_info is not None: jpayne@69: if issubclass(exc_info[0], self.failureException): jpayne@69: result.addFailure(test, exc_info) jpayne@69: else: jpayne@69: result.addError(test, exc_info) jpayne@69: jpayne@69: def _addExpectedFailure(self, result, exc_info): jpayne@69: try: jpayne@69: addExpectedFailure = result.addExpectedFailure jpayne@69: except AttributeError: jpayne@69: warnings.warn("TestResult has no addExpectedFailure method, reporting as passes", jpayne@69: RuntimeWarning) jpayne@69: result.addSuccess(self) jpayne@69: else: jpayne@69: addExpectedFailure(self, exc_info) jpayne@69: jpayne@69: def _addUnexpectedSuccess(self, result): jpayne@69: try: jpayne@69: addUnexpectedSuccess = result.addUnexpectedSuccess jpayne@69: except AttributeError: jpayne@69: warnings.warn("TestResult has no addUnexpectedSuccess method, reporting as failure", jpayne@69: RuntimeWarning) jpayne@69: # We need to pass an actual exception and traceback to addFailure, jpayne@69: # otherwise the legacy result can choke. jpayne@69: try: jpayne@69: raise _UnexpectedSuccess from None jpayne@69: except _UnexpectedSuccess: jpayne@69: result.addFailure(self, sys.exc_info()) jpayne@69: else: jpayne@69: addUnexpectedSuccess(self) jpayne@69: jpayne@69: def _callSetUp(self): jpayne@69: self.setUp() jpayne@69: jpayne@69: def _callTestMethod(self, method): jpayne@69: method() jpayne@69: jpayne@69: def _callTearDown(self): jpayne@69: self.tearDown() jpayne@69: jpayne@69: def _callCleanup(self, function, /, *args, **kwargs): jpayne@69: function(*args, **kwargs) jpayne@69: jpayne@69: def run(self, result=None): jpayne@69: orig_result = result jpayne@69: if result is None: jpayne@69: result = self.defaultTestResult() jpayne@69: startTestRun = getattr(result, 'startTestRun', None) jpayne@69: if startTestRun is not None: jpayne@69: startTestRun() jpayne@69: jpayne@69: result.startTest(self) jpayne@69: jpayne@69: testMethod = getattr(self, self._testMethodName) jpayne@69: if (getattr(self.__class__, "__unittest_skip__", False) or jpayne@69: getattr(testMethod, "__unittest_skip__", False)): jpayne@69: # If the class or method was skipped. jpayne@69: try: jpayne@69: skip_why = (getattr(self.__class__, '__unittest_skip_why__', '') jpayne@69: or getattr(testMethod, '__unittest_skip_why__', '')) jpayne@69: self._addSkip(result, self, skip_why) jpayne@69: finally: jpayne@69: result.stopTest(self) jpayne@69: return jpayne@69: expecting_failure_method = getattr(testMethod, jpayne@69: "__unittest_expecting_failure__", False) jpayne@69: expecting_failure_class = getattr(self, jpayne@69: "__unittest_expecting_failure__", False) jpayne@69: expecting_failure = expecting_failure_class or expecting_failure_method jpayne@69: outcome = _Outcome(result) jpayne@69: try: jpayne@69: self._outcome = outcome jpayne@69: jpayne@69: with outcome.testPartExecutor(self): jpayne@69: self._callSetUp() jpayne@69: if outcome.success: jpayne@69: outcome.expecting_failure = expecting_failure jpayne@69: with outcome.testPartExecutor(self, isTest=True): jpayne@69: self._callTestMethod(testMethod) jpayne@69: outcome.expecting_failure = False jpayne@69: with outcome.testPartExecutor(self): jpayne@69: self._callTearDown() jpayne@69: jpayne@69: self.doCleanups() jpayne@69: for test, reason in outcome.skipped: jpayne@69: self._addSkip(result, test, reason) jpayne@69: self._feedErrorsToResult(result, outcome.errors) jpayne@69: if outcome.success: jpayne@69: if expecting_failure: jpayne@69: if outcome.expectedFailure: jpayne@69: self._addExpectedFailure(result, outcome.expectedFailure) jpayne@69: else: jpayne@69: self._addUnexpectedSuccess(result) jpayne@69: else: jpayne@69: result.addSuccess(self) jpayne@69: return result jpayne@69: finally: jpayne@69: result.stopTest(self) jpayne@69: if orig_result is None: jpayne@69: stopTestRun = getattr(result, 'stopTestRun', None) jpayne@69: if stopTestRun is not None: jpayne@69: stopTestRun() jpayne@69: jpayne@69: # explicitly break reference cycles: jpayne@69: # outcome.errors -> frame -> outcome -> outcome.errors jpayne@69: # outcome.expectedFailure -> frame -> outcome -> outcome.expectedFailure jpayne@69: outcome.errors.clear() jpayne@69: outcome.expectedFailure = None jpayne@69: jpayne@69: # clear the outcome, no more needed jpayne@69: self._outcome = None jpayne@69: jpayne@69: def doCleanups(self): jpayne@69: """Execute all cleanup functions. Normally called for you after jpayne@69: tearDown.""" jpayne@69: outcome = self._outcome or _Outcome() jpayne@69: while self._cleanups: jpayne@69: function, args, kwargs = self._cleanups.pop() jpayne@69: with outcome.testPartExecutor(self): jpayne@69: self._callCleanup(function, *args, **kwargs) jpayne@69: jpayne@69: # return this for backwards compatibility jpayne@69: # even though we no longer use it internally jpayne@69: return outcome.success jpayne@69: jpayne@69: @classmethod jpayne@69: def doClassCleanups(cls): jpayne@69: """Execute all class cleanup functions. Normally called for you after jpayne@69: tearDownClass.""" jpayne@69: cls.tearDown_exceptions = [] jpayne@69: while cls._class_cleanups: jpayne@69: function, args, kwargs = cls._class_cleanups.pop() jpayne@69: try: jpayne@69: function(*args, **kwargs) jpayne@69: except Exception as exc: jpayne@69: cls.tearDown_exceptions.append(sys.exc_info()) jpayne@69: jpayne@69: def __call__(self, *args, **kwds): jpayne@69: return self.run(*args, **kwds) jpayne@69: jpayne@69: def debug(self): jpayne@69: """Run the test without collecting errors in a TestResult""" jpayne@69: self.setUp() jpayne@69: getattr(self, self._testMethodName)() jpayne@69: self.tearDown() jpayne@69: while self._cleanups: jpayne@69: function, args, kwargs = self._cleanups.pop(-1) jpayne@69: function(*args, **kwargs) jpayne@69: jpayne@69: def skipTest(self, reason): jpayne@69: """Skip this test.""" jpayne@69: raise SkipTest(reason) jpayne@69: jpayne@69: def fail(self, msg=None): jpayne@69: """Fail immediately, with the given message.""" jpayne@69: raise self.failureException(msg) jpayne@69: jpayne@69: def assertFalse(self, expr, msg=None): jpayne@69: """Check that the expression is false.""" jpayne@69: if expr: jpayne@69: msg = self._formatMessage(msg, "%s is not false" % safe_repr(expr)) jpayne@69: raise self.failureException(msg) jpayne@69: jpayne@69: def assertTrue(self, expr, msg=None): jpayne@69: """Check that the expression is true.""" jpayne@69: if not expr: jpayne@69: msg = self._formatMessage(msg, "%s is not true" % safe_repr(expr)) jpayne@69: raise self.failureException(msg) jpayne@69: jpayne@69: def _formatMessage(self, msg, standardMsg): jpayne@69: """Honour the longMessage attribute when generating failure messages. jpayne@69: If longMessage is False this means: jpayne@69: * Use only an explicit message if it is provided jpayne@69: * Otherwise use the standard message for the assert jpayne@69: jpayne@69: If longMessage is True: jpayne@69: * Use the standard message jpayne@69: * If an explicit message is provided, plus ' : ' and the explicit message jpayne@69: """ jpayne@69: if not self.longMessage: jpayne@69: return msg or standardMsg jpayne@69: if msg is None: jpayne@69: return standardMsg jpayne@69: try: jpayne@69: # don't switch to '{}' formatting in Python 2.X jpayne@69: # it changes the way unicode input is handled jpayne@69: return '%s : %s' % (standardMsg, msg) jpayne@69: except UnicodeDecodeError: jpayne@69: return '%s : %s' % (safe_repr(standardMsg), safe_repr(msg)) jpayne@69: jpayne@69: def assertRaises(self, expected_exception, *args, **kwargs): jpayne@69: """Fail unless an exception of class expected_exception is raised jpayne@69: by the callable when invoked with specified positional and jpayne@69: keyword arguments. If a different type of exception is jpayne@69: raised, it will not be caught, and the test case will be jpayne@69: deemed to have suffered an error, exactly as for an jpayne@69: unexpected exception. jpayne@69: jpayne@69: If called with the callable and arguments omitted, will return a jpayne@69: context object used like this:: jpayne@69: jpayne@69: with self.assertRaises(SomeException): jpayne@69: do_something() jpayne@69: jpayne@69: An optional keyword argument 'msg' can be provided when assertRaises jpayne@69: is used as a context object. jpayne@69: jpayne@69: The context manager keeps a reference to the exception as jpayne@69: the 'exception' attribute. This allows you to inspect the jpayne@69: exception after the assertion:: jpayne@69: jpayne@69: with self.assertRaises(SomeException) as cm: jpayne@69: do_something() jpayne@69: the_exception = cm.exception jpayne@69: self.assertEqual(the_exception.error_code, 3) jpayne@69: """ jpayne@69: context = _AssertRaisesContext(expected_exception, self) jpayne@69: try: jpayne@69: return context.handle('assertRaises', args, kwargs) jpayne@69: finally: jpayne@69: # bpo-23890: manually break a reference cycle jpayne@69: context = None jpayne@69: jpayne@69: def assertWarns(self, expected_warning, *args, **kwargs): jpayne@69: """Fail unless a warning of class warnClass is triggered jpayne@69: by the callable when invoked with specified positional and jpayne@69: keyword arguments. If a different type of warning is jpayne@69: triggered, it will not be handled: depending on the other jpayne@69: warning filtering rules in effect, it might be silenced, printed jpayne@69: out, or raised as an exception. jpayne@69: jpayne@69: If called with the callable and arguments omitted, will return a jpayne@69: context object used like this:: jpayne@69: jpayne@69: with self.assertWarns(SomeWarning): jpayne@69: do_something() jpayne@69: jpayne@69: An optional keyword argument 'msg' can be provided when assertWarns jpayne@69: is used as a context object. jpayne@69: jpayne@69: The context manager keeps a reference to the first matching jpayne@69: warning as the 'warning' attribute; similarly, the 'filename' jpayne@69: and 'lineno' attributes give you information about the line jpayne@69: of Python code from which the warning was triggered. jpayne@69: This allows you to inspect the warning after the assertion:: jpayne@69: jpayne@69: with self.assertWarns(SomeWarning) as cm: jpayne@69: do_something() jpayne@69: the_warning = cm.warning jpayne@69: self.assertEqual(the_warning.some_attribute, 147) jpayne@69: """ jpayne@69: context = _AssertWarnsContext(expected_warning, self) jpayne@69: return context.handle('assertWarns', args, kwargs) jpayne@69: jpayne@69: def assertLogs(self, logger=None, level=None): jpayne@69: """Fail unless a log message of level *level* or higher is emitted jpayne@69: on *logger_name* or its children. If omitted, *level* defaults to jpayne@69: INFO and *logger* defaults to the root logger. jpayne@69: jpayne@69: This method must be used as a context manager, and will yield jpayne@69: a recording object with two attributes: `output` and `records`. jpayne@69: At the end of the context manager, the `output` attribute will jpayne@69: be a list of the matching formatted log messages and the jpayne@69: `records` attribute will be a list of the corresponding LogRecord jpayne@69: objects. jpayne@69: jpayne@69: Example:: jpayne@69: jpayne@69: with self.assertLogs('foo', level='INFO') as cm: jpayne@69: logging.getLogger('foo').info('first message') jpayne@69: logging.getLogger('foo.bar').error('second message') jpayne@69: self.assertEqual(cm.output, ['INFO:foo:first message', jpayne@69: 'ERROR:foo.bar:second message']) jpayne@69: """ jpayne@69: return _AssertLogsContext(self, logger, level) jpayne@69: jpayne@69: def _getAssertEqualityFunc(self, first, second): jpayne@69: """Get a detailed comparison function for the types of the two args. jpayne@69: jpayne@69: Returns: A callable accepting (first, second, msg=None) that will jpayne@69: raise a failure exception if first != second with a useful human jpayne@69: readable error message for those types. jpayne@69: """ jpayne@69: # jpayne@69: # NOTE(gregory.p.smith): I considered isinstance(first, type(second)) jpayne@69: # and vice versa. I opted for the conservative approach in case jpayne@69: # subclasses are not intended to be compared in detail to their super jpayne@69: # class instances using a type equality func. This means testing jpayne@69: # subtypes won't automagically use the detailed comparison. Callers jpayne@69: # should use their type specific assertSpamEqual method to compare jpayne@69: # subclasses if the detailed comparison is desired and appropriate. jpayne@69: # See the discussion in http://bugs.python.org/issue2578. jpayne@69: # jpayne@69: if type(first) is type(second): jpayne@69: asserter = self._type_equality_funcs.get(type(first)) jpayne@69: if asserter is not None: jpayne@69: if isinstance(asserter, str): jpayne@69: asserter = getattr(self, asserter) jpayne@69: return asserter jpayne@69: jpayne@69: return self._baseAssertEqual jpayne@69: jpayne@69: def _baseAssertEqual(self, first, second, msg=None): jpayne@69: """The default assertEqual implementation, not type specific.""" jpayne@69: if not first == second: jpayne@69: standardMsg = '%s != %s' % _common_shorten_repr(first, second) jpayne@69: msg = self._formatMessage(msg, standardMsg) jpayne@69: raise self.failureException(msg) jpayne@69: jpayne@69: def assertEqual(self, first, second, msg=None): jpayne@69: """Fail if the two objects are unequal as determined by the '==' jpayne@69: operator. jpayne@69: """ jpayne@69: assertion_func = self._getAssertEqualityFunc(first, second) jpayne@69: assertion_func(first, second, msg=msg) jpayne@69: jpayne@69: def assertNotEqual(self, first, second, msg=None): jpayne@69: """Fail if the two objects are equal as determined by the '!=' jpayne@69: operator. jpayne@69: """ jpayne@69: if not first != second: jpayne@69: msg = self._formatMessage(msg, '%s == %s' % (safe_repr(first), jpayne@69: safe_repr(second))) jpayne@69: raise self.failureException(msg) jpayne@69: jpayne@69: def assertAlmostEqual(self, first, second, places=None, msg=None, jpayne@69: delta=None): jpayne@69: """Fail if the two objects are unequal as determined by their jpayne@69: difference rounded to the given number of decimal places jpayne@69: (default 7) and comparing to zero, or by comparing that the jpayne@69: difference between the two objects is more than the given jpayne@69: delta. jpayne@69: jpayne@69: Note that decimal places (from zero) are usually not the same jpayne@69: as significant digits (measured from the most significant digit). jpayne@69: jpayne@69: If the two objects compare equal then they will automatically jpayne@69: compare almost equal. jpayne@69: """ jpayne@69: if first == second: jpayne@69: # shortcut jpayne@69: return jpayne@69: if delta is not None and places is not None: jpayne@69: raise TypeError("specify delta or places not both") jpayne@69: jpayne@69: diff = abs(first - second) jpayne@69: if delta is not None: jpayne@69: if diff <= delta: jpayne@69: return jpayne@69: jpayne@69: standardMsg = '%s != %s within %s delta (%s difference)' % ( jpayne@69: safe_repr(first), jpayne@69: safe_repr(second), jpayne@69: safe_repr(delta), jpayne@69: safe_repr(diff)) jpayne@69: else: jpayne@69: if places is None: jpayne@69: places = 7 jpayne@69: jpayne@69: if round(diff, places) == 0: jpayne@69: return jpayne@69: jpayne@69: standardMsg = '%s != %s within %r places (%s difference)' % ( jpayne@69: safe_repr(first), jpayne@69: safe_repr(second), jpayne@69: places, jpayne@69: safe_repr(diff)) jpayne@69: msg = self._formatMessage(msg, standardMsg) jpayne@69: raise self.failureException(msg) jpayne@69: jpayne@69: def assertNotAlmostEqual(self, first, second, places=None, msg=None, jpayne@69: delta=None): jpayne@69: """Fail if the two objects are equal as determined by their jpayne@69: difference rounded to the given number of decimal places jpayne@69: (default 7) and comparing to zero, or by comparing that the jpayne@69: difference between the two objects is less than the given delta. jpayne@69: jpayne@69: Note that decimal places (from zero) are usually not the same jpayne@69: as significant digits (measured from the most significant digit). jpayne@69: jpayne@69: Objects that are equal automatically fail. jpayne@69: """ jpayne@69: if delta is not None and places is not None: jpayne@69: raise TypeError("specify delta or places not both") jpayne@69: diff = abs(first - second) jpayne@69: if delta is not None: jpayne@69: if not (first == second) and diff > delta: jpayne@69: return jpayne@69: standardMsg = '%s == %s within %s delta (%s difference)' % ( jpayne@69: safe_repr(first), jpayne@69: safe_repr(second), jpayne@69: safe_repr(delta), jpayne@69: safe_repr(diff)) jpayne@69: else: jpayne@69: if places is None: jpayne@69: places = 7 jpayne@69: if not (first == second) and round(diff, places) != 0: jpayne@69: return jpayne@69: standardMsg = '%s == %s within %r places' % (safe_repr(first), jpayne@69: safe_repr(second), jpayne@69: places) jpayne@69: jpayne@69: msg = self._formatMessage(msg, standardMsg) jpayne@69: raise self.failureException(msg) jpayne@69: jpayne@69: def assertSequenceEqual(self, seq1, seq2, msg=None, seq_type=None): jpayne@69: """An equality assertion for ordered sequences (like lists and tuples). jpayne@69: jpayne@69: For the purposes of this function, a valid ordered sequence type is one jpayne@69: which can be indexed, has a length, and has an equality operator. jpayne@69: jpayne@69: Args: jpayne@69: seq1: The first sequence to compare. jpayne@69: seq2: The second sequence to compare. jpayne@69: seq_type: The expected datatype of the sequences, or None if no jpayne@69: datatype should be enforced. jpayne@69: msg: Optional message to use on failure instead of a list of jpayne@69: differences. jpayne@69: """ jpayne@69: if seq_type is not None: jpayne@69: seq_type_name = seq_type.__name__ jpayne@69: if not isinstance(seq1, seq_type): jpayne@69: raise self.failureException('First sequence is not a %s: %s' jpayne@69: % (seq_type_name, safe_repr(seq1))) jpayne@69: if not isinstance(seq2, seq_type): jpayne@69: raise self.failureException('Second sequence is not a %s: %s' jpayne@69: % (seq_type_name, safe_repr(seq2))) jpayne@69: else: jpayne@69: seq_type_name = "sequence" jpayne@69: jpayne@69: differing = None jpayne@69: try: jpayne@69: len1 = len(seq1) jpayne@69: except (TypeError, NotImplementedError): jpayne@69: differing = 'First %s has no length. Non-sequence?' % ( jpayne@69: seq_type_name) jpayne@69: jpayne@69: if differing is None: jpayne@69: try: jpayne@69: len2 = len(seq2) jpayne@69: except (TypeError, NotImplementedError): jpayne@69: differing = 'Second %s has no length. Non-sequence?' % ( jpayne@69: seq_type_name) jpayne@69: jpayne@69: if differing is None: jpayne@69: if seq1 == seq2: jpayne@69: return jpayne@69: jpayne@69: differing = '%ss differ: %s != %s\n' % ( jpayne@69: (seq_type_name.capitalize(),) + jpayne@69: _common_shorten_repr(seq1, seq2)) jpayne@69: jpayne@69: for i in range(min(len1, len2)): jpayne@69: try: jpayne@69: item1 = seq1[i] jpayne@69: except (TypeError, IndexError, NotImplementedError): jpayne@69: differing += ('\nUnable to index element %d of first %s\n' % jpayne@69: (i, seq_type_name)) jpayne@69: break jpayne@69: jpayne@69: try: jpayne@69: item2 = seq2[i] jpayne@69: except (TypeError, IndexError, NotImplementedError): jpayne@69: differing += ('\nUnable to index element %d of second %s\n' % jpayne@69: (i, seq_type_name)) jpayne@69: break jpayne@69: jpayne@69: if item1 != item2: jpayne@69: differing += ('\nFirst differing element %d:\n%s\n%s\n' % jpayne@69: ((i,) + _common_shorten_repr(item1, item2))) jpayne@69: break jpayne@69: else: jpayne@69: if (len1 == len2 and seq_type is None and jpayne@69: type(seq1) != type(seq2)): jpayne@69: # The sequences are the same, but have differing types. jpayne@69: return jpayne@69: jpayne@69: if len1 > len2: jpayne@69: differing += ('\nFirst %s contains %d additional ' jpayne@69: 'elements.\n' % (seq_type_name, len1 - len2)) jpayne@69: try: jpayne@69: differing += ('First extra element %d:\n%s\n' % jpayne@69: (len2, safe_repr(seq1[len2]))) jpayne@69: except (TypeError, IndexError, NotImplementedError): jpayne@69: differing += ('Unable to index element %d ' jpayne@69: 'of first %s\n' % (len2, seq_type_name)) jpayne@69: elif len1 < len2: jpayne@69: differing += ('\nSecond %s contains %d additional ' jpayne@69: 'elements.\n' % (seq_type_name, len2 - len1)) jpayne@69: try: jpayne@69: differing += ('First extra element %d:\n%s\n' % jpayne@69: (len1, safe_repr(seq2[len1]))) jpayne@69: except (TypeError, IndexError, NotImplementedError): jpayne@69: differing += ('Unable to index element %d ' jpayne@69: 'of second %s\n' % (len1, seq_type_name)) jpayne@69: standardMsg = differing jpayne@69: diffMsg = '\n' + '\n'.join( jpayne@69: difflib.ndiff(pprint.pformat(seq1).splitlines(), jpayne@69: pprint.pformat(seq2).splitlines())) jpayne@69: jpayne@69: standardMsg = self._truncateMessage(standardMsg, diffMsg) jpayne@69: msg = self._formatMessage(msg, standardMsg) jpayne@69: self.fail(msg) jpayne@69: jpayne@69: def _truncateMessage(self, message, diff): jpayne@69: max_diff = self.maxDiff jpayne@69: if max_diff is None or len(diff) <= max_diff: jpayne@69: return message + diff jpayne@69: return message + (DIFF_OMITTED % len(diff)) jpayne@69: jpayne@69: def assertListEqual(self, list1, list2, msg=None): jpayne@69: """A list-specific equality assertion. jpayne@69: jpayne@69: Args: jpayne@69: list1: The first list to compare. jpayne@69: list2: The second list to compare. jpayne@69: msg: Optional message to use on failure instead of a list of jpayne@69: differences. jpayne@69: jpayne@69: """ jpayne@69: self.assertSequenceEqual(list1, list2, msg, seq_type=list) jpayne@69: jpayne@69: def assertTupleEqual(self, tuple1, tuple2, msg=None): jpayne@69: """A tuple-specific equality assertion. jpayne@69: jpayne@69: Args: jpayne@69: tuple1: The first tuple to compare. jpayne@69: tuple2: The second tuple to compare. jpayne@69: msg: Optional message to use on failure instead of a list of jpayne@69: differences. jpayne@69: """ jpayne@69: self.assertSequenceEqual(tuple1, tuple2, msg, seq_type=tuple) jpayne@69: jpayne@69: def assertSetEqual(self, set1, set2, msg=None): jpayne@69: """A set-specific equality assertion. jpayne@69: jpayne@69: Args: jpayne@69: set1: The first set to compare. jpayne@69: set2: The second set to compare. jpayne@69: msg: Optional message to use on failure instead of a list of jpayne@69: differences. jpayne@69: jpayne@69: assertSetEqual uses ducktyping to support different types of sets, and jpayne@69: is optimized for sets specifically (parameters must support a jpayne@69: difference method). jpayne@69: """ jpayne@69: try: jpayne@69: difference1 = set1.difference(set2) jpayne@69: except TypeError as e: jpayne@69: self.fail('invalid type when attempting set difference: %s' % e) jpayne@69: except AttributeError as e: jpayne@69: self.fail('first argument does not support set difference: %s' % e) jpayne@69: jpayne@69: try: jpayne@69: difference2 = set2.difference(set1) jpayne@69: except TypeError as e: jpayne@69: self.fail('invalid type when attempting set difference: %s' % e) jpayne@69: except AttributeError as e: jpayne@69: self.fail('second argument does not support set difference: %s' % e) jpayne@69: jpayne@69: if not (difference1 or difference2): jpayne@69: return jpayne@69: jpayne@69: lines = [] jpayne@69: if difference1: jpayne@69: lines.append('Items in the first set but not the second:') jpayne@69: for item in difference1: jpayne@69: lines.append(repr(item)) jpayne@69: if difference2: jpayne@69: lines.append('Items in the second set but not the first:') jpayne@69: for item in difference2: jpayne@69: lines.append(repr(item)) jpayne@69: jpayne@69: standardMsg = '\n'.join(lines) jpayne@69: self.fail(self._formatMessage(msg, standardMsg)) jpayne@69: jpayne@69: def assertIn(self, member, container, msg=None): jpayne@69: """Just like self.assertTrue(a in b), but with a nicer default message.""" jpayne@69: if member not in container: jpayne@69: standardMsg = '%s not found in %s' % (safe_repr(member), jpayne@69: safe_repr(container)) jpayne@69: self.fail(self._formatMessage(msg, standardMsg)) jpayne@69: jpayne@69: def assertNotIn(self, member, container, msg=None): jpayne@69: """Just like self.assertTrue(a not in b), but with a nicer default message.""" jpayne@69: if member in container: jpayne@69: standardMsg = '%s unexpectedly found in %s' % (safe_repr(member), jpayne@69: safe_repr(container)) jpayne@69: self.fail(self._formatMessage(msg, standardMsg)) jpayne@69: jpayne@69: def assertIs(self, expr1, expr2, msg=None): jpayne@69: """Just like self.assertTrue(a is b), but with a nicer default message.""" jpayne@69: if expr1 is not expr2: jpayne@69: standardMsg = '%s is not %s' % (safe_repr(expr1), jpayne@69: safe_repr(expr2)) jpayne@69: self.fail(self._formatMessage(msg, standardMsg)) jpayne@69: jpayne@69: def assertIsNot(self, expr1, expr2, msg=None): jpayne@69: """Just like self.assertTrue(a is not b), but with a nicer default message.""" jpayne@69: if expr1 is expr2: jpayne@69: standardMsg = 'unexpectedly identical: %s' % (safe_repr(expr1),) jpayne@69: self.fail(self._formatMessage(msg, standardMsg)) jpayne@69: jpayne@69: def assertDictEqual(self, d1, d2, msg=None): jpayne@69: self.assertIsInstance(d1, dict, 'First argument is not a dictionary') jpayne@69: self.assertIsInstance(d2, dict, 'Second argument is not a dictionary') jpayne@69: jpayne@69: if d1 != d2: jpayne@69: standardMsg = '%s != %s' % _common_shorten_repr(d1, d2) jpayne@69: diff = ('\n' + '\n'.join(difflib.ndiff( jpayne@69: pprint.pformat(d1).splitlines(), jpayne@69: pprint.pformat(d2).splitlines()))) jpayne@69: standardMsg = self._truncateMessage(standardMsg, diff) jpayne@69: self.fail(self._formatMessage(msg, standardMsg)) jpayne@69: jpayne@69: def assertDictContainsSubset(self, subset, dictionary, msg=None): jpayne@69: """Checks whether dictionary is a superset of subset.""" jpayne@69: warnings.warn('assertDictContainsSubset is deprecated', jpayne@69: DeprecationWarning) jpayne@69: missing = [] jpayne@69: mismatched = [] jpayne@69: for key, value in subset.items(): jpayne@69: if key not in dictionary: jpayne@69: missing.append(key) jpayne@69: elif value != dictionary[key]: jpayne@69: mismatched.append('%s, expected: %s, actual: %s' % jpayne@69: (safe_repr(key), safe_repr(value), jpayne@69: safe_repr(dictionary[key]))) jpayne@69: jpayne@69: if not (missing or mismatched): jpayne@69: return jpayne@69: jpayne@69: standardMsg = '' jpayne@69: if missing: jpayne@69: standardMsg = 'Missing: %s' % ','.join(safe_repr(m) for m in jpayne@69: missing) jpayne@69: if mismatched: jpayne@69: if standardMsg: jpayne@69: standardMsg += '; ' jpayne@69: standardMsg += 'Mismatched values: %s' % ','.join(mismatched) jpayne@69: jpayne@69: self.fail(self._formatMessage(msg, standardMsg)) jpayne@69: jpayne@69: jpayne@69: def assertCountEqual(self, first, second, msg=None): jpayne@69: """Asserts that two iterables have the same elements, the same number of jpayne@69: times, without regard to order. jpayne@69: jpayne@69: self.assertEqual(Counter(list(first)), jpayne@69: Counter(list(second))) jpayne@69: jpayne@69: Example: jpayne@69: - [0, 1, 1] and [1, 0, 1] compare equal. jpayne@69: - [0, 0, 1] and [0, 1] compare unequal. jpayne@69: jpayne@69: """ jpayne@69: first_seq, second_seq = list(first), list(second) jpayne@69: try: jpayne@69: first = collections.Counter(first_seq) jpayne@69: second = collections.Counter(second_seq) jpayne@69: except TypeError: jpayne@69: # Handle case with unhashable elements jpayne@69: differences = _count_diff_all_purpose(first_seq, second_seq) jpayne@69: else: jpayne@69: if first == second: jpayne@69: return jpayne@69: differences = _count_diff_hashable(first_seq, second_seq) jpayne@69: jpayne@69: if differences: jpayne@69: standardMsg = 'Element counts were not equal:\n' jpayne@69: lines = ['First has %d, Second has %d: %r' % diff for diff in differences] jpayne@69: diffMsg = '\n'.join(lines) jpayne@69: standardMsg = self._truncateMessage(standardMsg, diffMsg) jpayne@69: msg = self._formatMessage(msg, standardMsg) jpayne@69: self.fail(msg) jpayne@69: jpayne@69: def assertMultiLineEqual(self, first, second, msg=None): jpayne@69: """Assert that two multi-line strings are equal.""" jpayne@69: self.assertIsInstance(first, str, 'First argument is not a string') jpayne@69: self.assertIsInstance(second, str, 'Second argument is not a string') jpayne@69: jpayne@69: if first != second: jpayne@69: # don't use difflib if the strings are too long jpayne@69: if (len(first) > self._diffThreshold or jpayne@69: len(second) > self._diffThreshold): jpayne@69: self._baseAssertEqual(first, second, msg) jpayne@69: firstlines = first.splitlines(keepends=True) jpayne@69: secondlines = second.splitlines(keepends=True) jpayne@69: if len(firstlines) == 1 and first.strip('\r\n') == first: jpayne@69: firstlines = [first + '\n'] jpayne@69: secondlines = [second + '\n'] jpayne@69: standardMsg = '%s != %s' % _common_shorten_repr(first, second) jpayne@69: diff = '\n' + ''.join(difflib.ndiff(firstlines, secondlines)) jpayne@69: standardMsg = self._truncateMessage(standardMsg, diff) jpayne@69: self.fail(self._formatMessage(msg, standardMsg)) jpayne@69: jpayne@69: def assertLess(self, a, b, msg=None): jpayne@69: """Just like self.assertTrue(a < b), but with a nicer default message.""" jpayne@69: if not a < b: jpayne@69: standardMsg = '%s not less than %s' % (safe_repr(a), safe_repr(b)) jpayne@69: self.fail(self._formatMessage(msg, standardMsg)) jpayne@69: jpayne@69: def assertLessEqual(self, a, b, msg=None): jpayne@69: """Just like self.assertTrue(a <= b), but with a nicer default message.""" jpayne@69: if not a <= b: jpayne@69: standardMsg = '%s not less than or equal to %s' % (safe_repr(a), safe_repr(b)) jpayne@69: self.fail(self._formatMessage(msg, standardMsg)) jpayne@69: jpayne@69: def assertGreater(self, a, b, msg=None): jpayne@69: """Just like self.assertTrue(a > b), but with a nicer default message.""" jpayne@69: if not a > b: jpayne@69: standardMsg = '%s not greater than %s' % (safe_repr(a), safe_repr(b)) jpayne@69: self.fail(self._formatMessage(msg, standardMsg)) jpayne@69: jpayne@69: def assertGreaterEqual(self, a, b, msg=None): jpayne@69: """Just like self.assertTrue(a >= b), but with a nicer default message.""" jpayne@69: if not a >= b: jpayne@69: standardMsg = '%s not greater than or equal to %s' % (safe_repr(a), safe_repr(b)) jpayne@69: self.fail(self._formatMessage(msg, standardMsg)) jpayne@69: jpayne@69: def assertIsNone(self, obj, msg=None): jpayne@69: """Same as self.assertTrue(obj is None), with a nicer default message.""" jpayne@69: if obj is not None: jpayne@69: standardMsg = '%s is not None' % (safe_repr(obj),) jpayne@69: self.fail(self._formatMessage(msg, standardMsg)) jpayne@69: jpayne@69: def assertIsNotNone(self, obj, msg=None): jpayne@69: """Included for symmetry with assertIsNone.""" jpayne@69: if obj is None: jpayne@69: standardMsg = 'unexpectedly None' jpayne@69: self.fail(self._formatMessage(msg, standardMsg)) jpayne@69: jpayne@69: def assertIsInstance(self, obj, cls, msg=None): jpayne@69: """Same as self.assertTrue(isinstance(obj, cls)), with a nicer jpayne@69: default message.""" jpayne@69: if not isinstance(obj, cls): jpayne@69: standardMsg = '%s is not an instance of %r' % (safe_repr(obj), cls) jpayne@69: self.fail(self._formatMessage(msg, standardMsg)) jpayne@69: jpayne@69: def assertNotIsInstance(self, obj, cls, msg=None): jpayne@69: """Included for symmetry with assertIsInstance.""" jpayne@69: if isinstance(obj, cls): jpayne@69: standardMsg = '%s is an instance of %r' % (safe_repr(obj), cls) jpayne@69: self.fail(self._formatMessage(msg, standardMsg)) jpayne@69: jpayne@69: def assertRaisesRegex(self, expected_exception, expected_regex, jpayne@69: *args, **kwargs): jpayne@69: """Asserts that the message in a raised exception matches a regex. jpayne@69: jpayne@69: Args: jpayne@69: expected_exception: Exception class expected to be raised. jpayne@69: expected_regex: Regex (re.Pattern object or string) expected jpayne@69: to be found in error message. jpayne@69: args: Function to be called and extra positional args. jpayne@69: kwargs: Extra kwargs. jpayne@69: msg: Optional message used in case of failure. Can only be used jpayne@69: when assertRaisesRegex is used as a context manager. jpayne@69: """ jpayne@69: context = _AssertRaisesContext(expected_exception, self, expected_regex) jpayne@69: return context.handle('assertRaisesRegex', args, kwargs) jpayne@69: jpayne@69: def assertWarnsRegex(self, expected_warning, expected_regex, jpayne@69: *args, **kwargs): jpayne@69: """Asserts that the message in a triggered warning matches a regexp. jpayne@69: Basic functioning is similar to assertWarns() with the addition jpayne@69: that only warnings whose messages also match the regular expression jpayne@69: are considered successful matches. jpayne@69: jpayne@69: Args: jpayne@69: expected_warning: Warning class expected to be triggered. jpayne@69: expected_regex: Regex (re.Pattern object or string) expected jpayne@69: to be found in error message. jpayne@69: args: Function to be called and extra positional args. jpayne@69: kwargs: Extra kwargs. jpayne@69: msg: Optional message used in case of failure. Can only be used jpayne@69: when assertWarnsRegex is used as a context manager. jpayne@69: """ jpayne@69: context = _AssertWarnsContext(expected_warning, self, expected_regex) jpayne@69: return context.handle('assertWarnsRegex', args, kwargs) jpayne@69: jpayne@69: def assertRegex(self, text, expected_regex, msg=None): jpayne@69: """Fail the test unless the text matches the regular expression.""" jpayne@69: if isinstance(expected_regex, (str, bytes)): jpayne@69: assert expected_regex, "expected_regex must not be empty." jpayne@69: expected_regex = re.compile(expected_regex) jpayne@69: if not expected_regex.search(text): jpayne@69: standardMsg = "Regex didn't match: %r not found in %r" % ( jpayne@69: expected_regex.pattern, text) jpayne@69: # _formatMessage ensures the longMessage option is respected jpayne@69: msg = self._formatMessage(msg, standardMsg) jpayne@69: raise self.failureException(msg) jpayne@69: jpayne@69: def assertNotRegex(self, text, unexpected_regex, msg=None): jpayne@69: """Fail the test if the text matches the regular expression.""" jpayne@69: if isinstance(unexpected_regex, (str, bytes)): jpayne@69: unexpected_regex = re.compile(unexpected_regex) jpayne@69: match = unexpected_regex.search(text) jpayne@69: if match: jpayne@69: standardMsg = 'Regex matched: %r matches %r in %r' % ( jpayne@69: text[match.start() : match.end()], jpayne@69: unexpected_regex.pattern, jpayne@69: text) jpayne@69: # _formatMessage ensures the longMessage option is respected jpayne@69: msg = self._formatMessage(msg, standardMsg) jpayne@69: raise self.failureException(msg) jpayne@69: jpayne@69: jpayne@69: def _deprecate(original_func): jpayne@69: def deprecated_func(*args, **kwargs): jpayne@69: warnings.warn( jpayne@69: 'Please use {0} instead.'.format(original_func.__name__), jpayne@69: DeprecationWarning, 2) jpayne@69: return original_func(*args, **kwargs) jpayne@69: return deprecated_func jpayne@69: jpayne@69: # see #9424 jpayne@69: failUnlessEqual = assertEquals = _deprecate(assertEqual) jpayne@69: failIfEqual = assertNotEquals = _deprecate(assertNotEqual) jpayne@69: failUnlessAlmostEqual = assertAlmostEquals = _deprecate(assertAlmostEqual) jpayne@69: failIfAlmostEqual = assertNotAlmostEquals = _deprecate(assertNotAlmostEqual) jpayne@69: failUnless = assert_ = _deprecate(assertTrue) jpayne@69: failUnlessRaises = _deprecate(assertRaises) jpayne@69: failIf = _deprecate(assertFalse) jpayne@69: assertRaisesRegexp = _deprecate(assertRaisesRegex) jpayne@69: assertRegexpMatches = _deprecate(assertRegex) jpayne@69: assertNotRegexpMatches = _deprecate(assertNotRegex) jpayne@69: jpayne@69: jpayne@69: jpayne@69: class FunctionTestCase(TestCase): jpayne@69: """A test case that wraps a test function. jpayne@69: jpayne@69: This is useful for slipping pre-existing test functions into the jpayne@69: unittest framework. Optionally, set-up and tidy-up functions can be jpayne@69: supplied. As with TestCase, the tidy-up ('tearDown') function will jpayne@69: always be called if the set-up ('setUp') function ran successfully. jpayne@69: """ jpayne@69: jpayne@69: def __init__(self, testFunc, setUp=None, tearDown=None, description=None): jpayne@69: super(FunctionTestCase, self).__init__() jpayne@69: self._setUpFunc = setUp jpayne@69: self._tearDownFunc = tearDown jpayne@69: self._testFunc = testFunc jpayne@69: self._description = description jpayne@69: jpayne@69: def setUp(self): jpayne@69: if self._setUpFunc is not None: jpayne@69: self._setUpFunc() jpayne@69: jpayne@69: def tearDown(self): jpayne@69: if self._tearDownFunc is not None: jpayne@69: self._tearDownFunc() jpayne@69: jpayne@69: def runTest(self): jpayne@69: self._testFunc() jpayne@69: jpayne@69: def id(self): jpayne@69: return self._testFunc.__name__ jpayne@69: jpayne@69: def __eq__(self, other): jpayne@69: if not isinstance(other, self.__class__): jpayne@69: return NotImplemented jpayne@69: jpayne@69: return self._setUpFunc == other._setUpFunc and \ jpayne@69: self._tearDownFunc == other._tearDownFunc and \ jpayne@69: self._testFunc == other._testFunc and \ jpayne@69: self._description == other._description jpayne@69: jpayne@69: def __hash__(self): jpayne@69: return hash((type(self), self._setUpFunc, self._tearDownFunc, jpayne@69: self._testFunc, self._description)) jpayne@69: jpayne@69: def __str__(self): jpayne@69: return "%s (%s)" % (strclass(self.__class__), jpayne@69: self._testFunc.__name__) jpayne@69: jpayne@69: def __repr__(self): jpayne@69: return "<%s tec=%s>" % (strclass(self.__class__), jpayne@69: self._testFunc) jpayne@69: jpayne@69: def shortDescription(self): jpayne@69: if self._description is not None: jpayne@69: return self._description jpayne@69: doc = self._testFunc.__doc__ jpayne@69: return doc and doc.split("\n")[0].strip() or None jpayne@69: jpayne@69: jpayne@69: class _SubTest(TestCase): jpayne@69: jpayne@69: def __init__(self, test_case, message, params): jpayne@69: super().__init__() jpayne@69: self._message = message jpayne@69: self.test_case = test_case jpayne@69: self.params = params jpayne@69: self.failureException = test_case.failureException jpayne@69: jpayne@69: def runTest(self): jpayne@69: raise NotImplementedError("subtests cannot be run directly") jpayne@69: jpayne@69: def _subDescription(self): jpayne@69: parts = [] jpayne@69: if self._message is not _subtest_msg_sentinel: jpayne@69: parts.append("[{}]".format(self._message)) jpayne@69: if self.params: jpayne@69: params_desc = ', '.join( jpayne@69: "{}={!r}".format(k, v) jpayne@69: for (k, v) in self.params.items()) jpayne@69: parts.append("({})".format(params_desc)) jpayne@69: return " ".join(parts) or '()' jpayne@69: jpayne@69: def id(self): jpayne@69: return "{} {}".format(self.test_case.id(), self._subDescription()) jpayne@69: jpayne@69: def shortDescription(self): jpayne@69: """Returns a one-line description of the subtest, or None if no jpayne@69: description has been provided. jpayne@69: """ jpayne@69: return self.test_case.shortDescription() jpayne@69: jpayne@69: def __str__(self): jpayne@69: return "{} {}".format(self.test_case, self._subDescription())