blob: f8bb23f344c697fbd7f1097327c4758a7e14a628 [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
5import time
6import unittest
7import logging
Brad Bishopd7bf8c12018-02-25 22:55:05 -05008import re
Brad Bishopf86d0552018-12-04 14:18:15 -08009import json
Brad Bishop6e60e8b2018-02-01 10:27:11 -050010
Brad Bishopf86d0552018-12-04 14:18:15 -080011from unittest import TextTestResult as _TestResult
12from unittest import TextTestRunner as _TestRunner
Brad Bishop6e60e8b2018-02-01 10:27:11 -050013
14class OEStreamLogger(object):
15 def __init__(self, logger):
16 self.logger = logger
17 self.buffer = ""
18
19 def write(self, msg):
20 if len(msg) > 1 and msg[0] != '\n':
Brad Bishopd7bf8c12018-02-25 22:55:05 -050021 if '...' in msg:
22 self.buffer += msg
23 elif self.buffer:
24 self.buffer += msg
25 self.logger.log(logging.INFO, self.buffer)
26 self.buffer = ""
27 else:
28 self.logger.log(logging.INFO, msg)
Brad Bishop6e60e8b2018-02-01 10:27:11 -050029
30 def flush(self):
31 for handler in self.logger.handlers:
32 handler.flush()
33
34class OETestResult(_TestResult):
35 def __init__(self, tc, *args, **kwargs):
36 super(OETestResult, self).__init__(*args, **kwargs)
37
Brad Bishopf86d0552018-12-04 14:18:15 -080038 self.successes = []
39
40 # Inject into tc so that TestDepends decorator can see results
41 tc.results = self
42
Brad Bishop6e60e8b2018-02-01 10:27:11 -050043 self.tc = tc
44
Brad Bishopd7bf8c12018-02-25 22:55:05 -050045 def startTest(self, test):
46 # Allow us to trigger the testcase buffer mode on a per test basis
47 # so stdout/stderr are only printed upon failure. Enables debugging
48 # but clean output
49 if hasattr(test, "buffer"):
50 self.buffer = test.buffer
51 super(OETestResult, self).startTest(test)
52
Brad Bishopd7bf8c12018-02-25 22:55:05 -050053 def logSummary(self, component, context_msg=''):
54 elapsed_time = self.tc._run_end_time - self.tc._run_start_time
55 self.tc.logger.info("SUMMARY:")
56 self.tc.logger.info("%s (%s) - Ran %d test%s in %.3fs" % (component,
57 context_msg, self.testsRun, self.testsRun != 1 and "s" or "",
58 elapsed_time))
59
60 if self.wasSuccessful():
61 msg = "%s - OK - All required tests passed" % component
62 else:
63 msg = "%s - FAIL - Required tests failed" % component
Brad Bishopf86d0552018-12-04 14:18:15 -080064 msg += " (successes=%d, skipped=%d, failures=%d, errors=%d)" % (len(self.successes), len(self.skipped), len(self.failures), len(self.errors))
Brad Bishopd7bf8c12018-02-25 22:55:05 -050065 self.tc.logger.info(msg)
66
Brad Bishopf86d0552018-12-04 14:18:15 -080067 def _getTestResultDetails(self, case):
68 result_types = {'failures': 'FAILED', 'errors': 'ERROR', 'skipped': 'SKIPPED',
69 'expectedFailures': 'EXPECTEDFAIL', 'successes': 'PASSED'}
Brad Bishopd7bf8c12018-02-25 22:55:05 -050070
Brad Bishopf86d0552018-12-04 14:18:15 -080071 for rtype in result_types:
72 found = False
73 for (scase, msg) in getattr(self, rtype):
74 if case.id() == scase.id():
Brad Bishopd7bf8c12018-02-25 22:55:05 -050075 found = True
76 break
Brad Bishopf86d0552018-12-04 14:18:15 -080077 scase_str = str(scase.id())
Brad Bishopd7bf8c12018-02-25 22:55:05 -050078
Brad Bishopf86d0552018-12-04 14:18:15 -080079 # When fails at module or class level the class name is passed as string
80 # so figure out to see if match
81 m = re.search("^setUpModule \((?P<module_name>.*)\)$", scase_str)
82 if m:
83 if case.__class__.__module__ == m.group('module_name'):
84 found = True
85 break
Brad Bishopd7bf8c12018-02-25 22:55:05 -050086
Brad Bishopf86d0552018-12-04 14:18:15 -080087 m = re.search("^setUpClass \((?P<class_name>.*)\)$", scase_str)
88 if m:
89 class_name = "%s.%s" % (case.__class__.__module__,
90 case.__class__.__name__)
Brad Bishopd7bf8c12018-02-25 22:55:05 -050091
Brad Bishopf86d0552018-12-04 14:18:15 -080092 if class_name == m.group('class_name'):
93 found = True
94 break
Brad Bishopd7bf8c12018-02-25 22:55:05 -050095
Brad Bishopf86d0552018-12-04 14:18:15 -080096 if found:
97 return result_types[rtype], msg
Brad Bishopd7bf8c12018-02-25 22:55:05 -050098
Brad Bishopf86d0552018-12-04 14:18:15 -080099 return 'UNKNOWN', None
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500100
Brad Bishopf86d0552018-12-04 14:18:15 -0800101 def addSuccess(self, test):
102 #Added so we can keep track of successes too
103 self.successes.append((test, None))
104 super(OETestResult, self).addSuccess(test)
105
106 def logDetails(self, json_file_dir=None, configuration=None, result_id=None):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500107 self.tc.logger.info("RESULTS:")
Brad Bishopf86d0552018-12-04 14:18:15 -0800108
109 result = {}
110 logs = {}
111 if hasattr(self.tc, "extraresults"):
112 result = self.tc.extraresults
113
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500114 for case_name in self.tc._registry['cases']:
115 case = self.tc._registry['cases'][case_name]
116
Brad Bishopf86d0552018-12-04 14:18:15 -0800117 (status, log) = self._getTestResultDetails(case)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500118
119 oeid = -1
120 if hasattr(case, 'decorators'):
121 for d in case.decorators:
122 if hasattr(d, 'oeid'):
123 oeid = d.oeid
124
Brad Bishopf86d0552018-12-04 14:18:15 -0800125 if status not in logs:
126 logs[status] = []
127 logs[status].append("RESULTS - %s - Testcase %s: %s" % (case.id(), oeid, status))
128 if log:
129 result[case.id()] = {'status': status, 'log': log}
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500130 else:
Brad Bishopf86d0552018-12-04 14:18:15 -0800131 result[case.id()] = {'status': status}
132
133 for i in ['PASSED', 'SKIPPED', 'EXPECTEDFAIL', 'ERROR', 'FAILED', 'UNKNOWN']:
134 if i not in logs:
135 continue
136 for l in logs[i]:
137 self.tc.logger.info(l)
138
139 if json_file_dir:
140 tresultjsonhelper = OETestResultJSONHelper()
141 tresultjsonhelper.dump_testresult_file(json_file_dir, configuration, result_id, result)
142
143 def wasSuccessful(self):
144 # Override as we unexpected successes aren't failures for us
145 return (len(self.failures) == len(self.errors) == 0)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500146
147class OEListTestsResult(object):
148 def wasSuccessful(self):
149 return True
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500150
151class OETestRunner(_TestRunner):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500152 streamLoggerClass = OEStreamLogger
153
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500154 def __init__(self, tc, *args, **kwargs):
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500155 kwargs['stream'] = self.streamLoggerClass(tc.logger)
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500156 super(OETestRunner, self).__init__(*args, **kwargs)
157 self.tc = tc
158 self.resultclass = OETestResult
159
Brad Bishopf86d0552018-12-04 14:18:15 -0800160 def _makeResult(self):
161 return self.resultclass(self.tc, self.stream, self.descriptions,
162 self.verbosity)
Brad Bishopd7bf8c12018-02-25 22:55:05 -0500163
164 def _walk_suite(self, suite, func):
165 for obj in suite:
166 if isinstance(obj, unittest.suite.TestSuite):
167 if len(obj._tests):
168 self._walk_suite(obj, func)
169 elif isinstance(obj, unittest.case.TestCase):
170 func(self.tc.logger, obj)
171 self._walked_cases = self._walked_cases + 1
172
173 def _list_tests_name(self, suite):
174 from oeqa.core.decorator.oeid import OETestID
175 from oeqa.core.decorator.oetag import OETestTag
176
177 self._walked_cases = 0
178
179 def _list_cases_without_id(logger, case):
180
181 found_id = False
182 if hasattr(case, 'decorators'):
183 for d in case.decorators:
184 if isinstance(d, OETestID):
185 found_id = True
186
187 if not found_id:
188 logger.info('oeid missing for %s' % case.id())
189
190 def _list_cases(logger, case):
191 oeid = None
192 oetag = None
193
194 if hasattr(case, 'decorators'):
195 for d in case.decorators:
196 if isinstance(d, OETestID):
197 oeid = d.oeid
198 elif isinstance(d, OETestTag):
199 oetag = d.oetag
200
201 logger.info("%s\t%s\t\t%s" % (oeid, oetag, case.id()))
202
203 self.tc.logger.info("Listing test cases that don't have oeid ...")
204 self._walk_suite(suite, _list_cases_without_id)
205 self.tc.logger.info("-" * 80)
206
207 self.tc.logger.info("Listing all available tests:")
208 self._walked_cases = 0
209 self.tc.logger.info("id\ttag\t\ttest")
210 self.tc.logger.info("-" * 80)
211 self._walk_suite(suite, _list_cases)
212 self.tc.logger.info("-" * 80)
213 self.tc.logger.info("Total found:\t%s" % self._walked_cases)
214
215 def _list_tests_class(self, suite):
216 self._walked_cases = 0
217
218 curr = {}
219 def _list_classes(logger, case):
220 if not 'module' in curr or curr['module'] != case.__module__:
221 curr['module'] = case.__module__
222 logger.info(curr['module'])
223
224 if not 'class' in curr or curr['class'] != \
225 case.__class__.__name__:
226 curr['class'] = case.__class__.__name__
227 logger.info(" -- %s" % curr['class'])
228
229 logger.info(" -- -- %s" % case._testMethodName)
230
231 self.tc.logger.info("Listing all available test classes:")
232 self._walk_suite(suite, _list_classes)
233
234 def _list_tests_module(self, suite):
235 self._walked_cases = 0
236
237 listed = []
238 def _list_modules(logger, case):
239 if not case.__module__ in listed:
240 if case.__module__.startswith('_'):
241 logger.info("%s (hidden)" % case.__module__)
242 else:
243 logger.info(case.__module__)
244 listed.append(case.__module__)
245
246 self.tc.logger.info("Listing all available test modules:")
247 self._walk_suite(suite, _list_modules)
248
249 def list_tests(self, suite, display_type):
250 if display_type == 'name':
251 self._list_tests_name(suite)
252 elif display_type == 'class':
253 self._list_tests_class(suite)
254 elif display_type == 'module':
255 self._list_tests_module(suite)
256
257 return OEListTestsResult()
Brad Bishopf86d0552018-12-04 14:18:15 -0800258
259class OETestResultJSONHelper(object):
260
261 testresult_filename = 'testresults.json'
262
263 def _get_existing_testresults_if_available(self, write_dir):
264 testresults = {}
265 file = os.path.join(write_dir, self.testresult_filename)
266 if os.path.exists(file):
267 with open(file, "r") as f:
268 testresults = json.load(f)
269 return testresults
270
271 def _write_file(self, write_dir, file_name, file_content):
272 file_path = os.path.join(write_dir, file_name)
273 with open(file_path, 'w') as the_file:
274 the_file.write(file_content)
275
276 def dump_testresult_file(self, write_dir, configuration, result_id, test_result):
277 bb.utils.mkdirhier(write_dir)
278 lf = bb.utils.lockfile(os.path.join(write_dir, 'jsontestresult.lock'))
279 test_results = self._get_existing_testresults_if_available(write_dir)
280 test_results[result_id] = {'configuration': configuration, 'result': test_result}
281 json_testresults = json.dumps(test_results, sort_keys=True, indent=4)
282 self._write_file(write_dir, self.testresult_filename, json_testresults)
283 bb.utils.unlockfile(lf)