| #!/usr/bin/python |
| # Copyright 2017 the V8 project authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import argparse |
| import math |
| import multiprocessing |
| import os |
| import random |
| import subprocess |
| import sys |
| import tempfile |
| |
| # Configuration. |
| kChars = "0123456789abcdefghijklmnopqrstuvwxyz" |
| kBase = 16 |
| kLineLength = 71 # A bit less than 80. |
| kNumInputsGenerate = 20 |
| kNumInputsStress = 1000 |
| |
| # Internally used sentinels. |
| kNo = 0 |
| kYes = 1 |
| kRandom = 2 |
| |
| TEST_HEADER = """\ |
| // Copyright 2017 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Generated by %s. |
| |
| // Flags: --harmony-bigint |
| """ % sys.argv[0] |
| |
| TEST_BODY = """ |
| var error_count = 0; |
| for (var i = 0; i < data.length; i++) { |
| var d = data[i]; |
| %s |
| } |
| if (error_count !== 0) { |
| print("Finished with " + error_count + " errors.") |
| quit(1); |
| }""" |
| |
| def GenRandom(length, negative=kRandom): |
| if length == 0: return "0" |
| s = [] |
| if negative == kYes or (negative == kRandom and (random.randint(0, 1) == 0)): |
| s.append("-") # 50% chance of negative. |
| s.append(kChars[random.randint(1, kBase - 1)]) # No leading zero. |
| for i in range(1, length): |
| s.append(kChars[random.randint(0, kBase - 1)]) |
| return "".join(s) |
| |
| def Format(x, base): |
| original = x |
| negative = False |
| if x == 0: return "0" |
| if x < 0: |
| negative = True |
| x = -x |
| s = "" |
| while x > 0: |
| s = kChars[x % base] + s |
| x = x / base |
| if negative: |
| s = "-" + s |
| assert int(s, base) == original |
| return s |
| |
| class TestGenerator(object): |
| # Subclasses must implement these. |
| # Returns a JSON snippet defining inputs and expected output for one test. |
| def EmitOne(self): raise NotImplementedError |
| # Returns a snippet of JavaScript that will operate on a variable "d" |
| # whose content is defined by the result of a call to "EmitOne". |
| def EmitTestCore(self): raise NotImplementedError |
| |
| def EmitHeader(self): |
| return TEST_HEADER |
| |
| def EmitData(self, count): |
| s = [] |
| for i in range(count): |
| s.append(self.EmitOne()) |
| return "var data = [" + ", ".join(s) + "];" |
| |
| def EmitTestBody(self): |
| return TEST_BODY % self.EmitTestCore() |
| |
| def PrintTest(self, count): |
| print(self.EmitHeader()) |
| print(self.EmitData(count)) |
| print(self.EmitTestBody()) |
| |
| def RunTest(self, count, binary): |
| try: |
| fd, path = tempfile.mkstemp(suffix=".js", prefix="bigint-test-") |
| with open(path, "w") as f: |
| f.write(self.EmitData(count)) |
| f.write(self.EmitTestBody()) |
| return subprocess.call("%s --harmony-bigint %s" % (binary, path), |
| shell=True) |
| finally: |
| os.close(fd) |
| os.remove(path) |
| |
| class UnaryOp(TestGenerator): |
| # Subclasses must implement these two. |
| def GetOpString(self): raise NotImplementedError |
| def GenerateResult(self, x): raise NotImplementedError |
| |
| # Subclasses may override this: |
| def GenerateInput(self): |
| return GenRandom(random.randint(0, kLineLength)) |
| |
| # Subclasses should not override anything below. |
| def EmitOne(self): |
| x_str = self.GenerateInput() |
| x_num = int(x_str, kBase) |
| result_num = self.GenerateResult(x_num) |
| result_str = Format(result_num, kBase) |
| return "{\n a: \"%s\",\n r: \"%s\"\n}" % (x_str, result_str) |
| |
| def EmitTestCore(self): |
| return """\ |
| var a = BigInt.parseInt(d.a, %(base)d); |
| var r = %(op)sa; |
| if (d.r !== r.toString(%(base)d)) { |
| print("Input: " + a.toString(%(base)d)); |
| print("Result: " + r.toString(%(base)d)); |
| print("Expected: " + d.r); |
| error_count++; |
| }""" % {"op": self.GetOpString(), "base": kBase} |
| |
| class BinaryOp(TestGenerator): |
| # Subclasses must implement these two. |
| def GetOpString(self): raise NotImplementedError |
| def GenerateResult(self, left, right): raise NotImplementedError |
| |
| # Subclasses may override these: |
| def GenerateInputLengths(self): |
| return random.randint(0, kLineLength), random.randint(0, kLineLength) |
| |
| def GenerateInputs(self): |
| left_length, right_length = self.GenerateInputLengths() |
| return GenRandom(left_length), GenRandom(right_length) |
| |
| # Subclasses should not override anything below. |
| def EmitOne(self): |
| left_str, right_str = self.GenerateInputs() |
| left_num = int(left_str, kBase) |
| right_num = int(right_str, kBase) |
| result_num = self.GenerateResult(left_num, right_num) |
| result_str = Format(result_num, kBase) |
| return ("{\n a: \"%s\",\n b: \"%s\",\n r: \"%s\"\n}" % |
| (left_str, right_str, result_str)) |
| |
| def EmitTestCore(self): |
| return """\ |
| var a = BigInt.parseInt(d.a, %(base)d); |
| var b = BigInt.parseInt(d.b, %(base)d); |
| var r = a %(op)s b; |
| if (d.r !== r.toString(%(base)d)) { |
| print("Input A: " + a.toString(%(base)d)); |
| print("Input B: " + b.toString(%(base)d)); |
| print("Result: " + r.toString(%(base)d)); |
| print("Expected: " + d.r); |
| print("Op: %(op)s"); |
| error_count++; |
| }""" % {"op": self.GetOpString(), "base": kBase} |
| |
| class Neg(UnaryOp): |
| def GetOpString(self): return "-" |
| def GenerateResult(self, x): return -x |
| |
| class BitNot(UnaryOp): |
| def GetOpString(self): return "~" |
| def GenerateResult(self, x): return ~x |
| |
| class Inc(UnaryOp): |
| def GetOpString(self): return "++" |
| def GenerateResult(self, x): return x + 1 |
| |
| class Dec(UnaryOp): |
| def GetOpString(self): return "--" |
| def GenerateResult(self, x): return x - 1 |
| |
| class Add(BinaryOp): |
| def GetOpString(self): return "+" |
| def GenerateResult(self, a, b): return a + b |
| |
| class Sub(BinaryOp): |
| def GetOpString(self): return "-" |
| def GenerateResult(self, a, b): return a - b |
| |
| class Mul(BinaryOp): |
| def GetOpString(self): return "*" |
| def GenerateResult(self, a, b): return a * b |
| def GenerateInputLengths(self): |
| left_length = random.randint(1, kLineLength) |
| return left_length, kLineLength - left_length |
| |
| class Div(BinaryOp): |
| def GetOpString(self): return "/" |
| def GenerateResult(self, a, b): |
| result = abs(a) / abs(b) |
| if (a < 0) != (b < 0): result = -result |
| return result |
| def GenerateInputLengths(self): |
| # Let the left side be longer than the right side with high probability, |
| # because that case is more interesting. |
| min_left = kLineLength * 6 / 10 |
| max_right = kLineLength * 7 / 10 |
| return random.randint(min_left, kLineLength), random.randint(1, max_right) |
| |
| class Mod(Div): # Sharing GenerateInputLengths. |
| def GetOpString(self): return "%" |
| def GenerateResult(self, a, b): |
| result = a % b |
| if a < 0 and result > 0: |
| result -= abs(b) |
| if a > 0 and result < 0: |
| result += abs(b) |
| return result |
| |
| class Shl(BinaryOp): |
| def GetOpString(self): return "<<" |
| def GenerateInputsInternal(self, small_shift_positive): |
| left_length = random.randint(0, kLineLength - 1) |
| left = GenRandom(left_length) |
| small_shift = random.randint(0, 1) == 0 |
| if small_shift: |
| right_length = 1 + int(math.log((kLineLength - left_length), kBase)) |
| neg = kNo if small_shift_positive else kYes |
| else: |
| right_length = random.randint(0, 3) |
| neg = kYes if small_shift_positive else kNo |
| right = GenRandom(right_length, negative=neg) |
| return left, right |
| |
| def GenerateInputs(self): return self.GenerateInputsInternal(True) |
| def GenerateResult(self, a, b): |
| if b < 0: return a >> -b |
| return a << b |
| |
| class Sar(Shl): # Sharing GenerateInputsInternal. |
| def GetOpString(self): return ">>" |
| def GenerateInputs(self): |
| return self.GenerateInputsInternal(False) |
| def GenerateResult(self, a, b): |
| if b < 0: return a << -b |
| return a >> b |
| |
| class BitAnd(BinaryOp): |
| def GetOpString(self): return "&" |
| def GenerateResult(self, a, b): return a & b |
| |
| class BitOr(BinaryOp): |
| def GetOpString(self): return "|" |
| def GenerateResult(self, a, b): return a | b |
| |
| class BitXor(BinaryOp): |
| def GetOpString(self): return "^" |
| def GenerateResult(self, a, b): return a ^ b |
| |
| OPS = { |
| "add": Add, |
| "sub": Sub, |
| "mul": Mul, |
| "div": Div, |
| "mod": Mod, |
| "inc": Inc, |
| "dec": Dec, |
| "neg": Neg, |
| "not": BitNot, |
| "shl": Shl, |
| "sar": Sar, |
| "and": BitAnd, |
| "or": BitOr, |
| "xor": BitXor |
| } |
| |
| OPS_NAMES = ", ".join(sorted(OPS.keys())) |
| |
| def RunOne(op, num_inputs, binary): |
| return OPS[op]().RunTest(num_inputs, binary) |
| def WrapRunOne(args): |
| return RunOne(*args) |
| def RunAll(args): |
| for op in args.op: |
| for r in xrange(args.runs): |
| yield (op, args.num_inputs, args.binary) |
| |
| def Main(): |
| parser = argparse.ArgumentParser( |
| description="Helper for generating or running BigInt tests.") |
| parser.add_argument( |
| "action", help="Action to perform: 'generate' or 'stress'") |
| parser.add_argument( |
| "op", nargs="+", |
| help="Operation(s) to test, one or more of: %s. In 'stress' mode, " |
| "special op 'all' tests all ops." % OPS_NAMES) |
| parser.add_argument( |
| "-n", "--num-inputs", type=int, default=-1, |
| help="Number of input/output sets in each generated test. Defaults to " |
| "%d for 'generate' and '%d' for 'stress' mode." % |
| (kNumInputsGenerate, kNumInputsStress)) |
| |
| stressopts = parser.add_argument_group("'stress' mode arguments") |
| stressopts.add_argument( |
| "-r", "--runs", type=int, default=1000, |
| help="Number of tests (with NUM_INPUTS each) to generate and run. " |
| "Default: %(default)s.") |
| stressopts.add_argument( |
| "-b", "--binary", default="out/x64.debug/d8", |
| help="The 'd8' binary to use. Default: %(default)s.") |
| args = parser.parse_args() |
| |
| for op in args.op: |
| if op not in OPS.keys() and op != "all": |
| print("Invalid op '%s'. See --help." % op) |
| return 1 |
| |
| if len(args.op) == 1 and args.op[0] == "all": |
| args.op = OPS.keys() |
| |
| if args.action == "generate": |
| if args.num_inputs < 0: args.num_inputs = kNumInputsGenerate |
| for op in args.op: |
| OPS[op]().PrintTest(args.num_inputs) |
| elif args.action == "stress": |
| if args.num_inputs < 0: args.num_inputs = kNumInputsStress |
| result = 0 |
| pool = multiprocessing.Pool(multiprocessing.cpu_count()) |
| for r in pool.imap_unordered(WrapRunOne, RunAll(args)): |
| result = result or r |
| return result |
| else: |
| print("Invalid action '%s'. See --help." % args.action) |
| return 1 |
| |
| if __name__ == "__main__": |
| sys.exit(Main()) |