Skip to content

Commit

Permalink
Fix using both objectMode and encoding options
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky committed Jan 21, 2024
1 parent 1907e7c commit f29e799
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 21 deletions.
13 changes: 11 additions & 2 deletions lib/stdio/encoding.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
{
Expand All @@ -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) {
Expand All @@ -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.
Expand Down
15 changes: 15 additions & 0 deletions test/helpers/generator.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {setImmediate} from 'node:timers/promises';
import {foobarObject} from './input.js';

export const noopGenerator = objectMode => ({
Expand Down Expand Up @@ -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,
});
33 changes: 14 additions & 19 deletions test/stdio/encoding.js
Original file line number Diff line number Diff line change
@@ -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);

Expand Down Expand Up @@ -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]);
});

0 comments on commit f29e799

Please # to comment.