From c43744b166720f48f99dfdedf3513b52f34e6022 Mon Sep 17 00:00:00 2001 From: Fargier Sylvain Date: Thu, 12 Apr 2018 10:51:59 +0200 Subject: [PATCH] Native types exceptions can crash Mocha - Any non-extensible type thrown in an async callback raises an exception in Mocha runner, stopping tests. --- lib/runner.js | 40 ++++++++++++++++++++++++++++------- test/unit/throw.spec.js | 46 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 8 deletions(-) diff --git a/lib/runner.js b/lib/runner.js index 67e6fbabdb..d105bca321 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -231,14 +231,8 @@ Runner.prototype.fail = function(test, err) { ++this.failures; test.state = 'failed'; - if (!(err instanceof Error || (err && typeof err.message === 'string'))) { - err = new Error( - 'the ' + - type(err) + - ' ' + - stringify(err) + - ' was thrown, throw an Error :)' - ); + if (!isError(err)) { + err = thrown2Error(err); } try { @@ -731,6 +725,10 @@ Runner.prototype.uncaught = function(err) { debug('uncaught undefined exception'); err = undefinedError(); } + + if (!isError(err)) { + err = thrown2Error(err); + } err.uncaught = true; var runnable = this.currentRunnable; @@ -993,6 +991,32 @@ function filterLeaks(ok, globals) { }); } +/** + * Check if argument is an instance of Error object or a duck-typed equivalent. + * + * @private + * @param {Object} err - object to check + * @param {string} err.message - error message + * @returns {boolean} + */ +function isError(err) { + return err instanceof Error || (err && typeof err.message === 'string'); +} + +/** + * + * Converts thrown non-extensible type into proper Error. + * + * @private + * @param {*} thrown - Non-extensible type thrown by code + * @return {Error} + */ +function thrown2Error(err) { + return new Error( + 'the ' + type(err) + ' ' + stringify(err) + ' was thrown, throw an Error :)' + ); +} + /** * Array of globals dependent on the environment. * diff --git a/test/unit/throw.spec.js b/test/unit/throw.spec.js index 6aca25750c..66021d3d38 100644 --- a/test/unit/throw.spec.js +++ b/test/unit/throw.spec.js @@ -26,6 +26,52 @@ describe('a test that throws', function() { }); }); + describe('non-extensible', function() { + it('should not pass if throwing sync and test is sync', function(done) { + var test = new Test('im sync and throw string sync', function() { + throw 'non-extensible'; + }); + suite.addTest(test); + runner = new Runner(suite); + runner.on('end', function() { + expect(runner.failures, 'to be', 1); + expect(test.state, 'to be', 'failed'); + done(); + }); + runner.run(); + }); + + it('should not pass if throwing sync and test is async', function(done) { + var test = new Test('im async and throw string sync', function(done2) { + throw 'non-extensible'; + }); + suite.addTest(test); + runner = new Runner(suite); + runner.on('end', function() { + expect(runner.failures, 'to be', 1); + expect(test.state, 'to be', 'failed'); + done(); + }); + runner.run(); + }); + + it('should not pass if throwing async and test is async', function(done) { + var test = new Test('im async and throw string async', function(done2) { + process.nextTick(function() { + throw 'non-extensible'; + }); + }); + suite.addTest(test); + runner = new Runner(suite); + runner.on('end', function() { + expect(runner.failures, 'to be', 1); + expect(test.state, 'to be', 'failed'); + done(); + }); + runner.run(); + }); + }); + describe('undefined', function() { it('should not pass if throwing sync and test is sync', function(done) { var test = new Test('im sync and throw undefined sync', function() {