Files
rose-ash/shared/sx/tests/run.js
giles e9d86d628b Make test.sx self-executing: evaluators run it directly, no codegen
test.sx now defines deftest/defsuite as macros. Any host that provides
5 platform functions (try-call, report-pass, report-fail, push-suite,
pop-suite) can evaluate the file directly — no bootstrap compilation
step needed for JS.

- Added defmacro for deftest (wraps body in thunk, catches via try-call)
- Added defmacro for defsuite (push/pop suite context stack)
- Created run.js: sx-browser.js evaluates test.sx directly (81/81 pass)
- Created run.py: Python evaluator evaluates test.sx directly (81/81 pass)
- Deleted bootstrap_test_js.py and generated test_sx_spec.js
- Updated testing docs page to reflect self-executing architecture

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 10:50:28 +00:00

109 lines
3.6 KiB
JavaScript

// Run test.sx directly against sx-browser.js.
//
// sx-browser.js parses and evaluates test.sx — SX tests itself.
// This script provides only platform functions (error catching, reporting).
//
// Usage: node shared/sx/tests/run.js
Object.defineProperty(globalThis, "document", { value: undefined, writable: true });
var path = require("path");
var fs = require("fs");
var Sx = require(path.resolve(__dirname, "../../static/scripts/sx-browser.js"));
// --- Test state ---
var suiteStack = [];
var passed = 0, failed = 0, testNum = 0;
// --- Helpers ---
function isNil(x) { return x === Sx.NIL || x === null || x === undefined; }
function deepEqual(a, b) {
if (a === b) return true;
if (isNil(a) && isNil(b)) return true;
if (typeof a !== typeof b) return false;
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) return false;
for (var i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;
return true;
}
if (a && typeof a === "object" && b && typeof b === "object") {
var ka = Object.keys(a), kb = Object.keys(b);
if (ka.length !== kb.length) return false;
for (var j = 0; j < ka.length; j++) if (!deepEqual(a[ka[j]], b[ka[j]])) return false;
return true;
}
return false;
}
// --- Platform functions injected into the SX env ---
var env = {
// Error catching — calls an SX thunk, returns result dict
"try-call": function(thunk) {
try {
Sx.eval([thunk], env);
return { ok: true };
} catch(e) {
return { ok: false, error: e.message || String(e) };
}
},
// Test reporting
"report-pass": function(name) {
testNum++;
passed++;
var fullName = suiteStack.concat([name]).join(" > ");
console.log("ok " + testNum + " - " + fullName);
},
"report-fail": function(name, error) {
testNum++;
failed++;
var fullName = suiteStack.concat([name]).join(" > ");
console.log("not ok " + testNum + " - " + fullName);
console.log(" # " + error);
},
// Suite context
"push-suite": function(name) { suiteStack.push(name); },
"pop-suite": function() { suiteStack.pop(); },
// Primitives that sx-browser.js has internally but doesn't expose through env
"equal?": function(a, b) { return deepEqual(a, b); },
"eq?": function(a, b) { return a === b; },
"boolean?": function(x) { return typeof x === "boolean"; },
"string-length": function(s) { return String(s).length; },
"substring": function(s, start, end) { return String(s).slice(start, end); },
"string-contains?": function(s, needle) { return String(s).indexOf(needle) !== -1; },
"upcase": function(s) { return String(s).toUpperCase(); },
"downcase": function(s) { return String(s).toLowerCase(); },
"reverse": function(c) { return c ? c.slice().reverse() : []; },
"flatten": function(c) {
var r = [];
for (var i = 0; i < (c||[]).length; i++) {
if (Array.isArray(c[i])) for (var j = 0; j < c[i].length; j++) r.push(c[i][j]);
else r.push(c[i]);
}
return r;
},
"has-key?": function(d, k) { return d && typeof d === "object" && k in d; },
"append": function(c, x) { return Array.isArray(x) ? (c||[]).concat(x) : (c||[]).concat([x]); },
};
// --- Read and evaluate test.sx ---
var src = fs.readFileSync(path.resolve(__dirname, "../ref/test.sx"), "utf8");
var exprs = Sx.parseAll(src);
console.log("TAP version 13");
for (var i = 0; i < exprs.length; i++) {
Sx.eval(exprs[i], env);
}
// --- Summary ---
console.log("");
console.log("1.." + testNum);
console.log("# tests " + (passed + failed));
console.log("# pass " + passed);
if (failed > 0) {
console.log("# fail " + failed);
process.exit(1);
}