diff options
Diffstat (limited to 'tests')
46 files changed, 466 insertions, 316 deletions
diff --git a/tests/bench/bench.sh b/tests/bench/bench.sh index ab6b07d..65f455e 100644 --- a/tests/bench/bench.sh +++ b/tests/bench/bench.sh @@ -2,13 +2,15 @@ # o3 against plain code: each kernel in tests/programs/bench.zc on each backend, with # the time to start up and compile (the 'z' run) taken off. needs bc. cd "$(dirname "$0")/../.."; T=$(mktemp -d); trap 'rm -rf $T' EXIT -gcc -O2 -w -DJTYPE=int src/core/core.c src/core/io_linux.c -Isrc/core -o $T/zy +CC=${CC:-$(sh src/tools/findcc.sh)} || exit 1 +make -s build/core/genome.bin || exit 1 +$CC -O2 -w -DJTYPE=int src/core/core.c src/core/io_linux.c -Isrc/core -Ibuild/core/map --embed-dir=build/core -o $T/zy vms=("$T/zy"); names=("interp") if command -v llvm-config >/dev/null; then - gcc -O2 -w -DJTYPE=int -DZYLLVM $(llvm-config --cflags) src/core/core.c src/core/io_linux.c -Isrc/core -o $T/zyl $(llvm-config --ldflags --libs) && { vms+=("$T/zyl"); names+=("llvm"); } + $CC -O2 -w -DJTYPE=int -DZYLLVM $(llvm-config --cflags) src/core/core.c src/core/io_linux.c -Isrc/core -Ibuild/core/map --embed-dir=build/core -o $T/zyl $(llvm-config --ldflags --libs) && { vms+=("$T/zyl"); names+=("llvm"); } fi if command -v aarch64-linux-gnu-gcc >/dev/null && command -v qemu-aarch64 >/dev/null; then - aarch64-linux-gnu-gcc -O2 -w -DJTYPE=int -static src/core/core.c src/core/io_linux.c -Isrc/core -o $T/zyj && { vms+=("qemu-aarch64 $T/zyj"); names+=("arm64 (qemu)"); } + aarch64-linux-gnu-gcc -O2 -w -DJTYPE=int -static src/core/core.c src/core/io_linux.c -Isrc/core -Ibuild/core/map --embed-dir=build/core -o $T/zyj && { vms+=("qemu-aarch64 $T/zyj"); names+=("arm64 (qemu)"); } fi # seconds per run of kernel $2 on vm $1: the slope between $3 runs and 1 run, # best of three each, so starting up and compiling cancel out diff --git a/tests/bench/bench_bytes.sh b/tests/bench/bench_bytes.sh index acff074..775b71d 100644 --- a/tests/bench/bench_bytes.sh +++ b/tests/bench/bench_bytes.sh @@ -4,16 +4,18 @@ set -e # in the process. the slope between 1 repetition and N drops startup. # prints: backend ms/rep checksum cd "$(dirname "$0")/../.." +CC=${CC:-$(sh src/tools/findcc.sh)} || exit 1 +make -s build/core/genome.bin || exit 1 T=$(mktemp -d) trap 'rm -rf "$T"' EXIT -gcc -O2 -w src/tools/genmap.c -o "$T/genmap" || exit 1 +$CC -O2 -w src/tools/genmap.c -o "$T/genmap" || exit 1 GENMAP="$T/genmap" bash src/tools/automap.sh run "$T/run" src/runtime/run.zc tests/programs/bench_bytes.zc >/dev/null || exit 1 -cp src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/core/core.c src/core/io_linux.c src/core/fops.h src/core/bops.h src/core/stack.h "$T/run/" -gcc -O2 -w -DJTYPE=int "$T/run/core.c" "$T/run/io_linux.c" -o "$T/zy" || exit 1 -gcc -O2 -w "$T/run/micro.c" "$T/run/port_linux.c" -o "$T/zym" || exit 1 +cp src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/core/core.c src/core/io_linux.c src/core/fops.h src/core/bops.h src/core/stack.h src/core/genome.h "$T/run/" +$CC -O2 -w -DJTYPE=int "$T/run/core.c" "$T/run/io_linux.c" -o "$T/zy" || exit 1 +$CC -O2 -w "$T/run/micro.c" "$T/run/port_linux.c" -o "$T/zym" || exit 1 vms=("$T/zy" "$T/zym"); names=("interp" "micro") if command -v llvm-config >/dev/null; then - gcc -O2 -w -DJTYPE=int -DZYLLVM $(llvm-config --cflags) "$T/run/core.c" "$T/run/io_linux.c" -o "$T/zyl" $(llvm-config --ldflags --libs) \ + $CC -O2 -w -DJTYPE=int -DZYLLVM $(llvm-config --cflags) "$T/run/core.c" "$T/run/io_linux.c" -o "$T/zyl" $(llvm-config --ldflags --libs) \ && { vms+=("$T/zyl"); names+=("llvm"); } fi if command -v aarch64-linux-gnu-gcc >/dev/null && command -v qemu-aarch64 >/dev/null; then diff --git a/tests/bench/bench_float.sh b/tests/bench/bench_float.sh index 794e976..4ebfbe4 100644 --- a/tests/bench/bench_float.sh +++ b/tests/bench/bench_float.sh @@ -5,17 +5,19 @@ # this does not bootstrap the compiler. each kernel is poly() of 20000, # and (int) of one call is 59997. cd "$(dirname "$0")/../.." +CC=${CC:-$(sh src/tools/findcc.sh)} || exit 1 +make -s build/core/genome.bin || exit 1 T=$(mktemp -d) trap 'rm -rf "$T"' EXIT -gcc -O2 -w -DJTYPE=int src/core/core.c src/core/io_linux.c -Isrc/core -o "$T/zy" || exit 1 +$CC -O2 -w -DJTYPE=int src/core/core.c src/core/io_linux.c -Isrc/core -Ibuild/core/map --embed-dir=build/core -o "$T/zy" || exit 1 vms=("$T/zy"); names=("interp") -gcc -O2 -w src/micro/micro.c src/micro/port_linux.c -Isrc/core -Isrc/micro -o "$T/zym" && { vms+=("$T/zym"); names+=("micro"); } +$CC -O2 -w src/micro/micro.c src/micro/port_linux.c -Isrc/core -Ibuild/core/map --embed-dir=build/core -Isrc/micro -o "$T/zym" && { vms+=("$T/zym"); names+=("micro"); } if command -v llvm-config >/dev/null; then - gcc -O2 -w -DJTYPE=int -DZYLLVM $(llvm-config --cflags) src/core/core.c src/core/io_linux.c -Isrc/core -o "$T/zyl" $(llvm-config --ldflags --libs) \ + $CC -O2 -w -DJTYPE=int -DZYLLVM $(llvm-config --cflags) src/core/core.c src/core/io_linux.c -Isrc/core -Ibuild/core/map --embed-dir=build/core -o "$T/zyl" $(llvm-config --ldflags --libs) \ && { vms+=("$T/zyl"); names+=("llvm"); } fi if command -v aarch64-linux-gnu-gcc >/dev/null && command -v qemu-aarch64 >/dev/null; then - aarch64-linux-gnu-gcc -O2 -w -DJTYPE=int -static src/core/core.c src/core/io_linux.c -Isrc/core -o "$T/zyj" \ + aarch64-linux-gnu-gcc -O2 -w -DJTYPE=int -static src/core/core.c src/core/io_linux.c -Isrc/core -Ibuild/core/map --embed-dir=build/core -o "$T/zyj" \ && { vms+=("qemu-aarch64 $T/zyj"); names+=("arm64"); } fi src=$(cat tests/programs/fbench.zc) diff --git a/tests/bench/bench_share.sh b/tests/bench/bench_share.sh index b449e09..5cb1bfd 100644 --- a/tests/bench/bench_share.sh +++ b/tests/bench/bench_share.sh @@ -3,18 +3,19 @@ # resident compiler is not in the process. the slope between 1 repetition # and N drops startup. prints: backend ms/rep checksum cd "$(dirname "$0")/../.." +CC=${CC:-$(sh src/tools/findcc.sh)} || exit 1 T=$(mktemp -d) trap 'rm -rf "$T"' EXIT -gcc -O2 -w src/tools/genmap.c -o "$T/genmap" || exit 1 +$CC -O2 -w src/tools/genmap.c -o "$T/genmap" || exit 1 "$T/genmap" --preset run -o "$T/run" >/dev/null || exit 1 -cp src/tools/zc0.c src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/core/core.c src/core/io_linux.c src/core/fops.h src/core/bops.h src/core/stack.h "$T/run/" -gcc -O1 -w "$T/run/zc0.c" -o "$T/zc0" || exit 1 -cat "$T/run/rt.zc" src/runtime/run.zc tests/programs/bench_share.zc | "$T/zc0" > "$T/run/genome.h" || exit 1 -gcc -O2 -w -DJTYPE=int "$T/run/core.c" "$T/run/io_linux.c" -o "$T/zy" || exit 1 -gcc -O2 -w "$T/run/micro.c" "$T/run/port_linux.c" -o "$T/zym" || exit 1 +cp src/tools/zc0.c src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/core/core.c src/core/io_linux.c src/core/fops.h src/core/bops.h src/core/stack.h src/core/genome.h "$T/run/" +$CC -O1 -w "$T/run/zc0.c" -o "$T/zc0" || exit 1 +cat "$T/run/rt.zc" src/core/core.zc src/runtime/run.zc tests/programs/bench_share.zc | "$T/zc0" > "$T/run/genome.bin" || exit 1 +$CC -O2 -w -DJTYPE=int "$T/run/core.c" "$T/run/io_linux.c" -o "$T/zy" || exit 1 +$CC -O2 -w "$T/run/micro.c" "$T/run/port_linux.c" -o "$T/zym" || exit 1 vms=("$T/zy" "$T/zym"); names=("interp" "micro") if command -v llvm-config >/dev/null; then - gcc -O2 -w -DJTYPE=int -DZYLLVM $(llvm-config --cflags) "$T/run/core.c" "$T/run/io_linux.c" -o "$T/zyl" $(llvm-config --ldflags --libs) \ + $CC -O2 -w -DJTYPE=int -DZYLLVM $(llvm-config --cflags) "$T/run/core.c" "$T/run/io_linux.c" -o "$T/zyl" $(llvm-config --ldflags --libs) \ && { vms+=("$T/zyl"); names+=("llvm"); } fi if command -v aarch64-linux-gnu-gcc >/dev/null && command -v qemu-aarch64 >/dev/null; then diff --git a/tests/bench/bench_strings.sh b/tests/bench/bench_strings.sh index c353342..b14bf57 100644 --- a/tests/bench/bench_strings.sh +++ b/tests/bench/bench_strings.sh @@ -3,18 +3,19 @@ # resident compiler is not in the process. the slope between 1 repetition # and N drops startup. prints: backend ms/rep checksum cd "$(dirname "$0")/../.." +CC=${CC:-$(sh src/tools/findcc.sh)} || exit 1 T=$(mktemp -d) trap 'rm -rf "$T"' EXIT -gcc -O2 -w src/tools/genmap.c -o "$T/genmap" || exit 1 +$CC -O2 -w src/tools/genmap.c -o "$T/genmap" || exit 1 "$T/genmap" --preset run -o "$T/run" >/dev/null || exit 1 -cp src/tools/zc0.c src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/core/core.c src/core/io_linux.c src/core/fops.h src/core/bops.h src/core/stack.h "$T/run/" -gcc -O1 -w "$T/run/zc0.c" -o "$T/zc0" || exit 1 -cat "$T/run/rt.zc" src/runtime/run.zc tests/programs/bench_strings.zc | "$T/zc0" > "$T/run/genome.h" || exit 1 -gcc -O2 -w -DJTYPE=int "$T/run/core.c" "$T/run/io_linux.c" -o "$T/zy" || exit 1 -gcc -O2 -w "$T/run/micro.c" "$T/run/port_linux.c" -o "$T/zym" || exit 1 +cp src/tools/zc0.c src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/core/core.c src/core/io_linux.c src/core/fops.h src/core/bops.h src/core/stack.h src/core/genome.h "$T/run/" +$CC -O1 -w "$T/run/zc0.c" -o "$T/zc0" || exit 1 +cat "$T/run/rt.zc" src/core/core.zc src/runtime/run.zc tests/programs/bench_strings.zc | "$T/zc0" > "$T/run/genome.bin" || exit 1 +$CC -O2 -w -DJTYPE=int "$T/run/core.c" "$T/run/io_linux.c" -o "$T/zy" || exit 1 +$CC -O2 -w "$T/run/micro.c" "$T/run/port_linux.c" -o "$T/zym" || exit 1 vms=("$T/zy" "$T/zym"); names=("interp" "micro") if command -v llvm-config >/dev/null; then - gcc -O2 -w -DJTYPE=int -DZYLLVM $(llvm-config --cflags) "$T/run/core.c" "$T/run/io_linux.c" -o "$T/zyl" $(llvm-config --ldflags --libs) \ + $CC -O2 -w -DJTYPE=int -DZYLLVM $(llvm-config --cflags) "$T/run/core.c" "$T/run/io_linux.c" -o "$T/zyl" $(llvm-config --ldflags --libs) \ && { vms+=("$T/zyl"); names+=("llvm"); } fi if command -v aarch64-linux-gnu-gcc >/dev/null && command -v qemu-aarch64 >/dev/null; then diff --git a/tests/bench/run.py b/tests/bench/run.py index cb28b92..7f7ea6f 100755 --- a/tests/bench/run.py +++ b/tests/bench/run.py @@ -79,18 +79,21 @@ def machine_lines(): def build(td): td = Path(td) log("building vms") + need(["make", "-s", "build/core/genome.bin"], cwd=ROOT) + cc = os.environ.get("CC") or subprocess.check_output(["sh", "src/tools/findcc.sh"], cwd=ROOT, text=True).strip() + core = ["-Isrc/core", "-Ibuild/core/map", "--embed-dir=build/core"] zy = td / "zy" - need(["gcc", "-O3", "-w", "-DJTYPE=int", "src/core/core.c", "src/core/io_linux.c", "-Isrc/core", "-o", str(zy)], cwd=ROOT) + need([cc, "-O3", "-w", "-DJTYPE=int", "src/core/core.c", "src/core/io_linux.c", *core, "-o", str(zy)], cwd=ROOT) vms = {"interp": [str(zy)]} if shutil.which("llvm-config"): cflags = subprocess.check_output(["llvm-config", "--cflags"], text=True).split() ld = subprocess.check_output(["llvm-config", "--ldflags", "--libs"], text=True).split() zyl = td / "zyl" - need(["gcc", "-O3", "-w", "-DJTYPE=int", "-DZYLLVM", *cflags, "src/core/core.c", "src/core/io_linux.c", "-Isrc/core", "-o", str(zyl), *ld], cwd=ROOT) + need([cc, "-O3", "-w", "-DJTYPE=int", "-DZYLLVM", *cflags, "src/core/core.c", "src/core/io_linux.c", *core, "-o", str(zyl), *ld], cwd=ROOT) vms["llvm"] = [str(zyl)] if shutil.which("aarch64-linux-gnu-gcc") and shutil.which("qemu-aarch64"): zyj = td / "zyj" - need(["aarch64-linux-gnu-gcc", "-O3", "-w", "-DJTYPE=int", "-static", "src/core/core.c", "src/core/io_linux.c", "-Isrc/core", "-o", str(zyj)], cwd=ROOT) + need(["aarch64-linux-gnu-gcc", "-O3", "-w", "-DJTYPE=int", "-static", "src/core/core.c", "src/core/io_linux.c", *core, "-o", str(zyj)], cwd=ROOT) vms["arm64"] = ["qemu-aarch64", str(zyj)] log("building C") bins = {} diff --git a/tests/drivers/actor_image_test.py b/tests/drivers/actor_image_test.py index 9df8828..5b815f2 100644 --- a/tests/drivers/actor_image_test.py +++ b/tests/drivers/actor_image_test.py @@ -1,12 +1,16 @@ #!/usr/bin/env python3 # source syntax callbacks execute in the host while actor code is emitted separately. from pathlib import Path +import sys import argparse import subprocess import tempfile +sys.dont_write_bytecode = True # importing imagekit must not leave __pycache__ in the tree +from imagekit import micro_image parser = argparse.ArgumentParser() -parser.add_argument('--host', default='bin/zy') +parser.add_argument('--host', default='build/bin/zy') +parser.add_argument('--mkgenome', default='build/bin/mkgenome') parser.add_argument('--case', default='') args = parser.parse_args() prefix = b''.join(Path(f).read_bytes() + b'\n' for f in ('src/runtime/rt.zc', 'src/core/core.zc', 'src/runtime/run.zc')) @@ -18,11 +22,8 @@ for name, source, expected in cases: directory = Path(work) compiled = subprocess.run([args.host], input=Path('src/tools/build.zc').read_bytes() + b'`' + prefix + source.encode(), capture_output=True, timeout=30) assert compiled.returncode == 0 and len(compiled.stdout) >= 56 and not compiled.stdout.startswith(b'zc:'), (name, compiled.stdout[:2000], compiled.stderr) - header = subprocess.run(['bin/mkgenome'], input=compiled.stdout, capture_output=True, check=True) - (directory / 'genome.h').write_bytes(header.stdout) - for f in ('src/core/memmap.h', 'src/core/fops.h', 'src/core/bops.h', 'src/core/stack.h', 'src/micro/micro.c', 'src/micro/micro_port.h', 'src/micro/port_linux.c'): - (directory / Path(f).name).write_bytes(Path(f).read_bytes()) - subprocess.run(['cc', '-O2', '-w', '-fwrapv', '-pthread', '-DZY_THREADS', str(directory / 'micro.c'), str(directory / 'port_linux.c'), '-I' + work, '-o', str(directory / 'image')], check=True, timeout=30) - result = subprocess.run([str(directory / 'image')], capture_output=True, timeout=12) + image = subprocess.run([args.mkgenome], input=compiled.stdout, capture_output=True, check=True) + binary = micro_image(directory, image.stdout) + result = subprocess.run([str(binary)], capture_output=True, timeout=12) assert result.returncode == 0 and result.stdout == expected, (name, result.returncode, result.stdout, result.stderr) print('ok actor compiler-free image: ' + name) diff --git a/tests/drivers/actor_test.py b/tests/drivers/actor_test.py index 43a110f..0cfa51f 100644 --- a/tests/drivers/actor_test.py +++ b/tests/drivers/actor_test.py @@ -6,7 +6,7 @@ import subprocess import time parser = argparse.ArgumentParser() -parser.add_argument('vm', nargs='?', default='bin/zy') +parser.add_argument('vm', nargs='?', default='build/bin/zy') parser.add_argument('--case', default='') parser.add_argument('--library', default='') args = parser.parse_args() diff --git a/tests/drivers/analysis_cache_test.js b/tests/drivers/analysis_cache_test.js index 3ffde33..88bb4bd 100644 --- a/tests/drivers/analysis_cache_test.js +++ b/tests/drivers/analysis_cache_test.js @@ -1,4 +1,6 @@ // Repeated replies use cached results; edits, include updates and reopen invalidate them. +// interpreted hosts (no jit) are far slower; ZC_TEST_TIMEOUT_SCALE stretches every deadline +const TIMEOUT_SCALE = Number(process.env.ZC_TEST_TIMEOUT_SCALE) || 1; const assert = require('assert'), cp = require('child_process'), path = require('path'); const uri = 'file:///cache/main.zc', text = 'include "dep.zc";\nfn main() { int n = 7; return n + value(); }'; const msgs = []; let id = 1; @@ -26,7 +28,7 @@ if (process.argv[2] === '--wasm' || process.argv[2] === '--vm') { const p = (wasm ? host.wasmProcess : host.vmProcess)(path.resolve(process.argv[3]), { log: () => {} }); const chunks = []; p.stdout.on('data', d => chunks.push(d)); msgs.forEach(m => p.stdin.write(frame(m))); out = Buffer.concat(chunks); } else { - const r = cp.spawnSync(process.argv[2], process.argv.slice(3), { input: Buffer.concat(msgs.map(frame)), timeout: 60000, maxBuffer: 16 << 20 }); + const r = cp.spawnSync(process.argv[2], process.argv.slice(3), { input: Buffer.concat(msgs.map(frame)), timeout: 60000 * TIMEOUT_SCALE, maxBuffer: 16 << 20 }); assert.strictEqual(r.status, 0, String(r.stderr)); out = r.stdout; } const replies = new Map(); diff --git a/tests/drivers/array_methods_test.py b/tests/drivers/array_methods_test.py index 82fef67..bf6e670 100644 --- a/tests/drivers/array_methods_test.py +++ b/tests/drivers/array_methods_test.py @@ -5,7 +5,7 @@ import re import subprocess parser = argparse.ArgumentParser() -parser.add_argument('host', nargs='?', default='bin/zy') +parser.add_argument('host', nargs='?', default='build/bin/zy') parser.add_argument('--case', default='') args = parser.parse_args() diff --git a/tests/drivers/cli_help_test.py b/tests/drivers/cli_help_test.py index 1b32fa4..97eebea 100644 --- a/tests/drivers/cli_help_test.py +++ b/tests/drivers/cli_help_test.py @@ -7,15 +7,16 @@ from pathlib import Path parser = argparse.ArgumentParser() parser.add_argument('--group', choices=['binaries', 'scripts', 'js', 'all'], default='all') -parser.add_argument('--bin-dir', default='bin') +parser.add_argument('--bin-dir', default='build/bin') +parser.add_argument('--js-dir', default='build/js') args = parser.parse_args() commands = [] if args.group in ('binaries', 'all'): commands += [[str(Path(args.bin_dir) / name)] for name in ['zy', 'zym', 'zclsp', 'zc0', 'mkgenome', 'genmap']] if args.group in ('scripts', 'all'): - commands += [['bash', name] for name in ['tinyvm.sh', 'src/tools/zcc', 'src/tools/automap.sh', 'src/tools/embed.sh', 'src/tools/mklsp.sh', 'src/tools/mklspwasm.sh', 'src/tools/mkvsix.sh', 'src/web/build.sh', 'src/baremetal/run.sh']] + commands += [['bash', name] for name in ['tinyvm.sh', 'src/tools/zcc', 'src/tools/automap.sh', 'src/tools/findcc.sh', 'src/tools/bootstrap-check.sh', 'src/tools/mklsp.sh', 'src/tools/mklspwasm.sh', 'src/tools/mkvsix.sh', 'src/web/build.sh', 'src/baremetal/run.sh']] if args.group in ('js', 'all'): - commands += [['node', name] for name in ['src/js/tinyvm.js', 'src/js/extract_genome.js', 'src/js/extract_lsp.js']] + commands += [['node', name] for name in [str(Path(args.js_dir) / 'tinyvm.js'), 'src/js/mklayout.js', 'src/js/extract_lsp.js']] def invoke(command, option): # keep stdin open: help and rejected options must finish without reading it. diff --git a/tests/drivers/completion_test.js b/tests/drivers/completion_test.js index 22c5fa5..f1ee5e6 100644 --- a/tests/drivers/completion_test.js +++ b/tests/drivers/completion_test.js @@ -1,5 +1,7 @@ // compiler-backed completion, including incomplete source and position-aware lifetimes. // usage: node tests/drivers/completion_test.js path/to/zclsp [server arguments] +// interpreted hosts (no jit) are far slower; ZC_TEST_TIMEOUT_SCALE stretches every deadline +const TIMEOUT_SCALE = Number(process.env.ZC_TEST_TIMEOUT_SCALE) || 1; const assert = require('assert'); const cp = require('child_process'); const path = require('path'); @@ -25,7 +27,7 @@ function complete(source, extra = []) { for (const m of msgs) proc.stdin.write(frame(m)); output = Buffer.concat(chunks); } else { - const r = cp.spawnSync(process.argv[2], process.argv.slice(3), { input: Buffer.concat(msgs.map(frame)), timeout: 30000, maxBuffer: 32 << 20 }); + const r = cp.spawnSync(process.argv[2], process.argv.slice(3), { input: Buffer.concat(msgs.map(frame)), timeout: 30000 * TIMEOUT_SCALE, maxBuffer: 32 << 20 }); assert.strictEqual(r.status, 0, String(r.stderr)); output = r.stdout; } let b = output; const replies = []; diff --git a/tests/drivers/core_lsp_test.js b/tests/drivers/core_lsp_test.js index 983f9aa..c3c01aa 100644 --- a/tests/drivers/core_lsp_test.js +++ b/tests/drivers/core_lsp_test.js @@ -1,4 +1,6 @@ // check maintained zc sources through the editor document protocol, one file at a time. +// interpreted hosts (no jit) are far slower; ZC_TEST_TIMEOUT_SCALE stretches every deadline +const TIMEOUT_SCALE = Number(process.env.ZC_TEST_TIMEOUT_SCALE) || 1; const assert = require('assert'); const fs = require('fs'); const cp = require('child_process'); @@ -23,7 +25,7 @@ for (const file of files) { { jsonrpc: '2.0', id: 3, method: 'shutdown', params: null }, { jsonrpc: '2.0', method: 'exit' }, ]; - const result = cp.spawnSync(target, [], { input: Buffer.concat(messages.map(frame)), timeout: 30000, maxBuffer: 32 << 20 }); + const result = cp.spawnSync(target, [], { input: Buffer.concat(messages.map(frame)), timeout: 30000 * TIMEOUT_SCALE, maxBuffer: 32 << 20 }); assert.strictEqual(result.status, 0, file + ': ' + result.stderr); let data = result.stdout; const replies = []; diff --git a/tests/drivers/enhancement_backends_test.py b/tests/drivers/enhancement_backends_test.py index b7d553b..ddfee1b 100644 --- a/tests/drivers/enhancement_backends_test.py +++ b/tests/drivers/enhancement_backends_test.py @@ -9,7 +9,7 @@ source = (root / 'tests/programs/enhance.zc').read_text() source = source.replace('i < 5000', 'i < 200') source += '\n' reference = None -for command in [['bin/zy'], ['bin/zym'], ['node', 'src/js/tinyvm.js']]: +for command in [['build/bin/zy'], ['build/bin/zym'], ['node', 'src/js/tinyvm.js']]: result = subprocess.run(command, input=source, text=True, capture_output=True, timeout=25) assert result.returncode == 0 and result.stdout and 'zc:' not in result.stdout, (command, result.returncode, result.stdout, result.stderr) if reference is None: @@ -20,7 +20,7 @@ for command in [['bin/zy'], ['bin/zym'], ['node', 'src/js/tinyvm.js']]: source = '''o3 int increment(int value) { return value + 1; } fn main() { put_n(increment(6)); put_c(32); put_n(17 / 5); put_c(32); put_n(17 % 5); put_c(10); return 0; } ''' -for command in [['bin/zy'], ['bin/zym'], ['node', 'src/js/tinyvm.js']]: +for command in [['build/bin/zy'], ['build/bin/zym'], ['node', 'src/js/tinyvm.js']]: result = subprocess.run(command, input=source, text=True, capture_output=True, timeout=15) assert result.returncode == 0 and result.stdout == '7 3 2\n', (command, result.stdout, result.stderr) print('ok optimizer descriptors preserve portable division slots: ' + command[0]) @@ -39,7 +39,7 @@ fn main() { put_n(installed); put_c(32); put_n(first); put_c(32); put_n(rejected); put_c(32); put_n(second); put_c(10); return 0; } ''' -for command in [['bin/zy'], ['bin/zym'], ['node', 'src/js/tinyvm.js']]: +for command in [['build/bin/zy'], ['build/bin/zym'], ['node', 'src/js/tinyvm.js']]: result = subprocess.run(command, input=source, text=True, capture_output=True, timeout=15) assert result.returncode == 0 and result.stdout == '1 22 1 22\n', (command, result.stdout, result.stderr) print('ok replacing a built-in slot and rejecting a bad recipe: ' + command[0]) diff --git a/tests/drivers/ext_cache_test.js b/tests/drivers/ext_cache_test.js index f389fcc..93caf94 100644 --- a/tests/drivers/ext_cache_test.js +++ b/tests/drivers/ext_cache_test.js @@ -5,14 +5,25 @@ // starts; a change to an included file is checked again; putting it back is // answered from the cache. // -// node tests/drivers/ext_cache_test.js +// node tests/drivers/ext_cache_test.js [server-dir] +// +// the server directory holds what the packaged extension carries in server/: +// lsp.wasm, or tinyvm.js, layout.js, lsp.json and lsp.bin (the default is +// build/editors/vscode/server, which src/tools/mkvsix.sh fills). the extension is +// copied with that server into a scratch directory, since the tree has none. const fs = require('fs'); const os = require('os'); const path = require('path'); const Module = require('module'); -const client = path.join(__dirname, '..', '..', 'editors', 'vscode', 'client'); +const serverDir = path.resolve(process.argv[2] || path.join(__dirname, '..', '..', 'build', 'editors', 'vscode', 'server')); +if (!fs.existsSync(serverDir)) { console.log('FAIL no server directory at ' + serverDir); process.exit(1); } +const staged = fs.mkdtempSync(path.join(os.tmpdir(), 'zc-extension-')); +fs.cpSync(path.join(__dirname, '..', '..', 'editors', 'vscode'), staged, { recursive: true }); +fs.cpSync(serverDir, path.join(staged, 'server'), { recursive: true }); +process.on('exit', () => fs.rmSync(staged, { recursive: true, force: true })); +const client = path.join(staged, 'client'); // the vscode module, as far as the client uses it const world = { lines: [], sets: [], listeners: {}, watchers: [], textDocuments: [], saved: undefined, root: '' }; diff --git a/tests/drivers/float_backends_test.py b/tests/drivers/float_backends_test.py index cdcf48b..65901e2 100644 --- a/tests/drivers/float_backends_test.py +++ b/tests/drivers/float_backends_test.py @@ -4,7 +4,7 @@ import pathlib import subprocess root = pathlib.Path(__file__).resolve().parents[2] -commands = [['bin/zy'], ['bin/zym'], ['node', 'src/js/tinyvm.js']] +commands = [['build/bin/zy'], ['build/bin/zym'], ['node', 'src/js/tinyvm.js']] sources = [(root / 'tests/programs/float.zc').read_text(), '''include rt; fn render(bits, precision, mode) { asm { bits @ precision @ mode @ #1236 } return 0; } fn convert(bits) { return asm { bits @ #1218 }; } fn main() { diff --git a/tests/drivers/fuzz_compiler.py b/tests/drivers/fuzz_compiler.py index 512ec26..0fe0d27 100644 --- a/tests/drivers/fuzz_compiler.py +++ b/tests/drivers/fuzz_compiler.py @@ -1,17 +1,23 @@ #!/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] +Usage: python3 tests/drivers/fuzz_compiler.py [path/to/zy] [path/to/zym] [--js [path/to/tinyvm.js]] +(--js alone runs build/js/tinyvm.js, from make 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" +argv = sys.argv[1:] +js = None +if "--js" in argv: + k = argv.index("--js") + del argv[k] + js = argv.pop(k) if k < len(argv) and argv[k].endswith(".js") else "build/js/tinyvm.js" +args = argv +zy = args[0] if len(args) > 0 else "build/bin/zy" +zym = args[1] if len(args) > 1 else "build/bin/zym" rng = random.Random(0x5A17) values = [] library_values = [] @@ -33,26 +39,26 @@ def expr(depth): 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();"] +lines = ["namespace;", "include rt, run, std;", "fn churn(n) {", " int i = 0;", " while (i < n) {", " { int* p = rt.new(int); *p = i; }", " i++;", " }", " return 0;", "}", "fn stable() { return 73; }", "fn main() {", " let base = rt.live_objects();"] for _ in range(128): source, value = expr(4) - lines.append(f" put_n({source}); put_c(32);") + lines.append(f" rt.put_n({source}); rt.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);') + lines.append(f' rt.put_n(std.Text.len("{a}")); rt.put_c(32); rt.put_n(std.Text.eq("{a}", "{b}")); rt.put_c(32);') + lines.append(f' rt.put_n(std.Text.starts("{a}", "{b}")); rt.put_c(32); rt.put_n(std.Text.ends("{a}", "{b}")); rt.put_c(32); rt.put_n(std.Text.find("{a}", "{b}")); rt.put_c(32);') + lines.append(f' rt.put_n(std.Bytes.len(b"{a}")); rt.put_c(32); rt.put_n(std.Bytes.eq(b"{a}", b"{b}")); rt.put_c(32);') + lines.append(f' rt.put_n(std.Bytes.starts(b"{a}", b"{b}")); rt.put_c(32); rt.put_n(std.Bytes.ends(b"{a}", b"{b}")); rt.put_c(32); rt.put_n(std.Bytes.find(b"{a}", b"{b}")); rt.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);', + " churn(300); rt.gc(); rt.put_n(rt.live_objects() - base); rt.put_c(32);", + ' char* source = "fn f() { return 7; }"; let parsed = run.tokens(source);', + ' rt.put_n(parsed[0] > 0); rt.put_c(32); rt.put_n(rt.eq_w(source, "fn f() { return 7; }")); rt.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);', + ' while (e < 16) { let q = run.tokens(eval_source); rt.put_n(run.eval(q)); rt.put_c(32); e++; }', + ' rt.put_n(stable()); rt.put_c(32); rt.put_n(rt.eq_w(eval_source, "return 42;")); rt.put_c(10);', " return 0;", "}", ] @@ -78,7 +84,7 @@ def run(binary): hosts = [(zy, [zy]), (zym, [zym])] if js: - hosts.append(("javascript", ["node", "src/js/tinyvm.js"])) + hosts.append(("javascript", ["node", 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") diff --git a/tests/drivers/fuzz_lsp.js b/tests/drivers/fuzz_lsp.js index e22a44b..f8a1d3b 100644 --- a/tests/drivers/fuzz_lsp.js +++ b/tests/drivers/fuzz_lsp.js @@ -1,7 +1,9 @@ // Deterministic source/protocol fuzzing for diagnostics, caching, and recovery. // Usage: node tests/drivers/fuzz_lsp.js path/to/zclsp +// interpreted hosts (no jit) are far slower; ZC_TEST_TIMEOUT_SCALE stretches every deadline +const TIMEOUT_SCALE = Number(process.env.ZC_TEST_TIMEOUT_SCALE) || 1; const { spawnSync } = require('child_process'); -const server = process.argv[2] || 'bin/zclsp'; +const server = process.argv[2] || 'build/bin/zclsp'; let state = 0x5a17; function next() { state = (Math.imul(state, 1664525) + 1013904223) >>> 0; return state; } function frame(m) { @@ -35,7 +37,7 @@ for (let i = 0; i < 32; i++) { msgs.push({ jsonrpc: '2.0', method: 'textDocument/didChange', params: { textDocument: { uri, version: 2 }, contentChanges: [{ text: fixed }] } }); } const stdUri = 'file:///fuzz-std.zc'; -msgs.push({ jsonrpc: '2.0', method: 'textDocument/didOpen', params: { textDocument: { uri: stdUri, languageId: 'zc', version: 1, text: 'include std;\nfn main() { return Text.starts("alpha", "al"); }\n' } } }); +msgs.push({ jsonrpc: '2.0', method: 'textDocument/didOpen', params: { textDocument: { uri: stdUri, languageId: 'zc', version: 1, text: 'include std;\nfn main() { return std.Text.starts("alpha", "al"); }\n' } } }); // Keep one server alive while documents are repeatedly opened, made malformed, // repaired, closed, and reopened at a new version. This exercises cache @@ -57,7 +59,7 @@ for (const [uri, text] of churn) { msgs.push({ jsonrpc: '2.0', method: 'textDocument/didOpen', params: { textDocument: { uri, languageId: 'zc', version: churnVersion++, text } } }); } msgs.push({ jsonrpc: '2.0', method: 'exit' }); -const run = spawnSync(server, [], { input: Buffer.concat(msgs.map(frame)), timeout: 60000, maxBuffer: 1 << 28 }); +const run = spawnSync(server, [], { input: Buffer.concat(msgs.map(frame)), timeout: 60000 * TIMEOUT_SCALE, maxBuffer: 1 << 28 }); if (run.error || run.status !== 0) throw run.error || new Error(`server exit ${run.status}: ${run.stderr}`); const out = parse(run.stdout); const diagnostics = new Map(); diff --git a/tests/drivers/fuzz_malformed.py b/tests/drivers/fuzz_malformed.py index 5bf97cf..42b8a0e 100644 --- a/tests/drivers/fuzz_malformed.py +++ b/tests/drivers/fuzz_malformed.py @@ -8,7 +8,7 @@ import subprocess import sys -bins = sys.argv[1:] or ["bin/zy", "bin/zym"] +bins = sys.argv[1:] or ["build/bin/zy", "build/bin/zym"] if len(bins) == 1: bins.append(bins[0]) rng = random.Random(0xC0DE) diff --git a/tests/drivers/fuzz_wasm.js b/tests/drivers/fuzz_wasm.js index b0982d4..22fa7d5 100644 --- a/tests/drivers/fuzz_wasm.js +++ b/tests/drivers/fuzz_wasm.js @@ -31,7 +31,7 @@ function quote(s) { return '"' + s + '"'; } lines.push(' churn(300); gc(); put_n(live_objects() - base); put_c(32);', ' put_n(stable()); put_c(10);', ' return 0;', '}'); expected.push(0, 73); const source = lines.join('\n') + '\n', want = expected.join(' ') + '\n'; - const zc = await ZC.load(fs.readFileSync(process.argv[2] || 'src/web/zc.wasm')); + const zc = await ZC.load(fs.readFileSync(process.argv[2] || 'build/web/zc.wasm')); const result = await zc.run(source); if (result.output !== want) throw new Error(`wasm fuzz mismatch\nexpected: ${want.slice(0, 180)}\nactual: ${result.output.slice(0, 180)}\nmessages: ${result.messages}`); console.log('ok wasm compiler/runtime/stdlib fuzz: 128 expressions, 300 scoped allocations and GC, and 32 Text/Bytes cases'); diff --git a/tests/drivers/genome_blob_test.py b/tests/drivers/genome_blob_test.py index 81533a7..fea055b 100644 --- a/tests/drivers/genome_blob_test.py +++ b/tests/drivers/genome_blob_test.py @@ -1,13 +1,17 @@ #!/usr/bin/env python3 # documentation precedes the symbol table; the boot header must skip those bytes. +# usage: python3 tests/drivers/genome_blob_test.py [build/lsp] (the documented language server image) import pathlib import re +import struct +import sys -source = pathlib.Path('src/core/genome.h').read_text() -org = int(re.search(r'#define GORG (\d+)', source)[1]) -length = int(re.search(r'#define GLEN (\d+)', source)[1]) -words = [int(word.strip()) for word in re.search(r'genome\[\]\s*=\s*\{([\s\S]*?)\};', source)[1].split(',') if word.strip()] -assert len(words) == length and words[6] == org + length +folder = pathlib.Path(sys.argv[1] if len(sys.argv) > 1 else 'build/lsp') +image = (folder / 'lsp_genome.bin').read_bytes() +org = int(re.search(r'#define GORG (?:0x)?([0-9A-Fa-f]+)', (folder / 'memmap.h').read_text())[1], 16) +words = list(struct.unpack('<%dq' % (len(image) // 8), image)) +length = len(words) +assert words[6] == org + length cursor = words[5] - org assert 7 <= cursor < length records = {} @@ -23,8 +27,8 @@ while words[cursor]: cursor += 1 records[name] = (kind, value, signature) assert cursor == length - 1 -assert len(records) > 1000 and records['start'][0] == 1 -assert records['reserve_code'][0] == 1 and records['build_image_grow'][0] == 1 +assert len(records) > 1000 and records['run.start'][0] == 1 +assert records['run.reserve_code'][0] == 1 and records['run.build_image'][0] == 1 assert any(name.startswith('doc ') for name in records), 'the fixture contains documentation bytes' for name, (kind, value, _) in records.items(): if kind == 8: diff --git a/tests/drivers/hints_test.js b/tests/drivers/hints_test.js index 8bb1934..b3598be 100644 --- a/tests/drivers/hints_test.js +++ b/tests/drivers/hints_test.js @@ -1,5 +1,7 @@ // hints are informational diagnostics (severity 3) and follow positional hints on / small / off directives. // usage: node tests/drivers/hints_test.js path/to/zclsp +// interpreted hosts (no jit) are far slower; ZC_TEST_TIMEOUT_SCALE stretches every deadline +const TIMEOUT_SCALE = Number(process.env.ZC_TEST_TIMEOUT_SCALE) || 1; const assert = require('assert'); const cp = require('child_process'); const server = process.argv[2]; @@ -10,7 +12,7 @@ function diagnostics(text) { { jsonrpc: '2.0', id: 1, method: 'initialize', params: {} }, { jsonrpc: '2.0', method: 'textDocument/didOpen', params: { textDocument: { uri, version: 1, languageId: 'zc', text } } }, { jsonrpc: '2.0', method: 'exit' }].map(frame)); - const r = cp.spawnSync(server, process.argv.slice(3), { input, timeout: 20000, maxBuffer: 4 << 20 }); + const r = cp.spawnSync(server, process.argv.slice(3), { input, timeout: 20000 * TIMEOUT_SCALE, maxBuffer: 4 << 20 }); assert.strictEqual(r.status, 0, String(r.stderr)); let d = r.stdout, out = []; while (d.length) { diff --git a/tests/drivers/hover_features_test.js b/tests/drivers/hover_features_test.js index d6e1226..25f5965 100644 --- a/tests/drivers/hover_features_test.js +++ b/tests/drivers/hover_features_test.js @@ -1,5 +1,7 @@ // focused type, inference, and block-documentation hover regressions. // usage: node tests/drivers/hover_features_test.js path/to/zclsp +// interpreted hosts (no jit) are far slower; ZC_TEST_TIMEOUT_SCALE stretches every deadline +const TIMEOUT_SCALE = Number(process.env.ZC_TEST_TIMEOUT_SCALE) || 1; const assert = require('assert'); const cp = require('child_process'); const server = process.argv[2]; @@ -21,7 +23,7 @@ function hover(text, needle, nth = 0) { { jsonrpc: '2.0', id: 2, method: 'textDocument/hover', params: { textDocument: { uri }, position: { line, character } } }, { jsonrpc: '2.0', method: 'exit' }, ]; - const result = cp.spawnSync(server, process.argv.slice(3), { input: Buffer.concat(messages.map(frame)), timeout: 10000, maxBuffer: 4 << 20 }); + const result = cp.spawnSync(server, process.argv.slice(3), { input: Buffer.concat(messages.map(frame)), timeout: 10000 * TIMEOUT_SCALE, maxBuffer: 4 << 20 }); assert.strictEqual(result.status, 0, String(result.stderr)); let data = result.stdout; while (data.length) { diff --git a/tests/drivers/hover_surface_test.js b/tests/drivers/hover_surface_test.js index 13a9a3f..688aca6 100644 --- a/tests/drivers/hover_surface_test.js +++ b/tests/drivers/hover_surface_test.js @@ -1,4 +1,6 @@ // hover declarations and instructions in the source-only documents opened by f12. +// interpreted hosts (no jit) are far slower; ZC_TEST_TIMEOUT_SCALE stretches every deadline +const TIMEOUT_SCALE = Number(process.env.ZC_TEST_TIMEOUT_SCALE) || 1; const assert = require('assert'); const fs = require('fs'); const cp = require('child_process'); @@ -48,7 +50,7 @@ if (process.argv[2] === '--wasm' || process.argv[2] === '--vm') { for (const m of messages) p.stdin.write(frame({ jsonrpc: '2.0', ...m })); out = Buffer.concat(chunks); } else { - const r = cp.spawnSync(process.argv[2], process.argv.slice(3), { input: Buffer.concat(messages.map(m => frame({ jsonrpc: '2.0', ...m }))), timeout: 60000, maxBuffer: 32 << 20 }); + const r = cp.spawnSync(process.argv[2], process.argv.slice(3), { input: Buffer.concat(messages.map(m => frame({ jsonrpc: '2.0', ...m }))), timeout: 60000 * TIMEOUT_SCALE, maxBuffer: 32 << 20 }); assert.strictEqual(r.status, 0, String(r.stderr)); out = r.stdout; } const replies = new Map(); diff --git a/tests/drivers/hover_values_test.js b/tests/drivers/hover_values_test.js index badc2ed..0f09cfc 100644 --- a/tests/drivers/hover_values_test.js +++ b/tests/drivers/hover_values_test.js @@ -1,4 +1,6 @@ // value previews follow the compiler's bindings and the position of each use. +// interpreted hosts (no jit) are far slower; ZC_TEST_TIMEOUT_SCALE stretches every deadline +const TIMEOUT_SCALE = Number(process.env.ZC_TEST_TIMEOUT_SCALE) || 1; const assert = require('assert'); const cp = require('child_process'); const server = process.argv[2]; @@ -17,7 +19,7 @@ function hover(text, needle, nth = 0) { const body = Buffer.from(JSON.stringify(m)); return Buffer.concat([Buffer.from('Content-Length: ' + body.length + '\r\n\r\n'), body]); })); - const result = cp.spawnSync(server, process.argv.slice(3), { input, timeout: 20000, maxBuffer: 8 << 20 }); + const result = cp.spawnSync(server, process.argv.slice(3), { input, timeout: 20000 * TIMEOUT_SCALE, maxBuffer: 8 << 20 }); assert.strictEqual(result.status, 0, String(result.stderr)); let data = result.stdout; while (data.length) { diff --git a/tests/drivers/icon_test.py b/tests/drivers/icon_test.py index 560c622..72cd69b 100644 --- a/tests/drivers/icon_test.py +++ b/tests/drivers/icon_test.py @@ -5,7 +5,7 @@ import argparse import subprocess parser = argparse.ArgumentParser() -parser.add_argument('vm', nargs='?', default='bin/zy') +parser.add_argument('vm', nargs='?', default='build/bin/zy') parser.add_argument('--case', default='') parser.add_argument('--js', action='store_true') parser.add_argument('--source', action='store_true', help='test the working library source instead of the embedded module') diff --git a/tests/drivers/image_run.js b/tests/drivers/image_run.js index f657916..ea05f9f 100644 --- a/tests/drivers/image_run.js +++ b/tests/drivers/image_run.js @@ -2,12 +2,11 @@ const fs = require('fs'); const { TinyVM } = require('../../build/js/tinyvm.js'); const { mapValues } = require('../../src/js/mapvalues.js'); -const header = fs.readFileSync(process.argv[2], 'utf8'); -const org = Number(header.match(/#define\s+GORG\s+(\d+)/)[1]); -const words = header.match(/genome\[\]\s*=\s*\{([\s\S]*?)\};/)[1].split(',').map(s => s.trim()).filter(Boolean); +// usage: node tests/drivers/image_run.js genome.bin memmap.h [--stdin] +const image = fs.readFileSync(process.argv[2]); const layout = mapValues(process.argv[3], ['GORG', 'DS0', 'DSLIM', 'RS0', 'RSLIM', 'SYSGATE', 'ZFP', 'HEAP_LO', 'HEAP_HI']); const machine = new TinyVM({ layout, onMessage: message => process.stderr.write(message) }); -machine.loadGenome(words, org); +machine.loadImage(image, layout.GORG); if (process.argv[4] === '--stdin') machine.setInput(fs.readFileSync(0)); const status = machine.run(); process.stdout.write(machine.getOutput()); diff --git a/tests/drivers/imagekit.py b/tests/drivers/imagekit.py new file mode 100644 index 0000000..6cc083f --- /dev/null +++ b/tests/drivers/imagekit.py @@ -0,0 +1,34 @@ +"""shared steps for the drivers that build their own images: pick a c compiler with +#embed, and link a microcore around a genome image (little-endian 64-bit words) +for a given memory map. everything is written into the caller's directory.""" +import os +import shutil +import subprocess +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +DEFAULT_MAP = ROOT / 'build' / 'core' / 'map' / 'memmap.h' +MICRO_SOURCES = ['src/micro/micro.c', 'src/micro/micro_port.h', 'src/micro/port_linux.c', + 'src/core/fops.h', 'src/core/bops.h', 'src/core/stack.h', 'src/core/genome.h'] + + +def compiler(): + """the c compiler: CC, or the one src/tools/findcc.sh picks.""" + if os.environ.get('CC'): + return os.environ['CC'] + return subprocess.run(['sh', str(ROOT / 'src/tools/findcc.sh')], check=True, capture_output=True, text=True).stdout.strip() + + +def micro_image(directory, image, memmap=DEFAULT_MAP, threads=True, name='image', timeout=60): + """link a microcore in directory that boots image (bytes). memmap is the map header the + image was built for. returns the executable's path.""" + directory = Path(directory) + (directory / 'genome.bin').write_bytes(image) + shutil.copy(memmap, directory / 'memmap.h') + for f in MICRO_SOURCES: + shutil.copy(ROOT / f, directory) + flags = ['-pthread', '-DZY_THREADS'] if threads else [] + out = directory / name + subprocess.run([compiler(), '-O2', '-w', '-fwrapv', *flags, str(directory / 'micro.c'), str(directory / 'port_linux.c'), + '-I' + str(directory), '-o', str(out)], check=True, timeout=timeout) + return out diff --git a/tests/drivers/layout_config_test.py b/tests/drivers/layout_config_test.py index 469e7dd..2f0fa07 100644 --- a/tests/drivers/layout_config_test.py +++ b/tests/drivers/layout_config_test.py @@ -5,7 +5,7 @@ import subprocess import tempfile root = pathlib.Path(__file__).resolve().parents[2] -tool = root / 'bin/genmap' +tool = root / 'build/bin/genmap' result = subprocess.run([tool, '--help'], capture_output=True, text=True) assert result.returncode == 0 and '--generated-text' in result.stderr invalid = [ diff --git a/tests/drivers/lsp_growth_test.js b/tests/drivers/lsp_growth_test.js index b06230a..6d221fd 100644 --- a/tests/drivers/lsp_growth_test.js +++ b/tests/drivers/lsp_growth_test.js @@ -1,5 +1,7 @@ // focused regressions for growing document, uri, hover, and name-set storage. // usage: node tests/drivers/lsp_growth_test.js path/to/zclsp-or-lsp.wasm +// interpreted hosts (no jit) are far slower; ZC_TEST_TIMEOUT_SCALE stretches every deadline +const TIMEOUT_SCALE = Number(process.env.ZC_TEST_TIMEOUT_SCALE) || 1; const assert = require('assert'); const cp = require('child_process'); const { wasmProcess } = require('../../editors/vscode/client/wasmproc'); @@ -36,7 +38,7 @@ if (target.endsWith('.wasm')) { for (const message of messages) assert.ok(server.stdin.write(frame(message))); output = Buffer.concat(chunks); } else { - const result = cp.spawnSync(target, [], { input: Buffer.concat(messages.map(frame)), timeout: 30000, maxBuffer: 8 << 20 }); + const result = cp.spawnSync(target, [], { input: Buffer.concat(messages.map(frame)), timeout: 30000 * TIMEOUT_SCALE, maxBuffer: 8 << 20 }); assert.strictEqual(result.status, 0, String(result.stderr)); output = result.stdout; } diff --git a/tests/drivers/lsp_sandbox.js b/tests/drivers/lsp_sandbox.js index 14bc1e3..41523cc 100644 --- a/tests/drivers/lsp_sandbox.js +++ b/tests/drivers/lsp_sandbox.js @@ -2,10 +2,12 @@ // (immediate functions). the server must not let that code write files, so a file that // tries to leaves nothing behind and the server still answers. // usage: node tests/drivers/lsp_sandbox.js path/to/zclsp +// interpreted hosts (no jit) are far slower; ZC_TEST_TIMEOUT_SCALE stretches every deadline +const TIMEOUT_SCALE = Number(process.env.ZC_TEST_TIMEOUT_SCALE) || 1; const cp = require('child_process'), fs = require('fs'), os = require('os'), path = require('path'); const target = path.join(os.tmpdir(), 'zc-sandbox-' + process.pid); try { fs.unlinkSync(target); } catch (e) {} -const src = 'immediate fn evil() { let fd = syscall(1, "' + target + '", 1, 0); syscall(4, fd, 65, 1); syscall(2, fd, 0, 0); return 0; }\nevil\nfn main() { return 0; }\n'; +const src = 'include rt, run;\nimmediate fn evil() { run.next(); let fd = rt.syscall(1, "' + target + '", 1, 0); rt.syscall(4, fd, 65, 1); rt.syscall(2, fd, 0, 0); return 0; }\nevil\nfn main() { return 0; }\n'; const frame = m => { const b = Buffer.from(JSON.stringify(m)); return Buffer.concat([Buffer.from('Content-Length: ' + b.length + '\r\n\r\n'), b]); }; const input = Buffer.concat([ frame({ jsonrpc: '2.0', id: 1, method: 'initialize', params: { processId: 1, capabilities: {} } }), @@ -13,7 +15,7 @@ const input = Buffer.concat([ frame({ jsonrpc: '2.0', id: 3, method: 'textDocument/semanticTokens/full', params: { textDocument: { uri: 'file:///evil.zc' } } }), frame({ jsonrpc: '2.0', method: 'exit' }), ]); -const r = cp.spawnSync(process.argv[2], [], { input, timeout: 20000 }); +const r = cp.spawnSync(process.argv[2], [], { input, timeout: 20000 * TIMEOUT_SCALE }); const wrote = fs.existsSync(target); if (wrote) fs.unlinkSync(target); const answered = r.stdout && r.stdout.includes('"id":3'); diff --git a/tests/drivers/lsp_test.js b/tests/drivers/lsp_test.js index ab43c41..18d8bf7 100644 --- a/tests/drivers/lsp_test.js +++ b/tests/drivers/lsp_test.js @@ -1,5 +1,7 @@ // protocol tests for the language server (node, no packages). // usage: node tests/drivers/lsp_test.js path/to/zclsp +// interpreted hosts (no jit) are far slower; ZC_TEST_TIMEOUT_SCALE stretches every deadline +const TIMEOUT_SCALE = Number(process.env.ZC_TEST_TIMEOUT_SCALE) || 1; const { spawnSync } = require('child_process'); const fs = require('fs'); const server = process.argv[2]; @@ -24,7 +26,7 @@ function parse(buf) { return out; } function run(msgs) { - const r = spawnSync(server, [], { input: Buffer.concat(msgs.map(frame)), timeout: 30000, maxBuffer: 1 << 28 }); + const r = spawnSync(server, [], { input: Buffer.concat(msgs.map(frame)), timeout: 30000 * TIMEOUT_SCALE, maxBuffer: 1 << 28 }); return { code: r.status, msgs: parse(r.stdout) }; } const init = id => ({ jsonrpc: '2.0', id, method: 'initialize', params: {} }); @@ -173,7 +175,7 @@ check(hv && hv.includes('leaves the function with a value'), 'a keyword shows wh const builtinSource = 'include rt; fn main() {\n\trt.put_c(32);\n\treturn 0;\n}\n'; hv = hover(builtinSource, 1, at(builtinSource, 1, 'put_c')); check(hv && hv.includes('put_c('), 'a built-in function shows its signature', hv); -const kwsrc = 'include rt; fn main() {\n\tint* p = new(int);\n\trt.free(p);\n\treturn 0;\n}\n'; +const kwsrc = 'include rt; fn main() {\n\tint* p = rt.new(int);\n\trt.free(p);\n\treturn 0;\n}\n'; hv = hover(kwsrc, 1, at(kwsrc, 1, 'new')); check(hv && hv.includes('new(T)') && hv.includes('fresh heap object') && hv.includes('owns it'), 'hover on new shows its form and ownership behavior', hv); hv = hover(kwsrc, 2, at(kwsrc, 2, 'free')); @@ -209,7 +211,7 @@ check(r.code === 0 && r.msgs.some(m => m.id === 9 && m.result === null), 'shutdo // the corpus: every valid program checks clean, every bad one is rejected with its message function cli(file) { - const r = spawnSync(server, [], { input: fs.readFileSync(file), timeout: 30000 }); + const r = spawnSync(server, [], { input: fs.readFileSync(file), timeout: 30000 * TIMEOUT_SCALE }); return r.stdout.toString(); } let clean = 0, dirty = []; diff --git a/tests/drivers/memory_limits_test.py b/tests/drivers/memory_limits_test.py index a973480..808a239 100644 --- a/tests/drivers/memory_limits_test.py +++ b/tests/drivers/memory_limits_test.py @@ -13,7 +13,7 @@ source = '''fn main() { return 0; } ''' -commands = [['bin/zy'], ['bin/zym'], ['node', 'src/js/tinyvm.js']] +commands = [['build/bin/zy'], ['build/bin/zym'], ['node', 'src/js/tinyvm.js']] environment = dict(os.environ, TINYVM_MAX_MEMORY_WORDS='16777216') for command in commands: result = subprocess.run(command, input=source, text=True, capture_output=True, env=environment, timeout=15) diff --git a/tests/drivers/nav_test.js b/tests/drivers/nav_test.js index 23afbf8..8f0d8fd 100644 --- a/tests/drivers/nav_test.js +++ b/tests/drivers/nav_test.js @@ -1,5 +1,7 @@ // go to definition and find references, in the document and across includes. // usage: node tests/drivers/nav_test.js path/to/zclsp (run from the repository root) +// interpreted hosts (no jit) are far slower; ZC_TEST_TIMEOUT_SCALE stretches every deadline +const TIMEOUT_SCALE = Number(process.env.ZC_TEST_TIMEOUT_SCALE) || 1; const assert = require('assert'); const cp = require('child_process'); const fs = require('fs'); @@ -18,7 +20,7 @@ function request(text, needle, method, extra = {}) { { jsonrpc: '2.0', method: 'textDocument/didOpen', params: { textDocument: { uri, version: 1, languageId: 'zc', text } } }, { jsonrpc: '2.0', id: 2, method, params: { textDocument: { uri }, position, ...extra } }, { jsonrpc: '2.0', method: 'exit' }].map(frame)); - const r = cp.spawnSync(server, process.argv.slice(3), { input, cwd: root, timeout: 30000, maxBuffer: 8 << 20 }); + const r = cp.spawnSync(server, process.argv.slice(3), { input, cwd: root, timeout: 30000 * TIMEOUT_SCALE, maxBuffer: 8 << 20 }); assert.strictEqual(r.status, 0, String(r.stderr)); let d = r.stdout, init; while (d.length) { diff --git a/tests/drivers/protocol_growth_test.js b/tests/drivers/protocol_growth_test.js index 64015d8..d8dff6c 100644 --- a/tests/drivers/protocol_growth_test.js +++ b/tests/drivers/protocol_growth_test.js @@ -1,4 +1,6 @@ // preserve long request identifiers and ignore deeply nested unknown json fields. +// interpreted hosts (no jit) are far slower; ZC_TEST_TIMEOUT_SCALE stretches every deadline +const TIMEOUT_SCALE = Number(process.env.ZC_TEST_TIMEOUT_SCALE) || 1; const assert = require('assert'); const cp = require('child_process'); const target = process.argv[2]; @@ -15,7 +17,7 @@ for (let i = 0; i < 24; i++) messages.push({ jsonrpc: '2.0', method: 'textDocume text: 'enum choice { item { int value; }, empty };\nfn main() { choice*.m p = choice.item(7); return p.item_value; }\n', } } }); messages.push({ jsonrpc: '2.0', id: 'finish', method: 'shutdown', params: null }, { jsonrpc: '2.0', method: 'exit' }); -const result = cp.spawnSync(target, [], { input: Buffer.concat(messages.map(frame)), timeout: 20000, maxBuffer: 8 << 20 }); +const result = cp.spawnSync(target, [], { input: Buffer.concat(messages.map(frame)), timeout: 20000 * TIMEOUT_SCALE, maxBuffer: 8 << 20 }); assert.strictEqual(result.status, 0, String(result.stderr)); const responses = []; let data = result.stdout; diff --git a/tests/drivers/runtime_image_test.py b/tests/drivers/runtime_image_test.py index c1f4b9c..d214613 100644 --- a/tests/drivers/runtime_image_test.py +++ b/tests/drivers/runtime_image_test.py @@ -1,14 +1,18 @@ #!/usr/bin/env python3 """self-hosted run-only images retain enhancements and generic method bodies.""" +import sys import argparse import subprocess import tempfile from pathlib import Path +sys.dont_write_bytecode = True # importing imagekit must not leave __pycache__ in the tree +from imagekit import micro_image parser = argparse.ArgumentParser() parser.add_argument('--case', default='') -parser.add_argument('--host', default='bin/zy') -parser.add_argument('--mkgenome', default='bin/mkgenome') +parser.add_argument('--host', default='build/bin/zy') +parser.add_argument('--mkgenome', default='build/bin/mkgenome') +parser.add_argument('--map', default='build/core/map/memmap.h') args = parser.parse_args() cases = [ ('enhancements', 'include rt; fn main() { rt.put_n(17 % 5); rt.put_c(32); rt.put_n(17 / 5); }', b'2 3'), @@ -23,12 +27,9 @@ for name, source, expected in cases: directory = Path(work) r = subprocess.run([args.host], input=Path('src/tools/build.zc').read_bytes() + b'`' + prefix + source.encode(), capture_output=True, timeout=30) assert r.returncode == 0 and len(r.stdout) >= 56 and not r.stdout.startswith(b'zc:'), (name, r.stdout[:1000], r.stderr) - header = subprocess.run([args.mkgenome], input=r.stdout, capture_output=True, timeout=5) - assert header.returncode == 0, (name, header.stderr) - (directory / 'genome.h').write_bytes(header.stdout) - for f in ['src/core/memmap.h', 'src/core/fops.h', 'src/core/bops.h', 'src/core/stack.h', 'src/micro/micro.c', 'src/micro/micro_port.h', 'src/micro/port_linux.c']: - (directory / Path(f).name).write_bytes(Path(f).read_bytes()) - subprocess.run(['cc', '-O2', '-w', '-fwrapv', '-pthread', '-DZY_THREADS', str(directory / 'micro.c'), str(directory / 'port_linux.c'), '-I' + work, '-o', str(directory / 'image')], check=True, timeout=30) - r = subprocess.run([str(directory / 'image')], capture_output=True, timeout=10) + image = subprocess.run([args.mkgenome], input=r.stdout, capture_output=True, timeout=5) + assert image.returncode == 0, (name, image.stderr) + binary = micro_image(directory, image.stdout, args.map) + r = subprocess.run([str(binary)], capture_output=True, timeout=10) assert r.returncode == 0 and r.stdout == expected, (name, r.returncode, r.stdout, r.stderr, expected) print('ok self-hosted run-only image: ' + name) diff --git a/tests/drivers/seed_growth_test.py b/tests/drivers/seed_growth_test.py index 426c970..b94445e 100644 --- a/tests/drivers/seed_growth_test.py +++ b/tests/drivers/seed_growth_test.py @@ -1,25 +1,24 @@ #!/usr/bin/env python3 # stage 0 accepts long source streams and grows a tiny initial image reservation. +import sys import pathlib import subprocess import tempfile +sys.dont_write_bytecode = True # importing imagekit must not leave __pycache__ in the tree +from imagekit import compiler, micro_image root = pathlib.Path(__file__).resolve().parents[2] +(root / 'build').mkdir(exist_ok=True) with tempfile.TemporaryDirectory(prefix='seed-growth-', dir=root / 'build') as directory: folder = pathlib.Path(directory) - subprocess.run(['bin/genmap', '--preset', 'run', '--grow', '--genome', '600000', '--heap', '1048576', '-o', directory], check=True, capture_output=True) - compiler = folder / 'zc0' - subprocess.run(['cc', '-O1', '-w', '-fwrapv', '-DMAXW=16', 'src/tools/zc0.c', '-I' + directory, '-Isrc/core', '-o', str(compiler)], check=True) + subprocess.run(['build/bin/genmap', '--preset', 'run', '--grow', '--genome', '600000', '--heap', '1048576', '-o', directory], check=True, capture_output=True) + zc0 = folder / 'zc0' + subprocess.run([compiler(), '-O1', '-w', '-fwrapv', '-DMAXW=16', 'src/tools/zc0.c', '-I' + directory, '-Isrc/core', '-o', str(zc0)], check=True) source = '/*' + 'x' * (5 << 20) + '*/\n' - source += (folder / 'rt.zc').read_text() + (root / 'src/runtime/run.zc').read_text() - source += 'let data[550000]; fn main() { data[549999] = 7; put_n(data[549999]); put_c(32); put_n(4611686018427387904); put_c(10); return 0; }\n' - with (folder / 'genome.h').open('w') as output: - subprocess.run([str(compiler)], input=source, text=True, stdout=output, check=True, timeout=30) - binary = folder / 'vm' - import shutil - for path in ['src/micro/micro.c', 'src/micro/micro_port.h', 'src/micro/port_linux.c', 'src/core/fops.h', 'src/core/bops.h', 'src/core/stack.h']: - shutil.copy(root / path, folder) - subprocess.run(['cc', '-O2', '-w', '-fwrapv', str(folder / 'micro.c'), str(folder / 'port_linux.c'), '-I' + directory, '-o', str(binary)], check=True) + source += (folder / 'rt.zc').read_text() + (root / 'src/core/core.zc').read_text() + (root / 'src/runtime/run.zc').read_text() + source += 'namespace; include rt; let data[550000]; fn main() { data[549999] = 7; rt.put_n(data[549999]); rt.put_c(32); rt.put_n(4611686018427387904); rt.put_c(10); return 0; }\n' + image = subprocess.run([str(zc0)], input=source.encode(), capture_output=True, check=True, timeout=30).stdout + binary = micro_image(folder / 'vm', image, folder / 'memmap.h', threads=False) if (folder / 'vm').mkdir() is None else None result = subprocess.run([str(binary)], text=True, capture_output=True, timeout=20) assert result.returncode == 0 and result.stdout == '7 4611686018427387904\n', (result.returncode, result.stdout, result.stderr) print('ok stage 0 reads beyond four megabytes and grows beyond its initial image capacity') diff --git a/tests/drivers/stack_growth_test.py b/tests/drivers/stack_growth_test.py index d4abe81..3c90305 100644 --- a/tests/drivers/stack_growth_test.py +++ b/tests/drivers/stack_growth_test.py @@ -4,6 +4,8 @@ import subprocess import sys import tempfile from pathlib import Path +sys.dont_write_bytecode = True # importing imagekit must not leave __pycache__ in the tree +from imagekit import compiler command = sys.argv[1:] @@ -12,47 +14,53 @@ def run(source, expected, compile_image=False): if compile_image and command[0] == 'node': # compile the execution stress cases once with stage 0, so a javascript # compiler's speed does not determine the execution-test timeout. + Path('build').mkdir(exist_ok=True) with tempfile.TemporaryDirectory(prefix='stack-image-', dir='build') as directory: target = Path(directory) - subprocess.run(['bin/genmap', '--preset', 'run', '--grow', '--heap', '1048576', '--genome', '131072', '--frame', '4096', '--return', '256', '--data', '3072', '-o', directory], check=True, capture_output=True) - compiler = target / 'zc0' - subprocess.run(['cc', '-O1', '-w', '-fwrapv', 'src/tools/zc0.c', '-I' + directory, '-Isrc/core', '-o', str(compiler)], check=True, capture_output=True) - full_source = (target / 'rt.zc').read_text() + Path('src/runtime/run.zc').read_text() + source - image = subprocess.run([str(compiler)], input=full_source, text=True, capture_output=True, check=True) - header = target / 'genome.h' - header.write_text(image.stdout) - result = subprocess.run(['node', 'tests/drivers/image_run.js', str(header), str(target / 'memmap.h')], text=True, capture_output=True, timeout=45) + subprocess.run(['build/bin/genmap', '--preset', 'run', '--grow', '--heap', '1048576', '--genome', '131072', '--frame', '4096', '--return', '256', '--data', '3072', '-o', directory], check=True, capture_output=True) + zc0 = target / 'zc0' + subprocess.run([compiler(), '-O1', '-w', '-fwrapv', 'src/tools/zc0.c', '-I' + directory, '-Isrc/core', '-o', str(zc0)], check=True, capture_output=True) + full_source = (target / 'rt.zc').read_text() + Path('src/core/core.zc').read_text() + Path('src/runtime/run.zc').read_text() + source + image = subprocess.run([str(zc0)], input=full_source.encode(), capture_output=True, check=True) + (target / 'genome.bin').write_bytes(image.stdout) + result = subprocess.run(['node', 'tests/drivers/image_run.js', str(target / 'genome.bin'), str(target / 'memmap.h')], text=True, capture_output=True, timeout=45) else: result = subprocess.run(command, input=source, text=True, capture_output=True, timeout=45) assert result.returncode == 0 and result.stdout == expected, (command, result.returncode, result.stdout, result.stderr) -run('''fn main() { - int old_data = data_top - data_low, old_return = return_top - return_low; - put_n(reserve_data(old_data + 1) > old_data); put_c(32); - put_n(reserve_returns(old_return + 1) > old_return); put_c(32); - gc(); put_n(data_node != 0 && return_node != 0); put_c(10); return 0; +run('''namespace; +include rt; +fn main() { + int old_data = rt.data_top - rt.data_low, old_return = rt.return_top - rt.return_low; + rt.put_n(rt.reserve_data(old_data + 1) > old_data); rt.put_c(32); + rt.put_n(rt.reserve_returns(old_return + 1) > old_return); rt.put_c(32); + rt.gc(); rt.put_n(rt.data_node != 0 && rt.return_node != 0); rt.put_c(10); return 0; } ''', '1 1 1\n') -run('''fn main() { - put_n(reserve_data(-1) == -1); put_c(32); put_n(reserve_returns(-1) == -1); put_c(32); - put_n(syscall(9, 6, 0, 0) == -1); put_c(32); - int current = syscall(9, 0, 0, 0); syscall(9, 5, current, 0); - put_n(reserve_data(0x10000000) == -1); put_c(32); gc(); put_n(7); put_c(10); return 0; +run('''namespace; +include rt; +fn main() { + rt.put_n(rt.reserve_data(-1) == -1); rt.put_c(32); rt.put_n(rt.reserve_returns(-1) == -1); rt.put_c(32); + rt.put_n(rt.syscall(9, 6, 0, 0) == -1); rt.put_c(32); + int current = rt.syscall(9, 0, 0, 0); rt.syscall(9, 5, current, 0); + rt.put_n(rt.reserve_data(0x10000000) == -1); rt.put_c(32); rt.gc(); rt.put_n(7); rt.put_c(10); return 0; } ''', '1 1 1 1 7\n') count = 40000 -source = 'fn main() { int total = asm { ' + ' '.join(str(i + 1) for i in range(count)) -source += ' + ' * (count - 1) + ' }; gc(); put_n(total); put_c(32); ' -source += 'put_n(data_node != 0); put_c(10); return 0; }\n' +source = 'namespace; include rt; fn main() { int total = asm { ' + ' '.join(str(i + 1) for i in range(count)) +source += ' + ' * (count - 1) + ' }; rt.gc(); rt.put_n(total); rt.put_c(32); ' +source += 'rt.put_n(rt.data_node != 0); rt.put_c(10); return 0; }\n' run(source, '800020000 1\n', compile_image=True) -run('''unchecked int descend(int n) { - if (!n) { gc(); return 7; } +run('''namespace; +include rt; +unchecked int descend(int n) { + if (!n) { rt.gc(); return 7; } return descend(n - 1) + 1; } -fn main() { put_n(descend(70000)); put_c(32); put_n(return_node != 0); put_c(10); return 0; } +fn main() { rt.put_n(descend(70000)); rt.put_c(32); rt.put_n(rt.return_node != 0); rt.put_c(10); return 0; } ''', '70007 1\n', compile_image=True) print('ok explicit reservation, 40000 operands, and 70000 return addresses: ' + command[0]) diff --git a/tests/drivers/storage_analysis_test.js b/tests/drivers/storage_analysis_test.js index 774fa89..704a844 100644 --- a/tests/drivers/storage_analysis_test.js +++ b/tests/drivers/storage_analysis_test.js @@ -1,8 +1,12 @@ // definite storage findings use the compiler's parser and the editor protocol. +// interpreted hosts (no jit) are far slower; ZC_TEST_TIMEOUT_SCALE stretches every deadline +const TIMEOUT_SCALE = Number(process.env.ZC_TEST_TIMEOUT_SCALE) || 1; const assert = require('assert'); const cp = require('child_process'); const server = process.argv[2]; function check(text, needle) { + // the runtime is an explicit import; it shares the first line so line numbers stay as written + text = 'include rt; ' + text; const uri = 'file:///storage-analysis.zc'; const messages = [{ jsonrpc: '2.0', id: 1, method: 'initialize', params: {} }, { jsonrpc: '2.0', method: 'textDocument/didOpen', params: { textDocument: { uri, version: 1, text } } }]; @@ -13,7 +17,7 @@ function check(text, needle) { } messages.push({ jsonrpc: '2.0', method: 'exit' }); const input = Buffer.concat(messages.map(m => { const b = Buffer.from(JSON.stringify(m)); return Buffer.concat([Buffer.from('Content-Length: ' + b.length + '\r\n\r\n'), b]); })); - const r = cp.spawnSync(server, process.argv.slice(3), { input, timeout: 20000, maxBuffer: 8 << 20 }); + const r = cp.spawnSync(server, process.argv.slice(3), { input, timeout: 20000 * TIMEOUT_SCALE, maxBuffer: 8 << 20 }); assert.strictEqual(r.status, 0, String(r.stderr)); const replies = []; let data = r.stdout; while (data.length) { @@ -27,50 +31,50 @@ function check(text, needle) { } const prefix = 'unchecked fn main() {\n'; for (const [type, length] of [['int', 3], ['u8', 3], ['fixed int[?]', 3], ['u8[?]', 3]]) { - const r = check(prefix + ` let p = new(${type}, ${length});\n let alias = p;\n return alias[${length}];\n}\n`, 'alias['); + const r = check(prefix + ` let p = rt.new(${type}, ${length});\n let alias = p;\n return alias[${length}];\n}\n`, 'alias['); assert.strictEqual(r.warnings.length, 1, JSON.stringify(r.diagnostics)); assert.match(r.warnings[0].message, /out-of-bounds.*index 3.*length 3/); assert.match(r.hover, /size: 3 elements.*lifetime: heap storage; live here/); assert.strictEqual(r.warnings[0].range.start.line, 3); } -const negative = check(prefix + ' let p = new(3); return p[-1];\n}\n'); +const negative = check(prefix + ' let p = rt.new(3); return p[-1];\n}\n'); assert.match(negative.warnings[0]?.message || '', /index -1.*length 3/); -const freed = check(prefix + ' let p = new(int, 3);\n let q = p;\n free(p);\n return q[0];\n}\n', 'q['); +const freed = check(prefix + ' let p = rt.new(int, 3);\n let q = p;\n rt.free(p);\n return q[0];\n}\n', 'q['); assert.strictEqual(freed.warnings.length, 1, JSON.stringify(freed.diagnostics)); assert.match(freed.warnings[0].message, /use after free.*line 4/); assert.match(freed.hover, /size: 3 elements.*lifetime: ended on line 4/); -const dereference = check(prefix + ' let p = new(int); free(p); return *p;\n}\n'); +const dereference = check(prefix + ' let p = rt.new(int); rt.free(p); return *p;\n}\n'); assert.match(dereference.warnings[0]?.message || '', /use after free/); -const field = check('struct item { int value; };\n' + prefix + ' let p = new(item); free(p); return p.value;\n}\n'); +const field = check('struct item { int value; };\n' + prefix + ' let p = rt.new(item); rt.free(p); return p.value;\n}\n'); assert.match(field.warnings[0]?.message || '', /use after free/); const scope = check(prefix + ' int* p;\n { let a[3]; p = a; }\n return p[0];\n}\n', 'p['); assert.match(scope.warnings[0]?.message || '', /use after free.*line 3/); assert.match(scope.hover, /lifetime: ended on line 3/); const safe = [ - prefix + ' let p = new(int, 3); let q = p as u8*; return q[23]; }', - prefix + ' let p = new(int, 3); let q = (u8*)p; return q[23]; }', + prefix + ' let p = rt.new(int, 3); let q = p as u8*; return q[23]; }', + prefix + ' let p = rt.new(int, 3); let q = (u8*)p; return q[23]; }', prefix + ' int n = 2; let p = &n; let q = n; return q[2]; }', - prefix + ' let p = new(int, 3); let ignored = 0 && free(p); return p[0]; }', - prefix + ' let p = new(int, 3); let ignored = 1 || free(p); return p[0]; }', - prefix + ' let p = new(int, 50); return p[(u8)300]; }', - prefix + ' let p = new(int, 50); return p[300 as u8]; }', - prefix + ' let p = new(int, 3); let q = p; q[2] = 7; free(p); return 0; }', - prefix + ' let p = new(3); free(p); p = new(3); return p[0]; }', - 'unchecked fn unknown(int* p) {}\n' + prefix + ' let p = new(int, 3); unknown(p); return p[9]; }', - 'fn maybe(int n) { return n; }\n' + prefix + ' let p = new(int, 3); return p[maybe(9)]; }', - prefix + ' let p = new(int, 3); if (1) free(p); return p[0]; }', - prefix + ' let p = new(int, 3); while (0) free(p); return p[0]; }', - prefix + ' let p = new(int, 3); let f = fn() { free(p); return 0; }; return p[0]; }', - prefix + ' let p = new(int[?], 0); push(p, 9); return p[0]; }', + prefix + ' let p = rt.new(int, 3); let ignored = 0 && rt.free(p); return p[0]; }', + prefix + ' let p = rt.new(int, 3); let ignored = 1 || rt.free(p); return p[0]; }', + prefix + ' let p = rt.new(int, 50); return p[(u8)300]; }', + prefix + ' let p = rt.new(int, 50); return p[300 as u8]; }', + prefix + ' let p = rt.new(int, 3); let q = p; q[2] = 7; rt.free(p); return 0; }', + prefix + ' let p = rt.new(3); rt.free(p); p = rt.new(3); return p[0]; }', + 'unchecked fn unknown(int* p) {}\n' + prefix + ' let p = rt.new(int, 3); unknown(p); return p[9]; }', + 'fn maybe(int n) { return n; }\n' + prefix + ' let p = rt.new(int, 3); return p[maybe(9)]; }', + prefix + ' let p = rt.new(int, 3); if (1) rt.free(p); return p[0]; }', + prefix + ' let p = rt.new(int, 3); while (0) rt.free(p); return p[0]; }', + prefix + ' let p = rt.new(int, 3); let f = fn() { rt.free(p); return 0; }; return p[0]; }', + prefix + ' let p = rt.new(int[?], 0); push(p, 9); return p[0]; }', ]; for (const source of safe) assert.deepStrictEqual(check(source).warnings, [], source); -const preserved = check('int read(int* p) { return p[0]; }\n' + prefix + ' let p = new(int, 3); read(p); return p[9]; }'); +const preserved = check('int read(int* p) { return p[0]; }\n' + prefix + ' let p = rt.new(int, 3); read(p); return p[9]; }'); assert.match(preserved.warnings[0]?.message || '', /index 9.*length 3/); const unknown = check('int read(int* p) { return p[0]; }\nfn main() { int n = 1; return read(&n); }\n', 'p['); assert.match(unknown.hover, /size: unknown.*lifetime: unknown/); const stack = check('fn main() { let a[3]; return a[2]; }\n', 'a['); assert.match(stack.hover, /size: 3 elements.*lifetime: stack storage/); -const replacement = check('fn main() {\n let owner = new(1), replacement = new(2);\n free(owner); owner = replacement;\n return owner[0];\n}\n', 'replacement;'); +const replacement = check('fn main() {\n let owner = rt.new(1), replacement = rt.new(2);\n rt.free(owner); owner = replacement;\n return owner[0];\n}\n', 'replacement;'); assert.ok(!replacement.diagnostics.some(d => d.severity === 1), JSON.stringify(replacement.diagnostics)); assert.match(replacement.hover, /moved: replaces a released owner in `owner` on line 3/); console.log('ok definite bounds, free and scope lifetimes, alias hovers, and conservative unknown cases'); diff --git a/tests/drivers/thread_context_test.py b/tests/drivers/thread_context_test.py index fa3f144..b237acd 100644 --- a/tests/drivers/thread_context_test.py +++ b/tests/drivers/thread_context_test.py @@ -5,7 +5,7 @@ import argparse import subprocess parser = argparse.ArgumentParser() -parser.add_argument('vm', nargs='?', default='bin/zy') +parser.add_argument('vm', nargs='?', default='build/bin/zy') parser.add_argument('--case', default='') parser.add_argument('--library', default='') parser.add_argument('--backend', choices=('native', 'micro'), default='native') diff --git a/tests/drivers/toolchain_lsp_contract_test.js b/tests/drivers/toolchain_lsp_contract_test.js index 0d1b515..2872282 100644 --- a/tests/drivers/toolchain_lsp_contract_test.js +++ b/tests/drivers/toolchain_lsp_contract_test.js @@ -1,4 +1,6 @@ // pending requirements have explicit protocol oracles, selectable in small groups. +// interpreted hosts (no jit) are far slower; ZC_TEST_TIMEOUT_SCALE stretches every deadline +const TIMEOUT_SCALE = Number(process.env.ZC_TEST_TIMEOUT_SCALE) || 1; const assert = require('assert'); const cp = require('child_process'); const fs = require('fs'); @@ -21,7 +23,7 @@ function run(text, requests = [], uri = 'file:///contracts.zc', files = []) { for (const message of messages) proc.stdin.write(frame(message)); output = Buffer.concat(chunks); } else { - const r = cp.spawnSync(host, process.argv.slice(3), { input: Buffer.concat(messages.map(frame)), timeout: 60000, maxBuffer: 32 << 20 }); + const r = cp.spawnSync(host, process.argv.slice(3), { input: Buffer.concat(messages.map(frame)), timeout: 60000 * TIMEOUT_SCALE, maxBuffer: 32 << 20 }); assert.strictEqual(r.status, 0, String(r.stderr)); output = r.stdout; } const replies = []; let data = output; diff --git a/tests/drivers/tools_contract_test.py b/tests/drivers/tools_contract_test.py index df02b2b..6a98678 100644 --- a/tests/drivers/tools_contract_test.py +++ b/tests/drivers/tools_contract_test.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 """bounded accept/reject tests for standalone build tools and generated images.""" import argparse -import re import struct import subprocess import tempfile @@ -17,32 +16,33 @@ def command(argv, data=None): return r if args.group == 'image': - source = Path('src/core/genome.h').read_text() - body = source.split('{', 1)[1].split('}', 1)[0] - words = [int(x) for x in re.findall(r'-?\d+', body)] + image = Path('build/core/genome.bin').read_bytes() + words = list(struct.unpack('<%dq' % (len(image) // 8), image)) pack = lambda xs: b''.join(struct.pack('<q', x) for x in xs) - good = command(['bin/mkgenome'], pack(words)) - assert good.returncode == 0 and f'#define GLEN {len(words)}'.encode() in good.stdout - for data in [b'', b'x', pack(words[:6]), pack(words) + b'x']: - r = command(['bin/mkgenome'], data) - assert r.returncode != 0 and b'incomplete' in r.stderr + good = command(['build/bin/mkgenome'], image) + assert good.returncode == 0 and good.stdout == image + for data in [b'', b'x', pack(words[:6]), image + b'x']: + r = command(['build/bin/mkgenome'], data) + assert r.returncode != 0 and b'incomplete' in r.stderr and not r.stdout for index in range(3): changed = words[:]; changed[index] ^= 2 - r = command(['bin/mkgenome'], pack(changed)) - assert r.returncode != 0 and b'not a genome' in r.stderr - print('ok mkgenome roundtrip, truncation, alignment and map-header mutation contracts') + r = command(['build/bin/mkgenome'], pack(changed)) + assert r.returncode != 0 and b'not a genome' in r.stderr and not r.stdout + r = command(['build/bin/mkgenome'], b'zc: line 1: something is wrong\nzc: 1 error\n') + assert r.returncode != 0 and b'something is wrong' in r.stderr and not r.stdout + print('ok mkgenome roundtrip, truncation, alignment, map-header mutation and diagnostic contracts') elif args.group == 'map': for preset in ['default', 'small', 'run', 'lsp', 'fixed']: with tempfile.TemporaryDirectory(prefix='tinyvm preset ') as directory: - r = command(['bin/genmap', '--preset', preset, '-o', directory]) + r = command(['build/bin/genmap', '--preset', preset, '-o', directory]) assert r.returncode == 0, (preset, r.stderr) assert (Path(directory) / 'memmap.h').is_file() - assert command(['bin/genmap', '--preset', 'default', '--check']).returncode == 0 + assert command(['build/bin/genmap', '--preset', 'default', '--check']).returncode == 0 for options in [['--words', '-1'], ['--heap', '0'], ['--data', '0'], ['--symbols', '-1'], ['--total', '999999999999999999999'], ['--preset', 'imaginary']]: - r = command(['bin/genmap', *options, '--check']) + r = command(['build/bin/genmap', *options, '--check']) assert r.returncode != 0, options with tempfile.TemporaryDirectory(prefix='tinyvm map ') as directory: - r = command(['bin/genmap', '--preset', 'small', '--locals', '32', '--symbols', '64', '-o', directory]) + r = command(['build/bin/genmap', '--preset', 'small', '--locals', '32', '--symbols', '64', '-o', directory]) assert r.returncode == 0, r.stderr for name in ['memmap.h', 'rt.zc', 'compiler.zc', 'map.txt']: assert (Path(directory) / name).is_file(), name @@ -50,7 +50,7 @@ elif args.group == 'map': else: r = command(['bash', '-n', 'tinyvm.sh']) assert r.returncode == 0, r.stderr - for script in ['src/tools/automap.sh', 'src/tools/mklsp.sh', 'src/tools/mklspwasm.sh', 'src/tools/mkvsix.sh', 'src/tools/embed.sh']: + for script in ['src/tools/automap.sh', 'src/tools/mklsp.sh', 'src/tools/mklspwasm.sh', 'src/tools/mkvsix.sh', 'src/tools/findcc.sh', 'src/tools/bootstrap-check.sh']: assert command(['bash', '-n', script]).returncode == 0 assert command(['bash', 'tinyvm.sh', 'imaginary-command']).returncode != 0 assert Path('tinyvm.bat').is_file() diff --git a/tests/drivers/wasm_test.js b/tests/drivers/wasm_test.js index e4f7ecb..56f32a2 100644 --- a/tests/drivers/wasm_test.js +++ b/tests/drivers/wasm_test.js @@ -1,9 +1,9 @@ -// runs programs on the webassembly build (src/web/zc.wasm) and compares with the expected output. +// runs programs on the webassembly build (build/web/zc.wasm, from make web) and compares with the expected output. // usage: node tests/drivers/wasm_test.js [path/to/zc.wasm] const fs = require('fs'); const ZC = require('../../src/web/zc.js'); (async () => { - const zc = await ZC.load(fs.readFileSync(process.argv[2] || __dirname + '/../../src/web/zc.wasm')); + const zc = await ZC.load(fs.readFileSync(process.argv[2] || __dirname + '/../../build/web/zc.wasm')); const cases = [ ['examples/hello.zc', 'hello, world!'], ['examples/fact.zc', '1101110101111100000000\n3628800\n'], diff --git a/tests/drivers/word_literals_test.py b/tests/drivers/word_literals_test.py index a2d230d..cfac811 100644 --- a/tests/drivers/word_literals_test.py +++ b/tests/drivers/word_literals_test.py @@ -5,7 +5,7 @@ import subprocess values = [0, 1, (1 << 62) - 1, 1 << 62, (1 << 63) - 1, -(1 << 62), -(1 << 62) - 1, -(1 << 63), -1] source = 'fn main() {\n' + ''.join(f'put_n({value}); put_c(10);\n' for value in values) + 'return 0; }\n' expected = ''.join(str(value) + '\n' for value in values) -for command in [['bin/zy'], ['bin/zym'], ['node', 'src/js/tinyvm.js']]: +for command in [['build/bin/zy'], ['build/bin/zym'], ['node', 'src/js/tinyvm.js']]: result = subprocess.run(command, input=source, text=True, capture_output=True, timeout=20) assert result.returncode == 0 and result.stdout == expected, (command, result.stdout, result.stderr) print('ok full-width positive and negative word literals: ' + command[0]) @@ -13,7 +13,7 @@ source = '''immediate fn add_word_operator() { next(); add_binop("**", 11, "4611 add_word_operator fn main() { put_n(0 ** 0); put_c(10); return 0; } ''' -for command in [['bin/zy'], ['bin/zym'], ['node', 'src/js/tinyvm.js']]: +for command in [['build/bin/zy'], ['build/bin/zym'], ['node', 'src/js/tinyvm.js']]: result = subprocess.run(command, input=source, text=True, capture_output=True, timeout=20) assert result.returncode == 0 and result.stdout == '4611686018427387904\n', (command, result.stdout, result.stderr) print('ok full-width word in a stored operator sequence: ' + command[0]) diff --git a/tests/test.sh b/tests/test.sh index 16aa1a9..fd2bcd3 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -1,9 +1,14 @@ #!/bin/bash # full check: bootstraps the compiler from zc0, verifies the fixpoint, then runs # the examples and tests on the portable interpreter (host), the microcore, the -# arm64 jit under qemu-user, and the llvm backend. needs gcc. the jit half -# needs aarch64-linux-gnu-gcc and qemu-aarch64 (linux-user, not qemu-system). -# the llvm half needs llvm-config and libLLVM. src/baremetal/run.sh boots the +# arm64 jit under qemu-user, and the llvm backend. needs a c compiler with c23 +# #embed (src/tools/findcc.sh picks one; CC overrides it). the jit half needs +# aarch64-linux-gnu-gcc and qemu-aarch64 (linux-user, not qemu-system). +# +# the suite reads nothing from build/: it lays out the default map, bootstraps +# the compiler and builds every core into its own scratch directory, so it runs +# the same from a clean checkout as after `make`. +# the llvm half needs llvm-config (LLVM_CONFIG names another) and libLLVM. src/baremetal/run.sh boots the # microcore on qemu-system-riscv64 and qemu-system-aarch64 when those cross # compilers and system emulators are installed. each of these is skipped # when its tools are missing. @@ -43,26 +48,36 @@ if [ -n "$SELECT" ]; then fi ROOT=$T -# built here rather than inside a section: interp runs the program list on -# it, and the run-only and bare-metal sections read it as the reference. -gcc -pthread -O2 -w -fwrapv -DJTYPE=int src/core/core.c src/core/io_linux.c -Isrc/core -Ibuild/core -o $T/zy || exit 1 -gcc -O2 -w -fwrapv src/tools/genmap.c -Isrc/core -Ibuild/core -o $T/genmap || exit 1 +CC=${CC:-$(sh src/tools/findcc.sh)} || exit 1 +export CC +# the language server runs on the interpreter unless the host has the arm64 jit, and a +# check of a large document then takes many seconds: stretch the drivers' deadlines +if [ -z "${ZC_TEST_TIMEOUT_SCALE:-}" ]; then case "$(uname -m)" in arm64|aarch64) ZC_TEST_TIMEOUT_SCALE=1;; *) ZC_TEST_TIMEOUT_SCALE=8;; esac; fi +export ZC_TEST_TIMEOUT_SCALE +# the default map, generated as make does it, and the tools built against it +$CC -O2 -w -fwrapv src/tools/genmap.c -o $T/genmap || exit 1 +$T/genmap --preset default --check -o $T/map >$T/genmap.log 2>&1 || { cat $T/genmap.log; echo "FAIL the default map does not match the checked-in sources"; exit 1; } +MAP=$T/map +$CC -O1 -w -fwrapv src/tools/zc0.c -Isrc/core -I$MAP -o $T/zc0 && $CC -O1 -w -fwrapv src/tools/mkgenome.c -I$MAP -o $T/mkg || exit 1 # the c helper for tests/programs/structval.zc: structs by value, loaded with cload -mkdir -p build && gcc -shared -fPIC -w tests/data/sbv.c -o build/sbv.so || exit 1 +mkdir -p build && $CC -shared -fPIC -w tests/data/sbv.c -o build/sbv.so || exit 1 selfc(){ (cat src/tools/build.zc; printf '`'; src) | $1 > $2; } -# bootstrap: zc0 (c) -> g0, g0 compiles compiler.zc -> g1, g1 compiles it -> g2 -gcc -O1 -w -fwrapv src/tools/zc0.c -Isrc/core -Ibuild/core -o $T/zc0 && gcc -O1 -w -fwrapv src/tools/mkgenome.c -Isrc/core -Ibuild/core -o $T/mkg || exit 1 +# bootstrap: zc0 (c) -> g0, g0 compiles compiler.zc -> g1, g1 compiles it -> g2. +# each stage's core embeds the image in its own directory mkdir $T/s0 $T/s1 -cp src/core/core.c src/core/io_linux.c src/core/memmap.h src/core/fops.h src/core/bops.h src/core/stack.h $T/s0/; cp src/core/core.c src/core/io_linux.c src/core/memmap.h src/core/fops.h src/core/bops.h src/core/stack.h $T/s1/ -src | $T/zc0 > $T/s0/genome.h || exit 1 -gcc -pthread -O2 -w -fwrapv -DJTYPE=int $T/s0/core.c $T/s0/io_linux.c -o $T/zy0 -selfc $T/zy0 $T/g1.bin; $T/mkg < $T/g1.bin > $T/s1/genome.h -gcc -pthread -O2 -w -fwrapv -DJTYPE=int $T/s1/core.c $T/s1/io_linux.c -o $T/zy1 +src | $T/zc0 > $T/s0/g0.bin && $T/mkg < $T/s0/g0.bin > $T/s0/genome.bin || exit 1 +$CC -pthread -O2 -w -fwrapv -DJTYPE=int src/core/core.c src/core/io_linux.c -Isrc/core -I$MAP --embed-dir=$T/s0 -o $T/zy0 || exit 1 +selfc $T/zy0 $T/g1.bin; $T/mkg < $T/g1.bin > $T/s1/genome.bin || exit 1 +$CC -pthread -O2 -w -fwrapv -DJTYPE=int src/core/core.c src/core/io_linux.c -Isrc/core -I$MAP --embed-dir=$T/s1 -o $T/zy1 || exit 1 selfc $T/zy1 $T/g2.bin cmp -s $T/g1.bin $T/g2.bin && chk 1 1 "fixpoint: the compiler compiles itself to the same $(($(wc -c < $T/g1.bin) / 8)) words" || chk 0 1 "fixpoint" -cmp -s $T/s1/genome.h build/core/genome.h && chk 1 1 "genome.h is up to date" || chk 0 1 "genome.h is stale (run make genome)" +# every core below carries the fixpoint genome: GI is the include and embed path for it +GI="-Isrc/core -I$MAP --embed-dir=$T/s1" +# built here rather than inside a section: interp runs the program list on +# it, and the run-only and bare-metal sections read it as the reference. +$CC -pthread -O2 -w -fwrapv -DJTYPE=int src/core/core.c src/core/io_linux.c $GI -o $T/zy || exit 1 chk "$($T/zc0 < tests/programs/zc0typed.zc | md5sum)" "$($T/zc0 < tests/programs/zc0plain.zc | md5sum)" "zc0 reads types and throws them away: typed and plain sources give one image" for n in $SECS; do @@ -80,7 +95,7 @@ run(){ # $1 = vm command, $2 = label chk "$( (cat tests/programs/backtick.zc; printf '`xyz') | $vm)" "3 3 96 39 xyz" "$l a backtick in a comment, string or char literal does not end the source" chk "$($vm < examples/selfpatch.zc)" "ABC" "$l self-patching code" chk "$($vm < examples/lists.zc | tr '\n' '/')" "[0 1 4 9 16 25]/live: 7/after the block: 0/" "$l ownership frees everything" - chk "$( (cat examples/repl.zc; printf '`fn sq(x) { return x * x; }`put_n(sq(7)); put_c(10);`put_s("hi\\n");') | $vm | tr '\n' '/')" "49/hi/" "$l repl on the resident compiler" + chk "$( (cat examples/repl.zc; printf '`fn sq(x) { return x * x; }`rt.put_n(sq(7)); rt.put_c(10);`rt.put_s("hi\\n");') | $vm | tr '\n' '/')" "49/hi/" "$l repl on the resident compiler" chk "$($vm < examples/typed.zc | tr '\n' '/')" "last: 4/point is 2 words: x int, y int/vec<point*> is 3 words: len int, cap int, items point**/push is bool(vec<T>*, T.d)/the heap says v is a vec<point*>, and v.items[0] is a point/" "$l typed example" chk "$($vm < examples/wiring.zc)" "16 1234 25 4/9" "$l enhancement example" chk "$($vm < tests/programs/allocfast.zc)" "0 0 499500 0" "$l constant-size allocation fast path" @@ -111,7 +126,7 @@ run(){ # $1 = vm command, $2 = label chk "$($vm < tests/programs/enumscope.zc)" "0 5 6 blue" "$l enum members are reached as Enum.MEMBER, also in a case" chk "$($vm < tests/programs/enumtype.zc)" "6 5 1" "$l typed enums: members take their enum, an enum becomes an int" chk "$($vm < tests/programs/tagenum.zc)" "27 20 0 0 1 2" "$l tagged enums: members carry fields, constructors build them, a case binds the fields" - chk "$($vm < tests/programs/tagenum_warn.zc)" "$(printf 'zc: line 5: warning: this switch doesn'"'"'t cover rect\n0')" "$l a switch on a tagged enum that misses a member warns, on the switch's line" + chk "$($vm < tests/programs/tagenum_warn.zc)" "$(printf 'zc: line 8: warning: this switch doesn'"'"'t cover rect\n0')" "$l a switch on a tagged enum that misses a member warns, on the switch's line" chk "$($vm < tests/programs/tagenum_two.zc)" "3 8 12" "$l two tagged enums with the same member name, and a case in an o3 loop" chk "$($vm < tests/programs/o3push.zc | md5sum)" "$(md5sum < tests/programs/o3push.expected)" "$l push and pop of a growable array are inline in o3" chk "$($vm < tests/programs/o3copy.zc)" "520507 1005" "$l o3 copies of a loop variable read the source instead" @@ -136,11 +151,11 @@ hello world!42 1 0" "$l capture.start() collects output and end() hands back the string" chk "$($vm < tests/programs/sys.zc)" "2 zy time" "$l syscalls: write, read, time" - chk "$($vm < tests/programs/memleak.zc)" $'leak: line 5 3 words\n1' "$l memdebug names a live allocation" - chk "$($vm < tests/programs/uaf.zc)" $'zc: line 7: warning: use after free: this storage\'s lifetime ended on line 6\nzy: use after free from line 4' "$l memdebug: use after free" - chk "$($vm < tests/programs/uaf_in.zc)" "zy: use after free from line 4" "$l memdebug: use after free through an interior pointer" - chk "$($vm < tests/programs/doublefree.zc)" $'zy: double free from line 4\nok' "$l memdebug: double free is reported and does nothing" - chk "$($vm < tests/programs/scrub.zc)" $'zc: line 6: warning: use after free: this storage\'s lifetime ended on line 5\n57005' "$l memdebug scrub poisons a freed payload" + chk "$($vm < tests/programs/memleak.zc)" $'leak: line 8 3 words\n1' "$l memdebug names a live allocation" + chk "$($vm < tests/programs/uaf.zc)" $'zc: line 10: warning: use after free: this storage\'s lifetime ended on line 9\nzy: use after free from line 7' "$l memdebug: use after free" + chk "$($vm < tests/programs/uaf_in.zc)" "zy: use after free from line 7" "$l memdebug: use after free through an interior pointer" + chk "$($vm < tests/programs/doublefree.zc)" $'zy: double free from line 7\nok' "$l memdebug: double free is reported and does nothing" + chk "$($vm < tests/programs/scrub.zc)" $'zc: line 9: warning: use after free: this storage\'s lifetime ended on line 8\n57005' "$l memdebug scrub poisons a freed payload" chk "$($vm < tests/programs/interior.zc)" "0" "$l an interior pointer keeps a quarantined block" chk "$($vm < tests/programs/wide.zc)" "1099511627776 1 1 1 -1" "$l 64-bit shifts and equality" chk "$($vm < tests/programs/float.zc)" "4.000000 4.500000 3.000000 2.500000 2.500000 0.001500 0.100000 -0.125000 inf -inf nan 1 1 1 0 0 3 -3 3.000000 -2.000000 2" "$l floating point" @@ -169,7 +184,7 @@ ABC" "$l o3 lifts byte loads and stores" chk "$($vm < examples/bf.zc 2>&1 | md5sum | cut -c1-32)" "223570983752b17f7ece32e93d5e19e4" "$l example: brainfuck compiled at runtime" chk "$($vm < examples/dump.zc 2>&1 | md5sum | cut -c1-32)" "757774b00f6b3cd0bfbcba66448a8619" "$l example: struct printer through reflection" chk "$($vm < examples/tree.zc 2>&1 | md5sum | cut -c1-32)" "b86a2e4e9e31acc294a4ddf86be863f2" "$l example: owning tree frees itself" - chk "$($vm < examples/lang.zc 2>&1 | md5sum | cut -c1-32)" "6b234dc8a0a1aa0fb02eb55a6ff7ccc0" "$l example: pipe operator, unless, assert, times" + chk "$($vm < examples/lang.zc 2>&1 | md5sum | cut -c1-32)" "$(printf '22\none isn'"'"'t bigger than two\n***\nassertion failed on line 50\n' | md5sum | cut -c1-32)" "$l example: pipe operator, unless, assert, times" chk "$($vm < examples/staged.zc 2>&1 | md5sum | cut -c1-32)" "6f790a125c48b0cc7a897cee73ee5627" "$l example: staged code compiled while running" chk "$($vm < examples/quine.zc 2>&1 | md5sum | cut -c1-32)" "$(md5sum < examples/quine.zc | cut -c1-32)" "$l example: quine" chk "$($vm < examples/primes.zc 2>&1 | md5sum | cut -c1-32)" "814faffc70dad0ae520c0671c33e73d4" "$l example: o3 prime sieve" @@ -200,7 +215,7 @@ ABC" "$l o3 lifts byte loads and stores" } s_interp() { T=$ROOT/interp - gcc -pthread -O2 -w -fwrapv -DJTYPE=int src/core/core.c src/core/io_linux.c -Isrc/core -Ibuild/core -o $T/zy + $CC -pthread -O2 -w -fwrapv -DJTYPE=int src/core/core.c src/core/io_linux.c $GI -o $T/zy run $T/zy "interp" python3 tests/drivers/array_methods_test.py $T/zy > $T/arrays.out 2>&1; r=$? grep -v '^ok' $T/arrays.out @@ -218,14 +233,14 @@ s_interp() { chk "$($T/zy < tests/programs/infer_return.zc)" "3" "an owned return with no .u is inferred from the body" chk "$($T/zy < tests/programs/discard_m.zc)" "7" "m* discards an unnamed u result" chk "$($T/zy < tests/programs/closure.zc | tr '\n' ' ')" "31 35 42 2 7 7 109 30 7 16 33 20 101 " "closures: calls through values, captures, nesting, returns" - chk "$($T/zy < tests/programs/decl_undefined.zc 2>&1 | head -1)" "zc: line 1: helper is used but never defined" "a declared function nothing defines fails a build" + chk "$($T/zy < tests/programs/decl_undefined.zc 2>&1 | head -1)" "zc: line 3: helper is used but never defined" "a declared function nothing defines fails a build" if [ "$(uname -s)" = Linux ]; then chk "$($T/zy < tests/programs/rawsys.zc)" "-38" "Host.raw: an unknown syscall number gives -ENOSYS"; else chk "$($T/zy < tests/programs/rawsys.zc)" "-1" "Host.raw: unavailable on this host"; fi if [ "$(uname -m)" = x86_64 ]; then chk "$($T/zy < tests/programs/rawpid.zc)" "1" "Host.raw: getpid (39 on x86-64) answers"; fi } s_micro() { T=$ROOT/micro - gcc -pthread -DZY_THREADS -O2 -w -fwrapv src/micro/micro.c src/micro/port_linux.c -Isrc/core -Isrc/micro -o $T/zym || exit 1 + $CC -pthread -DZY_THREADS -O2 -w -fwrapv src/micro/micro.c src/micro/port_linux.c $GI -Isrc/micro -o $T/zym || exit 1 run $T/zym "micro" python3 tests/drivers/array_methods_test.py $T/zym > $T/arrays.out 2>&1; r=$? grep -v '^ok' $T/arrays.out @@ -233,8 +248,9 @@ s_micro() { python3 tests/drivers/actor_test.py $T/zym > $T/actor.out 2>&1; r=$? grep -v '^ok' $T/actor.out chk $r 0 "micro isolated actor acceptance, rejection and regression fixtures" - if command -v node >/dev/null; then - python3 tests/drivers/fuzz_compiler.py $ROOT/zy $T/zym --js > $T/fuzz.out 2>&1; r=$? + if command -v node >/dev/null && command -v tsc >/dev/null && node src/js/mklayout.js --map $MAP --out $T/jsvm >/dev/null \ + && cp $ROOT/s1/genome.bin $T/jsvm/ && tsc src/js/tinyvm.ts --target es2020 --lib es2020,dom --module commonjs --outDir $T/jsvm; then + python3 tests/drivers/fuzz_compiler.py $ROOT/zy $T/zym --js $T/jsvm/tinyvm.js > $T/fuzz.out 2>&1; r=$? else python3 tests/drivers/fuzz_compiler.py $ROOT/zy $T/zym > $T/fuzz.out 2>&1; r=$? fi @@ -245,8 +261,8 @@ s_micro() { chk $r 0 "malformed compiler input fuzz and host agreement" selfc $T/zym $T/gm.bin cmp -s $T/g1.bin $T/gm.bin && chk 1 1 "microcore self-compile matches" || chk 0 1 "microcore self-compile matches" - gcc -ffreestanding -w -c src/micro/micro.c -Isrc/core -Isrc/micro -o $T/micro.o && gcc -ffreestanding -w -c src/micro/port_stub.c -Isrc/core -Isrc/micro -o $T/stub.o && chk 1 1 "microcore and the stub port compile freestanding" || chk 0 1 "microcore and the stub port compile freestanding" - gcc -pthread -O2 -w -fwrapv -DJTYPE=int -DZY_SOFTFLOAT -DZYINTERP src/core/core.c src/core/io_linux.c -Isrc/core -Ibuild/core -o $T/zysoft || exit 1 + $CC -ffreestanding -w -c src/micro/micro.c $GI -Isrc/micro -o $T/micro.o && $CC -ffreestanding -w -c src/micro/port_stub.c $GI -Isrc/micro -o $T/stub.o && chk 1 1 "microcore and the stub port compile freestanding" || chk 0 1 "microcore and the stub port compile freestanding" + $CC -pthread -O2 -w -fwrapv -DJTYPE=int -DZY_SOFTFLOAT -DZYINTERP src/core/core.c src/core/io_linux.c $GI -o $T/zysoft || exit 1 chk "$($T/zysoft < tests/programs/float.zc)" "4.000000 4.500000 3.000000 2.500000 2.500000 0.001500 0.100000 -0.125000 inf -inf nan 1 1 1 0 0 3 -3 3.000000 -2.000000 2" "soft-float fallback matches the hardware line" chk "$($T/zysoft < tests/programs/ftruth.zc)" "00111000110107" "soft-float negative zero is false" chk "$($T/zysoft < tests/programs/fo3.zc)" "5997 5997 4.000000 nan 0 3.250000 1 0 0" "soft-float o3 float ops match" @@ -256,7 +272,7 @@ s_micro() { s_small() { T=$ROOT/small # smaller machines. addresses are literals, so each size is its own genome. - gcc -O2 -w -fwrapv src/tools/genmap.c -Isrc/core -Ibuild/core -o $T/genmap || exit 1 + $CC -O2 -w -fwrapv src/tools/genmap.c $GI -o $T/genmap || exit 1 $T/genmap --preset default --check >/dev/null && chk 1 1 "default map matches the checked-in sources" || chk 0 1 "default map matches the checked-in sources" $T/genmap --preset small -o $T/small || exit 1 $T/genmap --mode compiler --name small --heap-lo 8192 --heap 20480 --types 8192 \ @@ -265,13 +281,13 @@ s_small() { cmp -s $T/small/memmap.h $T/small-flags/memmap.h && cmp -s $T/small/rt.zc $T/small-flags/rt.zc \ && cmp -s $T/small/compiler.zc $T/small-flags/compiler.zc && cmp -s $T/small/map.txt $T/small-flags/map.txt \ && chk 1 1 "compiler flags reproduce the small map" || chk 0 1 "compiler flags reproduce the small map" - cp src/tools/zc0.c src/core/fops.h src/core/bops.h src/core/stack.h $T/small/ - gcc -O1 -w -fwrapv $T/small/zc0.c -o $T/zc0s || exit 1 - cat $T/small/rt.zc $T/small/compiler.zc | $T/zc0s > $T/small/genome.h || exit 1 - cp src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/micro/port_stub.c src/core/core.c src/core/io_linux.c src/core/fops.h src/core/bops.h src/core/stack.h $T/small/ - gcc -pthread -DZY_THREADS -O2 -w -fwrapv $T/small/micro.c $T/small/port_linux.c -o $T/zyms || exit 1 - gcc -pthread -O2 -w -fwrapv -DJTYPE=int $T/small/core.c $T/small/io_linux.c -o $T/zys || exit 1 - gcc -ffreestanding -w -c $T/small/micro.c -o $T/micro_s.o && gcc -ffreestanding -w -c $T/small/port_stub.c -o $T/stub_s.o && chk 1 1 "small microcore compiles freestanding" || chk 0 1 "small microcore compiles freestanding" + cp src/tools/zc0.c src/core/fops.h src/core/bops.h src/core/stack.h src/core/genome.h $T/small/ + $CC -O1 -w -fwrapv $T/small/zc0.c -o $T/zc0s || exit 1 + cat $T/small/rt.zc src/core/core.zc $T/small/compiler.zc | $T/zc0s > $T/small/genome.bin || exit 1 + cp src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/micro/port_stub.c src/core/core.c src/core/io_linux.c src/core/fops.h src/core/bops.h src/core/stack.h src/core/genome.h $T/small/ + $CC -pthread -DZY_THREADS -O2 -w -fwrapv $T/small/micro.c $T/small/port_linux.c -o $T/zyms || exit 1 + $CC -pthread -O2 -w -fwrapv -DJTYPE=int $T/small/core.c $T/small/io_linux.c -o $T/zys || exit 1 + $CC -ffreestanding -w -c $T/small/micro.c -o $T/micro_s.o && $CC -ffreestanding -w -c $T/small/port_stub.c -o $T/stub_s.o && chk 1 1 "small microcore compiles freestanding" || chk 0 1 "small microcore compiles freestanding" sameout() { # $1 = other vm, $2 = label, $3 = source file local a b a=$($T/zy < "$3" 2>&1 | tr '\n' '/') @@ -309,15 +325,15 @@ s_runonly() { # its own run map: the run-only image is built against this one, and the # small-machine section lays out its own rather than sharing this $T/genmap --preset run -o $T/run || exit 1 - cp src/tools/zc0.c src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/micro/port_stub.c src/core/fops.h src/core/bops.h src/core/stack.h $T/run/ - gcc -O1 -w -fwrapv $T/run/zc0.c -o $T/zc0r || exit 1 + cp src/tools/zc0.c src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/micro/port_stub.c src/core/fops.h src/core/bops.h src/core/stack.h src/core/genome.h $T/run/ + $CC -O1 -w -fwrapv $T/run/zc0.c -o $T/zc0r || exit 1 runonly() { # $1 label, $2 source. the program is already in the image, so it reads no source. # the map is sized to the program: src/tools/automap.sh lays it out for the image's own length local a b d=$T/run-$1 input=$2 if [ "$1" = gc ]; then input=$T/collector-progress.zc; sed 's/f >= 990/f > 0/' "$2" > "$input"; fi GENMAP=$T/genmap bash src/tools/automap.sh run $d src/runtime/run.zc "$input" >/dev/null || { chk 0 1 "run $1 builds"; return; } - cp src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/micro/port_stub.c src/core/fops.h src/core/bops.h src/core/stack.h $d/ - gcc -pthread -DZY_THREADS -O2 -w -fwrapv $d/micro.c $d/port_linux.c -o $T/zymr || { chk 0 1 "run $1 links"; return; } + cp src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/micro/port_stub.c src/core/fops.h src/core/bops.h src/core/stack.h src/core/genome.h $d/ + $CC -pthread -DZY_THREADS -O2 -w -fwrapv $d/micro.c $d/port_linux.c -o $T/zymr || { chk 0 1 "run $1 links"; return; } a=$($T/zy < "$input" 2>&1 | tr '\n' '/') b=$($T/zymr </dev/null 2>&1 | tr '\n' '/') chk "$b" "$a" "run $1" @@ -334,12 +350,12 @@ s_runonly() { runonly strings tests/programs/strings.zc runonly strings-ex examples/strings.zc runonly share tests/programs/share.zc - d=$T/run-echo; GENMAP=$T/genmap bash src/tools/automap.sh run $d src/runtime/run.zc examples/echo.zc >/dev/null && cp src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/micro/port_stub.c src/core/fops.h src/core/bops.h src/core/stack.h $d/ \ - && gcc -pthread -DZY_THREADS -O2 -w -fwrapv $d/micro.c $d/port_linux.c -o $T/zymr \ + d=$T/run-echo; GENMAP=$T/genmap bash src/tools/automap.sh run $d src/runtime/run.zc examples/echo.zc >/dev/null && cp src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/micro/port_stub.c src/core/fops.h src/core/bops.h src/core/stack.h src/core/genome.h $d/ \ + && $CC -pthread -DZY_THREADS -O2 -w -fwrapv $d/micro.c $d/port_linux.c -o $T/zymr \ && chk "$(printf 'abc\n' | $T/zymr | tr '\n' '/')" "$( (cat examples/echo.zc; printf '`abc\n') | $T/zy | tr '\n' '/')" "run echo" \ || chk 0 1 "run echo" # zcc: a program becomes a static binary that runs without stdin and with a held-open pipe - if CC=gcc src/tools/zcc -o $T/zcc-hello examples/hello.zc >/dev/null 2>&1; then + if src/tools/zcc -o $T/zcc-hello examples/hello.zc >/dev/null 2>&1; then chk "$($T/zcc-hello < /dev/null)" "hello, world!" "zcc: hello, static" chk "$(sleep 2 | timeout 5 $T/zcc-hello; echo $?)" "hello, world! 0" "zcc: a program that never reads stdin does not wait for it" @@ -348,7 +364,7 @@ s_runonly() { printf 'fn main() {\n\tput_n(1\n\treturn 0;\n}\n' > $T/zcc-bad.zc src/tools/zcc -o $T/zcc-bad $T/zcc-bad.zc </dev/null >/dev/null 2>&1; r=$? chk "$r $( [ -e $T/zcc-bad ] && echo yes || echo no)" "1 no" "zcc: a bad program exits 1 and writes no binary" - gcc -ffreestanding -w -c $T/run-echo/micro.c -o $T/micro_r.o && gcc -ffreestanding -w -c $T/run-echo/port_stub.c -o $T/stub_r.o && chk 1 1 "run-only microcore compiles freestanding" || chk 0 1 "run-only microcore compiles freestanding" + $CC -ffreestanding -w -c $T/run-echo/micro.c -o $T/micro_r.o && $CC -ffreestanding -w -c $T/run-echo/port_stub.c -o $T/stub_r.o && chk 1 1 "run-only microcore compiles freestanding" || chk 0 1 "run-only microcore compiles freestanding" } s_bare() { @@ -427,13 +443,13 @@ s_bare() { s_jit() { T=$ROOT/jit if command -v aarch64-linux-gnu-gcc >/dev/null && command -v qemu-aarch64 >/dev/null; then - aarch64-linux-gnu-gcc -pthread -O2 -w -fwrapv -DJTYPE=int -static src/core/core.c src/core/io_linux.c -Isrc/core -Ibuild/core -o $T/zyj || exit 1 + aarch64-linux-gnu-gcc -pthread -O2 -w -fwrapv -DJTYPE=int -static src/core/core.c src/core/io_linux.c $GI -o $T/zyj || exit 1 printf '#!/bin/sh\nexec qemu-aarch64 %s "$@"\n' $T/zyj > $T/jit; chmod +x $T/jit run $T/jit "jit" chk "$(ZYTRACE=1 $T/jit < tests/programs/o3.zc 2>&1 | grep -c 'bytes of arm64')" "8" "jit compiles every o3 region to arm64" selfc $T/jit $T/gj.bin; cmp -s $T/g1.bin $T/gj.bin && chk 1 1 "jit self-compile matches" || chk 0 1 "jit self-compile matches" # native calls need a dynamic binary (a static one has no dlsym), run against the aarch64 libc - if [ -d /usr/aarch64-linux-gnu/lib ] && aarch64-linux-gnu-gcc -pthread -O2 -w -fwrapv -DJTYPE=int src/core/core.c src/core/io_linux.c -Isrc/core -Ibuild/core -o $T/zyjd -ldl 2>/dev/null; then + if [ -d /usr/aarch64-linux-gnu/lib ] && aarch64-linux-gnu-gcc -pthread -O2 -w -fwrapv -DJTYPE=int src/core/core.c src/core/io_linux.c $GI -o $T/zyjd -ldl 2>/dev/null; then chk "$(qemu-aarch64 -L /usr/aarch64-linux-gnu $T/zyjd < tests/programs/cabi.zc | md5sum)" "$(md5sum < tests/programs/cabi.expected)" "jit native calls and callbacks (extern, cfunc)" chk "$(qemu-aarch64 -L /usr/aarch64-linux-gnu $T/zyjd < tests/programs/cabi_o3.zc | md5sum)" "$(md5sum < tests/programs/cabi_o3.expected)" "jit native calls from o3 code, stack arguments" chk "$(qemu-aarch64 -L /usr/aarch64-linux-gnu $T/zyjd < tests/programs/cabi_sized.zc | md5sum)" "$(md5sum < tests/programs/cabi_sized.expected)" "jit native calls with f32 and the sized integers" @@ -449,7 +465,9 @@ s_llvm() { T=$ROOT/llvm # llvm-config can exist without the headers and static libs (a runtime-only install), so the gate is a real build llvm_ok=0 - if command -v llvm-config >/dev/null && gcc -pthread -O2 -w -fwrapv -DJTYPE=int -DZYLLVM $(llvm-config --cflags) src/core/core.c src/core/io_linux.c -Isrc/core -Ibuild/core -o $T/zyl $(llvm-config --ldflags --libs) 2>/dev/null; then llvm_ok=1; fi + # LLVM_CONFIG picks the llvm build when the one on PATH has no headers (e.g. /usr/lib/llvm-20/bin/llvm-config) + local lc=${LLVM_CONFIG:-llvm-config} + if command -v $lc >/dev/null && $CC -pthread -O2 -w -fwrapv -DJTYPE=int -DZYLLVM $($lc --cflags) src/core/core.c src/core/io_linux.c $GI -o $T/zyl $($lc --ldflags --libs) 2>/dev/null; then llvm_ok=1; fi if [ $llvm_ok = 1 ]; then run $T/zyl "llvm" chk "$(ZYTRACE=1 $T/zyl < tests/programs/o3.zc 2>&1 | grep -c 'compiled by llvm')" "8" "llvm compiles every o3 region" @@ -558,11 +576,8 @@ s_lsp() { node tests/drivers/diagcache_test.js > $T/dcache.out 2>&1; r=$? grep -v '^ok' $T/dcache.out chk $r 0 "lsp: the diagnostics cache keys and store ($(grep -c '^ok' $T/dcache.out) checks)" - node tests/drivers/ext_cache_test.js > $T/ecache.out 2>&1; r=$? - grep -v '^ok' $T/ecache.out - chk $r 0 "lsp: the extension's cache, end to end ($(grep -c '^ok' $T/ecache.out) checks)" # the same server as WebAssembly, through the extension's host (needs clang) - if command -v clang >/dev/null && clang --print-targets 2>/dev/null | grep -q wasm32 && LSP_BUILD_DIR=$T/lsp LSP_REUSE_BUILD=1 bash src/tools/mklspwasm.sh $T/lsp/lsp.wasm >/dev/null 2>&1; then + if sh src/tools/findcc.sh --wasm >/dev/null 2>&1 && LSP_BUILD_DIR=$T/lsp LSP_REUSE_BUILD=1 bash src/tools/mklspwasm.sh $T/lsp/lsp.wasm >/dev/null 2>&1; then node tests/drivers/hover_surface_test.js --wasm $T/lsp/lsp.wasm > $T/hoversurfacewasm.out 2>&1; r=$? grep -v '^ok' $T/hoversurfacewasm.out chk $r 0 "lsp: embedded and asm hovers through the wasm host" @@ -575,7 +590,8 @@ s_lsp() { else echo "skip lsp wasm tests (no clang with wasm32)"; fi # the same server run by the javascript vm, the extension's fallback when wasm is not there. # the vm starts at the image's heap and grows on the heap's request, so a long document is the test - node src/js/extract_lsp.js >/dev/null && mkdir -p $T/vmserver && cp build/js/tinyvm.js build/js/layout.js build/js/lsp.json $T/vmserver/ + node src/js/extract_lsp.js --lsp $T/lsp --out $T/vmserver >/dev/null && node src/js/mklayout.js --map $MAP --out $T/vmserver >/dev/null \ + && tsc src/js/tinyvm.ts --target es2020 --lib es2020,dom --module commonjs --outDir $T/vmserver node tests/drivers/hover_surface_test.js --vm $T/vmserver > $T/hoversurfacevm.out 2>&1; r=$? grep -v '^ok' $T/hoversurfacevm.out chk $r 0 "lsp: embedded and asm hovers through the javascript host" @@ -590,6 +606,12 @@ s_lsp() { grep -v '^ok' $T/streamhost.out chk $r 0 "lsp: the wasm and vm servers stream and drop stale checks" fi + # the extension's client end to end, with the server files the package would carry + mkdir -p $T/extserver && cp $T/vmserver/tinyvm.js $T/vmserver/layout.js $T/vmserver/lsp.json $T/vmserver/lsp.bin $T/extserver/ + [ -f $T/lsp/lsp.wasm ] && cp $T/lsp/lsp.wasm $T/extserver/ + node tests/drivers/ext_cache_test.js $T/extserver > $T/ecache.out 2>&1; r=$? + grep -v '^ok' $T/ecache.out + chk $r 0 "lsp: the extension's cache, end to end ($(grep -c '^ok' $T/ecache.out) checks)" else echo "skip lsp protocol tests (no node)"; fi else chk 0 1 "lsp server builds"; fi } @@ -597,36 +619,39 @@ s_lsp() { s_js() { T=$ROOT/js # the javascript vm, and the microcore as webassembly. both run the same genome - if command -v node >/dev/null; then - chk "$(node -e "console.log(require('./build/js/genome.json').glen)")" "$(grep -m1 '#define GLEN' build/core/genome.h | awk '{print $3}')" "build/js/genome.json is up to date" - chk "$(node build/js/tinyvm.js examples/hello.zc)" "hello, world!" "js vm: hello" - chk "$(node build/js/tinyvm.js examples/fact.zc | tail -1)" "3628800" "js vm: factorial" - chk "$(node build/js/tinyvm.js examples/primes.zc | tail -1)" "widest gap: 72, after 31397" "js vm: primes" - chk "$(node build/js/tinyvm.js tests/programs/growone.zc)" "12 1" "js vm: a block past the initial heap grows the machine" - chk "$(node build/js/tinyvm.js tests/programs/replace_owner.zc)" "$(cat tests/programs/replace_owner.expected)" "js vm: replacement ownership and cleanup" - if command -v clang >/dev/null && command -v wasm-ld >/dev/null && clang --print-targets 2>/dev/null | grep -q wasm32; then - bash src/web/build.sh $T/zc.wasm >/dev/null && node tests/drivers/wasm_test.js $T/zc.wasm > $T/wasm.out 2>&1; r=$? + if command -v node >/dev/null && command -v tsc >/dev/null; then + # the vm as make js builds it, with the fixpoint genome beside it + node src/js/mklayout.js --map $MAP --out $T/jsvm >/dev/null && cp $ROOT/s1/genome.bin $T/jsvm/genome.bin \ + && tsc src/js/tinyvm.ts --target es2020 --lib es2020,dom --module commonjs --outDir $T/jsvm || chk 0 1 "js vm builds" + chk "$(node $T/jsvm/tinyvm.js examples/hello.zc)" "hello, world!" "js vm: hello" + chk "$(node $T/jsvm/tinyvm.js examples/fact.zc | tail -1)" "3628800" "js vm: factorial" + chk "$(node $T/jsvm/tinyvm.js examples/primes.zc | tail -1)" "widest gap: 72, after 31397" "js vm: primes" + chk "$(node $T/jsvm/tinyvm.js tests/programs/growone.zc)" "12 1" "js vm: a block past the initial heap grows the machine" + chk "$(node $T/jsvm/tinyvm.js tests/programs/replace_owner.zc)" "$(cat tests/programs/replace_owner.expected)" "js vm: replacement ownership and cleanup" + if sh src/tools/findcc.sh --wasm >/dev/null 2>&1 && command -v wasm-ld >/dev/null; then + GENOME_DIR=$ROOT/s1 MAP_DIR=$MAP bash src/web/build.sh $T/zc.wasm >/dev/null && node tests/drivers/wasm_test.js $T/zc.wasm > $T/wasm.out 2>&1; r=$? grep -v '^ok' $T/wasm.out chk $r 0 "wasm: the microcore in webassembly ($(grep -c '^ok' $T/wasm.out) checks)" node tests/drivers/fuzz_wasm.js $T/zc.wasm > $T/fuzzw.out 2>&1; r=$? grep -v '^ok' $T/fuzzw.out chk $r 0 "wasm: compiler/runtime/stdlib differential fuzz" - else echo "skip wasm (no clang with the wasm32 target, or no wasm-ld)"; fi - else echo "skip js and wasm (no node)"; fi + else echo "skip wasm (no clang with the wasm32 target and #embed, or no wasm-ld)"; fi + else echo "skip js and wasm (no node or tsc)"; fi } s_make() { T=$ROOT/make # the makefile builds the programs and the run-only image tool if command -v make >/dev/null; then - make -s BIN=$T/mbin B=$T/mbuild all >/dev/null 2>&1 && chk "$($T/mbin/zy < examples/hello.zc)" "hello, world!" "make: bin/zy runs a program" || chk 0 1 "make: all builds" - python3 tests/drivers/cli_help_test.py --group binaries --bin-dir $T/mbin > $T/help.out 2>&1; r=$? + # from an empty build directory, as a fresh checkout would be + make -s B=$T/mbuild all >/dev/null 2>&1 && chk "$($T/mbuild/bin/zy < examples/hello.zc)" "hello, world!" "make: build/bin/zy runs a program" || chk 0 1 "make: all builds" + python3 tests/drivers/cli_help_test.py --group binaries --bin-dir $T/mbuild/bin > $T/help.out 2>&1; r=$? grep -v '^ok' $T/help.out chk $r 0 "command-line help, invalid options and existing lsp source modes" python3 tests/drivers/cli_help_test.py --group scripts > $T/scripthelp.out 2>&1; r=$? grep -v '^ok' $T/scripthelp.out chk $r 0 "build-script help exits without reading input or starting a build" - make -s BIN=$T/mbin B=$T/mbuild run PROG=examples/hello.zc >/dev/null 2>&1 && chk "$($T/mbuild/run/zymr </dev/null)" "hello, world!" "make: run PROG= builds a run-only image" || chk 0 1 "make: run builds" + make -s B=$T/mbuild run PROG=examples/hello.zc >/dev/null 2>&1 && chk "$($T/mbuild/run/zymr </dev/null)" "hello, world!" "make: run PROG= builds a run-only image" || chk 0 1 "make: run builds" else echo "skip make (not installed)"; fi } @@ -634,7 +659,7 @@ s_make() { # every block in it is one of those examples (tests/drivers/learn.py) s_learn() { T=$ROOT/learn - gcc -pthread -O2 -w -fwrapv -DJTYPE=int src/core/core.c src/core/io_linux.c -Isrc/core -Ibuild/core -o $T/zy + $CC -pthread -O2 -w -fwrapv -DJTYPE=int src/core/core.c src/core/io_linux.c $GI -o $T/zy # learn.py prints its own ok and FAIL lines, which the tally below counts python3 tests/drivers/learn.py $T/zy; true } diff --git a/tests/test_win.ps1 b/tests/test_win.ps1 index 0edc86c..953e875 100644 --- a/tests/test_win.ps1 +++ b/tests/test_win.ps1 @@ -1,17 +1,29 @@ # TinyVM Windows test suite (PowerShell implementation of test.sh) $ErrorActionPreference = "Stop" -# detect compiler (prioritizing LLVM-MinGW with UCRT headers) +# the suite reads nothing from build\: it lays out the default map, bootstraps the +# compiler and builds every executable in its own temporary directory. the cores +# take their genome in with c23 #embed, so the compiler must be clang 19 or gcc 15. +$tempDir = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "tinyvm_test_" + [System.Guid]::NewGuid().ToString("N")) +New-Item -ItemType Directory -Path "$tempDir\s0", "$tempDir\s1", "$tempDir\probe" -Force | Out-Null +[IO.File]::WriteAllText("$tempDir\probe\probe.bin", "ab") +[IO.File]::WriteAllText("$tempDir\probe\probe.c", "static const unsigned char probe[] = {`n#embed `"probe.bin`"`n};`nint main(void) { return sizeof probe == 2 ? 0 : 1; }`n") +function Test-Embed($compiler) { + & $compiler -w "--embed-dir=$tempDir\probe" "$tempDir\probe\probe.c" -o "$tempDir\probe\probe.exe" 2>$null | Out-Null + return $LASTEXITCODE -eq 0 +} +# detect a compiler with #embed (CC first, then LLVM-MinGW with UCRT headers, then PATH) +$cc = $null +$candidates = @() +if ($env:CC) { $candidates += $env:CC } $llvmMinGW = Get-ChildItem "$env:LOCALAPPDATA\Microsoft\WinGet\Packages\MartinStorsjo.LLVM-MinGW*\llvm-mingw*\bin" -ErrorAction SilentlyContinue -if ($llvmMinGW) { - $env:Path = "$($llvmMinGW[0].FullName);$env:Path" - $cc = "clang" -} elseif (Get-Command clang -ErrorAction SilentlyContinue) { - $cc = "clang" -} elseif (Get-Command gcc -ErrorAction SilentlyContinue) { - $cc = "gcc" -} else { - Write-Error "No C compiler (clang or gcc) found in PATH." +if ($llvmMinGW) { $candidates += "$($llvmMinGW[0].FullName)\clang.exe" } +$candidates += @("clang", "gcc", "$env:ProgramFiles\LLVM\bin\clang.exe") +foreach ($c in $candidates) { + if ((Get-Command $c -ErrorAction SilentlyContinue) -and (Test-Embed $c)) { $cc = $c; break } +} +if (-not $cc) { + Write-Error "No C compiler with #embed support (clang 19 or gcc 15 and later) was found." exit 1 } @@ -21,9 +33,6 @@ Write-Host "==================================================================== Write-Host "Compiler: $cc" Write-Host "" -$tempDir = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "tinyvm_test_" + [System.Guid]::NewGuid().ToString("N")) -New-Item -ItemType Directory -Path "$tempDir\s0", "$tempDir\s1" -Force | Out-Null - $totalPassed = 0 $totalFailed = 0 @@ -40,41 +49,41 @@ function Report-Result($success, $name, $detail = "") { try { # --- 1. bootstrap & fixpoint --- Write-Host "=== 1. Bootstrap and Fixpoint Verification ===" -ForegroundColor Yellow - & $cc -fwrapv -O1 -w src\tools\zc0.c -Isrc/core -o "$tempDir\zc0.exe" - & $cc -fwrapv -O1 -w src\tools\mkgenome.c -Isrc/core -o "$tempDir\mkgenome.exe" + # the default map, as make writes it, and the tools built against it + & $cc -fwrapv -O2 -w src\tools\genmap.c -o "$tempDir\genmap.exe" + cmd.exe /c "`"$tempDir\genmap.exe`" --preset default --check -o `"$tempDir\map`" >nul 2>nul" + Report-Result ($LASTEXITCODE -eq 0) "genmap default preset matches the sources" + $map = "$tempDir\map" + & $cc -fwrapv -O1 -w src\tools\zc0.c -Isrc/core "-I$map" -o "$tempDir\zc0.exe" + & $cc -fwrapv -O1 -w src\tools\mkgenome.c "-I$map" -o "$tempDir\mkgenome.exe" - Copy-Item src\core\core.c, src\core\io_win.c, src\core\memmap.h, src\core\fops.h, src\core\bops.h, src\core\stack.h -Destination "$tempDir\s0" - Copy-Item src\core\core.c, src\core\io_win.c, src\core\memmap.h, src\core\fops.h, src\core\bops.h, src\core\stack.h -Destination "$tempDir\s1" - - cmd.exe /c "(type src\runtime\rt.zc & type src\compiler\compiler.zc) | $tempDir\zc0.exe > $tempDir\s0\genome.h" - & $cc -fwrapv -O2 -w -DJTYPE=int "$tempDir\s0\core.c" "$tempDir\s0\io_win.c" -o "$tempDir\zy0.exe" + cmd.exe /c "(type src\runtime\rt.zc & type src\core\core.zc & type src\compiler\compiler.zc) | `"$tempDir\zc0.exe`" > `"$tempDir\s0\g0.bin`"" + cmd.exe /c "`"$tempDir\mkgenome.exe`" < `"$tempDir\s0\g0.bin`" > `"$tempDir\s0\genome.bin`"" + & $cc -fwrapv -O2 -w -DJTYPE=int src\core\core.c src\core\io_win.c -Isrc/core "-I$map" "--embed-dir=$tempDir\s0" -o "$tempDir\zy0.exe" $bzc = [IO.File]::ReadAllText("src\tools\build.zc") $rt = [IO.File]::ReadAllText("src\runtime\rt.zc") + $core = [IO.File]::ReadAllText("src\core\core.zc") $cp = [IO.File]::ReadAllText("src\compiler\compiler.zc") - $combined = $bzc + [char]96 + $rt + $cp + $combined = $bzc + [char]96 + $rt + $core + $cp [IO.File]::WriteAllBytes("$tempDir\in.tmp", [Text.Encoding]::ASCII.GetBytes($combined)) $p1 = Start-Process -FilePath "$tempDir\zy0.exe" -NoNewWindow -PassThru -RedirectStandardInput "$tempDir\in.tmp" -RedirectStandardOutput "$tempDir\g1.bin" -Wait - cmd.exe /c "$tempDir\mkgenome.exe < $tempDir\g1.bin > $tempDir\s1\genome.h" + cmd.exe /c "`"$tempDir\mkgenome.exe`" < `"$tempDir\g1.bin`" > `"$tempDir\s1\genome.bin`"" + Report-Result ($LASTEXITCODE -eq 0) "the compiler compiles its own source" - & $cc -fwrapv -O2 -w -DJTYPE=int "$tempDir\s1\core.c" "$tempDir\s1\io_win.c" -o "$tempDir\zy1.exe" + & $cc -fwrapv -O2 -w -DJTYPE=int src\core\core.c src\core\io_win.c -Isrc/core "-I$map" "--embed-dir=$tempDir\s1" -o "$tempDir\zy1.exe" $p2 = Start-Process -FilePath "$tempDir\zy1.exe" -NoNewWindow -PassThru -RedirectStandardInput "$tempDir\in.tmp" -RedirectStandardOutput "$tempDir\g2.bin" -Wait $g1 = [IO.File]::ReadAllBytes("$tempDir\g1.bin") $g2 = [IO.File]::ReadAllBytes("$tempDir\g2.bin") $isFixpoint = [System.Linq.Enumerable]::SequenceEqual($g1, $g2) Report-Result $isFixpoint "Fixpoint: compiler compiles itself to identical $($g1.Length / 8) words" - - $ghRepo = (Get-FileHash "build\core\genome.h").Hash - $ghGen = (Get-FileHash "$tempDir\s1\genome.h").Hash - Report-Result ($ghRepo -eq $ghGen) "genome.h is up to date" - # --- 2. compile test executables --- + # --- 2. compile test executables, each carrying the fixpoint genome --- Write-Host "`n=== 2. Compiling Test Executables ===" -ForegroundColor Yellow - & $cc -fwrapv -O2 -w -DJTYPE=int src\core\core.c src\core\io_win.c -Isrc/core -o "$tempDir\zy.exe" - & $cc -fwrapv -O2 -w src\micro\micro.c src\micro\port_win.c -Isrc/core -Isrc/micro -o "$tempDir\zym.exe" - & $cc -fwrapv -O2 -w src\tools\genmap.c -Isrc/core -o "$tempDir\genmap.exe" + & $cc -fwrapv -O2 -w -DJTYPE=int src\core\core.c src\core\io_win.c -Isrc/core "-I$map" "--embed-dir=$tempDir\s1" -o "$tempDir\zy.exe" + & $cc -fwrapv -O2 -w src\micro\micro.c src\micro\port_win.c -Isrc/core "-I$map" "--embed-dir=$tempDir\s1" -Isrc/micro -o "$tempDir\zym.exe" # --- 3. feature tests on zy.exe --- Write-Host "`n=== 3. Running Feature Tests on VM Core (zy.exe) ===" -ForegroundColor Yellow @@ -158,7 +167,7 @@ try { $pass = 0 $fail = 0 foreach ($f in $files) { - $want = ((Get-Content $f.FullName -TotalCount 1) -replace '^// expect:\s*').Trim() + $want = ((Get-Content $f.FullName | Where-Object { $_ -match '^// expect:' } | Select-Object -First 1) -replace '^// expect:\s*').Trim() $got = (Get-Content $f.FullName -Raw | & "$tempDir\zy.exe" 2>&1 | Select-Object -First 1) if ($got -and $got.Contains($want)) { $pass++ @@ -172,9 +181,6 @@ try { # --- 6. tool verification --- Write-Host "`n=== 6. Memory Map Tool Verification (genmap.exe) ===" -ForegroundColor Yellow - cmd.exe /c "`"$tempDir\genmap.exe`" --preset default --check >nul 2>nul" - Report-Result ($LASTEXITCODE -eq 0) "genmap default preset check passed" - cmd.exe /c "`"$tempDir\genmap.exe`" --preset small -o `"$tempDir\small`" >nul 2>nul" Report-Result ($LASTEXITCODE -eq 0) "genmap small preset generated" @@ -182,21 +188,28 @@ try { Write-Host "`n=== 7. ZC Language Server (zclsp.exe) ===" -ForegroundColor Yellow # the server is the real compiler in check mode plus src\lsp\server.zc, on its own map $lspDir = "$tempDir\lsp" - New-Item -ItemType Directory -Force "$lspDir\src" | Out-Null - # two passes: compile once to learn the genome length, then lay the map out again for that length - $lspSrc = "(type `"$lspDir\map\rt.zc`" & type `"$lspDir\map\compiler.zc`" & type src\lsp\server.zc)" - cmd.exe /c "`"$tempDir\genmap.exe`" --preset lsp --grow -o `"$lspDir\map`" >nul 2>nul" - & $cc -fwrapv -O1 -w src\tools\zc0.c "-I$lspDir\map" -Isrc/core -o "$lspDir\zc0.exe" - cmd.exe /c "$lspSrc | `"$lspDir\zc0.exe`" > `"$lspDir\genome.h`"" - $glen = [int]((Select-String -Path "$lspDir\genome.h" -Pattern '#define\s+GLEN\s+(\d+)' | Select-Object -First 1).Matches[0].Groups[1].Value) + New-Item -ItemType Directory -Force "$lspDir" | Out-Null + # the steps of src/tools/automap.sh lsp with DOCS=1: compile once to learn the genome + # length, lay the map out for it, then again with the documentation the hovers read + function Build-LspImage($slot, $define, $out) { + if (Test-Path "$lspDir\map") { Remove-Item -Recurse -Force "$lspDir\map" } + $genomeOption = if ($slot) { "--genome $slot" } else { "" } + cmd.exe /c "`"$tempDir\genmap.exe`" --preset lsp --grow $genomeOption -o `"$lspDir\map`" >nul 2>nul" + & $cc -fwrapv -O1 -w $define src\tools\zc0.c "-I$lspDir\map" -Isrc/core -o "$lspDir\zc0.exe" + & $cc -fwrapv -O1 -w src\tools\mkgenome.c "-I$lspDir\map" -o "$lspDir\mkgenome.exe" + cmd.exe /c "(type `"$lspDir\map\rt.zc`" & type src\core\core.zc & type `"$lspDir\map\compiler.zc`" & type src\lsp\server.zc) | `"$lspDir\zc0.exe`" > `"$lspDir\raw.bin`"" + cmd.exe /c "`"$lspDir\mkgenome.exe`" < `"$lspDir\raw.bin`" > `"$lspDir\$out`"" + return [int]((Get-Item "$lspDir\$out").Length / 8) + } + # ZC0PLAIN defines nothing zc0 reads; it stands in for "no documentation" as one argument + $glen = Build-LspImage $null "-DZC0PLAIN" "image.bin" + $slot = [int]([math]::Ceiling($glen / 4096.0) * 4096) + $glen = Build-LspImage ($slot + 65536) "-DZC0DOCS" "image.bin" $slot = [int]([math]::Ceiling($glen / 4096.0) * 4096) - Remove-Item -Recurse -Force "$lspDir\map" - cmd.exe /c "`"$tempDir\genmap.exe`" --preset lsp --grow --genome $slot -o `"$lspDir\map`" >nul 2>nul" - & $cc -fwrapv -O1 -w src\tools\zc0.c "-I$lspDir\map" -Isrc/core -o "$lspDir\zc0.exe" - cmd.exe /c "$lspSrc | `"$lspDir\zc0.exe`" > `"$lspDir\src\lsp_genome.h`"" - foreach ($f in 'core.c','io_win.c','fops.h','bops.h','stack.h') { Copy-Item "src\core\$f" "$lspDir\src\" } - Copy-Item "$lspDir\map\memmap.h" "$lspDir\src\" - & $cc -fwrapv -O2 -w -DJTYPE=int -DUSE_LSP_GENOME "-I$lspDir\src" "$lspDir\src\core.c" "$lspDir\src\io_win.c" -o "$lspDir\zclsp.exe" + $final = Build-LspImage $slot "-DZC0DOCS" "lsp_genome.bin" + Report-Result ($final -eq $glen) "the LSP genome keeps its length on its sized map" + Copy-Item "$lspDir\map\memmap.h" "$lspDir\" + & $cc -fwrapv -O2 -w -DJTYPE=int -DZY_SANDBOX -DUSE_LSP_GENOME src\core\core.c src\core\io_win.c -Isrc/core "-I$lspDir" "--embed-dir=$lspDir" -o "$lspDir\zclsp.exe" Report-Result (Test-Path "$lspDir\zclsp.exe") "zclsp.exe compiled" # CLI check: a clean file @@ -209,7 +222,7 @@ try { # CLI check: the compiler's optimization hints $lspOpt = (cmd.exe /c "`"$lspDir\zclsp.exe`" < examples\opt_test.zc 2>&1") -join "`n" - $hints = @("is never read", "is never reassigned; make it a const", "is a shift; write << 3", "the number 1234 has no name", "new inside a loop", "stack buffer", "handed off once") + $hints = @("is never read", "is never reassigned; make it a const", "is a shift; write << 3", "unannotated numeric literal 1234", "new inside a loop", "stack buffer") foreach ($h in $hints) { Report-Result ($lspOpt.Contains($h)) "zclsp hint: $h" } # methods with this, and closures, check clean @@ -226,23 +239,28 @@ try { Write-Host " [SKIP] Node.js not detected in PATH, skipping lsp protocol tests." -ForegroundColor DarkYellow } - # --- 8. JavaScript / TypeScript vm runtime (build/js/tinyvm.js) --- - Write-Host "`n=== 8. JavaScript VM Runtime (node build/js/tinyvm.js) ===" -ForegroundColor Yellow + # --- 8. JavaScript / TypeScript vm runtime, built as make js builds it --- + Write-Host "`n=== 8. JavaScript VM Runtime (node tinyvm.js) ===" -ForegroundColor Yellow $nodeCmd = Get-Command node -ErrorAction SilentlyContinue - if ($nodeCmd) { - $jsHello = (node build\js\tinyvm.js examples\hello.zc 2>&1) -join "`n" + $tscCmd = Get-Command tsc -ErrorAction SilentlyContinue + if ($nodeCmd -and $tscCmd) { + $jsDir = "$tempDir\js" + node src\js\mklayout.js --map "$map\memmap.h" --out $jsDir | Out-Null + Copy-Item "$tempDir\s1\genome.bin" "$jsDir\genome.bin" + tsc src\js\tinyvm.ts --target es2020 --lib es2020,dom --module commonjs --outDir $jsDir + $jsHello = (node $jsDir\tinyvm.js examples\hello.zc 2>&1) -join "`n" Report-Result ($jsHello.Trim() -eq "hello, world!") "JS VM: hello.zc" - $jsFact = (node build\js\tinyvm.js examples\fact.zc 2>&1) -join "`n" + $jsFact = (node $jsDir\tinyvm.js examples\fact.zc 2>&1) -join "`n" Report-Result ($jsFact.Contains("3628800")) "JS VM: fact.zc" - $jsPrimes = (node build\js\tinyvm.js examples\primes.zc 2>&1) -join "`n" + $jsPrimes = (node $jsDir\tinyvm.js examples\primes.zc 2>&1) -join "`n" Report-Result ($jsPrimes.Contains("widest gap: 72")) "JS VM: primes.zc" - $jsMandel = (node build\js\tinyvm.js examples\mandel.zc 2>&1) -join "`n" + $jsMandel = (node $jsDir\tinyvm.js examples\mandel.zc 2>&1) -join "`n" Report-Result ($jsMandel.Contains("*") -or $jsMandel.Contains(".")) "JS VM: mandel.zc (custom fmul enhancement)" } else { - Write-Host " [SKIP] Node.js not detected in PATH, skipping JS tests." -ForegroundColor DarkYellow + Write-Host " [SKIP] Node.js or tsc not detected in PATH, skipping JS tests." -ForegroundColor DarkYellow } } finally { |
