Odoo Test environments requires to modify many parts of the unittest TestCase, Suite and Result. The main initial reason is to **avoid to postpone result at the end of the test suite**, because even if it is convenient to have all errors visible after the tests in some case, odoo logs adds information during the execution that can be useful to debug when a test fail, to have context for an error. (see **OdooTestResult**) We are also fixing the stack trace comming from a unittest and since there is no proper way to hook inside the TestPartExecutor, a dirty hack injects anoter result on the outcome to manage the error and complete the stack trace. This was also a way to avoid to postpone subtest logs at the end of the test case (see _ErrorCatcher) `_feedErrorsToResult` was used to test the test suite behavior since there are many customization and this is quite fragile, especially if unittest changes behavior in other python version. **Python 3.11** introduced python/cpython#664448d8 That, in a way, goes in the same direction of the changed introduced with _ErrorCatcher: immediately feed errors to resut instead of postponing it. But this also removes `_feedErrorsToResult` that was used to test this behaviors, as well as other ones. Since odoo should remain multi-version, this amount of changes on the initial behavior become to complicate to keep cross-version and the (already in our mind for a while) solution to **vendor unittest** will help to simplify most of our test code base. This commit modified the vendored unittest files to simplify them as much as possible to suite our needs. Since the runner is still the unittest one, we need to inherit from unittest.Testcase in order to have the right type. This also means that we still have access to all TestCase methods without overriding them all. This is convenient for assertion methods as an example but the initial idea is to vendor our own version of TestCase to avoid having trouble to adapte our miscommunications to future python versions. A trade-off must be done to chose what should remain in our code base. The idea is to keep logic closely linked to our changes in our code base, mainly around the run method, but also addClassCleanup wich need to be vendored for python 3.7, but assertions methods are independent. Any logic can be moved fom unittest to our vendored version in the future if needed. X-original-commit: 9a5d1ea54be49e4cc8208c33e76a6bbd2414d5d0 Part-of: odoo/odoo#113850
96 lines
2.8 KiB
Python
96 lines
2.8 KiB
Python
import importlib
|
|
import importlib.util
|
|
import inspect
|
|
import itertools
|
|
import threading
|
|
import unittest
|
|
|
|
from .. import tools
|
|
from .tag_selector import TagsSelector
|
|
from .suite import OdooSuite
|
|
from .result import OdooTestResult
|
|
|
|
|
|
def get_test_modules(module):
|
|
""" Return a list of module for the addons potentially containing tests to
|
|
feed unittest.TestLoader.loadTestsFromModule() """
|
|
results = _get_tests_modules(importlib.util.find_spec(f'odoo.addons.{module}'))
|
|
|
|
upgrade_spec = importlib.util.find_spec(f'odoo.upgrade.{module}')
|
|
if upgrade_spec:
|
|
results += _get_tests_modules(upgrade_spec)
|
|
|
|
return results
|
|
|
|
|
|
def _get_tests_modules(mod):
|
|
spec = importlib.util.find_spec('.tests', mod.name)
|
|
if not spec:
|
|
return []
|
|
|
|
tests_mod = importlib.import_module(spec.name)
|
|
return [
|
|
mod_obj
|
|
for name, mod_obj in inspect.getmembers(tests_mod, inspect.ismodule)
|
|
if name.startswith('test_')
|
|
]
|
|
|
|
|
|
def make_suite(module_names, position='at_install'):
|
|
""" Creates a test suite for all the tests in the specified modules,
|
|
filtered by the provided ``position`` and the current test tags
|
|
|
|
:param list[str] module_names: modules to load tests from
|
|
:param str position: "at_install" or "post_install"
|
|
"""
|
|
config_tags = TagsSelector(tools.config['test_tags'])
|
|
position_tag = TagsSelector(position)
|
|
tests = (
|
|
t
|
|
for module_name in module_names
|
|
for m in get_test_modules(module_name)
|
|
for t in unwrap_suite(unittest.TestLoader().loadTestsFromModule(m))
|
|
if position_tag.check(t) and config_tags.check(t)
|
|
)
|
|
return OdooSuite(sorted(tests, key=lambda t: t.test_sequence))
|
|
|
|
|
|
def run_suite(suite, module_name=None):
|
|
# avoid dependency hell
|
|
from ..modules import module
|
|
module.current_test = module_name
|
|
threading.current_thread().testing = True
|
|
|
|
results = OdooTestResult()
|
|
suite(results)
|
|
|
|
threading.current_thread().testing = False
|
|
module.current_test = None
|
|
return results
|
|
|
|
|
|
def unwrap_suite(test):
|
|
"""
|
|
Attempts to unpack testsuites (holding suites or cases) in order to
|
|
generate a single stream of terminals (either test cases or customized
|
|
test suites). These can then be checked for run/skip attributes
|
|
individually.
|
|
|
|
An alternative would be to use a variant of @unittest.skipIf with a state
|
|
flag of some sort e.g. @unittest.skipIf(common.runstate != 'at_install'),
|
|
but then things become weird with post_install as tests should *not* run
|
|
by default there
|
|
"""
|
|
if isinstance(test, unittest.TestCase):
|
|
yield test
|
|
return
|
|
|
|
subtests = list(test)
|
|
## custom test suite (no test cases)
|
|
#if not len(subtests):
|
|
# yield test
|
|
# return
|
|
|
|
for item in itertools.chain.from_iterable(unwrap_suite(t) for t in subtests):
|
|
yield item
|