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((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((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((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();