-
-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix #1798: Correctly attribute mutiple done err with hooks #1889
Conversation
danielstjules
commented
Sep 13, 2015
setMaxListeners is to avoid some noise:
I think this is preferable to the original solution: e673963 |
So hooks will end up having as many 'error' listeners as tests are run*? (in case of a 'beforeEach) I don't even get why we set up the 'error' listener once each time before we run the hook. Why not set them up before running the suites and remove them after the suites are done/finished? The callback is always the same, afaik. |
Yep, I just checked, and the "multiple done" error is the only 'error' event Runnable emits. |
Just realized I could instead move |
Because they could still emit an error after the suite finished, during a different suite's execution. Without leaving those listeners active, that'd result in an uncaught error |
Updated code will have the same number of listeners as what's currently in master, except it won't remove the listeners once hook execution has completed (preventing the uncaught error issue) |
Weird, locally, tests are stopping after 'acceptance/timeout.js'. After SIGINT: Works fine on Travis. |
Does that happen on master for you as well? Or only this branch? Edit: Ran off master and this branch, both succeeded with the same running time. So I think it's unrelated to this PR. |
+1? :) |
It does happen in master as well.. weird.
|
|
||
it('results in a failure', function() { | ||
assert.equal(res.stats.pending, 0); | ||
assert.equal(res.stats.passes, 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is passes
1?
➜ mocha git:(danielstjules-issue-1798) ✗ mocha test/integration/fixtures/multiple.done.before.js
․
0 passing (41ms)
1 failing
1) suite test1:
Uncaught Error: done() called multiple times
at Suite.<anonymous> (test/integration/fixtures/multiple.done.before.js:2:3)
at Object.<anonymous> (test/integration/fixtures/multiple.done.before.js:1:63)
at Array.forEach (native)
at node.js:814:3
(same thing for most other tests)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More clear even:
➜ mocha git:(danielstjules-issue-1798) ✗ mocha test/integration/fixtures/multiple.done.before.js --reporter JSON
{
"stats": {
"suites": 1,
"tests": 1,
"passes": 0,
"pending": 0,
"failures": 1,
"start": "2015-09-14T11:33:37.794Z",
"end": "2015-09-14T11:33:37.834Z",
"duration": 40
},
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is passes 1?
You're using the wrong bin. Use the project's local bin, rather than global mocha. Otherwise you're not actually running the branches' changes.
danielstjules:~/git/mocha (issue-1798)
$ ./bin/mocha test/integration/fixtures/multiple.done.before.js --reporter JSON
{
"stats": {
"suites": 1,
"tests": 1,
"passes": 1,
"pending": 0,
"failures": 1,
"start": "2015-09-14T16:28:29.589Z",
"end": "2015-09-14T16:28:29.658Z",
"duration": 69
},
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since those hook errors were previously uncaught, the suite would exit. Now that they can be handled by the error listener, a given suite will continue to run. That's why we go from 0 to 1 passes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. Since (at least in this case) we know that the before hook has failed before the test has finished running, couldn't we ignore the outcome of the test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean, we obviously could ignore it, but what do you think about doing so?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean, we obviously could ignore it, but what do you think about doing so?
I don't think it's ideal. I think that leads to the same confusing output we see in scenarios like:
$ cat test.js
beforeEach(function(done) {
done(new Error('error'));
});
it('test1', function(done) {
setTimeout(done, 20);
});
it('test1', function(done) {
setTimeout(done, 20);
});
$ mocha test.js
1) "before each" hook for "test1"
0 passing (11ms)
1 failing
1) "before each" hook for "test1":
Error: error
at Context.<anonymous> (test.js:2:8)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd also rather keep the scope of this PR small and push for a patch release, rather than make any major changes to runner behavior
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree to keep the scope of the PR small. I'd like to discuss about it in a issue then. :)
Good point. Okay, then setting them up once and not removing them? How does that sound? As a proof of concept, this passes your tests: diff --git a/lib/runner.js b/lib/runner.js
index 1c91712..09cd414 100644
--- a/lib/runner.js
+++ b/lib/runner.js
@@ -285,13 +285,14 @@ Runner.prototype.hook = function(name, fn) {
self.currentRunnable = hook;
hook.ctx.currentTest = self.test;
- hook.removeAllListeners('error');
self.emit('hook', hook);
- hook.on('error', function(err) {
- self.failHook(hook, err);
- });
+ if (EventEmitter.listenerCount(hook, 'error') === 0) {
+ hook.on('error', function(err) {
+ self.failHook(hook, err);
+ });
+ }
hook.run(function(err) {
var testError = hook.error(); |
Sure, that works :) |
Updated the branch with that change |
Used hook.listeners for node 0.8 support https://nodejs.org/docs/v0.8.0/api/events.html#events_emitter_listeners_event |
@dasilvacontin @boneskull anymore feedback? :) Would love to merge this in |
Cool, I hadn't seen that one. Well, that was just for showing it worked. What about setting up the hook listeners in a loop before running the suite? It'd be cleaner imo. Something similar to this, but extracted in a nice documented function. (this also passes tests) @@ -630,6 +624,17 @@ Runner.prototype.runSuite = function(suite, fn) {
this.nextSuite = next;
+ var hookNames = ['beforeAll', 'beforeEach', 'afterEach', 'afterAll']
+ hookNames.forEach(function (hookName) {
+ var hooks = self.suite['_' + hookName]
+ if (!hooks.length) return
+ hooks.forEach(function (hook) {
+ hook.on('error', function (err) {
+ self.failHook(hook, err)
+ })
+ })
+ })
+
this.hook('beforeAll', function(err) {
if (err) {
return done(); |
Sounds like a later PR could address that if it's just refactoring :) I like that this was almost a one line change haha Edit: Would need to get rid of both |
Just refactoring. :) LGTM then, good job! |
Fix #1798: Correctly attribute mutiple done err with hooks