Recreate the zooid container in teardown and let its network churn settle
This commit is contained in:
parent
7aba172a9c
commit
f30081a2f7
3 changed files with 61 additions and 3 deletions
|
|
@ -389,6 +389,18 @@ and refreshed on the way up — zooid saves a relay's toml back when a NIP-86 ca
|
|||
every test starts against the same relays with nothing in them. The container is a worker fixture,
|
||||
so the docker start-up cost is paid once per worker rather than once per test.
|
||||
|
||||
It is recreated in the teardown of the test that finishes, not the setup of the one that starts.
|
||||
Creating a container tears down a veth pair and builds another, and the bridge behind it loses
|
||||
carrier with them; chromium watches the host's interfaces and aborts everything it has in flight
|
||||
when they move, which the app meets as a route chunk that failed to import and, with `ssr = false`,
|
||||
a 500 page.
|
||||
Recreating from teardown puts the next test's seeding — an authenticated socket per identity, and
|
||||
every fixture written over it — between the churn and the first page that test opens. That is not
|
||||
on its own enough: every netlink event lands before `compose up --wait` returns, but chromium
|
||||
coalesces interface changes for up to two seconds before acting on one. So a recreate also stamps
|
||||
the moment it settles, and a page waits out whatever is left of that window when it opens — which
|
||||
for most tests is nothing, the teardown and the seeding having spent it already.
|
||||
|
||||
Seeding then writes over a real authenticated socket, one per identity per relay, held open for the
|
||||
rest of the test. A fixture must therefore be signed by an identity `harness/keys.ts` holds, since
|
||||
zooid authenticates every write.
|
||||
|
|
|
|||
|
|
@ -172,8 +172,8 @@ export const test = base.extend<HarnessFixtures, HarnessWorkerFixtures>({
|
|||
"them. Anything the app fetches belongs in a mock installed by `as(user, path)`.",
|
||||
)
|
||||
},
|
||||
// One container per worker, torn down when the worker ends. It is reset between tests rather
|
||||
// than recreated, so the docker start-up cost is paid once.
|
||||
// One container per worker, torn down when the worker ends. It is recreated between tests, in
|
||||
// the teardown of the test that finishes rather than the setup of the one that starts.
|
||||
zooid: [
|
||||
// playwright reads a fixture's dependencies off this pattern, so it has to stay a pattern
|
||||
// even when there are none.
|
||||
|
|
@ -192,7 +192,7 @@ export const test = base.extend<HarnessFixtures, HarnessWorkerFixtures>({
|
|||
testInfo.skip(Boolean(dockerProblem), dockerProblem)
|
||||
|
||||
await zooid.start()
|
||||
await zooid.reset()
|
||||
await zooid.ensure()
|
||||
|
||||
const contexts: BrowserContext[] = []
|
||||
const faults = watchFaults()
|
||||
|
|
@ -207,6 +207,8 @@ export const test = base.extend<HarnessFixtures, HarnessWorkerFixtures>({
|
|||
|
||||
const open = async (path: string, options: PageOptions, user?: TestUser) => {
|
||||
const {urls, indexerUrls, cache} = requireScenario()
|
||||
|
||||
await zooid.settle()
|
||||
// The project's own `use` first, so a viewport or device descriptor set in
|
||||
// playwright.config.ts reaches the context rather than being dropped.
|
||||
//
|
||||
|
|
@ -291,6 +293,11 @@ export const test = base.extend<HarnessFixtures, HarnessWorkerFixtures>({
|
|||
await context.close()
|
||||
}
|
||||
|
||||
// Before the assertions below, which throw: a test that fails still owes the next one a
|
||||
// container, and with every page already closed there is nothing left for the recreate's
|
||||
// network churn to interrupt.
|
||||
await zooid.reset()
|
||||
|
||||
if (faults.found.length > 0 || testInfo.status !== testInfo.expectedStatus) {
|
||||
// A path rather than a body: the list reporter truncates an inline attachment, and the
|
||||
// nightly run on the box keeps test-results and nothing else.
|
||||
|
|
|
|||
|
|
@ -22,6 +22,12 @@ import type {ZooidConnection} from "./transport"
|
|||
|
||||
const image = "gitea.coracle.social/coracle/zooid:latest"
|
||||
|
||||
// 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
|
||||
// them, all of them within a second, none after — but chromium coalesces interface changes for up
|
||||
// to two seconds before acting on one, so the abort arrives well after the harness has moved on.
|
||||
const SETTLE = 2500
|
||||
|
||||
const composeFile = fileURLToPath(new URL("docker/compose.yaml", import.meta.url))
|
||||
|
||||
const configSource = fileURLToPath(new URL("docker/config", import.meta.url))
|
||||
|
|
@ -167,6 +173,12 @@ export class Zooid {
|
|||
|
||||
private started = false
|
||||
|
||||
// Whether a container is up. `reset` is what creates one, so this stays false until it has run.
|
||||
private running = false
|
||||
|
||||
// When the last recreate's interface churn stops being able to abort a request.
|
||||
private settledAt = 0
|
||||
|
||||
// Verifies docker rather than bringing the container up, which `reset` does. Repeat calls are
|
||||
// free, so the fixture can call this per test.
|
||||
start = async () => {
|
||||
|
|
@ -226,10 +238,36 @@ export class Zooid {
|
|||
}
|
||||
}
|
||||
|
||||
// Leaves a fresh container for whoever runs next. Recreating one churns the host's network
|
||||
// interfaces — a veth pair torn down and rebuilt, the project's bridge losing carrier with it —
|
||||
// and chromium aborts every request it has in flight when it acts on that, which reaches the app
|
||||
// as a route chunk that failed to import and, with `ssr = false`, a 500 page. So this is called
|
||||
// from teardown rather than setup, and everything after it counts towards `settle` below.
|
||||
reset = async () => {
|
||||
this.closeSessions()
|
||||
|
||||
await this.up()
|
||||
|
||||
this.running = true
|
||||
this.settledAt = Date.now() + SETTLE
|
||||
}
|
||||
|
||||
// The container the test about to run will use. Only the first test of a worker finds none.
|
||||
ensure = async () => {
|
||||
if (!this.running) {
|
||||
await this.reset()
|
||||
}
|
||||
}
|
||||
|
||||
// Waited out before a page is opened, which is as late as it can be left: teardown, the next
|
||||
// test's fixtures and its seeding are all inside the window already, so most tests pay nothing
|
||||
// here.
|
||||
settle = async () => {
|
||||
const remaining = this.settledAt - Date.now()
|
||||
|
||||
if (remaining > 0) {
|
||||
await sleep(remaining)
|
||||
}
|
||||
}
|
||||
|
||||
stop = async () => {
|
||||
|
|
@ -239,6 +277,7 @@ export class Zooid {
|
|||
await compose("down")
|
||||
|
||||
this.started = false
|
||||
this.running = false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue