-
-
Notifications
You must be signed in to change notification settings - Fork 203
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
9 changed files
with
484 additions
and
570 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
export const getStdinOption = stdioOption => ({stdin: stdioOption}); | ||
export const getStdoutOption = stdioOption => ({stdout: stdioOption}); | ||
export const getStderrOption = stdioOption => ({stderr: stdioOption}); | ||
export const getPlainStdioOption = stdioOption => ({stdio: stdioOption}); | ||
export const getInputOption = input => ({input}); | ||
export const getInputFileOption = inputFile => ({inputFile}); | ||
|
||
export const getScriptSync = $ => $.sync; | ||
|
||
export const identity = value => value; |
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,31 @@ | ||
import {readFile, open} from 'node:fs/promises'; | ||
import test from 'ava'; | ||
import tempfile from 'tempfile'; | ||
import {execa, execaSync} from '../../index.js'; | ||
import {setFixtureDir} from '../helpers/fixtures-dir.js'; | ||
import {getStdinOption, getStdoutOption, getStderrOption} from '../helpers/stdio.js'; | ||
|
||
setFixtureDir(); | ||
|
||
const getStdinProp = ({stdin}) => stdin; | ||
|
||
const testFileDescriptorOption = async (t, fixtureName, getOptions, execaMethod) => { | ||
const filePath = tempfile(); | ||
const fileDescriptor = await open(filePath, 'w'); | ||
await execaMethod(fixtureName, ['foobar'], getOptions(fileDescriptor)); | ||
t.is(await readFile(filePath, 'utf8'), 'foobar\n'); | ||
}; | ||
|
||
test('pass `stdout` to a file descriptor', testFileDescriptorOption, 'noop.js', getStdoutOption, execa); | ||
test('pass `stderr` to a file descriptor', testFileDescriptorOption, 'noop-err.js', getStderrOption, execa); | ||
test('pass `stdout` to a file descriptor - sync', testFileDescriptorOption, 'noop.js', getStdoutOption, execaSync); | ||
test('pass `stderr` to a file descriptor - sync', testFileDescriptorOption, 'noop-err.js', getStderrOption, execaSync); | ||
|
||
const testStdinWrite = async (t, getStreamProp, fixtureName, getOptions) => { | ||
const subprocess = execa(fixtureName, getOptions('pipe')); | ||
getStreamProp(subprocess).end('unicorns'); | ||
const {stdout} = await subprocess; | ||
t.is(stdout, 'unicorns'); | ||
}; | ||
|
||
test('you can write to child.stdin', testStdinWrite, getStdinProp, 'stdin.js', getStdinOption); |
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,156 @@ | ||
import {readFile, writeFile} from 'node:fs/promises'; | ||
import {relative, dirname, basename} from 'node:path'; | ||
import process from 'node:process'; | ||
import {pathToFileURL} from 'node:url'; | ||
import test from 'ava'; | ||
import tempfile from 'tempfile'; | ||
import {execa, execaSync, $} from '../../index.js'; | ||
import {setFixtureDir} from '../helpers/fixtures-dir.js'; | ||
import {getStdinOption, getStdoutOption, getStderrOption, getInputFileOption, getScriptSync, identity} from '../helpers/stdio.js'; | ||
|
||
setFixtureDir(); | ||
|
||
const nonFileUrl = new URL('https://example.com'); | ||
|
||
const getRelativePath = filePath => relative('.', filePath); | ||
|
||
const testStdinFile = async (t, mapFilePath, getOptions, execaMethod) => { | ||
const inputPath = tempfile(); | ||
await writeFile(inputPath, 'foobar'); | ||
const {stdout} = await execaMethod('stdin.js', getOptions(mapFilePath(inputPath))); | ||
t.is(stdout, 'foobar'); | ||
}; | ||
|
||
test('inputFile can be a file URL', testStdinFile, pathToFileURL, getInputFileOption, execa); | ||
test('stdin can be a file URL', testStdinFile, pathToFileURL, getStdinOption, execa); | ||
test('inputFile can be an absolute file path', testStdinFile, identity, getInputFileOption, execa); | ||
test('stdin can be an absolute file path', testStdinFile, identity, getStdinOption, execa); | ||
test('inputFile can be a relative file path', testStdinFile, getRelativePath, getInputFileOption, execa); | ||
test('stdin can be a relative file path', testStdinFile, getRelativePath, getStdinOption, execa); | ||
test('inputFile can be a file URL - sync', testStdinFile, pathToFileURL, getInputFileOption, execaSync); | ||
test('stdin can be a file URL - sync', testStdinFile, pathToFileURL, getStdinOption, execaSync); | ||
test('inputFile can be an absolute file path - sync', testStdinFile, identity, getInputFileOption, execaSync); | ||
test('stdin can be an absolute file path - sync', testStdinFile, identity, getStdinOption, execaSync); | ||
test('inputFile can be a relative file path - sync', testStdinFile, getRelativePath, getInputFileOption, execaSync); | ||
test('stdin can be a relative file path - sync', testStdinFile, getRelativePath, getStdinOption, execaSync); | ||
|
||
// eslint-disable-next-line max-params | ||
const testOutputFile = async (t, mapFile, fixtureName, getOptions, execaMethod) => { | ||
const outputFile = tempfile(); | ||
await execaMethod(fixtureName, ['foobar'], getOptions(mapFile(outputFile))); | ||
t.is(await readFile(outputFile, 'utf8'), 'foobar\n'); | ||
}; | ||
|
||
test('stdout can be a file URL', testOutputFile, pathToFileURL, 'noop.js', getStdoutOption, execa); | ||
test('stderr can be a file URL', testOutputFile, pathToFileURL, 'noop-err.js', getStderrOption, execa); | ||
test('stdout can be an absolute file path', testOutputFile, identity, 'noop.js', getStdoutOption, execa); | ||
test('stderr can be an absolute file path', testOutputFile, identity, 'noop-err.js', getStderrOption, execa); | ||
test('stdout can be a relative file path', testOutputFile, getRelativePath, 'noop.js', getStdoutOption, execa); | ||
test('stderr can be a relative file path', testOutputFile, getRelativePath, 'noop-err.js', getStderrOption, execa); | ||
test('stdout can be a file URL - sync', testOutputFile, pathToFileURL, 'noop.js', getStdoutOption, execaSync); | ||
test('stderr can be a file URL - sync', testOutputFile, pathToFileURL, 'noop-err.js', getStderrOption, execaSync); | ||
test('stdout can be an absolute file path - sync', testOutputFile, identity, 'noop.js', getStdoutOption, execaSync); | ||
test('stderr can be an absolute file path - sync', testOutputFile, identity, 'noop-err.js', getStderrOption, execaSync); | ||
test('stdout can be a relative file path - sync', testOutputFile, getRelativePath, 'noop.js', getStdoutOption, execaSync); | ||
test('stderr can be a relative file path - sync', testOutputFile, getRelativePath, 'noop-err.js', getStderrOption, execaSync); | ||
|
||
const testStdioNonFileUrl = (t, getOptions, execaMethod) => { | ||
t.throws(() => { | ||
execaMethod('noop.js', getOptions(nonFileUrl)); | ||
}, {message: /pathToFileURL/}); | ||
}; | ||
|
||
test('inputFile cannot be a non-file URL', testStdioNonFileUrl, getInputFileOption, execa); | ||
test('stdin cannot be a non-file URL', testStdioNonFileUrl, getStdinOption, execa); | ||
test('stdout cannot be a non-file URL', testStdioNonFileUrl, getStdoutOption, execa); | ||
test('stderr cannot be a non-file URL', testStdioNonFileUrl, getStderrOption, execa); | ||
test('inputFile cannot be a non-file URL - sync', testStdioNonFileUrl, getInputFileOption, execaSync); | ||
test('stdin cannot be a non-file URL - sync', testStdioNonFileUrl, getStdinOption, execaSync); | ||
test('stdout cannot be a non-file URL - sync', testStdioNonFileUrl, getStdoutOption, execaSync); | ||
test('stderr cannot be a non-file URL - sync', testStdioNonFileUrl, getStderrOption, execaSync); | ||
|
||
const testInputFileValidUrl = async (t, execaMethod) => { | ||
const inputPath = tempfile(); | ||
await writeFile(inputPath, 'foobar'); | ||
const currentCwd = process.cwd(); | ||
process.chdir(dirname(inputPath)); | ||
try { | ||
const {stdout} = await execaMethod('stdin.js', {inputFile: basename(inputPath)}); | ||
t.is(stdout, 'foobar'); | ||
} finally { | ||
process.chdir(currentCwd); | ||
} | ||
}; | ||
|
||
test('inputFile does not need to start with . when being a relative file path', testInputFileValidUrl, execa); | ||
test('inputFile does not need to start with . when being a relative file path - sync', testInputFileValidUrl, execaSync); | ||
|
||
const testStdioValidUrl = (t, getOptions, execaMethod) => { | ||
t.throws(() => { | ||
execaMethod('noop.js', getOptions('foobar')); | ||
}, {message: /absolute file path/}); | ||
}; | ||
|
||
test('stdin must start with . when being a relative file path', testStdioValidUrl, getStdinOption, execa); | ||
test('stdout must start with . when being a relative file path', testStdioValidUrl, getStdoutOption, execa); | ||
test('stderr must start with . when being a relative file path', testStdioValidUrl, getStderrOption, execa); | ||
test('stdin must start with . when being a relative file path - sync', testStdioValidUrl, getStdinOption, execaSync); | ||
test('stdout must start with . when being a relative file path - sync', testStdioValidUrl, getStdoutOption, execaSync); | ||
test('stderr must start with . when being a relative file path - sync', testStdioValidUrl, getStderrOption, execaSync); | ||
|
||
const testFileError = async (t, mapFile, getOptions) => { | ||
await t.throwsAsync( | ||
execa('noop.js', getOptions(mapFile('./unknown/file'))), | ||
{code: 'ENOENT'}, | ||
); | ||
}; | ||
|
||
test('inputFile file URL errors should be handled', testFileError, pathToFileURL, getInputFileOption); | ||
test('stdin file URL errors should be handled', testFileError, pathToFileURL, getStdinOption); | ||
test('stdout file URL errors should be handled', testFileError, pathToFileURL, getStdoutOption); | ||
test('stderr file URL errors should be handled', testFileError, pathToFileURL, getStderrOption); | ||
test('inputFile file path errors should be handled', testFileError, identity, getInputFileOption); | ||
test('stdin file path errors should be handled', testFileError, identity, getStdinOption); | ||
test('stdout file path errors should be handled', testFileError, identity, getStdoutOption); | ||
test('stderr file path errors should be handled', testFileError, identity, getStderrOption); | ||
|
||
const testFileErrorSync = (t, mapFile, getOptions) => { | ||
t.throws(() => { | ||
execaSync('noop.js', getOptions(mapFile('./unknown/file'))); | ||
}, {code: 'ENOENT'}); | ||
}; | ||
|
||
test('inputFile file URL errors should be handled - sync', testFileErrorSync, pathToFileURL, getInputFileOption); | ||
test('stdin file URL errors should be handled - sync', testFileErrorSync, pathToFileURL, getStdinOption); | ||
test('stdout file URL errors should be handled - sync', testFileErrorSync, pathToFileURL, getStdoutOption); | ||
test('stderr file URL errors should be handled - sync', testFileErrorSync, pathToFileURL, getStderrOption); | ||
test('inputFile file path errors should be handled - sync', testFileErrorSync, identity, getInputFileOption); | ||
test('stdin file path errors should be handled - sync', testFileErrorSync, identity, getStdinOption); | ||
test('stdout file path errors should be handled - sync', testFileErrorSync, identity, getStdoutOption); | ||
test('stderr file path errors should be handled - sync', testFileErrorSync, identity, getStderrOption); | ||
|
||
const testInputFile = async (t, execaMethod) => { | ||
const inputFile = tempfile(); | ||
await writeFile(inputFile, 'foobar'); | ||
const {stdout} = await execaMethod('stdin.js', {inputFile}); | ||
t.is(stdout, 'foobar'); | ||
}; | ||
|
||
test('inputFile can be set', testInputFile, execa); | ||
test('inputFile can be set - sync', testInputFile, execa); | ||
|
||
const testInputFileScript = async (t, getExecaMethod) => { | ||
const inputFile = tempfile(); | ||
await writeFile(inputFile, 'foobar'); | ||
const {stdout} = await getExecaMethod($({inputFile}))`stdin.js`; | ||
t.is(stdout, 'foobar'); | ||
}; | ||
|
||
test('inputFile can be set with $', testInputFileScript, identity); | ||
test('inputFile can be set with $.sync', testInputFileScript, getScriptSync); | ||
|
||
test('inputFile option cannot be set when stdin is set', t => { | ||
t.throws(() => { | ||
execa('stdin.js', {inputFile: '', stdin: 'ignore'}); | ||
}, {message: /`inputFile` and `stdin` options/}); | ||
}); |
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,64 @@ | ||
import {Buffer} from 'node:buffer'; | ||
import {Readable} from 'node:stream'; | ||
import {pathToFileURL} from 'node:url'; | ||
import test from 'ava'; | ||
import {execa, execaSync, $} from '../../index.js'; | ||
import {setFixtureDir} from '../helpers/fixtures-dir.js'; | ||
import {getStdinOption, getPlainStdioOption, getScriptSync, identity} from '../helpers/stdio.js'; | ||
|
||
setFixtureDir(); | ||
|
||
const testInputOptionError = (t, stdin, inputName) => { | ||
t.throws(() => { | ||
execa('stdin.js', {stdin, [inputName]: 'foobar'}); | ||
}, {message: new RegExp(`\`${inputName}\` and \`stdin\` options`)}); | ||
}; | ||
|
||
test('stdin option cannot be an iterable when "input" is used', testInputOptionError, ['foo', 'bar'], 'input'); | ||
test('stdin option cannot be an iterable when "inputFile" is used', testInputOptionError, ['foo', 'bar'], 'inputFile'); | ||
test('stdin option cannot be a file URL when "input" is used', testInputOptionError, pathToFileURL('unknown'), 'input'); | ||
test('stdin option cannot be a file URL when "inputFile" is used', testInputOptionError, pathToFileURL('unknown'), 'inputFile'); | ||
test('stdin option cannot be a file path when "input" is used', testInputOptionError, './unknown', 'input'); | ||
test('stdin option cannot be a file path when "inputFile" is used', testInputOptionError, './unknown', 'inputFile'); | ||
test('stdin option cannot be a ReadableStream when "input" is used', testInputOptionError, new ReadableStream(), 'input'); | ||
test('stdin option cannot be a ReadableStream when "inputFile" is used', testInputOptionError, new ReadableStream(), 'inputFile'); | ||
|
||
const testInput = async (t, input, execaMethod) => { | ||
const {stdout} = await execaMethod('stdin.js', {input}); | ||
t.is(stdout, 'foobar'); | ||
}; | ||
|
||
test('input option can be a String', testInput, 'foobar', execa); | ||
test('input option can be a String - sync', testInput, 'foobar', execaSync); | ||
test('input option can be a Buffer', testInput, Buffer.from('foobar'), execa); | ||
test('input option can be a Buffer - sync', testInput, Buffer.from('foobar'), execaSync); | ||
|
||
const testInputScript = async (t, getExecaMethod) => { | ||
const {stdout} = await getExecaMethod($({input: 'foobar'}))`stdin.js`; | ||
t.is(stdout, 'foobar'); | ||
}; | ||
|
||
test('input option can be used with $', testInputScript, identity); | ||
test('input option can be used with $.sync', testInputScript, getScriptSync); | ||
|
||
const testInputWithStdinError = (t, input, getOptions, execaMethod) => { | ||
t.throws(() => { | ||
execaMethod('stdin.js', {input, ...getOptions('ignore')}); | ||
}, {message: /`input` and `stdin` options/}); | ||
}; | ||
|
||
test('input option cannot be a String when stdin is set', testInputWithStdinError, 'foobar', getStdinOption, execa); | ||
test('input option cannot be a String when stdio is set', testInputWithStdinError, 'foobar', getPlainStdioOption, execa); | ||
test('input option cannot be a String when stdin is set - sync', testInputWithStdinError, 'foobar', getStdinOption, execaSync); | ||
test('input option cannot be a String when stdio is set - sync', testInputWithStdinError, 'foobar', getPlainStdioOption, execaSync); | ||
test('input option cannot be a Node.js Readable when stdin is set', testInputWithStdinError, new Readable(), getStdinOption, execa); | ||
test('input option cannot be a Node.js Readable when stdio is set', testInputWithStdinError, new Readable(), getPlainStdioOption, execa); | ||
|
||
const testInputAndInputFile = async (t, execaMethod) => { | ||
t.throws(() => execaMethod('stdin.js', {inputFile: '', input: ''}), { | ||
message: /cannot be both set/, | ||
}); | ||
}; | ||
|
||
test('inputFile and input cannot be both set', testInputAndInputFile, execa); | ||
test('inputFile and input cannot be both set - sync', testInputAndInputFile, execaSync); |
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,60 @@ | ||
import test from 'ava'; | ||
import {execa, execaSync} from '../../index.js'; | ||
import {setFixtureDir} from '../helpers/fixtures-dir.js'; | ||
import {getStdinOption, getStdoutOption, getStderrOption} from '../helpers/stdio.js'; | ||
|
||
setFixtureDir(); | ||
|
||
const textEncoder = new TextEncoder(); | ||
const binaryFoo = textEncoder.encode('foo'); | ||
const binaryBar = textEncoder.encode('bar'); | ||
|
||
const stringGenerator = function * () { | ||
yield * ['foo', 'bar']; | ||
}; | ||
|
||
const binaryGenerator = function * () { | ||
yield * [binaryFoo, binaryBar]; | ||
}; | ||
|
||
// eslint-disable-next-line require-yield | ||
const throwingGenerator = function * () { | ||
throw new Error('generator error'); | ||
}; | ||
|
||
const testIterable = async (t, stdioOption, fixtureName, getOptions) => { | ||
const {stdout} = await execa(fixtureName, getOptions(stdioOption)); | ||
t.is(stdout, 'foobar'); | ||
}; | ||
|
||
test('stdin option can be a sync iterable of strings', testIterable, ['foo', 'bar'], 'stdin.js', getStdinOption); | ||
test('stdin option can be a sync iterable of Uint8Arrays', testIterable, [binaryFoo, binaryBar], 'stdin.js', getStdinOption); | ||
test('stdin option can be an sync iterable of strings', testIterable, stringGenerator(), 'stdin.js', getStdinOption); | ||
test('stdin option can be an sync iterable of Uint8Arrays', testIterable, binaryGenerator(), 'stdin.js', getStdinOption); | ||
|
||
const testIterableSync = (t, stdioOption, fixtureName, getOptions) => { | ||
t.throws(() => { | ||
execaSync(fixtureName, getOptions(stdioOption)); | ||
}, {message: /an iterable in sync mode/}); | ||
}; | ||
|
||
test('stdin option cannot be a sync iterable - sync', testIterableSync, ['foo', 'bar'], 'stdin.js', getStdinOption); | ||
test('stdin option cannot be an async iterable - sync', testIterableSync, stringGenerator(), 'stdin.js', getStdinOption); | ||
|
||
const testIterableError = async (t, fixtureName, getOptions) => { | ||
const {originalMessage} = await t.throwsAsync(execa(fixtureName, getOptions(throwingGenerator()))); | ||
t.is(originalMessage, 'generator error'); | ||
}; | ||
|
||
test('stdin option handles errors in iterables', testIterableError, 'stdin.js', getStdinOption); | ||
|
||
const testNoIterableOutput = (t, getOptions, execaMethod) => { | ||
t.throws(() => { | ||
execaMethod('noop.js', getOptions(['foo', 'bar'])); | ||
}, {message: /cannot be an iterable/}); | ||
}; | ||
|
||
test('stdout option cannot be an iterable', testNoIterableOutput, getStdoutOption, execa); | ||
test('stderr option cannot be an iterable', testNoIterableOutput, getStderrOption, execa); | ||
test('stdout option cannot be an iterable - sync', testNoIterableOutput, getStdoutOption, execaSync); | ||
test('stderr option cannot be an iterable - sync', testNoIterableOutput, getStderrOption, execaSync); |
Oops, something went wrong.