diff options
author | Aníbal Limón <anibal.limon@linux.intel.com> | 2016-11-09 10:38:37 -0600 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2017-01-23 12:05:18 +0000 |
commit | 13c8c08b95191829705b5a4c8f0d368c251f0174 (patch) | |
tree | 737412ed0d676dcf1982fc6b2cd0046bca760281 /meta/lib/oeqa/core/loader.py | |
parent | abb55ab304af91f68a4e09176ce9c6b995d903e0 (diff) | |
download | poky-13c8c08b95191829705b5a4c8f0d368c251f0174.tar.gz |
oeqa/core: Add loader, context and decorator modules
loader: Implements OETestLoader handling OETestDecorator
and filtering support when load tests. The OETestLoader is
responsible to set custom methods, attrs of the OEQA
frameowork.
[YOCTO #10231]
[YOCTO #10317]
[YOCTO #10353]
decorator: Add base class OETestDecorator to provide a common
way to define decorators to be used over OETestCase's, every
decorator has a method to be called when loading tests and
before test execution starts. Special decorators could be
implemented for filter tests on loading phase.
context: Provides HIGH level API for loadTests and runTests
of certain test component (i.e. runtime, sdk, selftest).
[YOCTO #10230]
(From OE-Core rev: 275ef03b77ef5f23b75cb01c55206d1ab0261342)
Signed-off-by: Aníbal Limón <anibal.limon@linux.intel.com>
Signed-off-by: Mariano Lopez <mariano.lopez@linux.intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'meta/lib/oeqa/core/loader.py')
-rw-r--r-- | meta/lib/oeqa/core/loader.py | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/meta/lib/oeqa/core/loader.py b/meta/lib/oeqa/core/loader.py new file mode 100644 index 0000000000..c73ef9a0fc --- /dev/null +++ b/meta/lib/oeqa/core/loader.py | |||
@@ -0,0 +1,235 @@ | |||
1 | # Copyright (C) 2016 Intel Corporation | ||
2 | # Released under the MIT license (see COPYING.MIT) | ||
3 | |||
4 | import os | ||
5 | import sys | ||
6 | import unittest | ||
7 | |||
8 | from oeqa.core.utils.path import findFile | ||
9 | from oeqa.core.utils.test import getSuiteModules, getCaseID | ||
10 | |||
11 | from oeqa.core.case import OETestCase | ||
12 | from oeqa.core.decorator import decoratorClasses, OETestDecorator, \ | ||
13 | OETestFilter, OETestDiscover | ||
14 | |||
15 | def _make_failed_test(classname, methodname, exception, suiteClass): | ||
16 | """ | ||
17 | When loading tests unittest framework stores the exception in a new | ||
18 | class created for be displayed into run(). | ||
19 | |||
20 | For our purposes will be better to raise the exception in loading | ||
21 | step instead of wait to run the test suite. | ||
22 | """ | ||
23 | raise exception | ||
24 | unittest.loader._make_failed_test = _make_failed_test | ||
25 | |||
26 | def _find_duplicated_modules(suite, directory): | ||
27 | for module in getSuiteModules(suite): | ||
28 | path = findFile('%s.py' % module, directory) | ||
29 | if path: | ||
30 | raise ImportError("Duplicated %s module found in %s" % (module, path)) | ||
31 | |||
32 | class OETestLoader(unittest.TestLoader): | ||
33 | caseClass = OETestCase | ||
34 | |||
35 | kwargs_names = ['testMethodPrefix', 'sortTestMethodUsing', 'suiteClass', | ||
36 | '_top_level_dir'] | ||
37 | |||
38 | def __init__(self, tc, module_paths, modules, tests, modules_required, | ||
39 | filters, *args, **kwargs): | ||
40 | self.tc = tc | ||
41 | |||
42 | self.modules = modules | ||
43 | self.tests = tests | ||
44 | self.modules_required = modules_required | ||
45 | |||
46 | self.filters = filters | ||
47 | self.decorator_filters = [d for d in decoratorClasses if \ | ||
48 | issubclass(d, OETestFilter)] | ||
49 | self._validateFilters(self.filters, self.decorator_filters) | ||
50 | self.used_filters = [d for d in self.decorator_filters | ||
51 | for f in self.filters | ||
52 | if f in d.attrs] | ||
53 | |||
54 | if isinstance(module_paths, str): | ||
55 | module_paths = [module_paths] | ||
56 | elif not isinstance(module_paths, list): | ||
57 | raise TypeError('module_paths must be a str or a list of str') | ||
58 | self.module_paths = module_paths | ||
59 | |||
60 | for kwname in self.kwargs_names: | ||
61 | if kwname in kwargs: | ||
62 | setattr(self, kwname, kwargs[kwname]) | ||
63 | |||
64 | self._patchCaseClass(self.caseClass) | ||
65 | |||
66 | def _patchCaseClass(self, testCaseClass): | ||
67 | # Adds custom attributes to the OETestCase class | ||
68 | setattr(testCaseClass, 'tc', self.tc) | ||
69 | setattr(testCaseClass, 'td', self.tc.td) | ||
70 | setattr(testCaseClass, 'logger', self.tc.logger) | ||
71 | |||
72 | def _validateFilters(self, filters, decorator_filters): | ||
73 | # Validate if filter isn't empty | ||
74 | for key,value in filters.items(): | ||
75 | if not value: | ||
76 | raise TypeError("Filter %s specified is empty" % key) | ||
77 | |||
78 | # Validate unique attributes | ||
79 | attr_filters = [attr for clss in decorator_filters \ | ||
80 | for attr in clss.attrs] | ||
81 | dup_attr = [attr for attr in attr_filters | ||
82 | if attr_filters.count(attr) > 1] | ||
83 | if dup_attr: | ||
84 | raise TypeError('Detected duplicated attribute(s) %s in filter' | ||
85 | ' decorators' % ' ,'.join(dup_attr)) | ||
86 | |||
87 | # Validate if filter is supported | ||
88 | for f in filters: | ||
89 | if f not in attr_filters: | ||
90 | classes = ', '.join([d.__name__ for d in decorator_filters]) | ||
91 | raise TypeError('Found "%s" filter but not declared in any of ' | ||
92 | '%s decorators' % (f, classes)) | ||
93 | |||
94 | def _registerTestCase(self, case): | ||
95 | case_id = case.id() | ||
96 | self.tc._registry['cases'][case_id] = case | ||
97 | |||
98 | def _handleTestCaseDecorators(self, case): | ||
99 | def _handle(obj): | ||
100 | if isinstance(obj, OETestDecorator): | ||
101 | if not obj.__class__ in decoratorClasses: | ||
102 | raise Exception("Decorator %s isn't registered" \ | ||
103 | " in decoratorClasses." % obj.__name__) | ||
104 | obj.bind(self.tc._registry, case) | ||
105 | |||
106 | def _walk_closure(obj): | ||
107 | if hasattr(obj, '__closure__') and obj.__closure__: | ||
108 | for f in obj.__closure__: | ||
109 | obj = f.cell_contents | ||
110 | _handle(obj) | ||
111 | _walk_closure(obj) | ||
112 | method = getattr(case, case._testMethodName, None) | ||
113 | _walk_closure(method) | ||
114 | |||
115 | def _filterTest(self, case): | ||
116 | """ | ||
117 | Returns True if test case must be filtered, False otherwise. | ||
118 | """ | ||
119 | if self.filters: | ||
120 | filters = self.filters.copy() | ||
121 | case_decorators = [cd for cd in case.decorators | ||
122 | if cd.__class__ in self.used_filters] | ||
123 | |||
124 | # Iterate over case decorators to check if needs to be filtered. | ||
125 | for cd in case_decorators: | ||
126 | if cd.filtrate(filters): | ||
127 | return True | ||
128 | |||
129 | # Case is missing one or more decorators for all the filters | ||
130 | # being used, so filter test case. | ||
131 | if filters: | ||
132 | return True | ||
133 | |||
134 | return False | ||
135 | |||
136 | def _getTestCase(self, testCaseClass, tcName): | ||
137 | if not hasattr(testCaseClass, '__oeqa_loader'): | ||
138 | # In order to support data_vars validation | ||
139 | # monkey patch the default setUp/tearDown{Class} to use | ||
140 | # the ones provided by OETestCase | ||
141 | setattr(testCaseClass, 'setUpClassMethod', | ||
142 | getattr(testCaseClass, 'setUpClass')) | ||
143 | setattr(testCaseClass, 'tearDownClassMethod', | ||
144 | getattr(testCaseClass, 'tearDownClass')) | ||
145 | setattr(testCaseClass, 'setUpClass', | ||
146 | testCaseClass._oeSetUpClass) | ||
147 | setattr(testCaseClass, 'tearDownClass', | ||
148 | testCaseClass._oeTearDownClass) | ||
149 | |||
150 | # In order to support decorators initialization | ||
151 | # monkey patch the default setUp/tearDown to use | ||
152 | # a setUpDecorators/tearDownDecorators that methods | ||
153 | # will call setUp/tearDown original methods. | ||
154 | setattr(testCaseClass, 'setUpMethod', | ||
155 | getattr(testCaseClass, 'setUp')) | ||
156 | setattr(testCaseClass, 'tearDownMethod', | ||
157 | getattr(testCaseClass, 'tearDown')) | ||
158 | setattr(testCaseClass, 'setUp', testCaseClass._oeSetUp) | ||
159 | setattr(testCaseClass, 'tearDown', testCaseClass._oeTearDown) | ||
160 | |||
161 | setattr(testCaseClass, '__oeqa_loader', True) | ||
162 | |||
163 | case = testCaseClass(tcName) | ||
164 | setattr(case, 'decorators', []) | ||
165 | |||
166 | return case | ||
167 | |||
168 | def loadTestsFromTestCase(self, testCaseClass): | ||
169 | """ | ||
170 | Returns a suite of all tests cases contained in testCaseClass. | ||
171 | """ | ||
172 | if issubclass(testCaseClass, unittest.suite.TestSuite): | ||
173 | raise TypeError("Test cases should not be derived from TestSuite." \ | ||
174 | " Maybe you meant to derive from TestCase?") | ||
175 | if not issubclass(testCaseClass, self.caseClass): | ||
176 | raise TypeError("Test cases need to be derived from %s" % \ | ||
177 | caseClass.__name__) | ||
178 | |||
179 | |||
180 | testCaseNames = self.getTestCaseNames(testCaseClass) | ||
181 | if not testCaseNames and hasattr(testCaseClass, 'runTest'): | ||
182 | testCaseNames = ['runTest'] | ||
183 | |||
184 | suite = [] | ||
185 | for tcName in testCaseNames: | ||
186 | case = self._getTestCase(testCaseClass, tcName) | ||
187 | # Filer by case id | ||
188 | if not (self.tests and not 'all' in self.tests | ||
189 | and not getCaseID(case) in self.tests): | ||
190 | self._handleTestCaseDecorators(case) | ||
191 | |||
192 | # Filter by decorators | ||
193 | if not self._filterTest(case): | ||
194 | self._registerTestCase(case) | ||
195 | suite.append(case) | ||
196 | |||
197 | return self.suiteClass(suite) | ||
198 | |||
199 | def discover(self): | ||
200 | big_suite = self.suiteClass() | ||
201 | for path in self.module_paths: | ||
202 | _find_duplicated_modules(big_suite, path) | ||
203 | suite = super(OETestLoader, self).discover(path, | ||
204 | pattern='*.py', top_level_dir=path) | ||
205 | big_suite.addTests(suite) | ||
206 | |||
207 | cases = None | ||
208 | discover_classes = [clss for clss in decoratorClasses | ||
209 | if issubclass(clss, OETestDiscover)] | ||
210 | for clss in discover_classes: | ||
211 | cases = clss.discover(self.tc._registry) | ||
212 | |||
213 | return self.suiteClass(cases) if cases else big_suite | ||
214 | |||
215 | # XXX After Python 3.5, remove backward compatibility hacks for | ||
216 | # use_load_tests deprecation via *args and **kws. See issue 16662. | ||
217 | if sys.version_info >= (3,5): | ||
218 | def loadTestsFromModule(self, module, *args, pattern=None, **kws): | ||
219 | if not self.modules or "all" in self.modules or \ | ||
220 | module.__name__ in self.modules: | ||
221 | return super(OETestLoader, self).loadTestsFromModule( | ||
222 | module, *args, pattern=pattern, **kws) | ||
223 | else: | ||
224 | return self.suiteClass() | ||
225 | else: | ||
226 | def loadTestsFromModule(self, module, use_load_tests=True): | ||
227 | """ | ||
228 | Returns a suite of all tests cases contained in module. | ||
229 | """ | ||
230 | if not self.modules or "all" in self.modules or \ | ||
231 | module.__name__ in self.modules: | ||
232 | return super(OETestLoader, self).loadTestsFromModule( | ||
233 | module, use_load_tests) | ||
234 | else: | ||
235 | return self.suiteClass() | ||