aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/bench/bench.sh8
-rw-r--r--tests/bench/bench_bytes.sh12
-rw-r--r--tests/bench/bench_float.sh10
-rw-r--r--tests/bench/bench_share.sh15
-rw-r--r--tests/bench/bench_strings.sh15
-rwxr-xr-xtests/bench/run.py9
-rw-r--r--tests/drivers/actor_image_test.py15
-rw-r--r--tests/drivers/actor_test.py2
-rw-r--r--tests/drivers/analysis_cache_test.js4
-rw-r--r--tests/drivers/array_methods_test.py2
-rw-r--r--tests/drivers/cli_help_test.py7
-rw-r--r--tests/drivers/completion_test.js4
-rw-r--r--tests/drivers/core_lsp_test.js4
-rw-r--r--tests/drivers/enhancement_backends_test.py6
-rw-r--r--tests/drivers/ext_cache_test.js15
-rw-r--r--tests/drivers/float_backends_test.py2
-rw-r--r--tests/drivers/fuzz_compiler.py40
-rw-r--r--tests/drivers/fuzz_lsp.js8
-rw-r--r--tests/drivers/fuzz_malformed.py2
-rw-r--r--tests/drivers/fuzz_wasm.js2
-rw-r--r--tests/drivers/genome_blob_test.py18
-rw-r--r--tests/drivers/hints_test.js4
-rw-r--r--tests/drivers/hover_features_test.js4
-rw-r--r--tests/drivers/hover_surface_test.js4
-rw-r--r--tests/drivers/hover_values_test.js4
-rw-r--r--tests/drivers/icon_test.py2
-rw-r--r--tests/drivers/image_run.js7
-rw-r--r--tests/drivers/imagekit.py34
-rw-r--r--tests/drivers/layout_config_test.py2
-rw-r--r--tests/drivers/lsp_growth_test.js4
-rw-r--r--tests/drivers/lsp_sandbox.js6
-rw-r--r--tests/drivers/lsp_test.js8
-rw-r--r--tests/drivers/memory_limits_test.py2
-rw-r--r--tests/drivers/nav_test.js4
-rw-r--r--tests/drivers/protocol_growth_test.js4
-rw-r--r--tests/drivers/runtime_image_test.py19
-rw-r--r--tests/drivers/seed_growth_test.py23
-rw-r--r--tests/drivers/stack_growth_test.py56
-rw-r--r--tests/drivers/storage_analysis_test.js48
-rw-r--r--tests/drivers/thread_context_test.py2
-rw-r--r--tests/drivers/toolchain_lsp_contract_test.js4
-rw-r--r--tests/drivers/tools_contract_test.py34
-rw-r--r--tests/drivers/wasm_test.js4
-rw-r--r--tests/drivers/word_literals_test.py4
-rwxr-xr-xtests/test.sh165
-rw-r--r--tests/test_win.ps1134
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 {