#!/usr/bin/env node /** * Test runner for the js_of_ocaml-compiled SX engine. * * Loads the OCaml CEK machine (compiled to JS) and runs the spec test suite. * * Usage: * node hosts/ocaml/browser/run_tests_js.js # standard tests * node hosts/ocaml/browser/run_tests_js.js --full # full suite */ const fs = require("fs"); const path = require("path"); // Load the compiled OCaml engine const enginePath = path.join(__dirname, "../_build/default/browser/sx_browser.bc.js"); if (!fs.existsSync(enginePath)) { console.error("Build first: cd hosts/ocaml && eval $(opam env) && dune build browser/sx_browser.bc.js"); process.exit(1); } require(enginePath); const K = globalThis.SxKernel; const full = process.argv.includes("--full"); // Test state 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→JS Engine Test Runner ===`); console.log(`Engine: ${K.engine()}`); console.log(`Mode: ${full ? "full" : "standard"}`); console.log(""); // Load a .sx file by reading it from disk and evaluating via loadSource function loadFile(filePath) { const src = fs.readFileSync(filePath, "utf8"); return K.loadSource(src); } // Test files 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 result = loadFile(filePath); 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);