Skip to content

Commit 91bbf85

Browse files
73rhodesJoshuaKGoldbergvoxpelli
authored
feat: add option to use posix exit code upon fatal signal (#4989)
Co-authored-by: Josh Goldberg ✨ <git@joshuakgoldberg.com> Co-authored-by: Pelle Wessman <pelle@kodfabrik.se>
1 parent 6c96545 commit 91bbf85

File tree

11 files changed

+258
-3
lines changed

11 files changed

+258
-3
lines changed

bin/mocha.js

100644100755
Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
* @private
1111
*/
1212

13+
const os = require('node:os');
1314
const {loadOptions} = require('../lib/cli/options');
1415
const {
1516
unparseNodeFlags,
@@ -22,6 +23,7 @@ const {aliases} = require('../lib/cli/run-option-metadata');
2223

2324
const mochaArgs = {};
2425
const nodeArgs = {};
26+
const SIGNAL_OFFSET = 128;
2527
let hasInspect = false;
2628

2729
const opts = loadOptions(process.argv.slice(2));
@@ -109,9 +111,13 @@ if (mochaArgs['node-option'] || Object.keys(nodeArgs).length || hasInspect) {
109111
proc.on('exit', (code, signal) => {
110112
process.on('exit', () => {
111113
if (signal) {
114+
signal = typeof signal === 'string' ? os.constants.signals[signal] : signal;
115+
if (mochaArgs['posix-exit-codes'] === true) {
116+
process.exitCode = SIGNAL_OFFSET + signal;
117+
}
112118
process.kill(process.pid, signal);
113119
} else {
114-
process.exit(code);
120+
process.exit(Math.min(code, mochaArgs['posix-exit-codes'] ? 1 : 255));
115121
}
116122
});
117123
});
@@ -126,7 +132,7 @@ if (mochaArgs['node-option'] || Object.keys(nodeArgs).length || hasInspect) {
126132
// be needed.
127133
if (!args.parallel || args.jobs < 2) {
128134
// win32 does not support SIGTERM, so use next best thing.
129-
if (require('node:os').platform() === 'win32') {
135+
if (os.platform() === 'win32') {
130136
proc.kill('SIGKILL');
131137
} else {
132138
// using SIGKILL won't cleanly close the output streams, which can result

docs/index.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -963,6 +963,18 @@ Define a global variable name. For example, suppose your app deliberately expose
963963

964964
By using this option in conjunction with `--check-leaks`, you can specify a whitelist of known global variables that you _expect_ to leak into global scope.
965965

966+
### `--posix-exit-codes`
967+
968+
Exits with standard POSIX exit codes instead of the number of failed tests.
969+
970+
Those exit codes are:
971+
972+
- `0`: if all tests passed
973+
- `1`: if any test failed
974+
- `128 + <signal>` if given a signal, such as:
975+
- 134: `SIGABRT` (`128 + 6`)
976+
- 143: `SIGTERM` (`128 + 15`)
977+
966978
### `--retries <n>`
967979

968980
Retries failed tests `n` times.

lib/cli/run-helpers.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const {UnmatchedFile} = require('./collect-files');
2727
*/
2828
const exitMochaLater = clampedCode => {
2929
process.on('exit', () => {
30-
process.exitCode = clampedCode;
30+
process.exitCode = Math.min(clampedCode, process.argv.includes('--posix-exit-codes') ? 1 : 255);
3131
});
3232
};
3333

@@ -39,6 +39,8 @@ const exitMochaLater = clampedCode => {
3939
* @private
4040
*/
4141
const exitMocha = clampedCode => {
42+
const usePosixExitCodes = process.argv.includes('--posix-exit-codes');
43+
clampedCode = Math.min(clampedCode, usePosixExitCodes ? 1 : 255);
4244
let draining = 0;
4345

4446
// Eagerly set the process's exit code in case stream.write doesn't

lib/cli/run-option-metadata.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const TYPES = (exports.types = {
4646
'list-reporters',
4747
'no-colors',
4848
'parallel',
49+
'posix-exit-codes',
4950
'recursive',
5051
'sort',
5152
'watch'

lib/cli/run.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,10 @@ exports.builder = yargs =>
195195
description: 'Run tests in parallel',
196196
group: GROUPS.RULES
197197
},
198+
'posix-exit-codes': {
199+
description: 'Use POSIX and UNIX shell exit codes as Mocha\'s return value',
200+
group: GROUPS.RULES
201+
},
198202
recursive: {
199203
description: 'Look for tests in subdirectories',
200204
group: GROUPS.FILES
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use strict';
2+
3+
// One passing test and three failing tests
4+
5+
var assert = require('assert');
6+
7+
describe('suite', function () {
8+
it('test1', function () {
9+
assert(true);
10+
});
11+
12+
it('test2', function () {
13+
assert(false);
14+
});
15+
16+
it('test3', function () {
17+
assert(false);
18+
});
19+
20+
it('test4', function () {
21+
assert(false);
22+
});
23+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict';
2+
3+
describe('signal suite', function () {
4+
it('test SIGABRT', function () {
5+
process.kill(process.pid, 'SIGABRT');
6+
});
7+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
'use strict';
2+
const os = require('node:os');
3+
4+
describe('signal suite', function () {
5+
it('test SIGTERM', function () {
6+
process.kill(process.pid, os.constants.signals['SIGTERM']);
7+
});
8+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict';
2+
3+
describe('signal suite', function () {
4+
it('test SIGTERM', function () {
5+
process.kill(process.pid, 'SIGTERM');
6+
});
7+
});

test/integration/helpers.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const {format} = require('node:util');
88
const path = require('node:path');
99
const Base = require('../../lib/reporters/base');
1010
const debug = require('debug')('mocha:test:integration:helpers');
11+
const SIGNAL_OFFSET = 128;
1112

1213
/**
1314
* Path to `mocha` executable
@@ -358,6 +359,18 @@ function createSubprocess(args, done, opts = {}) {
358359
});
359360
});
360361

362+
/**
363+
* Emulate node's exit code for fatal signal. Allows tests to see the same
364+
* exit code as the mocha cli.
365+
*/
366+
mocha.on('exit', (code, signal) => {
367+
if (signal) {
368+
mocha.exitCode =
369+
SIGNAL_OFFSET +
370+
(typeof signal == 'string' ? os.constants.signals[signal] : signal);
371+
}
372+
});
373+
361374
return mocha;
362375
}
363376

0 commit comments

Comments
 (0)