123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369 |
- #
- # Copyright (C) 2016 Intel Corporation
- #
- # SPDX-License-Identifier: MIT
- #
- import os
- import re
- import sys
- import unittest
- import inspect
- from oeqa.core.utils.path import findFile
- from oeqa.core.utils.test import getSuiteModules, getCaseID
- from oeqa.core.exception import OEQATestNotFound
- from oeqa.core.case import OETestCase
- from oeqa.core.decorator import decoratorClasses, OETestDecorator, \
- OETestFilter, OETestDiscover
- # When loading tests, the unittest framework stores any exceptions and
- # displays them only when the run method is called.
- #
- # For our purposes, it is better to raise the exceptions in the loading
- # step rather than waiting to run the test suite.
- #
- # Generate the function definition because this differ across python versions
- # Python >= 3.4.4 uses tree parameters instead four but for example Python 3.5.3
- # ueses four parameters so isn't incremental.
- _failed_test_args = inspect.getfullargspec(unittest.loader._make_failed_test).args
- exec("""def _make_failed_test(%s): raise exception""" % ', '.join(_failed_test_args))
- unittest.loader._make_failed_test = _make_failed_test
- def _find_duplicated_modules(suite, directory):
- for module in getSuiteModules(suite):
- path = findFile('%s.py' % module, directory)
- if path:
- raise ImportError("Duplicated %s module found in %s" % (module, path))
- def _built_modules_dict(modules):
- modules_dict = {}
- if modules == None:
- return modules_dict
- for module in modules:
- # Assumption: package and module names do not contain upper case
- # characters, whereas class names do
- m = re.match(r'^(\w+)(?:\.(\w[^.]*)(?:\.([^.]+))?)?$', module, flags=re.ASCII)
- if not m:
- continue
- module_name, class_name, test_name = m.groups()
- if module_name and module_name not in modules_dict:
- modules_dict[module_name] = {}
- if class_name and class_name not in modules_dict[module_name]:
- modules_dict[module_name][class_name] = []
- if test_name and test_name not in modules_dict[module_name][class_name]:
- modules_dict[module_name][class_name].append(test_name)
- return modules_dict
- class OETestLoader(unittest.TestLoader):
- caseClass = OETestCase
- kwargs_names = ['testMethodPrefix', 'sortTestMethodUsing', 'suiteClass',
- '_top_level_dir']
- def __init__(self, tc, module_paths, modules, tests, modules_required,
- filters, *args, **kwargs):
- self.tc = tc
- self.modules = _built_modules_dict(modules)
- self.tests = tests
- self.modules_required = modules_required
- self.filters = filters
- self.decorator_filters = [d for d in decoratorClasses if \
- issubclass(d, OETestFilter)]
- self._validateFilters(self.filters, self.decorator_filters)
- self.used_filters = [d for d in self.decorator_filters
- for f in self.filters
- if f in d.attrs]
- if isinstance(module_paths, str):
- module_paths = [module_paths]
- elif not isinstance(module_paths, list):
- raise TypeError('module_paths must be a str or a list of str')
- self.module_paths = module_paths
- for kwname in self.kwargs_names:
- if kwname in kwargs:
- setattr(self, kwname, kwargs[kwname])
- self._patchCaseClass(self.caseClass)
- super(OETestLoader, self).__init__()
- def _patchCaseClass(self, testCaseClass):
- # Adds custom attributes to the OETestCase class
- setattr(testCaseClass, 'tc', self.tc)
- setattr(testCaseClass, 'td', self.tc.td)
- setattr(testCaseClass, 'logger', self.tc.logger)
- def _validateFilters(self, filters, decorator_filters):
- # Validate if filter isn't empty
- for key,value in filters.items():
- if not value:
- raise TypeError("Filter %s specified is empty" % key)
- # Validate unique attributes
- attr_filters = [attr for clss in decorator_filters \
- for attr in clss.attrs]
- dup_attr = [attr for attr in attr_filters
- if attr_filters.count(attr) > 1]
- if dup_attr:
- raise TypeError('Detected duplicated attribute(s) %s in filter'
- ' decorators' % ' ,'.join(dup_attr))
- # Validate if filter is supported
- for f in filters:
- if f not in attr_filters:
- classes = ', '.join([d.__name__ for d in decorator_filters])
- raise TypeError('Found "%s" filter but not declared in any of '
- '%s decorators' % (f, classes))
- def _registerTestCase(self, case):
- case_id = case.id()
- self.tc._registry['cases'][case_id] = case
- def _handleTestCaseDecorators(self, case):
- def _handle(obj):
- if isinstance(obj, OETestDecorator):
- if not obj.__class__ in decoratorClasses:
- raise Exception("Decorator %s isn't registered" \
- " in decoratorClasses." % obj.__name__)
- obj.bind(self.tc._registry, case)
- def _walk_closure(obj):
- if hasattr(obj, '__closure__') and obj.__closure__:
- for f in obj.__closure__:
- obj = f.cell_contents
- _handle(obj)
- _walk_closure(obj)
- method = getattr(case, case._testMethodName, None)
- _walk_closure(method)
- def _filterTest(self, case):
- """
- Returns True if test case must be filtered, False otherwise.
- """
- # XXX; If the module has more than one namespace only use
- # the first to support run the whole module specifying the
- # <module_name>.[test_class].[test_name]
- module_name_small = case.__module__.split('.')[0]
- module_name = case.__module__
- class_name = case.__class__.__name__
- test_name = case._testMethodName
- # 'auto' is a reserved key word to run test cases automatically
- # warn users if their test case belong to a module named 'auto'
- if module_name_small == "auto":
- bb.warn("'auto' is a reserved key word for TEST_SUITES. "
- "But test case '%s' is detected to belong to auto module. "
- "Please condier using a new name for your module." % str(case))
- # check if case belongs to any specified module
- # if 'auto' is specified, such check is skipped
- if self.modules and not 'auto' in self.modules:
- module = None
- try:
- module = self.modules[module_name_small]
- except KeyError:
- try:
- module = self.modules[module_name]
- except KeyError:
- return True
- if module:
- if not class_name in module:
- return True
- if module[class_name]:
- if test_name not in module[class_name]:
- return True
- # Decorator filters
- if self.filters and isinstance(case, OETestCase):
- filters = self.filters.copy()
- case_decorators = [cd for cd in case.decorators
- if cd.__class__ in self.used_filters]
- # Iterate over case decorators to check if needs to be filtered.
- for cd in case_decorators:
- if cd.filtrate(filters):
- return True
- # Case is missing one or more decorators for all the filters
- # being used, so filter test case.
- if filters:
- return True
- return False
- def _getTestCase(self, testCaseClass, tcName):
- if not hasattr(testCaseClass, '__oeqa_loader') and \
- issubclass(testCaseClass, OETestCase):
- # In order to support data_vars validation
- # monkey patch the default setUp/tearDown{Class} to use
- # the ones provided by OETestCase
- setattr(testCaseClass, 'setUpClassMethod',
- getattr(testCaseClass, 'setUpClass'))
- setattr(testCaseClass, 'tearDownClassMethod',
- getattr(testCaseClass, 'tearDownClass'))
- setattr(testCaseClass, 'setUpClass',
- testCaseClass._oeSetUpClass)
- setattr(testCaseClass, 'tearDownClass',
- testCaseClass._oeTearDownClass)
- # In order to support decorators initialization
- # monkey patch the default setUp/tearDown to use
- # a setUpDecorators/tearDownDecorators that methods
- # will call setUp/tearDown original methods.
- setattr(testCaseClass, 'setUpMethod',
- getattr(testCaseClass, 'setUp'))
- setattr(testCaseClass, 'tearDownMethod',
- getattr(testCaseClass, 'tearDown'))
- setattr(testCaseClass, 'setUp', testCaseClass._oeSetUp)
- setattr(testCaseClass, 'tearDown', testCaseClass._oeTearDown)
- setattr(testCaseClass, '__oeqa_loader', True)
- case = testCaseClass(tcName)
- if isinstance(case, OETestCase):
- setattr(case, 'decorators', [])
- return case
- def loadTestsFromTestCase(self, testCaseClass):
- """
- Returns a suite of all tests cases contained in testCaseClass.
- """
- if issubclass(testCaseClass, unittest.suite.TestSuite):
- raise TypeError("Test cases should not be derived from TestSuite." \
- " Maybe you meant to derive %s from TestCase?" \
- % testCaseClass.__name__)
- if not issubclass(testCaseClass, unittest.case.TestCase):
- raise TypeError("Test %s is not derived from %s" % \
- (testCaseClass.__name__, unittest.case.TestCase.__name__))
- testCaseNames = self.getTestCaseNames(testCaseClass)
- if not testCaseNames and hasattr(testCaseClass, 'runTest'):
- testCaseNames = ['runTest']
- suite = []
- for tcName in testCaseNames:
- case = self._getTestCase(testCaseClass, tcName)
- # Filer by case id
- if not (self.tests and not 'auto' in self.tests
- and not getCaseID(case) in self.tests):
- self._handleTestCaseDecorators(case)
- # Filter by decorators
- if not self._filterTest(case):
- self._registerTestCase(case)
- suite.append(case)
- return self.suiteClass(suite)
- def _required_modules_validation(self):
- """
- Search in Test context registry if a required
- test is found, raise an exception when not found.
- """
- for module in self.modules_required:
- found = False
- # The module name is splitted to only compare the
- # first part of a test case id.
- comp_len = len(module.split('.'))
- for case in self.tc._registry['cases']:
- case_comp = '.'.join(case.split('.')[0:comp_len])
- if module == case_comp:
- found = True
- break
- if not found:
- raise OEQATestNotFound("Not found %s in loaded test cases" % \
- module)
- def discover(self):
- big_suite = self.suiteClass()
- for path in self.module_paths:
- _find_duplicated_modules(big_suite, path)
- suite = super(OETestLoader, self).discover(path,
- pattern='*.py', top_level_dir=path)
- big_suite.addTests(suite)
- cases = None
- discover_classes = [clss for clss in decoratorClasses
- if issubclass(clss, OETestDiscover)]
- for clss in discover_classes:
- cases = clss.discover(self.tc._registry)
- if self.modules_required:
- self._required_modules_validation()
- return self.suiteClass(cases) if cases else big_suite
- def _filterModule(self, module):
- if module.__name__ in sys.builtin_module_names:
- msg = 'Tried to import %s test module but is a built-in'
- raise ImportError(msg % module.__name__)
- # XXX; If the module has more than one namespace only use
- # the first to support run the whole module specifying the
- # <module_name>.[test_class].[test_name]
- module_name_small = module.__name__.split('.')[0]
- module_name = module.__name__
- # Normal test modules are loaded if no modules were specified,
- # if module is in the specified module list or if 'auto' is in
- # module list.
- # Underscore modules are loaded only if specified in module list.
- load_module = True if not module_name.startswith('_') \
- and (not self.modules \
- or module_name in self.modules \
- or module_name_small in self.modules \
- or 'auto' in self.modules) \
- else False
- load_underscore = True if module_name.startswith('_') \
- and (module_name in self.modules or \
- module_name_small in self.modules) \
- else False
- return (load_module, load_underscore)
- # XXX After Python 3.5, remove backward compatibility hacks for
- # use_load_tests deprecation via *args and **kws. See issue 16662.
- if sys.version_info >= (3,5):
- def loadTestsFromModule(self, module, *args, pattern=None, **kws):
- """
- Returns a suite of all tests cases contained in module.
- """
- load_module, load_underscore = self._filterModule(module)
- if load_module or load_underscore:
- return super(OETestLoader, self).loadTestsFromModule(
- module, *args, pattern=pattern, **kws)
- else:
- return self.suiteClass()
- else:
- def loadTestsFromModule(self, module, use_load_tests=True):
- """
- Returns a suite of all tests cases contained in module.
- """
- load_module, load_underscore = self._filterModule(module)
- if load_module or load_underscore:
- return super(OETestLoader, self).loadTestsFromModule(
- module, use_load_tests)
- else:
- return self.suiteClass()
|