|
3 | 3 | */ |
4 | 4 | import { readdir, readFile } from 'node:fs/promises' |
5 | 5 | import { join } from 'node:path' |
6 | | -import { describe, expect, it, vi } from 'vitest' |
7 | | - |
8 | | -// Structurally slow — it scans call sites across the repo — so under a fully-parallel local run this file |
9 | | -// blows the default timeout while passing in isolation and on CI. Give it a |
10 | | -// real budget instead of letting machine load decide the verdict. |
11 | | -vi.setConfig({ testTimeout: 30_000 }) |
| 6 | +import { beforeAll, describe, expect, it } from 'vitest' |
12 | 7 |
|
13 | 8 | /** |
14 | 9 | * `signalTableRowsChangedByActor` lets the acting tab skip its own refetch, which is only sound |
@@ -51,42 +46,76 @@ const FORWARDING_MODULE = 'lib/table/application/rows.ts' |
51 | 46 | */ |
52 | 47 | const SUPPLIER_PATTERNS = [/actorClientId:/, /signalTableRowsChangedByActor\([^)]*,/] as const |
53 | 48 |
|
54 | | -async function* walk(dir: string): AsyncGenerator<string> { |
55 | | - for (const entry of await readdir(dir, { withFileTypes: true })) { |
56 | | - if (entry.name === 'node_modules' || entry.name === '.next') continue |
57 | | - const full = join(dir, entry.name) |
58 | | - if (entry.isDirectory()) yield* walk(full) |
59 | | - else if (entry.name.endsWith('.ts') && !entry.name.includes('.test.')) yield full |
| 49 | +/** Files read per batch; bounds open descriptors while keeping the disk busy. */ |
| 50 | +const READ_BATCH_SIZE = 64 |
| 51 | + |
| 52 | +async function walk(dir: string): Promise<string[]> { |
| 53 | + const entries = await readdir(dir, { withFileTypes: true }) |
| 54 | + const nested = await Promise.all( |
| 55 | + entries.map(async (entry) => { |
| 56 | + if (entry.name === 'node_modules' || entry.name === '.next') return [] |
| 57 | + const full = join(dir, entry.name) |
| 58 | + if (entry.isDirectory()) return walk(full) |
| 59 | + return entry.name.endsWith('.ts') && !entry.name.includes('.test.') ? [full] : [] |
| 60 | + }) |
| 61 | + ) |
| 62 | + return nested.flat() |
| 63 | +} |
| 64 | + |
| 65 | +/** |
| 66 | + * Every source file under the app root, keyed by its relative path. Read once |
| 67 | + * for the file: both sweeps scan the same tree, and walking it per test was the |
| 68 | + * whole cost of this file. |
| 69 | + */ |
| 70 | +let sources: Map<string, string> |
| 71 | + |
| 72 | +async function readSources(): Promise<Map<string, string>> { |
| 73 | + const files = await walk(APP_ROOT) |
| 74 | + const found = new Map<string, string>() |
| 75 | + for (let start = 0; start < files.length; start += READ_BATCH_SIZE) { |
| 76 | + const batch = files.slice(start, start + READ_BATCH_SIZE) |
| 77 | + const contents = await Promise.all(batch.map((file) => readFile(file, 'utf8'))) |
| 78 | + batch.forEach((file, index) => { |
| 79 | + found.set(file.slice(APP_ROOT.length + 1), contents[index]) |
| 80 | + }) |
60 | 81 | } |
| 82 | + return found |
61 | 83 | } |
62 | 84 |
|
63 | | -async function filesMatching( |
| 85 | +function filesMatching( |
64 | 86 | matches: (source: string) => boolean, |
65 | 87 | skip: (relative: string) => boolean = () => false |
66 | | -) { |
| 88 | +): string[] { |
67 | 89 | const found: string[] = [] |
68 | | - for await (const file of walk(APP_ROOT)) { |
69 | | - const source = await readFile(file, 'utf8') |
| 90 | + for (const [relative, source] of sources) { |
70 | 91 | if (!matches(source)) continue |
71 | | - const relative = file.slice(APP_ROOT.length + 1) |
72 | 92 | if (skip(relative)) continue |
73 | 93 | found.push(relative) |
74 | 94 | } |
75 | 95 | return found.sort() |
76 | 96 | } |
77 | 97 |
|
78 | 98 | describe('signalTableRowsChangedByActor call sites', () => { |
79 | | - it('is called only where the acting tab reconciles the write locally', async () => { |
80 | | - const callers = await filesMatching( |
| 99 | + /** |
| 100 | + * Structurally slow — it reads every source file in the app — so under a |
| 101 | + * fully-parallel local run the scan blows the default budget while passing in |
| 102 | + * isolation and on CI. Give it a real budget of its own, outside any test's. |
| 103 | + */ |
| 104 | + beforeAll(async () => { |
| 105 | + sources = await readSources() |
| 106 | + }, 30_000) |
| 107 | + |
| 108 | + it('is called only where the acting tab reconciles the write locally', () => { |
| 109 | + const callers = filesMatching( |
81 | 110 | (source) => source.includes('signalTableRowsChangedByActor('), |
82 | 111 | (relative) => relative === DECLARING_MODULE |
83 | 112 | ) |
84 | 113 |
|
85 | 114 | expect(callers).toEqual([...ATTRIBUTED_CALL_SITES].sort()) |
86 | 115 | }) |
87 | 116 |
|
88 | | - it('is given an actor only by surfaces whose client hook reconciles locally', async () => { |
89 | | - const suppliers = await filesMatching( |
| 117 | + it('is given an actor only by surfaces whose client hook reconciles locally', () => { |
| 118 | + const suppliers = filesMatching( |
90 | 119 | (source) => SUPPLIER_PATTERNS.some((pattern) => pattern.test(source)), |
91 | 120 | (relative) => relative === DECLARING_MODULE || relative === FORWARDING_MODULE |
92 | 121 | ) |
|
0 commit comments