From f833c2b77a2836ada222553c8f7a046f4c9f5f79 Mon Sep 17 00:00:00 2001 From: James Lal Date: Wed, 11 Sep 2013 21:50:31 +0200 Subject: [PATCH 1/4] issue #973 - mark suite with failing hook(s) as pending --- lib/runnable.js | 14 ++++++++++++++ lib/runner.js | 20 ++++++++++++++------ lib/suite.js | 20 ++++++++++++++++++++ test/acceptance/pending.js | 2 +- test/runnable.js | 31 +++++++++++++++++++++++++++++++ test/runner.js | 6 ------ test/suite.js | 29 +++++++++++++++++++++++++++++ 7 files changed, 109 insertions(+), 13 deletions(-) diff --git a/lib/runnable.js b/lib/runnable.js index b34bf1e10b..eea73407cf 100644 --- a/lib/runnable.js +++ b/lib/runnable.js @@ -141,6 +141,20 @@ Runnable.prototype.resetTimeout = function(){ }, ms); }; +/** + * Mark the runnable pending. + * + * @api private + */ +Runnable.prototype.markPending = function(){ + // don't alter the tests state after the fact + if (this.state) return; + + this.pending = true; + this.async = false; + this.sync = true; +} + /** * Run the test and invoke `fn(err)`. * diff --git a/lib/runner.js b/lib/runner.js index dffc3c0591..779e531a92 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -215,7 +215,7 @@ Runner.prototype.fail = function(test, err){ Runner.prototype.failHook = function(hook, err){ this.fail(hook, err); - this.emit('end'); + hook.parent && hook.parent.markPending(); }; /** @@ -250,7 +250,7 @@ Runner.prototype.hook = function(name, fn){ hook.removeAllListeners('error'); var testError = hook.error(); if (testError) self.fail(self.test, testError); - if (err) return self.failHook(hook, err); + if (err) self.failHook(hook, err); self.emit('hook end', hook); delete hook.ctx.currentTest; next(++i); @@ -391,15 +391,23 @@ Runner.prototype.runTests = function(suite, fn){ if (!match) return next(); // pending - if (test.pending) { - self.emit('pending', test); - self.emit('test end', test); - return next(); + function checkPending() { + if (test && test.pending) { + self.emit('pending', test); + self.emit('test end', test); + next(); + return true; + } } + // go to the next test + if (checkPending()) return; + // execute test and hook(s) self.emit('test', self.test = test); self.hookDown('beforeEach', function(){ + if (checkPending()) return; + self.currentRunnable = self.test; self.runTest(function(err){ test = self.test; diff --git a/lib/suite.js b/lib/suite.js index 869bb88d52..fc404c4af2 100644 --- a/lib/suite.js +++ b/lib/suite.js @@ -277,6 +277,26 @@ Suite.prototype.total = function(){ }, 0) + this.tests.length; }; +/** + * Marks suite (and all children suites/tests) pending. + * + * @api private + * @return {Suite} + */ +Suite.prototype.markPending = function() { + this.pending = true; + + // mark all suites pending + utils.forEach(this.suites, function(suite) { + suite.markPending(); + }); + + // mark all tests pending + this.eachTest(function(test) { + test.markPending(); + }); +}; + /** * Iterates through each suite recursively to find * all tests. Applies a function in the format diff --git a/test/acceptance/pending.js b/test/acceptance/pending.js index cf738b27ba..6f6305c1f1 100644 --- a/test/acceptance/pending.js +++ b/test/acceptance/pending.js @@ -1,4 +1,4 @@ describe('pending', function(){ it('should be allowed') -}) \ No newline at end of file +}) diff --git a/test/runnable.js b/test/runnable.js index d21e80df52..d3d2d6efa9 100644 --- a/test/runnable.js +++ b/test/runnable.js @@ -71,6 +71,37 @@ describe('Runnable(title, fn)', function(){ }) }) + describe('.markPending', function() { + describe('when passing', function() { + it('should still be passing', function() { + var run = new Runnable('foo', function() {}); + // should needs a not-undefined value + run.pending = ''; + run.state = 'passed'; + run.markPending(); + run.pending.should.not.be.true; + }) + }); + + describe('when sync', function() { + it('should be sync and pending', function() { + var run = new Runnable('foo', function() {}); + run.markPending(); + run.sync.should.be.true; + run.pending.should.be.true; + }) + }) + + describe('when async', function() { + it('should be sync and pending', function() { + var run = new Runnable('foo', function(done) {}); + run.markPending(); + run.sync.should.be.true; + run.pending.should.be.true; + }); + }); + }); + describe('.run(fn)', function(){ describe('when .pending', function(){ it('should not invoke the callback', function(done){ diff --git a/test/runner.js b/test/runner.js index 54d574c9de..69e773e31a 100644 --- a/test/runner.js +++ b/test/runner.js @@ -178,11 +178,5 @@ describe('Runner', function(){ }); runner.failHook(hook, err); }) - - it('should emit "end"', function(done){ - var hook = {}, err = {}; - runner.on('end', done); - runner.failHook(hook, err); - }) }) }) diff --git a/test/suite.js b/test/suite.js index 8cacff8de9..0678b71e38 100644 --- a/test/suite.js +++ b/test/suite.js @@ -245,6 +245,35 @@ describe('Suite', function(){ // this.suite.tests[0].should.equal(this.test); // }); // }); + describe('.markPending', function() { + beforeEach(function() { + this.suite = new Suite('Parent'); + this.suite.addTest(new Test('test 1', function() {})); + + // add a child suite + var child = new Suite('Child'); + this.suite.addSuite(child); + + // test in child + child.addTest(new Test('test 2', function() {})); + + this.suite.markPending(); + }); + + it('should mark suite pending', function() { + this.suite.pending.should.be.true; + }); + + it('should mark tests pending', function() { + this.suite.eachTest(function(test) { + test.pending.should.be.true; + }); + }); + + it('should mark child suite pending', function() { + this.suite.suites[0].pending.should.be.true; + }); + }); describe('.fullTitle()', function(){ beforeEach(function(){ From 2e25f38248cbdbaba679804e11e43aab2403aca0 Mon Sep 17 00:00:00 2001 From: James Lal Date: Fri, 13 Sep 2013 14:50:30 +0200 Subject: [PATCH 2/4] integration tests for hook failures --- Makefile | 5 +- test/integration/hook_fail_before_each.js | 17 +++++ test/integration/runner.js | 26 +++++++ .../tests/hook_fail_before_each.json | 67 +++++++++++++++++++ .../tests/hook_fail_before_each/one.js | 15 +++++ .../tests/hook_fail_before_each/three.js | 6 ++ .../tests/hook_fail_before_each/two.js | 4 ++ 7 files changed, 139 insertions(+), 1 deletion(-) create mode 100755 test/integration/hook_fail_before_each.js create mode 100644 test/integration/runner.js create mode 100644 test/integration/tests/hook_fail_before_each.json create mode 100644 test/integration/tests/hook_fail_before_each/one.js create mode 100644 test/integration/tests/hook_fail_before_each/three.js create mode 100644 test/integration/tests/hook_fail_before_each/two.js diff --git a/Makefile b/Makefile index 6c555012f3..66e4cfe25d 100644 --- a/Makefile +++ b/Makefile @@ -32,11 +32,14 @@ lib-cov: test: test-unit -test-all: test-bdd test-tdd test-qunit test-exports test-unit test-grep test-jsapi test-compilers test-sort test-glob test-requires test-reporters test-only +test-all: test-integration test-bdd test-tdd test-qunit test-exports test-unit test-grep test-jsapi test-compilers test-sort test-glob test-requires test-reporters test-only test-jsapi: @node test/jsapi +test-integration: + test/integration/hook_fail_before_each.js + test-unit: @./bin/mocha \ --reporter $(REPORTER) \ diff --git a/test/integration/hook_fail_before_each.js b/test/integration/hook_fail_before_each.js new file mode 100755 index 0000000000..af92c72109 --- /dev/null +++ b/test/integration/hook_fail_before_each.js @@ -0,0 +1,17 @@ +#! /usr/bin/env node + +var run = require('./runner'); +var expected = require('./tests/hook_fail_before_each.json'); +var assert = require('assert'); + +run([ + 'hook_fail_before_each/one.js', + 'hook_fail_before_each/two.js', + 'hook_fail_before_each/three.js' +], function(err, report) { + if (!err) { + console.error('mocha should fail'); + process.exit(1); + } + assert.deepEqual(report.tests, expected.tests); +}); diff --git a/test/integration/runner.js b/test/integration/runner.js new file mode 100644 index 0000000000..cc34c0f3df --- /dev/null +++ b/test/integration/runner.js @@ -0,0 +1,26 @@ +var fsPath = require('path'), + exec = require('child_process').exec, + debug = require('debug')('integration test'); + +var TESTS = __dirname + '/tests/'; +var BIN = __dirname + '/../../bin/mocha'; + +function run(tests, callback) { + var paths = []; + + var tests = tests.map(function(test) { + return fsPath.join(TESTS, test); + }); + + var cmd = [BIN, '--reporter', 'json'].concat(tests); + debug('run', cmd); + + exec( + cmd.join(' '), + function(err, stdout, stderr) { + callback(err, JSON.parse(stdout)); + } + ); +} + +module.exports = run; diff --git a/test/integration/tests/hook_fail_before_each.json b/test/integration/tests/hook_fail_before_each.json new file mode 100644 index 0000000000..f94cbf328b --- /dev/null +++ b/test/integration/tests/hook_fail_before_each.json @@ -0,0 +1,67 @@ +{ + "stats": { + "suites": 3, + "tests": 5, + "passes": 4, + "pending": 1, + "failures": 1, + "start": "2013-09-13T12:41:15.471Z", + "end": "2013-09-13T12:41:15.474Z", + "duration": 3 + }, + "tests": [ + { + "title": "should work", + "fullTitle": "one should work", + "duration": 0 + }, + { + "title": "should fail", + "fullTitle": "one should fail" + }, + { + "title": "should work", + "fullTitle": "two should work", + "duration": 0 + }, + { + "title": "should work 1", + "fullTitle": "three should work 1", + "duration": 0 + }, + { + "title": "should work 2", + "fullTitle": "three should work 2", + "duration": 0 + } + ], + "failures": [ + { + "title": "\"before each\" hook", + "fullTitle": "one \"before each\" hook", + "duration": 0 + } + ], + "passes": [ + { + "title": "should work", + "fullTitle": "one should work", + "duration": 0 + }, + { + "title": "should work", + "fullTitle": "two should work", + "duration": 0 + }, + { + "title": "should work 1", + "fullTitle": "three should work 1", + "duration": 0 + }, + { + "title": "should work 2", + "fullTitle": "three should work 2", + "duration": 0 + } + ] +} diff --git a/test/integration/tests/hook_fail_before_each/one.js b/test/integration/tests/hook_fail_before_each/one.js new file mode 100644 index 0000000000..a2b49ce45f --- /dev/null +++ b/test/integration/tests/hook_fail_before_each/one.js @@ -0,0 +1,15 @@ +describe('one', function() { + var increment = 0; + + beforeEach(function() { + if (++increment === 2) { + throw new Error('ouch'); + } + }); + + it('should work', function() { + }); + + it('should fail', function() { + }); +}); diff --git a/test/integration/tests/hook_fail_before_each/three.js b/test/integration/tests/hook_fail_before_each/three.js new file mode 100644 index 0000000000..8e292d3c1c --- /dev/null +++ b/test/integration/tests/hook_fail_before_each/three.js @@ -0,0 +1,6 @@ +describe('three', function() { + it('should work 1', function() { + }); + it('should work 2', function() { + }); +}); diff --git a/test/integration/tests/hook_fail_before_each/two.js b/test/integration/tests/hook_fail_before_each/two.js new file mode 100644 index 0000000000..cefc3a5a3a --- /dev/null +++ b/test/integration/tests/hook_fail_before_each/two.js @@ -0,0 +1,4 @@ +describe('two', function() { + it('should work', function() { + }); +}); From d165dcd2f12204144347b923407c52559b455a34 Mon Sep 17 00:00:00 2001 From: James Lal Date: Sun, 20 Oct 2013 16:10:26 -0700 Subject: [PATCH 3/4] markPending should be chaninable --- lib/suite.js | 2 ++ test/suite.js | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/suite.js b/lib/suite.js index fc404c4af2..fa00341367 100644 --- a/lib/suite.js +++ b/lib/suite.js @@ -295,6 +295,8 @@ Suite.prototype.markPending = function() { this.eachTest(function(test) { test.markPending(); }); + + return this; }; /** diff --git a/test/suite.js b/test/suite.js index 0678b71e38..5d9408b48b 100644 --- a/test/suite.js +++ b/test/suite.js @@ -246,6 +246,7 @@ describe('Suite', function(){ // }); // }); describe('.markPending', function() { + var result; beforeEach(function() { this.suite = new Suite('Parent'); this.suite.addTest(new Test('test 1', function() {})); @@ -257,7 +258,11 @@ describe('Suite', function(){ // test in child child.addTest(new Test('test 2', function() {})); - this.suite.markPending(); + result = this.suite.markPending(); + }); + + it('should be chainable', function() { + result.should.equal(this.suite); }); it('should mark suite pending', function() { From 35bb98341afdf94c0453eeaa7042322912c6435d Mon Sep 17 00:00:00 2001 From: James Lal Date: Sun, 20 Oct 2013 23:41:12 -0700 Subject: [PATCH 4/4] add test-integration to PHONY --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 66e4cfe25d..927f00f0a1 100644 --- a/Makefile +++ b/Makefile @@ -168,4 +168,4 @@ non-tty: tm: @open editors/$(TM_BUNDLE) -.PHONY: test-cov test-jsapi test-compilers watch test test-all test-bdd test-tdd test-qunit test-exports test-unit non-tty test-grep tm clean +.PHONY: test-cov test-jsapi test-compilers watch test test-all test-bdd test-tdd test-qunit test-exports test-unit non-tty test-grep tm clean test-integration