Public: anyone can read this; the crew can change it.
test/apps-idle.test.ts runs in under 10 s, every test under 1 s, proving what it proves today #10253
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/10239-test-apps-idle-test-ts-runs-in-under-10 → main 1 commit fork
1 file
changed +36
−18
test/apps-idle.test.ts +36 −18
| @@ -28,6 +28,11 @@ describe("previews of idle pulls sleep", () => { | ||
| 28 | 28 | let alice: User; |
| 29 | 29 | let repo: Repo; |
| 30 | 30 | let record: AppRecord; |
| 31 | // Three previews, deployed once for the file: deploying is the slow part, | |
| 32 | // and no test here is about deploying. | |
| 33 | let fresh: number; | |
| 34 | let idle: number; | |
| 35 | let later: number; | |
| 31 | 36 | const DOMAIN = "apps.test"; |
| 32 | 37 | |
| 33 | 38 | const options = () => ({ |
| @@ -35,9 +40,21 @@ describe("previews of idle pulls sleep", () => { | ||
| 35 | 40 | scheme: "http" as const, |
| 36 | 41 | publicPort: servers.port, |
| 37 | 42 | forgeUrl: servers.url, |
| 38 | sweepEveryMs: 150, | |
| 43 | sweepEveryMs: 50, | |
| 39 | 44 | idlePullMs: 3 * DAY, |
| 45 | // The sweep reads its clock once per pass, after it has put idle pulls | |
| 46 | // to sleep: counting reads is counting finished sweeps. | |
| 47 | clock: () => { | |
| 48 | swept++; | |
| 49 | return Date.now(); | |
| 50 | }, | |
| 40 | 51 | }); |
| 52 | let swept = 0; | |
| 53 | /** Wait until `n` whole sweeps have run from now: what "the sweep left it alone" needs. */ | |
| 54 | const sweeps = (n: number) => { | |
| 55 | const target = swept + n + 1; | |
| 56 | return until(() => (swept >= target ? true : undefined)); | |
| 57 | }; | |
| 41 | 58 | const envOf = (n: number): EnvStatus | undefined => |
| 42 | 59 | runtime.status(record).find((e) => e.env === `pull-${n}`); |
| 43 | 60 | |
| @@ -46,7 +63,7 @@ describe("previews of idle pulls sleep", () => { | ||
| 46 | 63 | while (Date.now() < deadline) { |
| 47 | 64 | const value = read(); |
| 48 | 65 | if (value !== undefined) return value; |
| 49 | await Bun.sleep(100); | |
| 66 | await Bun.sleep(10); | |
| 50 | 67 | } |
| 51 | 68 | throw new Error("did not happen in time"); |
| 52 | 69 | } |
| @@ -106,6 +123,9 @@ describe("previews of idle pulls sleep", () => { | ||
| 106 | 123 | if (!made.ok) throw new Error(String(made.error)); |
| 107 | 124 | repo = made.value.repo; |
| 108 | 125 | record = made.value.app; |
| 126 | fresh = await pull("fresh"); | |
| 127 | idle = await pull("idle"); | |
| 128 | later = await pull("later"); | |
| 109 | 129 | }, 120_000); |
| 110 | 130 | |
| 111 | 131 | afterAll(async () => { |
| @@ -116,8 +136,7 @@ describe("previews of idle pulls sleep", () => { | ||
| 116 | 136 | }); |
| 117 | 137 | |
| 118 | 138 | test("a pull touched an hour ago keeps its preview; one idle past the age loses its process, and its page says so", async () => { |
| 119 | const fresh = await pull("fresh"); | |
| 120 | const idle = await pull("idle"); | |
| 139 | // Invariant: the sweep stops a preview idle past the age (state kept, page says asleep), and spares a fresh one and production. | |
| 121 | 140 | age(fresh, 3_600_000); |
| 122 | 141 | age(idle, 4 * DAY); |
| 123 | 142 | await until(() => (envOf(idle)?.dormant !== undefined ? true : undefined)); |
| @@ -128,7 +147,7 @@ describe("previews of idle pulls sleep", () => { | ||
| 128 | 147 | expect(asleep.port).toBeUndefined(); |
| 129 | 148 | expect(asleep.commitOid).not.toBe(""); |
| 130 | 149 | expect(asleep.runId).toBeDefined(); |
| 131 | await Bun.sleep(500); | |
| 150 | await sweeps(2); | |
| 132 | 151 | expect(envOf(fresh)?.state).toBe("running"); |
| 133 | 152 | expect(envOf(fresh)?.dormant).toBeUndefined(); |
| 134 | 153 | // Production is never put to sleep by a pull's age. It is waited for: |
| @@ -172,10 +191,9 @@ describe("previews of idle pulls sleep", () => { | ||
| 172 | 191 | }, 120_000); |
| 173 | 192 | |
| 174 | 193 | test("asking for a stopped preview redeploys it on the ordinary path", async () => { |
| 175 | const idle = await until(() => { | |
| 176 | const e = runtime.status(record).find((x) => x.dormant !== undefined); | |
| 177 | return e === undefined ? undefined : Number(e.env.slice(5)); | |
| 178 | }); | |
| 194 | // Invariant: a request for a dormant preview runs one "wake" deploy, and the woken preview counts as touched. | |
| 195 | // The idle preview the first test put to sleep. | |
| 196 | await until(() => (envOf(idle)?.dormant !== undefined ? true : undefined)); | |
| 179 | 197 | const before = listRuns(app.db, repo.id, { kind: "deploy", limit: 100 }).length; |
| 180 | 198 | const res = await fetch(`${servers.url}/`, { |
| 181 | 199 | headers: { host: `hello--${idle}.${DOMAIN}` }, |
| @@ -187,25 +205,25 @@ describe("previews of idle pulls sleep", () => { | ||
| 187 | 205 | expect(runs.length).toBe(before + 1); |
| 188 | 206 | expect(runs[0]!.reason).toBe("wake"); |
| 189 | 207 | // Woken, it counts as touched: the next sweep leaves it up. |
| 190 | await Bun.sleep(500); | |
| 208 | await sweeps(2); | |
| 191 | 209 | expect(envOf(idle)?.state).toBe("running"); |
| 192 | 210 | }, 120_000); |
| 193 | 211 | |
| 194 | 212 | test("a restart leaves a sleeping preview asleep", async () => { |
| 195 | const idle = await pull("later"); | |
| 196 | age(idle, 5 * DAY); | |
| 197 | await until(() => (envOf(idle)?.dormant !== undefined ? true : undefined)); | |
| 213 | // Invariant: a runtime booting over a dormant preview keeps it stopped and deploys nothing for it. | |
| 214 | age(later, 5 * DAY); | |
| 215 | await until(() => (envOf(later)?.dormant !== undefined ? true : undefined)); | |
| 198 | 216 | const before = listRuns(app.db, repo.id, { kind: "deploy", limit: 100 }).filter( |
| 199 | (r) => r.env === `pull-${idle}`, | |
| 217 | (r) => r.env === `pull-${later}`, | |
| 200 | 218 | ).length; |
| 201 | 219 | await runtime.stop(); |
| 202 | 220 | runtime = new AppsRuntime(app, options()); |
| 203 | 221 | await runtime.start(); |
| 204 | expect(envOf(idle)?.dormant).toBeDefined(); | |
| 205 | expect(envOf(idle)?.state).toBe("stopped"); | |
| 206 | await Bun.sleep(500); | |
| 222 | expect(envOf(later)?.dormant).toBeDefined(); | |
| 223 | expect(envOf(later)?.state).toBe("stopped"); | |
| 224 | await sweeps(2); | |
| 207 | 225 | const after = listRuns(app.db, repo.id, { kind: "deploy", limit: 100 }).filter( |
| 208 | (r) => r.env === `pull-${idle}`, | |
| 226 | (r) => r.env === `pull-${later}`, | |
| 209 | 227 | ).length; |
| 210 | 228 | expect(after).toBe(before); |
| 211 | 229 | }, 120_000); |