runner.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. # Copyright (C) 2016 Intel Corporation
  2. # Released under the MIT license (see COPYING.MIT)
  3. import os
  4. import time
  5. import unittest
  6. import logging
  7. import re
  8. import json
  9. import sys
  10. from unittest import TextTestResult as _TestResult
  11. from unittest import TextTestRunner as _TestRunner
  12. class OEStreamLogger(object):
  13. def __init__(self, logger):
  14. self.logger = logger
  15. self.buffer = ""
  16. def write(self, msg):
  17. if len(msg) > 1 and msg[0] != '\n':
  18. if '...' in msg:
  19. self.buffer += msg
  20. elif self.buffer:
  21. self.buffer += msg
  22. self.logger.log(logging.INFO, self.buffer)
  23. self.buffer = ""
  24. else:
  25. self.logger.log(logging.INFO, msg)
  26. def flush(self):
  27. for handler in self.logger.handlers:
  28. handler.flush()
  29. class OETestResult(_TestResult):
  30. def __init__(self, tc, *args, **kwargs):
  31. super(OETestResult, self).__init__(*args, **kwargs)
  32. self.successes = []
  33. self.starttime = {}
  34. self.endtime = {}
  35. self.progressinfo = {}
  36. # Inject into tc so that TestDepends decorator can see results
  37. tc.results = self
  38. self.tc = tc
  39. # stdout and stderr for each test case
  40. self.logged_output = {}
  41. def startTest(self, test):
  42. # May have been set by concurrencytest
  43. if test.id() not in self.starttime:
  44. self.starttime[test.id()] = time.time()
  45. super(OETestResult, self).startTest(test)
  46. def stopTest(self, test):
  47. self.endtime[test.id()] = time.time()
  48. if self.buffer:
  49. self.logged_output[test.id()] = (
  50. sys.stdout.getvalue(), sys.stderr.getvalue())
  51. super(OETestResult, self).stopTest(test)
  52. if test.id() in self.progressinfo:
  53. self.tc.logger.info(self.progressinfo[test.id()])
  54. # Print the errors/failures early to aid/speed debugging, its a pain
  55. # to wait until selftest finishes to see them.
  56. for t in ['failures', 'errors', 'skipped', 'expectedFailures']:
  57. for (scase, msg) in getattr(self, t):
  58. if test.id() == scase.id():
  59. self.tc.logger.info(str(msg))
  60. break
  61. def logSummary(self, component, context_msg=''):
  62. elapsed_time = self.tc._run_end_time - self.tc._run_start_time
  63. self.tc.logger.info("SUMMARY:")
  64. self.tc.logger.info("%s (%s) - Ran %d test%s in %.3fs" % (component,
  65. context_msg, self.testsRun, self.testsRun != 1 and "s" or "",
  66. elapsed_time))
  67. if self.wasSuccessful():
  68. msg = "%s - OK - All required tests passed" % component
  69. else:
  70. msg = "%s - FAIL - Required tests failed" % component
  71. msg += " (successes=%d, skipped=%d, failures=%d, errors=%d)" % (len(self.successes), len(self.skipped), len(self.failures), len(self.errors))
  72. self.tc.logger.info(msg)
  73. def _getTestResultDetails(self, case):
  74. result_types = {'failures': 'FAILED', 'errors': 'ERROR', 'skipped': 'SKIPPED',
  75. 'expectedFailures': 'EXPECTEDFAIL', 'successes': 'PASSED'}
  76. for rtype in result_types:
  77. found = False
  78. for (scase, msg) in getattr(self, rtype):
  79. if case.id() == scase.id():
  80. found = True
  81. break
  82. scase_str = str(scase.id())
  83. # When fails at module or class level the class name is passed as string
  84. # so figure out to see if match
  85. m = re.search(r"^setUpModule \((?P<module_name>.*)\)$", scase_str)
  86. if m:
  87. if case.__class__.__module__ == m.group('module_name'):
  88. found = True
  89. break
  90. m = re.search(r"^setUpClass \((?P<class_name>.*)\)$", scase_str)
  91. if m:
  92. class_name = "%s.%s" % (case.__class__.__module__,
  93. case.__class__.__name__)
  94. if class_name == m.group('class_name'):
  95. found = True
  96. break
  97. if found:
  98. return result_types[rtype], msg
  99. return 'UNKNOWN', None
  100. def addSuccess(self, test):
  101. #Added so we can keep track of successes too
  102. self.successes.append((test, None))
  103. super(OETestResult, self).addSuccess(test)
  104. def logDetails(self, json_file_dir=None, configuration=None, result_id=None,
  105. dump_streams=False):
  106. self.tc.logger.info("RESULTS:")
  107. result = {}
  108. logs = {}
  109. if hasattr(self.tc, "extraresults"):
  110. result = self.tc.extraresults
  111. for case_name in self.tc._registry['cases']:
  112. case = self.tc._registry['cases'][case_name]
  113. (status, log) = self._getTestResultDetails(case)
  114. t = ""
  115. if case.id() in self.starttime and case.id() in self.endtime:
  116. t = " (" + "{0:.2f}".format(self.endtime[case.id()] - self.starttime[case.id()]) + "s)"
  117. if status not in logs:
  118. logs[status] = []
  119. logs[status].append("RESULTS - %s: %s%s" % (case.id(), status, t))
  120. report = {'status': status}
  121. if log:
  122. report['log'] = log
  123. if dump_streams and case.id() in self.logged_output:
  124. (stdout, stderr) = self.logged_output[case.id()]
  125. report['stdout'] = stdout
  126. report['stderr'] = stderr
  127. result[case.id()] = report
  128. for i in ['PASSED', 'SKIPPED', 'EXPECTEDFAIL', 'ERROR', 'FAILED', 'UNKNOWN']:
  129. if i not in logs:
  130. continue
  131. for l in logs[i]:
  132. self.tc.logger.info(l)
  133. if json_file_dir:
  134. tresultjsonhelper = OETestResultJSONHelper()
  135. tresultjsonhelper.dump_testresult_file(json_file_dir, configuration, result_id, result)
  136. def wasSuccessful(self):
  137. # Override as we unexpected successes aren't failures for us
  138. return (len(self.failures) == len(self.errors) == 0)
  139. class OEListTestsResult(object):
  140. def wasSuccessful(self):
  141. return True
  142. class OETestRunner(_TestRunner):
  143. streamLoggerClass = OEStreamLogger
  144. def __init__(self, tc, *args, **kwargs):
  145. kwargs['stream'] = self.streamLoggerClass(tc.logger)
  146. super(OETestRunner, self).__init__(*args, **kwargs)
  147. self.tc = tc
  148. self.resultclass = OETestResult
  149. def _makeResult(self):
  150. return self.resultclass(self.tc, self.stream, self.descriptions,
  151. self.verbosity)
  152. def _walk_suite(self, suite, func):
  153. for obj in suite:
  154. if isinstance(obj, unittest.suite.TestSuite):
  155. if len(obj._tests):
  156. self._walk_suite(obj, func)
  157. elif isinstance(obj, unittest.case.TestCase):
  158. func(self.tc.logger, obj)
  159. self._walked_cases = self._walked_cases + 1
  160. def _list_tests_name(self, suite):
  161. from oeqa.core.decorator.oetag import OETestTag
  162. self._walked_cases = 0
  163. def _list_cases(logger, case):
  164. oetag = None
  165. if hasattr(case, 'decorators'):
  166. for d in case.decorators:
  167. if isinstance(d, OETestTag):
  168. oetag = d.oetag
  169. logger.info("%s\t\t%s" % (oetag, case.id()))
  170. self.tc.logger.info("Listing all available tests:")
  171. self._walked_cases = 0
  172. self.tc.logger.info("id\ttag\t\ttest")
  173. self.tc.logger.info("-" * 80)
  174. self._walk_suite(suite, _list_cases)
  175. self.tc.logger.info("-" * 80)
  176. self.tc.logger.info("Total found:\t%s" % self._walked_cases)
  177. def _list_tests_class(self, suite):
  178. self._walked_cases = 0
  179. curr = {}
  180. def _list_classes(logger, case):
  181. if not 'module' in curr or curr['module'] != case.__module__:
  182. curr['module'] = case.__module__
  183. logger.info(curr['module'])
  184. if not 'class' in curr or curr['class'] != \
  185. case.__class__.__name__:
  186. curr['class'] = case.__class__.__name__
  187. logger.info(" -- %s" % curr['class'])
  188. logger.info(" -- -- %s" % case._testMethodName)
  189. self.tc.logger.info("Listing all available test classes:")
  190. self._walk_suite(suite, _list_classes)
  191. def _list_tests_module(self, suite):
  192. self._walked_cases = 0
  193. listed = []
  194. def _list_modules(logger, case):
  195. if not case.__module__ in listed:
  196. if case.__module__.startswith('_'):
  197. logger.info("%s (hidden)" % case.__module__)
  198. else:
  199. logger.info(case.__module__)
  200. listed.append(case.__module__)
  201. self.tc.logger.info("Listing all available test modules:")
  202. self._walk_suite(suite, _list_modules)
  203. def list_tests(self, suite, display_type):
  204. if display_type == 'name':
  205. self._list_tests_name(suite)
  206. elif display_type == 'class':
  207. self._list_tests_class(suite)
  208. elif display_type == 'module':
  209. self._list_tests_module(suite)
  210. return OEListTestsResult()
  211. class OETestResultJSONHelper(object):
  212. testresult_filename = 'testresults.json'
  213. def _get_existing_testresults_if_available(self, write_dir):
  214. testresults = {}
  215. file = os.path.join(write_dir, self.testresult_filename)
  216. if os.path.exists(file):
  217. with open(file, "r") as f:
  218. testresults = json.load(f)
  219. return testresults
  220. def _write_file(self, write_dir, file_name, file_content):
  221. file_path = os.path.join(write_dir, file_name)
  222. with open(file_path, 'w') as the_file:
  223. the_file.write(file_content)
  224. def dump_testresult_file(self, write_dir, configuration, result_id, test_result):
  225. bb.utils.mkdirhier(write_dir)
  226. lf = bb.utils.lockfile(os.path.join(write_dir, 'jsontestresult.lock'))
  227. test_results = self._get_existing_testresults_if_available(write_dir)
  228. test_results[result_id] = {'configuration': configuration, 'result': test_result}
  229. json_testresults = json.dumps(test_results, sort_keys=True, indent=4)
  230. self._write_file(write_dir, self.testresult_filename, json_testresults)
  231. bb.utils.unlockfile(lf)