diff --git a/one-node-mesh-report.json b/one-node-mesh-report.json index 9ac706f..a759bdd 100644 --- a/one-node-mesh-report.json +++ b/one-node-mesh-report.json @@ -1,13 +1,13 @@ { "scenario": "one-node-mesh", - "established": 12, + "established": 18, "of": 21, "steps": [ { "code": "R1", "title": "a bare machine becomes a mesh of one, raised by the installer", "status": "pass", - "seconds": 135, + "seconds": 133, "why": "" }, { @@ -56,28 +56,28 @@ "code": "P1", "title": "the mesh builds the shared base from source", "status": "pass", - "seconds": 87, + "seconds": 83, "why": "" }, { "code": "P2", "title": "the mesh builds and runs a store of its own", "status": "pass", - "seconds": 41, + "seconds": 47, "why": "" }, { "code": "P3", "title": "the mesh builds and runs its own catalogue", "status": "pass", - "seconds": 37, + "seconds": 26, "why": "" }, { "code": "P4", "title": "the mesh rebuilds its own control plane from source", "status": "pass", - "seconds": 35, + "seconds": 30, "why": "" }, { @@ -90,58 +90,58 @@ { "code": "N2", "title": "the machine has a packet filter, loaded from what modules declared", - "status": "fail", - "seconds": 0, - "why": "anchor: docker exec mesh-control /mesh-control assign anchor firewall\n\nmesh-control: no module of that name: firewall\n" + "status": "pass", + "seconds": 7, + "why": "" }, { "code": "U1", "title": "the mesh builds a module standing on that base", - "status": "skip", - "seconds": 0, - "why": "not attempted — N2 (the machine has a packet filter, loaded from what modules declared) did not succeed" + "status": "pass", + "seconds": 23, + "why": "" }, { "code": "U2", "title": "the mesh runs a broker for that module to talk to", - "status": "skip", - "seconds": 0, - "why": "not attempted — U1 (the mesh builds a module standing on that base) did not succeed" + "status": "pass", + "seconds": 20, + "why": "" }, { "code": "U3", "title": "the anchor runs the module the mesh built", - "status": "skip", - "seconds": 0, - "why": "not attempted — U2 (the mesh runs a broker for that module to talk to) did not succeed" + "status": "pass", + "seconds": 6, + "why": "" }, { "code": "V1", "title": "the control plane can describe the mesh, and what it says is true", - "status": "skip", - "seconds": 0, - "why": "not attempted — U3 (the anchor runs the module the mesh built) did not succeed" + "status": "pass", + "seconds": 1, + "why": "" }, { "code": "V2", "title": "the catalogue holds every module this mesh built", - "status": "skip", - "seconds": 0, - "why": "not attempted — V1 (the control plane can describe the mesh, and what it says is true) did not succeed" + "status": "fail", + "seconds": 1, + "why": "the catalogue does not hold mesh-tools, postgres — the mesh built them and its own record has no trace of it (novox/hq issue 050):\n{\"modules\":[{\"module\":\"amqp-ping\",\"commit\":\"e0c92195d4240841bfcf4b4a9ef869d5afeca331\",\"repository\":\"https://git.novox.be/novox/mesh-catalog.git\",\"path\":\"modules/amqp-ping\"},{\"module\":\"lavinmq\",\"commit\":\"e0c92195d4240841bfcf4b4a9ef869d5afeca331\",\"repository\":\"https://git.novox.be/novox/mesh-catalog.git\",\"path\":\"modules/lavinmq\"},{\"module\":\"mesh-control\",\"commit\":\"5062c36fc9efe159aa9706c0ca2c873351ef1ce0\",\"repository\":\"https://git.novox.be/novox/mesh-control.git\",\"path\":\"\"}]}\n\n+ actual - expected\n\n+ [\n+ 'mesh-tools',\n+ 'postgres'\n+ ]\n- []\n" }, { "code": "V3", "title": "the machine's networking is what the modules asked for", - "status": "skip", - "seconds": 0, - "why": "not attempted — V1 (the control plane can describe the mesh, and what it says is true) did not succeed" + "status": "pass", + "seconds": 1, + "why": "" }, { "code": "E1", "title": "a change to a module's source reaches the machine on its own", - "status": "skip", - "seconds": 0, - "why": "not attempted — V3 (the machine's networking is what the modules asked for) did not succeed" + "status": "fail", + "seconds": 1, + "why": "the mesh does not report a module behind its source:\n1 machine(s), all doing what they were told, all heard from, running what the mesh would send them, and every module current with its source\n" }, { "code": "E2", diff --git a/test/integration/one-node-mesh.test.ts b/test/integration/one-node-mesh.test.ts index c7c09bd..9f511cf 100644 --- a/test/integration/one-node-mesh.test.ts +++ b/test/integration/one-node-mesh.test.ts @@ -38,7 +38,8 @@ */ import { test, before, after } from "node:test"; import assert from "node:assert/strict"; -import { existsSync, writeFileSync } from "node:fs"; +import { existsSync, writeFileSync, appendFileSync } from "node:fs"; +import { execFileSync } from "node:child_process"; import { resolve } from "node:path"; import { loadScenario } from "../../src/declaration/parse.ts"; import { raise } from "../../src/lifecycle/raise.ts"; @@ -133,7 +134,7 @@ const CATALOGUED = "the catalogue holds every module this mesh built"; const NETWORK = "the machine's networking is what the modules asked for"; const FOLLOWS = "a change to a module's source reaches the machine on its own"; const SURVIVES = "the mesh comes back after the machine reboots"; -const MODULE = { module: "amqp-ping", repo: "mesh-catalog", path: "modules/amqp-ping" }; +const MODULE = { module: "amqp-ping", repo: "mesh-catalog", path: "modules/amqp-ping", container: "amqp-ping" }; const capability = await labIsUsable(); const binary = hostBinaryPath(); @@ -833,26 +834,51 @@ before(async () => { // machinery; this is what the machinery is for. await step("E1", FOLLOWS, NETWORK, async () => { const before = await mesh(`builds ${MODULE.module}`); - const wasPinned = before.match(/sha256:[0-9a-f]{64}/)?.[0] ?? ""; - assert.ok(wasPinned, `nothing is pinned to rebuild from:\n${before}`); + const was = before.match(/sha256:[0-9a-f]{64}/)?.[0] ?? ""; + assert.ok(was, `nothing is pinned to rebuild from:\n${before}`); - // The mesh is told its copy is older than the source. In life a push does this; here it is - // stated, because what is under test is what the mesh does next, not how it hears. - const head = (await must(CONTROL, `git ls-remote ${forgeUrl(MODULE.repo)} ` + - `${refFor(MODULE.repo)} | cut -f1`, 120_000)).trim(); - assert.match(head, /^[0-9a-f]{40}$/, `could not read the source's head: ${head}`); + // **The source has to actually move, and it cannot be faked.** + // + // The first version of this read the branch head and told the mesh the source had moved there + // — the same commit it had just built. The mesh answered, correctly, that everything was + // current. Naming some other commit would not work either: staleness compares ARTIFACTS, not + // commits, which is a deliberate choice so that editing a comment in a shared base does not + // rebuild everything standing on it to arrive back where it started. + // + // So this makes a real change to the module's source and pushes it. It is a test that writes + // to a branch, which is worth knowing about; the alternative is a test that proves the loop by + // telling the mesh something untrue. + const checkout = resolve(catalogDir, ".."); + const marker = `// changed by the one-node test at build ${was.slice(7, 19)}\n`; + const file = resolve(catalogDir, MODULE.module, "index.ts"); + await must(CONTROL, `true`); // keep the shape uniform; the change is made on this workstation + appendFileSync(file, marker); + // Path-scoped: `commit -am` would sweep whatever else is in the working tree into a commit + // this test is about to push. + execFileSync("git", ["-C", checkout, "add", file], { stdio: "pipe" }); + execFileSync("git", ["-C", checkout, "commit", "-q", "-m", + `Move ${MODULE.module}'s source, so the mesh has something to notice`], { stdio: "pipe" }); + execFileSync("git", ["-C", checkout, "push", "-q", "origin", refFor(MODULE.repo)], + { stdio: "pipe" }); + const head = execFileSync("git", ["-C", checkout, "rev-parse", "HEAD"], + { encoding: "utf8" }).trim(); + + // Now the mesh is told. In life a push notices itself; what is under test here is what the + // mesh does NEXT, not how it hears. await mesh(`module moved ${MODULE.module} ${head}`); - const behind = await mesh(`status`); assert.match(behind, /behind|build --behind/, - `the mesh does not report a module behind its source:\n${behind}`); + `the source moved and the mesh does not report the module behind it:\n${behind}`); await mesh(`build --behind --wait 1200s`, 1_500_000); const rolled = await mesh(`upgrade ${MODULE.module} roll-out`, 900_000); - await waitForContainer(CONTROL, MODULE.module); + await waitForContainer(CONTROL, MODULE.container); const after = await mesh(`builds ${MODULE.module}`); - assert.match(after, /sha256:[0-9a-f]{64}/, `nothing was pinned after the rebuild:\n${after}`); + const now = after.match(/sha256:[0-9a-f]{64}/)?.[0] ?? ""; + assert.notEqual(now, was, + `the module was rebuilt and came back on the same artifact, so nothing reached the machine:` + + `\n${after}`); return `${behind}\n${rolled}\n${after}`; });