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