patchtest 7.8 KB

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