blob: 2255cf1dc571114063ede4c743d226da54ae357c [file] [log] [blame]
Brad Bishop6e60e8b2018-02-01 10:27:11 -05001# Copyright (C) 2016 Intel Corporation
2# Released under the MIT license (see COPYING.MIT)
3
4import os
Brad Bishopd7bf8c12018-02-25 22:55:05 -05005import re
Brad Bishop6e60e8b2018-02-01 10:27:11 -05006import sys
7import unittest
Brad Bishopd7bf8c12018-02-25 22:55:05 -05008import inspect
Brad Bishop6e60e8b2018-02-01 10:27:11 -05009
10from oeqa.core.utils.path import findFile
11from oeqa.core.utils.test import getSuiteModules, getCaseID
12
Brad Bishopd7bf8c12018-02-25 22:55:05 -050013from oeqa.core.exception import OEQATestNotFound
Brad Bishop6e60e8b2018-02-01 10:27:11 -050014from oeqa.core.case import OETestCase
15from oeqa.core.decorator import decoratorClasses, OETestDecorator, \
16 OETestFilter, OETestDiscover
17
Brad Bishopd7bf8c12018-02-25 22:55:05 -050018# When loading tests, the unittest framework stores any exceptions and
19# displays them only when the run method is called.
20#
21# For our purposes, it is better to raise the exceptions in the loading
22# step rather than waiting to run the test suite.
23#
24# Generate the function definition because this differ across python versions
25# Python >= 3.4.4 uses tree parameters instead four but for example Python 3.5.3
26# ueses four parameters so isn't incremental.
Brad Bishopf86d0552018-12-04 14:18:15 -080027_failed_test_args = inspect.getfullargspec(unittest.loader._make_failed_test).args
Brad Bishopd7bf8c12018-02-25 22:55:05 -050028exec("""def _make_failed_test(%s): raise exception""" % ', '.join(_failed_test_args))
Brad Bishop6e60e8b2018-02-01 10:27:11 -050029unittest.loader._make_failed_test = _make_failed_test
30
31def _find_duplicated_modules(suite, directory):
32 for module in getSuiteModules(suite):
33 path = findFile('%s.py' % module, directory)
34 if path:
35 raise ImportError("Duplicated %s module found in %s" % (module, path))
36
Brad Bishopd7bf8c12018-02-25 22:55:05 -050037def _built_modules_dict(modules):
38 modules_dict = {}
39
40 if modules == None:
41 return modules_dict
42
43 for module in modules:
44 # Assumption: package and module names do not contain upper case
45 # characters, whereas class names do
Brad Bishop316dfdd2018-06-25 12:45:53 -040046 m = re.match(r'^(\w+)(?:\.(\w[^.]*)(?:\.([^.]+))?)?$', module, flags=re.ASCII)
Brad Bishopd7bf8c12018-02-25 22:55:05 -050047
48 module_name, class_name, test_name = m.groups()
49
50 if module_name and module_name not in modules_dict:
51 modules_dict[module_name] = {}
52 if class_name and class_name not in modules_dict[module_name]:
53 modules_dict[module_name][class_name] = []
54 if test_name and test_name not in modules_dict[module_name][class_name]:
55 modules_dict[module_name][class_name].append(test_name)
56
57 return modules_dict
58
Brad Bishop6e60e8b2018-02-01 10:27:11 -050059class OETestLoader(unittest.TestLoader):
60 caseClass = OETestCase
61
62 kwargs_names = ['testMethodPrefix', 'sortTestMethodUsing', 'suiteClass',
63 '_top_level_dir']
64
65 def __init__(self, tc, module_paths, modules, tests, modules_required,
66 filters, *args, **kwargs):
67 self.tc = tc
68
Brad Bishopd7bf8c12018-02-25 22:55:05 -050069 self.modules = _built_modules_dict(modules)
70
Brad Bishop6e60e8b2018-02-01 10:27:11 -050071 self.tests = tests
72 self.modules_required = modules_required
73
74 self.filters = filters
75 self.decorator_filters = [d for d in decoratorClasses if \
76 issubclass(d, OETestFilter)]
77 self._validateFilters(self.filters, self.decorator_filters)
78 self.used_filters = [d for d in self.decorator_filters
79 for f in self.filters
80 if f in d.attrs]
81
82 if isinstance(module_paths, str):
83 module_paths = [module_paths]
84 elif not isinstance(module_paths, list):
85 raise TypeError('module_paths must be a str or a list of str')
86 self.module_paths = module_paths
87
88 for kwname in self.kwargs_names:
89 if kwname in kwargs:
90 setattr(self, kwname, kwargs[kwname])
91
92 self._patchCaseClass(self.caseClass)
93
Brad Bishopd7bf8c12018-02-25 22:55:05 -050094 super(OETestLoader, self).__init__()
95
Brad Bishop6e60e8b2018-02-01 10:27:11 -050096 def _patchCaseClass(self, testCaseClass):
97 # Adds custom attributes to the OETestCase class
98 setattr(testCaseClass, 'tc', self.tc)
99 setattr(testCaseClass, 'td', self.tc.td)
100 setattr(testCaseClass, 'logger', self.tc.logger)
101
102 def _validateFilters(self, filters, decorator_filters):
103 # Validate if filter isn't empty
104 for key,value in filters.items():
105 if not value:
106 raise TypeError("Filter %s specified is empty" % key)
107
108 # Validate unique attributes
109 attr_filters = [attr for clss in decorator_filters \
110 for attr in clss.attrs]
111 dup_attr = [attr for attr in attr_filters
112 if attr_filters.count(attr) > 1]
113 if dup_attr:
114 raise TypeError('Detected duplicated attribute(s) %s in filter'
115 ' decorators' % ' ,'.join(dup_attr))
116
117 # Validate if filter is supported
118 for f in filters:
119 if f not in attr_filters:
120 classes = ', '.join([d.__name__ for d in decorator_filters])
121 raise TypeError('Found "%s" filter but not declared in any of '
122 '%s decorators' % (f, classes))
123
124 def _registerTestCase(self, case):
125 case_id = case.id()
126 self.tc._registry['cases'][case_id] = case
127
128 def _handleTestCaseDecorators(self, case):
129 def _handle(obj):
130 if isinstance(obj, OETestDecorator):
131 if not obj.__class__ in decoratorClasses:
132 raise Exception("Decorator %s isn't registered" \
133 " in decoratorClasses." % obj.__name__)
134 obj.bind(self.tc._registry, case)
135
136 def _walk_closure(obj):
137 if hasattr(obj, '__closure__') and obj.__closure__:
138 for f in obj.__closure__:
139 obj = f.cell_contents
140 _handle(obj)
141 _walk_closure(obj)
142 method = getattr(case, case._testMethodName, None)
143 _walk_closure(method)
144
145 def _filterTest(self, case):
146 """
147 Returns True if test case must be filtered, False otherwise.
148 """
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500149 # XXX; If the module has more than one namespace only use
150 # the first to support run the whole module specifying the
151 # <module_name>.[test_class].[test_name]
152 module_name_small = case.__module__.split('.')[0]
153 module_name = case.__module__
154
155 class_name = case.__class__.__name__
156 test_name = case._testMethodName
157
Brad Bishopd5ae7d92018-06-14 09:52:03 -0700158 # 'auto' is a reserved key word to run test cases automatically
159 # warn users if their test case belong to a module named 'auto'
160 if module_name_small == "auto":
161 bb.warn("'auto' is a reserved key word for TEST_SUITES. "
162 "But test case '%s' is detected to belong to auto module. "
163 "Please condier using a new name for your module." % str(case))
164
165 # check if case belongs to any specified module
166 # if 'auto' is specified, such check is skipped
167 if self.modules and not 'auto' in self.modules:
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500168 module = None
169 try:
170 module = self.modules[module_name_small]
171 except KeyError:
172 try:
173 module = self.modules[module_name]
174 except KeyError:
175 return True
176
177 if module:
178 if not class_name in module:
179 return True
180
181 if module[class_name]:
182 if test_name not in module[class_name]:
183 return True
184
185 # Decorator filters
186 if self.filters and isinstance(case, OETestCase):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500187 filters = self.filters.copy()
188 case_decorators = [cd for cd in case.decorators
189 if cd.__class__ in self.used_filters]
190
191 # Iterate over case decorators to check if needs to be filtered.
192 for cd in case_decorators:
193 if cd.filtrate(filters):
194 return True
195
196 # Case is missing one or more decorators for all the filters
197 # being used, so filter test case.
198 if filters:
199 return True
200
201 return False
202
203 def _getTestCase(self, testCaseClass, tcName):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500204 if not hasattr(testCaseClass, '__oeqa_loader') and \
205 issubclass(testCaseClass, OETestCase):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500206 # In order to support data_vars validation
207 # monkey patch the default setUp/tearDown{Class} to use
208 # the ones provided by OETestCase
209 setattr(testCaseClass, 'setUpClassMethod',
210 getattr(testCaseClass, 'setUpClass'))
211 setattr(testCaseClass, 'tearDownClassMethod',
212 getattr(testCaseClass, 'tearDownClass'))
213 setattr(testCaseClass, 'setUpClass',
214 testCaseClass._oeSetUpClass)
215 setattr(testCaseClass, 'tearDownClass',
216 testCaseClass._oeTearDownClass)
217
218 # In order to support decorators initialization
219 # monkey patch the default setUp/tearDown to use
220 # a setUpDecorators/tearDownDecorators that methods
221 # will call setUp/tearDown original methods.
222 setattr(testCaseClass, 'setUpMethod',
223 getattr(testCaseClass, 'setUp'))
224 setattr(testCaseClass, 'tearDownMethod',
225 getattr(testCaseClass, 'tearDown'))
226 setattr(testCaseClass, 'setUp', testCaseClass._oeSetUp)
227 setattr(testCaseClass, 'tearDown', testCaseClass._oeTearDown)
228
229 setattr(testCaseClass, '__oeqa_loader', True)
230
231 case = testCaseClass(tcName)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500232 if isinstance(case, OETestCase):
233 setattr(case, 'decorators', [])
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500234
235 return case
236
237 def loadTestsFromTestCase(self, testCaseClass):
238 """
239 Returns a suite of all tests cases contained in testCaseClass.
240 """
241 if issubclass(testCaseClass, unittest.suite.TestSuite):
242 raise TypeError("Test cases should not be derived from TestSuite." \
243 " Maybe you meant to derive %s from TestCase?" \
244 % testCaseClass.__name__)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500245 if not issubclass(testCaseClass, unittest.case.TestCase):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500246 raise TypeError("Test %s is not derived from %s" % \
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500247 (testCaseClass.__name__, unittest.case.TestCase.__name__))
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500248
249 testCaseNames = self.getTestCaseNames(testCaseClass)
250 if not testCaseNames and hasattr(testCaseClass, 'runTest'):
251 testCaseNames = ['runTest']
252
253 suite = []
254 for tcName in testCaseNames:
255 case = self._getTestCase(testCaseClass, tcName)
256 # Filer by case id
Brad Bishopd5ae7d92018-06-14 09:52:03 -0700257 if not (self.tests and not 'auto' in self.tests
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500258 and not getCaseID(case) in self.tests):
259 self._handleTestCaseDecorators(case)
260
261 # Filter by decorators
262 if not self._filterTest(case):
263 self._registerTestCase(case)
264 suite.append(case)
265
266 return self.suiteClass(suite)
267
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500268 def _required_modules_validation(self):
269 """
270 Search in Test context registry if a required
271 test is found, raise an exception when not found.
272 """
273
274 for module in self.modules_required:
275 found = False
276
277 # The module name is splitted to only compare the
278 # first part of a test case id.
279 comp_len = len(module.split('.'))
280 for case in self.tc._registry['cases']:
281 case_comp = '.'.join(case.split('.')[0:comp_len])
282 if module == case_comp:
283 found = True
284 break
285
286 if not found:
287 raise OEQATestNotFound("Not found %s in loaded test cases" % \
288 module)
289
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500290 def discover(self):
291 big_suite = self.suiteClass()
292 for path in self.module_paths:
293 _find_duplicated_modules(big_suite, path)
294 suite = super(OETestLoader, self).discover(path,
295 pattern='*.py', top_level_dir=path)
296 big_suite.addTests(suite)
297
298 cases = None
299 discover_classes = [clss for clss in decoratorClasses
300 if issubclass(clss, OETestDiscover)]
301 for clss in discover_classes:
302 cases = clss.discover(self.tc._registry)
303
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500304 if self.modules_required:
305 self._required_modules_validation()
306
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500307 return self.suiteClass(cases) if cases else big_suite
308
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500309 def _filterModule(self, module):
310 if module.__name__ in sys.builtin_module_names:
311 msg = 'Tried to import %s test module but is a built-in'
312 raise ImportError(msg % module.__name__)
313
314 # XXX; If the module has more than one namespace only use
315 # the first to support run the whole module specifying the
316 # <module_name>.[test_class].[test_name]
317 module_name_small = module.__name__.split('.')[0]
318 module_name = module.__name__
319
320 # Normal test modules are loaded if no modules were specified,
Brad Bishopd5ae7d92018-06-14 09:52:03 -0700321 # if module is in the specified module list or if 'auto' is in
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500322 # module list.
323 # Underscore modules are loaded only if specified in module list.
324 load_module = True if not module_name.startswith('_') \
325 and (not self.modules \
326 or module_name in self.modules \
327 or module_name_small in self.modules \
Brad Bishopd5ae7d92018-06-14 09:52:03 -0700328 or 'auto' in self.modules) \
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500329 else False
330
331 load_underscore = True if module_name.startswith('_') \
332 and (module_name in self.modules or \
333 module_name_small in self.modules) \
334 else False
335
336 return (load_module, load_underscore)
337
338
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500339 # XXX After Python 3.5, remove backward compatibility hacks for
340 # use_load_tests deprecation via *args and **kws. See issue 16662.
341 if sys.version_info >= (3,5):
342 def loadTestsFromModule(self, module, *args, pattern=None, **kws):
343 """
344 Returns a suite of all tests cases contained in module.
345 """
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500346 load_module, load_underscore = self._filterModule(module)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500347
348 if load_module or load_underscore:
349 return super(OETestLoader, self).loadTestsFromModule(
350 module, *args, pattern=pattern, **kws)
351 else:
352 return self.suiteClass()
353 else:
354 def loadTestsFromModule(self, module, use_load_tests=True):
355 """
356 Returns a suite of all tests cases contained in module.
357 """
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500358 load_module, load_underscore = self._filterModule(module)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500359
360 if load_module or load_underscore:
361 return super(OETestLoader, self).loadTestsFromModule(
362 module, use_load_tests)
363 else:
364 return self.suiteClass()