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>
This commit is contained in:
108
shared/sx/tests/run.js
Normal file
108
shared/sx/tests/run.js
Normal file
@@ -0,0 +1,108 @@
|
||||
// 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);
|
||||
}
|
||||
Reference in New Issue
Block a user