From 1535649572253e4dbf4502907668c9552a6a5e48 Mon Sep 17 00:00:00 2001 From: andreinknv Date: Sun, 26 Apr 2026 17:01:54 -0400 Subject: [PATCH] test(watcher): fix fs.watch flake by adding settle delay before file write MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three watcher tests wrote files immediately after `watcher.start()`, hitting a race with macOS FSEvents (and Linux inotify): there's a small but real latency between `fs.watch()` returning and the kernel actually delivering events. Under parallel test load (when the host CPU is busy running other test files in worker threads), that latency balloons and the file-change event is dropped before the watcher is fully registered. Result: the test waits 5s for a sync that never fires and times out. Affected: - debounced sync > should trigger sync after file change - callbacks > should call onSyncComplete after successful sync - callbacks > should call onSyncError when sync throws - debounced sync > should debounce rapid changes (less affected; its 50ms-spaced loop incidentally settles, but explicit is better) - CodeGraph integration > should auto-sync when files change Other tests in the same file already had a 400ms settle delay with a comment ("Let watcher settle — fs.watch may fire residual events from beforeEach") in the filtering tests. This PR factors that out into a `letWatcherSettle()` helper and applies it consistently to every test that writes immediately after `start()`. No production code change. The flake had no user impact — it was purely a test-order artifact under parallel load. ## Verification - Pre-fix: ran `npm test` 8+ times across this session — fs.watch flake fired in roughly 1 of 3 runs under parallel load. - Post-fix: 3 consecutive `npm test` runs, 380/380 each, no flakes. Co-Authored-By: Claude Opus 4.7 (1M context) --- __tests__/watcher.test.ts | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/__tests__/watcher.test.ts b/__tests__/watcher.test.ts index f3638e6d..7e72539b 100644 --- a/__tests__/watcher.test.ts +++ b/__tests__/watcher.test.ts @@ -31,6 +31,19 @@ function waitFor( }); } +/** + * fs.watch on macOS (FSEvents) and Linux (inotify) has a small but real + * latency between `fs.watch()` returning and the kernel actually + * delivering events. Writing a file in that window — particularly under + * parallel test load when the host CPU is busy — drops the event and + * causes a 5s timeout for "should trigger sync after file change" style + * tests. This helper standardizes the settle delay to match the pattern + * already used by the filtering tests in this file. + */ +async function letWatcherSettle(): Promise { + await new Promise((r) => setTimeout(r, 400)); +} + describe('FileWatcher', () => { let testDir: string; @@ -101,6 +114,7 @@ describe('FileWatcher', () => { const watcher = new FileWatcher(testDir, baseConfig, syncFn, { debounceMs: 200 }); watcher.start(); + await letWatcherSettle(); // Create a new file fs.writeFileSync(path.join(testDir, 'src', 'new.ts'), 'export const y = 2;'); @@ -117,6 +131,7 @@ describe('FileWatcher', () => { const watcher = new FileWatcher(testDir, baseConfig, syncFn, { debounceMs: 500 }); watcher.start(); + await letWatcherSettle(); // Rapid-fire changes for (let i = 0; i < 5; i++) { @@ -145,7 +160,7 @@ describe('FileWatcher', () => { watcher.start(); // Let watcher settle — fs.watch may fire residual events from beforeEach - await new Promise((r) => setTimeout(r, 400)); + await letWatcherSettle(); syncFn.mockClear(); // Create a file that doesn't match include patterns @@ -165,7 +180,7 @@ describe('FileWatcher', () => { watcher.start(); // Let watcher settle — fs.watch may fire residual events from beforeEach - await new Promise((r) => setTimeout(r, 400)); + await letWatcherSettle(); syncFn.mockClear(); // Simulate a .codegraph directory change @@ -191,6 +206,7 @@ describe('FileWatcher', () => { }); watcher.start(); + await letWatcherSettle(); fs.writeFileSync(path.join(testDir, 'src', 'test.ts'), 'export const z = 3;'); @@ -209,6 +225,7 @@ describe('FileWatcher', () => { }); watcher.start(); + await letWatcherSettle(); fs.writeFileSync(path.join(testDir, 'src', 'test.ts'), 'export const z = 3;'); @@ -268,6 +285,7 @@ describe('FileWatcher', () => { const initialNodes = initialStats.nodeCount; cg.watch({ debounceMs: 300 }); + await letWatcherSettle(); // Add a new file with a function fs.writeFileSync(