191 lines
6.6 KiB
Python
191 lines
6.6 KiB
Python
|
"""
|
||
|
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 currentClass.__unittest_skip__:
|
||
|
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 previousClass.__unittest_skip__:
|
||
|
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 "<ErrorHolder description=%r>" % (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)
|