runner.py 10 KB

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