Replace ambiguous setup-boundary observations with calibrated, repeatable timeout ownership evidence. - record fixture lifecycle events in an append-only JSONL ledger with process-safe ordering - classify four timeout-budget arms across repeated isolate-mode cells and fail closed on incomplete evidence - expand connectionless unit coverage and document the terminal insufficient-data survey result Files changed: .../test-failures/postgres-ddl-admission-bound.md | 2 + .../vitest-setup-boundary-timeout-ownership.md | 37 +++ docs/testing.md | 2 +- scripts/__tests__/pg-setup-boundary-probe.test.mjs | 195 ++++++++---- scripts/pg-setup-boundary-probe.mjs | 341 ++++++++++++--------- 5 files changed, 371 insertions(+), 206 deletions(-) Fusion-Task-Id: FN-9140 Fusion-Task-Lineage: 9c6970b8-af80-400a-b2b2-49718d4fe87f Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
154 lines
9.9 KiB
JavaScript
154 lines
9.9 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import test from "node:test";
|
|
import { URL } from "node:url";
|
|
|
|
import {
|
|
classifyArm,
|
|
decideSurveyVerdict,
|
|
deriveBudgets,
|
|
fixtureFiles,
|
|
parseLedger,
|
|
parseProbeArgs,
|
|
resolveOwnership,
|
|
resolveVitestRunner,
|
|
runFixtureCell,
|
|
runSurvey,
|
|
summarizeLedger,
|
|
summarizeRepeats,
|
|
} from "../pg-setup-boundary-probe.mjs";
|
|
|
|
const event = (overrides = {}) => ({ type: "boundary", boundary: "sample", phase: "start", pid: 1, file: "one", epochMs: 1000, monotonicNs: "100", ...overrides });
|
|
const options = { workers: 2, files: 6, repeats: 3, isolate: true, delayMs: 4000, orderingMarginMs: 50, runTimeoutMs: 60_000 };
|
|
|
|
/*
|
|
FNXC:PgSetupBoundaryProbe 2026-08-17-23:40:
|
|
FN-9140 keeps survey verification connectionless by using synthetic ledgers for ownership and
|
|
ordering. The opt-in smoke is the only test that starts Vitest, so normal script checks remain fast.
|
|
*/
|
|
test("arguments derive a discriminating matrix and reject undecidable file/worker counts", () => {
|
|
assert.deepEqual(deriveBudgets(4000), { D: 4000, SMALL: 1000, LARGE: 20000 });
|
|
assert.deepEqual(parseProbeArgs([]), { ...options, isolate: "both", json: undefined, runTimeoutMs: 74_000 });
|
|
assert.throws(() => parseProbeArgs(["--files", "2", "--workers", "2"]), /indeterminate configuration/);
|
|
assert.throws(() => parseProbeArgs(["--isolate", "maybe"]), /isolate/);
|
|
});
|
|
|
|
test("ledger parser ignores malformed or truncated final records", () => {
|
|
const text = `${JSON.stringify(event())}\n{"type":"boundary"`;
|
|
assert.deepEqual(parseLedger(text), [{ ...event(), ledgerIndex: 0 }]);
|
|
});
|
|
|
|
test("ledger granularity distinguishes worker and file execution with six files", () => {
|
|
const workerEvents = ["0", "1"].flatMap((file, pid) => [event({ pid: pid + 1, file }), event({ pid: pid + 1, file, phase: "end", epochMs: 5000, monotonicNs: "500" })]);
|
|
assert.equal(summarizeLedger("sample", workerEvents, options).granularity, "per worker");
|
|
const fileEvents = Array.from({ length: 6 }, (_, index) => event({ pid: index % 2 + 1, file: String(index) }));
|
|
assert.equal(summarizeLedger("sample", fileEvents, options).granularity, "per file");
|
|
});
|
|
|
|
test("setup-file granularity requires a real test-file identity across worker lifecycles", () => {
|
|
/*
|
|
FNXC:PgSetupBoundaryProbe 2026-08-17-05:34:
|
|
Persistent workers can execute several files; each setup callback must retain the active path.
|
|
*/
|
|
const persistentWorkers = [event({ pid: 1, file: "a.test.mjs" }), event({ pid: 1, file: "b.test.mjs" }), event({ pid: 2, file: "c.test.mjs" })];
|
|
assert.equal(summarizeLedger("sample", persistentWorkers, { ...options, files: 3 }).granularity, "per file");
|
|
/*
|
|
FNXC:PgSetupBoundaryProbe 2026-08-17-05:34:
|
|
Recycled workers may produce as many PIDs as files. Empty setup-file paths must not fake per-file proof.
|
|
*/
|
|
const recycledWorkers = [event({ pid: 11, file: "" }), event({ pid: 12, file: "" }), event({ pid: 13, file: "" })];
|
|
assert.equal(summarizeLedger("sample", recycledWorkers, { ...options, files: 3 }).granularity, "indeterminate");
|
|
});
|
|
|
|
test("ordering uses same-process monotonic time and cross-process epoch margin", () => {
|
|
const samePid = [event(), event({ type: "test", boundary: "test", pid: 1, phase: "start", monotonicNs: "200", epochMs: 900 })];
|
|
assert.equal(summarizeLedger("sample", samePid, options).ordering, "before-first-test");
|
|
const near = [event({ pid: 1, epochMs: 1000 }), event({ type: "test", boundary: "test", pid: 2, phase: "start", epochMs: 1020, monotonicNs: "1" })];
|
|
assert.equal(summarizeLedger("sample", near, options).ordering, "indeterminate");
|
|
const before = [event({ pid: 1, epochMs: 1000 }), event({ type: "test", boundary: "test", pid: 2, phase: "start", epochMs: 1100, monotonicNs: "1" })];
|
|
assert.equal(summarizeLedger("sample", before, options).ordering, "before-first-test");
|
|
assert.equal(summarizeLedger("sample", [event()], options).ordering, "not-observed");
|
|
});
|
|
|
|
test("unrecognized failures retain their diagnostic and never become off-budget", () => {
|
|
const result = classifyArm({ exitCode: 1, output: "Error: unrelated fixture failure", events: [], boundary: "sample", delayMs: 4000 });
|
|
assert.deepEqual(result, { outcome: "failed-unclassified", diagnostic: "Error: unrelated fixture failure" });
|
|
assert.equal(resolveOwnership({ A: result, B: { outcome: "passed" }, C: { outcome: "passed" }, R: { outcome: "passed" } }), "failed-unclassified");
|
|
});
|
|
|
|
test("only a duration-verified both-small pass establishes falsifying off-budget evidence", () => {
|
|
const passed = { outcome: "passed" };
|
|
assert.equal(resolveOwnership({ A: { outcome: "timed-out-test" }, B: passed, C: passed, R: passed }), "failed-unclassified");
|
|
assert.equal(resolveOwnership({ R: passed }), "completed-within-budget");
|
|
assert.equal(resolveOwnership({ A: passed, B: passed, C: passed, R: passed }), "off-budget");
|
|
const short = classifyArm({ exitCode: 0, events: [event(), event({ phase: "end", epochMs: 2000, monotonicNs: "200" })], boundary: "sample", delayMs: 4000 });
|
|
assert.equal(short.outcome, "failed-unclassified");
|
|
assert.equal(resolveOwnership({ A: passed, B: { outcome: "timed-out-test" }, C: passed, R: passed }), "failed-unclassified");
|
|
});
|
|
|
|
test("repeat disagreement, bad calibration, and indeterminate ordering force insufficient data", () => {
|
|
const stable = {
|
|
ownership: "off-budget",
|
|
arms: { A: { outcome: "passed" }, B: { outcome: "passed" }, C: { outcome: "passed" }, R: { outcome: "passed" } },
|
|
summaries: Object.fromEntries(["A", "B", "C", "R"].map((arm) => [arm, { ordering: "before-first-test", granularity: "per worker", durationMs: 4000 }])),
|
|
};
|
|
const flapping = summarizeRepeats("sample", [stable, { ...stable, ownership: "test-timeout" }]);
|
|
assert.equal(flapping.deterministic, false);
|
|
assert.equal(decideSurveyVerdict({ calibration: "passed", cells: [flapping] }), "insufficient-data");
|
|
assert.equal(decideSurveyVerdict({ calibration: "failed", cells: [{ ...stable, deterministic: true, ordering: "before-first-test", ownership: "off-budget" }] }), "insufficient-data");
|
|
assert.equal(decideSurveyVerdict({ calibration: "passed", cells: [{ ...stable, deterministic: true, ordering: "indeterminate", ownership: "off-budget" }] }), "insufficient-data");
|
|
assert.equal(decideSurveyVerdict({ calibration: "passed", cells: [{ ...stable, deterministic: true, ordering: "after-first-test", ownership: "completed-within-budget" }] }), "prerequisite-not-established");
|
|
assert.equal(decideSurveyVerdict({ calibration: "passed", cells: [{ ...stable, deterministic: true, ordering: "before-first-test", ownership: "off-budget" }] }), "prerequisite-established");
|
|
});
|
|
|
|
test("arm-level ordering and outcomes must agree across repeats", () => {
|
|
const repeat = {
|
|
ownership: "off-budget",
|
|
arms: { A: { outcome: "passed" }, B: { outcome: "passed" }, C: { outcome: "passed" }, R: { outcome: "passed" } },
|
|
summaries: Object.fromEntries(["A", "B", "C", "R"].map((arm) => [arm, { ordering: "before-first-test", granularity: "per invocation", durationMs: 4000 }])),
|
|
};
|
|
const aAfterTest = JSON.parse(JSON.stringify(repeat));
|
|
aAfterTest.summaries.A.ordering = "after-first-test";
|
|
const armFlap = JSON.parse(JSON.stringify(repeat));
|
|
armFlap.arms.B.outcome = "timed-out-test";
|
|
assert.equal(summarizeRepeats("sample", [repeat, aAfterTest]).deterministic, false);
|
|
assert.equal(summarizeRepeats("sample", [repeat, armFlap]).deterministic, false);
|
|
});
|
|
|
|
test("fixture-local config contains derived budgets and survey remains repository-inert", () => {
|
|
const fixture = fixtureFiles({ boundary: "setup-top-level-await", arm: "A", options, ledgerPath: "/tmp/ledger", vitestApiUrl: "vitest", control: undefined });
|
|
assert.match(fixture["vitest.config.mjs"], /testTimeout:1000/);
|
|
assert.match(fixture["vitest.config.mjs"], /hookTimeout:1000/);
|
|
const perFileFixture = fixtureFiles({ boundary: "setup-before-all", arm: "A", options, ledgerPath: "/tmp/ledger", vitestApiUrl: "vitest", control: undefined });
|
|
assert.match(perFileFixture["setup.mjs"], /expect\.getState\(\)\.testPath/);
|
|
const source = readFileSync(new URL("../pg-setup-boundary-probe.mjs", import.meta.url), "utf8");
|
|
assert.doesNotMatch(source, /pg-test-harness|pg-preadmission-campaign|packages\/core\/src\/__test-utils__|FUSION_PG_TEST_/);
|
|
assert.match(source, /mkdtempSync\(join\(tmpdir\(\)/);
|
|
});
|
|
|
|
test("unresolvable Vitest becomes a retained insufficient-data report", async () => {
|
|
const runner = resolveVitestRunner(() => { throw new Error("test runner unavailable"); });
|
|
assert.equal(runner.vitestVersion, "unresolvable");
|
|
const result = await runFixtureCell({ boundary: "global-setup", arm: "A", options, runner });
|
|
assert.equal(result.arm.outcome, "failed-unclassified");
|
|
assert.match(result.arm.diagnostic, /Vitest resolution failed: test runner unavailable/);
|
|
const report = await runSurvey({ ...options, repeats: 1, isolate: "true" }, runner);
|
|
assert.equal(report.calibration, "failed");
|
|
assert.equal(report.calibrationFailureReason, "classifier-uncalibrated");
|
|
assert.equal(report.verdict, "insufficient-data");
|
|
assert.match(report.runnerResolutionError, /test runner unavailable/);
|
|
assert.ok(report.cells.every((cell) => cell.ownership === "failed-unclassified"));
|
|
});
|
|
|
|
test("live fixture cells preserve setup-file identities on the installed runner", { skip: process.env.FUSION_BOUNDARY_SURVEY_LIVE !== "1" }, async () => {
|
|
const liveOptions = { ...options, files: 3, workers: 2, delayMs: 100, runTimeoutMs: 60_000, isolate: false };
|
|
const global = await runFixtureCell({ boundary: "global-setup", arm: "A", options: liveOptions });
|
|
assert.ok(global.events.length > 0);
|
|
assert.equal(global.arm.outcome, "passed");
|
|
const setup = await runFixtureCell({ boundary: "setup-before-all", arm: "R", options: liveOptions });
|
|
const summary = summarizeLedger("setup-before-all", setup.events, liveOptions);
|
|
assert.equal(setup.arm.outcome, "passed");
|
|
assert.equal(summary.granularity, "per file");
|
|
assert.equal(summary.filesObserved, 3);
|
|
});
|