From 0e1ccbb915ba8c2f73134af5bebd357f3329b9b7 Mon Sep 17 00:00:00 2001 From: Juerg B <44573692+juergba@users.noreply.github.com> Date: Sat, 11 Jan 2020 13:01:04 +0100 Subject: [PATCH] Fix leaking global 'uncaughtException' handler (#4147) --- lib/runner.js | 21 ++++++++++++------- .../fixtures/uncaught/listeners.fixture.js | 12 +++++++++++ test/integration/uncaught.spec.js | 12 +++++++++++ 3 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 test/integration/fixtures/uncaught/listeners.fixture.js diff --git a/lib/runner.js b/lib/runner.js index 3340567f26..948a9b9021 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -800,7 +800,7 @@ Runner.prototype.runSuite = function(suite, fn) { }; /** - * Handle uncaught exceptions. + * Handle uncaught exceptions within runner. * * @param {Error} err * @private @@ -893,6 +893,17 @@ Runner.prototype.uncaught = function(err) { this.abort(); }; +/** + * Handle uncaught exceptions after runner's end event. + * + * @param {Error} err + * @private + */ +Runner.prototype.uncaughtEnd = function uncaughtEnd(err) { + if (err instanceof Pending) return; + throw err; +}; + /** * Run the root suite and invoke `fn(failures)` * on completion. @@ -940,16 +951,12 @@ Runner.prototype.run = function(fn) { this.on(constants.EVENT_RUN_END, function() { debug(constants.EVENT_RUN_END); process.removeListener('uncaughtException', uncaught); - process.on('uncaughtException', function(err) { - if (err instanceof Pending) { - return; - } - throw err; - }); + process.on('uncaughtException', self.uncaughtEnd); fn(self.failures); }); // uncaught exception + process.removeListener('uncaughtException', self.uncaughtEnd); process.on('uncaughtException', uncaught); if (this._delay) { diff --git a/test/integration/fixtures/uncaught/listeners.fixture.js b/test/integration/fixtures/uncaught/listeners.fixture.js new file mode 100644 index 0000000000..3ad398cfe0 --- /dev/null +++ b/test/integration/fixtures/uncaught/listeners.fixture.js @@ -0,0 +1,12 @@ +'use strict'; + +const assert = require('assert'); +const mocha = require("../../../../lib/mocha"); + +for (let i = 0; i < 15; i++) { + const r = new mocha.Runner(new mocha.Suite("" + i, undefined)); + r.run(); +} + +assert.equal(process.listenerCount('uncaughtException'), 1); +assert.equal(process.listeners('uncaughtException')[0].name, 'uncaughtEnd'); diff --git a/test/integration/uncaught.spec.js b/test/integration/uncaught.spec.js index 3b7e684925..7d673b1eaa 100644 --- a/test/integration/uncaught.spec.js +++ b/test/integration/uncaught.spec.js @@ -86,4 +86,16 @@ describe('uncaught exceptions', function() { done(); }); }); + + it('removes uncaught exceptions handlers correctly', function(done) { + run('uncaught/listeners.fixture.js', args, function(err, res) { + if (err) { + return done(err); + } + + expect(res, 'to have passed').and('to have passed test count', 0); + + done(); + }); + }); });