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
108 lines
5.2 KiB
TypeScript
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"}`;
|
|
}
|