Pin the e2e relay to a zooid digest (#578)
This commit is contained in:
parent
0776befc17
commit
ec835f7cfb
3 changed files with 45 additions and 16 deletions
|
|
@ -425,13 +425,17 @@ stranger.
|
|||
The suite is not run by agents (see CLAUDE.md).
|
||||
|
||||
```sh
|
||||
pnpm exec playwright install # once
|
||||
docker pull gitea.coracle.social/coracle/zooid:latest # once; the harness never pulls
|
||||
pnpm test # starts and stops the container itself
|
||||
pnpm exec playwright install # once
|
||||
pnpm test # starts and stops the container itself
|
||||
```
|
||||
|
||||
Every test skips when docker is unavailable, rather than failing.
|
||||
|
||||
The relay is one pinned zooid, named by digest in `harness/zooid/relay.ts`, and the harness fetches
|
||||
that image the first time a machine needs it. So a spec asserting relay behaviour runs against the
|
||||
relay the diff names, and needing a newer zooid is a bump in that file. `ZOOID_IMAGE` overrides the
|
||||
pin, which is how a spec is tried against a zooid built from a checkout.
|
||||
|
||||
A test fails when the app broke while it ran, whatever it asserted: an uncaught exception on any
|
||||
of its pages, or code of ours the browser refused under the content security policy. A refusal
|
||||
reaches the console and nothing else, which is how a policy that had rotted past the script it
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ name: flotilla-e2e-zooid
|
|||
|
||||
services:
|
||||
relay:
|
||||
image: gitea.coracle.social/coracle/zooid:latest
|
||||
# Pinned by digest in e2e/harness/zooid/relay.ts, the only thing that brings this container up.
|
||||
# No default, for the same reason the config mount below has none.
|
||||
image: ${ZOOID_IMAGE:?staged by e2e/harness/zooid/relay.ts, which starts this container}
|
||||
# The harness dials this published port and sends a `Host` per virtual relay, which is what the
|
||||
# dispatcher matches against the `host` in each file under config/.
|
||||
ports:
|
||||
|
|
|
|||
|
|
@ -20,7 +20,13 @@ import type {PublishOptions} from "./types"
|
|||
import {connectToZooid, port, requestZooid} from "./transport"
|
||||
import type {ZooidConnection} from "./transport"
|
||||
|
||||
const image = "gitea.coracle.social/coracle/zooid:latest"
|
||||
// The relay every test runs against, pinned by digest so a spec is checked against one zooid rather
|
||||
// than whatever this machine last pulled. The tag is there to read; docker resolves the digest, and
|
||||
// `docker pull` of a newer tag prints the digest to paste beside it. A zooid built from a checkout
|
||||
// has no published digest, so ZOOID_IMAGE is how an unreleased one is tried.
|
||||
const image =
|
||||
process.env.ZOOID_IMAGE ??
|
||||
"gitea.coracle.social/coracle/zooid:0.2.2@sha256:70ecf2d3ad0ac338d48f14fe8e5ab9964193d2eb30d6b6f7d8972ca179cad513"
|
||||
|
||||
// How long after a recreate chromium can still abort what a page has in flight. Every netlink
|
||||
// event a create-and-destroy produces lands before `compose up --wait` returns — measured at 26 of
|
||||
|
|
@ -63,11 +69,13 @@ const isRelayPortTaken = () =>
|
|||
socket.once("error", () => resolve(false))
|
||||
})
|
||||
|
||||
// Every invocation carries ZOOID_CONFIG, `down` included. The compose file names it without a
|
||||
// default, so a call that left it out fails to interpolate rather than quietly mounting something
|
||||
// else.
|
||||
// Every invocation carries the config directory and the image, `down` included. The compose file
|
||||
// names both without a default, so a call that left one out fails to interpolate rather than
|
||||
// quietly mounting something else or running a relay nothing here chose.
|
||||
const docker = (...args: string[]) =>
|
||||
execFileAsync(dockerCommand, args, {env: {...process.env, ZOOID_CONFIG: configDir}})
|
||||
execFileAsync(dockerCommand, args, {
|
||||
env: {...process.env, ZOOID_CONFIG: configDir, ZOOID_IMAGE: image},
|
||||
})
|
||||
|
||||
const compose = (...args: string[]) => docker("compose", "-f", composeFile, ...args)
|
||||
|
||||
|
|
@ -196,11 +204,7 @@ export class Zooid {
|
|||
)
|
||||
|
||||
if (!hasImage) {
|
||||
throw new Error(
|
||||
`The zooid image ${image} is not present locally. Fetch it with ` +
|
||||
`\`${dockerCommand} pull ${image}\`, or build it from a zooid checkout with ` +
|
||||
`\`${dockerCommand} build -t ${image} .\`.`,
|
||||
)
|
||||
await this.fetchImage()
|
||||
}
|
||||
|
||||
if (await isRelayPortTaken()) {
|
||||
|
|
@ -208,8 +212,8 @@ export class Zooid {
|
|||
`127.0.0.1:${port} is already in use, but the zooid relay container publishes ` +
|
||||
"exactly that port and the harness talks to whatever answers there. This is a leftover " +
|
||||
"container from an interrupted run, or another copy of this suite running on the machine " +
|
||||
`(under any user). Free it before running — \`${dockerCommand} compose -f ` +
|
||||
"e2e/harness/zooid/docker/compose.yaml down\` clears one this project started.",
|
||||
`(under any user). Free it before running — \`${dockerCommand} rm -f ` +
|
||||
"flotilla-e2e-zooid-relay-1` clears the one this project starts.",
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -281,6 +285,25 @@ export class Zooid {
|
|||
}
|
||||
}
|
||||
|
||||
// Fetching unasked is safe for a pinned reference: the pull can only produce the relay this suite
|
||||
// was written against, and a machine that already has it never gets here.
|
||||
private fetchImage = async () => {
|
||||
console.warn(`\nFetching the zooid image this suite is pinned to:\n ${image}\n`)
|
||||
|
||||
try {
|
||||
await docker("pull", image)
|
||||
} catch (error) {
|
||||
const {stderr} = Object(error) as {stderr?: string}
|
||||
|
||||
throw new Error(
|
||||
`Could not fetch ${image}: ${stderr?.trim() || String(error)}\n\n` +
|
||||
`Fetch it by hand with \`${dockerCommand} pull ${image}\`, or point ZOOID_IMAGE at a ` +
|
||||
"zooid you built yourself.",
|
||||
{cause: error},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Whatever the relay wrote before it died. Compose reports only that a container exited, so
|
||||
// without this a startup failure is a status code and no reason.
|
||||
private logs = () =>
|
||||
|
|
|
|||
Loading…
Reference in a new issue