bigint-tester.py 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. #!/usr/bin/python
  2. # Copyright 2017 the V8 project authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. # for py2/py3 compatibility
  6. from __future__ import print_function
  7. import argparse
  8. import math
  9. import multiprocessing
  10. import os
  11. import random
  12. import subprocess
  13. import sys
  14. import tempfile
  15. # Configuration.
  16. kChars = "0123456789abcdef"
  17. kBase = 16
  18. kLineLength = 70 # A bit less than 80.
  19. kNumInputsGenerate = 20
  20. kNumInputsStress = 1000
  21. # Internally used sentinels.
  22. kNo = 0
  23. kYes = 1
  24. kRandom = 2
  25. TEST_HEADER = """\
  26. // Copyright 2017 the V8 project authors. All rights reserved.
  27. // Use of this source code is governed by a BSD-style license that can be
  28. // found in the LICENSE file.
  29. // Generated by %s.
  30. """ % sys.argv[0]
  31. TEST_BODY = """
  32. var error_count = 0;
  33. for (var i = 0; i < data.length; i++) {
  34. var d = data[i];
  35. %s
  36. }
  37. if (error_count !== 0) {
  38. print("Finished with " + error_count + " errors.")
  39. quit(1);
  40. }"""
  41. def GenRandom(length, negative=kRandom):
  42. if length == 0: return "0n"
  43. s = []
  44. if negative == kYes or (negative == kRandom and (random.randint(0, 1) == 0)):
  45. s.append("-") # 50% chance of negative.
  46. s.append("0x")
  47. s.append(kChars[random.randint(1, kBase - 1)]) # No leading zero.
  48. for i in range(1, length):
  49. s.append(kChars[random.randint(0, kBase - 1)])
  50. s.append("n")
  51. return "".join(s)
  52. def Parse(x):
  53. assert x[-1] == 'n', x
  54. return int(x[:-1], kBase)
  55. def Format(x):
  56. original = x
  57. negative = False
  58. if x == 0: return "0n"
  59. if x < 0:
  60. negative = True
  61. x = -x
  62. s = ""
  63. while x > 0:
  64. s = kChars[x % kBase] + s
  65. x = x / kBase
  66. s = "0x" + s + "n"
  67. if negative:
  68. s = "-" + s
  69. assert Parse(s) == original
  70. return s
  71. class TestGenerator(object):
  72. # Subclasses must implement these.
  73. # Returns a JSON snippet defining inputs and expected output for one test.
  74. def EmitOne(self): raise NotImplementedError
  75. # Returns a snippet of JavaScript that will operate on a variable "d"
  76. # whose content is defined by the result of a call to "EmitOne".
  77. def EmitTestCore(self): raise NotImplementedError
  78. def EmitHeader(self):
  79. return TEST_HEADER
  80. def EmitData(self, count):
  81. s = []
  82. for i in range(count):
  83. s.append(self.EmitOne())
  84. return "var data = [" + ", ".join(s) + "];"
  85. def EmitTestBody(self):
  86. return TEST_BODY % self.EmitTestCore()
  87. def PrintTest(self, count):
  88. print(self.EmitHeader())
  89. print(self.EmitData(count))
  90. print(self.EmitTestBody())
  91. def RunTest(self, count, binary):
  92. try:
  93. fd, path = tempfile.mkstemp(suffix=".js", prefix="bigint-test-")
  94. with open(path, "w") as f:
  95. f.write(self.EmitData(count))
  96. f.write(self.EmitTestBody())
  97. return subprocess.call("%s %s" % (binary, path),
  98. shell=True)
  99. finally:
  100. os.close(fd)
  101. os.remove(path)
  102. class UnaryOp(TestGenerator):
  103. # Subclasses must implement these two.
  104. def GetOpString(self): raise NotImplementedError
  105. def GenerateResult(self, x): raise NotImplementedError
  106. # Subclasses may override this:
  107. def GenerateInput(self):
  108. return GenRandom(random.randint(0, kLineLength))
  109. # Subclasses should not override anything below.
  110. def EmitOne(self):
  111. x_str = self.GenerateInput()
  112. x_num = Parse(x_str)
  113. result_num = self.GenerateResult(x_num)
  114. result_str = Format(result_num)
  115. return "{\n a: %s,\n r: %s\n}" % (x_str, result_str)
  116. def EmitTestCore(self):
  117. return """\
  118. var r = %(op)sd.a;
  119. if (d.r !== r) {
  120. print("Input: " + d.a.toString(%(base)d));
  121. print("Result: " + r.toString(%(base)d));
  122. print("Expected: " + d.r);
  123. error_count++;
  124. }""" % {"op": self.GetOpString(), "base": kBase}
  125. class BinaryOp(TestGenerator):
  126. # Subclasses must implement these two.
  127. def GetOpString(self): raise NotImplementedError
  128. def GenerateResult(self, left, right): raise NotImplementedError
  129. # Subclasses may override these:
  130. def GenerateInputLengths(self):
  131. return random.randint(0, kLineLength), random.randint(0, kLineLength)
  132. def GenerateInputs(self):
  133. left_length, right_length = self.GenerateInputLengths()
  134. return GenRandom(left_length), GenRandom(right_length)
  135. # Subclasses should not override anything below.
  136. def EmitOne(self):
  137. left_str, right_str = self.GenerateInputs()
  138. left_num = Parse(left_str)
  139. right_num = Parse(right_str)
  140. result_num = self.GenerateResult(left_num, right_num)
  141. result_str = Format(result_num)
  142. return ("{\n a: %s,\n b: %s,\n r: %s\n}" %
  143. (left_str, right_str, result_str))
  144. def EmitTestCore(self):
  145. return """\
  146. var r = d.a %(op)s d.b;
  147. if (d.r !== r) {
  148. print("Input A: " + d.a.toString(%(base)d));
  149. print("Input B: " + d.b.toString(%(base)d));
  150. print("Result: " + r.toString(%(base)d));
  151. print("Expected: " + d.r);
  152. print("Op: %(op)s");
  153. error_count++;
  154. }""" % {"op": self.GetOpString(), "base": kBase}
  155. class Neg(UnaryOp):
  156. def GetOpString(self): return "-"
  157. def GenerateResult(self, x): return -x
  158. class BitNot(UnaryOp):
  159. def GetOpString(self): return "~"
  160. def GenerateResult(self, x): return ~x
  161. class Inc(UnaryOp):
  162. def GetOpString(self): return "++"
  163. def GenerateResult(self, x): return x + 1
  164. class Dec(UnaryOp):
  165. def GetOpString(self): return "--"
  166. def GenerateResult(self, x): return x - 1
  167. class Add(BinaryOp):
  168. def GetOpString(self): return "+"
  169. def GenerateResult(self, a, b): return a + b
  170. class Sub(BinaryOp):
  171. def GetOpString(self): return "-"
  172. def GenerateResult(self, a, b): return a - b
  173. class Mul(BinaryOp):
  174. def GetOpString(self): return "*"
  175. def GenerateResult(self, a, b): return a * b
  176. def GenerateInputLengths(self):
  177. left_length = random.randint(1, kLineLength)
  178. return left_length, kLineLength - left_length
  179. class Div(BinaryOp):
  180. def GetOpString(self): return "/"
  181. def GenerateResult(self, a, b):
  182. result = abs(a) / abs(b)
  183. if (a < 0) != (b < 0): result = -result
  184. return result
  185. def GenerateInputLengths(self):
  186. # Let the left side be longer than the right side with high probability,
  187. # because that case is more interesting.
  188. min_left = kLineLength * 6 / 10
  189. max_right = kLineLength * 7 / 10
  190. return random.randint(min_left, kLineLength), random.randint(1, max_right)
  191. class Mod(Div): # Sharing GenerateInputLengths.
  192. def GetOpString(self): return "%"
  193. def GenerateResult(self, a, b):
  194. result = a % b
  195. if a < 0 and result > 0:
  196. result -= abs(b)
  197. if a > 0 and result < 0:
  198. result += abs(b)
  199. return result
  200. class Shl(BinaryOp):
  201. def GetOpString(self): return "<<"
  202. def GenerateInputsInternal(self, small_shift_positive):
  203. left_length = random.randint(0, kLineLength - 1)
  204. left = GenRandom(left_length)
  205. small_shift = random.randint(0, 1) == 0
  206. if small_shift:
  207. right_length = 1 + int(math.log((kLineLength - left_length), kBase))
  208. neg = kNo if small_shift_positive else kYes
  209. else:
  210. right_length = random.randint(0, 3)
  211. neg = kYes if small_shift_positive else kNo
  212. right = GenRandom(right_length, negative=neg)
  213. return left, right
  214. def GenerateInputs(self): return self.GenerateInputsInternal(True)
  215. def GenerateResult(self, a, b):
  216. if b < 0: return a >> -b
  217. return a << b
  218. class Sar(Shl): # Sharing GenerateInputsInternal.
  219. def GetOpString(self): return ">>"
  220. def GenerateInputs(self):
  221. return self.GenerateInputsInternal(False)
  222. def GenerateResult(self, a, b):
  223. if b < 0: return a << -b
  224. return a >> b
  225. class BitAnd(BinaryOp):
  226. def GetOpString(self): return "&"
  227. def GenerateResult(self, a, b): return a & b
  228. class BitOr(BinaryOp):
  229. def GetOpString(self): return "|"
  230. def GenerateResult(self, a, b): return a | b
  231. class BitXor(BinaryOp):
  232. def GetOpString(self): return "^"
  233. def GenerateResult(self, a, b): return a ^ b
  234. OPS = {
  235. "add": Add,
  236. "sub": Sub,
  237. "mul": Mul,
  238. "div": Div,
  239. "mod": Mod,
  240. "inc": Inc,
  241. "dec": Dec,
  242. "neg": Neg,
  243. "not": BitNot,
  244. "shl": Shl,
  245. "sar": Sar,
  246. "and": BitAnd,
  247. "or": BitOr,
  248. "xor": BitXor
  249. }
  250. OPS_NAMES = ", ".join(sorted(OPS.keys()))
  251. def RunOne(op, num_inputs, binary):
  252. return OPS[op]().RunTest(num_inputs, binary)
  253. def WrapRunOne(args):
  254. return RunOne(*args)
  255. def RunAll(args):
  256. for op in args.op:
  257. for r in range(args.runs):
  258. yield (op, args.num_inputs, args.binary)
  259. def Main():
  260. parser = argparse.ArgumentParser(
  261. description="Helper for generating or running BigInt tests.")
  262. parser.add_argument(
  263. "action", help="Action to perform: 'generate' or 'stress'")
  264. parser.add_argument(
  265. "op", nargs="+",
  266. help="Operation(s) to test, one or more of: %s. In 'stress' mode, "
  267. "special op 'all' tests all ops." % OPS_NAMES)
  268. parser.add_argument(
  269. "-n", "--num-inputs", type=int, default=-1,
  270. help="Number of input/output sets in each generated test. Defaults to "
  271. "%d for 'generate' and '%d' for 'stress' mode." %
  272. (kNumInputsGenerate, kNumInputsStress))
  273. stressopts = parser.add_argument_group("'stress' mode arguments")
  274. stressopts.add_argument(
  275. "-r", "--runs", type=int, default=1000,
  276. help="Number of tests (with NUM_INPUTS each) to generate and run. "
  277. "Default: %(default)s.")
  278. stressopts.add_argument(
  279. "-b", "--binary", default="out/x64.debug/d8",
  280. help="The 'd8' binary to use. Default: %(default)s.")
  281. args = parser.parse_args()
  282. for op in args.op:
  283. if op not in OPS.keys() and op != "all":
  284. print("Invalid op '%s'. See --help." % op)
  285. return 1
  286. if len(args.op) == 1 and args.op[0] == "all":
  287. args.op = OPS.keys()
  288. if args.action == "generate":
  289. if args.num_inputs < 0: args.num_inputs = kNumInputsGenerate
  290. for op in args.op:
  291. OPS[op]().PrintTest(args.num_inputs)
  292. elif args.action == "stress":
  293. if args.num_inputs < 0: args.num_inputs = kNumInputsStress
  294. result = 0
  295. pool = multiprocessing.Pool(multiprocessing.cpu_count())
  296. for r in pool.imap_unordered(WrapRunOne, RunAll(args)):
  297. result = result or r
  298. return result
  299. else:
  300. print("Invalid action '%s'. See --help." % args.action)
  301. return 1
  302. if __name__ == "__main__":
  303. sys.exit(Main())