diff --git a/lib/internal/test_runner/runner.js b/lib/internal/test_runner/runner.js index 2b622e6be39eb0..700067e0c8a405 100644 --- a/lib/internal/test_runner/runner.js +++ b/lib/internal/test_runner/runner.js @@ -444,6 +444,9 @@ function runTestFile(path, filesWatcher, opts) { finished(child.stdout, { __proto__: null, signal: t.signal }), ]); + // Close readline interface to prevent memory leak + rl.close(); + if (watchMode) { filesWatcher.runningProcesses.delete(path); filesWatcher.runningSubtests.delete(path); @@ -506,7 +509,7 @@ function watchFiles(testFiles, opts) { } // Watch for changes in current filtered files - watcher.on('changed', ({ owners, eventType }) => { + const onChanged = ({ owners, eventType }) => { if (!opts.hasFiles && (eventType === 'rename' || eventType === 'change')) { const updatedTestFiles = createTestFileList(opts.globPatterns, opts.cwd); const newFileName = ArrayPrototypeFind(updatedTestFiles, (x) => !ArrayPrototypeIncludes(testFiles, x)); @@ -547,19 +550,29 @@ function watchFiles(testFiles, opts) { triggerUncaughtException(error, true /* fromPromise */); })); } - }); + }; + + watcher.on('changed', onChanged); + + // Cleanup function to remove event listener and prevent memory leak + const cleanup = () => { + watcher.removeListener('changed', onChanged); + opts.root.harness.watching = false; + opts.root.postRun(); + }; + if (opts.signal) { kResistStopPropagation ??= require('internal/event_target').kResistStopPropagation; opts.signal.addEventListener( 'abort', - () => { - opts.root.harness.watching = false; - opts.root.postRun(); - }, + cleanup, { __proto__: null, once: true, [kResistStopPropagation]: true }, ); } + // Expose cleanup method for proper resource management + filesWatcher.cleanup = cleanup; + return filesWatcher; }