The suite next door raises one scenario and asks deep questions of it. This one asks shallow questions of every scenario — the half that was missing, since both faults found by hand lived in scenarios nothing ever built. Adds bootstrap-single, the cheapest, and the loop that lets the list grow. Also adds the second universal invariant: every address a 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. Verified to bite rather than assumed: against a live instance, the real declaration passes and a declaration claiming an address nothing holds fails with 'anchor declared 192.0.2.99 on hosting but holds 192.0.2.10'. Integration now runs with --test-concurrency=1. Two files raise real instances, node --test runs files in parallel by default, and two concurrent runs of this suite already produced a whole-suite failure once — every test red, from resource contention rather than from any fault in the code. Gate: 45.7s -> 60.2s.
48 lines
2.0 KiB
TypeScript
48 lines
2.0 KiB
TypeScript
/**
|
|
* Every scenario, raised for real, checked against the invariants that hold of all of them.
|
|
*
|
|
* The suite next door raises one scenario and asks deep questions of it. This one asks
|
|
* shallow questions of every scenario, which is the half that was missing: both faults found
|
|
* by hand so far — two gateways holding one address, and a gateway drawn across an unrelated
|
|
* network — lived in scenarios nothing ever built.
|
|
*
|
|
* The list grows. Each entry costs a boot, so it is added deliberately rather than by
|
|
* globbing the directory: a scenario that is expensive and adds no new shape is not worth
|
|
* the wall clock, and one that is cheap and adds a shape is.
|
|
*/
|
|
|
|
import { test, after } from "node:test";
|
|
import { loadScenario } from "../../src/declaration/parse.ts";
|
|
import { raise } from "../../src/lifecycle/raise.ts";
|
|
import { destroy } from "../../src/lifecycle/operate.ts";
|
|
import { labIsUsable, destroyAll, assertUniversalInvariants } from "./harness.ts";
|
|
|
|
const capability = await labIsUsable();
|
|
const skip = capability.usable ? false : `lab not usable: ${capability.why}`;
|
|
|
|
/** Grows one step at a time. Each addition is a boot, and a shape not covered before. */
|
|
const SCENARIOS = [
|
|
"bootstrap-single", // one machine, one public network — the floor
|
|
];
|
|
|
|
const raised: string[] = [];
|
|
|
|
after(async () => {
|
|
for (const instanceId of raised) await destroy(instanceId);
|
|
}, { timeout: 600_000 });
|
|
|
|
for (const name of SCENARIOS) {
|
|
test(`${name}: comes up holding what it declared, with no address held twice`, { skip, timeout: 900_000 }, async () => {
|
|
const scenario = loadScenario(`scenarios/${name}.yml`);
|
|
const instance = await raise(scenario, {});
|
|
raised.push(instance.instanceId);
|
|
await assertUniversalInvariants(scenario, instance.instanceId);
|
|
});
|
|
}
|
|
|
|
after(async () => {
|
|
// Anything a failed raise left standing. RaiseError leaves wreckage on purpose, which is
|
|
// right for a person debugging and wrong for the next run of the suite.
|
|
for (const name of SCENARIOS) await destroyAll(`${name}-`);
|
|
}, { timeout: 600_000 });
|