88 lines
3.2 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
// Parity check for the plan viewer's JavaScript engine.
//
// Extracts the engine script block from index.html and runs every fixture
// in crates/plan-runner/fixtures/ through it, requiring the result to match
// the fixture's expected_bindings oracle, exactly as the Rust runner does.
// Also checks the provenance helpers: every output row of every node must
// have at least one contributing row in each of its inputs.
//
// The Rust crates remain the correctness oracle; this test only catches the
// viewer drifting away from them.
"use strict";
const fs = require("fs");
const path = require("path");
const vm = require("vm");
const html = fs.readFileSync(path.join(__dirname, "index.html"), "utf8");
const match = html.match(/<script id="engine">([\s\S]*?)<\/script>/);
if (!match) {
console.error("engine script block not found in index.html");
process.exit(1);
}
const sandbox = { module: { exports: {} }, console };
vm.runInNewContext(match[1], sandbox, { filename: "index.html#engine" });
const engine = sandbox.module.exports;
const fixturesDir = path.join(__dirname, "..", "..", "crates", "plan-runner", "fixtures");
const files = fs.readdirSync(fixturesDir).filter((f) => f.endsWith(".json")).sort();
if (files.length === 0) {
console.error("no fixtures found in " + fixturesDir);
process.exit(1);
}
// Every output row must have at least one contributing row in each input.
function checkProvenance(plan, tables, results) {
const byId = new Map(plan.query.nodes.map((n) => [n.id, n]));
for (const node of plan.query.nodes) {
const rel = results.get(node.id);
for (const row of rel.rows) {
const bindings = {};
rel.columns.forEach((c, i) => { bindings[c] = row[i]; });
if (node.action.scan) {
const scan = node.action.scan;
const matched = engine.scanMatches(tables.get(scan.table), scan.columns, bindings);
if (matched.length === 0) {
throw new Error("node " + node.id + ": output row has no matching fact row");
}
} else {
for (const id of [node.action.join.left, node.action.join.right]) {
const matched = engine.filterByBindings(results.get(id), bindings);
if (matched.length === 0) {
throw new Error(
"node " + node.id + ": output row has no contributing row in node " + id);
}
}
}
}
}
}
let failures = 0;
for (const file of files) {
const plan = JSON.parse(fs.readFileSync(path.join(fixturesDir, file), "utf8"));
try {
const tables = engine.buildTables(plan);
const results = engine.executePlan(plan, tables);
const verdict = engine.verifyBindings(plan, results.get(plan.query.root));
if (verdict.status !== "match") {
throw new Error("bindings " + verdict.status +
(verdict.message ? ": " + verdict.message : ""));
}
checkProvenance(plan, tables, results);
engine.layoutQuery(plan.query);
console.log("ok " + file);
} catch (err) {
failures += 1;
console.log("FAIL " + file + " (" + err.message + ")");
}
}
if (failures > 0) {
console.error(failures + " of " + files.length + " fixtures failed");
process.exit(1);
}
console.log("all " + files.length + " fixtures match their oracles");