From 46ca9ac55b57e15e04488a0437314cc1a6bbb366 Mon Sep 17 00:00:00 2001 From: Juerg B <44573692+juergba@users.noreply.github.com> Date: Fri, 22 Nov 2019 16:40:23 +0100 Subject: [PATCH] uncaughtException: fix when current test is pending (#4083) --- lib/runner.js | 16 ++++++++++--- .../allow-uncaught/propagate.fixture.js | 12 ++++++++++ .../fixtures/uncaught-pending.fixture.js | 15 ++++++++++++ .../integration/options/allowUncaught.spec.js | 21 +++++++++++++++++ test/integration/uncaught.spec.js | 23 +++++++++++++++++++ test/unit/runner.spec.js | 4 ++-- 6 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 test/integration/fixtures/options/allow-uncaught/propagate.fixture.js create mode 100644 test/integration/fixtures/uncaught-pending.fixture.js diff --git a/lib/runner.js b/lib/runner.js index d1c00da30f..374a6143bf 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -794,6 +794,10 @@ Runner.prototype.uncaught = function(err) { if (err instanceof Pending) { return; } + if (this.allowUncaught) { + throw err; + } + if (err) { debug('uncaught exception %O', err); } else { @@ -829,11 +833,17 @@ Runner.prototype.uncaught = function(err) { runnable.clearTimeout(); - // Ignore errors if already failed or pending - // See #3226 - if (runnable.isFailed() || runnable.isPending()) { + if (runnable.isFailed()) { + // Ignore error if already failed + return; + } else if (runnable.isPending()) { + // report 'pending test' retrospectively as failed + runnable.isPending = alwaysFalse; + this.fail(runnable, err); + delete runnable.isPending; return; } + // we cannot recover gracefully if a Runnable has already passed // then fails asynchronously var alreadyPassed = runnable.isPassed(); diff --git a/test/integration/fixtures/options/allow-uncaught/propagate.fixture.js b/test/integration/fixtures/options/allow-uncaught/propagate.fixture.js new file mode 100644 index 0000000000..86d926ed21 --- /dev/null +++ b/test/integration/fixtures/options/allow-uncaught/propagate.fixture.js @@ -0,0 +1,12 @@ +'use strict'; + +describe('Uncaught exception - throw and exit', () => { + it('test1', () => { + setTimeout(() => { + throw new Error('Uncaught error after test1'); + }, 1); + }); + it('test2', function () { }); + it('test3', function () { }); + it('test4', function () { }); +}); diff --git a/test/integration/fixtures/uncaught-pending.fixture.js b/test/integration/fixtures/uncaught-pending.fixture.js new file mode 100644 index 0000000000..593e0da251 --- /dev/null +++ b/test/integration/fixtures/uncaught-pending.fixture.js @@ -0,0 +1,15 @@ +'use strict'; + +describe('Uncaught exception within pending test', () => { + it('test1', function () { }); + + it('test2', function () { + process.nextTick(function () { + throw new Error('I am uncaught!'); + }); + this.skip(); + }); + + it('test3 - should run', function () { }); + it('test4 - should run', function () { }); +}); diff --git a/test/integration/options/allowUncaught.spec.js b/test/integration/options/allowUncaught.spec.js index a3d8739ffe..181aba1446 100644 --- a/test/integration/options/allowUncaught.spec.js +++ b/test/integration/options/allowUncaught.spec.js @@ -2,11 +2,32 @@ var path = require('path').posix; var helpers = require('../helpers'); +var runMocha = helpers.runMocha; var runMochaJSON = helpers.runMochaJSON; describe('--allow-uncaught', function() { var args = ['--allow-uncaught']; + it('should throw an uncaught error and exit process', function(done) { + var fixture = path.join('options', 'allow-uncaught', 'propagate'); + runMocha( + fixture, + args, + function(err, res) { + if (err) { + return done(err); + } + + expect(res.code, 'to be greater than', 0); + expect(res.output, 'to contain', 'Error: Uncaught error after test1'); + expect(res.passing, 'to be', 0); + expect(res.failing, 'to be', 0); + done(); + }, + {stdio: 'pipe'} + ); + }); + it('should run with conditional `this.skip()`', function(done) { var fixture = path.join('options', 'allow-uncaught', 'this-skip-it'); runMochaJSON(fixture, args, function(err, res) { diff --git a/test/integration/uncaught.spec.js b/test/integration/uncaught.spec.js index a65c4b5097..3b7e684925 100644 --- a/test/integration/uncaught.spec.js +++ b/test/integration/uncaught.spec.js @@ -63,4 +63,27 @@ describe('uncaught exceptions', function() { done(); }); }); + + it('handles uncaught exceptions within pending tests', function(done) { + run('uncaught-pending.fixture.js', args, function(err, res) { + if (err) { + return done(err); + } + + expect(res, 'to have failed') + .and('to have passed test count', 3) + .and('to have pending test count', 1) + .and('to have failed test count', 1) + .and( + 'to have passed test', + 'test1', + 'test3 - should run', + 'test4 - should run' + ) + .and('to have pending test order', 'test2') + .and('to have failed test', 'test2'); + + done(); + }); + }); }); diff --git a/test/unit/runner.spec.js b/test/unit/runner.spec.js index 67043a98bb..b3b3a903a5 100644 --- a/test/unit/runner.spec.js +++ b/test/unit/runner.spec.js @@ -811,9 +811,9 @@ describe('Runner', function() { sandbox.stub(runnable, 'isPending').returns(true); }); - it('should not attempt to fail', function() { + it('should attempt to fail', function() { runner.uncaught(err); - expect(runner.fail, 'was not called'); + expect(runner.fail, 'was called once'); }); });