diff --git a/source/context.js b/source/context.js index 75c9a50..c6ca379 100644 --- a/source/context.js +++ b/source/context.js @@ -4,7 +4,9 @@ import {stripVTControlCharacters} from 'node:util'; export const getContext = raw => ({ start: process.hrtime.bigint(), command: raw.map(part => getCommandPart(stripVTControlCharacters(part))).join(' '), - state: {stdout: '', stderr: '', output: ''}, + state: { + stdout: '', stderr: '', output: '', isIterating: {}, + }, }); const getCommandPart = part => /[^\w./-]/.test(part) diff --git a/source/iterable.js b/source/iterable.js index dc4ba83..76005ad 100644 --- a/source/iterable.js +++ b/source/iterable.js @@ -3,12 +3,12 @@ import * as readline from 'node:readline/promises'; export const lineIterator = async function * (subprocess, {state}, streamName) { // Prevent buffering when iterating. // This would defeat one of the main goals of iterating: low memory consumption. - if (state.isIterating === false) { + if (state.isIterating[streamName] === false) { throw new Error(`The subprocess must be iterated right away, for example: for await (const line of spawn(...)) { ... }`); } - state.isIterating = true; + state.isIterating[streamName] = true; try { const {[streamName]: stream} = await subprocess.nodeChildProcess; diff --git a/source/spawn.js b/source/spawn.js index 0018a55..8b28a42 100644 --- a/source/spawn.js +++ b/source/spawn.js @@ -48,8 +48,8 @@ const concatenateShell = (file, commandArguments, options) => options.shell && c const bufferOutput = (stream, {state}, streamName) => { if (stream) { stream.setEncoding('utf8'); - if (!state.isIterating) { - state.isIterating = false; + if (!state.isIterating[streamName]) { + state.isIterating[streamName] = false; stream.on('data', chunk => { state[streamName] += chunk; state.output += chunk; diff --git a/test/iterable.js b/test/iterable.js index 3880d6e..06235ca 100644 --- a/test/iterable.js +++ b/test/iterable.js @@ -139,8 +139,8 @@ test('subprocess.stdout has no iterations but waits for the subprocess if option const promiseError = await t.throwsAsync(subprocess); t.is(promiseError, error); t.is(promiseError.stdout, ''); - t.is(promiseError.stderr, ''); - t.is(promiseError.output, ''); + t.is(promiseError.stderr, secondTestString); + t.is(promiseError.output, secondTestString); }); const testIterationLate = async (t, iterableType) => { @@ -193,8 +193,8 @@ test.serial('subprocess.stdout works with multibyte sequences', async t => { t.is(output, ''); }); -const testStreamIterateError = async (t, streamName) => { - const subprocess = spawn(...nodePrintStdout); +const testStreamIterateError = async (t, streamName, fixture) => { + const subprocess = spawn(...fixture); const cause = new Error(testString); destroySubprocessStream(subprocess, cause, streamName); const error = await t.throwsAsync(arrayFromAsync(subprocess[streamName])); @@ -205,8 +205,8 @@ const testStreamIterateError = async (t, streamName) => { t.is(promiseError.output, ''); }; -test('Handles subprocess.stdout error', testStreamIterateError, 'stdout'); -test('Handles subprocess.stderr error', testStreamIterateError, 'stderr'); +test('Handles subprocess.stdout error', testStreamIterateError, 'stdout', nodePrintStdout); +test('Handles subprocess.stderr error', testStreamIterateError, 'stderr', nodePrintStderr); const testStreamIterateAllError = async (t, streamName) => { const subprocess = spawn(...nodePrintStdout); @@ -309,3 +309,17 @@ test.serial('subprocess.stdout iteration break waits for the subprocess failure' test.serial('subprocess[Symbol.asyncIterator] iteration break waits for the subprocess failure', testIterationFail, false, ''); test.serial('subprocess.stdout iteration exception waits for the subprocess failure', testIterationFail, true, 'stdout'); test.serial('subprocess[Symbol.asyncIterator] iteration exception waits for the subprocess failure', testIterationFail, true, ''); + +// eslint-disable-next-line max-params +const testPartialIteration = async (t, streamName, otherStreamName, expectedLines, expectedOutput) => { + const subprocess = spawn(...nodePrintBoth); + const lines = await arrayFromAsync(subprocess[streamName]); + t.deepEqual(lines, [expectedLines]); + const result = await subprocess; + t.is(result[streamName], ''); + t.is(result[otherStreamName], expectedOutput); + t.is(result.output, expectedOutput); +}; + +test('subprocess.stderr is still buffered when subprocess.stdout is iterated', testPartialIteration, 'stdout', 'stderr', testString, secondTestString); +test('subprocess.stdout is still buffered when subprocess.stderr is iterated', testPartialIteration, 'stderr', 'stdout', secondTestString, testString); diff --git a/test/pipe.js b/test/pipe.js index ef53676..1da008b 100644 --- a/test/pipe.js +++ b/test/pipe.js @@ -334,6 +334,14 @@ test('.pipe() + stdout iteration, destination fail', async t => { }); test('.pipe() with EPIPE', async t => { + // Skip this test if the current OS is lacking the Unix utility `head` + try { + await spawn('head', ['--version']); + } catch { + t.pass(); + return; + } + const subprocess = spawn(...nodeEval(`setInterval(() => { console.log("${testString}"); }, 0);