Files
mesh-lab/src/suite.ts
T
jschoubben 751948f0f9 The lab had a registry that production does not, so it tested a fiction
The lab raised a `registry` VM, pushed ~73 images into it from the workstation, and
rewrote every manifest reference — third-party ones included — to point at it. No
production mesh has such a thing. So every bed proved that a machine could fetch an
image from a registry that exists nowhere else, and the bootstrap problems that only
appear when a machine has to fetch for itself went unfound.

What replaces it is the two things that are true in the world:

**Public images come from the public internet.** mesh-lab already created a NAT'd
uplink for exactly this and attached it to any machine declaring `egress`; no scenario
ever declared it. They do now, and third-party references are left exactly as the
catalogue writes them.

**The mesh's own images have no registry and never will.** mesh-control, mesh-builder,
mesh-route-proxy and the per-module runtimes are built from source and exist in no
registry. A machine gets them the way an operator's machine does — they are built here
and loaded onto it — and is then named by the digest of its own image configuration,
which mesh-host now accepts as "an image this machine already holds".

`images:` therefore means only *ours*, and a third-party entry is refused rather than
quietly loaded: otherwise the fiction returns one convenient line at a time. It is
per-machine as well, because "everything, everywhere" was never a description of
anything real — handing whole-mesh-full's union to its two 30GiB workstations would
fill the disk with runtimes nothing on them will start.

**The uplink and the declared gateway would have fought, silently.** A gateway container
and the transit router reach the scenario and nothing else; a default route through
either is a black hole for anything outside, and it beats the uplink's DHCP route on
metric. So a machine with egress states the scenario's ranges explicitly — through the
same gateway or transit it would have defaulted to, so the overlay-across-NAT path is
unchanged — and leaves the default to the uplink. A range with no path inside the
scenario becomes `unreachable` rather than falling through: 192.168.1.0/24 is an
ordinary private range in fact, and letting it escape would put scenario traffic on
whatever network the workstation is sitting on. `scenarioRoutesFor` is pure and tested,
because a decision only a full raise could check is one nobody checks.

The registry-reachability check the raise gained earlier is kept, pointed at the real
thing: every machine with egress must resolve a name and reach the internet before the
raise says it finished. Same failure it was written for — a raise that returns, an apply
that dies on its first pull, an instance left a bare shell — now guarding the path that
actually carries.

The base image's trust of the documentation ranges as plain-HTTP registries STAYS. It
was never only for the lab's registry: the mesh has one of its own, the `registry`
module, serving artifacts to the whole mesh over plain HTTP from whatever node runs it.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
2026-09-10 23:16:05 +02:00

108 lines
5.2 KiB
TypeScript

/**
* Run the end-to-end suite, and leave a receipt saying it ran.
*
* **Here rather than inside the tests, because the tests cannot know their own totals.** Node's
* runner reports them to whatever invoked it, and a test file inventing its own count would be a
* receipt that says whatever the last edit made it say.
*
* Here rather than in a shell script for the same reason the rebuild is: a step that lives in
* somebody's terminal history is a step that runs when they remember (novox/hq 04-ISSUES/005).
*/
import { spawn } from "node:child_process";
import { endToEnd, record, whatWasTested } from "./lastrun.ts";
import { rebuild } from "./rebuild.ts";
/** counted is what the runner said, or nulls when it said nothing recognisable. */
export function counted(output: string): { passed: number | null; failed: number | null } {
// The runner's own summary lines, each on a line of its own. Anchored, so a test *named*
// "pass 3" cannot be mistaken for the total — which is not a hypothetical worry in a suite whose
// tests are named in sentences.
// Stripped first: the runner colours its summary even when its stdout is a pipe, so the line is
// "\x1b[34m\u2139 pass 8\x1b[39m" and an anchored pattern never sees the start of it. Found by
// running this against the real runner — the fixture it was first written against was output I
// had imagined, which is a test that agrees with the mistake it was written beside.
const plain = output.replace(/\u001b\[[0-9;]*m/g, "");
const total = (what: RegExp) => {
const found = plain.match(what);
return found ? Number(found[1]) : null;
};
return {
passed: total(/^\s*(?:\u2139|#)\s*pass\s+(\d+)\s*$/m),
failed: total(/^\s*(?:\u2139|#)\s*fail\s+(\d+)\s*$/m),
};
}
export async function runSuite(args: string[]): Promise<number> {
const ran = args.filter((a) => a !== "--no-build");
const files = ran.length > 0 ? ran : [endToEnd];
if (!args.includes("--no-build")) {
// Before the run, always. The artifacts are built from two other repositories, and a suite
// that tests yesterday's binary reports on code nobody is looking at (novox/hq 04-ISSUES/005).
const built = rebuild();
if (built.length > 0) console.log(`built: ${built.join(", ")}\n`);
}
// Read now, while it is true. The receipt names these, and reading them when the run ends
// names whatever was committed during the twenty minutes in between instead.
const against = whatWasTested(process.env);
// **No canary.** There was one: a second scenario, one machine, raised first so a broken mesh
// failed in two minutes rather than in forty. It walked exactly the path the first three tests
// of the long run walk — a mesh comes up, a module lands, a consumer gets a credential — and
// the long run reaches the end of that path in about 160 seconds.
//
// So it cost a whole scenario, every passing run, to save about 45 seconds on a failing one.
// A scenario is machines that each boot a kernel, which is where the two minutes went.
// `test/integration/canary.test.ts` is still there and still runs when it is named; it is no
// longer raised on the way to everything else.
const { code, seen } = await runFiles(files);
console.log("\n" + reportOn(counted(seen), (p, f) => record(p, f, files, process.env, against)));
return code;
}
/** Run some test files, passing their output through as it arrives. */
async function runFiles(files: string[]): Promise<{ code: number; seen: string }> {
const running = spawn(
process.execPath,
["--test", "--test-concurrency=1", "--experimental-strip-types",
...files],
{ stdio: ["inherit", "pipe", "inherit"] },
);
let seen = "";
running.stdout.on("data", (chunk: Buffer) => {
// Passed through as it arrives: a suite that takes a quarter of an hour must not look hung.
process.stdout.write(chunk);
seen += chunk.toString();
});
const code: number = await new Promise((resolve) => {
running.on("close", (c) => resolve(c ?? 1));
});
return { code, seen };
}
/**
* reportOn decides whether this run says anything worth recording, and records it if so.
*
* Separated from the spawning so the decision can be tested: **no receipt rather than a guessed
* one** is the rule that keeps the record meaning something, and it was written where nothing
* could check it — which is 04-ISSUES/005 in miniature, inside the fix for it.
*/
export function reportOn(
counts: { passed: number | null; failed: number | null },
write: (passed: number, failed: number) => { against: Record<string, string>; ran: string[] },
): string {
if (counts.passed === null || counts.failed === null) {
// A run whose result could not be read is a run nobody can say anything about. Writing
// "0 failed" because nothing said otherwise is how a green record comes to mean nothing.
return "could not read what the runner reported; no receipt written";
}
const receipt = write(counts.passed, counts.failed);
const against = Object.entries(receipt.against).map(([n, c]) => `${n} ${c}`).join(", ");
return `recorded: ${receipt.ran.join(", ")} — ${counts.passed} passed, ` +
`${counts.failed} failed, against ${against || "nothing in git"}`;
}