diff --git a/lib/Suite.js b/lib/Suite.js index df7af7881..fda86db4d 100644 --- a/lib/Suite.js +++ b/lib/Suite.js @@ -11,6 +11,10 @@ define([ this.reporterManager && this.reporterManager.emit('newSuite', this); } + // BAIL_REASON needs to be a string so that Intern can tell when a remote has bailed during unit tests so that it + // can skip functional tests. + var BAIL_REASON = 'bailed'; + Suite.prototype = { constructor: Suite, name: null, @@ -22,6 +26,7 @@ define([ teardown: null, error: null, timeElapsed: null, + _bail: null, _grep: null, _remote: null, _environmentType: null, @@ -33,6 +38,17 @@ define([ */ publishAfterSetup: false, + /** + * A flag used to indicate whether a test run shoudl stop after a failed test. + */ + get bail() { + return this._bail || (this.parent && this.parent.bail); + }, + + set bail(value) { + this._bail = value; + }, + /** * A regular expression used to filter, by test ID, which tests are run. */ @@ -291,8 +307,8 @@ define([ throw reason; }); - function next() { - var test = tests[i++]; + function next(test) { + test = tests[i++]; if (!test) { firstError ? reject(firstError) : resolve(); @@ -350,7 +366,23 @@ define([ }); } - current.then(next); + current.then(function () { + function skipRestOfSuite() { + self.skipped = self.skipped != null ? self.skipped : BAIL_REASON; + } + + // If the test was a suite and the suite was skipped due to bailing, skip the rest of this + // suite + if (test.tests && test.skipped === BAIL_REASON) { + skipRestOfSuite(); + } + // If the test errored and bail mode is enabled, skip the rest of this suite + else if (test.error && self.bail) { + skipRestOfSuite(); + } + + next(); + }); } next(); diff --git a/lib/executors/Client.js b/lib/executors/Client.js index a2665ecbf..06b431b30 100644 --- a/lib/executors/Client.js +++ b/lib/executors/Client.js @@ -50,7 +50,8 @@ define([ grep: config.grep, sessionId: config.sessionId, timeout: config.defaultTimeout, - reporterManager: this.reporterManager + reporterManager: this.reporterManager, + bail: config.bail }); this.suites = [ suite ]; diff --git a/lib/executors/Runner.js b/lib/executors/Runner.js index 29137c30e..3e97f0733 100644 --- a/lib/executors/Runner.js +++ b/lib/executors/Runner.js @@ -184,6 +184,7 @@ define([ reporterManager: reporterManager, publishAfterSetup: true, grep: config.grep, + bail: config.bail, timeout: config.defaultTimeout, setup: function () { return util.retry(function () { diff --git a/tests/unit/lib/Suite.js b/tests/unit/lib/Suite.js index 167e12423..607cd31a4 100644 --- a/tests/unit/lib/Suite.js +++ b/tests/unit/lib/Suite.js @@ -644,6 +644,65 @@ define([ })); }, + 'Suite#run bail': function () { + var dfd = this.async(5000); + var suite = createSuite({ + bail: true + }); + var testsRun = []; + var fooTest = new Test({ + name: 'foo', + parent: suite, + test: function () { + testsRun.push(this); + } + }); + var barSuite = createSuite({ + name: 'bar', + parent: suite, + tests: [ + new Test({ + name: 'foo', + test: function () { + testsRun.push(this); + // Fail this test; everything after this should not run + throw new Error('fail'); + } + }), + new Test({ + name: 'baz', + test: function () { + testsRun.push(this); + } + }) + ] + }); + var foodTest = new Test({ + name: 'food', + parent: suite, + test: function () { + testsRun.push(this); + } + }); + + var teardownRan = false; + barSuite.teardown = function () { + teardownRan = true; + }; + + suite.tests.push(fooTest); + suite.tests.push(barSuite); + suite.tests.push(foodTest); + + suite.run().then(dfd.callback(function () { + assert.deepEqual(testsRun, [ fooTest, barSuite.tests[0] ], + 'Only tests before failing test should have run'); + assert.isTrue(teardownRan, 'teardown should have run for bailing suite'); + }, function () { + dfd.reject(new assert.AssertionError('Suite should not fail')); + })); + }, + 'Suite#run skip': function () { var dfd = this.async(5000); var suite = createSuite();