diff --git a/lib/internal/test_runner/harness.js b/lib/internal/test_runner/harness.js index 5418a14a4410a4..cc557adcc4c88e 100644 --- a/lib/internal/test_runner/harness.js +++ b/lib/internal/test_runner/harness.js @@ -20,6 +20,7 @@ const { ERR_TEST_FAILURE, }, } = require('internal/errors'); +const signalNumbers = internalBinding('constants').os.signals; const { exitCodes: { kGenericUserError } } = internalBinding('errors'); const { kCancelledByParent, Test, Suite } = require('internal/test_runner/test'); const { @@ -285,8 +286,8 @@ function setupProcessState(root, globalOptions) { process.removeListener('unhandledRejection', rejectionHandler); process.removeListener('beforeExit', exitHandler); if (globalOptions.isTestRunner) { - process.removeListener('SIGINT', terminationHandler); - process.removeListener('SIGTERM', terminationHandler); + process.removeListener('SIGINT', sigintHandler); + process.removeListener('SIGTERM', sigtermHandler); } }; @@ -310,24 +311,27 @@ function setupProcessState(root, globalOptions) { return running; }; - const terminationHandler = async () => { + const terminationHandler = async (signal) => { const runningTests = findRunningTests(root); if (runningTests.length > 0) { root.reporter.interrupted(runningTests); // Allow the reporter stream to process the interrupted event await new Promise((resolve) => setImmediate(resolve)); } + process.exitCode = 128 + signalNumbers[signal]; await exitHandler(true); process.exit(); }; + const sigintHandler = FunctionPrototypeBind(terminationHandler, null, 'SIGINT'); + const sigtermHandler = FunctionPrototypeBind(terminationHandler, null, 'SIGTERM'); 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) { - process.on('SIGINT', terminationHandler); - process.on('SIGTERM', terminationHandler); + process.on('SIGINT', sigintHandler); + process.on('SIGTERM', sigtermHandler); } root.harness.coverage = FunctionPrototypeBind(collectCoverage, null, root, coverage); diff --git a/test/parallel/test-runner-exit-code.js b/test/parallel/test-runner-exit-code.js index c25becee3f708f..f965b374404d12 100644 --- a/test/parallel/test-runner-exit-code.js +++ b/test/parallel/test-runner-exit-code.js @@ -6,7 +6,7 @@ const { spawnSync, spawn } = require('child_process'); const { once } = require('events'); const { finished } = require('stream/promises'); -async function runAndKill(file, expectedTestName) { +async function runAndKill(file, expectedTestName, killSignal, expectedCode) { if (common.isWindows) { common.printSkipMessage(`signals are not supported in windows, skipping ${file}`); return; @@ -15,17 +15,17 @@ async function runAndKill(file, expectedTestName) { const child = spawn(process.execPath, ['--test', '--test-reporter=tap', file]); child.stdout.setEncoding('utf8'); child.stdout.on('data', (chunk) => { - if (!stdout.length) child.kill('SIGINT'); + if (!stdout.length) child.kill(killSignal); stdout += chunk; }); - const [code, signal] = await once(child, 'exit'); + const [code, exitSignal] = await once(child, 'exit'); await finished(child.stdout); assert(stdout.startsWith('TAP version 13\n')); // Verify interrupted test message assert(stdout.includes(`Interrupted while running: ${expectedTestName}`), `Expected output to contain interrupted test name`); - assert.strictEqual(signal, null); - assert.strictEqual(code, 1); + assert.strictEqual(exitSignal, null); + assert.strictEqual(code, expectedCode); } if (process.argv[2] === 'child') { @@ -82,6 +82,6 @@ if (process.argv[2] === 'child') { // because the parent runner only knows about file-level tests const neverEndingSync = fixtures.path('test-runner', 'never_ending_sync.js'); const neverEndingAsync = fixtures.path('test-runner', 'never_ending_async.js'); - runAndKill(neverEndingSync, neverEndingSync).then(common.mustCall()); - runAndKill(neverEndingAsync, neverEndingAsync).then(common.mustCall()); + runAndKill(neverEndingSync, neverEndingSync, 'SIGINT', 130).then(common.mustCall()); + runAndKill(neverEndingAsync, neverEndingAsync, 'SIGTERM', 143).then(common.mustCall()); }