Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
test_runner: make harness hook configurable
  • Loading branch information
mete0rfish committed Sep 1, 2025
commit 017d30c27d1151d07d28165419a3dfb0c86253ae
3 changes: 1 addition & 2 deletions lib/internal/test_runner/harness.js
Original file line number Diff line number Diff line change
Expand Up @@ -297,8 +297,7 @@ function setupProcessState(root, globalOptions) {
process.on('uncaughtException', exceptionHandler);
process.on('unhandledRejection', rejectionHandler);
process.on('beforeExit', exitHandler);
// TODO(MoLow): Make it configurable to hook when isTestRunner === false.
if (globalOptions.isTestRunner) {
if (globalOptions.isTestRunner || globalOptions.hookSignal) {
process.on('SIGINT', terminationHandler);
process.on('SIGTERM', terminationHandler);
}
Expand Down
1 change: 1 addition & 0 deletions lib/internal/test_runner/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,7 @@ function run(options = kEmptyObject) {
// behavior has relied on it, so removing it must be done in a semver major.
...parseCommandLine(),
setup, // This line can be removed when parseCommandLine() is removed here.
hookSignal,
coverage,
coverageExcludeGlobs,
coverageIncludeGlobs,
Expand Down
34 changes: 31 additions & 3 deletions test/parallel/test-runner-exit-code.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
const common = require('../common');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const { spawnSync, spawn } = require('child_process');
const { spawnSync, spawn } = require('node:child_process');
const { once } = require('events');
const { finished } = require('stream/promises');

Expand All @@ -25,18 +25,35 @@ async function runAndKill(file) {
assert.strictEqual(code, 1);
}

async function spawnAndKillProgrammatic(childArgs, { code: expectedCode, signal: expectedSignal }) {
if (common.isWindows) {
common.printSkipMessage('signals are not supported on windows');
return;
}
const child = spawn(process.execPath, childArgs);
child.stdout.once('data', () => child.kill('SIGINT'));
const [code, signal] = await once(child, 'exit');
assert.strictEqual(signal, expectedSignal);
assert.strictEqual(code, expectedCode);
}

if (process.argv[2] === 'child') {
const test = require('node:test');
const { test, run } = require('node:test');

if (process.argv[3] === 'pass') {
test('passing test', () => {
assert.strictEqual(true, true);
});
} else if (process.argv[3] === 'fail') {
assert.strictEqual(process.argv[3], 'fail');
test('failing test', () => {
assert.strictEqual(true, false);
});
} else if (process.argv[3] === 'run-signal-false') {
run({ files: [fixtures.path('test-runner', 'never_ending_async.js')] });
console.log('child started');
} else if (process.argv[3] === 'run-signal-true') {
run({ files: [fixtures.path('test-runner', 'never_ending_async.js')], signal: true });
console.log('child started');
} else assert.fail('unreachable');
} else {
let child = spawnSync(process.execPath, [__filename, 'child', 'pass']);
Expand Down Expand Up @@ -69,4 +86,15 @@ if (process.argv[2] === 'child') {

runAndKill(fixtures.path('test-runner', 'never_ending_sync.js')).then(common.mustCall());
runAndKill(fixtures.path('test-runner', 'never_ending_async.js')).then(common.mustCall());

(async () => {
await spawnAndKillProgrammatic(
[__filename, 'child', 'run-signal-false'],
{ signal: 'SIGINT', code: null },
);
await spawnAndKillProgrammatic(
[__filename, 'child', 'run-signal-true'],
{ signal: null, code: 1 },
);
})().then(common.mustCall());
}