Skip to content

Commit deb8679

Browse files
feat: add option to not fail on failing test suite (#4771)
* Add option fail-on-failing-test-suite to not fail test run if there were no infrastructure problem * Add test on failOnFailingTestSuite * Use parameters which by default is false * Update docs/index.md * refactor: shared clampedCode --------- Co-authored-by: Josh Goldberg <[email protected]>
1 parent 9a1c458 commit deb8679

File tree

8 files changed

+126
-9
lines changed

8 files changed

+126
-9
lines changed

docs/index.md

+4
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,10 @@ To ensure your tests aren't leaving messes around, here are some ideas to get st
915915
- Try something like [wtfnode][npm-wtfnode]
916916
- Use [`.only`](#exclusive-tests) until you find the test that causes Mocha to hang
917917

918+
### `--pass-on-failing-test-suite`
919+
920+
If set to `true`, Mocha returns exit code `0` even if there are failed tests during run.
921+
918922
### `--fail-zero`
919923

920924
> _New in v9.1.0_ Fail test run if no tests are encountered with `exit-code: 1`.

lib/cli/run-helpers.js

+29-9
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,24 @@ const {UnmatchedFile} = require('./collect-files');
2121

2222
/**
2323
* Exits Mocha when tests + code under test has finished execution (default)
24-
* @param {number} code - Exit code; typically # of failures
24+
* @param {number} clampedCode - Exit code; typically # of failures
2525
* @ignore
2626
* @private
2727
*/
28-
const exitMochaLater = code => {
28+
const exitMochaLater = clampedCode => {
2929
process.on('exit', () => {
30-
process.exitCode = Math.min(code, 255);
30+
process.exitCode = clampedCode;
3131
});
3232
};
3333

3434
/**
3535
* Exits Mocha when Mocha itself has finished execution, regardless of
3636
* what the tests or code under test is doing.
37-
* @param {number} code - Exit code; typically # of failures
37+
* @param {number} clampedCode - Exit code; typically # of failures
3838
* @ignore
3939
* @private
4040
*/
41-
const exitMocha = code => {
42-
const clampedCode = Math.min(code, 255);
41+
const exitMocha = clampedCode => {
4342
let draining = 0;
4443

4544
// Eagerly set the process's exit code in case stream.write doesn't
@@ -139,12 +138,17 @@ const handleUnmatchedFiles = (mocha, unmatchedFiles) => {
139138
* @param {Mocha} mocha - Mocha instance
140139
* @param {Options} [opts] - Command line options
141140
* @param {boolean} [opts.exit] - Whether or not to force-exit after tests are complete
141+
* @param {boolean} [opts.passOnFailingTestSuite] - Whether or not to fail test run if tests were failed
142142
* @param {Object} fileCollectParams - Parameters that control test
143143
* file collection. See `lib/cli/collect-files.js`.
144144
* @returns {Promise<Runner>}
145145
* @private
146146
*/
147-
const singleRun = async (mocha, {exit}, fileCollectParams) => {
147+
const singleRun = async (
148+
mocha,
149+
{exit, passOnFailingTestSuite},
150+
fileCollectParams
151+
) => {
148152
const fileCollectionObj = collectFiles(fileCollectParams);
149153

150154
if (fileCollectionObj.unmatchedFiles.length > 0) {
@@ -156,7 +160,9 @@ const singleRun = async (mocha, {exit}, fileCollectParams) => {
156160

157161
// handles ESM modules
158162
await mocha.loadFilesAsync();
159-
return mocha.run(exit ? exitMocha : exitMochaLater);
163+
return mocha.run(
164+
createExitHandler({exit, passOnFailingTestSuite})
165+
);
160166
};
161167

162168
/**
@@ -186,7 +192,9 @@ const parallelRun = async (mocha, options, fileCollectParams) => {
186192
mocha.files = fileCollectionObj.files;
187193

188194
// note that we DO NOT load any files here; this is handled by the worker
189-
return mocha.run(options.exit ? exitMocha : exitMochaLater);
195+
return mocha.run(
196+
createExitHandler(options)
197+
);
190198
};
191199

192200
/**
@@ -282,3 +290,15 @@ exports.validateLegacyPlugin = (opts, pluginType, map = {}) => {
282290
}
283291
}
284292
};
293+
294+
const createExitHandler = ({ exit, passOnFailingTestSuite }) => {
295+
return code => {
296+
const clampedCode = passOnFailingTestSuite
297+
? 0
298+
: Math.min(code, 255);
299+
300+
return exit
301+
? exitMocha(clampedCode)
302+
: exitMochaLater(clampedCode);
303+
};
304+
};

lib/cli/run-option-metadata.js

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const TYPES = (exports.types = {
3535
'diff',
3636
'dry-run',
3737
'exit',
38+
'pass-on-failing-test-suite',
3839
'fail-zero',
3940
'forbid-only',
4041
'forbid-pending',

lib/cli/run.js

+5
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ exports.builder = yargs =>
9898
requiresArg: true,
9999
coerce: list
100100
},
101+
'pass-on-failing-test-suite': {
102+
default: false,
103+
description: 'Not fail test run if tests were failed',
104+
group: GROUPS.RULES
105+
},
101106
'fail-zero': {
102107
description: 'Fail test run if no test(s) encountered',
103108
group: GROUPS.RULES

lib/mocha.js

+16
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ exports.run = function (...args) {
157157
* @param {boolean} [options.delay] - Delay root suite execution?
158158
* @param {boolean} [options.diff] - Show diff on failure?
159159
* @param {boolean} [options.dryRun] - Report tests without running them?
160+
* @param {boolean} [options.passOnFailingTestSuite] - Fail test run if tests were failed?
160161
* @param {boolean} [options.failZero] - Fail test run if zero tests?
161162
* @param {string} [options.fgrep] - Test filter given string.
162163
* @param {boolean} [options.forbidOnly] - Tests marked `only` fail the suite?
@@ -216,6 +217,7 @@ function Mocha(options = {}) {
216217
'delay',
217218
'diff',
218219
'dryRun',
220+
'passOnFailingTestSuite',
219221
'failZero',
220222
'forbidOnly',
221223
'forbidPending',
@@ -870,6 +872,20 @@ Mocha.prototype.failZero = function (failZero) {
870872
return this;
871873
};
872874

875+
/**
876+
* Fail test run if tests were failed.
877+
*
878+
* @public
879+
* @see [CLI option](../#-pass-on-failing-test-suite)
880+
* @param {boolean} [passOnFailingTestSuite=false] - Whether to fail test run.
881+
* @return {Mocha} this
882+
* @chainable
883+
*/
884+
Mocha.prototype.passOnFailingTestSuite = function(passOnFailingTestSuite) {
885+
this.options.passOnFailingTestSuite = passOnFailingTestSuite === true;
886+
return this;
887+
};
888+
873889
/**
874890
* Causes tests marked `only` to fail the suite.
875891
*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use strict';
2+
3+
var assert = require('assert');
4+
5+
describe('a suite', function() {
6+
it('should succeed', function() {
7+
assert(false);
8+
});
9+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
'use strict';
2+
3+
var helpers = require('../helpers');
4+
var runMochaJSON = helpers.runMochaJSON;
5+
6+
describe('Enabled --pass-on-failing-test-suite', function() {
7+
var args = ['--pass-on-failing-test-suite=true'];
8+
9+
it('Test should finish with zero code with disabled option', function(done) {
10+
var fixture = 'failing-sync.fixture.js';
11+
runMochaJSON(fixture, args, function(err, res) {
12+
if (err) {
13+
return done(err);
14+
}
15+
16+
expect(res, 'to have passed test count', 0)
17+
.and('to have test count', 1)
18+
.and('to have exit code', 0);
19+
done();
20+
});
21+
});
22+
});
23+
24+
describe('Disabled --pass-on-failing-test-suite', function() {
25+
var args = ['--pass-on-failing-test-suite=false'];
26+
27+
it('Test should return non-zero code with enabled option', function(done) {
28+
var fixture = 'failing-sync.fixture.js';
29+
runMochaJSON(fixture, args, function(err, res) {
30+
if (err) {
31+
return done(err);
32+
}
33+
34+
expect(res, 'to have passed test count', 0)
35+
.and('to have test count', 1)
36+
.and('to have exit code', 1);
37+
done();
38+
});
39+
});
40+
});

test/unit/mocha.spec.js

+22
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,28 @@ describe('Mocha', function () {
376376
});
377377
});
378378

379+
describe('passOnFailingTestSuite()', function() {
380+
it('should set the passOnFailingTestSuite option to false', function() {
381+
mocha.passOnFailingTestSuite();
382+
expect(
383+
mocha.options,
384+
'to have property',
385+
'passOnFailingTestSuite',
386+
false
387+
);
388+
});
389+
390+
it('should set the passOnFailingTestSuite option to true', function() {
391+
mocha.passOnFailingTestSuite(true);
392+
expect(
393+
mocha.options,
394+
'to have property',
395+
'passOnFailingTestSuite',
396+
true
397+
);
398+
});
399+
});
400+
379401
describe('failZero()', function () {
380402
it('should set the failZero option to true', function () {
381403
mocha.failZero();

0 commit comments

Comments
 (0)