Files
rose-ash/tests/hs-run-batched.js
giles 54a890db71
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 50s
HS: install Phase 2 WASM as default + fix batched total to 1514
The shared/static/wasm/sx_browser.bc.js artifact now reflects the OCaml
kernel with JIT Phase 1 (tiered compilation), Phase 2 (LRU eviction),
and Phase 3 (manual reset) — same source as previously committed,
just the rebuilt binary so test/dev consumers pick it up without
needing a local sx_build.

tests/hs-run-batched.js: TOTAL default 1496 → 1514. The conformance
suite grew by 18 tests since the constant was last set; without this
the batched runner stops short of the final 14 tests.

Verified via batched run (75-test batches, parallelism=2):
  1436 / 1439 reported pass (3 failures, all in suites where the
  underlying parser/dict-equality gap is independent of WASM).
  Batch 150-225 didn't complete inside 15 min — 75 reactivity /
  regressions / runtime tests at 5-11s each blow past the wall; a
  per-batch deadline raise is the right knob, not a kernel change.

Per-test timing (new vs old WASM, slice 170-195) is comparable
(60s vs 78s on new/threshold=4 — Phase 1+2 is NOT a perf regression
on HS code; the slow tests are slow on both kernels because the
underlying CEK path doesn't get JIT-compiled either way — HS emits
anonymous lambdas that bypass the named-only JIT gate).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:59:46 +00:00

