From 7451eb090d890fb5ea5e4d40627a1b4df15a49de Mon Sep 17 00:00:00 2001 From: zinc Date: Wed, 14 Oct 2026 07:28:39 +0000 Subject: build: embed images with #embed, keep everything generated in build/ the cores take their genome in with c23 #embed through src/core/genome.h, so images are plain little-endian word files (genome.bin) instead of generated c headers. zc0 writes that format, mkgenome checks an image against its map and shows a compiler's diagnostics when it gets them, and the javascript vm and the editor's vm host read the same files. src/tools/findcc.sh picks a compiler with #embed (clang 19 or gcc 15). zc gets an embed declaration, embed "file" as [type] name, for globals and locals: a byte string by default, char*, numbers read little-endian, and arrays of numbers. the compiler and language server carry their library and source texts through it, so embed.sh and the text it spliced into compiler.zc are gone. make writes executables to build/bin, the default map to build/core/map, the web page to build/web and the extension staging to build/vsix, and the genome now rebuilds when its sources change. the test suite lays out its own map and bootstrap and reads nothing from build/. also: rt.put_s of a byte string in zc0 images, retained function sources no longer count as live objects, a missing include no longer cascades into undefined-name errors, consecutive comment lines join into one hover paragraph, and stale test fixtures follow the include format. --- tests/drivers/actor_image_test.py | 15 ++++---- tests/drivers/actor_test.py | 2 +- tests/drivers/analysis_cache_test.js | 4 +- tests/drivers/array_methods_test.py | 2 +- tests/drivers/cli_help_test.py | 7 ++-- tests/drivers/completion_test.js | 4 +- tests/drivers/core_lsp_test.js | 4 +- tests/drivers/enhancement_backends_test.py | 6 +-- tests/drivers/ext_cache_test.js | 15 +++++++- tests/drivers/float_backends_test.py | 2 +- tests/drivers/fuzz_compiler.py | 40 +++++++++++--------- tests/drivers/fuzz_lsp.js | 8 ++-- tests/drivers/fuzz_malformed.py | 2 +- tests/drivers/fuzz_wasm.js | 2 +- tests/drivers/genome_blob_test.py | 18 +++++---- tests/drivers/hints_test.js | 4 +- tests/drivers/hover_features_test.js | 4 +- tests/drivers/hover_surface_test.js | 4 +- tests/drivers/hover_values_test.js | 4 +- tests/drivers/icon_test.py | 2 +- tests/drivers/image_run.js | 7 ++-- tests/drivers/imagekit.py | 34 +++++++++++++++++ tests/drivers/layout_config_test.py | 2 +- tests/drivers/lsp_growth_test.js | 4 +- tests/drivers/lsp_sandbox.js | 6 ++- tests/drivers/lsp_test.js | 8 ++-- tests/drivers/memory_limits_test.py | 2 +- tests/drivers/nav_test.js | 4 +- tests/drivers/protocol_growth_test.js | 4 +- tests/drivers/runtime_image_test.py | 19 +++++----- tests/drivers/seed_growth_test.py | 23 ++++++------ tests/drivers/stack_growth_test.py | 56 ++++++++++++++++------------ tests/drivers/storage_analysis_test.js | 48 +++++++++++++----------- tests/drivers/thread_context_test.py | 2 +- tests/drivers/toolchain_lsp_contract_test.js | 4 +- tests/drivers/tools_contract_test.py | 34 ++++++++--------- tests/drivers/wasm_test.js | 4 +- tests/drivers/word_literals_test.py | 4 +- 38 files changed, 255 insertions(+), 159 deletions(-) create mode 100644 tests/drivers/imagekit.py (limited to 'tests/drivers') 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(' { - 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]) -- cgit v1.2.3