W14: F8 cross-host differential battery (test-only) — CHECKLIST COMPLETE
Committed replacement for the review's ephemeral 130-probe corpus: spec/tests/differential-probes.txt (49 probes: F-1 int/float display, K18 overflow, F-3 apply + dict order, S-4 float printing, strings, collections, special forms, error normalization) evaluated on the native server (epoch protocol printer) and the SHIPPED WASM kernel (eval_wasm_probes.js via guest sx-serialize), diffed by scripts/test-differential.sh with a KNOWN_DIVERGENT heal-detecting ledger. Result: 46/49 agree. All 3 divergences share one root cause, verified live: bare sx_server's `apply` does not spread its argument list — (apply + (list 1 2 3)) errors "Expected number, got list", (apply str l) returns the serialized list; the WASM kernel spreads correctly and the test runner masks the bug with its own apply binding (F-7 class). Finding refinement: F-1's float-display divergence (0.3 vs 0.30000000000000004) is a K.eval JS-boundary artifact — guest-serialized output agrees across hosts; the battery therefore compares guest serialization. This completes the W14 checklist: 7 pin suites, 6 gate scripts/runners, 2 harness capabilities, C9 label cleanup, adapter-dom render coverage. Test-only: no semantics edits, no push. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
68
hosts/ocaml/browser/eval_wasm_probes.js
Executable file
68
hosts/ocaml/browser/eval_wasm_probes.js
Executable file
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env node
|
||||
// eval_wasm_probes.js — W14/F8: evaluate a file of probe expressions (one
|
||||
// per line, '#'-comments allowed) on the SHIPPED browser kernel and print
|
||||
// PROBE <n> <result-or-ERROR>
|
||||
// per line, for diffing against the native server (scripts/test-differential.sh).
|
||||
// Boot stubs mirror test_wasm_native.js / run_wasm_corpus.js.
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const PROJECT_ROOT = path.resolve(__dirname, '../../..');
|
||||
const WASM_DIR = path.join(PROJECT_ROOT, 'shared/static/wasm');
|
||||
|
||||
const probeFile = process.argv[2];
|
||||
if (!probeFile) { console.error('usage: eval_wasm_probes.js <probes.txt>'); process.exit(2); }
|
||||
|
||||
global.window = global;
|
||||
global.document = {
|
||||
createElement: () => ({ style: {}, setAttribute() {}, appendChild() {}, children: [] }),
|
||||
createDocumentFragment: () => ({ appendChild() {}, children: [], childNodes: [] }),
|
||||
head: { appendChild() {} }, body: { appendChild() {} },
|
||||
querySelector: () => null, querySelectorAll: () => [],
|
||||
createTextNode: s => ({ textContent: s }), addEventListener() {},
|
||||
createComment: s => ({ textContent: s || '' }),
|
||||
getElementsByTagName: () => [],
|
||||
};
|
||||
global.localStorage = { getItem: () => null, setItem() {}, removeItem() {} };
|
||||
global.CustomEvent = class { constructor(n, o) { this.type = n; this.detail = (o || {}).detail || {}; } };
|
||||
global.MutationObserver = class { observe() {} disconnect() {} };
|
||||
global.requestIdleCallback = fn => setTimeout(fn, 0);
|
||||
global.matchMedia = () => ({ matches: false });
|
||||
global.navigator = { serviceWorker: { register: () => Promise.resolve() } };
|
||||
global.location = { href: '', pathname: '/', hostname: 'localhost' };
|
||||
global.history = { pushState() {}, replaceState() {} };
|
||||
global.fetch = () => Promise.resolve({ ok: true, text: () => Promise.resolve('') });
|
||||
|
||||
async function main() {
|
||||
require(path.join(WASM_DIR, 'sx_browser.bc.wasm.js'));
|
||||
const K = await new Promise((resolve, reject) => {
|
||||
let tries = 0;
|
||||
const poll = setInterval(() => {
|
||||
if (globalThis.SxKernel) { clearInterval(poll); resolve(globalThis.SxKernel); }
|
||||
else if (++tries > 200) { clearInterval(poll); reject(new Error('SxKernel not found')); }
|
||||
}, 50);
|
||||
});
|
||||
|
||||
const lines = fs.readFileSync(probeFile, 'utf8').split('\n');
|
||||
let n = 0;
|
||||
for (const raw of lines) {
|
||||
const line = raw.trim();
|
||||
if (!line || line.startsWith('#')) continue;
|
||||
n++;
|
||||
let out;
|
||||
try {
|
||||
// Serialize through the kernel's own printer so both hosts emit SX
|
||||
// text (K.eval returns raw JS values otherwise — [object Object]).
|
||||
const r = K.eval(`(sx-serialize ${line})`);
|
||||
out = (typeof r === 'string') ? r : String(r);
|
||||
} catch (e) {
|
||||
out = 'ERROR';
|
||||
}
|
||||
// errors normalized: kernel returns "Error: ..." strings for eval errors
|
||||
if (typeof out === 'string' && out.startsWith('Error')) out = 'ERROR';
|
||||
console.log(`PROBE ${n} ${out.replace(/\n/g, '\\n')}`);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(e => { console.error('FATAL:', e.message); process.exit(1); });
|
||||
Reference in New Issue
Block a user