Initial commit
This commit is contained in:
@@ -0,0 +1,350 @@
|
||||
#
|
||||
# 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, \
|
||||
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, logger):
|
||||
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'^([0-9a-z_.]+)(?:\.(\w[^.]*)(?:\.([^.]+))?)?$', module, flags=re.ASCII)
|
||||
if not m:
|
||||
logger.warn("module '%s' was skipped from selected modules, "\
|
||||
"because it doesn't match with module name assumptions: "\
|
||||
"package and module names do not contain upper case characters, whereas class names do" % module)
|
||||
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)
|
||||
if modules and not modules_dict:
|
||||
raise OEQATestNotFound("All selected modules were skipped, this would trigger selftest with all tests and -r ignored.")
|
||||
|
||||
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,
|
||||
*args, **kwargs):
|
||||
self.tc = tc
|
||||
|
||||
self.modules = _built_modules_dict(modules, tc.logger)
|
||||
|
||||
self.tests = tests
|
||||
self.modules_required = modules_required
|
||||
|
||||
self.tags_filter = kwargs.get("tags_filter", None)
|
||||
|
||||
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 _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.tags_filter is not None and callable(self.tags_filter):
|
||||
alltags = set()
|
||||
# pull tags from the case class
|
||||
if hasattr(case, "__oeqa_testtags"):
|
||||
for t in getattr(case, "__oeqa_testtags"):
|
||||
alltags.add(t)
|
||||
# pull tags from the method itself
|
||||
if hasattr(case, test_name):
|
||||
method = getattr(case, test_name)
|
||||
if hasattr(method, "__oeqa_testtags"):
|
||||
for t in getattr(method, "__oeqa_testtags"):
|
||||
alltags.add(t)
|
||||
|
||||
if self.tags_filter(alltags):
|
||||
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
|
||||
|
||||
if any(c.isupper() for c in module.__name__):
|
||||
raise SystemExit("Module '%s' contains uppercase characters and this isn't supported. Please fix the module name." % module.__name__)
|
||||
|
||||
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()
|
||||
Reference in New Issue
Block a user