""" Vendor unittest.TestSuite This is a modified version of python 3.8 unitest.TestSuite Odoo tests customisation combined with the need of a cross version compatibility started to make TestSuite and other unitest object more complicated than vendoring the part we need for Odoo. This versions is simplified in order to minimise the code to maintain - Removes expected failure support - Removes module setUp/tearDown support """ import logging import sys from . import case from .common import HttpCase from .result import stats_logger from unittest import util, BaseTestSuite, TestCase __unittest = True class TestSuite(BaseTestSuite): """A test suite is a composite test consisting of a number of TestCases. For use, create an instance of TestSuite, then add test case instances. When all tests have been added, the suite can be passed to a test runner, such as TextTestRunner. It will run the individual test cases in the order in which they were added, aggregating the results. When subclassing, do not forget to call the base class constructor. """ def run(self, result, debug=False): for test in self: assert isinstance(test, (TestCase)) self._tearDownPreviousClass(test, result) self._handleClassSetUp(test, result) result._previousTestClass = test.__class__ if not test.__class__._classSetupFailed: test(result) self._tearDownPreviousClass(None, result) return result def _handleClassSetUp(self, test, result): previousClass = result._previousTestClass currentClass = test.__class__ if currentClass == previousClass: return if result._moduleSetUpFailed: return if getattr(currentClass, "__unittest_skip__", False): return currentClass._classSetupFailed = False try: currentClass.setUpClass() except Exception as e: currentClass._classSetupFailed = True className = util.strclass(currentClass) self._createClassOrModuleLevelException(result, e, 'setUpClass', className) finally: if currentClass._classSetupFailed is True: currentClass.doClassCleanups() if len(currentClass.tearDown_exceptions) > 0: for exc in currentClass.tearDown_exceptions: self._createClassOrModuleLevelException( result, exc[1], 'setUpClass', className, info=exc) def _createClassOrModuleLevelException(self, result, exception, method_name, parent, info=None): errorName = f'{method_name} ({parent})' error = _ErrorHolder(errorName) if isinstance(exception, case.SkipTest): result.addSkip(error, str(exception)) else: if not info: result.addError(error, sys.exc_info()) else: result.addError(error, info) def _tearDownPreviousClass(self, test, result): previousClass = result._previousTestClass currentClass = test.__class__ if currentClass == previousClass: return if not previousClass: return if previousClass._classSetupFailed: return if getattr(previousClass, "__unittest_skip__", False): return try: previousClass.tearDownClass() except Exception as e: className = util.strclass(previousClass) self._createClassOrModuleLevelException(result, e, 'tearDownClass', className) finally: previousClass.doClassCleanups() if len(previousClass.tearDown_exceptions) > 0: for exc in previousClass.tearDown_exceptions: className = util.strclass(previousClass) self._createClassOrModuleLevelException(result, exc[1], 'tearDownClass', className, info=exc) class _ErrorHolder(object): """ Placeholder for a TestCase inside a result. As far as a TestResult is concerned, this looks exactly like a unit test. Used to insert arbitrary errors into a test suite run. """ # Inspired by the ErrorHolder from Twisted: # http://twistedmatrix.com/trac/browser/trunk/twisted/trial/runner.py # attribute used by TestResult._exc_info_to_string failureException = None def __init__(self, description): self.description = description def id(self): return self.description def shortDescription(self): return None def __repr__(self): return "" % (self.description,) def __str__(self): return self.id() def run(self, result): # could call result.addError(...) - but this test-like object # shouldn't be run anyway pass def __call__(self, result): return self.run(result) def countTestCases(self): return 0 class OdooSuite(TestSuite): def _handleClassSetUp(self, test, result): previous_test_class = result._previousTestClass if not ( previous_test_class != type(test) and hasattr(result, 'stats') and stats_logger.isEnabledFor(logging.INFO) ): super()._handleClassSetUp(test, result) return test_class = type(test) test_id = f'{test_class.__module__}.{test_class.__qualname__}.setUpClass' with result.collectStats(test_id): super()._handleClassSetUp(test, result) def _tearDownPreviousClass(self, test, result): previous_test_class = result._previousTestClass if not ( previous_test_class and previous_test_class != type(test) and hasattr(result, 'stats') and stats_logger.isEnabledFor(logging.INFO) ): super()._tearDownPreviousClass(test, result) return test_id = f'{previous_test_class.__module__}.{previous_test_class.__qualname__}.tearDownClass' with result.collectStats(test_id): super()._tearDownPreviousClass(test, result) def has_http_case(self): return self.countTestCases() and any(isinstance(test_case, HttpCase) for test_case in self)