-
-
Notifications
You must be signed in to change notification settings - Fork 214
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
235 additions
and
213 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
const isStream = require('is-stream'); | ||
const getStream = require('get-stream'); | ||
const mergeStream = require('merge-stream'); | ||
|
||
// `input` option | ||
const handleInput = (spawned, input) => { | ||
// Checking for stdin is workaround for https://github.com/nodejs/node/issues/26852 | ||
// TODO: Remove `|| spawned.stdin === undefined` once we drop support for Node.js <=12.2.0 | ||
if (input === undefined || spawned.stdin === undefined) { | ||
return; | ||
} | ||
|
||
if (isStream(input)) { | ||
input.pipe(spawned.stdin); | ||
} else { | ||
spawned.stdin.end(input); | ||
} | ||
}; | ||
|
||
// `all` interleaves `stdout` and `stderr` | ||
const makeAllStream = spawned => { | ||
if (!spawned.stdout && !spawned.stderr) { | ||
return; | ||
} | ||
|
||
const mixed = mergeStream(); | ||
|
||
if (spawned.stdout) { | ||
mixed.add(spawned.stdout); | ||
} | ||
|
||
if (spawned.stderr) { | ||
mixed.add(spawned.stderr); | ||
} | ||
|
||
return mixed; | ||
}; | ||
|
||
// On failure, `result.stdout|stderr|all` should contain the currently buffered stream | ||
const getBufferedData = async (stream, streamPromise) => { | ||
if (!stream) { | ||
return; | ||
} | ||
|
||
stream.destroy(); | ||
|
||
try { | ||
return await streamPromise; | ||
} catch (error) { | ||
return error.bufferedData; | ||
} | ||
}; | ||
|
||
const getStreamPromise = (stream, {encoding, buffer, maxBuffer}) => { | ||
if (!stream) { | ||
return; | ||
} | ||
|
||
if (!buffer) { | ||
// TODO: Use `ret = util.promisify(stream.finished)(stream);` when targeting Node.js 10 | ||
return new Promise((resolve, reject) => { | ||
stream | ||
.once('end', resolve) | ||
.once('error', reject); | ||
}); | ||
} | ||
|
||
if (encoding) { | ||
return getStream(stream, {encoding, maxBuffer}); | ||
} | ||
|
||
return getStream.buffer(stream, {maxBuffer}); | ||
}; | ||
|
||
// Retrieve result of child process: exit code, signal, error, streams (stdout/stderr/all) | ||
const getSpawnedResult = async ({stdout, stderr, all}, {encoding, buffer, maxBuffer}, processDone) => { | ||
const stdoutPromise = getStreamPromise(stdout, {encoding, buffer, maxBuffer}); | ||
const stderrPromise = getStreamPromise(stderr, {encoding, buffer, maxBuffer}); | ||
const allPromise = getStreamPromise(all, {encoding, buffer, maxBuffer: maxBuffer * 2}); | ||
|
||
try { | ||
return await Promise.all([processDone, stdoutPromise, stderrPromise, allPromise]); | ||
} catch (error) { | ||
return Promise.all([ | ||
{error, code: error.code, signal: error.signal}, | ||
getBufferedData(stdout, stdoutPromise), | ||
getBufferedData(stderr, stderrPromise), | ||
getBufferedData(all, allPromise) | ||
]); | ||
} | ||
}; | ||
|
||
const validateInputSync = ({input}) => { | ||
if (isStream(input)) { | ||
throw new TypeError('The `input` option cannot be a stream in sync mode'); | ||
} | ||
}; | ||
|
||
module.exports = { | ||
handleInput, | ||
makeAllStream, | ||
getSpawnedResult, | ||
validateInputSync | ||
}; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import path from 'path'; | ||
import stream from 'stream'; | ||
import test from 'ava'; | ||
import getStream from 'get-stream'; | ||
import execa from '..'; | ||
|
||
process.env.PATH = path.join(__dirname, 'fixtures') + path.delimiter + process.env.PATH; | ||
|
||
test('buffer', async t => { | ||
const {stdout} = await execa('noop', ['foo'], {encoding: null}); | ||
t.true(Buffer.isBuffer(stdout)); | ||
t.is(stdout.toString(), 'foo'); | ||
}); | ||
|
||
test.serial('result.all shows both `stdout` and `stderr` intermixed', async t => { | ||
const {all} = await execa('noop-132'); | ||
t.is(all, '132'); | ||
}); | ||
|
||
test('stdout/stderr/all are undefined if ignored', async t => { | ||
const {stdout, stderr, all} = await execa('noop', {stdio: 'ignore'}); | ||
t.is(stdout, undefined); | ||
t.is(stderr, undefined); | ||
t.is(all, undefined); | ||
}); | ||
|
||
test('stdout/stderr/all are undefined if ignored in sync mode', t => { | ||
const {stdout, stderr, all} = execa.sync('noop', {stdio: 'ignore'}); | ||
t.is(stdout, undefined); | ||
t.is(stderr, undefined); | ||
t.is(all, undefined); | ||
}); | ||
|
||
test('input option can be a String', async t => { | ||
const {stdout} = await execa('stdin', {input: 'foobar'}); | ||
t.is(stdout, 'foobar'); | ||
}); | ||
|
||
test('input option can be a Buffer', async t => { | ||
const {stdout} = await execa('stdin', {input: 'testing12'}); | ||
t.is(stdout, 'testing12'); | ||
}); | ||
|
||
test('input can be a Stream', async t => { | ||
const s = new stream.PassThrough(); | ||
s.write('howdy'); | ||
s.end(); | ||
const {stdout} = await execa('stdin', {input: s}); | ||
t.is(stdout, 'howdy'); | ||
}); | ||
|
||
test('you can write to child.stdin', async t => { | ||
const child = execa('stdin'); | ||
child.stdin.end('unicorns'); | ||
t.is((await child).stdout, 'unicorns'); | ||
}); | ||
|
||
test('input option can be a String - sync', t => { | ||
const {stdout} = execa.sync('stdin', {input: 'foobar'}); | ||
t.is(stdout, 'foobar'); | ||
}); | ||
|
||
test('input option can be a Buffer - sync', t => { | ||
const {stdout} = execa.sync('stdin', {input: Buffer.from('testing12', 'utf8')}); | ||
t.is(stdout, 'testing12'); | ||
}); | ||
|
||
test('opts.stdout:ignore - stdout will not collect data', async t => { | ||
const {stdout} = await execa('stdin', { | ||
input: 'hello', | ||
stdio: [undefined, 'ignore', undefined] | ||
}); | ||
t.is(stdout, undefined); | ||
}); | ||
|
||
test('helpful error trying to provide an input stream in sync mode', t => { | ||
t.throws( | ||
() => { | ||
execa.sync('stdin', {input: new stream.PassThrough()}); | ||
}, | ||
/The `input` option cannot be a stream in sync mode/ | ||
); | ||
}); | ||
|
||
test('maxBuffer affects stdout', async t => { | ||
await t.notThrowsAsync(execa('max-buffer', ['stdout', '10'], {maxBuffer: 10})); | ||
const {stdout, all} = await t.throwsAsync(execa('max-buffer', ['stdout', '11'], {maxBuffer: 10}), /max-buffer stdout/); | ||
t.is(stdout, '.'.repeat(10)); | ||
t.is(all, '.'.repeat(10)); | ||
}); | ||
|
||
test('maxBuffer affects stderr', async t => { | ||
await t.notThrowsAsync(execa('max-buffer', ['stderr', '10'], {maxBuffer: 10})); | ||
const {stderr, all} = await t.throwsAsync(execa('max-buffer', ['stderr', '11'], {maxBuffer: 10}), /max-buffer stderr/); | ||
t.is(stderr, '.'.repeat(10)); | ||
t.is(all, '.'.repeat(10)); | ||
}); | ||
|
||
test('do not buffer stdout when `buffer` set to `false`', async t => { | ||
const promise = execa('max-buffer', ['stdout', '10'], {buffer: false}); | ||
const [result, stdout] = await Promise.all([ | ||
promise, | ||
getStream(promise.stdout), | ||
getStream(promise.all) | ||
]); | ||
|
||
t.is(result.stdout, undefined); | ||
t.is(stdout, '.........\n'); | ||
}); | ||
|
||
test('do not buffer stderr when `buffer` set to `false`', async t => { | ||
const promise = execa('max-buffer', ['stderr', '10'], {buffer: false}); | ||
const [result, stderr] = await Promise.all([ | ||
promise, | ||
getStream(promise.stderr), | ||
getStream(promise.all) | ||
]); | ||
|
||
t.is(result.stderr, undefined); | ||
t.is(stderr, '.........\n'); | ||
}); | ||
|
||
test('do not buffer when streaming', async t => { | ||
const {stdout} = execa('max-buffer', ['stdout', '21'], {maxBuffer: 10}); | ||
const result = await getStream(stdout); | ||
t.is(result, '....................\n'); | ||
}); |
Oops, something went wrong.