summaryrefslogtreecommitdiff
path: root/odoo/tests/loader.py
blob: e7c1a2dde99add86fe26240e95057ee059754fda (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import importlib
import importlib.util
import inspect
import logging
import sys
import threading
from pathlib import Path
from unittest import case

from .. import tools
from .tag_selector import TagsSelector
from .suite import OdooSuite
from .result import OdooTestResult


_logger = logging.getLogger(__name__)


def get_module_test_cases(module):
    """Return a suite of all test cases contained in the given module"""
    for obj in module.__dict__.values():
        if not isinstance(obj, type):
            continue
        if not issubclass(obj, case.TestCase):
            continue
        if obj.__module__ != module.__name__:
            continue

        test_case_class = obj
        test_cases = test_case_class.__dict__.items()
        if getattr(test_case_class, 'allow_inherited_tests_method', False):
            # keep iherited method for specific classes.
            # This is likely to be removed once a better solution is found
            test_cases = inspect.getmembers(test_case_class, callable)
        else:
            # sort test case to keep the initial behaviour.
            # This is likely to be removed in the future
            test_cases = sorted(test_cases, key=lambda pair: pair[0])

        for method_name, method in test_cases:
            if not callable(method):
                continue
            if not method_name.startswith('test'):
                continue
            yield test_case_class(method_name)


def get_test_modules(module):
    """ Return a list of module for the addons potentially containing tests to
    feed get_module_test_cases() """
    results = _get_tests_modules(importlib.util.find_spec(f'odoo.addons.{module}'))
    results += list(_get_upgrade_test_modules(module))

    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 _get_upgrade_test_modules(module):
    upgrade_modules = (
        f"odoo.upgrade.{module}",
        f"odoo.addons.{module}.migrations",
        f"odoo.addons.{module}.upgrades",
    )
    for module_name in upgrade_modules:
        if not importlib.util.find_spec(module_name):
            continue

        upg = importlib.import_module(module_name)
        for path in map(Path, upg.__path__):
            for test in path.glob("tests/test_*.py"):
                spec = importlib.util.spec_from_file_location(f"{upg.__name__}.tests.{test.stem}", test)
                if not spec:
                    continue
                pymod = importlib.util.module_from_spec(spec)
                sys.modules[spec.name] = pymod
                spec.loader.exec_module(pymod)
                yield pymod


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 get_module_test_cases(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):
    # avoid dependency hell
    from ..modules import module
    module.current_test = True
    threading.current_thread().testing = True

    results = OdooTestResult()
    suite(results)

    threading.current_thread().testing = False
    module.current_test = False
    return results