aboutsummaryrefslogtreecommitdiff
path: root/tests/drivers/fuzz_compiler.py
blob: 512ec260fd70c57f38ee7e53a1c8043a2f485b81 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#!/usr/bin/env python3
"""Deterministic grammar fuzzing for the parser, runtime, and ownership paths.

Usage: python3 tests/drivers/fuzz_compiler.py [path/to/zy] [path/to/zym] [--js]
"""
import random
import subprocess
import sys


args = [arg for arg in sys.argv[1:] if arg != "--js"]
js = "--js" in sys.argv[1:]
zy = args[0] if len(args) > 0 else "bin/zy"
zym = args[1] if len(args) > 1 else "bin/zym"
rng = random.Random(0x5A17)
values = []
library_values = []


def expr(depth):
    if depth == 0 or rng.randrange(4) == 0:
        value = rng.randrange(10)
        return str(value), value
    left, a = expr(depth - 1)
    right, b = expr(depth - 1)
    op = rng.choice(("+", "-", "*"))
    if op == "+":
        value = a + b
    elif op == "-":
        value = a - b
    else:
        value = a * b
    return f"({left} {op} {right})", value


lines = ["include std;", "fn churn(n) {", " int i = 0;", " while (i < n) {", "  { int* p = new(int); *p = i; }", "  i++;", " }", " return 0;", "}", "fn stable() { return 73; }", "fn main() {", " let base = live_objects();"]
for _ in range(128):
    source, value = expr(4)
    lines.append(f" put_n({source}); put_c(32);")
    values.append(value)
for _ in range(32):
    a = "".join(rng.choice("abc") for _ in range(rng.randrange(11)))
    b = "".join(rng.choice("abc") for _ in range(rng.randrange(6)))
    lines.append(f' put_n(Text.len("{a}")); put_c(32); put_n(Text.eq("{a}", "{b}")); put_c(32);')
    lines.append(f' put_n(Text.starts("{a}", "{b}")); put_c(32); put_n(Text.ends("{a}", "{b}")); put_c(32); put_n(Text.find("{a}", "{b}")); put_c(32);')
    lines.append(f' put_n(Bytes.len(b"{a}")); put_c(32); put_n(Bytes.eq(b"{a}", b"{b}")); put_c(32);')
    lines.append(f' put_n(Bytes.starts(b"{a}", b"{b}")); put_c(32); put_n(Bytes.ends(b"{a}", b"{b}")); put_c(32); put_n(Bytes.find(b"{a}", b"{b}")); put_c(32);')
    library_values += [len(a), int(a == b), int(a.startswith(b)), int(a.endswith(b)), a.find(b), len(a.encode()), int(a == b), int(a.startswith(b)), int(a.endswith(b)), a.find(b)]
lines += [
    " churn(300); gc(); put_n(live_objects() - base); put_c(32);",
    ' char* source = "fn f() { return 7; }"; let parsed = tokens(source);',
    ' put_n(parsed[0] > 0); put_c(32); put_n(eq_w(source, "fn f() { return 7; }")); put_c(32);',
    ' char* eval_source = "return 42;"; int e = 0;',
    ' while (e < 16) { let q = tokens(eval_source); put_n(eval(q)); put_c(32); e++; }',
    ' put_n(stable()); put_c(32); put_n(eq_w(eval_source, "return 42;")); put_c(10);',
    " return 0;",
    "}",
]
program = "\n".join(lines) + "\n"
expected = " ".join(str(x) for x in values + library_values + [0, 1, 1, *([42] * 16), 73, 1]) + "\n"


def run(binary):
    result = subprocess.run(binary, input=program, text=True, capture_output=True, timeout=30)
    if result.returncode or result.stdout != expected:
        actual_words = result.stdout.split()
        expected_words = expected.split()
        mismatch = next((i for i, pair in enumerate(zip(actual_words, expected_words)) if pair[0] != pair[1]), min(len(actual_words), len(expected_words)))
        raise SystemExit(
            f"{' '.join(binary)} failed deterministic fuzz seed 0x5a17\n"
            f"exit={result.returncode}, token mismatch at {mismatch}\n"
            f"expected={expected_words[max(0, mismatch - 3):mismatch + 4]!r}\n"
            f"stdout={actual_words[max(0, mismatch - 3):mismatch + 4]!r}\n"
            f"stderr={result.stderr!r}"
        )
    return result.stdout


hosts = [(zy, [zy]), (zym, [zym])]
if js:
    hosts.append(("javascript", ["node", "src/js/tinyvm.js"]))
outputs = [run(command) for _, command in hosts]
if any(output != outputs[0] for output in outputs[1:]):
    raise SystemExit("compiler hosts disagreed on deterministic fuzz output")
print("ok   compiler/runtime/stdlib fuzz: 128 generated expressions, 300 scoped allocations and GC, "
      "16 eval recompiles, unchanged source strings, and 32 randomized Text/Bytes cases on "
      + ", ".join(name for name, _ in hosts))