| @@ -48,6 +50,47 @@ def native(code): |
| 48 | 50 | return function, image, thunk |
| 49 | 51 | |
| 50 | 52 | |
| 53 | +def worker(connection, code, vectors): |
| 54 | + resource.setrlimit(resource.RLIMIT_CORE,(0,0)) |
| 55 | + try: |
| 56 | + function, image, thunk = native(code) |
| 57 | + results = [function(*vector) for vector in vectors] |
| 58 | + del function |
| 59 | + image.close() |
| 60 | + thunk.close() |
| 61 | + connection.send((True,results)) |
| 62 | + except BaseException as error: |
| 63 | + connection.send((False,repr(error))) |
| 64 | + finally: |
| 65 | + connection.close() |
| 66 | + |
| 67 | + |
| 68 | +def execute(code, vectors): |
| 69 | + context = multiprocessing.get_context('fork') |
| 70 | + reader, writer = context.Pipe(duplex=False) |
| 71 | + process = context.Process(target=worker,args=(writer,code,vectors)) |
| 72 | + process.start() |
| 73 | + writer.close() |
| 74 | + try: |
| 75 | + if not reader.poll(30): |
| 76 | + raise RuntimeError('native child exceeded 30 seconds') |
| 77 | + try: |
| 78 | + ok, result = reader.recv() |
| 79 | + except EOFError: |
| 80 | + process.join(1) |
| 81 | + raise RuntimeError(f'native child failed with status {process.exitcode}') from None |
| 82 | + process.join(1) |
| 83 | + if not ok or process.exitcode != 0: |
| 84 | + raise RuntimeError(f'native child failed: {result}, status {process.exitcode}') |
| 85 | + return result |
| 86 | + finally: |
| 87 | + reader.close() |
| 88 | + if process.is_alive(): |
| 89 | + process.terminate() |
| 90 | + process.join() |
| 91 | + process.close() |
| 92 | + |
| 93 | + |
| 51 | 94 | def generate(rng, depth, names): |
| 52 | 95 | if depth == 0 or rng.randrange(5) == 0: |
| 53 | 96 | return rng.choice([0, 1, 63, 64, 65, MASK, 1 << 63, *names]) |
| @@ -63,6 +106,12 @@ def generate(rng, depth, names): |
| 63 | 106 | def main(): |
| 64 | 107 | if platform.system() != "Linux" or platform.machine() != "x86_64": |
| 65 | 108 | raise RuntimeError("native differential tests require an x86-64 Linux host") |
| 109 | + try: |
| 110 | + execute(bytes.fromhex('0f 0b'),[(0,0,0)]) |
| 111 | + except RuntimeError as error: |
| 112 | + assert 'status -4' in str(error), error |
| 113 | + else: |
| 114 | + raise AssertionError('invalid opcode did not kill the isolated child') |
| 66 | 115 | rng = random.Random(0x52454D59) |
| 67 | 116 | cases = [(op, "x", "y") for op in OPERATORS] |
| 68 | 117 | cases += [(op, "x", 0) for op in ("+", "-", "or", "xor", "shl", "shr")] |
| @@ -82,20 +131,16 @@ def main(): |
| 82 | 131 | assert result.returncode == 0, (case, result.stderr) |
| 83 | 132 | checked = subprocess.run([ROOT / ".build/remy", "check", binary],capture_output=True,text=True,timeout=30) |
| 84 | 133 | assert checked.returncode == 0, (case,checked.stderr) |
| 85 | | - function, image, thunk = native(binary.read_bytes()) |
| 86 | 134 | vectors = [(a, b, a ^ b) for a in (0, 1, MASK, 1 << 63) for b in (0, 1, 63, 64, 65, MASK)] |
| 87 | 135 | vectors += [tuple(rng.getrandbits(64) for _ in range(3)) for _ in range(24)] |
| 88 | | - for x, y, t in vectors: |
| 136 | + results = execute(binary.read_bytes(),vectors) |
| 137 | + for (x, y, t), actual in zip(vectors,results,strict=True): |
| 89 | 138 | expected = evaluate(case, {"x": x, "y": y, "t": t}) |
| 90 | | - actual = function(x, y, t) |
| 91 | 139 | assert actual == expected, (source(case), x, y, t, expected, actual) |
| 92 | 140 | comparisons += 1 |
| 93 | 141 | result = subprocess.run([ROOT / ".build/remy", "eval", path, str(x), str(y), str(t)], |
| 94 | 142 | capture_output=True, text=True, timeout=30) |
| 95 | 143 | assert result.returncode == 0 and int(result.stdout) == expected, (case, result.stdout, result.stderr) |
| 96 | | - del function |
| 97 | | - image.close() |
| 98 | | - thunk.close() |
| 99 | 144 | for bad in ("", "(+ x)", "(+ x y) 1", "(let a 3 b)", "(if 1 2)", "(let if 3 4)", |
| 100 | 145 | "18446744073709551616", "(+ x z)", "(div x y)", "é", " " * 65537): |
| 101 | 146 | path.write_text(bad) |