Skip to content

Commit c0f1d14

Browse files
authored
uncaughtException: fix recovery when current test is still running (#4150)
1 parent 9c10ada commit c0f1d14

File tree

6 files changed

+122
-136
lines changed

6 files changed

+122
-136
lines changed

lib/runnable.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ Runnable.prototype.run = function(fn) {
335335
fn(err);
336336
}
337337

338-
// for .resetTimeout()
338+
// for .resetTimeout() and Runner#uncaught()
339339
this.callback = done;
340340

341341
if (this.fn && typeof this.fn.call !== 'function') {

lib/runner.js

+11-43
Original file line numberDiff line numberDiff line change
@@ -724,7 +724,6 @@ Runner.prototype.runSuite = function(suite, fn) {
724724
var i = 0;
725725
var self = this;
726726
var total = this.grepTotal(suite);
727-
var afterAllHookCalled = false;
728727

729728
debug('run suite %s', suite.fullTitle());
730729

@@ -772,21 +771,13 @@ Runner.prototype.runSuite = function(suite, fn) {
772771
self.suite = suite;
773772
self.nextSuite = next;
774773

775-
if (afterAllHookCalled) {
776-
fn(errSuite);
777-
} else {
778-
// mark that the afterAll block has been called once
779-
// and so can be skipped if there is an error in it.
780-
afterAllHookCalled = true;
781-
782-
// remove reference to test
783-
delete self.test;
774+
// remove reference to test
775+
delete self.test;
784776

785-
self.hook(HOOK_TYPE_AFTER_ALL, function() {
786-
self.emit(constants.EVENT_SUITE_END, suite);
787-
fn(errSuite);
788-
});
789-
}
777+
self.hook(HOOK_TYPE_AFTER_ALL, function() {
778+
self.emit(constants.EVENT_SUITE_END, suite);
779+
fn(errSuite);
780+
});
790781
}
791782

792783
this.nextSuite = next;
@@ -861,36 +852,13 @@ Runner.prototype.uncaught = function(err) {
861852

862853
// we cannot recover gracefully if a Runnable has already passed
863854
// then fails asynchronously
864-
var alreadyPassed = runnable.isPassed();
865-
// this will change the state to "failed" regardless of the current value
866-
this.fail(runnable, err);
867-
if (!alreadyPassed) {
868-
// recover from test
869-
if (runnable.type === constants.EVENT_TEST_BEGIN) {
870-
this.emit(constants.EVENT_TEST_END, runnable);
871-
this.hookUp(HOOK_TYPE_AFTER_EACH, this.next);
872-
return;
873-
}
855+
if (runnable.isPassed()) {
856+
this.fail(runnable, err);
857+
this.abort();
858+
} else {
874859
debug(runnable);
875-
876-
// recover from hooks
877-
var errSuite = this.suite;
878-
879-
// XXX how about a less awful way to determine this?
880-
// if hook failure is in afterEach block
881-
if (runnable.fullTitle().indexOf('after each') > -1) {
882-
return this.hookErr(err, errSuite, true);
883-
}
884-
// if hook failure is in beforeEach block
885-
if (runnable.fullTitle().indexOf('before each') > -1) {
886-
return this.hookErr(err, errSuite, false);
887-
}
888-
// if hook failure is in after or before blocks
889-
return this.nextSuite(errSuite);
860+
return runnable.callback(err);
890861
}
891-
892-
// bail
893-
this.abort();
894862
};
895863

896864
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
'use strict';
2+
const assert = require('assert');
3+
4+
describe('uncaught', function() {
5+
var hookOrder = [];
6+
it('throw delayed error', (done) => {
7+
setTimeout(() => {
8+
throw new Error('Whoops!');
9+
}, 10)
10+
setTimeout(done, 10);
11+
});
12+
it('should wait 15ms', (done) => {
13+
setTimeout(done, 15);
14+
});
15+
it('test 3', () => { });
16+
17+
afterEach(function() {
18+
hookOrder.push(this.currentTest.title);
19+
});
20+
after(function() {
21+
hookOrder.push('after');
22+
assert.deepEqual(
23+
hookOrder,
24+
['throw delayed error', 'should wait 15ms', 'test 3', 'after']
25+
);
26+
throw new Error('should get upto here and throw');
27+
});
28+
});

test/integration/hook-err.spec.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,10 @@ describe('hook error handling', function() {
194194
run('hooks/before-hook-async-error-tip.fixture.js', onlyErrorTitle())
195195
);
196196
it('should verify results', function() {
197-
expect(lines, 'to equal', ['1) spec 2', '"before all" hook:']);
197+
expect(lines, 'to equal', [
198+
'1) spec 2',
199+
'"before all" hook for "skipped":'
200+
]);
198201
});
199202
});
200203

test/integration/uncaught.spec.js

+28-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ describe('uncaught exceptions', function() {
1717

1818
assert.strictEqual(
1919
res.failures[0].fullTitle,
20-
'uncaught "before each" hook'
20+
'uncaught "before each" hook for "test"'
2121
);
2222
assert.strictEqual(res.code, 1);
2323
done();
@@ -87,6 +87,33 @@ describe('uncaught exceptions', function() {
8787
});
8888
});
8989

90+
it('handles uncaught exceptions within open tests', function(done) {
91+
run('uncaught/recover.fixture.js', args, function(err, res) {
92+
if (err) {
93+
return done(err);
94+
}
95+
96+
expect(
97+
res,
98+
'to have failed with error',
99+
'Whoops!',
100+
'Whoops!', // JSON reporter does not show the second error message
101+
'should get upto here and throw'
102+
)
103+
.and('to have passed test count', 2)
104+
.and('to have failed test count', 3)
105+
.and('to have passed test', 'should wait 15ms', 'test 3')
106+
.and(
107+
'to have failed test',
108+
'throw delayed error',
109+
'throw delayed error',
110+
'"after all" hook for "test 3"'
111+
);
112+
113+
done();
114+
});
115+
});
116+
90117
it('removes uncaught exceptions handlers correctly', function(done) {
91118
run('uncaught/listeners.fixture.js', args, function(err, res) {
92119
if (err) {

0 commit comments

Comments
 (0)