From e330bd8194c6b053280adf56fe0740a8adfb912a Mon Sep 17 00:00:00 2001 From: ehmicky Date: Tue, 29 Oct 2024 05:48:34 +0000 Subject: [PATCH] Runs CI tests on Node 23 (#1169) --- .github/workflows/main.yml | 4 +- test/convert/duplex.js | 23 ++++++--- test/convert/readable.js | 23 +++++---- test/fixtures/graceful-ref.js | 3 +- test/helpers/node-version.js | 3 ++ test/stdio/type-invalid.js | 63 +++++++++++++++++++++++ test/stdio/{type.js => type-undefined.js} | 57 +------------------- test/terminate/kill-signal.js | 7 ++- 8 files changed, 103 insertions(+), 80 deletions(-) create mode 100644 test/helpers/node-version.js create mode 100644 test/stdio/type-invalid.js rename test/stdio/{type.js => type-undefined.js} (53%) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f25cd87a75..42286bf001 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,7 +10,7 @@ jobs: fail-fast: false matrix: node-version: - - 22 + - 23 - 18 os: - ubuntu @@ -31,7 +31,7 @@ jobs: with: args: --cache --verbose --no-progress --include-fragments --exclude packagephobia --exclude /pull/ --exclude linkedin --exclude file:///test --exclude invalid.com '*.md' 'docs/*.md' '.github/**/*.md' '*.json' '*.js' 'lib/**/*.js' 'test/**/*.js' '*.ts' 'test-d/**/*.ts' fail: true - if: ${{ matrix.os == 'ubuntu' && matrix.node-version == 22 }} + if: ${{ matrix.os == 'ubuntu' && matrix.node-version == 23 }} - run: npm run lint - run: npm run type - run: npm run unit diff --git a/test/convert/duplex.js b/test/convert/duplex.js index dd71ce371a..d8ad6ef62e 100644 --- a/test/convert/duplex.js +++ b/test/convert/duplex.js @@ -23,6 +23,7 @@ import { getReadWriteSubprocess, } from '../helpers/convert.js'; import {foobarString} from '../helpers/input.js'; +import {majorNodeVersion} from '../helpers/node-version.js'; import {prematureClose, fullStdio, fullReadableStdio} from '../helpers/stdio.js'; import {defaultHighWaterMark} from '../helpers/stream.js'; @@ -148,13 +149,21 @@ test('.duplex() can pipe to errored stream with Stream.pipeline()', async t => { const cause = new Error('test'); outputStream.destroy(cause); - await assertPromiseError(t, pipeline(inputStream, stream, outputStream), cause); - await t.throwsAsync(finishedStream(stream)); - - await assertStreamError(t, inputStream, cause); - const error = await assertStreamError(t, stream, cause); - await assertStreamReadError(t, outputStream, cause); - await assertSubprocessError(t, subprocess, {cause: error}); + // Node 23 does not allow calling `stream.pipeline()` with an already errored stream + if (majorNodeVersion >= 23) { + outputStream.on('error', () => {}); + stream.on('error', () => {}); + await t.throwsAsync(pipeline(stream, outputStream), {code: 'ERR_STREAM_UNABLE_TO_PIPE'}); + stream.end(); + } else { + await assertPromiseError(t, pipeline(inputStream, stream, outputStream), cause); + await t.throwsAsync(finishedStream(stream)); + + await assertStreamError(t, inputStream, cause); + const error = await assertStreamError(t, stream, cause); + await assertStreamReadError(t, outputStream, cause); + await assertSubprocessError(t, subprocess, {cause: error}); + } }); test('.duplex() can be piped to errored stream with Stream.pipeline()', async t => { diff --git a/test/convert/readable.js b/test/convert/readable.js index 3b9454cb46..504c749d67 100644 --- a/test/convert/readable.js +++ b/test/convert/readable.js @@ -1,5 +1,4 @@ import {once} from 'node:events'; -import process from 'node:process'; import { compose, Readable, @@ -29,6 +28,7 @@ import { } from '../helpers/convert.js'; import {foobarString, foobarBuffer, foobarObject} from '../helpers/input.js'; import {simpleFull} from '../helpers/lines.js'; +import {majorNodeVersion} from '../helpers/node-version.js'; import {prematureClose, fullStdio} from '../helpers/stdio.js'; import {outputObjectGenerator, getOutputsAsyncGenerator} from '../helpers/generator.js'; import {defaultHighWaterMark, defaultObjectHighWaterMark} from '../helpers/stream.js'; @@ -231,12 +231,18 @@ test('.readable() can pipe to errored stream with Stream.pipeline()', async t => const cause = new Error('test'); outputStream.destroy(cause); - await assertPromiseError(t, pipeline(stream, outputStream), cause); - await t.throwsAsync(finishedStream(stream)); - - const error = await assertStreamError(t, stream, cause); - await assertStreamReadError(t, outputStream, cause); - await assertSubprocessError(t, subprocess, {cause: error}); + // Node 23 does not allow calling `stream.pipeline()` with an already errored stream + if (majorNodeVersion >= 23) { + outputStream.on('error', () => {}); + await t.throwsAsync(pipeline(stream, outputStream), {code: 'ERR_STREAM_UNABLE_TO_PIPE'}); + } else { + await assertPromiseError(t, pipeline(stream, outputStream), cause); + await t.throwsAsync(finishedStream(stream)); + + const error = await assertStreamError(t, stream, cause); + await assertStreamReadError(t, outputStream, cause); + await assertSubprocessError(t, subprocess, {cause: error}); + } }); test('.readable() can be used with Stream.compose()', async t => { @@ -421,8 +427,7 @@ test('.duplex() can be paused', async t => { // This feature does not work on Node 18. // @todo: remove after dropping support for Node 18. -const majorVersion = Number(process.version.split('.')[0].slice(1)); -if (majorVersion >= 20) { +if (majorNodeVersion >= 20) { const testHighWaterMark = async (t, methodName) => { const subprocess = execa('stdin.js'); const stream = subprocess[methodName](); diff --git a/test/fixtures/graceful-ref.js b/test/fixtures/graceful-ref.js index ae28fba5c3..216b257fde 100755 --- a/test/fixtures/graceful-ref.js +++ b/test/fixtures/graceful-ref.js @@ -1,6 +1,5 @@ #!/usr/bin/env node -import {once} from 'node:events'; import {getCancelSignal} from 'execa'; const cancelSignal = await getCancelSignal(); -once(cancelSignal, 'abort'); +cancelSignal.addEventListener('abort', () => {}); diff --git a/test/helpers/node-version.js b/test/helpers/node-version.js new file mode 100644 index 0000000000..bbe49ab3c9 --- /dev/null +++ b/test/helpers/node-version.js @@ -0,0 +1,3 @@ +import {version} from 'node:process'; + +export const majorNodeVersion = Number(version.split('.')[0].slice(1)); diff --git a/test/stdio/type-invalid.js b/test/stdio/type-invalid.js new file mode 100644 index 0000000000..488abfcaf6 --- /dev/null +++ b/test/stdio/type-invalid.js @@ -0,0 +1,63 @@ +import test from 'ava'; +import {execa, execaSync} from '../../index.js'; +import {getStdio} from '../helpers/stdio.js'; +import {noopGenerator} from '../helpers/generator.js'; +import {generatorsMap} from '../helpers/map.js'; +import {setFixtureDirectory} from '../helpers/fixtures-directory.js'; + +setFixtureDirectory(); + +const testInvalidGenerator = (t, fdNumber, stdioOption, execaMethod) => { + t.throws(() => { + execaMethod('empty.js', getStdio(fdNumber, {...noopGenerator(), ...stdioOption})); + }, {message: 'final' in stdioOption ? /must be a generator/ : /must be a generator, a Duplex stream or a web TransformStream/}); +}; + +test('Cannot use invalid "transform" with stdin', testInvalidGenerator, 0, {transform: true}, execa); +test('Cannot use invalid "transform" with stdout', testInvalidGenerator, 1, {transform: true}, execa); +test('Cannot use invalid "transform" with stderr', testInvalidGenerator, 2, {transform: true}, execa); +test('Cannot use invalid "transform" with stdio[*]', testInvalidGenerator, 3, {transform: true}, execa); +test('Cannot use invalid "final" with stdin', testInvalidGenerator, 0, {final: true}, execa); +test('Cannot use invalid "final" with stdout', testInvalidGenerator, 1, {final: true}, execa); +test('Cannot use invalid "final" with stderr', testInvalidGenerator, 2, {final: true}, execa); +test('Cannot use invalid "final" with stdio[*]', testInvalidGenerator, 3, {final: true}, execa); +test('Cannot use invalid "transform" with stdin, sync', testInvalidGenerator, 0, {transform: true}, execaSync); +test('Cannot use invalid "transform" with stdout, sync', testInvalidGenerator, 1, {transform: true}, execaSync); +test('Cannot use invalid "transform" with stderr, sync', testInvalidGenerator, 2, {transform: true}, execaSync); +test('Cannot use invalid "transform" with stdio[*], sync', testInvalidGenerator, 3, {transform: true}, execaSync); +test('Cannot use invalid "final" with stdin, sync', testInvalidGenerator, 0, {final: true}, execaSync); +test('Cannot use invalid "final" with stdout, sync', testInvalidGenerator, 1, {final: true}, execaSync); +test('Cannot use invalid "final" with stderr, sync', testInvalidGenerator, 2, {final: true}, execaSync); +test('Cannot use invalid "final" with stdio[*], sync', testInvalidGenerator, 3, {final: true}, execaSync); + +// eslint-disable-next-line max-params +const testInvalidBinary = (t, fdNumber, optionName, type, execaMethod) => { + t.throws(() => { + execaMethod('empty.js', getStdio(fdNumber, {...generatorsMap[type].uppercase(), [optionName]: 'true'})); + }, {message: /a boolean/}); +}; + +test('Cannot use invalid "binary" with stdin', testInvalidBinary, 0, 'binary', 'generator', execa); +test('Cannot use invalid "binary" with stdout', testInvalidBinary, 1, 'binary', 'generator', execa); +test('Cannot use invalid "binary" with stderr', testInvalidBinary, 2, 'binary', 'generator', execa); +test('Cannot use invalid "binary" with stdio[*]', testInvalidBinary, 3, 'binary', 'generator', execa); +test('Cannot use invalid "objectMode" with stdin, generators', testInvalidBinary, 0, 'objectMode', 'generator', execa); +test('Cannot use invalid "objectMode" with stdout, generators', testInvalidBinary, 1, 'objectMode', 'generator', execa); +test('Cannot use invalid "objectMode" with stderr, generators', testInvalidBinary, 2, 'objectMode', 'generator', execa); +test('Cannot use invalid "objectMode" with stdio[*], generators', testInvalidBinary, 3, 'objectMode', 'generator', execa); +test('Cannot use invalid "binary" with stdin, sync', testInvalidBinary, 0, 'binary', 'generator', execaSync); +test('Cannot use invalid "binary" with stdout, sync', testInvalidBinary, 1, 'binary', 'generator', execaSync); +test('Cannot use invalid "binary" with stderr, sync', testInvalidBinary, 2, 'binary', 'generator', execaSync); +test('Cannot use invalid "binary" with stdio[*], sync', testInvalidBinary, 3, 'binary', 'generator', execaSync); +test('Cannot use invalid "objectMode" with stdin, generators, sync', testInvalidBinary, 0, 'objectMode', 'generator', execaSync); +test('Cannot use invalid "objectMode" with stdout, generators, sync', testInvalidBinary, 1, 'objectMode', 'generator', execaSync); +test('Cannot use invalid "objectMode" with stderr, generators, sync', testInvalidBinary, 2, 'objectMode', 'generator', execaSync); +test('Cannot use invalid "objectMode" with stdio[*], generators, sync', testInvalidBinary, 3, 'objectMode', 'generator', execaSync); +test('Cannot use invalid "objectMode" with stdin, duplexes', testInvalidBinary, 0, 'objectMode', 'duplex', execa); +test('Cannot use invalid "objectMode" with stdout, duplexes', testInvalidBinary, 1, 'objectMode', 'duplex', execa); +test('Cannot use invalid "objectMode" with stderr, duplexes', testInvalidBinary, 2, 'objectMode', 'duplex', execa); +test('Cannot use invalid "objectMode" with stdio[*], duplexes', testInvalidBinary, 3, 'objectMode', 'duplex', execa); +test('Cannot use invalid "objectMode" with stdin, webTransforms', testInvalidBinary, 0, 'objectMode', 'webTransform', execa); +test('Cannot use invalid "objectMode" with stdout, webTransforms', testInvalidBinary, 1, 'objectMode', 'webTransform', execa); +test('Cannot use invalid "objectMode" with stderr, webTransforms', testInvalidBinary, 2, 'objectMode', 'webTransform', execa); +test('Cannot use invalid "objectMode" with stdio[*], webTransforms', testInvalidBinary, 3, 'objectMode', 'webTransform', execa); diff --git a/test/stdio/type.js b/test/stdio/type-undefined.js similarity index 53% rename from test/stdio/type.js rename to test/stdio/type-undefined.js index 7407dbf2af..56beb40c89 100644 --- a/test/stdio/type.js +++ b/test/stdio/type-undefined.js @@ -1,7 +1,7 @@ import test from 'ava'; import {execa, execaSync} from '../../index.js'; import {getStdio} from '../helpers/stdio.js'; -import {noopGenerator, uppercaseGenerator} from '../helpers/generator.js'; +import {uppercaseGenerator} from '../helpers/generator.js'; import {uppercaseBufferDuplex} from '../helpers/duplex.js'; import {uppercaseBufferWebTransform} from '../helpers/web-transform.js'; import {generatorsMap} from '../helpers/map.js'; @@ -9,61 +9,6 @@ import {setFixtureDirectory} from '../helpers/fixtures-directory.js'; setFixtureDirectory(); -const testInvalidGenerator = (t, fdNumber, stdioOption, execaMethod) => { - t.throws(() => { - execaMethod('empty.js', getStdio(fdNumber, {...noopGenerator(), ...stdioOption})); - }, {message: 'final' in stdioOption ? /must be a generator/ : /must be a generator, a Duplex stream or a web TransformStream/}); -}; - -test('Cannot use invalid "transform" with stdin', testInvalidGenerator, 0, {transform: true}, execa); -test('Cannot use invalid "transform" with stdout', testInvalidGenerator, 1, {transform: true}, execa); -test('Cannot use invalid "transform" with stderr', testInvalidGenerator, 2, {transform: true}, execa); -test('Cannot use invalid "transform" with stdio[*]', testInvalidGenerator, 3, {transform: true}, execa); -test('Cannot use invalid "final" with stdin', testInvalidGenerator, 0, {final: true}, execa); -test('Cannot use invalid "final" with stdout', testInvalidGenerator, 1, {final: true}, execa); -test('Cannot use invalid "final" with stderr', testInvalidGenerator, 2, {final: true}, execa); -test('Cannot use invalid "final" with stdio[*]', testInvalidGenerator, 3, {final: true}, execa); -test('Cannot use invalid "transform" with stdin, sync', testInvalidGenerator, 0, {transform: true}, execaSync); -test('Cannot use invalid "transform" with stdout, sync', testInvalidGenerator, 1, {transform: true}, execaSync); -test('Cannot use invalid "transform" with stderr, sync', testInvalidGenerator, 2, {transform: true}, execaSync); -test('Cannot use invalid "transform" with stdio[*], sync', testInvalidGenerator, 3, {transform: true}, execaSync); -test('Cannot use invalid "final" with stdin, sync', testInvalidGenerator, 0, {final: true}, execaSync); -test('Cannot use invalid "final" with stdout, sync', testInvalidGenerator, 1, {final: true}, execaSync); -test('Cannot use invalid "final" with stderr, sync', testInvalidGenerator, 2, {final: true}, execaSync); -test('Cannot use invalid "final" with stdio[*], sync', testInvalidGenerator, 3, {final: true}, execaSync); - -// eslint-disable-next-line max-params -const testInvalidBinary = (t, fdNumber, optionName, type, execaMethod) => { - t.throws(() => { - execaMethod('empty.js', getStdio(fdNumber, {...generatorsMap[type].uppercase(), [optionName]: 'true'})); - }, {message: /a boolean/}); -}; - -test('Cannot use invalid "binary" with stdin', testInvalidBinary, 0, 'binary', 'generator', execa); -test('Cannot use invalid "binary" with stdout', testInvalidBinary, 1, 'binary', 'generator', execa); -test('Cannot use invalid "binary" with stderr', testInvalidBinary, 2, 'binary', 'generator', execa); -test('Cannot use invalid "binary" with stdio[*]', testInvalidBinary, 3, 'binary', 'generator', execa); -test('Cannot use invalid "objectMode" with stdin, generators', testInvalidBinary, 0, 'objectMode', 'generator', execa); -test('Cannot use invalid "objectMode" with stdout, generators', testInvalidBinary, 1, 'objectMode', 'generator', execa); -test('Cannot use invalid "objectMode" with stderr, generators', testInvalidBinary, 2, 'objectMode', 'generator', execa); -test('Cannot use invalid "objectMode" with stdio[*], generators', testInvalidBinary, 3, 'objectMode', 'generator', execa); -test('Cannot use invalid "binary" with stdin, sync', testInvalidBinary, 0, 'binary', 'generator', execaSync); -test('Cannot use invalid "binary" with stdout, sync', testInvalidBinary, 1, 'binary', 'generator', execaSync); -test('Cannot use invalid "binary" with stderr, sync', testInvalidBinary, 2, 'binary', 'generator', execaSync); -test('Cannot use invalid "binary" with stdio[*], sync', testInvalidBinary, 3, 'binary', 'generator', execaSync); -test('Cannot use invalid "objectMode" with stdin, generators, sync', testInvalidBinary, 0, 'objectMode', 'generator', execaSync); -test('Cannot use invalid "objectMode" with stdout, generators, sync', testInvalidBinary, 1, 'objectMode', 'generator', execaSync); -test('Cannot use invalid "objectMode" with stderr, generators, sync', testInvalidBinary, 2, 'objectMode', 'generator', execaSync); -test('Cannot use invalid "objectMode" with stdio[*], generators, sync', testInvalidBinary, 3, 'objectMode', 'generator', execaSync); -test('Cannot use invalid "objectMode" with stdin, duplexes', testInvalidBinary, 0, 'objectMode', 'duplex', execa); -test('Cannot use invalid "objectMode" with stdout, duplexes', testInvalidBinary, 1, 'objectMode', 'duplex', execa); -test('Cannot use invalid "objectMode" with stderr, duplexes', testInvalidBinary, 2, 'objectMode', 'duplex', execa); -test('Cannot use invalid "objectMode" with stdio[*], duplexes', testInvalidBinary, 3, 'objectMode', 'duplex', execa); -test('Cannot use invalid "objectMode" with stdin, webTransforms', testInvalidBinary, 0, 'objectMode', 'webTransform', execa); -test('Cannot use invalid "objectMode" with stdout, webTransforms', testInvalidBinary, 1, 'objectMode', 'webTransform', execa); -test('Cannot use invalid "objectMode" with stderr, webTransforms', testInvalidBinary, 2, 'objectMode', 'webTransform', execa); -test('Cannot use invalid "objectMode" with stdio[*], webTransforms', testInvalidBinary, 3, 'objectMode', 'webTransform', execa); - // eslint-disable-next-line max-params const testUndefinedOption = (t, fdNumber, optionName, type, optionValue) => { t.throws(() => { diff --git a/test/terminate/kill-signal.js b/test/terminate/kill-signal.js index c3647d6dd6..99937c80c1 100644 --- a/test/terminate/kill-signal.js +++ b/test/terminate/kill-signal.js @@ -1,15 +1,15 @@ import {once} from 'node:events'; -import {platform, version} from 'node:process'; +import {platform} from 'node:process'; import {constants} from 'node:os'; import {setImmediate} from 'node:timers/promises'; import test from 'ava'; import {execa, execaSync} from '../../index.js'; import {setFixtureDirectory} from '../helpers/fixtures-directory.js'; +import {majorNodeVersion} from '../helpers/node-version.js'; setFixtureDirectory(); const isWindows = platform === 'win32'; -const majorNodeVersion = Number(version.split('.')[0].slice(1)); const testKillSignal = async (t, killSignal) => { const {isTerminated, signal} = await t.throwsAsync(execa('forever.js', {killSignal, timeout: 1})); @@ -61,11 +61,10 @@ test('Can call `.kill()` multiple times', async t => { subprocess.kill(); const {exitCode, isTerminated, signal, code} = await t.throwsAsync(subprocess); - // On Windows, calling `subprocess.kill()` twice emits an `error` event on the subprocess. // This does not happen when passing an `error` argument, nor when passing a non-terminating signal. // There is no easy way to make this cross-platform, so we document the difference here. - if (isWindows && majorNodeVersion >= 22) { + if (isWindows && majorNodeVersion === 22) { t.is(exitCode, undefined); t.false(isTerminated); t.is(signal, undefined);