Uncaught exceptions is really dangerous thing, that may break your mochajs
tests queue.
Let's consider code example with async calls and uncaught exceptions.
/* sample.js */
var sleep = timeout => {
return new Promise(resolve => {
setTimeout(() => {
console.log(`I was sleeping ${timeout} ms`);
resolve();
}, timeout);
});
};
var error = timeout => {
setTimeout(() => {
throw new Error("BOOM!!!");
}, timeout);
};
describe("scope", () => {
it ("test #1", async () => {
error(1000);
await sleep(1000);
});
it ("test #2", async () => await sleep(1000));
it ("test #3", async () => {
error(1000);
await sleep(1000);
});
it ("test #4", async () => await sleep(1000));
it ("test #5", async () => await sleep(1000));
it ("test #6", async () => await sleep(1000));
});
Execute it and get something really strange in report:
$ mocha sample.js
scope
1) test #1 # in console it colored as red (failed)
I was sleeping 1000 ms
√ test #1 (1012ms)
2) test #3 # in console it colored as red (failed)
I was sleeping 1000 ms
I was sleeping 1000 ms
√ test #4
√ test #4
I was sleeping 1000 ms
I was sleeping 1000 ms
I was sleeping 1000 ms
√ test #6 (1002ms)
√ test #6 (1002ms)
√ test #6 (1003ms)
6 passing (3s)
2 failing
1) scope
test #1:
Uncaught Error: BOOM!!!
at Timeout.setTimeout [as _onTimeout] (proba.js:12:15)
2) scope
test #3:
Uncaught Error: BOOM!!!
at Timeout.setTimeout [as _onTimeout] (proba.js:12:15)
6 passing (3s)
2 failing
1) scope
test #1:
Uncaught Error: BOOM!!!
at Timeout.setTimeout [as _onTimeout] (proba.js:12:15)
2) scope
test #3:
Uncaught Error: BOOM!!!
at Timeout.setTimeout [as _onTimeout] (proba.js:12:15)
6 passing (3s)
2 failing
1) scope
test #1:
Uncaught Error: BOOM!!!
at Timeout.setTimeout [as _onTimeout] (proba.js:12:15)
2) scope
test #3:
Uncaught Error: BOOM!!!
at Timeout.setTimeout [as _onTimeout] (proba.js:12:15
- Pay attention, that
test #1
is mentioned twice: as passed and as failed! test #2
&test #5
are absent in report!- You may see concurrent printing of messages
I was sleeping 1000 ms
, 1 time, 2 times, 3 times.
Let's see why it happens.
The problem, that by default mochajs
processes uncaught exceptions
: https://github.com/mochajs/mocha/blob/master/lib/runner.js#L698. And if such exception happens, mochajs
fails currently executed test. And it doesn't matter whether such exception happens in current test or was born many tests ago (due to unstopped timer, for example). This processing is implemented via listener, and in above example very interesting things happen:
- In
test #1
on one handuncaught exception
happens after 1 second and its processing starts. On the other hand,test #1
waits forsleep
finishing 1 second and thenmochajs
marks it as passed. - Due to async of
JavaScript
, there are two concurrentlytest #1
processors, that leads totest #1
reporting twice. - More over, since there are two places, which emit events to start new test execution. And factically, there are two tests queues.
- In
test #3
previous situation repeats. And there are 3 concurrently executed tests queues! The evidence is a number of printed messagesI was sleeping 1000 ms
.
How to fix
Simple and working variant is to suppress uncaught exception
.
var Mocha = require("mocha");
Mocha.Runner.prototype.uncaught = function (err) {
logger.error("UNCAUGHT ERROR", err);
};
It's better to get one failed test and in test finalizers to close all descriptors, to stop proxies, to kill processes and so on, than to get fully failed tests queue in nightly build.
That's why mechanism to suppress uncaught exceptions
is enabled by default in GlaceJS
. But it may be disabled with option --allow-uncaught
.