diff options
| author | 2026-10-14 07:28:39 +0000 | |
|---|---|---|
| committer | 2026-10-14 07:28:39 +0000 | |
| commit | 7451eb090d890fb5ea5e4d40627a1b4df15a49de (patch) | |
| tree | 357bdf4b528e860bffa688865306f656b788d2c2 /tests | |
| parent | analyzer: close the ownership gaps that stopped the compiler compiling itself (diff) | |
build: embed images with #embed, keep everything generated in build/
the cores take their genome in with c23 #embed through src/core/genome.h,
so images are plain little-endian word files (genome.bin) instead of
generated c headers. zc0 writes that format, mkgenome checks an image
against its map and shows a compiler's diagnostics when it gets them,
and the javascript vm and the editor's vm host read the same files.
src/tools/findcc.sh picks a compiler with #embed (clang 19 or gcc 15).
zc gets an embed declaration, embed "file" as [type] name, for globals
and locals: a byte string by default, char*, numbers read little-endian,
and arrays of numbers. the compiler and language server carry their
library and source texts through it, so embed.sh and the text it
spliced into compiler.zc are gone.
make writes executables to build/bin, the default map to build/core/map,
the web page to build/web and the extension staging to build/vsix, and
the genome now rebuilds when its sources change. the test suite lays
out its own map and bootstrap and reads nothing from build/.
also: rt.put_s of a byte string in zc0 images, retained function
sources no longer count as live objects, a missing include no longer
cascades into undefined-name errors, consecutive comment lines join
into one hover paragraph, and stale test fixtures follow the include
format.
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 { |
