Public: anyone can read this; the crew can change it.
test/rehearsal-real.test.ts runs in the gate: its two real rolls stay green under the gate's load #11114
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/11098-test-rehearsal-real-test-ts-runs-in-the → main 6 commits fork 4 threads · 4 unresolved
7 files
changed +33
−24
scripts/flake.ts +5
| @@ -227,6 +227,11 @@ export function quarantine( | ||
| 227 | 227 | reason = "flaky: red in one hunt run and green in another", |
| 228 | 228 | ): string { |
| 229 | 229 | const start = source.indexOf("export const EXCLUDED"); |
| 230 | // An empty quarantine is written on one line; open it so an entry can be added. | |
| 231 | const empty = /(export const EXCLUDED[^=\n]*= \[)\];/.exec(source); | |
| 232 | if (empty && empty.index === start) { | |
| 233 | source = source.replace(empty[0], `${empty[1]}\n];`); | |
| 234 | } | |
| 230 | 235 | const end = source.indexOf("\n];", start); |
| 231 | 236 | if (start === -1 || end === -1) return source; |
| 232 | 237 | if (source.slice(start, end).includes(`file: ${JSON.stringify(file)}`)) return source; |
scripts/gate.ts +3 −9
| @@ -32,13 +32,7 @@ export type Exclusion = { | ||
| 32 | 32 | issue: number; |
| 33 | 33 | }; |
| 34 | 34 | |
| 35 | export const EXCLUDED: readonly Exclusion[] = [ | |
| 36 | { | |
| 37 | file: "test/rehearsal-real.test.ts", | |
| 38 | reason: "green alone (3 of 3, 2026-11-10) but unproven under the gate's load, where on main@accaa65 the second real roll's old forge did not come up", | |
| 39 | issue: 11098, | |
| 40 | }, | |
| 41 | ]; | |
| 35 | export const EXCLUDED: readonly Exclusion[] = []; | |
| 42 | 36 | |
| 43 | 37 | /** |
outdated
Delete these five entries; EXCLUDED should be |
||
| 44 | 38 | * Everything wrong with the quarantine, against the test files that exist: |
| @@ -120,8 +114,8 @@ export async function treeGate(tree: string, key: string): Promise<string[]> { | ||
| 120 | 114 | * What the check prints when the quarantine is wrong: every problem, then |
| 121 | 115 | * one last line naming the files to fix. Empty when it is right. |
| 122 | 116 | */ |
| 123 | export function gateRefusal(present: readonly string[]): string[] { | |
| 124 | const problems = auditGate(present, EXCLUDED); | |
| 117 | export function gateRefusal(present: readonly string[], excluded: readonly Exclusion[] = EXCLUDED): string[] { | |
| 118 | const problems = auditGate(present, excluded); | |
| 125 | 119 | if (problems.length === 0) return []; |
| 126 | 120 | const named = [ |
| 127 | 121 | ...new Set(problems.map((p) => p.match(/^(test\/\S+\.test\.ts) /)?.[1])), |
test/check-list.test.ts +16 −3
| @@ -6,7 +6,7 @@ | ||
| 6 | 6 | */ |
| 7 | 7 | |
| 8 | 8 | import { describe, expect, test } from "bun:test"; |
| 9 | import { copyFileSync, mkdirSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from "node:fs"; | |
| 9 | import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; | |
| 10 | 10 | import { tmpdir } from "node:os"; |
| 11 | 11 | import { join } from "node:path"; |
| 12 | 12 | import { auditGate, EXCLUDED, gateFiles, gateRefusal, treeGate } from "../scripts/gate.ts"; |
| @@ -94,6 +94,17 @@ describe("auditGate", () => { | ||
| 94 | 94 | }); |
| 95 | 95 | }); |
| 96 | 96 | |
| 97 | describe("gateRefusal", () => { | |
| 98 | test("a stale exclusion is named, and the last line names the file to fix", () => { | |
| 99 | const stale = [{ file: "test/gone.test.ts", reason: "slow", issue: 3 }]; | |
| 100 | expect(gateRefusal(["test/a.test.ts"], stale)).toEqual([ | |
| 101 | "test/gone.test.ts is in EXCLUDED but does not exist", | |
| 102 | "test/gone.test.ts: fix its EXCLUDED entry in scripts/gate.ts", | |
| 103 | ]); | |
| 104 | expect(gateRefusal(["test/a.test.ts"], [])).toEqual([]); | |
| 105 | }); | |
| 106 | }); | |
| 107 | ||
| 97 | 108 | describe("treeGate: the gate an older commit's tree names", () => { |
| 98 | 109 | const tree = (gate: string, tests: string[]): string => { |
| 99 | 110 | const dir = mkdtempSync(join(tmpdir(), "forge-tree-gate-")); |
| @@ -117,8 +128,10 @@ describe("treeGate: the gate an older commit's tree names", () => { | ||
| 117 | 128 | }); |
| 118 | 129 | |
| 119 | 130 | test("a later tree derives it from its own test files minus EXCLUDED", async () => { |
| 120 | const dir = tree("", ["test/a.test.ts", "test/zz-new.test.ts", EXCLUDED[0]!.file]); | |
| 121 | copyFileSync(`${root}scripts/gate.ts`, join(dir, "scripts/gate.ts")); | |
| 131 | const dir = tree("", ["test/a.test.ts", "test/zz-new.test.ts", "test/zz-out.test.ts"]); | |
| 132 | const real = readFileSync(`${root}scripts/gate.ts`, "utf8"); | |
| 133 | const out = '[{ file: "test/zz-out.test.ts", reason: "r", issue: 1 }]'; | |
| 134 | writeFileSync(join(dir, "scripts/gate.ts"), real.replace(/(export const EXCLUDED[^=\n]*= )\[[^\]]*\];/, `$1${out};`)); | |
| 122 | 135 | try { |
| 123 | 136 | expect(await treeGate(dir, "new")).toEqual(["test/a.test.ts", "test/zz-new.test.ts"]); |
| 124 | 137 | } finally { |
test/flake.test.ts +2 −2
| @@ -199,8 +199,8 @@ describe("quarantine", () => { | ||
| 199 | 199 | }); |
| 200 | 200 | |
| 201 | 201 | test("a file excluded already is left alone", async () => { |
| 202 | const { EXCLUDED } = await import("../scripts/gate.ts"); | |
| 203 | expect(quarantine(source, EXCLUDED[0]!.file, 1)).toBe(source); | |
| 202 | const once = quarantine(source, file, 1); | |
| 203 | expect(quarantine(once, file, 2)).toBe(once); | |
| 204 | 204 | expect(quarantine("no list here", file, 1)).toBe("no list here"); |
| 205 | 205 | }); |
| 206 | 206 | }); |
test/rehearsal-real.test.ts +1 −4
| @@ -4,10 +4,7 @@ | ||
| 4 | 4 | * signalled refuses the requests routers still send it; one that lingers |
| 5 | 5 | * past a probe period refuses none. |
| 6 | 6 | * |
| 7 | * Out of the gate (`scripts/gate.ts` EXCLUDED) under #7632: under the gate | |
| 8 | * on main@accaa65 the second roll's old forge does not come up, and every | |
| 9 | * proof went red on it, so nothing landed. The nightly outside-gate run | |
| 10 | * still runs it; it rejoins with #7632. | |
| 7 | * In the gate (#11098); `scripts/flake-hunt.ts admit` is how it is proven. | |
| 11 | 8 | */ |
outdated
The hunt ran in the builder's sandbox, not on the check host. Word this accurately, or leave the claim out of the header and let the check prove it. |
||
| 12 | 9 | |
| 13 | 10 | import { describe, expect, test } from "bun:test"; |
test/rehearsal.test.ts +1 −2
| @@ -1,7 +1,6 @@ | ||
| 1 | 1 | /** |
| 2 | 2 | * The roll rehearsal (#873): its plain parts. The two short real rolls of |
| 3 | * two forge processes on one data directory are `rehearsal-real.test.ts`, | |
| 4 | * out of the gate while #7632 is open. | |
| 3 | * two forge processes on one data directory are `rehearsal-real.test.ts`. | |
| 5 | 4 | */ |
| 6 | 5 | |
| 7 | 6 | import { describe, expect, test } from "bun:test"; |
test/shards-run.test.ts +5 −4
| @@ -142,8 +142,9 @@ describe("the gate audit before any shard", () => { | ||
| 142 | 142 | return { code, out, err, ms: performance.now() - started }; |
| 143 | 143 | }; |
| 144 | 144 | |
| 145 | test("a stale exclusion is refused by name, last line, nothing spawned", async () => { | |
| 146 | const gone = EXCLUDED[0]!.file; | |
| 145 | // The quarantine is empty when nothing is flaky: no real entry to make stale. | |
| 146 | test.skipIf(EXCLUDED.length === 0)("a stale exclusion is refused by name, last line, nothing spawned", async () => { | |
Non-blocking: skipped while EXCLUDED is empty, so the subprocess audit path is unexercised; use a scratch gate.ts fixture later. |
||
| 147 | const gone = EXCLUDED[0]?.file ?? ""; | |
| 147 | 148 | for (const args of [["--gate"], ["--tier", "fast", "--diff", "a..b"]]) { |
| 148 | 149 | const got = await run([], args, [gone]); |
| 149 | 150 | expect(got.code).toBe(2); |
| @@ -221,8 +222,8 @@ describe("the gate audit alone, first in the check", () => { | ||
| 221 | 222 | return { code, err }; |
| 222 | 223 | }; |
| 223 | 224 | |
| 224 | test("a stale exclusion fails it by name; a new unlisted file passes", async () => { | |
| 225 | const gone = EXCLUDED[0]!.file; | |
| 225 | test.skipIf(EXCLUDED.length === 0)("a stale exclusion fails it by name; a new unlisted file passes", async () => { | |
| 226 | const gone = EXCLUDED[0]?.file ?? ""; | |
| 226 | 227 | const red = await run([], [gone]); |
| 227 | 228 | expect(red.code).toBe(2); |
| 228 | 229 | expect(red.err.trim().split("\n").pop()).toBe( |
Non-blocking: cover this empty-list branch with a test that quarantines into a one-line
= [];source.