Files
mesh-lab/test/base-mirrors.test.ts
T
jochen b053dc66bf
mesh/merge-gate head b053dc66bf9d merged into trunk c0b1225f6d7d — pass: the change touches no module of the mesh's graph
mesh/repo-check head b053dc66bf9d merged into trunk c0b1225f6d7d — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (typescript)…
mesh/delivery delivered
Give the base build several mirrors and retry a failed pacman step (issue 700)
The stock Arch image names one mirror, and the base build failed twice in
a morning when mirrors.kernel.org timed out and then answered 500. Write a
fixed list of six mirrors before anything is installed, so pacman falls
back per file, and retry a failed step three times with a growing pause,
saying which mirror failed.
2026-10-14 07:39:30 +02:00

101 lines
4.8 KiB
TypeScript

import { test } from "node:test";
import assert from "node:assert/strict";
import { IncusError } from "../src/incus/client.ts";
import {
buildBaseImage, failedMirrors, MIRRORS, mirrorlist, PACMAN_BACKOFF_MS, pacmanIn, type BaseDeps,
} from "../src/lifecycle/base.ts";
import { BASE_IMAGE_ALIAS } from "../src/lifecycle/place.ts";
/**
* Defends novox/hq issue 700: the base build installed from the one mirror the stock image names,
* mirrors.kernel.org, and failed twice in a morning when that mirror timed out and then answered 500.
*/
const KERNEL_500 = "error: failed retrieving file 'wireguard-tools-1.0.20260223-1-x86_64.pkg.tar.zst' from " +
"mirrors.kernel.org : The requested URL returned error: 500\nwarning: failed to retrieve some files\n" +
"error: failed to commit transaction (failed to retrieve some files)";
/** A hypervisor for one base build: pacman fails `fails` times per step, then installs. */
function aBuilder(fails: number, said = KERNEL_500) {
const ran: string[] = [];
const slept: number[] = [];
let mirrorlistWritten = "";
const tries = new Map<string, number>();
const deps: BaseDeps = {
incus: async (args) => {
ran.push(args.join(" "));
const at = args.indexOf("pacman");
if (at >= 0) {
const step = args.slice(at).join(" ");
const n = (tries.get(step) ?? 0) + 1;
tries.set(step, n);
if (n <= fails) throw new IncusError(args, said, 1);
}
const printf = args.join(" ").match(/printf '%s' '([\s\S]*)' > \/etc\/pacman.d\/mirrorlist/);
if (printf) mirrorlistWritten = printf[1]!;
return { stdout: "", stderr: "" };
},
incusOk: async (args) => {
const c = args.join(" ");
if (c.endsWith("cat /etc/pacman.d/mirrorlist")) return mirrorlistWritten;
if (c.includes("{{.ServerVersion}}")) return "29.8.2";
if (c.includes("InsecureRegistryCIDRs")) return "[127.0.0.0/8 192.0.2.0/24]";
if (c.startsWith("image list")) return BASE_IMAGE_ALIAS;
return "tool 1.0";
},
succeeds: async () => true,
sleep: async (ms) => { slept.push(ms); },
};
return { deps, ran, slept };
}
test("the builder is given several mirrors, the stock one last, before anything is installed", async () => {
const hosts = MIRRORS.map((m) => new URL(m.replace("$repo", "core").replace("$arch", "x86_64")).host);
assert.ok(new Set(hosts).size >= 4, `too few mirrors: ${hosts.join(", ")}`);
assert.equal(hosts.at(-1), "mirrors.kernel.org");
assert.ok(!mirrorlist().includes("'"), "the mirrorlist is written inside single quotes and holds one");
assert.equal(mirrorlist().split("\n").filter((l) => l.startsWith("Server = ")).length, MIRRORS.length);
const b = aBuilder(0);
await buildBaseImage(() => {}, b.deps);
const wrote = b.ran.findIndex((c) => c.includes("/etc/pacman.d/mirrorlist"));
const firstPacman = b.ran.findIndex((c) => c.includes(" pacman "));
assert.ok(wrote >= 0 && wrote < firstPacman, "pacman ran before the mirrorlist was written");
});
test("a pacman step that fails is tried again after a pause, saying which mirror failed", async () => {
const said: string[] = [];
const b = aBuilder(2);
await pacmanIn("builder", ["-S", "--noconfirm", "wireguard-tools"], (m) => said.push(m), b.deps);
assert.equal(b.ran.length, 3);
assert.deepEqual(b.slept, PACMAN_BACKOFF_MS.slice(0, 2));
assert.ok(PACMAN_BACKOFF_MS[1]! > PACMAN_BACKOFF_MS[0]!, "no backoff");
assert.equal(said.length, 2);
assert.match(said[0]!, /failed from mirrors\.kernel\.org \(error: failed retrieving file 'wireguard-tools/);
assert.match(said[0]!, /trying again in 10s \(attempt 2 of 4\)/);
});
test("a step that keeps failing stops the build, naming every mirror that failed", async () => {
const b = aBuilder(99, "error: too many errors from mirrors.kernel.org\nerror: failed retrieving file 'x' from ftp.fau.de : 500");
await assert.rejects(
pacmanIn("builder", ["-Sy", "--noconfirm", "docker"], () => {}, b.deps),
/failed 4 times; the mirrors that failed: mirrors\.kernel\.org, ftp\.fau\.de/,
);
assert.equal(b.ran.length, PACMAN_BACKOFF_MS.length + 1);
});
test("a whole base build rides out a mirror's bad minute on every package", async () => {
const b = aBuilder(1);
const built = await buildBaseImage(() => {}, b.deps);
assert.equal(built.alias, BASE_IMAGE_ALIAS);
const pacman = b.ran.filter((c) => c.includes(" pacman "));
assert.equal(pacman.length, 10, pacman.join("\n"));
});
test("the mirrors a failure names are read from what pacman said, each once", () => {
assert.deepEqual(failedMirrors(KERNEL_500), ["mirrors.kernel.org"]);
assert.deepEqual(failedMirrors("Connection timed out after 10000 milliseconds\nerror: too many errors from mirrors.kernel.org"),
["mirrors.kernel.org"]);
assert.deepEqual(failedMirrors("error: target not found: dockr"), []);
});