From 3c4647136419bcceec90a1d10d3bfd6c47e4547e Mon Sep 17 00:00:00 2001 From: edilson258 Date: Sat, 28 Feb 2026 10:12:49 +0200 Subject: [PATCH] test_runner: fix wrong signal exit codes The test runner was exiting with a generic error code when interrupted by signals such as SIGINT, instead of using the standard signal-based exit codes. This change ensures the runner returns the correct signal exit code (e.g., 130 for SIGINT) rather than 1. Fixes: #62037 --- lib/internal/test_runner/harness.js | 14 +++++++------- src/node_exit_code.h | 4 +++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/internal/test_runner/harness.js b/lib/internal/test_runner/harness.js index 5418a14a4410a4..b256febdfb533d 100644 --- a/lib/internal/test_runner/harness.js +++ b/lib/internal/test_runner/harness.js @@ -20,7 +20,7 @@ const { ERR_TEST_FAILURE, }, } = require('internal/errors'); -const { exitCodes: { kGenericUserError } } = internalBinding('errors'); +const { exitCodes: { kGenericUserError, kSigInt, kSigTerm } } = internalBinding('errors'); const { kCancelledByParent, Test, Suite } = require('internal/test_runner/test'); const { parseCommandLine, @@ -285,8 +285,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', () => terminationHandler(kSigInt)); + process.removeListener('SIGTERM', () => terminationHandler(kSigTerm)); } }; @@ -310,7 +310,7 @@ function setupProcessState(root, globalOptions) { return running; }; - const terminationHandler = async () => { + const terminationHandler = async (exitCode) => { const runningTests = findRunningTests(root); if (runningTests.length > 0) { root.reporter.interrupted(runningTests); @@ -318,7 +318,7 @@ function setupProcessState(root, globalOptions) { await new Promise((resolve) => setImmediate(resolve)); } await exitHandler(true); - process.exit(); + process.exit(exitCode); }; process.on('uncaughtException', exceptionHandler); @@ -326,8 +326,8 @@ function setupProcessState(root, globalOptions) { 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', () => terminationHandler(kSigInt)); + process.on('SIGTERM', () => terminationHandler(kSigTerm)); } root.harness.coverage = FunctionPrototypeBind(collectCoverage, null, root, coverage); diff --git a/src/node_exit_code.h b/src/node_exit_code.h index 0ba0511b5a3de5..3a8a9adf28846f 100644 --- a/src/node_exit_code.h +++ b/src/node_exit_code.h @@ -33,7 +33,9 @@ namespace node { /* typically the exit codes are 128 + signal number. We also exit with */ \ /* certain error codes directly for legacy reasons. Here we define those */ \ /* that are used to normalize the exit code on Windows. */ \ - V(Abort, 134) + V(Abort, 134) \ + V(SigInt, 130) \ + V(SigTerm, 143) // TODO(joyeecheung): expose this to user land when the codes are stable. // The underlying type should be an int, or we can get undefined behavior when