87 lines
2.9 KiB
TypeScript
87 lines
2.9 KiB
TypeScript
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();
|