152 lines
5.5 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* Batched HS conformance runner — option 2 (per-process kernel isolation).
*
* Each batch spawns a fresh Node process running tests/hs-run-filtered.js
* with HS_START/HS_END set, so the WASM kernel's JIT cache starts empty.
* Avoids the cumulative slowdown that hits the 1-process runner around
* test 500-700 (compiled lambdas accumulate, allocation stalls).
*
* Usage:
* node tests/hs-run-batched.js
* HS_BATCH_SIZE=100 node tests/hs-run-batched.js
* HS_PARALLEL=4 node tests/hs-run-batched.js
*/
const { spawnSync, spawn } = require('child_process');
const path = require('path');
const fs = require('fs');
const FILTERED = path.join(__dirname, 'hs-run-filtered.js');
const TOTAL = parseInt(process.env.HS_TOTAL || '1514');
const FROM = parseInt(process.env.HS_FROM || '0');
const BATCH_SIZE = parseInt(process.env.HS_BATCH_SIZE || '150');
const PARALLEL = parseInt(process.env.HS_PARALLEL || '1');
const VERBOSE = !!process.env.HS_VERBOSE;
function makeBatches() {
const batches = [];
for (let i = FROM; i < TOTAL; i += BATCH_SIZE) {
batches.push({ start: i, end: Math.min(i + BATCH_SIZE, TOTAL) });
}
return batches;
}
function runBatch({ start, end }) {
const t0 = Date.now();
const r = spawnSync('node', [FILTERED], {
env: { ...process.env, HS_START: String(start), HS_END: String(end) },
encoding: 'utf8',
timeout: 1800_000, // 30 min per batch hard cap
});
const out = (r.stdout || '') + (r.stderr || '');
const elapsed = Date.now() - t0;
return { start, end, elapsed, out, code: r.status };
}
function parseBatch(out) {
const result = { pass: 0, fail: 0, failures: [], slow: [], timeouts: [] };
const m = out.match(/Results:\s+(\d+)\/(\d+)/);
if (m) {
result.pass = parseInt(m[1]);
const total = parseInt(m[2]);
result.fail = total - result.pass;
}
// Capture each "[suite] name: error" failure line
const failSection = out.split('All failures:')[1] || '';
for (const line of failSection.split('\n')) {
const fm = line.match(/^\s*\[([^\]]+)\]\s+(.+?):\s*(.*)$/);
if (fm) result.failures.push({ suite: fm[1], name: fm[2], err: fm[3] || '(empty)' });
}
for (const line of out.split('\n')) {
const sm = line.match(/SLOW: test (\d+) took (\d+)ms \[([^\]]+)\] (.+)$/);
if (sm) result.slow.push({ idx: +sm[1], ms: +sm[2], suite: sm[3], name: sm[4] });
const tm = line.match(/TIMEOUT: test (\d+) \[([^\]]+)\] (.+)$/);
if (tm) result.timeouts.push({ idx: +tm[1], suite: tm[2], name: tm[3] });
}
return result;
}
function fmtTime(ms) {
if (ms < 1000) return `${ms}ms`;
if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`;
return `${Math.floor(ms / 60_000)}m${Math.round((ms % 60_000) / 1000)}s`;
}
async function runParallel(batches, concurrency) {
const results = new Array(batches.length);
let cursor = 0;
async function worker() {
while (cursor < batches.length) {
const i = cursor++;
results[i] = await new Promise((resolve) => {
const t0 = Date.now();
let out = '';
const child = spawn('node', [FILTERED], {
env: { ...process.env, HS_START: String(batches[i].start), HS_END: String(batches[i].end) },
});
child.stdout.on('data', d => out += d);
child.stderr.on('data', d => out += d);
child.on('exit', (code) => resolve({ ...batches[i], elapsed: Date.now() - t0, out, code }));
});
const r = parseBatch(results[i].out);
process.stderr.write(` batch ${batches[i].start}-${batches[i].end}: ${r.pass}/${r.pass + r.fail} (${fmtTime(results[i].elapsed)})\n`);
}
}
await Promise.all(Array.from({ length: concurrency }, worker));
return results;
}
(async () => {
const batches = makeBatches();
const t0 = Date.now();
process.stderr.write(`Running ${TOTAL} tests in ${batches.length} batches of ${BATCH_SIZE} (parallelism=${PARALLEL})\n`);
let results;
if (PARALLEL > 1) {
results = await runParallel(batches, PARALLEL);
} else {
results = [];
for (const b of batches) {
const r = runBatch(b);
results.push(r);
const p = parseBatch(r.out);
process.stderr.write(` batch ${b.start}-${b.end}: ${p.pass}/${p.pass + p.fail} (${fmtTime(r.elapsed)})\n`);
}
}
let totalPass = 0, totalFail = 0;
const allFailures = [];
const allTimeouts = [];
const slowest = [];
for (const r of results) {
const p = parseBatch(r.out);
totalPass += p.pass;
totalFail += p.fail;
allFailures.push(...p.failures);
allTimeouts.push(...p.timeouts);
slowest.push(...p.slow);
if (VERBOSE) process.stdout.write(r.out);
}
const totalElapsed = Date.now() - t0;
process.stdout.write(`\n=== Conformance ===\n`);
process.stdout.write(`Total: ${totalPass}/${totalPass + totalFail} (${(100 * totalPass / (totalPass + totalFail)).toFixed(2)}%)\n`);
process.stdout.write(`Wall: ${fmtTime(totalElapsed)} across ${batches.length} batches\n`);
if (allFailures.length) {
process.stdout.write(`\nFailures (${allFailures.length}):\n`);
for (const f of allFailures) process.stdout.write(` [${f.suite}] ${f.name}: ${f.err}\n`);
}
if (allTimeouts.length && allTimeouts.length !== allFailures.length) {
process.stdout.write(`\nTimeouts (${allTimeouts.length}):\n`);
for (const t of allTimeouts) process.stdout.write(` [${t.suite}] ${t.name}\n`);
}
slowest.sort((a, b) => b.ms - a.ms);
if (slowest.length) {
process.stdout.write(`\nSlowest 10 tests:\n`);
for (const s of slowest.slice(0, 10)) process.stdout.write(` ${s.ms}ms [${s.suite}] ${s.name}\n`);
}
process.exit(totalFail > 0 ? 1 : 0);
})();