All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 23m17s
- wasm_of_ocaml compiles OCaml SX engine to WASM (722/722 spec tests) - js_of_ocaml fallback also working (722/722 spec tests) - Thin JS platform layer (sx-platform.js) with ~80 DOM/browser natives - Lambda callback bridge: SX lambdas callable from JS via handle table - Side-channel pattern bypasses js_of_ocaml return-value property stripping - Web adapters (signals, deps, router, adapter-html) load as SX source - Render mode dispatch: HTML tags + fragments route to OCaml renderer - Island/component accessors handle both Component and Island types - Dict-based signal support (signals.sx creates dicts, not native Signal) - Scope stack implementation (collect!/collected/emit!/emitted/context) - Bundle script embeds web adapters + WASM loader + platform layer - SX_USE_WASM env var toggles WASM engine in dev/production - Bootstrap extended: --web flag transpiles web adapters, :effects stripping Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
147 lines
4.4 KiB
JavaScript
147 lines
4.4 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Test runner for the wasm_of_ocaml-compiled SX engine.
|
|
*
|
|
* Loads the OCaml CEK machine (compiled to WASM) and runs the spec test suite.
|
|
* Requires Node.js 22+ with --experimental-wasm-imported-strings flag.
|
|
*
|
|
* Usage:
|
|
* node --experimental-wasm-imported-strings hosts/ocaml/browser/run_tests_wasm.js
|
|
* node --experimental-wasm-imported-strings hosts/ocaml/browser/run_tests_wasm.js --full
|
|
*/
|
|
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
|
|
const wasmDir = path.join(__dirname, "../_build/default/browser");
|
|
const loaderPath = path.join(wasmDir, "sx_browser.bc.wasm.js");
|
|
|
|
if (!fs.existsSync(loaderPath)) {
|
|
console.error("Build first: cd hosts/ocaml && eval $(opam env) && dune build browser/sx_browser.bc.wasm.js");
|
|
process.exit(1);
|
|
}
|
|
|
|
// Set require.main.filename so the WASM loader can find .wasm assets
|
|
if (!require.main) {
|
|
require.main = { filename: path.join(wasmDir, "test.js") };
|
|
} else {
|
|
require.main.filename = path.join(wasmDir, "test.js");
|
|
}
|
|
|
|
require(loaderPath);
|
|
|
|
const full = process.argv.includes("--full");
|
|
|
|
// WASM loader is async — wait for SxKernel to be available
|
|
setTimeout(() => {
|
|
const K = globalThis.SxKernel;
|
|
if (!K) {
|
|
console.error("SxKernel not available — WASM initialization failed");
|
|
process.exit(1);
|
|
}
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
let errors = [];
|
|
let suiteStack = [];
|
|
|
|
function currentSuite() {
|
|
return suiteStack.length > 0 ? suiteStack.join(" > ") : "";
|
|
}
|
|
|
|
// Register platform test functions
|
|
K.registerNative("report-pass", (args) => {
|
|
const name = typeof args[0] === "string" ? args[0] : JSON.stringify(args[0]);
|
|
passed++;
|
|
if (process.env.VERBOSE) {
|
|
console.log(` PASS: ${currentSuite()} > ${name}`);
|
|
} else {
|
|
process.stdout.write(".");
|
|
if (passed % 80 === 0) process.stdout.write("\n");
|
|
}
|
|
return null;
|
|
});
|
|
|
|
K.registerNative("report-fail", (args) => {
|
|
const name = typeof args[0] === "string" ? args[0] : JSON.stringify(args[0]);
|
|
const error = args.length > 1 && args[1] != null
|
|
? (typeof args[1] === "string" ? args[1] : JSON.stringify(args[1]))
|
|
: "unknown";
|
|
failed++;
|
|
const fullName = currentSuite() ? `${currentSuite()} > ${name}` : name;
|
|
errors.push(`FAIL: ${fullName}\n ${error}`);
|
|
process.stdout.write("F");
|
|
});
|
|
|
|
K.registerNative("push-suite", (args) => {
|
|
const name = typeof args[0] === "string" ? args[0] : String(args[0]);
|
|
suiteStack.push(name);
|
|
return null;
|
|
});
|
|
|
|
K.registerNative("pop-suite", (_args) => {
|
|
suiteStack.pop();
|
|
return null;
|
|
});
|
|
|
|
console.log(`=== SX OCaml→WASM Engine Test Runner ===`);
|
|
console.log(`Engine: ${K.engine()}`);
|
|
console.log(`Mode: ${full ? "full" : "standard"}`);
|
|
console.log("");
|
|
|
|
const specDir = path.join(__dirname, "../../../spec");
|
|
const testDir = path.join(specDir, "tests");
|
|
|
|
const standardTests = [
|
|
"test-framework.sx", "test-eval.sx", "test-parser.sx",
|
|
"test-primitives.sx", "test-collections.sx", "test-closures.sx",
|
|
"test-defcomp.sx", "test-macros.sx", "test-errors.sx",
|
|
"test-render.sx", "test-tco.sx", "test-scope.sx",
|
|
"test-cek.sx", "test-advanced.sx",
|
|
];
|
|
|
|
const fullOnlyTests = [
|
|
"test-freeze.sx", "test-continuations.sx",
|
|
"test-continuations-advanced.sx", "test-cek-advanced.sx",
|
|
"test-signals-advanced.sx", "test-render-advanced.sx",
|
|
"test-integration.sx", "test-strict.sx", "test-types.sx",
|
|
];
|
|
|
|
const testFiles = full ? [...standardTests, ...fullOnlyTests] : standardTests;
|
|
|
|
for (const file of testFiles) {
|
|
const filePath = path.join(testDir, file);
|
|
if (!fs.existsSync(filePath)) {
|
|
console.log(`\nSkipping ${file} (not found)`);
|
|
continue;
|
|
}
|
|
|
|
const label = file.replace(".sx", "").replace("test-", "");
|
|
process.stdout.write(`\n[${label}] `);
|
|
|
|
const src = fs.readFileSync(filePath, "utf8");
|
|
const result = K.loadSource(src);
|
|
if (typeof result === "string" && result.startsWith("Error:")) {
|
|
console.log(`\n LOAD ERROR: ${result}`);
|
|
failed++;
|
|
errors.push(`LOAD ERROR: ${file}\n ${result}`);
|
|
}
|
|
}
|
|
|
|
console.log("\n");
|
|
|
|
if (errors.length > 0) {
|
|
console.log(`--- Failures (${errors.length}) ---`);
|
|
for (const e of errors.slice(0, 20)) {
|
|
console.log(e);
|
|
}
|
|
if (errors.length > 20) {
|
|
console.log(`... and ${errors.length - 20} more`);
|
|
}
|
|
console.log("");
|
|
}
|
|
|
|
console.log(`Results: ${passed} passed, ${failed} failed, ${passed + failed} total`);
|
|
process.exit(failed > 0 ? 1 : 0);
|
|
}, 1000);
|