Public: anyone can read this; the crew can change it.

Every test file is in the gate or excluded by name with an owning issue; the gate's list leaves forge.json (first half of #261) #496

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

mason/forge:mason/492-every-test-file-is-in-the-gate-or-exclud → main 4 commits fork

  • Merged. mason/492-every-test-file-is-in-the-gate-or-exclud was merged into main as b93ae66.

Review

Sign in to review this change.
8 files changed +468 −11
CLAUDE.md +3
@@ -74,6 +74,9 @@ bun run src/main.ts # boots on :3000, data in ./data
7474 the reason is printed so a skip is never read as a pass.
7575 - **`bun run check` is the gate**, and it reads its command from
7676 `forge.json`, so what you see is exactly what the push will be judged by.
77 It runs the files in `GATE` (`scripts/gate.ts`), not the whole suite; every
78 other test file is in `EXCLUDED` with its reason and owning issue. Add a new
79 test file to `GATE`, or the check goes red.
7780 - **Every non-trivial change ships with a test.** Prefer protocol-level tests
7881 that boot a real app (`createApp` + `serve`) over unit mocks. Adversarial
7982 tests (can this be bypassed?) are worth more than happy-path ones.
README.md +8 −1
@@ -376,7 +376,7 @@ bun run check:record # the same, and rewrites scripts/test-times.json
376376
377377 `bun run check` is the gate: the same command `forge.json` declares, so what
378378 it says here is what the deploy will say. It type-checks once, then runs the
379 test files named in `check.run` as parallel `bun test` shards
379 test files `scripts/gate.ts` lists in `GATE` as parallel `bun test` shards
380380 (`scripts/test-shards.ts`): one per CPU the process may use, at most eight,
381381 and `FORGE_CHECK_SHARDS=2` turns that down on a small or busy host. Each
382382 shard's output is written whole as it finishes; the log ends with every
@@ -414,6 +414,13 @@ in that test. Two things it will not do:
414414 and names the tool (`test/helpers/tools.ts`). Install OpenSSH and they run
415415 again — the skip is conditional on the binary, never on anything else.
416416
417 Every `test/*.test.ts` is either in `GATE` or in `EXCLUDED` with a one-line
418 reason and the issue that owns admitting it; `test/check-list.test.ts` goes
419 red on a file in neither, in both, or named without existing, so a new test
420 file never sits outside the gate silently. A file joins the gate by moving
421 from `EXCLUDED` to `GATE` in a pull whose check is green — that pull is the
422 proof it passes.
423
417424 ## Shape
418425
419426 ```
forge.json +1 −1
@@ -6,7 +6,7 @@
66 "ready": 90
77 },
88 "check": {
9 "run": "bun x tsc --noEmit && bun scripts/test-shards.ts --timeout 60000 -- crew review-route review-lines review-gate crew-wrapup crew-limits crew-parks crew-sandbox steer session-settle landing decide ledger checks dependencies map-closing autonomy studio studio-words forks fork-flow cross-repo-pull pulls apps apps-adversarial apps-busy apps-token surfaces-adversarial orgs web git-trust helpers shards migrate vitals guard asks feed feed-page offline share cloud run-secrets forge-cloudflare usage tokens credentials standing board board-windows spawn-plan spawn web-apps fleet-ops meter told-by",
9 "run": "bun x tsc --noEmit && bun scripts/test-shards.ts --timeout 60000 --gate",
1010 "timeout": 1800
1111 }
1212 }
scripts/check.ts +1 −1
@@ -10,7 +10,7 @@
1010 * `--record` runs the same string with `scripts/test-shards.ts --record`
1111 * in place of `scripts/test-shards.ts`, which rewrites
1212 * `scripts/test-times.json` from this run, so the file list still lives
13 * only in `forge.json`.
13 * only in `scripts/gate.ts`.
1414 */
1515
1616 const manifest = (await Bun.file(
scripts/gate.ts added +352
@@ -0,0 +1,352 @@
1 /**
2 * Which test files the gate runs, and why every other one does not.
3 *
4 * `GATE` is the list `bun scripts/test-shards.ts --gate` runs, so
5 * `forge.json`'s `check.run` names no test file itself. `EXCLUDED` is every
6 * other `test/*.test.ts`, each with the reason it is out and the issue that
7 * owns bringing it in. `test/check-list.test.ts` goes red when a file is in
8 * neither list, in both, or named here without existing — so a new test file
9 * cannot sit outside the gate silently.
10 *
11 * A file joins the gate by moving from `EXCLUDED` to `GATE` in a pull whose
12 * check is green.
13 */
14
15 export type Exclusion = {
16 /** Repository-relative path, `test/….test.ts`. */
17 file: string;
18 /** One line: why the gate does not run it today. */
19 reason: string;
20 /** The issue that owns admitting it. */
21 issue: number;
22 };
23
24 export const GATE: readonly string[] = [
25 "test/crew.test.ts",
26 "test/review-route.test.ts",
27 "test/review-lines.test.ts",
28 "test/review-gate.test.ts",
29 "test/crew-wrapup.test.ts",
30 "test/crew-limits.test.ts",
31 "test/crew-parks.test.ts",
32 "test/crew-sandbox.test.ts",
33 "test/steer.test.ts",
34 "test/session-settle.test.ts",
35 "test/landing.test.ts",
36 "test/decide.test.ts",
37 "test/ledger.test.ts",
38 "test/checks.test.ts",
39 "test/dependencies.test.ts",
40 "test/map-closing.test.ts",
41 "test/autonomy.test.ts",
42 "test/studio.test.ts",
43 "test/studio-words.test.ts",
44 "test/forks.test.ts",
45 "test/fork-flow.test.ts",
46 "test/cross-repo-pull.test.ts",
47 "test/pulls.test.ts",
48 "test/apps.test.ts",
49 "test/apps-adversarial.test.ts",
50 "test/apps-busy.test.ts",
51 "test/apps-token.test.ts",
52 "test/surfaces-adversarial.test.ts",
53 "test/orgs.test.ts",
54 "test/web.test.ts",
55 "test/git-trust.test.ts",
56 "test/helpers.test.ts",
57 "test/shards.test.ts",
58 "test/migrate.test.ts",
59 "test/vitals.test.ts",
60 "test/guard.test.ts",
61 "test/asks.test.ts",
62 "test/feed.test.ts",
63 "test/feed-page.test.ts",
64 "test/offline.test.ts",
65 "test/share.test.ts",
66 "test/cloud.test.ts",
67 "test/run-secrets.test.ts",
68 "test/forge-cloudflare.test.ts",
69 "test/usage.test.ts",
70 "test/tokens.test.ts",
71 "test/credentials.test.ts",
72 "test/standing.test.ts",
73 "test/board.test.ts",
74 "test/spawn-plan.test.ts",
75 "test/spawn.test.ts",
76 "test/web-apps.test.ts",
77 "test/fleet-ops.test.ts",
78 "test/meter.test.ts",
79 "test/told-by.test.ts",
80 "test/check-list.test.ts",
81 ];
82
83 export const EXCLUDED: readonly Exclusion[] = [
84 { file: "test/actions.test.ts", reason: "not yet admitted", issue: 261 },
85 {
86 file: "test/agents.test.ts",
87 reason: "red: fifty concurrent pushes come back 'auth required'",
88 issue: 293,
89 },
90 { file: "test/api-errors.test.ts", reason: "not yet admitted", issue: 261 },
91 { file: "test/app-domains.test.ts", reason: "not yet admitted", issue: 261 },
92 { file: "test/app-env.test.ts", reason: "not yet admitted", issue: 261 },
93 { file: "test/app.test.ts", reason: "not yet admitted", issue: 261 },
94 { file: "test/apps-api.test.ts", reason: "not yet admitted", issue: 261 },
95 { file: "test/apps-idle.test.ts", reason: "not yet admitted", issue: 261 },
96 {
97 file: "test/apps-open-host.test.ts",
98 reason: "not yet admitted",
99 issue: 261,
100 },
101 { file: "test/apps-sleep.test.ts", reason: "not yet admitted", issue: 261 },
102 { file: "test/apps-ssh.test.ts", reason: "not yet admitted", issue: 261 },
103 { file: "test/apps-ws.test.ts", reason: "not yet admitted", issue: 261 },
104 { file: "test/audit.test.ts", reason: "not yet admitted", issue: 261 },
105 {
106 file: "test/board-windows.test.ts",
107 reason: "red: an app with spend and nothing shipped is missing from the board",
108 issue: 555,
109 },
110 { file: "test/branches.test.ts", reason: "not yet admitted", issue: 261 },
111 { file: "test/cli-sync.test.ts", reason: "not yet admitted", issue: 261 },
112 { file: "test/code.test.ts", reason: "not yet admitted", issue: 261 },
113 { file: "test/contents-api.test.ts", reason: "not yet admitted", issue: 261 },
114 { file: "test/contract.test.ts", reason: "not yet admitted", issue: 261 },
115 { file: "test/counts.test.ts", reason: "not yet admitted", issue: 261 },
116 {
117 file: "test/crew-interrupted.test.ts",
118 reason: "not yet admitted",
119 issue: 261,
120 },
121 { file: "test/crew-loop.test.ts", reason: "not yet admitted", issue: 261 },
122 { file: "test/crew-totals.test.ts", reason: "not yet admitted", issue: 261 },
123 {
124 file: "test/diff-edge-cases.test.ts",
125 reason: "not yet admitted",
126 issue: 261,
127 },
128 { file: "test/diff.test.ts", reason: "not yet admitted", issue: 261 },
129 { file: "test/edge-auth.test.ts", reason: "not yet admitted", issue: 261 },
130 {
131 file: "test/edits-security.test.ts",
132 reason: "not yet admitted",
133 issue: 261,
134 },
135 { file: "test/edits.test.ts", reason: "not yet admitted", issue: 261 },
136 {
137 file: "test/event-audience.test.ts",
138 reason: "not yet admitted",
139 issue: 261,
140 },
141 { file: "test/feeds.test.ts", reason: "not yet admitted", issue: 261 },
142 { file: "test/fleet-graph.test.ts", reason: "not yet admitted", issue: 261 },
143 { file: "test/following.test.ts", reason: "not yet admitted", issue: 261 },
144 { file: "test/git-local.test.ts", reason: "not yet admitted", issue: 261 },
145 { file: "test/git-ssh.test.ts", reason: "not yet admitted", issue: 261 },
146 { file: "test/git.test.ts", reason: "not yet admitted", issue: 261 },
147 { file: "test/hardening.test.ts", reason: "not yet admitted", issue: 261 },
148 {
149 file: "test/highlight.test.ts",
150 reason: "red: a 10ms highlighter bound fails on a slower machine",
151 issue: 294,
152 },
153 { file: "test/image-gif.test.ts", reason: "not yet admitted", issue: 261 },
154 {
155 file: "test/image-graphics.test.ts",
156 reason: "not yet admitted",
157 issue: 261,
158 },
159 { file: "test/image-jpeg.test.ts", reason: "not yet admitted", issue: 261 },
160 { file: "test/image-png.test.ts", reason: "not yet admitted", issue: 261 },
161 { file: "test/issues.test.ts", reason: "not yet admitted", issue: 261 },
162 { file: "test/kernel.test.ts", reason: "not yet admitted", issue: 261 },
163 { file: "test/keys.test.ts", reason: "not yet admitted", issue: 261 },
164 { file: "test/live-pages.test.ts", reason: "not yet admitted", issue: 261 },
165 { file: "test/live.test.ts", reason: "not yet admitted", issue: 261 },
166 {
167 file: "test/looks.test.ts",
168 reason:
169 "red: asserts the dashboard and studio pages as they were before the redesign",
170 issue: 291,
171 },
172 { file: "test/map.test.ts", reason: "not yet admitted", issue: 261 },
173 { file: "test/mcp.test.ts", reason: "not yet admitted", issue: 261 },
174 { file: "test/notify.test.ts", reason: "not yet admitted", issue: 261 },
175 { file: "test/onboarding.test.ts", reason: "not yet admitted", issue: 261 },
176 {
177 file: "test/origins-oauth.test.ts",
178 reason: "not yet admitted",
179 issue: 261,
180 },
181 {
182 file: "test/origins-operator.test.ts",
183 reason: "not yet admitted",
184 issue: 261,
185 },
186 {
187 file: "test/origins-security-auth.test.ts",
188 reason: "not yet admitted",
189 issue: 261,
190 },
191 {
192 file: "test/origins-security-provider.test.ts",
193 reason: "not yet admitted",
194 issue: 261,
195 },
196 {
197 file: "test/origins-security-push.test.ts",
198 reason: "not yet admitted",
199 issue: 261,
200 },
201 { file: "test/origins-sync.test.ts", reason: "not yet admitted", issue: 261 },
202 { file: "test/origins.test.ts", reason: "not yet admitted", issue: 261 },
203 { file: "test/pacing.test.ts", reason: "not yet admitted", issue: 261 },
204 { file: "test/palette.test.ts", reason: "not yet admitted", issue: 261 },
205 { file: "test/parity.test.ts", reason: "not yet admitted", issue: 261 },
206 { file: "test/policy.test.ts", reason: "not yet admitted", issue: 261 },
207 { file: "test/probe.test.ts", reason: "not yet admitted", issue: 261 },
208 { file: "test/probes.test.ts", reason: "not yet admitted", issue: 261 },
209 { file: "test/prompts.test.ts", reason: "not yet admitted", issue: 261 },
210 { file: "test/public-url.test.ts", reason: "not yet admitted", issue: 261 },
211 { file: "test/push-through.test.ts", reason: "not yet admitted", issue: 261 },
212 { file: "test/qr.test.ts", reason: "not yet admitted", issue: 261 },
213 { file: "test/read-api.test.ts", reason: "not yet admitted", issue: 261 },
214 { file: "test/revert.test.ts", reason: "not yet admitted", issue: 261 },
215 {
216 file: "test/reviews-states.test.ts",
217 reason: "not yet admitted",
218 issue: 261,
219 },
220 { file: "test/reviews.test.ts", reason: "not yet admitted", issue: 261 },
221 { file: "test/runs-lease.test.ts", reason: "not yet admitted", issue: 261 },
222 { file: "test/scaffold.test.ts", reason: "not yet admitted", issue: 261 },
223 { file: "test/scale.test.ts", reason: "not yet admitted", issue: 261 },
224 { file: "test/search.test.ts", reason: "not yet admitted", issue: 261 },
225 { file: "test/secret-store.test.ts", reason: "not yet admitted", issue: 261 },
226 {
227 file: "test/security-merge.test.ts",
228 reason: "not yet admitted",
229 issue: 261,
230 },
231 { file: "test/security-xss.test.ts", reason: "not yet admitted", issue: 261 },
232 { file: "test/settings.test.ts", reason: "not yet admitted", issue: 261 },
233 {
234 file: "test/signin-throttle.test.ts",
235 reason: "not yet admitted",
236 issue: 261,
237 },
238 {
239 file: "test/snapshot-destination.test.ts",
240 reason: "not yet admitted",
241 issue: 261,
242 },
243 { file: "test/snapshot.test.ts", reason: "not yet admitted", issue: 261 },
244 {
245 file: "test/spawn-placement.test.ts",
246 reason: "not yet admitted",
247 issue: 261,
248 },
249 { file: "test/sse.test.ts", reason: "not yet admitted", issue: 261 },
250 { file: "test/ssh-app.test.ts", reason: "not yet admitted", issue: 261 },
251 {
252 file: "test/ssh-port-taken.test.ts",
253 reason: "not yet admitted",
254 issue: 261,
255 },
256 {
257 file: "test/ssh-site.test.ts",
258 reason:
259 "red: asserts the dashboard and studio pages as they were before the redesign",
260 issue: 291,
261 },
262 { file: "test/ssh.test.ts", reason: "not yet admitted", issue: 261 },
263 { file: "test/standards.test.ts", reason: "not yet admitted", issue: 261 },
264 { file: "test/studio-phone.test.ts", reason: "not yet admitted", issue: 261 },
265 {
266 file: "test/subscriptions.test.ts",
267 reason: "not yet admitted",
268 issue: 261,
269 },
270 { file: "test/tui-app.test.ts", reason: "not yet admitted", issue: 261 },
271 { file: "test/tui-apps.test.ts", reason: "not yet admitted", issue: 261 },
272 { file: "test/tui-code.test.ts", reason: "not yet admitted", issue: 261 },
273 { file: "test/tui-editor.test.ts", reason: "not yet admitted", issue: 261 },
274 { file: "test/tui-icons.test.ts", reason: "not yet admitted", issue: 261 },
275 { file: "test/tui-input.test.ts", reason: "not yet admitted", issue: 261 },
276 { file: "test/tui-local.test.ts", reason: "not yet admitted", issue: 261 },
277 { file: "test/tui-mouse.test.ts", reason: "not yet admitted", issue: 261 },
278 { file: "test/tui-render.test.ts", reason: "not yet admitted", issue: 261 },
279 { file: "test/tui-screens.test.ts", reason: "not yet admitted", issue: 261 },
280 { file: "test/tui-size.test.ts", reason: "not yet admitted", issue: 261 },
281 { file: "test/tui-widgets.test.ts", reason: "not yet admitted", issue: 261 },
282 { file: "test/tui-width.test.ts", reason: "not yet admitted", issue: 261 },
283 {
284 file: "test/tui-workflows.test.ts",
285 reason: "not yet admitted",
286 issue: 261,
287 },
288 {
289 file: "test/tui-workspace.test.ts",
290 reason: "not yet admitted",
291 issue: 261,
292 },
293 { file: "test/view-source.test.ts", reason: "not yet admitted", issue: 261 },
294 { file: "test/web-browse.test.ts", reason: "not yet admitted", issue: 261 },
295 {
296 file: "test/web-code.test.ts",
297 reason:
298 "red: asserts the dashboard and studio pages as they were before the redesign",
299 issue: 291,
300 },
301 { file: "test/web-editor.test.ts", reason: "not yet admitted", issue: 261 },
302 {
303 file: "test/web-origins.test.ts",
304 reason:
305 "red: asserts the dashboard and studio pages as they were before the redesign",
306 issue: 291,
307 },
308 { file: "test/web-phone.test.ts", reason: "not yet admitted", issue: 261 },
309 { file: "test/web-ui.test.ts", reason: "not yet admitted", issue: 261 },
310 {
311 file: "test/web-workflows.test.ts",
312 reason: "not yet admitted",
313 issue: 261,
314 },
315 { file: "test/webhooks.test.ts", reason: "not yet admitted", issue: 261 },
316 ];
317
318 /**
319 * Everything wrong with a gate list, against the test files that exist:
320 * one line per problem, none when every file is accounted for exactly once.
321 */
322 export function auditGate(
323 present: readonly string[],
324 gate: readonly string[],
325 excluded: readonly Exclusion[],
326 ): string[] {
327 const problems: string[] = [];
328 const exists = new Set(present);
329 const gated = new Set<string>();
330 for (const file of gate) {
331 if (gated.has(file)) problems.push(`${file} is in GATE twice`);
332 gated.add(file);
333 if (!exists.has(file))
334 problems.push(`${file} is in GATE but does not exist`);
335 }
336 const out = new Set<string>();
337 for (const { file, reason, issue } of excluded) {
338 if (out.has(file)) problems.push(`${file} is in EXCLUDED twice`);
339 out.add(file);
340 if (!exists.has(file))
341 problems.push(`${file} is in EXCLUDED but does not exist`);
342 if (gated.has(file)) problems.push(`${file} is in both GATE and EXCLUDED`);
343 if (typeof reason !== "string" || reason.trim() === "")
344 problems.push(`${file} is excluded with no reason`);
345 if (!Number.isInteger(issue) || issue <= 0)
346 problems.push(`${file} is excluded with no issue number`);
347 }
348 for (const file of present)
349 if (!gated.has(file) && !out.has(file))
350 problems.push(`${file} is in neither GATE nor EXCLUDED`);
351 return problems;
352 }
scripts/test-shards.ts +10 −5
@@ -1,12 +1,14 @@
11 /**
22 * `bun test`, over the named files, in parallel shards.
33 *
4 * bun scripts/test-shards.ts [bun test flags…] --gate
45 * bun scripts/test-shards.ts [bun test flags…] test/a.test.ts test/b.test.ts …
56 * bun scripts/test-shards.ts --record [bun test flags…] test/a.test.ts …
67 * bun scripts/test-shards.ts [bun test flags…] -- a b … (test/a.test.ts …)
78 *
8 * The files come from the command line, so `forge.json`'s `check.run`
9 * stays the one place that names them. Each shard is its own `bun test`
9 * `--gate` runs the files `scripts/gate.ts` admits, which is how
10 * `forge.json`'s `check.run` names them; files on the command line are for
11 * running a slice by hand. Each shard is its own `bun test`
1012 * over a disjoint slice (`scripts/shards.ts` decides the slices); a
1113 * shard's output is written through whole when it finishes, so the log
1214 * reads one shard at a time, and the tail names every failing test from
@@ -31,21 +33,24 @@ import {
3133 weigher,
3234 type ShardResult,
3335 } from "./shards.ts";
36 import { GATE } from "./gate.ts";
3437
3538 const TIMES = new URL("./test-times.json", import.meta.url).pathname;
3639
3740 const argv = process.argv.slice(2);
3841 const record = argv.includes("--record");
39 const args = argv.filter((arg) => arg !== "--record");
42 const gate = argv.includes("--gate");
43 const args = argv.filter((arg) => arg !== "--record" && arg !== "--gate");
4044 const split = splitArgs(args);
4145 if (!split.ok) {
4246 console.error(`test-shards: ${split.error}`);
4347 process.exit(2);
4448 }
45 const { files, flags } = split;
49 const flags = split.flags;
50 const files = [...(gate ? GATE : []), ...split.files];
4651
4752 if (files.length === 0) {
48 console.error("test-shards: name the test files to run");
53 console.error("test-shards: name the test files to run, or --gate");
4954 process.exit(2);
5055 }
5156 // A file that is not there would be a filter matching nothing to `bun
test/check-list.test.ts added +88
@@ -0,0 +1,88 @@
1 /**
2 * Every test file is accounted for (`scripts/gate.ts`): in the gate, or
3 * excluded by name with a reason and the issue that owns admitting it. A
4 * new test file that is in neither list turns the check red, so it cannot
5 * sit outside the gate silently.
6 */
7
8 import { describe, expect, test } from "bun:test";
9 import { readdirSync } from "node:fs";
10 import { auditGate, EXCLUDED, GATE } from "../scripts/gate.ts";
11
12 const root = new URL("..", import.meta.url).pathname;
13 const present = readdirSync(`${root}test`)
14 .filter((name) => name.endsWith(".test.ts"))
15 .map((name) => `test/${name}`)
16 .sort();
17
18 describe("the gate's list", () => {
19 test("every test/*.test.ts is in exactly one list, and every entry exists", () => {
20 expect(auditGate(present, GATE, EXCLUDED)).toEqual([]);
21 });
22
23 test("this guard is itself in the gate", () => {
24 expect(GATE).toContain("test/check-list.test.ts");
25 });
26
27 test("forge.json's check names no test file and runs the gate's list", async () => {
28 const manifest = (await Bun.file(`${root}forge.json`).json()) as {
29 check: { run: string };
30 };
31 expect(manifest.check.run).not.toMatch(/\.test\.[cm]?[jt]sx?\b/);
32 expect(manifest.check.run).toContain("scripts/test-shards.ts");
33 expect(manifest.check.run).toContain("--gate");
34 });
35 });
36
37 describe("auditGate", () => {
38 const files = ["test/a.test.ts", "test/b.test.ts"];
39
40 test("a file in neither list is named", () => {
41 expect(auditGate(files, ["test/a.test.ts"], [])).toEqual([
42 "test/b.test.ts is in neither GATE nor EXCLUDED",
43 ]);
44 });
45
46 test("a file in both lists is named", () => {
47 expect(
48 auditGate(files, files, [
49 { file: "test/b.test.ts", reason: "slow", issue: 1 },
50 ]),
51 ).toEqual(["test/b.test.ts is in both GATE and EXCLUDED"]);
52 });
53
54 test("an exclusion with no reason or no issue is named", () => {
55 expect(
56 auditGate(
57 files,
58 [],
59 [
60 { file: "test/a.test.ts", reason: " ", issue: 7 },
61 { file: "test/b.test.ts", reason: "slow", issue: 0 },
62 ],
63 ),
64 ).toEqual([
65 "test/a.test.ts is excluded with no reason",
66 "test/b.test.ts is excluded with no issue number",
67 ]);
68 });
69
70 test("an entry naming a file that does not exist is named", () => {
71 expect(
72 auditGate(
73 files,
74 [...files, "test/gone.test.ts"],
75 [{ file: "test/old.test.ts", reason: "slow", issue: 3 }],
76 ),
77 ).toEqual([
78 "test/gone.test.ts is in GATE but does not exist",
79 "test/old.test.ts is in EXCLUDED but does not exist",
80 ]);
81 });
82
83 test("a file named twice in one list is named", () => {
84 expect(auditGate(files, [...files, "test/a.test.ts"], [])).toEqual([
85 "test/a.test.ts is in GATE twice",
86 ]);
87 });
88 });
test/shards.test.ts +5 −3
@@ -18,6 +18,7 @@ import {
1818 type ShardResult,
1919 } from "../scripts/shards.ts";
2020 import { parseCheck } from "../src/apps/manifest.ts";
21 import { GATE } from "../scripts/gate.ts";
2122
2223 const files = Array.from({ length: 23 }, (_, i) => `test/f${i}.test.ts`);
2324
@@ -295,7 +296,7 @@ describe("splitArgs", () => {
295296 expect(splitArgs(["--", name]).ok).toBe(false);
296297 });
297298
298 test("forge.json's check fits the manifest and names only files that exist", async () => {
299 test("forge.json's check fits the manifest and runs only files that exist", async () => {
299300 const text = readFileSync(new URL("../forge.json", import.meta.url), "utf8");
300301 // The forge's own reading of the manifest, which is what refused it.
301302 const check = parseCheck(text, undefined);
@@ -306,8 +307,9 @@ describe("splitArgs", () => {
306307 const split = splitArgs(args);
307308 expect(split.ok).toBe(true);
308309 if (!split.ok) return;
309 expect(split.files.length).toBeGreaterThan(0);
310 for (const file of split.files)
310 const files = [...(split.flags.includes("--gate") ? GATE : []), ...split.files];
311 expect(files.length).toBeGreaterThan(0);
312 for (const file of files)
311313 expect(await Bun.file(new URL(`../${file}`, import.meta.url)).exists()).toBe(true);
312314 });
313315 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs