/** * Integration tests run against a real hypervisor. Mocking it is forbidden — a test that * fakes the system under integration asserts that the fake behaves as expected, which is * the shape of test this project exists to stop shipping (novox/hq ADR 0017). * * Consequence, accepted: these are slow, and they need a machine that can raise scenarios. * They skip rather than fail where it cannot, so that a machine without a hypervisor gets * an honest "not run" instead of a green suite that checked nothing. */ import assert from "node:assert/strict"; import { isReachable, pools, supportedDrivers } from "../../src/incus/client.ts"; import { destroy, list } from "../../src/lifecycle/operate.ts"; import { diagramFromLive } from "../../src/diagram/from-live.ts"; import { duplicateAddresses, describeConflicts, type Held } from "../../src/lifecycle/invariants.ts"; import type { Scenario } from "../../src/declaration/types.ts"; export interface Capability { usable: boolean; why: string; } /** Can this machine run scenarios at all? Checked once, reported honestly. */ export async function labIsUsable(): Promise { if (!(await isReachable())) { return { usable: false, why: "the incus daemon is not reachable as this user (try MESH_LAB_INCUS='sudo -n incus')", }; } const drivers = await supportedDrivers(); if (!drivers.some((d) => d === "btrfs" || d === "zfs")) { return { usable: false, why: "no copy-on-write driver — snapshots would be full copies" }; } if (!(await pools()).some((p) => p.driver === "btrfs" || p.driver === "zfs")) { return { usable: false, why: "no pool uses a copy-on-write driver" }; } return { usable: true, why: "" }; } /** Tear down anything a test left behind, whether it passed or not. */ export async function destroyAll(prefix: string): Promise { for (const instance of await list()) { if (instance.instanceId.startsWith(prefix)) { await destroy(instance.instanceId); } } } /** * Every address the hypervisor says is held, by which machine, on which segment. * * Read through the live diagram because that is already the one place that joins addresses * to devices by MAC and devices to segments by tag. A second reader would be a second thing * to get wrong in the same way — and the way it was wrong once, a virtual machine's * addresses silently going missing, is exactly what these assertions would then miss. */ export async function heldAddresses(instanceId: string): Promise { const drawn = await diagramFromLive(instanceId); return drawn.machines.flatMap((machine) => machine.attachments.flatMap((attachment) => attachment.addresses.map((address) => ({ machine: machine.name, segment: attachment.segment, address, })), ), ); } function bare(address: string): string { const slash = address.lastIndexOf("/"); return slash === -1 ? address : address.slice(0, slash); } /** * Invariants that hold of ANY raised scenario, whatever it declares. * * Asserted against what actually came up, never against the declaration — the declaration * is what was accepted, and in the fault that prompted these, it was accepted. */ export async function assertUniversalInvariants( scenario: Scenario, instanceId: string, ): Promise { const held = await heldAddresses(instanceId); assert.ok(held.length > 0, `${scenario.scenario}: no addresses were read back at all`); const conflicts = duplicateAddresses(held); assert.deepEqual( conflicts, [], `${scenario.scenario}: address conflict — ${describeConflicts(conflicts)}`, ); // Every address the scenario declared is one the machine actually holds. A machine that // came up bare looks identical to one that came up correctly until something asks it. const holders = new Map>(); for (const entry of held) { const key = `${entry.machine} ${entry.segment}`; holders.set(key, (holders.get(key) ?? new Set()).add(bare(entry.address))); } for (const [name, spec] of Object.entries(scenario.machines)) { if (spec.at === "detached") continue; for (const attachment of spec.at) { const actual = holders.get(`${name} ${attachment.segment}`) ?? new Set(); for (const address of attachment.address) { assert.ok( actual.has(address), `${scenario.scenario}: '${name}' declared ${address} on '${attachment.segment}' ` + `but holds ${[...actual].join(", ") || "nothing"}`, ); } } } }