feat(watchers): add Rust-powered watcher backend with runtime fallback and cross-platform test coverage
This commit is contained in:
86
test/test.fswatcher-linger.node.ts
Normal file
86
test/test.fswatcher-linger.node.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import * as chokidar from 'chokidar';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const TEST_DIR = './test/assets';
|
||||
const delay = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
/**
|
||||
* Count active FSWatcher handles in the process
|
||||
*/
|
||||
function countFSWatcherHandles(): number {
|
||||
const handles = (process as any)._getActiveHandles();
|
||||
return handles.filter((h: any) => h?.constructor?.name === 'FSWatcher').length;
|
||||
}
|
||||
|
||||
tap.test('should not leave lingering FSWatcher handles after chokidar close', async () => {
|
||||
const handlesBefore = countFSWatcherHandles();
|
||||
console.log(`FSWatcher handles before: ${handlesBefore}`);
|
||||
|
||||
// Start a chokidar watcher
|
||||
const watcher = chokidar.watch(path.resolve(TEST_DIR), {
|
||||
persistent: true,
|
||||
ignoreInitial: false,
|
||||
});
|
||||
|
||||
// Wait for ready
|
||||
await new Promise<void>((resolve) => watcher.on('ready', resolve));
|
||||
|
||||
const handlesDuring = countFSWatcherHandles();
|
||||
console.log(`FSWatcher handles during watch: ${handlesDuring}`);
|
||||
expect(handlesDuring).toBeGreaterThan(handlesBefore);
|
||||
|
||||
// Close the watcher
|
||||
await watcher.close();
|
||||
console.log('chokidar.close() resolved');
|
||||
|
||||
// Check immediately after close
|
||||
const handlesAfterClose = countFSWatcherHandles();
|
||||
console.log(`FSWatcher handles immediately after close: ${handlesAfterClose}`);
|
||||
|
||||
// Wait a bit and check again to see if handles are cleaned up asynchronously
|
||||
await delay(500);
|
||||
const handlesAfterDelay500 = countFSWatcherHandles();
|
||||
console.log(`FSWatcher handles after 500ms: ${handlesAfterDelay500}`);
|
||||
|
||||
await delay(1500);
|
||||
const handlesAfterDelay2000 = countFSWatcherHandles();
|
||||
console.log(`FSWatcher handles after 2000ms: ${handlesAfterDelay2000}`);
|
||||
|
||||
const lingeringHandles = handlesAfterDelay2000 - handlesBefore;
|
||||
console.log(`Lingering FSWatcher handles: ${lingeringHandles}`);
|
||||
|
||||
if (lingeringHandles > 0) {
|
||||
console.log('WARNING: chokidar left lingering FSWatcher handles after close()');
|
||||
} else {
|
||||
console.log('OK: all FSWatcher handles were cleaned up');
|
||||
}
|
||||
|
||||
expect(lingeringHandles).toEqual(0);
|
||||
});
|
||||
|
||||
tap.test('should not leave handles after multiple open/close cycles', async () => {
|
||||
const handlesBefore = countFSWatcherHandles();
|
||||
console.log(`\nMulti-cycle test - handles before: ${handlesBefore}`);
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const watcher = chokidar.watch(path.resolve(TEST_DIR), {
|
||||
persistent: true,
|
||||
ignoreInitial: false,
|
||||
});
|
||||
await new Promise<void>((resolve) => watcher.on('ready', resolve));
|
||||
const during = countFSWatcherHandles();
|
||||
console.log(` Cycle ${i + 1} - handles during: ${during}`);
|
||||
await watcher.close();
|
||||
await delay(500);
|
||||
}
|
||||
|
||||
const handlesAfter = countFSWatcherHandles();
|
||||
const leaked = handlesAfter - handlesBefore;
|
||||
console.log(`Handles after 3 cycles: ${handlesAfter} (leaked: ${leaked})`);
|
||||
|
||||
expect(leaked).toEqual(0);
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
Reference in New Issue
Block a user