patchtest 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  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. # This program is free software; you can redistribute it and/or modify
  10. # it under the terms of the GNU General Public License version 2 as
  11. # published by the Free Software Foundation.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU General Public License along
  19. # with this program; if not, write to the Free Software Foundation, Inc.,
  20. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  21. #
  22. # Author: Leo Sandoval <leonardo.sandoval.gonzalez@linux.intel.com>
  23. #
  24. import sys
  25. import os
  26. import unittest
  27. import fileinput
  28. import logging
  29. import traceback
  30. import json
  31. # Include current path so test cases can see it
  32. sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
  33. # Include patchtest library
  34. sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), '../meta/lib/patchtest'))
  35. from data import PatchTestInput
  36. from repo import PatchTestRepo
  37. import utils
  38. logger = utils.logger_create('patchtest')
  39. info = logger.info
  40. error = logger.error
  41. import repo
  42. def getResult(patch, mergepatch, logfile=None):
  43. class PatchTestResult(unittest.TextTestResult):
  44. """ Patchtest TextTestResult """
  45. shouldStop = True
  46. longMessage = False
  47. success = 'PASS'
  48. fail = 'FAIL'
  49. skip = 'SKIP'
  50. def startTestRun(self):
  51. # let's create the repo already, it can be used later on
  52. repoargs = {
  53. 'repodir': PatchTestInput.repodir,
  54. 'commit' : PatchTestInput.basecommit,
  55. 'branch' : PatchTestInput.basebranch,
  56. 'patch' : patch,
  57. }
  58. self.repo_error = False
  59. self.test_error = False
  60. self.test_failure = False
  61. try:
  62. self.repo = PatchTestInput.repo = PatchTestRepo(**repoargs)
  63. except:
  64. logger.error(traceback.print_exc())
  65. self.repo_error = True
  66. self.stop()
  67. return
  68. if mergepatch:
  69. self.repo.merge()
  70. def addError(self, test, err):
  71. self.test_error = True
  72. (ty, va, trace) = err
  73. logger.error(traceback.print_exc())
  74. def addFailure(self, test, err):
  75. test_description = test.id().split('.')[-1].replace('_', ' ').replace("cve", "CVE").replace("signed off by",
  76. "Signed-off-by").replace("upstream status",
  77. "Upstream-Status").replace("non auh",
  78. "non-AUH").replace("presence format", "presence")
  79. self.test_failure = True
  80. fail_str = '{}: {}: {} ({})'.format(self.fail,
  81. test_description, json.loads(str(err[1]))["issue"],
  82. test.id())
  83. print(fail_str)
  84. if logfile:
  85. with open(logfile, "a") as f:
  86. f.write(fail_str + "\n")
  87. def addSuccess(self, test):
  88. test_description = test.id().split('.')[-1].replace('_', ' ').replace("cve", "CVE").replace("signed off by",
  89. "Signed-off-by").replace("upstream status",
  90. "Upstream-Status").replace("non auh",
  91. "non-AUH").replace("presence format", "presence")
  92. success_str = '{}: {} ({})'.format(self.success,
  93. test_description, test.id())
  94. print(success_str)
  95. if logfile:
  96. with open(logfile, "a") as f:
  97. f.write(success_str + "\n")
  98. def addSkip(self, test, reason):
  99. test_description = test.id().split('.')[-1].replace('_', ' ').replace("cve", "CVE").replace("signed off by",
  100. "Signed-off-by").replace("upstream status",
  101. "Upstream-Status").replace("non auh",
  102. "non-AUH").replace("presence format", "presence")
  103. skip_str = '{}: {}: {} ({})'.format(self.skip,
  104. test_description, json.loads(str(reason))["issue"],
  105. test.id())
  106. print(skip_str)
  107. if logfile:
  108. with open(logfile, "a") as f:
  109. f.write(skip_str + "\n")
  110. def stopTestRun(self):
  111. # in case there was an error on repo object creation, just return
  112. if self.repo_error:
  113. return
  114. self.repo.clean()
  115. return PatchTestResult
  116. def _runner(resultklass, prefix=None):
  117. # load test with the corresponding prefix
  118. loader = unittest.TestLoader()
  119. if prefix:
  120. loader.testMethodPrefix = prefix
  121. # create the suite with discovered tests and the corresponding runner
  122. suite = loader.discover(start_dir=PatchTestInput.startdir, pattern=PatchTestInput.pattern, top_level_dir=PatchTestInput.topdir)
  123. ntc = suite.countTestCases()
  124. # if there are no test cases, just quit
  125. if not ntc:
  126. return 2
  127. runner = unittest.TextTestRunner(resultclass=resultklass, verbosity=0)
  128. try:
  129. result = runner.run(suite)
  130. except:
  131. logger.error(traceback.print_exc())
  132. logger.error('patchtest: something went wrong')
  133. return 1
  134. return 0
  135. def run(patch, logfile=None):
  136. """ Load, setup and run pre and post-merge tests """
  137. # Get the result class and install the control-c handler
  138. unittest.installHandler()
  139. # run pre-merge tests, meaning those methods with 'pretest' as prefix
  140. premerge_resultklass = getResult(patch, False, logfile)
  141. premerge_result = _runner(premerge_resultklass, 'pretest')
  142. # run post-merge tests, meaning those methods with 'test' as prefix
  143. postmerge_resultklass = getResult(patch, True, logfile)
  144. postmerge_result = _runner(postmerge_resultklass, 'test')
  145. if premerge_result == 2 and postmerge_result == 2:
  146. logger.error('patchtest: any test cases found - did you specify the correct suite directory?')
  147. return premerge_result or postmerge_result
  148. def main():
  149. tmp_patch = False
  150. patch_path = PatchTestInput.patch_path
  151. log_results = PatchTestInput.log_results
  152. log_path = None
  153. patch_list = None
  154. if os.path.isdir(patch_path):
  155. patch_list = [os.path.join(patch_path, filename) for filename in os.listdir(patch_path)]
  156. else:
  157. patch_list = [patch_path]
  158. for patch in patch_list:
  159. if os.path.getsize(patch) == 0:
  160. logger.error('patchtest: patch is empty')
  161. return 1
  162. logger.info('Testing patch %s' % patch)
  163. if log_results:
  164. log_path = patch + ".testresult"
  165. with open(log_path, "a") as f:
  166. f.write("Patchtest results for patch '%s':\n\n" % patch)
  167. try:
  168. if log_path:
  169. run(patch, log_path)
  170. else:
  171. run(patch)
  172. finally:
  173. if tmp_patch:
  174. os.remove(patch)
  175. if __name__ == '__main__':
  176. ret = 1
  177. # Parse the command line arguments and store it on the PatchTestInput namespace
  178. PatchTestInput.set_namespace()
  179. # set debugging level
  180. if PatchTestInput.debug:
  181. logger.setLevel(logging.DEBUG)
  182. # if topdir not define, default it to startdir
  183. if not PatchTestInput.topdir:
  184. PatchTestInput.topdir = PatchTestInput.startdir
  185. try:
  186. ret = main()
  187. except Exception:
  188. import traceback
  189. traceback.print_exc(5)
  190. sys.exit(ret)