From 014a1b9a6254f484f0cfcb726617584b496acc5e Mon Sep 17 00:00:00 2001 From: Christopher Hiller Date: Mon, 2 Nov 2020 12:10:12 -0800 Subject: [PATCH] properly dispose Mocha instance in watch mode; closes #4495 Signed-off-by: Christopher Hiller --- lib/cli/watch-run.js | 6 ++++++ test/integration/helpers.js | 23 ++++++++++++-------- test/integration/options/watch.spec.js | 30 ++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/lib/cli/watch-run.js b/lib/cli/watch-run.js index 3bac550389..559329647e 100644 --- a/lib/cli/watch-run.js +++ b/lib/cli/watch-run.js @@ -41,6 +41,9 @@ exports.watchParallelRun = ( // I don't know why we're cloning the root suite. const rootSuite = mocha.suite.clone(); + // ensure we aren't leaking event listeners + mocha.dispose(); + // this `require` is needed because the require cache has been cleared. the dynamic // exports set via the below call to `mocha.ui()` won't work properly if a // test depends on this module (see `required-tokens.spec.js`). @@ -100,6 +103,9 @@ exports.watchRun = (mocha, {watchFiles, watchIgnore}, fileCollectParams) => { // I don't know why we're cloning the root suite. const rootSuite = mocha.suite.clone(); + // ensure we aren't leaking event listeners + mocha.dispose(); + // this `require` is needed because the require cache has been cleared. the dynamic // exports set via the below call to `mocha.ui()` won't work properly if a // test depends on this module (see `required-tokens.spec.js`). diff --git a/test/integration/helpers.js b/test/integration/helpers.js index 5435b1ddee..ccb20c9b11 100644 --- a/test/integration/helpers.js +++ b/test/integration/helpers.js @@ -415,8 +415,12 @@ async function runMochaWatchAsync(args, opts, change) { if (typeof opts === 'string') { opts = {cwd: opts}; } - opts = {sleepMs: 2000, ...opts, fork: process.platform === 'win32'}; - opts.stdio = ['pipe', 'pipe', 'inherit']; + opts = { + sleepMs: 2000, + stdio: ['pipe', 'pipe', 'inherit'], + ...opts, + fork: process.platform === 'win32' + }; const [mochaProcess, resultPromise] = invokeMochaAsync( [...args, '--watch'], opts @@ -539,24 +543,25 @@ function sleep(time) { module.exports = { DEFAULT_FIXTURE, SPLIT_DOT_REPORTER_REGEXP, + copyFixture, createTempDir, + escapeRegExp, + getSummary, invokeMocha, invokeMochaAsync, invokeNode, - getSummary, + replaceFileContents, resolveFixturePath, - toJSONResult, - escapeRegExp, runMocha, - runMochaJSON, runMochaAsync, + runMochaJSON, runMochaJSONAsync, runMochaWatchAsync, runMochaWatchJSONAsync, - copyFixture, - touchFile, - replaceFileContents + sleep, + toJSONResult, + touchFile }; /** diff --git a/test/integration/options/watch.spec.js b/test/integration/options/watch.spec.js index 0211ca25c2..498ddcf5f9 100644 --- a/test/integration/options/watch.spec.js +++ b/test/integration/options/watch.spec.js @@ -5,6 +5,8 @@ const path = require('path'); const { copyFixture, runMochaWatchJSONAsync, + sleep, + runMochaWatchAsync, touchFile, replaceFileContents, createTempDir, @@ -343,5 +345,33 @@ describe('--watch', function() { it('mochaHooks.afterAll runs as expected', setupHookTest('afterAll')); it('mochaHooks.afterEach runs as expected', setupHookTest('afterEach')); }); + + it('should not leak event listeners', function() { + this.timeout(20000); + const testFile = path.join(tempDir, 'test.js'); + copyFixture(DEFAULT_FIXTURE, testFile); + + return expect( + runMochaWatchAsync( + [testFile], + {cwd: tempDir, stdio: 'pipe'}, + async () => { + // we want to cause _n + 1_ reruns, which should cause the warning + // to occur if the listeners aren't properly destroyed + const iterations = new Array(process.getMaxListeners() + 1); + // eslint-disable-next-line no-unused-vars + for await (const _ of iterations) { + touchFile(testFile); + await sleep(1000); + } + } + ), + 'when fulfilled', + 'to satisfy', + { + output: expect.it('not to match', /MaxListenersExceededWarning/) + } + ); + }); }); });