Skip to content

Commit

Permalink
test_runner: graceful termination on --test only
Browse files Browse the repository at this point in the history
PR-URL: #43977
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Nitzan Uziely <linkgoron@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
  • Loading branch information
MoLow authored and danielleadams committed Aug 11, 2022
1 parent ad537e6 commit e2a3bf3
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 21 deletions.
10 changes: 7 additions & 3 deletions lib/internal/test_runner/harness.js
Expand Up @@ -13,9 +13,10 @@ const {
ERR_TEST_FAILURE,
},
} = require('internal/errors');
const { getOptionValue } = require('internal/options');
const { Test, ItTest, Suite } = require('internal/test_runner/test');


const isTestRunner = getOptionValue('--test');
const testResources = new SafeMap();
const root = new Test({ __proto__: null, name: '<root>' });
let wasRootSetup = false;
Expand Down Expand Up @@ -134,8 +135,11 @@ function setup(root) {
process.on('uncaughtException', exceptionHandler);
process.on('unhandledRejection', rejectionHandler);
process.on('beforeExit', exitHandler);
process.on('SIGINT', terminationHandler);
process.on('SIGTERM', terminationHandler);
// TODO(MoLow): Make it configurable to hook when isTestRunner === false.
if (isTestRunner) {
process.on('SIGINT', terminationHandler);
process.on('SIGTERM', terminationHandler);
}

root.reporter.pipe(process.stdout);
root.reporter.version();
Expand Down
6 changes: 6 additions & 0 deletions test/fixtures/test-runner/never_ending_async.js
@@ -0,0 +1,6 @@
const test = require('node:test');
const { setTimeout } = require('timers/promises');

// We are using a very large timeout value to ensure that the parent process
// will have time to send a SIGINT signal to cancel the test.
test('never ending test', () => setTimeout(100_000_000));
5 changes: 5 additions & 0 deletions test/fixtures/test-runner/never_ending_sync.js
@@ -0,0 +1,5 @@
const test = require('node:test');

test('never ending test', () => {
while (true);
});
43 changes: 25 additions & 18 deletions test/parallel/test-runner-exit-code.js
Expand Up @@ -2,8 +2,29 @@
const common = require('../common');
const fixtures = require('../common/fixtures');
const assert = require('assert');
const { spawnSync } = require('child_process');
const { setTimeout } = require('timers/promises');
const { spawnSync, spawn } = require('child_process');
const { once } = require('events');
const { finished } = require('stream/promises');

async function runAndKill(file) {
if (common.isWindows) {
common.printSkipMessage(`signals are not supported in windows, skipping ${file}`);
return;
}
let stdout = '';
const child = spawn(process.execPath, ['--test', file]);
child.stdout.setEncoding('utf8');
child.stdout.on('data', (chunk) => {
if (!stdout.length) child.kill('SIGINT');
stdout += chunk;
});
const [code, signal] = await once(child, 'exit');
await finished(child.stdout);
assert.match(stdout, /not ok 1/);
assert.match(stdout, /# cancelled 1\n/);
assert.strictEqual(signal, null);
assert.strictEqual(code, 1);
}

if (process.argv[2] === 'child') {
const test = require('node:test');
Expand All @@ -17,12 +38,6 @@ if (process.argv[2] === 'child') {
test('failing test', () => {
assert.strictEqual(true, false);
});
} else if (process.argv[3] === 'never_ends') {
assert.strictEqual(process.argv[3], 'never_ends');
test('never ending test', () => {
return setTimeout(100_000_000);
});
process.kill(process.pid, 'SIGINT');
} else assert.fail('unreachable');
} else {
let child = spawnSync(process.execPath, [__filename, 'child', 'pass']);
Expand All @@ -37,14 +52,6 @@ if (process.argv[2] === 'child') {
assert.strictEqual(child.status, 1);
assert.strictEqual(child.signal, null);

child = spawnSync(process.execPath, [__filename, 'child', 'never_ends']);
assert.strictEqual(child.status, 1);
assert.strictEqual(child.signal, null);
if (common.isWindows) {
common.printSkipMessage('signals are not supported in windows');
} else {
const stdout = child.stdout.toString();
assert.match(stdout, /not ok 1 - never ending test/);
assert.match(stdout, /# cancelled 1/);
}
runAndKill(fixtures.path('test-runner', 'never_ending_sync.js')).then(common.mustCall());
runAndKill(fixtures.path('test-runner', 'never_ending_async.js')).then(common.mustCall());
}

0 comments on commit e2a3bf3

Please sign in to comment.