A run rebuilds what it tests, and leaves a receipt saying what it covered
The danger is not that the suite breaks. It is that nobody notices it stopped running (novox/hq 04-ISSUES/005). The harness this replaces had not built for two and a half months and nothing said so — and this suite needs a hypervisor, so it inherits exactly that: it runs when somebody remembers, and remembering is not a mechanism. So running, recording, and rebuilding are one act: - the host binary, control-plane image and builder are rebuilt from source first. The last two both parse manifests; building one and not the other left a binary eleven hours old refusing a field the mesh had just renamed, found by a full run. - a receipt lands in XDG state — outside git, because the question is whether *this machine* has run it, and a receipt in git would be a claim about everybody's machine made by whoever committed last. - `last-run` judges it and exits non-zero when it no longer counts. Three faults found by running the thing rather than reading it, each now held by a test confirmed to fail without it: - counted() passed every test while parsing nothing. The runner colours its summary even into a pipe; the fixtures were clean text that had been imagined rather than captured. A fixture that agrees with the mistake proves the mistake. - a receipt for `suite test/lastrun.test.ts` was indistinguishable from one for the real thing — 005's own symptom, rebuilt inside its remedy. The receipt now records what ran. - a tree with uncommitted work reported the bare commit, claiming coverage of code nobody can check out. Nothing else could tell: the hash is identical either way. Proven on real machines: 22/22, against all three repositories.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Rebuild what the lab runs, from source, before it runs.
|
||||
*
|
||||
* **A stale artifact reporting success against old rules is the fault this project keeps writing
|
||||
* down** (novox/hq 04-ISSUES/005). The lab consumes three artifacts from two repositories, and they
|
||||
* were rebuilt by hand, one at a time, from memory. A rename in the control plane's catalogue needs
|
||||
* both the control-plane image *and* the builder binary, because both parse manifests; rebuilding
|
||||
* one left a binary eleven hours old refusing a field the mesh had just renamed, and cost a full
|
||||
* run to find out.
|
||||
*
|
||||
* In the repository rather than in a shell script beside it, for the reason 005 is about: a step
|
||||
* that lives in somebody's terminal history runs when they remember, and remembering is not a
|
||||
* mechanism.
|
||||
*/
|
||||
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { repositories } from "./repos.ts";
|
||||
|
||||
export interface Build {
|
||||
/** What it produces, for the log. */
|
||||
what: string;
|
||||
/** The repository root to run in. */
|
||||
in: string;
|
||||
argv: string[];
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}
|
||||
|
||||
/**
|
||||
* planned is what must be built, given where this run has been pointed.
|
||||
*
|
||||
* Derived from the same environment the suite is configured by, so there is one place that says
|
||||
* where a repository is. A repository this run was not pointed at is not built — and, per
|
||||
* {@link whatWasTested}, is not claimed in the receipt either.
|
||||
*/
|
||||
export function planned(env: NodeJS.ProcessEnv = process.env): Build[] {
|
||||
const builds: Build[] = [];
|
||||
const where = repositories(env);
|
||||
const host = env["MESH_LAB_HOST_BINARY"];
|
||||
if (host && where["mesh-host"]) {
|
||||
builds.push({
|
||||
what: "host",
|
||||
in: where["mesh-host"],
|
||||
argv: ["go", "build", "-ldflags=-s -w -X main.builtFor=arch", "-o", host, "./cmd/mesh-host"],
|
||||
env: { CGO_ENABLED: "0" },
|
||||
});
|
||||
}
|
||||
const control = where["mesh-control"];
|
||||
if (control) {
|
||||
builds.push({ what: "control plane image", in: control, argv: ["make", "image"] });
|
||||
const builder = env["MESH_LAB_BUILDER"];
|
||||
if (builder) {
|
||||
// Both of these parse manifests. Building one and not the other is the eleven-hour-old
|
||||
// binary above, so they are one step and not two.
|
||||
builds.push({
|
||||
what: "builder",
|
||||
in: control,
|
||||
argv: ["go", "build", "-o", builder, "./cmd/mesh-builder"],
|
||||
});
|
||||
}
|
||||
}
|
||||
return builds;
|
||||
}
|
||||
|
||||
/** rebuild runs the plan, and throws on the first failure rather than testing a stale artifact. */
|
||||
export function rebuild(env: NodeJS.ProcessEnv = process.env): string[] {
|
||||
const built: string[] = [];
|
||||
for (const build of planned(env)) {
|
||||
const [command, ...args] = build.argv;
|
||||
const ran = spawnSync(command!, args, {
|
||||
cwd: build.in,
|
||||
env: { ...env, ...build.env },
|
||||
encoding: "utf8",
|
||||
});
|
||||
if (ran.status !== 0) {
|
||||
// Loudly, and stopping. A suite that runs anyway is a suite reporting on code that is not
|
||||
// the code in front of you, which is the whole of 005.
|
||||
throw new Error(
|
||||
`could not build the ${build.what}: ${build.argv.join(" ")} in ${build.in}\n\n` +
|
||||
`${(ran.stderr || ran.stdout || String(ran.error)).trim()}`,
|
||||
);
|
||||
}
|
||||
built.push(build.what);
|
||||
}
|
||||
return built;
|
||||
}
|
||||
Reference in New Issue
Block a user