summaryrefslogtreecommitdiffstats
path: root/meta/lib/oeqa/core/loader.py
diff options
context:
space:
mode:
authorAníbal Limón <anibal.limon@linux.intel.com>2016-11-09 10:38:37 -0600
committerRichard Purdie <richard.purdie@linuxfoundation.org>2017-01-23 12:05:18 +0000
commit13c8c08b95191829705b5a4c8f0d368c251f0174 (patch)
tree737412ed0d676dcf1982fc6b2cd0046bca760281 /meta/lib/oeqa/core/loader.py
parentabb55ab304af91f68a4e09176ce9c6b995d903e0 (diff)
downloadpoky-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.py235
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
4import os
5import sys
6import unittest
7
8from oeqa.core.utils.path import findFile
9from oeqa.core.utils.test import getSuiteModules, getCaseID
10
11from oeqa.core.case import OETestCase
12from oeqa.core.decorator import decoratorClasses, OETestDecorator, \
13 OETestFilter, OETestDiscover
14
15def _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
24unittest.loader._make_failed_test = _make_failed_test
25
26def _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
32class 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()