Files
mesh-catalog/modules/lab/test/runs.test.ts
T
jochen c6a9c52dc7
mesh/merge-gate head c6a9c52dc7f2 merged into trunk 45d01e77c26d — pass: builds lab → shanks; no bus step; every machine composes with the change as it did…
mesh/repo-check head c6a9c52dc7f2 merged into trunk 45d01e77c26d — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered
Install each lab checkout where its lock file is, check out mesh-tools-go, and have lab_check catch both (hq issue 624)
lab_run ran npm ci at the root of mesh-sdk, mesh-tools and mesh-lab; mesh-tools keeps its package in
node-tools/ with no lock file at its root, so every run failed at its setup in nine seconds. A run now
installs (and, for the SDK and the base, builds) where a package-lock.json is: the root, or each
directory one level down; a package.json with no lock file fails the run naming it. It checks out
mesh-tools-go, which genesis builds since issue 616, and tells the beds where its checkouts are
(MESH_LAB_REPOS). lab_check asks the forge for every repository a run clones and applies the same install
rule to the ones it installs, so a run's setup failure is found before a run.
2026-10-13 11:55:01 +02:00

101 lines
4.9 KiB
TypeScript

import { test } from "node:test";
import assert from "node:assert/strict";
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { spawnSync } from "node:child_process";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { installPlan, installStep, NPM_REPOSITORIES, REPOSITORIES, script } from "../tools/runs.ts";
// Defends novox/hq issue 624: lab_run ran `npm ci` at the root of every checkout it installs, and
// mesh-tools keeps its package in node-tools/ with no lock file at its root, so every run failed at its
// setup, in 9 seconds, before anything was built.
function aCheckout(files: Record<string, string>): string {
const root = mkdtempSync(join(tmpdir(), "lab-checkout-"));
for (const [path, content] of Object.entries(files)) {
mkdirSync(join(root, path, ".."), { recursive: true });
writeFileSync(join(root, path), content);
}
return root;
}
test("a checkout with a lock file at its root is installed there", () => {
const root = aCheckout({ "package.json": "{}", "package-lock.json": "{}", "node-tools/package.json": "{}" });
assert.deepEqual(installPlan(root), { install: [root], unlocked: [] });
rmSync(root, { recursive: true, force: true });
});
test("a checkout whose package lives one directory down is installed there, as mesh-tools' node-tools", () => {
const root = aCheckout({ "node-tools/package.json": "{}", "node-tools/package-lock.json": "{}", "Dockerfile": "" });
assert.deepEqual(installPlan(root), { install: [join(root, "node-tools")], unlocked: [] });
rmSync(root, { recursive: true, force: true });
});
test("a package.json with no lock file is named, never installed", () => {
const root = aCheckout({ "package.json": "{}" });
assert.deepEqual(installPlan(root), { install: [], unlocked: [root] });
rmSync(root, { recursive: true, force: true });
});
// The shell a run executes follows the same rule: npm is asked where installPlan says, and a checkout
// with nothing installable fails the run loudly, naming it.
function runStep(root: string, build: boolean): { ok: boolean; said: string; asked: string[] } {
const bin = mkdtempSync(join(tmpdir(), "lab-npm-"));
const record = join(bin, "asked");
writeFileSync(join(bin, "npm"), `#!/bin/sh\necho "$(pwd) $*" >> ${JSON.stringify(record)}\n`);
chmodSync(join(bin, "npm"), 0o755);
const ran = spawnSync("bash", ["-c", `set -euo pipefail\n${installStep(root, build)}`], {
encoding: "utf8", env: { ...process.env, PATH: `${bin}:${process.env.PATH}` },
});
let asked: string[] = [];
try {
asked = readFileSync(record, "utf8").trim().split("\n").filter(Boolean);
} catch { /* nothing asked */ }
rmSync(bin, { recursive: true, force: true });
return { ok: ran.status === 0, said: `${ran.stdout}${ran.stderr}`, asked };
}
test("a run installs and builds mesh-tools in node-tools, where its lock file is", () => {
const root = aCheckout({ "node-tools/package.json": "{}", "node-tools/package-lock.json": "{}" });
const r = runStep(root, true);
assert.ok(r.ok, r.said);
assert.deepEqual(r.asked, [
`${join(root, "node-tools")} ci --no-audit --no-fund --loglevel=error`,
`${join(root, "node-tools")} run build --if-present`,
]);
rmSync(root, { recursive: true, force: true });
});
test("a run installs a checkout with its lock file at the root there, and builds only when asked", () => {
const root = aCheckout({ "package.json": "{}", "package-lock.json": "{}" });
const r = runStep(root, false);
assert.ok(r.ok, r.said);
assert.deepEqual(r.asked, [`${root} ci --no-audit --no-fund --loglevel=error`]);
rmSync(root, { recursive: true, force: true });
});
test("a run fails at its setup, naming the checkout, when nothing of it can be installed", () => {
const layouts: Record<string, string>[] = [{ "package.json": "{}" }, { "Dockerfile": "" }, { "sub/package.json": "{}" }];
for (const files of layouts) {
const root = aCheckout(files);
const r = runStep(root, false);
assert.equal(r.ok, false, `installed ${JSON.stringify(files)}`);
assert.match(r.said, /issue 624/);
assert.deepEqual(r.asked, []);
rmSync(root, { recursive: true, force: true });
}
});
// A run checks out mesh-tools-go too (novox/hq issue 616): genesis builds the Go toolchain from the
// repository beside mesh-tools, and a bed that raises genesis serves it. And it tells the beds where the
// checkouts are.
test("a run checks out mesh-tools-go and says where its checkouts are", () => {
assert.ok(REPOSITORIES.includes("mesh-tools-go"));
const s = script("/work", "run-x", "http://forge", ["test/integration/genesis-keeps-its-bus.test.ts"], {});
assert.match(s, /novox\/mesh-tools-go\.git' '\/work\/run-x\/mesh-tools-go'/);
assert.match(s, /export MESH_LAB_REPOS='\/work\/run-x'/);
assert.doesNotMatch(s, /cd '\/work\/run-x'\/\$repo && npm ci/);
for (const repo of NPM_REPOSITORIES) assert.ok(s.includes(`'/work/run-x/${repo}'/package-lock.json`), repo);
});