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>
109 lines
3.6 KiB
JavaScript
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);
|
|
}
|