|
1 | | -import { readFileSync } from "node:fs"; |
2 | | -import { join } from "node:path"; |
| 1 | +import { readdirSync, readFileSync, statSync } from "node:fs"; |
| 2 | +import { join, relative } from "node:path"; |
3 | 3 | import { describe, expect, test } from "bun:test"; |
4 | 4 |
|
5 | 5 | // Guard against the gate drifting apart again (CL-7300): `bun run check` and |
@@ -27,6 +27,25 @@ const GUARD_SCRIPT = "check:projects-dir-guard"; |
27 | 27 | const TEST_SUITE = |
28 | 28 | "bun test ./src ./tests ./evals ./scripts --randomize --seed 424242"; |
29 | 29 |
|
| 30 | +function expandToTestFiles(filters: string[]): string[] { |
| 31 | + const files: string[] = []; |
| 32 | + const walk = (dir: string) => { |
| 33 | + for (const entry of readdirSync(dir, { withFileTypes: true })) { |
| 34 | + const absolute = join(dir, entry.name); |
| 35 | + if (entry.isDirectory()) walk(absolute); |
| 36 | + else if (entry.name.endsWith(".test.ts")) |
| 37 | + files.push(relative(repoRoot, absolute).split("/").join("/")); |
| 38 | + } |
| 39 | + }; |
| 40 | + for (const filter of filters) { |
| 41 | + const absolute = join(repoRoot, filter.replace(/^\.\//, "")); |
| 42 | + if (statSync(absolute).isFile()) |
| 43 | + files.push(relative(repoRoot, absolute).split("/").join("/")); |
| 44 | + else walk(absolute); |
| 45 | + } |
| 46 | + return files.sort(); |
| 47 | +} |
| 48 | + |
30 | 49 | describe("check gate", () => { |
31 | 50 | test("`test` is the seeded, randomized one-process suite whose path union CI shards", () => { |
32 | 51 | expect(pkg.scripts.test).toBe(TEST_SUITE); |
@@ -67,14 +86,20 @@ describe("check gate", () => { |
67 | 86 | }); |
68 | 87 |
|
69 | 88 | test("CI test shards cover exactly the suite's paths", () => { |
70 | | - // Sharding must never silently drop part of the suite: the union of the |
71 | | - // matrix shards has to equal the unsharded `test` script's paths. |
72 | | - const shardPaths = [...ci.matchAll(/^\s+paths: (.+)$/gm)] |
73 | | - .flatMap((match) => match[1]?.trim().split(/\s+/) ?? []) |
74 | | - .sort(); |
75 | | - const suitePaths = TEST_SUITE.split(" ") |
76 | | - .filter((part) => part.startsWith("./")) |
77 | | - .sort(); |
78 | | - expect(shardPaths).toEqual(suitePaths); |
| 89 | + // Sharding must never silently drop (or double-run) part of the suite: |
| 90 | + // expanding the matrix shards' filters to test files has to equal the |
| 91 | + // unsharded `test` script's paths expanded the same way. Subdirectory |
| 92 | + // shards (src-a/b/c) can never equal the literal ./src string, so this |
| 93 | + // compares sorted file sets; a file covered twice fails the equality |
| 94 | + // through the duplicate entry. |
| 95 | + const shardFilters = [...ci.matchAll(/^\s+paths: (.+)$/gm)].flatMap( |
| 96 | + (match) => match[1]?.trim().split(/\s+/) ?? [], |
| 97 | + ); |
| 98 | + const suiteFilters = TEST_SUITE.split(" ").filter((part) => |
| 99 | + part.startsWith("./"), |
| 100 | + ); |
| 101 | + expect(expandToTestFiles(shardFilters)).toEqual( |
| 102 | + expandToTestFiles(suiteFilters), |
| 103 | + ); |
79 | 104 | }); |
80 | 105 | }); |
0 commit comments