xref: /openbmc/openbmc/poky/meta/lib/oeqa/core/loader.py (revision 8e7b46e2)
1#
2# Copyright (C) 2016 Intel Corporation
3#
4# SPDX-License-Identifier: MIT
5#
6
7import os
8import re
9import sys
10import unittest
11import inspect
12
13from oeqa.core.utils.path import findFile
14from oeqa.core.utils.test import getSuiteModules, getCaseID
15
16from oeqa.core.exception import OEQATestNotFound
17from oeqa.core.case import OETestCase
18from oeqa.core.decorator import decoratorClasses, OETestDecorator, \
19        OETestDiscover
20
21# When loading tests, the unittest framework stores any exceptions and
22# displays them only when the run method is called.
23#
24# For our purposes, it is better to raise the exceptions in the loading
25# step rather than waiting to run the test suite.
26#
27# Generate the function definition because this differ across python versions
28# Python >= 3.4.4 uses tree parameters instead four but for example Python 3.5.3
29# ueses four parameters so isn't incremental.
30_failed_test_args = inspect.getfullargspec(unittest.loader._make_failed_test).args
31exec("""def _make_failed_test(%s): raise exception""" % ', '.join(_failed_test_args))
32unittest.loader._make_failed_test = _make_failed_test
33
34def _find_duplicated_modules(suite, directory):
35    for module in getSuiteModules(suite):
36        path = findFile('%s.py' % module, directory)
37        if path:
38            raise ImportError("Duplicated %s module found in %s" % (module, path))
39
40def _built_modules_dict(modules, logger):
41    modules_dict = {}
42
43    if modules == None:
44        return modules_dict
45
46    for module in modules:
47        # Assumption: package and module names do not contain upper case
48        # characters, whereas class names do
49        m = re.match(r'^([0-9a-z_.]+)(?:\.(\w[^.]*)(?:\.([^.]+))?)?$', module, flags=re.ASCII)
50        if not m:
51            logger.warn("module '%s' was skipped from selected modules, "\
52                "because it doesn't match with module name assumptions: "\
53                "package and module names do not contain upper case characters, whereas class names do" % module)
54            continue
55
56        module_name, class_name, test_name = m.groups()
57
58        if module_name and module_name not in modules_dict:
59            modules_dict[module_name] = {}
60        if class_name and class_name not in modules_dict[module_name]:
61            modules_dict[module_name][class_name] = []
62        if test_name and test_name not in modules_dict[module_name][class_name]:
63            modules_dict[module_name][class_name].append(test_name)
64    if modules and not modules_dict:
65        raise OEQATestNotFound("All selected modules were skipped, this would trigger selftest with all tests and -r ignored.")
66
67    return modules_dict
68
69class OETestLoader(unittest.TestLoader):
70    caseClass = OETestCase
71
72    kwargs_names = ['testMethodPrefix', 'sortTestMethodUsing', 'suiteClass',
73            '_top_level_dir']
74
75    def __init__(self, tc, module_paths, modules, tests, modules_required,
76            *args, **kwargs):
77        self.tc = tc
78
79        self.modules = _built_modules_dict(modules, tc.logger)
80
81        self.tests = tests
82        self.modules_required = modules_required
83
84        self.tags_filter = kwargs.get("tags_filter", None)
85
86        if isinstance(module_paths, str):
87            module_paths = [module_paths]
88        elif not isinstance(module_paths, list):
89            raise TypeError('module_paths must be a str or a list of str')
90        self.module_paths = module_paths
91
92        for kwname in self.kwargs_names:
93            if kwname in kwargs:
94                setattr(self, kwname, kwargs[kwname])
95
96        self._patchCaseClass(self.caseClass)
97
98        super(OETestLoader, self).__init__()
99
100    def _patchCaseClass(self, testCaseClass):
101        # Adds custom attributes to the OETestCase class
102        setattr(testCaseClass, 'tc', self.tc)
103        setattr(testCaseClass, 'td', self.tc.td)
104        setattr(testCaseClass, 'logger', self.tc.logger)
105
106    def _registerTestCase(self, case):
107        case_id = case.id()
108        self.tc._registry['cases'][case_id] = case
109
110    def _handleTestCaseDecorators(self, case):
111        def _handle(obj):
112            if isinstance(obj, OETestDecorator):
113                if not obj.__class__ in decoratorClasses:
114                    raise Exception("Decorator %s isn't registered" \
115                            " in decoratorClasses." % obj.__name__)
116                obj.bind(self.tc._registry, case)
117
118        def _walk_closure(obj):
119            if hasattr(obj, '__closure__') and obj.__closure__:
120                for f in obj.__closure__:
121                    obj = f.cell_contents
122                    _handle(obj)
123                    _walk_closure(obj)
124        method = getattr(case, case._testMethodName, None)
125        _walk_closure(method)
126
127    def _filterTest(self, case):
128        """
129            Returns True if test case must be filtered, False otherwise.
130        """
131        # XXX; If the module has more than one namespace only use
132        # the first to support run the whole module specifying the
133        # <module_name>.[test_class].[test_name]
134        module_name_small = case.__module__.split('.')[0]
135        module_name = case.__module__
136
137        class_name = case.__class__.__name__
138        test_name = case._testMethodName
139
140        # 'auto' is a reserved key word to run test cases automatically
141        # warn users if their test case belong to a module named 'auto'
142        if module_name_small == "auto":
143            bb.warn("'auto' is a reserved key word for TEST_SUITES. "
144                    "But test case '%s' is detected to belong to auto module. "
145                    "Please condier using a new name for your module." % str(case))
146
147        # check if case belongs to any specified module
148        # if 'auto' is specified, such check is skipped
149        if self.modules and not 'auto' in self.modules:
150            module = None
151            try:
152                module = self.modules[module_name_small]
153            except KeyError:
154                try:
155                    module = self.modules[module_name]
156                except KeyError:
157                    return True
158
159            if module:
160                if not class_name in module:
161                    return True
162
163                if module[class_name]:
164                    if test_name not in module[class_name]:
165                        return True
166
167        # Decorator filters
168        if self.tags_filter is not None and callable(self.tags_filter):
169            alltags = set()
170            # pull tags from the case class
171            if hasattr(case, "__oeqa_testtags"):
172                for t in getattr(case, "__oeqa_testtags"):
173                    alltags.add(t)
174            # pull tags from the method itself
175            if hasattr(case, test_name):
176                method = getattr(case, test_name)
177                if hasattr(method, "__oeqa_testtags"):
178                    for t in getattr(method, "__oeqa_testtags"):
179                        alltags.add(t)
180
181            if self.tags_filter(alltags):
182                return True
183
184        return False
185
186    def _getTestCase(self, testCaseClass, tcName):
187        if not hasattr(testCaseClass, '__oeqa_loader') and \
188                issubclass(testCaseClass, OETestCase):
189            # In order to support data_vars validation
190            # monkey patch the default setUp/tearDown{Class} to use
191            # the ones provided by OETestCase
192            setattr(testCaseClass, 'setUpClassMethod',
193                    getattr(testCaseClass, 'setUpClass'))
194            setattr(testCaseClass, 'tearDownClassMethod',
195                    getattr(testCaseClass, 'tearDownClass'))
196            setattr(testCaseClass, 'setUpClass',
197                    testCaseClass._oeSetUpClass)
198            setattr(testCaseClass, 'tearDownClass',
199                    testCaseClass._oeTearDownClass)
200
201            # In order to support decorators initialization
202            # monkey patch the default setUp/tearDown to use
203            # a setUpDecorators/tearDownDecorators that methods
204            # will call setUp/tearDown original methods.
205            setattr(testCaseClass, 'setUpMethod',
206                    getattr(testCaseClass, 'setUp'))
207            setattr(testCaseClass, 'tearDownMethod',
208                    getattr(testCaseClass, 'tearDown'))
209            setattr(testCaseClass, 'setUp', testCaseClass._oeSetUp)
210            setattr(testCaseClass, 'tearDown', testCaseClass._oeTearDown)
211
212            setattr(testCaseClass, '__oeqa_loader', True)
213
214        case = testCaseClass(tcName)
215        if isinstance(case, OETestCase):
216            setattr(case, 'decorators', [])
217
218        return case
219
220    def loadTestsFromTestCase(self, testCaseClass):
221        """
222            Returns a suite of all tests cases contained in testCaseClass.
223        """
224        if issubclass(testCaseClass, unittest.suite.TestSuite):
225            raise TypeError("Test cases should not be derived from TestSuite." \
226                                " Maybe you meant to derive %s from TestCase?" \
227                                % testCaseClass.__name__)
228        if not issubclass(testCaseClass, unittest.case.TestCase):
229            raise TypeError("Test %s is not derived from %s" % \
230                    (testCaseClass.__name__, unittest.case.TestCase.__name__))
231
232        testCaseNames = self.getTestCaseNames(testCaseClass)
233        if not testCaseNames and hasattr(testCaseClass, 'runTest'):
234            testCaseNames = ['runTest']
235
236        suite = []
237        for tcName in testCaseNames:
238            case = self._getTestCase(testCaseClass, tcName)
239            # Filer by case id
240            if not (self.tests and not 'auto' in self.tests
241                    and not getCaseID(case) in self.tests):
242                self._handleTestCaseDecorators(case)
243
244                # Filter by decorators
245                if not self._filterTest(case):
246                    self._registerTestCase(case)
247                    suite.append(case)
248
249        return self.suiteClass(suite)
250
251    def _required_modules_validation(self):
252        """
253            Search in Test context registry if a required
254            test is found, raise an exception when not found.
255        """
256
257        for module in self.modules_required:
258            found = False
259
260            # The module name is splitted to only compare the
261            # first part of a test case id.
262            comp_len = len(module.split('.'))
263            for case in self.tc._registry['cases']:
264                case_comp = '.'.join(case.split('.')[0:comp_len])
265                if module == case_comp:
266                    found = True
267                    break
268
269            if not found:
270                raise OEQATestNotFound("Not found %s in loaded test cases" % \
271                        module)
272
273    def discover(self):
274        big_suite = self.suiteClass()
275        for path in self.module_paths:
276            _find_duplicated_modules(big_suite, path)
277            suite = super(OETestLoader, self).discover(path,
278                    pattern='*.py', top_level_dir=path)
279            big_suite.addTests(suite)
280
281        cases = None
282        discover_classes = [clss for clss in decoratorClasses
283                            if issubclass(clss, OETestDiscover)]
284        for clss in discover_classes:
285            cases = clss.discover(self.tc._registry)
286
287        if self.modules_required:
288            self._required_modules_validation()
289
290        return self.suiteClass(cases) if cases else big_suite
291
292    def _filterModule(self, module):
293        if module.__name__ in sys.builtin_module_names:
294            msg = 'Tried to import %s test module but is a built-in'
295            raise ImportError(msg % module.__name__)
296
297        # XXX; If the module has more than one namespace only use
298        # the first to support run the whole module specifying the
299        # <module_name>.[test_class].[test_name]
300        module_name_small = module.__name__.split('.')[0]
301        module_name = module.__name__
302
303        # Normal test modules are loaded if no modules were specified,
304        # if module is in the specified module list or if 'auto' is in
305        # module list.
306        # Underscore modules are loaded only if specified in module list.
307        load_module = True if not module_name.startswith('_') \
308                              and (not self.modules \
309                                   or module_name in self.modules \
310                                   or module_name_small in self.modules \
311                                   or 'auto' in self.modules) \
312                           else False
313
314        load_underscore = True if module_name.startswith('_') \
315                                  and (module_name in self.modules or \
316                                  module_name_small in self.modules) \
317                               else False
318
319        if any(c.isupper() for c in module.__name__):
320            raise SystemExit("Module '%s' contains uppercase characters and this isn't supported. Please fix the module name." % module.__name__)
321
322        return (load_module, load_underscore)
323
324
325    # XXX After Python 3.5, remove backward compatibility hacks for
326    # use_load_tests deprecation via *args and **kws.  See issue 16662.
327    if sys.version_info >= (3,5):
328        def loadTestsFromModule(self, module, *args, pattern=None, **kws):
329            """
330                Returns a suite of all tests cases contained in module.
331            """
332            load_module, load_underscore = self._filterModule(module)
333
334            if load_module or load_underscore:
335                return super(OETestLoader, self).loadTestsFromModule(
336                        module, *args, pattern=pattern, **kws)
337            else:
338                return self.suiteClass()
339    else:
340        def loadTestsFromModule(self, module, use_load_tests=True):
341            """
342                Returns a suite of all tests cases contained in module.
343            """
344            load_module, load_underscore = self._filterModule(module)
345
346            if load_module or load_underscore:
347                return super(OETestLoader, self).loadTestsFromModule(
348                        module, use_load_tests)
349            else:
350                return self.suiteClass()
351