precommit_checker.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. #!/usr/bin/env python2.7
  2. # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
  3. from __future__ import absolute_import
  4. from __future__ import division
  5. from __future__ import print_function
  6. from __future__ import unicode_literals
  7. import argparse
  8. import commands
  9. import subprocess
  10. import sys
  11. import re
  12. import os
  13. import time
  14. #
  15. # Simple logger
  16. #
  17. class Log:
  18. def __init__(self, filename):
  19. self.filename = filename
  20. self.f = open(self.filename, 'w+', 0)
  21. def caption(self, str):
  22. line = "\n##### %s #####\n" % str
  23. if self.f:
  24. self.f.write("%s \n" % line)
  25. else:
  26. print(line)
  27. def error(self, str):
  28. data = "\n\n##### ERROR ##### %s" % str
  29. if self.f:
  30. self.f.write("%s \n" % data)
  31. else:
  32. print(data)
  33. def log(self, str):
  34. if self.f:
  35. self.f.write("%s \n" % str)
  36. else:
  37. print(str)
  38. #
  39. # Shell Environment
  40. #
  41. class Env(object):
  42. def __init__(self, logfile, tests):
  43. self.tests = tests
  44. self.log = Log(logfile)
  45. def shell(self, cmd, path=os.getcwd()):
  46. if path:
  47. os.chdir(path)
  48. self.log.log("==== shell session ===========================")
  49. self.log.log("%s> %s" % (path, cmd))
  50. status = subprocess.call("cd %s; %s" % (path, cmd), shell=True,
  51. stdout=self.log.f, stderr=self.log.f)
  52. self.log.log("status = %s" % status)
  53. self.log.log("============================================== \n\n")
  54. return status
  55. def GetOutput(self, cmd, path=os.getcwd()):
  56. if path:
  57. os.chdir(path)
  58. self.log.log("==== shell session ===========================")
  59. self.log.log("%s> %s" % (path, cmd))
  60. status, out = commands.getstatusoutput(cmd)
  61. self.log.log("status = %s" % status)
  62. self.log.log("out = %s" % out)
  63. self.log.log("============================================== \n\n")
  64. return status, out
  65. #
  66. # Pre-commit checker
  67. #
  68. class PreCommitChecker(Env):
  69. def __init__(self, args):
  70. Env.__init__(self, args.logfile, args.tests)
  71. self.ignore_failure = args.ignore_failure
  72. #
  73. # Get commands for a given job from the determinator file
  74. #
  75. def get_commands(self, test):
  76. status, out = self.GetOutput(
  77. "RATIO=1 build_tools/rocksdb-lego-determinator %s" % test, ".")
  78. return status, out
  79. #
  80. # Run a specific CI job
  81. #
  82. def run_test(self, test):
  83. self.log.caption("Running test %s locally" % test)
  84. # get commands for the CI job determinator
  85. status, cmds = self.get_commands(test)
  86. if status != 0:
  87. self.log.error("Error getting commands for test %s" % test)
  88. return False
  89. # Parse the JSON to extract the commands to run
  90. cmds = re.findall("'shell':'([^\']*)'", cmds)
  91. if len(cmds) == 0:
  92. self.log.log("No commands found")
  93. return False
  94. # Run commands
  95. for cmd in cmds:
  96. # Replace J=<..> with the local environment variable
  97. if "J" in os.environ:
  98. cmd = cmd.replace("J=1", "J=%s" % os.environ["J"])
  99. cmd = cmd.replace("make ", "make -j%s " % os.environ["J"])
  100. # Run the command
  101. status = self.shell(cmd, ".")
  102. if status != 0:
  103. self.log.error("Error running command %s for test %s"
  104. % (cmd, test))
  105. return False
  106. return True
  107. #
  108. # Run specified CI jobs
  109. #
  110. def run_tests(self):
  111. if not self.tests:
  112. self.log.error("Invalid args. Please provide tests")
  113. return False
  114. self.print_separator()
  115. self.print_row("TEST", "RESULT")
  116. self.print_separator()
  117. result = True
  118. for test in self.tests:
  119. start_time = time.time()
  120. self.print_test(test)
  121. result = self.run_test(test)
  122. elapsed_min = (time.time() - start_time) / 60
  123. if not result:
  124. self.log.error("Error running test %s" % test)
  125. self.print_result("FAIL (%dm)" % elapsed_min)
  126. if not self.ignore_failure:
  127. return False
  128. result = False
  129. else:
  130. self.print_result("PASS (%dm)" % elapsed_min)
  131. self.print_separator()
  132. return result
  133. #
  134. # Print a line
  135. #
  136. def print_separator(self):
  137. print("".ljust(60, "-"))
  138. #
  139. # Print two colums
  140. #
  141. def print_row(self, c0, c1):
  142. print("%s%s" % (c0.ljust(40), c1.ljust(20)))
  143. def print_test(self, test):
  144. print(test.ljust(40), end="")
  145. sys.stdout.flush()
  146. def print_result(self, result):
  147. print(result.ljust(20))
  148. #
  149. # Main
  150. #
  151. parser = argparse.ArgumentParser(description='RocksDB pre-commit checker.')
  152. # --log <logfile>
  153. parser.add_argument('--logfile', default='/tmp/precommit-check.log',
  154. help='Log file. Default is /tmp/precommit-check.log')
  155. # --ignore_failure
  156. parser.add_argument('--ignore_failure', action='store_true', default=False,
  157. help='Stop when an error occurs')
  158. # <test ....>
  159. parser.add_argument('tests', nargs='+',
  160. help='CI test(s) to run. e.g: unit punit asan tsan ubsan')
  161. args = parser.parse_args()
  162. checker = PreCommitChecker(args)
  163. print("Please follow log %s" % checker.log.filename)
  164. if not checker.run_tests():
  165. print("Error running tests. Please check log file %s"
  166. % checker.log.filename)
  167. sys.exit(1)
  168. sys.exit(0)