patchtest 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. #!/usr/bin/env python3
  2. # ex:ts=4:sw=4:sts=4:et
  3. # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
  4. #
  5. # patchtest: execute all unittest test cases discovered for a single patch
  6. #
  7. # Copyright (C) 2016 Intel Corporation
  8. #
  9. # SPDX-License-Identifier: GPL-2.0-only
  10. #
  11. import json
  12. import logging
  13. import os
  14. import sys
  15. import traceback
  16. import unittest
  17. # Include current path so test cases can see it
  18. sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
  19. # Include patchtest library
  20. sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), '../meta/lib/patchtest'))
  21. from patchtest_parser import PatchtestParser
  22. from repo import PatchTestRepo
  23. logger = logging.getLogger("patchtest")
  24. loggerhandler = logging.StreamHandler()
  25. loggerhandler.setFormatter(logging.Formatter("%(message)s"))
  26. logger.addHandler(loggerhandler)
  27. logger.setLevel(logging.INFO)
  28. info = logger.info
  29. error = logger.error
  30. def getResult(patch, mergepatch, logfile=None):
  31. class PatchTestResult(unittest.TextTestResult):
  32. """ Patchtest TextTestResult """
  33. shouldStop = True
  34. longMessage = False
  35. success = 'PASS'
  36. fail = 'FAIL'
  37. skip = 'SKIP'
  38. def startTestRun(self):
  39. # let's create the repo already, it can be used later on
  40. repoargs = {
  41. "repodir": PatchtestParser.repodir,
  42. "commit": PatchtestParser.basecommit,
  43. "branch": PatchtestParser.basebranch,
  44. "patch": patch,
  45. }
  46. self.repo_error = False
  47. self.test_error = False
  48. self.test_failure = False
  49. try:
  50. self.repo = PatchtestParser.repo = PatchTestRepo(**repoargs)
  51. except:
  52. logger.error(traceback.print_exc())
  53. self.repo_error = True
  54. self.stop()
  55. return
  56. if mergepatch:
  57. self.repo.merge()
  58. def addError(self, test, err):
  59. self.test_error = True
  60. (ty, va, trace) = err
  61. logger.error(traceback.print_exc())
  62. def addFailure(self, test, err):
  63. test_description = test.id().split('.')[-1].replace('_', ' ').replace("cve", "CVE").replace("signed off by",
  64. "Signed-off-by").replace("upstream status",
  65. "Upstream-Status").replace("non auh",
  66. "non-AUH").replace("presence format", "presence")
  67. self.test_failure = True
  68. fail_str = '{}: {}: {} ({})'.format(self.fail,
  69. test_description, json.loads(str(err[1]))["issue"],
  70. test.id())
  71. print(fail_str)
  72. if logfile:
  73. with open(logfile, "a") as f:
  74. f.write(fail_str + "\n")
  75. def addSuccess(self, test):
  76. test_description = test.id().split('.')[-1].replace('_', ' ').replace("cve", "CVE").replace("signed off by",
  77. "Signed-off-by").replace("upstream status",
  78. "Upstream-Status").replace("non auh",
  79. "non-AUH").replace("presence format", "presence")
  80. success_str = '{}: {} ({})'.format(self.success,
  81. test_description, test.id())
  82. print(success_str)
  83. if logfile:
  84. with open(logfile, "a") as f:
  85. f.write(success_str + "\n")
  86. def addSkip(self, test, reason):
  87. test_description = test.id().split('.')[-1].replace('_', ' ').replace("cve", "CVE").replace("signed off by",
  88. "Signed-off-by").replace("upstream status",
  89. "Upstream-Status").replace("non auh",
  90. "non-AUH").replace("presence format", "presence")
  91. skip_str = '{}: {}: {} ({})'.format(self.skip,
  92. test_description, json.loads(str(reason))["issue"],
  93. test.id())
  94. print(skip_str)
  95. if logfile:
  96. with open(logfile, "a") as f:
  97. f.write(skip_str + "\n")
  98. def stopTestRun(self):
  99. # in case there was an error on repo object creation, just return
  100. if self.repo_error:
  101. return
  102. self.repo.clean()
  103. return PatchTestResult
  104. def _runner(resultklass, prefix=None):
  105. # load test with the corresponding prefix
  106. loader = unittest.TestLoader()
  107. if prefix:
  108. loader.testMethodPrefix = prefix
  109. # create the suite with discovered tests and the corresponding runner
  110. suite = loader.discover(
  111. start_dir=PatchtestParser.testdir,
  112. pattern=PatchtestParser.pattern,
  113. top_level_dir=PatchtestParser.topdir,
  114. )
  115. ntc = suite.countTestCases()
  116. # if there are no test cases, just quit
  117. if not ntc:
  118. return 2
  119. runner = unittest.TextTestRunner(resultclass=resultklass, verbosity=0)
  120. try:
  121. result = runner.run(suite)
  122. except:
  123. logger.error(traceback.print_exc())
  124. logger.error('patchtest: something went wrong')
  125. return 1
  126. if result.test_failure or result.test_error:
  127. return 1
  128. return 0
  129. def run(patch, logfile=None):
  130. """ Load, setup and run pre and post-merge tests """
  131. # Get the result class and install the control-c handler
  132. unittest.installHandler()
  133. # run pre-merge tests, meaning those methods with 'pretest' as prefix
  134. premerge_resultklass = getResult(patch, False, logfile)
  135. premerge_result = _runner(premerge_resultklass, 'pretest')
  136. # run post-merge tests, meaning those methods with 'test' as prefix
  137. postmerge_resultklass = getResult(patch, True, logfile)
  138. postmerge_result = _runner(postmerge_resultklass, 'test')
  139. print_result_message(premerge_result, postmerge_result)
  140. return premerge_result or postmerge_result
  141. def print_result_message(preresult, postresult):
  142. print("----------------------------------------------------------------------\n")
  143. if preresult == 2 and postresult == 2:
  144. logger.error(
  145. "patchtest: No test cases found - did you specify the correct suite directory?"
  146. )
  147. if preresult == 1 or postresult == 1:
  148. logger.error(
  149. "WARNING: patchtest: At least one patchtest caused a failure or an error - please check https://wiki.yoctoproject.org/wiki/Patchtest for further guidance"
  150. )
  151. else:
  152. logger.info("OK: patchtest: All patchtests passed")
  153. print("----------------------------------------------------------------------\n")
  154. def main():
  155. tmp_patch = False
  156. patch_path = PatchtestParser.patch_path
  157. log_results = PatchtestParser.log_results
  158. log_path = None
  159. patch_list = None
  160. git_status = os.popen("(cd %s && git status)" % PatchtestParser.repodir).read()
  161. status_matches = ["Changes not staged for commit", "Changes to be committed"]
  162. if any([match in git_status for match in status_matches]):
  163. logger.error("patchtest: there are uncommitted changes in the target repo that would be overwritten. Please commit or restore them before running patchtest")
  164. return 1
  165. if os.path.isdir(patch_path):
  166. patch_list = [os.path.join(patch_path, filename) for filename in sorted(os.listdir(patch_path))]
  167. else:
  168. patch_list = [patch_path]
  169. for patch in patch_list:
  170. if os.path.getsize(patch) == 0:
  171. logger.error('patchtest: patch is empty')
  172. return 1
  173. logger.info('Testing patch %s' % patch)
  174. if log_results:
  175. log_path = patch + ".testresult"
  176. with open(log_path, "a") as f:
  177. f.write("Patchtest results for patch '%s':\n\n" % patch)
  178. try:
  179. if log_path:
  180. run(patch, log_path)
  181. else:
  182. run(patch)
  183. finally:
  184. if tmp_patch:
  185. os.remove(patch)
  186. if __name__ == '__main__':
  187. ret = 1
  188. # Parse the command line arguments and store it on the PatchtestParser namespace
  189. PatchtestParser.set_namespace()
  190. # set debugging level
  191. if PatchtestParser.debug:
  192. logger.setLevel(logging.DEBUG)
  193. # if topdir not define, default it to testdir
  194. if not PatchtestParser.topdir:
  195. PatchtestParser.topdir = PatchtestParser.testdir
  196. try:
  197. ret = main()
  198. except Exception:
  199. import traceback
  200. traceback.print_exc(5)
  201. sys.exit(ret)