From f29e7994d2d874253773982b0e7c9e1f83bfd9ba Mon Sep 17 00:00:00 2001 From: ehmicky Date: Sun, 21 Jan 2024 14:10:47 +0000 Subject: [PATCH] Fix using both `objectMode` and `encoding` options --- lib/stdio/encoding.js | 13 +++++++++++-- test/helpers/generator.js | 15 +++++++++++++++ test/stdio/encoding.js | 33 ++++++++++++++------------------- 3 files changed, 40 insertions(+), 21 deletions(-) diff --git a/lib/stdio/encoding.js b/lib/stdio/encoding.js index 19012adad3..2fc7c310a9 100644 --- a/lib/stdio/encoding.js +++ b/lib/stdio/encoding.js @@ -10,8 +10,9 @@ export const handleStreamsEncoding = (stdioStreams, {encoding}, isSync) => { return newStdioStreams; } - const transform = encodingEndGenerator.bind(undefined, encoding); const objectMode = newStdioStreams.findLast(({type}) => type === 'generator')?.value.objectMode === true; + const encodingEndGenerator = objectMode ? encodingEndObjectGenerator : encodingEndStringGenerator; + const transform = encodingEndGenerator.bind(undefined, encoding); return [ ...newStdioStreams, { @@ -26,7 +27,7 @@ export const handleStreamsEncoding = (stdioStreams, {encoding}, isSync) => { // eslint-disable-next-line unicorn/text-encoding-identifier-case const IGNORED_ENCODINGS = new Set(['utf8', 'utf-8', 'buffer']); -const encodingEndGenerator = async function * (encoding, chunks) { +const encodingEndStringGenerator = async function * (encoding, chunks) { const stringDecoder = new StringDecoder(encoding); for await (const chunk of chunks) { @@ -39,6 +40,14 @@ const encodingEndGenerator = async function * (encoding, chunks) { } }; +const encodingEndObjectGenerator = async function * (encoding, chunks) { + const stringDecoder = new StringDecoder(encoding); + + for await (const chunk of chunks) { + yield isUint8Array(chunk) ? stringDecoder.end(chunk) : chunk; + } +}; + /* When using generators, add an internal generator that converts chunks from `Buffer` to `string` or `Uint8Array`. This allows generator functions to operate with those types instead. diff --git a/test/helpers/generator.js b/test/helpers/generator.js index 7dc5adfccc..5a722ab903 100644 --- a/test/helpers/generator.js +++ b/test/helpers/generator.js @@ -1,3 +1,4 @@ +import {setImmediate} from 'node:timers/promises'; import {foobarObject} from './input.js'; export const noopGenerator = objectMode => ({ @@ -37,3 +38,17 @@ export const getOutputGenerator = (input, objectMode) => ({ }); export const outputObjectGenerator = getOutputGenerator(foobarObject, true); + +export const getChunksGenerator = (chunks, objectMode) => ({ + async * transform(lines) { + // eslint-disable-next-line no-unused-vars + for await (const line of lines) { + for (const chunk of chunks) { + yield chunk; + // eslint-disable-next-line no-await-in-loop + await setImmediate(); + } + } + }, + objectMode, +}); diff --git a/test/stdio/encoding.js b/test/stdio/encoding.js index b00fea9038..1992de6cac 100644 --- a/test/stdio/encoding.js +++ b/test/stdio/encoding.js @@ -1,12 +1,13 @@ import {Buffer} from 'node:buffer'; import {exec} from 'node:child_process'; -import {setImmediate} from 'node:timers/promises'; import {promisify} from 'node:util'; import test from 'ava'; import getStream, {getStreamAsBuffer} from 'get-stream'; import {execa, execaSync} from '../../index.js'; import {setFixtureDir, FIXTURES_DIR} from '../helpers/fixtures-dir.js'; import {fullStdio} from '../helpers/stdio.js'; +import {outputObjectGenerator, getChunksGenerator} from '../helpers/generator.js'; +import {foobarObject} from '../helpers/input.js'; const pExec = promisify(exec); @@ -132,23 +133,17 @@ test('validate unknown encodings', async t => { const foobarArray = ['fo', 'ob', 'ar', '..']; -const delayedGenerator = async function * (lines) { - // eslint-disable-next-line no-unused-vars - for await (const line of lines) { - yield foobarArray[0]; - await setImmediate(); - yield foobarArray[1]; - await setImmediate(); - yield foobarArray[2]; - await setImmediate(); - yield foobarArray[3]; - } -}; +test('Handle multibyte characters', async t => { + const {stdout} = await execa('noop.js', {stdout: getChunksGenerator(foobarArray, false), encoding: 'base64'}); + t.is(stdout, btoa(foobarArray.join(''))); +}); -const testMultiByteCharacter = async (t, objectMode) => { - const {stdout} = await execa('noop.js', {stdout: {transform: delayedGenerator, objectMode}, encoding: 'base64'}); - t.is(objectMode ? stdout.join('') : stdout, btoa(foobarArray.join(''))); -}; +test('Handle multibyte characters, with objectMode', async t => { + const {stdout} = await execa('noop.js', {stdout: getChunksGenerator(foobarArray, true), encoding: 'base64'}); + t.deepEqual(stdout, foobarArray.map(chunk => btoa(chunk))); +}); -test('Handle multibyte characters', testMultiByteCharacter, false); -test('Handle multibyte characters, with objectMode', testMultiByteCharacter, true); +test('Other encodings work with transforms that return objects', async t => { + const {stdout} = await execa('noop.js', {stdout: outputObjectGenerator, encoding: 'base64'}); + t.deepEqual(stdout, [foobarObject]); +});