diff --git a/test/common/watch.js b/test/common/watch.js new file mode 100644 index 00000000000000..068790780e1470 --- /dev/null +++ b/test/common/watch.js @@ -0,0 +1,16 @@ +'use strict'; +const common = require('./index.js'); + +exports.skipIfNoWatchModeSignals = function() { + if (common.isWindows) { + common.skip('no signals on Windows'); + } + + if (common.isIBMi) { + common.skip('IBMi does not support `fs.watch()`'); + } + + if (common.isAIX) { + common.skip('folder watch capability is limited in AIX.'); + } +}; diff --git a/test/fixtures/kill-signal-for-watch.js b/test/fixtures/kill-signal-for-watch.js new file mode 100644 index 00000000000000..bfa67a63a65db0 --- /dev/null +++ b/test/fixtures/kill-signal-for-watch.js @@ -0,0 +1,4 @@ +process.on('SIGTERM', () => { console.log('__SIGTERM received__'); process.exit(); }); +process.on('SIGINT', () => { console.log('__SIGINT received__'); process.exit(); }); +process.send('script ready'); +setTimeout(() => {}, 100_000); diff --git a/test/parallel/test-runner-watch-mode-kill-signal.mjs b/test/parallel/test-runner-watch-mode-kill-signal.mjs deleted file mode 100644 index d59f4522fadc7f..00000000000000 --- a/test/parallel/test-runner-watch-mode-kill-signal.mjs +++ /dev/null @@ -1,131 +0,0 @@ -import * as common from '../common/index.mjs'; -import { describe, it, beforeEach } from 'node:test'; -import assert from 'node:assert'; -import { spawn } from 'node:child_process'; -import { writeFileSync } from 'node:fs'; -import tmpdir from '../common/tmpdir.js'; - -if (common.isWindows) { - common.skip('no signals on Windows'); -} - -if (common.isIBMi) { - common.skip('IBMi does not support `fs.watch()`'); -} - -if (common.isAIX) { - common.skip('folder watch capability is limited in AIX.'); -} - -const indexContents = ` - process.on('SIGTERM', () => { console.log('__SIGTERM received__'); process.exit(); }); - process.on('SIGINT', () => { console.log('__SIGINT received__'); process.exit(); }); - process.send('script ready'); - setTimeout(() => {}, 100_000); -`; - -let indexPath = ''; - -function refresh() { - tmpdir.refresh(); - indexPath = tmpdir.resolve('index.js'); - writeFileSync(indexPath, indexContents); -} - -describe('test runner watch mode with --watch-kill-signal', () => { - beforeEach(refresh); - - it('defaults to SIGTERM', async () => { - const child = spawn( - process.execPath, - ['--watch', indexPath], - { - cwd: tmpdir.path, - stdio: ['pipe', 'pipe', 'pipe', 'ipc'], - } - ); - - let stdout = ''; - child.stdout.on('data', (data) => { - stdout += `${data}`; - if (/__(SIGINT|SIGTERM) received__/.test(stdout)) { - child.kill(); - } - }); - - child.on('message', (msg) => { - if (msg === 'script ready') { - writeFileSync(indexPath, indexContents); - } - }); - - await new Promise((resolve) => - child.on('exit', () => { - resolve(); - }) - ); - - assert.match(stdout, /__SIGTERM received__/); - assert.doesNotMatch(stdout, /__SIGINT received__/); - }); - - it('can be overridden (to SIGINT)', async () => { - const child = spawn( - process.execPath, - ['--watch', '--watch-kill-signal', 'SIGINT', indexPath], - { - cwd: tmpdir.path, - stdio: ['pipe', 'pipe', 'pipe', 'ipc'], - } - ); - - let stdout = ''; - child.stdout.on('data', (data) => { - stdout += `${data}`; - if (/__(SIGINT|SIGTERM) received__/.test(stdout)) { - child.kill(); - } - }); - - child.on('message', (msg) => { - if (msg === 'script ready') { - writeFileSync(indexPath, indexContents); - } - }); - - await new Promise((resolve) => - child.on('exit', () => { - resolve(); - }) - ); - - assert.match(stdout, /__SIGINT received__/); - assert.doesNotMatch(stdout, /__SIGTERM received__/); - }); - - it('errors if an invalid signal is provided', async () => { - const currentRun = Promise.withResolvers(); - const child = spawn( - process.execPath, - ['--watch', '--watch-kill-signal', 'invalid_signal', indexPath], - { - cwd: tmpdir.path, - } - ); - let stdout = ''; - - child.stderr.on('data', (data) => { - stdout += data.toString(); - currentRun.resolve(); - }); - - await currentRun.promise; - - assert.match( - stdout, - new RegExp( - /TypeError \[ERR_UNKNOWN_SIGNAL\]: Unknown signal: invalid_signal/ - ) - ); - }); -}); diff --git a/test/parallel/test-watch-mode-kill-signal-default.mjs b/test/parallel/test-watch-mode-kill-signal-default.mjs new file mode 100644 index 00000000000000..c3b2331e367ebb --- /dev/null +++ b/test/parallel/test-watch-mode-kill-signal-default.mjs @@ -0,0 +1,45 @@ +// Test that the kill signal sent by --watch defaults to SIGTERM. + +import '../common/index.mjs'; +import assert from 'node:assert'; +import { writeFileSync } from 'node:fs'; +import { spawn } from 'node:child_process'; +import { once } from 'node:events'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; +import { skipIfNoWatchModeSignals } from '../common/watch.js'; + +skipIfNoWatchModeSignals(); + +tmpdir.refresh(); +const indexPath = tmpdir.resolve('kill-signal-for-watch.js'); +const indexContents = fixtures.readSync('kill-signal-for-watch.js', 'utf8'); +writeFileSync(indexPath, indexContents); + +const child = spawn( + process.execPath, + ['--watch', indexPath], + { + cwd: tmpdir.path, + stdio: ['inherit', 'pipe', 'inherit', 'ipc'], + } +); + +let stdout = ''; +child.stdout.on('data', (data) => { + stdout += `${data}`; + if (/__(SIGINT|SIGTERM) received__/.test(stdout)) { + child.kill(); + } +}); + +child.on('message', (msg) => { + if (msg === 'script ready') { + writeFileSync(indexPath, indexContents); + } +}); + +await once(child, 'exit'); + +assert.match(stdout, /__SIGTERM received__/); +assert.doesNotMatch(stdout, /__SIGINT received__/); diff --git a/test/parallel/test-watch-mode-kill-signal-invalid.mjs b/test/parallel/test-watch-mode-kill-signal-invalid.mjs new file mode 100644 index 00000000000000..e2f32ba978e210 --- /dev/null +++ b/test/parallel/test-watch-mode-kill-signal-invalid.mjs @@ -0,0 +1,39 @@ +// Test that --watch-kill-signal errors when an invalid kill signal is provided. + +import '../common/index.mjs'; +import assert from 'node:assert'; +import { writeFileSync } from 'node:fs'; +import { spawn } from 'node:child_process'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; +import { skipIfNoWatchModeSignals } from '../common/watch.js'; + +skipIfNoWatchModeSignals(); + +tmpdir.refresh(); +const indexPath = tmpdir.resolve('kill-signal-for-watch.js'); +const indexContents = fixtures.readSync('kill-signal-for-watch.js', 'utf8'); +writeFileSync(indexPath, indexContents); + +const currentRun = Promise.withResolvers(); +const child = spawn( + process.execPath, + ['--watch', '--watch-kill-signal', 'invalid_signal', indexPath], + { + cwd: tmpdir.path, + stdio: ['inherit', 'inherit', 'pipe'], + } +); +let stderr = ''; + +child.stderr.on('data', (data) => { + stderr += data.toString(); + currentRun.resolve(); +}); + +await currentRun.promise; + +assert.match( + stderr, + /TypeError \[ERR_UNKNOWN_SIGNAL\]: Unknown signal: invalid_signal/ +); diff --git a/test/parallel/test-watch-mode-kill-signal-override.mjs b/test/parallel/test-watch-mode-kill-signal-override.mjs new file mode 100644 index 00000000000000..cc9e1219bc616e --- /dev/null +++ b/test/parallel/test-watch-mode-kill-signal-override.mjs @@ -0,0 +1,46 @@ +// Test that the kill signal sent by --watch can be overridden to SIGINT +// by using --watch-kill-signal. + +import '../common/index.mjs'; +import assert from 'node:assert'; +import { writeFileSync } from 'node:fs'; +import { spawn } from 'node:child_process'; +import { once } from 'node:events'; +import tmpdir from '../common/tmpdir.js'; +import fixtures from '../common/fixtures.js'; +import { skipIfNoWatchModeSignals } from '../common/watch.js'; + +skipIfNoWatchModeSignals(); + +tmpdir.refresh(); +const indexPath = tmpdir.resolve('kill-signal-for-watch.js'); +const indexContents = fixtures.readSync('kill-signal-for-watch.js', 'utf8'); +writeFileSync(indexPath, indexContents); + +const child = spawn( + process.execPath, + ['--watch', '--watch-kill-signal', 'SIGINT', indexPath], + { + cwd: tmpdir.path, + stdio: ['inherit', 'pipe', 'inherit', 'ipc'], + } +); + +let stdout = ''; +child.stdout.on('data', (data) => { + stdout += `${data}`; + if (/__(SIGINT|SIGTERM) received__/.test(stdout)) { + child.kill(); + } +}); + +child.on('message', (msg) => { + if (msg === 'script ready') { + writeFileSync(indexPath, indexContents); + } +}); + +await once(child, 'exit'); + +assert.match(stdout, /__SIGINT received__/); +assert.doesNotMatch(stdout, /__SIGTERM received__/);