(https://sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: package.json
================================================
{
"name": "execa",
"version": "9.6.1",
"description": "Process execution for humans",
"license": "MIT",
"repository": "sindresorhus/execa",
"funding": "https://github.com/sindresorhus/execa?sponsor=1",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "https://sindresorhus.com"
},
"type": "module",
"exports": {
"types": "./index.d.ts",
"default": "./index.js"
},
"sideEffects": false,
"engines": {
"node": "^18.19.0 || >=20.5.0"
},
"scripts": {
"test": "npm run lint && npm run unit && npm run type",
"lint": "xo",
"unit": "c8 --merge-async ava",
"type": "tsd && tsc && npx --yes tsd@0.29.0 && npx --yes --package typescript@5.1 tsc"
},
"files": [
"index.js",
"index.d.ts",
"lib/**/*.js",
"types/**/*.ts"
],
"keywords": [
"exec",
"child",
"process",
"subprocess",
"execute",
"fork",
"execfile",
"spawn",
"file",
"shell",
"bin",
"binary",
"binaries",
"npm",
"path",
"local",
"zx"
],
"dependencies": {
"@sindresorhus/merge-streams": "^4.0.0",
"cross-spawn": "^7.0.6",
"figures": "^6.1.0",
"get-stream": "^9.0.0",
"human-signals": "^8.0.1",
"is-plain-obj": "^4.1.0",
"is-stream": "^4.0.1",
"npm-run-path": "^6.0.0",
"pretty-ms": "^9.2.0",
"signal-exit": "^4.1.0",
"strip-final-newline": "^4.0.0",
"yoctocolors": "^2.1.1"
},
"devDependencies": {
"@types/node": "^22.15.21",
"ava": "^6.3.0",
"c8": "^10.1.3",
"get-node": "^15.0.3",
"is-in-ci": "^1.0.0",
"is-running": "^2.1.0",
"log-process-errors": "^12.0.1",
"path-exists": "^5.0.0",
"path-key": "^4.0.0",
"tempfile": "^5.0.0",
"tsd": "^0.32.0",
"typescript": "^5.8.3",
"which": "^5.0.0",
"xo": "^0.60.0"
},
"c8": {
"reporter": [
"text",
"lcov"
],
"exclude": [
"**/fixtures/**",
"**/test.js",
"**/test/**"
]
},
"ava": {
"workerThreads": false,
"concurrency": 1,
"timeout": "240s"
},
"xo": {
"rules": {
"unicorn/no-empty-file": "off",
"@typescript-eslint/ban-types": "off"
}
}
}
================================================
FILE: readme.md
================================================
[](https://codecov.io/gh/sindresorhus/execa)
> Process execution for humans
---
---
Execa runs commands in your script, application or library. Unlike shells, it is [optimized](docs/bash.md) for programmatic usage. Built on top of the [`child_process`](https://nodejs.org/api/child_process.html) core module.
## Features
- [Simple syntax](#simple-syntax): promises and [template strings](docs/execution.md#template-string-syntax), like [`zx`](docs/bash.md).
- [Script](#script) interface.
- [No escaping](docs/escaping.md) nor quoting needed. No risk of shell injection.
- Execute [locally installed binaries](#local-binaries) without `npx`.
- Improved [Windows support](docs/windows.md): [shebangs](docs/windows.md#shebang), [`PATHEXT`](https://ss64.com/nt/path.html#pathext), [graceful termination](#graceful-termination), [and more](https://github.com/moxystudio/node-cross-spawn?tab=readme-ov-file#why).
- [Detailed errors](#detailed-error), [verbose mode](#verbose-mode) and [custom logging](#custom-logging), for [debugging](docs/debugging.md).
- [Pipe multiple subprocesses](#pipe-multiple-subprocesses) better than in shells: retrieve [intermediate results](docs/pipe.md#result), use multiple [sources](docs/pipe.md#multiple-sources-1-destination)/[destinations](docs/pipe.md#1-source-multiple-destinations), [unpipe](docs/pipe.md#unpipe).
- [Split](#split-into-text-lines) the output into text lines, or [iterate](#iterate-over-text-lines) progressively over them.
- Strip [unnecessary newlines](docs/lines.md#newlines).
- Pass any [input](docs/input.md) to the subprocess: [files](#file-input), [strings](#simple-input), [`Uint8Array`s](docs/binary.md#binary-input), [iterables](docs/streams.md#iterables-as-input), [objects](docs/transform.md#object-mode) and almost any [other type](#any-input-type).
- Return [almost any type](#any-output-type) from the subprocess, or redirect it to [files](#file-output).
- Get [interleaved output](#interleaved-output) from `stdout` and `stderr` similar to what is printed on the terminal.
- Retrieve the output [programmatically and print it](#programmatic--terminal-output) on the console at the same time.
- [Transform or filter](#transformfilter-output) the input and output with [simple functions](docs/transform.md).
- Pass [Node.js streams](docs/streams.md#nodejs-streams) or [web streams](#web-streams) to subprocesses, or [convert](#convert-to-duplex-stream) subprocesses to [a stream](docs/streams.md#converting-a-subprocess-to-a-stream).
- [Exchange messages](#exchange-messages) with the subprocess.
- Ensure subprocesses exit even when they [intercept termination signals](docs/termination.md#forceful-termination), or when the current process [ends abruptly](docs/termination.md#current-process-exit).
## Install
```sh
npm install execa
```
## Documentation
Execution:
- ▶️ [Basic execution](docs/execution.md)
- 💬 [Escaping/quoting](docs/escaping.md)
- 💻 [Shell](docs/shell.md)
- 📜 [Scripts](docs/scripts.md)
- 🐢 [Node.js files](docs/node.md)
- 🌐 [Environment](docs/environment.md)
- ❌ [Errors](docs/errors.md)
- 🏁 [Termination](docs/termination.md)
Input/output:
- 🎹 [Input](docs/input.md)
- 📢 [Output](docs/output.md)
- 📃 [Text lines](docs/lines.md)
- 🤖 [Binary data](docs/binary.md)
- 🧙 [Transforms](docs/transform.md)
Advanced usage:
- 🔀 [Piping multiple subprocesses](docs/pipe.md)
- ⏳️ [Streams](docs/streams.md)
- 📞 [Inter-process communication](docs/ipc.md)
- 🐛 [Debugging](docs/debugging.md)
- 📎 [Windows](docs/windows.md)
- 🔍 [Difference with Bash and zx](docs/bash.md)
- 🐭 [Small packages](docs/small.md)
- 🤓 [TypeScript](docs/typescript.md)
- 📔 [API reference](docs/api.md)
## Examples
### Execution
#### Simple syntax
```js
import {execa} from 'execa';
const {stdout} = await execa`npm run build`;
// Print command's output
console.log(stdout);
```
#### Script
```js
import {$} from 'execa';
const {stdout: name} = await $`cat package.json`.pipe`grep name`;
console.log(name);
const branch = await $`git branch --show-current`;
await $`dep deploy --branch=${branch}`;
await Promise.all([
$`sleep 1`,
$`sleep 2`,
$`sleep 3`,
]);
const directoryName = 'foo bar';
await $`mkdir /tmp/${directoryName}`;
```
#### Local binaries
```sh
$ npm install -D eslint
```
```js
await execa({preferLocal: true})`eslint`;
```
#### Pipe multiple subprocesses
```js
const {stdout, pipedFrom} = await execa`npm run build`
.pipe`sort`
.pipe`head -n 2`;
// Output of `npm run build | sort | head -n 2`
console.log(stdout);
// Output of `npm run build | sort`
console.log(pipedFrom[0].stdout);
// Output of `npm run build`
console.log(pipedFrom[0].pipedFrom[0].stdout);
```
### Input/output
#### Interleaved output
```js
const {all} = await execa({all: true})`npm run build`;
// stdout + stderr, interleaved
console.log(all);
```
#### Programmatic + terminal output
```js
const {stdout} = await execa({stdout: ['pipe', 'inherit']})`npm run build`;
// stdout is also printed to the terminal
console.log(stdout);
```
#### Simple input
```js
const getInputString = () => { /* ... */ };
const {stdout} = await execa({input: getInputString()})`sort`;
console.log(stdout);
```
#### File input
```js
// Similar to: npm run build < input.txt
await execa({stdin: {file: 'input.txt'}})`npm run build`;
```
#### File output
```js
// Similar to: npm run build > output.txt
await execa({stdout: {file: 'output.txt'}})`npm run build`;
```
#### Split into text lines
```js
const {stdout} = await execa({lines: true})`npm run build`;
// Print first 10 lines
console.log(stdout.slice(0, 10).join('\n'));
```
### Streaming
#### Iterate over text lines
```js
for await (const line of execa`npm run build`) {
if (line.includes('WARN')) {
console.warn(line);
}
}
```
#### Transform/filter output
```js
let count = 0;
// Filter out secret lines, then prepend the line number
const transform = function * (line) {
if (!line.includes('secret')) {
yield `[${count++}] ${line}`;
}
};
await execa({stdout: transform})`npm run build`;
```
#### Web streams
```js
const response = await fetch('https://example.com');
await execa({stdin: response.body})`sort`;
```
#### Convert to Duplex stream
```js
import {execa} from 'execa';
import {pipeline} from 'node:stream/promises';
import {createReadStream, createWriteStream} from 'node:fs';
await pipeline(
createReadStream('./input.txt'),
execa`node ./transform.js`.duplex(),
createWriteStream('./output.txt'),
);
```
### IPC
#### Exchange messages
```js
// parent.js
import {execaNode} from 'execa';
const subprocess = execaNode`child.js`;
await subprocess.sendMessage('Hello from parent');
const message = await subprocess.getOneMessage();
console.log(message); // 'Hello from child'
```
```js
// child.js
import {getOneMessage, sendMessage} from 'execa';
const message = await getOneMessage(); // 'Hello from parent'
const newMessage = message.replace('parent', 'child'); // 'Hello from child'
await sendMessage(newMessage);
```
#### Any input type
```js
// main.js
import {execaNode} from 'execa';
const ipcInput = [
{task: 'lint', ignore: /test\.js/},
{task: 'copy', files: new Set(['main.js', 'index.js']),
}];
await execaNode({ipcInput})`build.js`;
```
```js
// build.js
import {getOneMessage} from 'execa';
const ipcInput = await getOneMessage();
```
#### Any output type
```js
// main.js
import {execaNode} from 'execa';
const {ipcOutput} = await execaNode`build.js`;
console.log(ipcOutput[0]); // {kind: 'start', timestamp: date}
console.log(ipcOutput[1]); // {kind: 'stop', timestamp: date}
```
```js
// build.js
import {sendMessage} from 'execa';
const runBuild = () => { /* ... */ };
await sendMessage({kind: 'start', timestamp: new Date()});
await runBuild();
await sendMessage({kind: 'stop', timestamp: new Date()});
```
#### Graceful termination
```js
// main.js
import {execaNode} from 'execa';
const controller = new AbortController();
setTimeout(() => {
controller.abort();
}, 5000);
await execaNode({
cancelSignal: controller.signal,
gracefulCancel: true,
})`build.js`;
```
```js
// build.js
import {getCancelSignal} from 'execa';
const cancelSignal = await getCancelSignal();
const url = 'https://example.com/build/info';
const response = await fetch(url, {signal: cancelSignal});
```
### Debugging
#### Detailed error
```js
import {execa, ExecaError} from 'execa';
try {
await execa`unknown command`;
} catch (error) {
if (error instanceof ExecaError) {
console.log(error);
}
/*
ExecaError: Command failed with ENOENT: unknown command
spawn unknown ENOENT
at ...
at ... {
shortMessage: 'Command failed with ENOENT: unknown command\nspawn unknown ENOENT',
originalMessage: 'spawn unknown ENOENT',
command: 'unknown command',
escapedCommand: 'unknown command',
cwd: '/path/to/cwd',
durationMs: 28.217566,
failed: true,
timedOut: false,
isCanceled: false,
isTerminated: false,
isMaxBuffer: false,
code: 'ENOENT',
stdout: '',
stderr: '',
stdio: [undefined, '', ''],
pipedFrom: []
[cause]: Error: spawn unknown ENOENT
at ...
at ... {
errno: -2,
code: 'ENOENT',
syscall: 'spawn unknown',
path: 'unknown',
spawnargs: [ 'command' ]
}
}
*/
}
```
#### Verbose mode
```js
await execa`npm run build`;
await execa`npm run test`;
```
#### Custom logging
```js
import {execa as execa_} from 'execa';
import {createLogger, transports} from 'winston';
// Log to a file using Winston
const transport = new transports.File({filename: 'logs.txt'});
const logger = createLogger({transports: [transport]});
const LOG_LEVELS = {
command: 'info',
output: 'verbose',
ipc: 'verbose',
error: 'error',
duration: 'info',
};
const execa = execa_({
verbose(verboseLine, {message, ...verboseObject}) {
const level = LOG_LEVELS[verboseObject.type];
logger[level](message, verboseObject);
},
});
await execa`npm run build`;
await execa`npm run test`;
```
## Related
- [nano-spawn](https://github.com/sindresorhus/nano-spawn) - Like Execa but [smaller](docs/small.md)
- [gulp-execa](https://github.com/ehmicky/gulp-execa) - Gulp plugin for Execa
- [nvexeca](https://github.com/ehmicky/nvexeca) - Run Execa using any Node.js version
## Maintainers
- [Sindre Sorhus](https://github.com/sindresorhus)
- [@ehmicky](https://github.com/ehmicky)
================================================
FILE: test/arguments/cwd.js
================================================
import {mkdir, rmdir} from 'node:fs/promises';
import path from 'node:path';
import process from 'node:process';
import {pathToFileURL, fileURLToPath} from 'node:url';
import tempfile from 'tempfile';
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {FIXTURES_DIRECTORY, setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {majorNodeVersion} from '../helpers/node-version.js';
setFixtureDirectory();
const isWindows = process.platform === 'win32';
const testOptionCwdString = async (t, execaMethod) => {
const cwd = '/';
const {stdout} = await execaMethod('node', ['-p', 'process.cwd()'], {cwd});
t.is(path.toNamespacedPath(stdout), path.toNamespacedPath(cwd));
};
test('The "cwd" option can be a string', testOptionCwdString, execa);
test('The "cwd" option can be a string - sync', testOptionCwdString, execaSync);
const testOptionCwdUrl = async (t, execaMethod) => {
const cwd = '/';
const cwdUrl = pathToFileURL(cwd);
const {stdout} = await execaMethod('node', ['-p', 'process.cwd()'], {cwd: cwdUrl});
t.is(path.toNamespacedPath(stdout), path.toNamespacedPath(cwd));
};
test('The "cwd" option can be a URL', testOptionCwdUrl, execa);
test('The "cwd" option can be a URL - sync', testOptionCwdUrl, execaSync);
const testOptionCwdInvalid = (t, execaMethod) => {
t.throws(() => {
execaMethod('empty.js', {cwd: true});
}, {message: /The "cwd" option must be a string or a file URL: true/});
};
test('The "cwd" option cannot be an invalid type', testOptionCwdInvalid, execa);
test('The "cwd" option cannot be an invalid type - sync', testOptionCwdInvalid, execaSync);
const testErrorCwdDefault = async (t, execaMethod) => {
const {cwd} = await execaMethod('empty.js');
t.is(cwd, process.cwd());
};
test('The "cwd" option defaults to process.cwd()', testErrorCwdDefault, execa);
test('The "cwd" option defaults to process.cwd() - sync', testErrorCwdDefault, execaSync);
// Windows does not allow removing a directory used as `cwd` of a running subprocess
if (!isWindows) {
const testCwdPreSpawn = async (t, execaMethod) => {
const currentCwd = process.cwd();
const filePath = tempfile();
await mkdir(filePath);
process.chdir(filePath);
await rmdir(filePath);
try {
t.throws(() => {
execaMethod('empty.js');
}, {message: /The current directory does not exist/});
} finally {
process.chdir(currentCwd);
}
};
test.serial('The "cwd" option default fails if current cwd is missing', testCwdPreSpawn, execa);
test.serial('The "cwd" option default fails if current cwd is missing - sync', testCwdPreSpawn, execaSync);
}
const cwdNotExisting = {cwd: 'does_not_exist', expectedCode: 'ENOENT', expectedMessage: 'The "cwd" option is invalid'};
const cwdTooLong = {cwd: '.'.repeat(1e5), expectedCode: isWindows && majorNodeVersion >= 20 ? 'ENOENT' : 'ENAMETOOLONG', expectedMessage: 'The "cwd" option is invalid'};
// @todo: use import.meta.dirname after dropping support for Node <20.11.0
const cwdNotDirectory = {cwd: fileURLToPath(import.meta.url), expectedCode: isWindows ? 'ENOENT' : 'ENOTDIR', expectedMessage: 'The "cwd" option is not a directory'};
const testCwdPostSpawn = async (t, {cwd, expectedCode, expectedMessage}, execaMethod) => {
const {failed, code, message} = await execaMethod('empty.js', {cwd, reject: false});
t.true(failed);
t.is(code, expectedCode);
t.true(message.includes(expectedMessage));
t.true(message.includes(cwd));
};
test('The "cwd" option must be an existing file', testCwdPostSpawn, cwdNotExisting, execa);
test('The "cwd" option must be an existing file - sync', testCwdPostSpawn, cwdNotExisting, execaSync);
test('The "cwd" option must not be too long', testCwdPostSpawn, cwdTooLong, execa);
test('The "cwd" option must not be too long - sync', testCwdPostSpawn, cwdTooLong, execaSync);
test('The "cwd" option must be a directory', testCwdPostSpawn, cwdNotDirectory, execa);
test('The "cwd" option must be a directory - sync', testCwdPostSpawn, cwdNotDirectory, execaSync);
const successProperties = {fixtureName: 'empty.js', expectedFailed: false};
const errorProperties = {fixtureName: 'fail.js', expectedFailed: true};
const testErrorCwd = async (t, execaMethod, {fixtureName, expectedFailed}) => {
const {failed, cwd} = await execaMethod(fixtureName, {cwd: path.relative('.', FIXTURES_DIRECTORY), reject: false});
t.is(failed, expectedFailed);
t.is(cwd, FIXTURES_DIRECTORY);
};
test('result.cwd is defined', testErrorCwd, execa, successProperties);
test('result.cwd is defined - sync', testErrorCwd, execaSync, successProperties);
test('error.cwd is defined', testErrorCwd, execa, errorProperties);
test('error.cwd is defined - sync', testErrorCwd, execaSync, errorProperties);
================================================
FILE: test/arguments/encoding-option.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
setFixtureDirectory();
const testInvalidEncoding = (t, encoding, message, execaMethod) => {
const error = t.throws(() => {
execaMethod('empty.js', {encoding});
});
t.true(error.message.includes(message));
};
const UNKNOWN_ENCODING_MESSAGE = 'Please rename it to one of';
const getCorrectEncodingMessage = correctEncoding => `Please rename it to "${correctEncoding}"`;
test('cannot pass unknown encodings', testInvalidEncoding, 'unknown', UNKNOWN_ENCODING_MESSAGE, execa);
test('cannot pass unknown encodings, sync', testInvalidEncoding, 'unknown', UNKNOWN_ENCODING_MESSAGE, execaSync);
test('cannot pass empty encodings', testInvalidEncoding, '', UNKNOWN_ENCODING_MESSAGE, execa);
test('cannot pass encoding: false', testInvalidEncoding, false, UNKNOWN_ENCODING_MESSAGE, execa);
test('cannot pass encoding: Symbol', testInvalidEncoding, Symbol('test'), UNKNOWN_ENCODING_MESSAGE, execa);
test('cannot pass encoding: null', testInvalidEncoding, null, getCorrectEncodingMessage('buffer'), execa);
test('cannot pass encoding: null, sync', testInvalidEncoding, null, getCorrectEncodingMessage('buffer'), execaSync);
/* eslint-disable unicorn/text-encoding-identifier-case */
test('cannot pass encoding: utf-8', testInvalidEncoding, 'utf-8', getCorrectEncodingMessage('utf8'), execa);
test('cannot pass encoding: utf-8, sync', testInvalidEncoding, 'utf-8', getCorrectEncodingMessage('utf8'), execaSync);
test('cannot pass encoding: UTF-8', testInvalidEncoding, 'UTF-8', getCorrectEncodingMessage('utf8'), execa);
test('cannot pass encoding: UTF-8, sync', testInvalidEncoding, 'UTF-8', getCorrectEncodingMessage('utf8'), execaSync);
test('cannot pass encoding: UTF8', testInvalidEncoding, 'UTF8', getCorrectEncodingMessage('utf8'), execa);
test('cannot pass encoding: UTF8, sync', testInvalidEncoding, 'UTF8', getCorrectEncodingMessage('utf8'), execaSync);
/* eslint-enable unicorn/text-encoding-identifier-case */
test('cannot pass encoding: utf-16le', testInvalidEncoding, 'utf-16le', getCorrectEncodingMessage('utf16le'), execa);
test('cannot pass encoding: UTF-16LE', testInvalidEncoding, 'UTF-16LE', getCorrectEncodingMessage('utf16le'), execa);
test('cannot pass encoding: UTF16LE', testInvalidEncoding, 'UTF16LE', getCorrectEncodingMessage('utf16le'), execa);
test('cannot pass encoding: ucs2', testInvalidEncoding, 'ucs2', getCorrectEncodingMessage('utf16le'), execa);
test('cannot pass encoding: UCS2', testInvalidEncoding, 'UCS2', getCorrectEncodingMessage('utf16le'), execa);
test('cannot pass encoding: ucs-2', testInvalidEncoding, 'ucs-2', getCorrectEncodingMessage('utf16le'), execa);
test('cannot pass encoding: UCS-2', testInvalidEncoding, 'UCS-2', getCorrectEncodingMessage('utf16le'), execa);
test('cannot pass encoding: binary', testInvalidEncoding, 'binary', getCorrectEncodingMessage('latin1'), execa);
================================================
FILE: test/arguments/env.js
================================================
import process from 'node:process';
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory, PATH_KEY} from '../helpers/fixtures-directory.js';
setFixtureDirectory();
process.env.FOO = 'foo';
const isWindows = process.platform === 'win32';
test('use environment variables by default', async t => {
const {stdout} = await execa('environment.js');
t.deepEqual(stdout.split('\n'), ['foo', 'undefined']);
});
test('extend environment variables by default', async t => {
const {stdout} = await execa('environment.js', [], {env: {BAR: 'bar', [PATH_KEY]: process.env[PATH_KEY]}});
t.deepEqual(stdout.split('\n'), ['foo', 'bar']);
});
test('do not extend environment with `extendEnv: false`', async t => {
const {stdout} = await execa('environment.js', [], {env: {BAR: 'bar', [PATH_KEY]: process.env[PATH_KEY]}, extendEnv: false});
t.deepEqual(stdout.split('\n'), ['undefined', 'bar']);
});
test('use extend environment with `extendEnv: true` and `shell: true`', async t => {
process.env.TEST = 'test';
const command = isWindows ? 'echo %TEST%' : 'echo $TEST';
const {stdout} = await execa(command, {shell: true, env: {}, extendEnv: true});
t.is(stdout, 'test');
delete process.env.TEST;
});
================================================
FILE: test/arguments/escape-no-icu.js
================================================
// Mimics Node.js when built without ICU support
// See https://github.com/sindresorhus/execa/issues/1143
globalThis.RegExp = class extends RegExp {
constructor(regExpString, flags) {
if (flags?.includes('u') && regExpString.includes('\\p{')) {
throw new Error('Invalid property name');
}
super(regExpString, flags);
}
static isMocked = true;
};
// Execa computes the RegExp when first loaded, so we must delay this import
await import('./escape.js');
================================================
FILE: test/arguments/escape.js
================================================
import {platform} from 'node:process';
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
setFixtureDirectory();
const isWindows = platform === 'win32';
const testResultCommand = async (t, expected, ...commandArguments) => {
const {command: failCommand} = await t.throwsAsync(execa('fail.js', commandArguments));
t.is(failCommand, `fail.js${expected}`);
const {command} = await execa('noop.js', commandArguments);
t.is(command, `noop.js${expected}`);
};
testResultCommand.title = (message, expected) => `result.command is: ${JSON.stringify(expected)}`;
test(testResultCommand, ' foo bar', 'foo', 'bar');
test(testResultCommand, ' baz quz', 'baz', 'quz');
test(testResultCommand, '');
// eslint-disable-next-line max-params
const testEscapedCommand = async (t, commandArguments, expectedUnix, expectedWindows, expectedUnixNoIcu = expectedUnix, expectedWindowsNoIcu = expectedWindows) => {
const expected = RegExp.isMocked
? (isWindows ? expectedWindowsNoIcu : expectedUnixNoIcu)
: (isWindows ? expectedWindows : expectedUnix);
t.like(
await t.throwsAsync(execa('fail.js', commandArguments)),
{escapedCommand: `fail.js ${expected}`},
);
t.like(t.throws(() => {
execaSync('fail.js', commandArguments);
}), {escapedCommand: `fail.js ${expected}`});
t.like(
await execa('noop.js', commandArguments),
{escapedCommand: `noop.js ${expected}`},
);
t.like(
execaSync('noop.js', commandArguments),
{escapedCommand: `noop.js ${expected}`},
);
};
test('result.escapedCommand - foo bar', testEscapedCommand, ['foo', 'bar'], 'foo bar', 'foo bar');
test('result.escapedCommand - foo\\ bar', testEscapedCommand, ['foo bar'], '\'foo bar\'', '"foo bar"');
test('result.escapedCommand - "foo"', testEscapedCommand, ['"foo"'], '\'"foo"\'', '"""foo"""');
test('result.escapedCommand - \'foo\'', testEscapedCommand, ['\'foo\''], '\'\'\\\'\'foo\'\\\'\'\'', '"\'foo\'"');
test('result.escapedCommand - "0"', testEscapedCommand, ['0'], '0', '0');
test('result.escapedCommand - 0', testEscapedCommand, [0], '0', '0');
test('result.escapedCommand - *', testEscapedCommand, ['*'], '\'*\'', '"*"');
test('result.escapedCommand - .', testEscapedCommand, ['.'], '.', '.');
test('result.escapedCommand - -', testEscapedCommand, ['-'], '-', '-');
test('result.escapedCommand - _', testEscapedCommand, ['_'], '_', '_');
test('result.escapedCommand - /', testEscapedCommand, ['/'], '/', '/');
test('result.escapedCommand - ,', testEscapedCommand, [','], '\',\'', '","');
test('result.escapedCommand - :', testEscapedCommand, [':'], '\':\'', '":"');
test('result.escapedCommand - ;', testEscapedCommand, [';'], '\';\'', '";"');
test('result.escapedCommand - ~', testEscapedCommand, ['~'], '\'~\'', '"~"');
test('result.escapedCommand - %', testEscapedCommand, ['%'], '\'%\'', '"%"');
test('result.escapedCommand - $', testEscapedCommand, ['$'], '\'$\'', '"$"');
test('result.escapedCommand - !', testEscapedCommand, ['!'], '\'!\'', '"!"');
test('result.escapedCommand - ?', testEscapedCommand, ['?'], '\'?\'', '"?"');
test('result.escapedCommand - #', testEscapedCommand, ['#'], '\'#\'', '"#"');
test('result.escapedCommand - &', testEscapedCommand, ['&'], '\'&\'', '"&"');
test('result.escapedCommand - =', testEscapedCommand, ['='], '\'=\'', '"="');
test('result.escapedCommand - @', testEscapedCommand, ['@'], '\'@\'', '"@"');
test('result.escapedCommand - ^', testEscapedCommand, ['^'], '\'^\'', '"^"');
test('result.escapedCommand - `', testEscapedCommand, ['`'], '\'`\'', '"`"');
test('result.escapedCommand - |', testEscapedCommand, ['|'], '\'|\'', '"|"');
test('result.escapedCommand - +', testEscapedCommand, ['+'], '\'+\'', '"+"');
test('result.escapedCommand - \\', testEscapedCommand, ['\\'], '\'\\\'', '"\\"');
test('result.escapedCommand - ()', testEscapedCommand, ['()'], '\'()\'', '"()"');
test('result.escapedCommand - {}', testEscapedCommand, ['{}'], '\'{}\'', '"{}"');
test('result.escapedCommand - []', testEscapedCommand, ['[]'], '\'[]\'', '"[]"');
test('result.escapedCommand - <>', testEscapedCommand, ['<>'], '\'<>\'', '"<>"');
test('result.escapedCommand - ã', testEscapedCommand, ['ã'], '\'ã\'', '"ã"');
test('result.escapedCommand - \\a', testEscapedCommand, ['\u0007'], '\'\\u0007\'', '"\\u0007"');
test('result.escapedCommand - \\b', testEscapedCommand, ['\b'], '\'\\b\'', '"\\b"');
test('result.escapedCommand - \\e', testEscapedCommand, ['\u001B'], '\'\\u001b\'', '"\\u001b"');
test('result.escapedCommand - \\f', testEscapedCommand, ['\f'], '\'\\f\'', '"\\f"');
test('result.escapedCommand - \\n', testEscapedCommand, ['\n'], '\'\\n\'', '"\\n"');
test('result.escapedCommand - \\r\\n', testEscapedCommand, ['\r\n'], '\'\\r\\n\'', '"\\r\\n"');
test('result.escapedCommand - \\t', testEscapedCommand, ['\t'], '\'\\t\'', '"\\t"');
test('result.escapedCommand - \\v', testEscapedCommand, ['\v'], '\'\\u000b\'', '"\\u000b"');
test('result.escapedCommand - \\x01', testEscapedCommand, ['\u0001'], '\'\\u0001\'', '"\\u0001"');
test('result.escapedCommand - \\x7f', testEscapedCommand, ['\u007F'], '\'\\u007f\'', '"\\u007f"');
test('result.escapedCommand - \\u0085', testEscapedCommand, ['\u0085'], '\'\\u0085\'', '"\\u0085"');
test('result.escapedCommand - \\u2000', testEscapedCommand, ['\u2000'], '\'\\u2000\'', '"\\u2000"');
test('result.escapedCommand - \\u200E', testEscapedCommand, ['\u200E'], '\'\\u200e\'', '"\\u200e"', '\'\u200E\'', '"\u200E"');
test('result.escapedCommand - \\u2028', testEscapedCommand, ['\u2028'], '\'\\u2028\'', '"\\u2028"');
test('result.escapedCommand - \\u2029', testEscapedCommand, ['\u2029'], '\'\\u2029\'', '"\\u2029"');
test('result.escapedCommand - \\u5555', testEscapedCommand, ['\u5555'], '\'\u5555\'', '"\u5555"');
test('result.escapedCommand - \\uD800', testEscapedCommand, ['\uD800'], '\'\\ud800\'', '"\\ud800"', '\'\uD800\'', '"\uD800"');
test('result.escapedCommand - \\uE000', testEscapedCommand, ['\uE000'], '\'\\ue000\'', '"\\ue000"', '\'\uE000\'', '"\uE000"');
test('result.escapedCommand - \\U1D172', testEscapedCommand, ['\u{1D172}'], '\'\u{1D172}\'', '"\u{1D172}"');
test('result.escapedCommand - \\U1D173', testEscapedCommand, ['\u{1D173}'], '\'\\U1d173\'', '"\\U1d173"', '\'\u{1D173}\'', '"\u{1D173}"');
test('result.escapedCommand - \\U10FFFD', testEscapedCommand, ['\u{10FFFD}'], '\'\\U10fffd\'', '"\\U10fffd"', '\'\u{10FFFD}\'', '"\u{10FFFD}"');
================================================
FILE: test/arguments/fd-options.js
================================================
import {PassThrough} from 'node:stream';
import {spawn} from 'node:child_process';
import process from 'node:process';
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {fullStdio, getStdio} from '../helpers/stdio.js';
import {getEarlyErrorSubprocess} from '../helpers/early-error.js';
import {assertPipeError} from '../helpers/pipe.js';
setFixtureDirectory();
const getMessage = message => Array.isArray(message)
? `"${message[0]}: ${message[1]}" option is incompatible`
: message;
const testPipeError = async (t, {
message,
sourceOptions = {},
destinationOptions = {},
getSource = () => execa('empty.js', sourceOptions),
getDestination = () => execa('empty.js', destinationOptions),
isScript = false,
from,
to,
}) => {
const source = getSource();
const pipePromise = isScript ? source.pipe({from, to})`empty.js` : source.pipe(getDestination(), {from, to});
await assertPipeError(t, pipePromise, getMessage(message));
};
const testNodeStream = async (t, {
message,
sourceOptions = {},
getSource = () => execa('empty.js', sourceOptions),
from,
to,
writable = to !== undefined,
}) => {
assertNodeStream({
t,
message,
getSource,
from,
to,
methodName: writable ? 'writable' : 'readable',
});
assertNodeStream({
t,
message,
getSource,
from,
to,
methodName: 'duplex',
});
};
const assertNodeStream = ({t, message, getSource, from, to, methodName}) => {
const error = t.throws(() => {
getSource()[methodName]({from, to});
});
t.true(error.message.includes(getMessage(message)));
};
const testIterable = async (t, {
message,
sourceOptions = {},
getSource = () => execa('empty.js', sourceOptions),
from,
}) => {
const error = t.throws(() => {
getSource().iterable({from});
});
t.true(error.message.includes(getMessage(message)));
};
test('Must set "all" option to "true" to use .pipe("all")', testPipeError, {
from: 'all',
message: '"all" option must be true',
});
test('Must set "all" option to "true" to use .duplex("all")', testNodeStream, {
from: 'all',
message: '"all" option must be true',
});
test('Must set "all" option to "true" to use .iterable("all")', testIterable, {
from: 'all',
message: '"all" option must be true',
});
test('.pipe() cannot pipe to non-subprocesses', testPipeError, {
getDestination: () => new PassThrough(),
message: 'an Execa subprocess',
});
test('.pipe() cannot pipe to non-Execa subprocesses', testPipeError, {
getDestination: () => spawn('node', ['--version']),
message: 'an Execa subprocess',
});
test('.pipe() "from" option cannot be "stdin"', testPipeError, {
from: 'stdin',
message: '"from" must not be',
});
test('.duplex() "from" option cannot be "stdin"', testNodeStream, {
from: 'stdin',
message: '"from" must not be',
});
test('.iterable() "from" option cannot be "stdin"', testIterable, {
from: 'stdin',
message: '"from" must not be',
});
test('$.pipe() "from" option cannot be "stdin"', testPipeError, {
from: 'stdin',
isScript: true,
message: '"from" must not be',
});
test('.pipe() "to" option cannot be "stdout"', testPipeError, {
to: 'stdout',
message: '"to" must not be',
});
test('.duplex() "to" option cannot be "stdout"', testNodeStream, {
to: 'stdout',
message: '"to" must not be',
});
test('$.pipe() "to" option cannot be "stdout"', testPipeError, {
to: 'stdout',
isScript: true,
message: '"to" must not be',
});
test('.pipe() "from" option cannot be any string', testPipeError, {
from: 'other',
message: 'must be "stdout", "stderr", "all"',
});
test('.duplex() "from" option cannot be any string', testNodeStream, {
from: 'other',
message: 'must be "stdout", "stderr", "all"',
});
test('.iterable() "from" option cannot be any string', testIterable, {
from: 'other',
message: 'must be "stdout", "stderr", "all"',
});
test('.pipe() "to" option cannot be any string', testPipeError, {
to: 'other',
message: 'must be "stdin"',
});
test('.duplex() "to" option cannot be any string', testNodeStream, {
to: 'other',
message: 'must be "stdin"',
});
test('.pipe() "from" option cannot be a number without "fd"', testPipeError, {
from: '1',
message: 'must be "stdout", "stderr", "all"',
});
test('.duplex() "from" option cannot be a number without "fd"', testNodeStream, {
from: '1',
message: 'must be "stdout", "stderr", "all"',
});
test('.iterable() "from" option cannot be a number without "fd"', testIterable, {
from: '1',
message: 'must be "stdout", "stderr", "all"',
});
test('.pipe() "to" option cannot be a number without "fd"', testPipeError, {
to: '0',
message: 'must be "stdin"',
});
test('.duplex() "to" option cannot be a number without "fd"', testNodeStream, {
to: '0',
message: 'must be "stdin"',
});
test('.pipe() "from" option cannot be just "fd"', testPipeError, {
from: 'fd',
message: 'must be "stdout", "stderr", "all"',
});
test('.duplex() "from" option cannot be just "fd"', testNodeStream, {
from: 'fd',
message: 'must be "stdout", "stderr", "all"',
});
test('.iterable() "from" option cannot be just "fd"', testIterable, {
from: 'fd',
message: 'must be "stdout", "stderr", "all"',
});
test('.pipe() "to" option cannot be just "fd"', testPipeError, {
to: 'fd',
message: 'must be "stdin"',
});
test('.duplex() "to" option cannot be just "fd"', testNodeStream, {
to: 'fd',
message: 'must be "stdin"',
});
test('.pipe() "from" option cannot be a float', testPipeError, {
from: 'fd1.5',
message: 'must be "stdout", "stderr", "all"',
});
test('.duplex() "from" option cannot be a float', testNodeStream, {
from: 'fd1.5',
message: 'must be "stdout", "stderr", "all"',
});
test('.iterable() "from" option cannot be a float', testIterable, {
from: 'fd1.5',
message: 'must be "stdout", "stderr", "all"',
});
test('.pipe() "to" option cannot be a float', testPipeError, {
to: 'fd1.5',
message: 'must be "stdin"',
});
test('.duplex() "to" option cannot be a float', testNodeStream, {
to: 'fd1.5',
message: 'must be "stdin"',
});
test('.pipe() "from" option cannot be a negative number', testPipeError, {
from: 'fd-1',
message: 'must be "stdout", "stderr", "all"',
});
test('.duplex() "from" option cannot be a negative number', testNodeStream, {
from: 'fd-1',
message: 'must be "stdout", "stderr", "all"',
});
test('.iterable() "from" option cannot be a negative number', testIterable, {
from: 'fd-1',
message: 'must be "stdout", "stderr", "all"',
});
test('.pipe() "to" option cannot be a negative number', testPipeError, {
to: 'fd-1',
message: 'must be "stdin"',
});
test('.duplex() "to" option cannot be a negative number', testNodeStream, {
to: 'fd-1',
message: 'must be "stdin"',
});
test('.pipe() "from" option cannot be a non-existing file descriptor', testPipeError, {
from: 'fd3',
message: 'file descriptor does not exist',
});
test('.duplex() "from" cannot be a non-existing file descriptor', testNodeStream, {
from: 'fd3',
message: 'file descriptor does not exist',
});
test('.iterable() "from" cannot be a non-existing file descriptor', testIterable, {
from: 'fd3',
message: 'file descriptor does not exist',
});
test('.pipe() "to" option cannot be a non-existing file descriptor', testPipeError, {
to: 'fd3',
message: 'file descriptor does not exist',
});
test('.duplex() "to" cannot be a non-existing file descriptor', testNodeStream, {
to: 'fd3',
message: 'file descriptor does not exist',
});
test('.pipe() "from" option cannot be an input file descriptor', testPipeError, {
sourceOptions: getStdio(3, new Uint8Array()),
from: 'fd3',
message: 'must be a readable stream',
});
test('.duplex() "from" option cannot be an input file descriptor', testNodeStream, {
sourceOptions: getStdio(3, new Uint8Array()),
from: 'fd3',
message: 'must be a readable stream',
});
test('.iterable() "from" option cannot be an input file descriptor', testIterable, {
sourceOptions: getStdio(3, new Uint8Array()),
from: 'fd3',
message: 'must be a readable stream',
});
test('.pipe() "to" option cannot be an output file descriptor', testPipeError, {
destinationOptions: fullStdio,
to: 'fd3',
message: 'must be a writable stream',
});
test('.duplex() "to" option cannot be an output file descriptor', testNodeStream, {
sourceOptions: fullStdio,
to: 'fd3',
message: 'must be a writable stream',
});
test('.pipe() "to" option cannot be "all"', testPipeError, {
destinationOptions: fullStdio,
to: 'all',
message: 'must be a writable stream',
});
test('.duplex() "to" option cannot be "all"', testNodeStream, {
sourceOptions: fullStdio,
to: 'all',
message: 'must be a writable stream',
});
test('Cannot set "stdout" option to "ignore" to use .pipe()', testPipeError, {
sourceOptions: {stdout: 'ignore'},
message: ['stdout', '\'ignore\''],
});
test('Cannot set "stdout" option to "ignore" to use .duplex()', testNodeStream, {
sourceOptions: {stdout: 'ignore'},
message: ['stdout', '\'ignore\''],
});
test('Cannot set "stdout" option to "ignore" to use .iterable()', testIterable, {
sourceOptions: {stdout: 'ignore'},
message: ['stdout', '\'ignore\''],
});
test('Cannot set "stdin" option to "ignore" to use .pipe()', testPipeError, {
destinationOptions: {stdin: 'ignore'},
message: ['stdin', '\'ignore\''],
});
test('Cannot set "stdin" option to "ignore" to use .duplex()', testNodeStream, {
sourceOptions: {stdin: 'ignore'},
message: ['stdin', '\'ignore\''],
writable: true,
});
test('Cannot set "stdout" option to "ignore" to use .pipe(1)', testPipeError, {
sourceOptions: {stdout: 'ignore'},
from: 'fd1',
message: ['stdout', '\'ignore\''],
});
test('Cannot set "stdout" option to "ignore" to use .duplex(1)', testNodeStream, {
sourceOptions: {stdout: 'ignore'},
from: 'fd1',
message: ['stdout', '\'ignore\''],
});
test('Cannot set "stdout" option to "ignore" to use .iterable(1)', testIterable, {
sourceOptions: {stdout: 'ignore'},
from: 'fd1',
message: ['stdout', '\'ignore\''],
});
test('Cannot set "stdin" option to "ignore" to use .pipe(0)', testPipeError, {
destinationOptions: {stdin: 'ignore'},
message: ['stdin', '\'ignore\''],
to: 'fd0',
});
test('Cannot set "stdin" option to "ignore" to use .duplex(0)', testNodeStream, {
sourceOptions: {stdin: 'ignore'},
message: ['stdin', '\'ignore\''],
to: 'fd0',
});
test('Cannot set "stdout" option to "ignore" to use .pipe("stdout")', testPipeError, {
sourceOptions: {stdout: 'ignore'},
from: 'stdout',
message: ['stdout', '\'ignore\''],
});
test('Cannot set "stdout" option to "ignore" to use .duplex("stdout")', testNodeStream, {
sourceOptions: {stdout: 'ignore'},
from: 'stdout',
message: ['stdout', '\'ignore\''],
});
test('Cannot set "stdout" option to "ignore" to use .iterable("stdout")', testIterable, {
sourceOptions: {stdout: 'ignore'},
from: 'stdout',
message: ['stdout', '\'ignore\''],
});
test('Cannot set "stdin" option to "ignore" to use .pipe("stdin")', testPipeError, {
destinationOptions: {stdin: 'ignore'},
message: ['stdin', '\'ignore\''],
to: 'stdin',
});
test('Cannot set "stdin" option to "ignore" to use .duplex("stdin")', testNodeStream, {
sourceOptions: {stdin: 'ignore'},
message: ['stdin', '\'ignore\''],
to: 'stdin',
});
test('Cannot set "stdout" + "stderr" option to "ignore" to use .pipe()', testPipeError, {
sourceOptions: {stdout: 'ignore', stderr: 'ignore'},
message: ['stdout', '\'ignore\''],
});
test('Cannot set "stdout" + "stderr" option to "ignore" to use .duplex()', testNodeStream, {
sourceOptions: {stdout: 'ignore', stderr: 'ignore'},
message: ['stdout', '\'ignore\''],
});
test('Cannot set "stdout" + "stderr" option to "ignore" to use .iterable()', testIterable, {
sourceOptions: {stdout: 'ignore', stderr: 'ignore'},
message: ['stdout', '\'ignore\''],
});
test('Cannot set "stdout" + "stderr" option to "ignore" to use .pipe(1)', testPipeError, {
sourceOptions: {stdout: 'ignore', stderr: 'ignore'},
from: 'fd1',
message: ['stdout', '\'ignore\''],
});
test('Cannot set "stdout" + "stderr" option to "ignore" to use .duplex(1)', testNodeStream, {
sourceOptions: {stdout: 'ignore', stderr: 'ignore'},
from: 'fd1',
message: ['stdout', '\'ignore\''],
});
test('Cannot set "stdout" + "stderr" option to "ignore" to use .iterable(1)', testIterable, {
sourceOptions: {stdout: 'ignore', stderr: 'ignore'},
from: 'fd1',
message: ['stdout', '\'ignore\''],
});
test('Cannot set "stdout" + "stderr" option to "ignore" to use .pipe("stdout")', testPipeError, {
sourceOptions: {stdout: 'ignore', stderr: 'ignore'},
from: 'stdout',
message: ['stdout', '\'ignore\''],
});
test('Cannot set "stdout" + "stderr" option to "ignore" to use .duplex("stdout")', testNodeStream, {
sourceOptions: {stdout: 'ignore', stderr: 'ignore'},
from: 'stdout',
message: ['stdout', '\'ignore\''],
});
test('Cannot set "stdout" + "stderr" option to "ignore" to use .iterable("stdout")', testIterable, {
sourceOptions: {stdout: 'ignore', stderr: 'ignore'},
from: 'stdout',
message: ['stdout', '\'ignore\''],
});
test('Cannot set "stdio[1]" option to "ignore" to use .pipe()', testPipeError, {
sourceOptions: {stdio: ['pipe', 'ignore', 'pipe']},
message: ['stdio[1]', '\'ignore\''],
});
test('Cannot set "stdio[1]" option to "ignore" to use .duplex()', testNodeStream, {
sourceOptions: {stdio: ['pipe', 'ignore', 'pipe']},
message: ['stdio[1]', '\'ignore\''],
});
test('Cannot set "stdio[1]" option to "ignore" to use .iterable()', testIterable, {
sourceOptions: {stdio: ['pipe', 'ignore', 'pipe']},
message: ['stdio[1]', '\'ignore\''],
});
test('Cannot set "stdio[0]" option to "ignore" to use .pipe()', testPipeError, {
destinationOptions: {stdio: ['ignore', 'pipe', 'pipe']},
message: ['stdio[0]', '\'ignore\''],
});
test('Cannot set "stdio[0]" option to "ignore" to use .duplex()', testNodeStream, {
sourceOptions: {stdio: ['ignore', 'pipe', 'pipe']},
message: ['stdio[0]', '\'ignore\''],
writable: true,
});
test('Cannot set "stdio[1]" option to "ignore" to use .pipe(1)', testPipeError, {
sourceOptions: {stdio: ['pipe', 'ignore', 'pipe']},
from: 'fd1',
message: ['stdio[1]', '\'ignore\''],
});
test('Cannot set "stdio[1]" option to "ignore" to use .duplex(1)', testNodeStream, {
sourceOptions: {stdio: ['pipe', 'ignore', 'pipe']},
from: 'fd1',
message: ['stdio[1]', '\'ignore\''],
});
test('Cannot set "stdio[1]" option to "ignore" to use .iterable(1)', testIterable, {
sourceOptions: {stdio: ['pipe', 'ignore', 'pipe']},
from: 'fd1',
message: ['stdio[1]', '\'ignore\''],
});
test('Cannot set "stdio[0]" option to "ignore" to use .pipe(0)', testPipeError, {
destinationOptions: {stdio: ['ignore', 'pipe', 'pipe']},
message: ['stdio[0]', '\'ignore\''],
to: 'fd0',
});
test('Cannot set "stdio[0]" option to "ignore" to use .duplex(0)', testNodeStream, {
sourceOptions: {stdio: ['ignore', 'pipe', 'pipe']},
message: ['stdio[0]', '\'ignore\''],
to: 'fd0',
});
test('Cannot set "stdio[1]" option to "ignore" to use .pipe("stdout")', testPipeError, {
sourceOptions: {stdio: ['pipe', 'ignore', 'pipe']},
from: 'stdout',
message: ['stdio[1]', '\'ignore\''],
});
test('Cannot set "stdio[1]" option to "ignore" to use .duplex("stdout")', testNodeStream, {
sourceOptions: {stdio: ['pipe', 'ignore', 'pipe']},
from: 'stdout',
message: ['stdio[1]', '\'ignore\''],
});
test('Cannot set "stdio[1]" option to "ignore" to use .iterable("stdout")', testIterable, {
sourceOptions: {stdio: ['pipe', 'ignore', 'pipe']},
from: 'stdout',
message: ['stdio[1]', '\'ignore\''],
});
test('Cannot set "stdio[0]" option to "ignore" to use .pipe("stdin")', testPipeError, {
destinationOptions: {stdio: ['ignore', 'pipe', 'pipe']},
message: ['stdio[0]', '\'ignore\''],
to: 'stdin',
});
test('Cannot set "stdio[0]" option to "ignore" to use .duplex("stdin")', testNodeStream, {
sourceOptions: {stdio: ['ignore', 'pipe', 'pipe']},
message: ['stdio[0]', '\'ignore\''],
to: 'stdin',
});
test('Cannot set "stderr" option to "ignore" to use .pipe(2)', testPipeError, {
sourceOptions: {stderr: 'ignore'},
from: 'fd2',
message: ['stderr', '\'ignore\''],
});
test('Cannot set "stderr" option to "ignore" to use .duplex(2)', testNodeStream, {
sourceOptions: {stderr: 'ignore'},
from: 'fd2',
message: ['stderr', '\'ignore\''],
});
test('Cannot set "stderr" option to "ignore" to use .iterable(2)', testIterable, {
sourceOptions: {stderr: 'ignore'},
from: 'fd2',
message: ['stderr', '\'ignore\''],
});
test('Cannot set "stderr" option to "ignore" to use .pipe("stderr")', testPipeError, {
sourceOptions: {stderr: 'ignore'},
from: 'stderr',
message: ['stderr', '\'ignore\''],
});
test('Cannot set "stderr" option to "ignore" to use .duplex("stderr")', testNodeStream, {
sourceOptions: {stderr: 'ignore'},
from: 'stderr',
message: ['stderr', '\'ignore\''],
});
test('Cannot set "stderr" option to "ignore" to use .iterable("stderr")', testIterable, {
sourceOptions: {stderr: 'ignore'},
from: 'stderr',
message: ['stderr', '\'ignore\''],
});
test('Cannot set "stdout" + "stderr" option to "ignore" to use .pipe(2)', testPipeError, {
sourceOptions: {stdout: 'ignore', stderr: 'ignore'},
from: 'fd2',
message: ['stderr', '\'ignore\''],
});
test('Cannot set "stdout" + "stderr" option to "ignore" to use .duplex(2)', testNodeStream, {
sourceOptions: {stdout: 'ignore', stderr: 'ignore'},
from: 'fd2',
message: ['stderr', '\'ignore\''],
});
test('Cannot set "stdout" + "stderr" option to "ignore" to use .iterable(2)', testIterable, {
sourceOptions: {stdout: 'ignore', stderr: 'ignore'},
from: 'fd2',
message: ['stderr', '\'ignore\''],
});
test('Cannot set "stdout" + "stderr" option to "ignore" to use .pipe("stderr")', testPipeError, {
sourceOptions: {stdout: 'ignore', stderr: 'ignore'},
from: 'stderr',
message: ['stderr', '\'ignore\''],
});
test('Cannot set "stdout" + "stderr" option to "ignore" to use .duplex("stderr")', testNodeStream, {
sourceOptions: {stdout: 'ignore', stderr: 'ignore'},
from: 'stderr',
message: ['stderr', '\'ignore\''],
});
test('Cannot set "stdout" + "stderr" option to "ignore" to use .iterable("stderr")', testIterable, {
sourceOptions: {stdout: 'ignore', stderr: 'ignore'},
from: 'stderr',
message: ['stderr', '\'ignore\''],
});
test('Cannot set "stdio[2]" option to "ignore" to use .pipe(2)', testPipeError, {
sourceOptions: {stdio: ['pipe', 'pipe', 'ignore']},
from: 'fd2',
message: ['stdio[2]', '\'ignore\''],
});
test('Cannot set "stdio[2]" option to "ignore" to use .duplex(2)', testNodeStream, {
sourceOptions: {stdio: ['pipe', 'pipe', 'ignore']},
from: 'fd2',
message: ['stdio[2]', '\'ignore\''],
});
test('Cannot set "stdio[2]" option to "ignore" to use .iterable(2)', testIterable, {
sourceOptions: {stdio: ['pipe', 'pipe', 'ignore']},
from: 'fd2',
message: ['stdio[2]', '\'ignore\''],
});
test('Cannot set "stdio[2]" option to "ignore" to use .pipe("stderr")', testPipeError, {
sourceOptions: {stdio: ['pipe', 'pipe', 'ignore']},
from: 'stderr',
message: ['stdio[2]', '\'ignore\''],
});
test('Cannot set "stdio[2]" option to "ignore" to use .duplex("stderr")', testNodeStream, {
sourceOptions: {stdio: ['pipe', 'pipe', 'ignore']},
from: 'stderr',
message: ['stdio[2]', '\'ignore\''],
});
test('Cannot set "stdio[2]" option to "ignore" to use .iterable("stderr")', testIterable, {
sourceOptions: {stdio: ['pipe', 'pipe', 'ignore']},
from: 'stderr',
message: ['stdio[2]', '\'ignore\''],
});
test('Cannot set "stdio[3]" option to "ignore" to use .pipe(3)', testPipeError, {
sourceOptions: getStdio(3, 'ignore'),
from: 'fd3',
message: ['stdio[3]', '\'ignore\''],
});
test('Cannot set "stdio[3]" option to "ignore" to use .duplex(3)', testNodeStream, {
sourceOptions: getStdio(3, 'ignore'),
from: 'fd3',
message: ['stdio[3]', '\'ignore\''],
});
test('Cannot set "stdio[3]" option to "ignore" to use .iterable(3)', testIterable, {
sourceOptions: getStdio(3, 'ignore'),
from: 'fd3',
message: ['stdio[3]', '\'ignore\''],
});
test('Cannot set "stdout" + "stderr" option to "ignore" to use .pipe("all")', testPipeError, {
sourceOptions: {stdout: 'ignore', stderr: 'ignore', all: true},
from: 'all',
message: ['stdout', '\'ignore\''],
});
test('Cannot set "stdout" + "stderr" option to "ignore" to use .duplex("all")', testNodeStream, {
sourceOptions: {stdout: 'ignore', stderr: 'ignore', all: true},
from: 'all',
message: ['stdout', '\'ignore\''],
});
test('Cannot set "stdout" + "stderr" option to "ignore" to use .iterable("all")', testIterable, {
sourceOptions: {stdout: 'ignore', stderr: 'ignore', all: true},
from: 'all',
message: ['stdout', '\'ignore\''],
});
test('Cannot set "stdio[1]" + "stdio[2]" option to "ignore" to use .pipe("all")', testPipeError, {
sourceOptions: {stdio: ['pipe', 'ignore', 'ignore'], all: true},
from: 'all',
message: ['stdio[1]', '\'ignore\''],
});
test('Cannot set "stdio[1]" + "stdio[2]" option to "ignore" to use .duplex("all")', testNodeStream, {
sourceOptions: {stdio: ['pipe', 'ignore', 'ignore'], all: true},
from: 'all',
message: ['stdio[1]', '\'ignore\''],
});
test('Cannot set "stdio[1]" + "stdio[2]" option to "ignore" to use .iterable("all")', testIterable, {
sourceOptions: {stdio: ['pipe', 'ignore', 'ignore'], all: true},
from: 'all',
message: ['stdio[1]', '\'ignore\''],
});
test('Cannot set "stdout" option to "inherit" to use .pipe()', testPipeError, {
sourceOptions: {stdout: 'inherit'},
message: ['stdout', '\'inherit\''],
});
test('Cannot set "stdout" option to "inherit" to use .duplex()', testNodeStream, {
sourceOptions: {stdout: 'inherit'},
message: ['stdout', '\'inherit\''],
});
test('Cannot set "stdout" option to "inherit" to use .iterable()', testIterable, {
sourceOptions: {stdout: 'inherit'},
message: ['stdout', '\'inherit\''],
});
test('Cannot set "stdin" option to "inherit" to use .pipe()', testPipeError, {
destinationOptions: {stdin: 'inherit'},
message: ['stdin', '\'inherit\''],
});
test('Cannot set "stdin" option to "inherit" to use .duplex()', testNodeStream, {
sourceOptions: {stdin: 'inherit'},
message: ['stdin', '\'inherit\''],
writable: true,
});
test('Cannot set "stdout" option to "ipc" to use .pipe()', testPipeError, {
sourceOptions: {stdout: 'ipc'},
message: ['stdout', '\'ipc\''],
});
test('Cannot set "stdout" option to "ipc" to use .duplex()', testNodeStream, {
sourceOptions: {stdout: 'ipc'},
message: ['stdout', '\'ipc\''],
});
test('Cannot set "stdout" option to "ipc" to use .iterable()', testIterable, {
sourceOptions: {stdout: 'ipc'},
message: ['stdout', '\'ipc\''],
});
test('Cannot set "stdin" option to "ipc" to use .pipe()', testPipeError, {
destinationOptions: {stdin: 'ipc'},
message: ['stdin', '\'ipc\''],
});
test('Cannot set "stdin" option to "ipc" to use .duplex()', testNodeStream, {
sourceOptions: {stdin: 'ipc'},
message: ['stdin', '\'ipc\''],
writable: true,
});
test('Cannot set "stdout" option to file descriptors to use .pipe()', testPipeError, {
sourceOptions: {stdout: 1},
message: ['stdout', '1'],
});
test('Cannot set "stdout" option to file descriptors to use .duplex()', testNodeStream, {
sourceOptions: {stdout: 1},
message: ['stdout', '1'],
});
test('Cannot set "stdout" option to file descriptors to use .iterable()', testIterable, {
sourceOptions: {stdout: 1},
message: ['stdout', '1'],
});
test('Cannot set "stdin" option to file descriptors to use .pipe()', testPipeError, {
destinationOptions: {stdin: 0},
message: ['stdin', '0'],
});
test('Cannot set "stdin" option to file descriptors to use .duplex()', testNodeStream, {
sourceOptions: {stdin: 0},
message: ['stdin', '0'],
writable: true,
});
test('Cannot set "stdout" option to Node.js streams to use .pipe()', testPipeError, {
sourceOptions: {stdout: process.stdout},
message: ['stdout', 'Stream'],
});
test('Cannot set "stdout" option to Node.js streams to use .duplex()', testNodeStream, {
sourceOptions: {stdout: process.stdout},
message: ['stdout', 'Stream'],
});
test('Cannot set "stdout" option to Node.js streams to use .iterable()', testIterable, {
sourceOptions: {stdout: process.stdout},
message: ['stdout', 'Stream'],
});
test('Cannot set "stdin" option to Node.js streams to use .pipe()', testPipeError, {
destinationOptions: {stdin: process.stdin},
message: ['stdin', 'Stream'],
});
test('Cannot set "stdin" option to Node.js streams to use .duplex()', testNodeStream, {
sourceOptions: {stdin: process.stdin},
message: ['stdin', 'Stream'],
writable: true,
});
test('Cannot set "stdio[3]" option to Node.js Writable streams to use .pipe()', testPipeError, {
sourceOptions: getStdio(3, process.stdout),
message: ['stdio[3]', 'Stream'],
from: 'fd3',
});
test('Cannot set "stdio[3]" option to Node.js Writable streams to use .duplex()', testNodeStream, {
sourceOptions: getStdio(3, process.stdout),
message: ['stdio[3]', 'Stream'],
from: 'fd3',
});
test('Cannot set "stdio[3]" option to Node.js Writable streams to use .iterable()', testIterable, {
sourceOptions: getStdio(3, process.stdout),
message: ['stdio[3]', 'Stream'],
from: 'fd3',
});
test('Cannot set "stdio[3]" option to Node.js Readable streams to use .pipe()', testPipeError, {
destinationOptions: getStdio(3, process.stdin),
message: ['stdio[3]', 'Stream'],
to: 'fd3',
});
test('Cannot set "stdio[3]" option to Node.js Readable streams to use .duplex()', testNodeStream, {
sourceOptions: getStdio(3, process.stdin),
message: ['stdio[3]', 'Stream'],
to: 'fd3',
});
test('Sets the right error message when the "all" option is incompatible - execa.$', async t => {
await assertPipeError(
t,
execa('empty.js')
.pipe({all: false})`stdin.js`
.pipe(execa('empty.js'), {from: 'all'}),
'"all" option must be true',
);
});
test('Sets the right error message when the "all" option is incompatible - execa.execa', async t => {
await assertPipeError(
t,
execa('empty.js')
.pipe(execa('stdin.js', {all: false}))
.pipe(execa('empty.js'), {from: 'all'}),
'"all" option must be true',
);
});
test('Sets the right error message when the "all" option is incompatible - early error', async t => {
await assertPipeError(
t,
getEarlyErrorSubprocess()
.pipe(execa('stdin.js', {all: false}))
.pipe(execa('empty.js'), {from: 'all'}),
'"all" option must be true',
);
});
================================================
FILE: test/arguments/local.js
================================================
import path from 'node:path';
import process from 'node:process';
import {pathToFileURL} from 'node:url';
import test from 'ava';
import {execa, $} from '../../index.js';
import {setFixtureDirectory, PATH_KEY} from '../helpers/fixtures-directory.js';
setFixtureDirectory();
process.env.FOO = 'foo';
const isWindows = process.platform === 'win32';
const ENOENT_REGEXP = isWindows ? /failed with exit code 1/ : /spawn.* ENOENT/;
const getPathWithoutLocalDirectory = () => {
const newPath = process.env[PATH_KEY]
.split(path.delimiter)
.filter(pathDirectory => !BIN_DIR_REGEXP.test(pathDirectory)).join(path.delimiter);
return {[PATH_KEY]: newPath};
};
const BIN_DIR_REGEXP = /node_modules[\\/]\.bin/;
const pathWitoutLocalDirectory = getPathWithoutLocalDirectory();
test('preferLocal: true', async t => {
await t.notThrowsAsync(execa('ava', ['--version'], {preferLocal: true, env: pathWitoutLocalDirectory}));
});
test('preferLocal: false', async t => {
await t.throwsAsync(execa('ava', ['--version'], {preferLocal: false, env: pathWitoutLocalDirectory}), {message: ENOENT_REGEXP});
});
test('preferLocal: undefined', async t => {
await t.throwsAsync(execa('ava', ['--version'], {env: pathWitoutLocalDirectory}), {message: ENOENT_REGEXP});
});
test('preferLocal: undefined with $', async t => {
await t.notThrowsAsync($('ava', ['--version'], {env: pathWitoutLocalDirectory}));
});
test('preferLocal: undefined with $.sync', t => {
t.notThrows(() => $.sync('ava', ['--version'], {env: pathWitoutLocalDirectory}));
});
test('preferLocal: undefined with execa.pipe`...`', async t => {
await t.throwsAsync(() => execa('node', ['--version']).pipe({env: pathWitoutLocalDirectory})`ava --version`);
});
test('preferLocal: undefined with $.pipe`...`', async t => {
await t.notThrows(() => $('node', ['--version']).pipe({env: pathWitoutLocalDirectory})`ava --version`);
});
test('preferLocal: undefined with execa.pipe()', async t => {
await t.throwsAsync(() => execa('node', ['--version']).pipe('ava', ['--version'], {env: pathWitoutLocalDirectory}));
});
test('preferLocal: undefined with $.pipe()', async t => {
await t.notThrows(() => $('node', ['--version']).pipe('ava', ['--version'], {env: pathWitoutLocalDirectory}));
});
test('localDir option', async t => {
const command = isWindows ? 'echo %PATH%' : 'echo $PATH';
const {stdout} = await execa(command, {shell: true, preferLocal: true, localDir: '/test'});
const envPaths = stdout.split(path.delimiter);
t.true(envPaths.some(envPath => envPath.endsWith('.bin')));
});
test('localDir option can be a URL', async t => {
const command = isWindows ? 'echo %PATH%' : 'echo $PATH';
const {stdout} = await execa(command, {shell: true, preferLocal: true, localDir: pathToFileURL('/test')});
const envPaths = stdout.split(path.delimiter);
t.true(envPaths.some(envPath => envPath.endsWith('.bin')));
});
================================================
FILE: test/arguments/shell.js
================================================
import process from 'node:process';
import {pathToFileURL} from 'node:url';
import test from 'ava';
import which from 'which';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {identity} from '../helpers/stdio.js';
setFixtureDirectory();
process.env.FOO = 'foo';
const isWindows = process.platform === 'win32';
test('can use `options.shell: true`', async t => {
const {stdout} = await execa('node test/fixtures/noop.js foo', {shell: true});
t.is(stdout, 'foo');
});
const testShellPath = async (t, mapPath) => {
const shellPath = isWindows ? 'cmd.exe' : 'bash';
const shell = mapPath(await which(shellPath));
const {stdout} = await execa('node test/fixtures/noop.js foo', {shell});
t.is(stdout, 'foo');
};
test('can use `options.shell: string`', testShellPath, identity);
test('can use `options.shell: file URL`', testShellPath, pathToFileURL);
================================================
FILE: test/arguments/specific.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
setFixtureDirectory();
// eslint-disable-next-line max-params
const testPriorityOrder = async (t, buffer, bufferStdout, bufferStderr, execaMethod) => {
const {stdout, stderr} = await execaMethod('noop-both.js', {buffer});
t.is(stdout, bufferStdout ? foobarString : undefined);
t.is(stderr, bufferStderr ? foobarString : undefined);
};
test('buffer: {stdout, fd1}', testPriorityOrder, {stdout: true, fd1: false}, true, true, execa);
test('buffer: {stdout, all}', testPriorityOrder, {stdout: true, all: false}, true, false, execa);
test('buffer: {fd1, all}', testPriorityOrder, {fd1: true, all: false}, true, false, execa);
test('buffer: {stderr, fd2}', testPriorityOrder, {stderr: true, fd2: false}, true, true, execa);
test('buffer: {stderr, all}', testPriorityOrder, {stderr: true, all: false}, false, true, execa);
test('buffer: {fd2, all}', testPriorityOrder, {fd2: true, all: false}, false, true, execa);
test('buffer: {fd1, stdout}', testPriorityOrder, {fd1: false, stdout: true}, true, true, execa);
test('buffer: {all, stdout}', testPriorityOrder, {all: false, stdout: true}, true, false, execa);
test('buffer: {all, fd1}', testPriorityOrder, {all: false, fd1: true}, true, false, execa);
test('buffer: {fd2, stderr}', testPriorityOrder, {fd2: false, stderr: true}, true, true, execa);
test('buffer: {all, stderr}', testPriorityOrder, {all: false, stderr: true}, false, true, execa);
test('buffer: {all, fd2}', testPriorityOrder, {all: false, fd2: true}, false, true, execa);
test('buffer: {stdout, fd1}, sync', testPriorityOrder, {stdout: true, fd1: false}, true, true, execaSync);
test('buffer: {stdout, all}, sync', testPriorityOrder, {stdout: true, all: false}, true, false, execaSync);
test('buffer: {fd1, all}, sync', testPriorityOrder, {fd1: true, all: false}, true, false, execaSync);
test('buffer: {stderr, fd2}, sync', testPriorityOrder, {stderr: true, fd2: false}, true, true, execaSync);
test('buffer: {stderr, all}, sync', testPriorityOrder, {stderr: true, all: false}, false, true, execaSync);
test('buffer: {fd2, all}, sync', testPriorityOrder, {fd2: true, all: false}, false, true, execaSync);
test('buffer: {fd1, stdout}, sync', testPriorityOrder, {fd1: false, stdout: true}, true, true, execaSync);
test('buffer: {all, stdout}, sync', testPriorityOrder, {all: false, stdout: true}, true, false, execaSync);
test('buffer: {all, fd1}, sync', testPriorityOrder, {all: false, fd1: true}, true, false, execaSync);
test('buffer: {fd2, stderr}, sync', testPriorityOrder, {fd2: false, stderr: true}, true, true, execaSync);
test('buffer: {all, stderr}, sync', testPriorityOrder, {all: false, stderr: true}, false, true, execaSync);
test('buffer: {all, fd2}, sync', testPriorityOrder, {all: false, fd2: true}, false, true, execaSync);
================================================
FILE: test/convert/concurrent.js
================================================
import {setTimeout} from 'node:timers/promises';
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
import {fullReadableStdio} from '../helpers/stdio.js';
import {
finishedStream,
assertStreamOutput,
assertStreamError,
assertStreamReadError,
assertSubprocessOutput,
assertSubprocessError,
getReadWriteSubprocess,
} from '../helpers/convert.js';
setFixtureDirectory();
const endStream = async stream => {
stream.end(foobarString);
await setTimeout(0);
};
// eslint-disable-next-line max-params
const endSameWritable = async (t, stream, secondStream, subprocess, fdNumber) => {
await endStream(stream);
t.true(subprocess.stdio[fdNumber].writable);
await endStream(secondStream);
t.false(subprocess.stdio[fdNumber].writable);
};
// eslint-disable-next-line max-params
const endDifferentWritable = async (t, stream, secondStream, subprocess, fdNumber = 0, secondFdNumber = 3) => {
await endStream(stream);
t.false(subprocess.stdio[fdNumber].writable);
t.true(subprocess.stdio[secondFdNumber].writable);
await endStream(secondStream);
t.false(subprocess.stdio[secondFdNumber].writable);
};
const testReadableTwice = async (t, fdNumber, from) => {
const subprocess = execa('noop-fd.js', [`${fdNumber}`, foobarString]);
const stream = subprocess.readable({from});
const secondStream = subprocess.readable({from});
await Promise.all([
assertStreamOutput(t, stream),
assertStreamOutput(t, secondStream),
]);
await assertSubprocessOutput(t, subprocess, foobarString, fdNumber);
};
test('Can call .readable() twice on same file descriptor', testReadableTwice, 1);
test('Can call .readable({from: "stderr"}) twice on same file descriptor', testReadableTwice, 2, 'stderr');
const testWritableTwice = async (t, fdNumber, to, options) => {
const subprocess = execa('stdin-fd.js', [`${fdNumber}`], options);
const stream = subprocess.writable({to});
const secondStream = subprocess.writable({to});
await Promise.all([
finishedStream(stream),
finishedStream(secondStream),
endSameWritable(t, stream, secondStream, subprocess, fdNumber),
]);
await assertSubprocessOutput(t, subprocess, `${foobarString}${foobarString}`);
};
test('Can call .writable() twice on same file descriptor', testWritableTwice, 0, undefined, {});
test('Can call .writable({to: "fd3"}) twice on same file descriptor', testWritableTwice, 3, 'fd3', fullReadableStdio());
const testDuplexTwice = async (t, fdNumber, to, options) => {
const subprocess = execa('stdin-fd.js', [`${fdNumber}`], options);
const stream = subprocess.duplex({to});
const secondStream = subprocess.duplex({to});
const expectedOutput = `${foobarString}${foobarString}`;
await Promise.all([
assertStreamOutput(t, stream, expectedOutput),
assertStreamOutput(t, secondStream, expectedOutput),
endSameWritable(t, stream, secondStream, subprocess, fdNumber),
]);
await assertSubprocessOutput(t, subprocess, expectedOutput);
};
test('Can call .duplex() twice on same file descriptor', testDuplexTwice, 0, undefined, {});
test('Can call .duplex({to: "fd3"}) twice on same file descriptor', testDuplexTwice, 3, 'fd3', fullReadableStdio());
test('Can call .duplex() twice on same readable file descriptor but different writable one', async t => {
const subprocess = execa('stdin-fd-both.js', ['3'], fullReadableStdio());
const stream = subprocess.duplex();
const secondStream = subprocess.duplex({to: 'fd3'});
const expectedOutput = `${foobarString}${foobarString}`;
await Promise.all([
assertStreamOutput(t, stream, expectedOutput),
assertStreamOutput(t, secondStream, expectedOutput),
endDifferentWritable(t, stream, secondStream, subprocess),
]);
await assertSubprocessOutput(t, subprocess, expectedOutput);
});
test('Can call .readable() twice on different file descriptors', async t => {
const subprocess = execa('noop-both.js', [foobarString]);
const stream = subprocess.readable();
const secondStream = subprocess.readable({from: 'stderr'});
const expectedOutput = `${foobarString}\n`;
await Promise.all([
assertStreamOutput(t, stream, expectedOutput),
assertStreamOutput(t, secondStream, expectedOutput),
]);
await assertSubprocessOutput(t, subprocess);
await assertSubprocessOutput(t, subprocess, foobarString, 2);
});
test('Can call .writable() twice on different file descriptors', async t => {
const subprocess = execa('stdin-fd-both.js', ['3'], fullReadableStdio());
const stream = subprocess.writable();
const secondStream = subprocess.writable({to: 'fd3'});
await Promise.all([
finishedStream(stream),
finishedStream(secondStream),
endDifferentWritable(t, stream, secondStream, subprocess),
]);
await assertSubprocessOutput(t, subprocess, `${foobarString}${foobarString}`);
});
test('Can call .duplex() twice on different file descriptors', async t => {
const subprocess = execa('stdin-twice-both.js', ['3'], fullReadableStdio());
const stream = subprocess.duplex();
const secondStream = subprocess.duplex({from: 'stderr', to: 'fd3'});
await Promise.all([
assertStreamOutput(t, stream),
assertStreamOutput(t, secondStream),
endDifferentWritable(t, stream, secondStream, subprocess),
]);
await assertSubprocessOutput(t, subprocess);
await assertSubprocessOutput(t, subprocess, foobarString, 2);
});
test('Can call .readable() and .writable()', async t => {
const subprocess = getReadWriteSubprocess();
const stream = subprocess.writable();
const secondStream = subprocess.readable();
stream.end(foobarString);
await Promise.all([
finishedStream(stream),
assertStreamOutput(t, secondStream),
]);
await assertSubprocessOutput(t, subprocess);
});
test('Can call .writable() and .duplex()', async t => {
const subprocess = execa('stdin-fd-both.js', ['3'], fullReadableStdio());
const stream = subprocess.duplex();
const secondStream = subprocess.writable({to: 'fd3'});
const expectedOutput = `${foobarString}${foobarString}`;
await Promise.all([
assertStreamOutput(t, stream, expectedOutput),
finishedStream(secondStream),
endDifferentWritable(t, stream, secondStream, subprocess),
]);
await assertSubprocessOutput(t, subprocess, expectedOutput);
});
test('Can call .readable() and .duplex()', async t => {
const subprocess = execa('stdin-both.js');
const stream = subprocess.duplex();
const secondStream = subprocess.readable({from: 'stderr'});
stream.end(foobarString);
await Promise.all([
assertStreamOutput(t, stream),
assertStreamOutput(t, secondStream),
]);
await assertSubprocessOutput(t, subprocess);
await assertSubprocessOutput(t, subprocess, foobarString, 2);
});
test('Can error one of two .readable() on same file descriptor', async t => {
const subprocess = execa('noop-fd.js', ['1', foobarString]);
const stream = subprocess.readable();
const secondStream = subprocess.readable();
const cause = new Error(foobarString);
stream.destroy(cause);
await Promise.all([
assertStreamReadError(t, stream, cause),
assertStreamOutput(t, secondStream),
]);
await assertSubprocessOutput(t, subprocess);
});
test('Can error both .readable() on same file descriptor', async t => {
const subprocess = execa('noop-fd.js', ['1', foobarString]);
const stream = subprocess.readable();
const secondStream = subprocess.readable();
const cause = new Error(foobarString);
stream.destroy(cause);
secondStream.destroy(cause);
const [error, secondError] = await Promise.all([
assertStreamReadError(t, stream, {cause}),
assertStreamReadError(t, secondStream, {cause}),
]);
t.is(error, secondError);
await assertSubprocessError(t, subprocess, error);
});
test('Can error one of two .readable() on different file descriptors', async t => {
const subprocess = execa('noop-both.js', [foobarString]);
const stream = subprocess.readable();
const secondStream = subprocess.readable({from: 'stderr'});
const cause = new Error(foobarString);
stream.destroy(cause);
const [error, secondError] = await Promise.all([
assertStreamReadError(t, stream, {cause}),
assertStreamReadError(t, secondStream, {cause}),
]);
t.is(error, secondError);
t.is(error.stderr, foobarString);
await assertSubprocessError(t, subprocess, error);
});
test('Can error both .readable() on different file descriptors', async t => {
const subprocess = execa('noop-both.js', [foobarString]);
const stream = subprocess.readable();
const secondStream = subprocess.readable({from: 'stderr'});
const cause = new Error(foobarString);
stream.destroy(cause);
secondStream.destroy(cause);
const [error, secondError] = await Promise.all([
assertStreamReadError(t, stream, {cause}),
assertStreamReadError(t, secondStream, {cause}),
]);
t.is(error, secondError);
await assertSubprocessError(t, subprocess, error);
});
test('Can error one of two .writable() on same file descriptor', async t => {
const subprocess = execa('stdin.js');
const stream = subprocess.writable();
const secondStream = subprocess.writable();
const cause = new Error(foobarString);
stream.destroy(cause);
secondStream.end(foobarString);
await Promise.all([
assertStreamError(t, stream, cause),
finishedStream(secondStream),
]);
await assertSubprocessOutput(t, subprocess);
});
test('Can error both .writable() on same file descriptor', async t => {
const subprocess = execa('stdin.js');
const stream = subprocess.writable();
const secondStream = subprocess.writable();
const cause = new Error(foobarString);
stream.destroy(cause);
secondStream.destroy(cause);
const [error, secondError] = await Promise.all([
assertStreamError(t, stream, {cause}),
assertStreamError(t, secondStream, {cause}),
]);
t.is(error, secondError);
await assertSubprocessError(t, subprocess, error);
});
test('Can error one of two .writable() on different file descriptors', async t => {
const subprocess = execa('stdin-fd-both.js', ['3'], fullReadableStdio());
const stream = subprocess.writable();
const secondStream = subprocess.writable({to: 'fd3'});
const cause = new Error(foobarString);
stream.destroy(cause);
secondStream.end(foobarString);
const [error, secondError] = await Promise.all([
assertStreamError(t, stream, {cause}),
assertStreamError(t, secondStream, {cause}),
]);
t.is(error, secondError);
t.is(error.stdout, foobarString);
await assertSubprocessError(t, subprocess, error);
});
test('Can error both .writable() on different file descriptors', async t => {
const subprocess = execa('stdin-fd-both.js', ['3'], fullReadableStdio());
const stream = subprocess.writable();
const secondStream = subprocess.writable({to: 'fd3'});
const cause = new Error(foobarString);
stream.destroy(cause);
secondStream.destroy(cause);
const [error, secondError] = await Promise.all([
assertStreamError(t, stream, {cause}),
assertStreamError(t, secondStream, {cause}),
]);
t.is(error, secondError);
await assertSubprocessError(t, subprocess, error);
});
test('Can error one of two .duplex() on same file descriptor', async t => {
const subprocess = execa('stdin.js');
const stream = subprocess.duplex();
const secondStream = subprocess.duplex();
const cause = new Error(foobarString);
stream.destroy(cause);
secondStream.end(foobarString);
await Promise.all([
assertStreamReadError(t, stream, cause),
assertStreamOutput(t, secondStream),
]);
await assertSubprocessOutput(t, subprocess);
});
test('Can error both .duplex() on same file descriptor', async t => {
const subprocess = execa('stdin.js');
const stream = subprocess.duplex();
const secondStream = subprocess.duplex();
const cause = new Error(foobarString);
stream.destroy(cause);
secondStream.destroy(cause);
await Promise.all([
assertStreamReadError(t, stream, cause),
assertStreamReadError(t, secondStream, cause),
]);
await assertSubprocessError(t, subprocess, {cause});
});
test('Can error one of two .duplex() on different file descriptors', async t => {
const subprocess = execa('stdin-twice-both.js', ['3'], fullReadableStdio());
const stream = subprocess.duplex();
const secondStream = subprocess.duplex({from: 'stderr', to: 'fd3'});
const cause = new Error(foobarString);
stream.destroy(cause);
secondStream.end(foobarString);
const [error] = await Promise.all([
assertStreamReadError(t, secondStream, {cause}),
assertStreamReadError(t, stream, cause),
]);
t.is(error.stderr, foobarString);
await assertSubprocessError(t, subprocess, error);
});
test('Can error both .duplex() on different file descriptors', async t => {
const subprocess = execa('stdin-twice-both.js', ['3'], fullReadableStdio());
const stream = subprocess.duplex();
const secondStream = subprocess.duplex({from: 'stderr', to: 'fd3'});
const cause = new Error(foobarString);
stream.destroy(cause);
secondStream.destroy(cause);
await Promise.all([
assertStreamReadError(t, stream, cause),
assertStreamReadError(t, secondStream, cause),
]);
await assertSubprocessError(t, subprocess, {cause});
});
================================================
FILE: test/convert/duplex.js
================================================
import {
compose,
Readable,
Writable,
PassThrough,
} from 'node:stream';
import {pipeline} from 'node:stream/promises';
import {text} from 'node:stream/consumers';
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {
finishedStream,
assertReadableAborted,
assertWritableAborted,
assertProcessNormalExit,
assertStreamOutput,
assertStreamError,
assertStreamReadError,
assertSubprocessOutput,
assertSubprocessError,
assertPromiseError,
getReadWriteSubprocess,
} from '../helpers/convert.js';
import {foobarString} from '../helpers/input.js';
import {majorNodeVersion} from '../helpers/node-version.js';
import {prematureClose, fullStdio, fullReadableStdio} from '../helpers/stdio.js';
import {defaultHighWaterMark} from '../helpers/stream.js';
setFixtureDirectory();
test('.duplex() success', async t => {
const subprocess = getReadWriteSubprocess();
const stream = subprocess.duplex();
t.true(stream instanceof Writable);
t.true(stream.writable);
t.true(stream instanceof Readable);
t.true(stream.readable);
stream.end(foobarString);
await assertStreamOutput(t, stream);
await assertSubprocessOutput(t, subprocess);
});
// eslint-disable-next-line max-params
const testReadableDuplexDefault = async (t, fdNumber, from, options, hasResult) => {
const subprocess = execa('noop-stdin-fd.js', [`${fdNumber}`], options);
const stream = subprocess.duplex({from});
stream.end(foobarString);
await assertStreamOutput(t, stream, hasResult ? foobarString : '');
await assertSubprocessOutput(t, subprocess, foobarString, fdNumber);
};
test('.duplex() can use stdout', testReadableDuplexDefault, 1, 'stdout', {}, true);
test('.duplex() can use stderr', testReadableDuplexDefault, 2, 'stderr', {}, true);
test('.duplex() can use output stdio[*]', testReadableDuplexDefault, 3, 'fd3', fullStdio, true);
test('.duplex() uses stdout by default', testReadableDuplexDefault, 1, undefined, {}, true);
test('.duplex() does not use stderr by default', testReadableDuplexDefault, 2, undefined, {}, false);
test('.duplex() does not use stdio[*] by default', testReadableDuplexDefault, 3, undefined, fullStdio, false);
test('.duplex() uses stdout even if stderr is "ignore"', testReadableDuplexDefault, 1, 'stdout', {stderr: 'ignore'}, true);
test('.duplex() uses stderr even if stdout is "ignore"', testReadableDuplexDefault, 2, 'stderr', {stdout: 'ignore'}, true);
test('.duplex() uses stdout if "all" is used', testReadableDuplexDefault, 1, 'all', {all: true}, true);
test('.duplex() uses stderr if "all" is used', testReadableDuplexDefault, 2, 'all', {all: true}, true);
const testWritableDuplexDefault = async (t, fdNumber, to, options) => {
const subprocess = execa('stdin-fd.js', [`${fdNumber}`], options);
const stream = subprocess.duplex({to});
stream.end(foobarString);
await assertStreamOutput(t, stream, foobarString);
await assertSubprocessOutput(t, subprocess);
};
test('.duplex() can use stdin', testWritableDuplexDefault, 0, 'stdin', {});
test('.duplex() can use input stdio[*]', testWritableDuplexDefault, 3, 'fd3', fullReadableStdio());
test('.duplex() uses stdin by default', testWritableDuplexDefault, 0, undefined, {});
test('.duplex() abort -> subprocess fail', async t => {
const subprocess = getReadWriteSubprocess();
const stream = subprocess.duplex();
const textPromise = text(stream);
stream.destroy();
const error = await t.throwsAsync(textPromise);
t.like(error, prematureClose);
assertProcessNormalExit(t, error);
assertWritableAborted(t, subprocess.stdin);
assertReadableAborted(t, subprocess.stdout);
t.true(subprocess.stderr.readableEnded);
await assertSubprocessError(t, subprocess, error);
});
test('.duplex() error -> subprocess fail', async t => {
const subprocess = getReadWriteSubprocess();
const stream = subprocess.duplex();
const cause = new Error(foobarString);
stream.destroy(cause);
const error = await assertStreamError(t, stream, {cause});
assertProcessNormalExit(t, error);
t.is(subprocess.stdin.errored, cause);
t.is(subprocess.stdout.errored, cause);
t.true(subprocess.stderr.readableEnded);
await assertSubprocessError(t, subprocess, error);
});
test('.duplex() can be used with Stream.pipeline()', async t => {
const subprocess = getReadWriteSubprocess();
const inputStream = Readable.from([foobarString]);
const stream = subprocess.duplex();
const outputStream = new PassThrough();
await pipeline(inputStream, stream, outputStream);
await finishedStream(inputStream);
await finishedStream(stream);
await assertStreamOutput(t, outputStream);
await assertSubprocessOutput(t, subprocess);
});
test('.duplex() can error with Stream.pipeline()', async t => {
const subprocess = execa('stdin-fail.js');
const inputStream = Readable.from([foobarString]);
const stream = subprocess.duplex();
const outputStream = new PassThrough();
const error = await t.throwsAsync(pipeline(inputStream, stream, outputStream));
assertProcessNormalExit(t, error, 2);
t.like(error, {stdout: foobarString});
await finishedStream(inputStream);
await assertStreamError(t, stream, error);
await assertStreamReadError(t, outputStream, error);
await assertSubprocessError(t, subprocess, error);
});
test('.duplex() can pipe to errored stream with Stream.pipeline()', async t => {
const subprocess = execa('stdin-fail.js');
const inputStream = Readable.from([foobarString]);
const stream = subprocess.duplex();
const outputStream = new PassThrough();
const cause = new Error('test');
outputStream.destroy(cause);
// Node 23 does not allow calling `stream.pipeline()` with an already errored stream
if (majorNodeVersion >= 23) {
outputStream.on('error', () => {});
stream.on('error', () => {});
await t.throwsAsync(pipeline(stream, outputStream), {code: 'ERR_STREAM_UNABLE_TO_PIPE'});
stream.end();
} else {
await assertPromiseError(t, pipeline(inputStream, stream, outputStream), cause);
await t.throwsAsync(finishedStream(stream));
await assertStreamError(t, inputStream, cause);
const error = await assertStreamError(t, stream, cause);
await assertStreamReadError(t, outputStream, cause);
await assertSubprocessError(t, subprocess, {cause: error});
}
});
test('.duplex() can be piped to errored stream with Stream.pipeline()', async t => {
const subprocess = execa('stdin-fail.js');
const inputStream = Readable.from([foobarString]);
const stream = subprocess.duplex();
const outputStream = new PassThrough();
const cause = new Error('test');
inputStream.destroy(cause);
await assertPromiseError(t, pipeline(inputStream, stream, outputStream), cause);
await t.throwsAsync(finishedStream(stream));
await assertStreamError(t, inputStream, cause);
const error = await assertStreamError(t, stream, cause);
await assertStreamReadError(t, outputStream, cause);
await assertSubprocessError(t, subprocess, {cause: error});
});
test('.duplex() can be used with Stream.compose()', async t => {
const subprocess = getReadWriteSubprocess();
const inputStream = Readable.from([foobarString]);
const stream = subprocess.duplex();
const outputStream = new PassThrough();
await assertStreamOutput(t, compose(inputStream, stream, outputStream));
await assertSubprocessOutput(t, subprocess);
});
test('.duplex() has the right highWaterMark', async t => {
const subprocess = getReadWriteSubprocess();
const stream = subprocess.duplex();
t.is(stream.readableHighWaterMark, defaultHighWaterMark);
t.is(stream.writableHighWaterMark, defaultHighWaterMark);
stream.end();
await text(stream);
});
================================================
FILE: test/convert/iterable.js
================================================
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
import {fullStdio, assertEpipe} from '../helpers/stdio.js';
import {
arrayFromAsync,
assertWritableAborted,
assertReadableAborted,
assertProcessNormalExit,
} from '../helpers/convert.js';
import {simpleFull, noNewlinesChunks} from '../helpers/lines.js';
setFixtureDirectory();
const partialArrayFromAsync = async (asyncIterable, lines = []) => {
// eslint-disable-next-line no-unreachable-loop
for await (const line of asyncIterable) {
lines.push(line);
break;
}
return lines;
};
const errorArrayFromAsync = async (t, cause, asyncIterable, lines = []) => {
const {value} = await asyncIterable.next();
lines.push(value);
await asyncIterable.throw(cause);
};
const throwsAsync = async (t, asyncIterable, arrayFromAsyncMethod) => {
const lines = [];
const error = await t.throwsAsync(arrayFromAsyncMethod(asyncIterable, lines));
return {error, lines};
};
const assertStdoutAbort = (t, subprocess, error, cause) => {
assertProcessNormalExit(t, error, 1);
assertEpipe(t, error.stderr);
assertWritableAborted(t, subprocess.stdin);
t.true(subprocess.stderr.readableEnded);
if (cause === undefined) {
assertReadableAborted(t, subprocess.stdout);
} else {
t.is(subprocess.stdout.errored, cause);
}
};
const testSuccess = async (t, fdNumber, from, options = {}) => {
const lines = await arrayFromAsync(execa('noop-fd.js', [`${fdNumber}`, simpleFull], options).iterable({from}));
t.deepEqual(lines, noNewlinesChunks);
};
test('Uses stdout by default', testSuccess, 1, undefined);
test('Can iterate successfully on stdout', testSuccess, 1, 'stdout');
test('Can iterate successfully on stderr', testSuccess, 2, 'stderr');
test('Can iterate successfully on stdio[*]', testSuccess, 3, 'fd3', fullStdio);
test('Can iterate successfully on all', async t => {
const lines = await arrayFromAsync(execa('noop-both.js', [simpleFull], {all: true}).iterable({from: 'all'}));
t.deepEqual(lines, [...noNewlinesChunks, ...noNewlinesChunks]);
});
test('Can iterate using Symbol.asyncIterator', async t => {
const lines = await arrayFromAsync(execa('noop-fd.js', ['1', simpleFull]));
t.deepEqual(lines, noNewlinesChunks);
});
const assertMultipleCalls = async (t, iterable, iterableTwo) => {
t.not(iterable, iterableTwo);
const lines = await arrayFromAsync(iterable);
const linesTwo = await arrayFromAsync(iterableTwo);
t.deepEqual(lines, linesTwo);
t.deepEqual(lines, noNewlinesChunks);
};
test('Can be called multiple times', async t => {
const subprocess = execa('noop-fd.js', ['1', simpleFull]);
const iterable = subprocess.iterable();
const iterableTwo = subprocess.iterable();
await assertMultipleCalls(t, iterable, iterableTwo);
});
test('Can be called on different file descriptors', async t => {
const subprocess = execa('noop-both.js', [simpleFull]);
const iterable = subprocess.iterable();
const iterableTwo = subprocess.iterable({from: 'stderr'});
await assertMultipleCalls(t, iterable, iterableTwo);
});
test('Wait for the subprocess exit', async t => {
const subprocess = execa('noop-delay.js', ['1', simpleFull]);
const linesPromise = arrayFromAsync(subprocess);
t.is(await Promise.race([linesPromise, subprocess]), await subprocess);
t.deepEqual(await linesPromise, noNewlinesChunks);
});
test('Wait for the subprocess exit on iterator.return()', async t => {
const subprocess = execa('noop-delay.js', ['1', simpleFull]);
const linesPromise = partialArrayFromAsync(subprocess);
t.is(await Promise.race([linesPromise, subprocess]), await subprocess);
t.deepEqual(await linesPromise, [noNewlinesChunks[0]]);
});
test('Wait for the subprocess exit on iterator.throw()', async t => {
const subprocess = execa('noop-delay.js', ['1', simpleFull]);
const cause = new Error(foobarString);
const lines = [];
const linesPromise = t.throwsAsync(errorArrayFromAsync(t, cause, subprocess.iterable(), lines));
t.is(await Promise.race([linesPromise, subprocess]), await subprocess);
t.deepEqual(lines, [noNewlinesChunks[0]]);
});
test('Abort stdout on iterator.return()', async t => {
const subprocess = execa('noop-repeat.js', ['1', simpleFull]);
const {error, lines} = await throwsAsync(t, subprocess, partialArrayFromAsync);
t.deepEqual(lines, [noNewlinesChunks[0]]);
assertStdoutAbort(t, subprocess, error);
t.is(error, await t.throwsAsync(subprocess));
});
test('Abort stdout on iterator.throw()', async t => {
const subprocess = execa('noop-repeat.js', ['1', simpleFull]);
const cause = new Error(foobarString);
const {error, lines} = await throwsAsync(t, subprocess.iterable(), errorArrayFromAsync.bind(undefined, t, cause));
t.deepEqual(lines, [noNewlinesChunks[0]]);
assertStdoutAbort(t, subprocess, error);
t.is(error, await t.throwsAsync(subprocess));
});
test('Propagate subprocess failure', async t => {
const subprocess = execa('noop-fail.js', ['1', simpleFull]);
const {error, lines} = await throwsAsync(t, subprocess, arrayFromAsync);
t.is(error, await t.throwsAsync(subprocess));
t.deepEqual(lines, noNewlinesChunks);
});
const testStdoutError = async (t, destroyStdout, isAbort, cause) => {
const subprocess = execa('noop-repeat.js', ['1', simpleFull]);
subprocess.stdout.once('data', () => {
destroyStdout(subprocess.stdout, cause);
});
const {error} = await throwsAsync(t, subprocess, arrayFromAsync);
t.is(error.cause, cause);
assertStdoutAbort(t, subprocess, error, isAbort ? undefined : cause);
t.is(error, await t.throwsAsync(subprocess));
};
test('Propagate stdout abort', testStdoutError, subprocessStdout => subprocessStdout.destroy(), true);
test('Propagate stdout error', testStdoutError, (subprocessStdout, cause) => subprocessStdout.destroy(cause), false, new Error(foobarString));
test('Propagate stdout "error" event', testStdoutError, (subprocessStdout, cause) => subprocessStdout.emit('error', cause), true, new Error(foobarString));
================================================
FILE: test/convert/readable.js
================================================
import {once} from 'node:events';
import {
compose,
Readable,
Writable,
PassThrough,
} from 'node:stream';
import {pipeline} from 'node:stream/promises';
import {text} from 'node:stream/consumers';
import {setTimeout} from 'node:timers/promises';
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {
finishedStream,
assertReadableAborted,
assertWritableAborted,
assertProcessNormalExit,
assertStreamOutput,
assertStreamChunks,
assertStreamError,
assertStreamReadError,
assertSubprocessOutput,
assertSubprocessError,
assertPromiseError,
getReadableSubprocess,
getReadWriteSubprocess,
} from '../helpers/convert.js';
import {foobarString, foobarBuffer, foobarObject} from '../helpers/input.js';
import {simpleFull} from '../helpers/lines.js';
import {majorNodeVersion} from '../helpers/node-version.js';
import {prematureClose, fullStdio} from '../helpers/stdio.js';
import {outputObjectGenerator, getOutputsAsyncGenerator} from '../helpers/generator.js';
import {defaultHighWaterMark, defaultObjectHighWaterMark} from '../helpers/stream.js';
setFixtureDirectory();
test('.readable() success', async t => {
const subprocess = getReadableSubprocess();
const stream = subprocess.readable();
t.false(stream instanceof Writable);
t.is(stream.writable, undefined);
t.true(stream instanceof Readable);
t.true(stream.readable);
await assertStreamOutput(t, stream);
await assertSubprocessOutput(t, subprocess);
});
// eslint-disable-next-line max-params
const testReadableDefault = async (t, fdNumber, from, options, hasResult) => {
const subprocess = execa('noop-fd.js', [`${fdNumber}`, foobarString], options);
const stream = subprocess.readable({from});
await assertStreamOutput(t, stream, hasResult ? foobarString : '');
await assertSubprocessOutput(t, subprocess, foobarString, fdNumber);
};
test('.readable() can use stdout', testReadableDefault, 1, 'stdout', {}, true);
test('.readable() can use stderr', testReadableDefault, 2, 'stderr', {}, true);
test('.readable() can use stdio[*]', testReadableDefault, 3, 'fd3', fullStdio, true);
test('.readable() uses stdout by default', testReadableDefault, 1, undefined, {}, true);
test('.readable() does not use stderr by default', testReadableDefault, 2, undefined, {}, false);
test('.readable() does not use stdio[*] by default', testReadableDefault, 3, undefined, fullStdio, false);
test('.readable() uses stdout even if stderr is "ignore"', testReadableDefault, 1, 'stdout', {stderr: 'ignore'}, true);
test('.readable() uses stderr even if stdout is "ignore"', testReadableDefault, 2, 'stderr', {stdout: 'ignore'}, true);
test('.readable() uses stdout if "all" is used', testReadableDefault, 1, 'all', {all: true}, true);
test('.readable() uses stderr if "all" is used', testReadableDefault, 2, 'all', {all: true}, true);
const testBuffering = async (t, methodName) => {
const subprocess = execa('noop-stdin-fd.js', ['1'], {buffer: false});
const stream = subprocess[methodName]();
subprocess.stdin.write(foobarString);
await once(subprocess.stdout, 'readable');
subprocess.stdin.end();
await assertStreamOutput(t, stream);
};
test('.readable() buffers until read', testBuffering, 'readable');
test('.duplex() buffers until read', testBuffering, 'duplex');
test('.readable() abort -> subprocess fail', async t => {
const subprocess = execa('noop-repeat.js');
const stream = subprocess.readable();
stream.destroy();
const error = await t.throwsAsync(text(stream));
assertProcessNormalExit(t, error, 1);
t.true(error.message.includes('EPIPE'));
assertWritableAborted(t, subprocess.stdin);
assertReadableAborted(t, subprocess.stdout);
t.true(subprocess.stderr.readableEnded);
await assertSubprocessError(t, subprocess, error);
});
test('.readable() error -> subprocess fail', async t => {
const subprocess = execa('noop-repeat.js');
const stream = subprocess.readable();
const cause = new Error(foobarString);
stream.destroy(cause);
const error = await assertStreamReadError(t, stream, {cause});
assertProcessNormalExit(t, error, 1);
t.true(error.message.includes('EPIPE'));
assertWritableAborted(t, subprocess.stdin);
t.is(subprocess.stdout.errored, cause);
t.true(subprocess.stderr.readableEnded);
await assertSubprocessError(t, subprocess, error);
});
const testStdoutAbort = async (t, methodName) => {
const subprocess = execa('ipc-echo.js', {ipc: true});
const stream = subprocess[methodName]();
subprocess.stdout.destroy();
await subprocess.sendMessage(foobarString);
const [error, message] = await Promise.all([
t.throwsAsync(finishedStream(stream)),
subprocess.getOneMessage(),
]);
t.like(error, prematureClose);
t.is(message, foobarString);
assertWritableAborted(t, subprocess.stdin);
assertReadableAborted(t, subprocess.stdout);
t.true(subprocess.stderr.readableEnded);
await assertSubprocessOutput(t, subprocess, '');
};
test('subprocess.stdout abort + no more writes -> .readable() error + subprocess success', testStdoutAbort, 'readable');
test('subprocess.stdout abort + no more writes -> .duplex() error + subprocess success', testStdoutAbort, 'duplex');
const testStdoutError = async (t, methodName) => {
const subprocess = execa('ipc-echo.js', {ipc: true});
const stream = subprocess[methodName]();
const cause = new Error(foobarString);
subprocess.stdout.destroy(cause);
await subprocess.sendMessage(foobarString);
const [error, message] = await Promise.all([
t.throwsAsync(finishedStream(stream)),
subprocess.getOneMessage(),
]);
t.is(message, foobarString);
t.is(error.cause, cause);
assertProcessNormalExit(t, error);
t.is(subprocess.stdout.errored, cause);
t.true(subprocess.stderr.readableEnded);
assertWritableAborted(t, subprocess.stdin);
await assertSubprocessError(t, subprocess, error);
};
test('subprocess.stdout error + no more writes -> .readable() error + subprocess fail', testStdoutError, 'readable');
test('subprocess.stdout error + no more writes -> .duplex() error + subprocess fail', testStdoutError, 'duplex');
const testStdinAbortWrites = async (t, methodName) => {
const subprocess = getReadWriteSubprocess();
const stream = subprocess[methodName]();
subprocess.stdout.destroy();
subprocess.stdin.end(foobarString);
const error = await t.throwsAsync(finishedStream(stream));
assertProcessNormalExit(t, error, 1);
t.true(subprocess.stdin.writableEnded);
assertReadableAborted(t, subprocess.stdout);
t.true(subprocess.stderr.readableEnded);
await assertSubprocessError(t, subprocess, error);
};
test('subprocess.stdout abort + more writes -> .readable() error + subprocess fail', testStdinAbortWrites, 'readable');
test('subprocess.stdout abort + more writes -> .duplex() error + subprocess fail', testStdinAbortWrites, 'duplex');
const testStdinErrorWrites = async (t, methodName) => {
const subprocess = getReadWriteSubprocess();
const stream = subprocess[methodName]();
const cause = new Error(foobarString);
subprocess.stdout.destroy(cause);
subprocess.stdin.end(foobarString);
const error = await assertStreamError(t, stream, {cause});
assertProcessNormalExit(t, error, 1);
t.true(subprocess.stdin.writableEnded);
t.is(subprocess.stdout.errored, cause);
t.true(subprocess.stderr.readableEnded);
await assertSubprocessError(t, subprocess, error);
};
test('subprocess.stdout error + more writes -> .readable() error + subprocess fail', testStdinErrorWrites, 'readable');
test('subprocess.stdout error + more writes -> .duplex() error + subprocess fail', testStdinErrorWrites, 'duplex');
test('.readable() can be used with Stream.pipeline()', async t => {
const subprocess = getReadableSubprocess();
const stream = subprocess.readable();
const outputStream = new PassThrough();
await pipeline(stream, outputStream);
await finishedStream(stream);
await assertStreamOutput(t, outputStream);
await assertSubprocessOutput(t, subprocess);
});
test('.readable() can error with Stream.pipeline()', async t => {
const subprocess = execa('noop-fail.js', ['1', foobarString]);
const stream = subprocess.readable();
const outputStream = new PassThrough();
const error = await t.throwsAsync(pipeline(stream, outputStream));
assertProcessNormalExit(t, error, 2);
t.like(error, {stdout: foobarString});
await assertStreamError(t, stream, error);
await assertStreamReadError(t, outputStream, error);
await assertSubprocessError(t, subprocess, error);
});
test('.readable() can pipe to errored stream with Stream.pipeline()', async t => {
const subprocess = getReadableSubprocess();
const stream = subprocess.readable();
const outputStream = new PassThrough();
const cause = new Error('test');
outputStream.destroy(cause);
// Node 23 does not allow calling `stream.pipeline()` with an already errored stream
if (majorNodeVersion >= 23) {
outputStream.on('error', () => {});
await t.throwsAsync(pipeline(stream, outputStream), {code: 'ERR_STREAM_UNABLE_TO_PIPE'});
} else {
await assertPromiseError(t, pipeline(stream, outputStream), cause);
await t.throwsAsync(finishedStream(stream));
const error = await assertStreamError(t, stream, cause);
await assertStreamReadError(t, outputStream, cause);
await assertSubprocessError(t, subprocess, {cause: error});
}
});
test('.readable() can be used with Stream.compose()', async t => {
const subprocess = getReadableSubprocess();
const stream = subprocess.readable();
const outputStream = new PassThrough();
await assertStreamOutput(t, compose(stream, outputStream));
await assertSubprocessOutput(t, subprocess);
});
test('.readable() works with objectMode', async t => {
const subprocess = execa('noop.js', {stdout: outputObjectGenerator()});
const stream = subprocess.readable();
t.true(stream.readableObjectMode);
t.is(stream.readableHighWaterMark, defaultObjectHighWaterMark);
await assertStreamChunks(t, stream, [foobarObject]);
await assertSubprocessOutput(t, subprocess, [foobarObject]);
});
test('.duplex() works with objectMode and reads', async t => {
const subprocess = getReadWriteSubprocess({stdout: outputObjectGenerator()});
const stream = subprocess.duplex();
t.true(stream.readableObjectMode);
t.is(stream.readableHighWaterMark, defaultObjectHighWaterMark);
t.false(stream.writableObjectMode);
t.is(stream.writableHighWaterMark, defaultHighWaterMark);
stream.end(foobarString);
await assertStreamChunks(t, stream, [foobarObject]);
await assertSubprocessOutput(t, subprocess, [foobarObject]);
});
test('.readable() works with default encoding', async t => {
const subprocess = getReadableSubprocess();
const stream = subprocess.readable();
t.is(stream.readableEncoding, null);
await assertStreamChunks(t, stream, [foobarBuffer]);
await assertSubprocessOutput(t, subprocess, foobarString);
});
test('.duplex() works with default encoding', async t => {
const subprocess = getReadWriteSubprocess();
const stream = subprocess.duplex();
t.is(stream.readableEncoding, null);
stream.end(foobarString);
await assertStreamChunks(t, stream, [foobarBuffer]);
await assertSubprocessOutput(t, subprocess, foobarString);
});
test('.readable() works with encoding "utf8"', async t => {
const subprocess = getReadableSubprocess();
subprocess.stdout.setEncoding('utf8');
const stream = subprocess.readable();
t.is(stream.readableEncoding, 'utf8');
await assertStreamChunks(t, stream, [foobarString]);
await assertSubprocessOutput(t, subprocess, foobarString);
});
test('.duplex() works with encoding "utf8"', async t => {
const subprocess = getReadWriteSubprocess();
subprocess.stdout.setEncoding('utf8');
const stream = subprocess.duplex();
t.is(stream.readableEncoding, 'utf8');
stream.end(foobarBuffer);
await assertStreamChunks(t, stream, [foobarString]);
await assertSubprocessOutput(t, subprocess, foobarString);
});
test('.readable() has the right highWaterMark', async t => {
const subprocess = execa('noop.js');
const stream = subprocess.readable();
t.is(stream.readableHighWaterMark, defaultHighWaterMark);
await text(stream);
});
test('.readable() can iterate over lines', async t => {
const subprocess = execa('noop-fd.js', ['1', simpleFull]);
const lines = [];
for await (const line of subprocess.readable({binary: false, preserveNewlines: false})) {
lines.push(line);
}
const expectedLines = ['aaa', 'bbb', 'ccc'];
t.deepEqual(lines, expectedLines);
await assertSubprocessOutput(t, subprocess, simpleFull);
});
test('.readable() can wait for data', async t => {
const subprocess = execa('noop.js', {stdout: getOutputsAsyncGenerator([foobarString, foobarString])(false, true)});
const stream = subprocess.readable();
t.is(stream.read(), null);
await once(stream, 'readable');
t.is(stream.read().toString(), foobarString);
t.is(stream.read(), null);
await once(stream, 'readable');
t.is(stream.read().toString(), foobarString);
t.is(stream.read(), null);
await once(stream, 'readable');
t.is(stream.read(), null);
await finishedStream(stream);
await assertSubprocessOutput(t, subprocess, `${foobarString}${foobarString}`);
});
const testBufferData = async (t, methodName) => {
const chunk = '.'.repeat(defaultHighWaterMark).repeat(2);
const subprocess = getReadWriteSubprocess();
const stream = subprocess[methodName]();
subprocess.stdin.end(chunk);
await assertStreamOutput(t, stream, chunk);
await assertSubprocessOutput(t, subprocess, chunk);
};
test('.readable() can buffer data', testBufferData, 'readable');
test('.duplex() can buffer data', testBufferData, 'duplex');
const assertDataEvents = async (t, stream, subprocess) => {
const [output] = await once(stream, 'data');
t.is(output.toString(), foobarString);
await finishedStream(stream);
await assertSubprocessOutput(t, subprocess);
};
test('.readable() can be read with "data" events', async t => {
const subprocess = getReadableSubprocess();
const stream = subprocess.readable();
await assertDataEvents(t, stream, subprocess);
});
test('.duplex() can be read with "data" events', async t => {
const subprocess = getReadWriteSubprocess();
const stream = subprocess.duplex();
stream.end(foobarString);
await assertDataEvents(t, stream, subprocess);
});
const assertPause = async (t, stream, subprocess) => {
const onceData = once(stream, 'data');
stream.pause();
t.is(stream.readableLength, 0);
do {
// eslint-disable-next-line no-await-in-loop
await setTimeout(10);
} while (stream.readableLength === 0);
t.false(await Promise.race([onceData, false]));
stream.resume();
const [output] = await onceData;
t.is(output.toString(), foobarString);
await finishedStream(stream);
await assertSubprocessOutput(t, subprocess);
};
test('.readable() can be paused', async t => {
const subprocess = getReadableSubprocess();
const stream = subprocess.readable();
await assertPause(t, stream, subprocess);
});
test('.duplex() can be paused', async t => {
const subprocess = getReadWriteSubprocess();
const stream = subprocess.duplex();
stream.end(foobarString);
await assertPause(t, stream, subprocess);
});
// This feature does not work on Node 18.
// @todo: remove after dropping support for Node 18.
if (majorNodeVersion >= 20) {
const testHighWaterMark = async (t, methodName) => {
const subprocess = execa('stdin.js');
const stream = subprocess[methodName]();
let count = 0;
const onPause = once(subprocess.stdout, 'pause');
for (; !subprocess.stdout.isPaused(); count += 1) {
subprocess.stdin.write('.');
// eslint-disable-next-line no-await-in-loop
await Promise.race([onPause, once(subprocess.stdout, 'data')]);
}
const expectedCount = defaultObjectHighWaterMark + 1;
const expectedOutput = '.'.repeat(expectedCount);
t.is(count, expectedCount);
subprocess.stdin.end();
await assertStreamOutput(t, stream, expectedOutput);
await assertSubprocessOutput(t, subprocess, expectedOutput);
};
test('.readable() pauses its buffering when too high', testHighWaterMark, 'readable');
test('.duplex() pauses its buffering when too high', testHighWaterMark, 'duplex');
}
const testBigOutput = async (t, methodName) => {
const bigChunk = '.'.repeat(1e6);
const subprocess = execa('stdin.js', {input: bigChunk});
const stream = subprocess[methodName]();
await assertStreamOutput(t, stream, bigChunk);
await assertSubprocessOutput(t, subprocess, bigChunk);
};
test('.readable() with big output', testBigOutput, 'readable');
test('.duplex() with big output', testBigOutput, 'duplex');
================================================
FILE: test/convert/shared.js
================================================
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {
finishedStream,
assertWritableAborted,
assertStreamError,
assertSubprocessError,
getReadWriteSubprocess,
} from '../helpers/convert.js';
import {foobarString} from '../helpers/input.js';
setFixtureDirectory();
const testSubprocessFail = async (t, methodName) => {
const subprocess = getReadWriteSubprocess();
const stream = subprocess[methodName]();
const cause = new Error(foobarString);
subprocess.kill(cause);
const error = await assertStreamError(t, stream, {cause});
assertWritableAborted(t, subprocess.stdin);
t.true(subprocess.stdout.readableEnded);
t.true(subprocess.stderr.readableEnded);
await assertSubprocessError(t, subprocess, error);
};
test('subprocess fail -> .readable() error', testSubprocessFail, 'readable');
test('subprocess fail -> .writable() error', testSubprocessFail, 'writable');
test('subprocess fail -> .duplex() error', testSubprocessFail, 'duplex');
const testErrorEvent = async (t, methodName) => {
const subprocess = execa('empty.js');
const stream = subprocess[methodName]();
t.is(stream.listenerCount('error'), 0);
stream.destroy();
await t.throwsAsync(finishedStream(stream));
};
test('.readable() requires listening to "error" event', testErrorEvent, 'readable');
test('.writable() requires listening to "error" event', testErrorEvent, 'writable');
test('.duplex() requires listening to "error" event', testErrorEvent, 'duplex');
const testSubprocessError = async (t, methodName) => {
const subprocess = getReadWriteSubprocess();
const stream = subprocess[methodName]();
const cause = new Error(foobarString);
subprocess.kill(cause);
await assertStreamError(t, stream, {cause});
};
test('Do not need to await subprocess with .readable()', testSubprocessError, 'readable');
test('Do not need to await subprocess with .writable()', testSubprocessError, 'writable');
test('Do not need to await subprocess with .duplex()', testSubprocessError, 'duplex');
================================================
FILE: test/convert/writable.js
================================================
import {once} from 'node:events';
import {compose, Readable, Writable} from 'node:stream';
import {pipeline} from 'node:stream/promises';
import {text} from 'node:stream/consumers';
import {setTimeout, scheduler} from 'node:timers/promises';
import {promisify} from 'node:util';
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {
finishedStream,
assertWritableAborted,
assertProcessNormalExit,
assertStreamOutput,
assertStreamError,
assertSubprocessOutput,
assertSubprocessError,
assertPromiseError,
getWritableSubprocess,
getReadableSubprocess,
getReadWriteSubprocess,
} from '../helpers/convert.js';
import {
foobarString,
foobarBuffer,
foobarObject,
foobarObjectString,
} from '../helpers/input.js';
import {prematureClose, fullReadableStdio} from '../helpers/stdio.js';
import {
throwingGenerator,
serializeGenerator,
noopAsyncGenerator,
} from '../helpers/generator.js';
import {defaultHighWaterMark, defaultObjectHighWaterMark} from '../helpers/stream.js';
setFixtureDirectory();
test('.writable() success', async t => {
const subprocess = getWritableSubprocess();
const stream = subprocess.writable();
t.true(stream instanceof Writable);
t.true(stream.writable);
t.false(stream instanceof Readable);
t.is(stream.readable, undefined);
stream.end(foobarString);
await finishedStream(stream);
await assertSubprocessOutput(t, subprocess, foobarString, 2);
});
const testWritableDefault = async (t, fdNumber, to, options) => {
const subprocess = execa('stdin-fd.js', [`${fdNumber}`], options);
const stream = subprocess.writable({to});
stream.end(foobarString);
await finishedStream(stream);
await assertSubprocessOutput(t, subprocess);
};
test('.writable() can use stdin', testWritableDefault, 0, 'stdin', {});
test('.writable() can use stdio[*]', testWritableDefault, 3, 'fd3', fullReadableStdio());
test('.writable() uses stdin by default', testWritableDefault, 0, undefined, {});
test('.writable() hangs until ended', async t => {
const subprocess = getReadWriteSubprocess();
const stream = subprocess.writable();
stream.write(foobarString);
await setTimeout(1e2);
stream.end();
await finishedStream(stream);
await assertSubprocessOutput(t, subprocess);
});
test('.duplex() hangs until ended', async t => {
const subprocess = getReadWriteSubprocess();
const stream = subprocess.duplex();
stream.write(foobarString);
await setTimeout(1e2);
stream.end();
await assertStreamOutput(t, stream);
await assertSubprocessOutput(t, subprocess);
});
const testEarlySuccess = async (t, methodName, hasWrites) => {
const subprocess = hasWrites ? getReadableSubprocess() : execa('empty.js');
const stream = subprocess[methodName]();
const error = await t.throwsAsync(finishedStream(stream));
t.like(error, prematureClose);
assertWritableAborted(t, subprocess.stdin);
t.true(subprocess.stdout.readableEnded);
t.true(subprocess.stderr.readableEnded);
await assertSubprocessOutput(t, subprocess, hasWrites ? foobarString : '');
};
test('subprocess early success with no writes -> .writable() abort', testEarlySuccess, 'writable', false);
test('subprocess early success with no writes -> .duplex() abort', testEarlySuccess, 'duplex', false);
test('subprocess early success with writes -> .writable() abort', testEarlySuccess, 'writable', true);
test('subprocess early success with writes -> .duplex() abort', testEarlySuccess, 'duplex', true);
test('.writable() abort -> subprocess fail', async t => {
const subprocess = getWritableSubprocess();
const stream = subprocess.writable();
stream.destroy();
const error = await t.throwsAsync(finishedStream(stream));
t.like(error, prematureClose);
assertProcessNormalExit(t, error);
assertWritableAborted(t, subprocess.stdin);
t.true(subprocess.stdout.readableEnded);
t.true(subprocess.stderr.readableEnded);
await assertSubprocessError(t, subprocess, error);
});
test('.writable() error -> subprocess fail', async t => {
const subprocess = getWritableSubprocess();
const stream = subprocess.writable();
const cause = new Error(foobarString);
stream.destroy(cause);
const error = await assertStreamError(t, stream, {cause});
assertProcessNormalExit(t, error);
t.is(subprocess.stdin.errored, cause);
t.true(subprocess.stdout.readableEnded);
t.true(subprocess.stderr.readableEnded);
await assertSubprocessError(t, subprocess, error);
});
test('.writable() EPIPE error -> subprocess success', async t => {
const subprocess = getWritableSubprocess();
const stream = subprocess.writable();
const error = new Error(foobarString);
error.code = 'EPIPE';
stream.destroy(error);
await assertStreamError(t, stream, error);
t.is(subprocess.stdin.errored, error);
t.true(subprocess.stdout.readableEnded);
t.true(subprocess.stderr.readableEnded);
await subprocess;
});
test('subprocess.stdin end -> .writable() end + subprocess success', async t => {
const subprocess = getReadWriteSubprocess();
const stream = subprocess.writable();
subprocess.stdin.end(foobarString);
await finishedStream(stream);
await assertSubprocessOutput(t, subprocess);
});
test('subprocess.stdin end -> .duplex() end + subprocess success', async t => {
const subprocess = getReadWriteSubprocess();
const stream = subprocess.duplex();
subprocess.stdin.end(foobarString);
await assertStreamOutput(t, stream);
await assertSubprocessOutput(t, subprocess);
});
const testStdinAbort = async (t, methodName) => {
const subprocess = getReadWriteSubprocess();
const stream = subprocess[methodName]();
subprocess.stdin.destroy();
const error = await t.throwsAsync(finishedStream(stream));
t.like(error, prematureClose);
assertProcessNormalExit(t, error);
assertWritableAborted(t, subprocess.stdin);
t.true(subprocess.stdout.readableEnded);
t.true(subprocess.stderr.readableEnded);
await assertSubprocessError(t, subprocess, error);
};
test('subprocess.stdin abort -> .writable() error + subprocess fail', testStdinAbort, 'writable');
test('subprocess.stdin abort -> .duplex() error + subprocess fail', testStdinAbort, 'duplex');
const testStdinError = async (t, methodName) => {
const subprocess = getReadWriteSubprocess();
const stream = subprocess[methodName]();
const cause = new Error(foobarString);
subprocess.stdin.destroy(cause);
const error = await assertStreamError(t, stream, {cause});
assertProcessNormalExit(t, error);
t.is(subprocess.stdin.errored, cause);
t.true(subprocess.stderr.readableEnded);
t.true(subprocess.stdout.readableEnded);
await assertSubprocessError(t, subprocess, error);
};
test('subprocess.stdin error -> .writable() error + subprocess fail', testStdinError, 'writable');
test('subprocess.stdin error -> .duplex() error + subprocess fail', testStdinError, 'duplex');
test('.writable() can be used with Stream.pipeline()', async t => {
const subprocess = getWritableSubprocess();
const inputStream = Readable.from([foobarString]);
const stream = subprocess.writable();
await pipeline(inputStream, stream);
await finishedStream(inputStream);
await finishedStream(stream);
await assertSubprocessOutput(t, subprocess, foobarString, 2);
});
test('.writable() can error with Stream.pipeline()', async t => {
const subprocess = execa('noop-stdin-fail.js', ['2']);
const inputStream = Readable.from([foobarString]);
const stream = subprocess.writable();
const error = await t.throwsAsync(pipeline(inputStream, stream));
assertProcessNormalExit(t, error, 2);
t.is(error.stderr, foobarString);
await finishedStream(inputStream);
await assertStreamError(t, stream, error);
await assertSubprocessError(t, subprocess, error);
});
test('.writable() can pipe to errored stream with Stream.pipeline()', async t => {
const subprocess = getWritableSubprocess();
const inputStream = Readable.from([foobarString]);
const stream = subprocess.writable();
const cause = new Error('test');
inputStream.destroy(cause);
await assertPromiseError(t, pipeline(inputStream, stream), cause);
await t.throwsAsync(finishedStream(stream));
await assertStreamError(t, inputStream, cause);
const error = await assertStreamError(t, stream, cause);
await assertSubprocessError(t, subprocess, {cause: error});
});
test('.writable() can be used with Stream.compose()', async t => {
const subprocess = getWritableSubprocess();
const inputStream = Readable.from([foobarString]);
const stream = subprocess.writable();
await finishedStream(compose(inputStream, stream));
await assertSubprocessOutput(t, subprocess, foobarString, 2);
});
test('.writable() works with objectMode', async t => {
const subprocess = getReadWriteSubprocess({stdin: serializeGenerator(true, true)});
const stream = subprocess.writable();
t.true(stream.writableObjectMode);
t.is(stream.writableHighWaterMark, defaultObjectHighWaterMark);
stream.end(foobarObject);
await finishedStream(stream);
await assertSubprocessOutput(t, subprocess, foobarObjectString);
});
test('.duplex() works with objectMode and writes', async t => {
const subprocess = getReadWriteSubprocess({stdin: serializeGenerator(true, true)});
const stream = subprocess.duplex();
t.false(stream.readableObjectMode);
t.is(stream.readableHighWaterMark, defaultHighWaterMark);
t.true(stream.writableObjectMode);
t.is(stream.writableHighWaterMark, defaultObjectHighWaterMark);
stream.end(foobarObject);
await assertStreamOutput(t, stream, foobarObjectString);
await assertSubprocessOutput(t, subprocess, foobarObjectString);
});
test('.writable() has the right highWaterMark', async t => {
const subprocess = getReadWriteSubprocess();
const stream = subprocess.writable();
t.is(stream.writableHighWaterMark, defaultHighWaterMark);
stream.end();
await finishedStream(stream);
});
const writeUntilFull = async (t, stream, subprocess) => {
const size = stream.writableHighWaterMark / 2;
const chunk = '.'.repeat(size);
t.is(subprocess.stdin.writableLength, 0);
t.is(stream.writableLength, 0);
t.false(subprocess.stdin.writableNeedDrain);
t.false(stream.writableNeedDrain);
t.true(stream.write(chunk));
t.is(subprocess.stdin.writableLength, size);
t.is(stream.writableLength, 0);
t.false(subprocess.stdin.writableNeedDrain);
t.false(stream.writableNeedDrain);
t.true(stream.write(chunk));
t.is(subprocess.stdin.writableLength, size * 2);
t.is(stream.writableLength, size);
t.true(subprocess.stdin.writableNeedDrain);
t.false(stream.writableNeedDrain);
t.false(stream.write(chunk));
t.is(subprocess.stdin.writableLength, size * 2);
t.is(stream.writableLength, size * 2);
t.true(subprocess.stdin.writableNeedDrain);
t.true(stream.writableNeedDrain);
await once(stream, 'drain');
stream.end();
return '.'.repeat(size * 3);
};
test('.writable() waits when its buffer is full', async t => {
const subprocess = getReadWriteSubprocess({stdin: noopAsyncGenerator(false, true)});
const stream = subprocess.writable();
const expectedOutput = await writeUntilFull(t, stream, subprocess);
await assertSubprocessOutput(t, subprocess, expectedOutput);
});
test('.duplex() waits when its buffer is full', async t => {
const subprocess = getReadWriteSubprocess({stdin: noopAsyncGenerator(false, true)});
const stream = subprocess.duplex();
const expectedOutput = await writeUntilFull(t, stream, subprocess);
await assertStreamOutput(t, stream, expectedOutput);
await assertSubprocessOutput(t, subprocess, expectedOutput);
});
const testPropagateError = async (t, methodName) => {
const cause = new Error(foobarString);
const subprocess = getReadWriteSubprocess({stdin: throwingGenerator(cause)()});
const stream = subprocess[methodName]();
stream.end('.');
await assertStreamError(t, stream, {cause});
};
test('.writable() propagates write errors', testPropagateError, 'writable');
test('.duplex() propagates write errors', testPropagateError, 'duplex');
const testWritev = async (t, methodName, waitForStream) => {
const subprocess = getReadWriteSubprocess({stdin: noopAsyncGenerator()});
const stream = subprocess[methodName]();
const chunk = '.'.repeat(stream.writableHighWaterMark);
stream.write(chunk);
t.true(stream.writableNeedDrain);
const [writeInOneTick] = await Promise.race([
Promise.all([true, promisify(stream.write.bind(stream))(chunk)]),
Promise.all([false, scheduler.yield()]),
]);
t.true(writeInOneTick);
stream.end();
await waitForStream(stream);
};
test('.writable() can use .writev()', testWritev, 'writable', finishedStream);
test('.duplex() can use .writev()', testWritev, 'duplex', text);
test('.writable() can set encoding', async t => {
const subprocess = getReadWriteSubprocess();
const stream = subprocess.writable();
stream.end(foobarBuffer.toString('hex'), 'hex');
await finishedStream(stream);
await assertSubprocessOutput(t, subprocess);
});
test('.duplex() can set encoding', async t => {
const subprocess = getReadWriteSubprocess();
const stream = subprocess.duplex();
stream.end(foobarBuffer.toString('hex'), 'hex');
await assertStreamOutput(t, stream);
await assertSubprocessOutput(t, subprocess);
});
================================================
FILE: test/fixtures/all-fail.js
================================================
#!/usr/bin/env node
import process from 'node:process';
console.log('std\nout');
console.error('std\nerr');
process.exitCode = 1;
================================================
FILE: test/fixtures/all.js
================================================
#!/usr/bin/env node
console.log('std\nout');
console.error('std\nerr');
================================================
FILE: test/fixtures/command with space.js
================================================
#!/usr/bin/env node
import process from 'node:process';
console.log(process.argv.slice(2).join('\n'));
================================================
FILE: test/fixtures/delay.js
================================================
#!/usr/bin/env node
import process from 'node:process';
const delay = Number(process.argv[2]);
setTimeout(() => {}, delay);
================================================
FILE: test/fixtures/detach.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {execa} from '../../index.js';
const subprocess = execa('forever.js', {detached: true});
console.log(subprocess.pid);
process.exit(0);
================================================
FILE: test/fixtures/echo-fail.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {getWriteStream} from '../helpers/fs.js';
console.log('stdout');
console.error('stderr');
getWriteStream(3).write('fd3');
process.exitCode = 1;
================================================
FILE: test/fixtures/echo.js
================================================
#!/usr/bin/env node
import process from 'node:process';
console.log(process.argv.slice(2).join('\n'));
================================================
FILE: test/fixtures/empty.js
================================================
#!/usr/bin/env node
================================================
FILE: test/fixtures/environment.js
================================================
#!/usr/bin/env node
import process from 'node:process';
console.log(process.env.FOO);
console.log(process.env.BAR);
================================================
FILE: test/fixtures/exit.js
================================================
#!/usr/bin/env node
import process from 'node:process';
process.exitCode = Number(process.argv[2]);
================================================
FILE: test/fixtures/fail.js
================================================
#!/usr/bin/env node
import process from 'node:process';
process.exitCode = 2;
================================================
FILE: test/fixtures/forever.js
================================================
#!/usr/bin/env node
setTimeout(() => {}, 1e8);
================================================
FILE: test/fixtures/graceful-disconnect.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {once} from 'node:events';
import {getCancelSignal, sendMessage} from 'execa';
import {onAbortedSignal} from '../helpers/graceful.js';
const cancelSignal = await getCancelSignal();
await onAbortedSignal(cancelSignal);
await Promise.all([
once(process, 'disconnect'),
sendMessage(cancelSignal.reason),
]);
================================================
FILE: test/fixtures/graceful-echo.js
================================================
#!/usr/bin/env node
import {getCancelSignal, sendMessage, getOneMessage} from 'execa';
await getCancelSignal();
await sendMessage(await getOneMessage());
================================================
FILE: test/fixtures/graceful-listener.js
================================================
#!/usr/bin/env node
import {getCancelSignal, sendMessage} from 'execa';
const id = setTimeout(() => {}, 1e8);
const cancelSignal = await getCancelSignal();
// eslint-disable-next-line unicorn/prefer-add-event-listener
cancelSignal.onabort = async () => {
await sendMessage(cancelSignal.reason);
clearTimeout(id);
};
await sendMessage('.');
================================================
FILE: test/fixtures/graceful-none.js
================================================
#!/usr/bin/env node
import {getCancelSignal} from 'execa';
await getCancelSignal();
================================================
FILE: test/fixtures/graceful-print.js
================================================
#!/usr/bin/env node
import {getCancelSignal} from 'execa';
import {onAbortedSignal} from '../helpers/graceful.js';
const cancelSignal = await getCancelSignal();
await onAbortedSignal(cancelSignal);
console.log(cancelSignal.reason);
================================================
FILE: test/fixtures/graceful-ref.js
================================================
#!/usr/bin/env node
import {getCancelSignal} from 'execa';
const cancelSignal = await getCancelSignal();
cancelSignal.addEventListener('abort', () => {});
================================================
FILE: test/fixtures/graceful-send-echo.js
================================================
#!/usr/bin/env node
import {getCancelSignal, getOneMessage, sendMessage} from 'execa';
import {onAbortedSignal} from '../helpers/graceful.js';
const message = await getOneMessage();
const cancelSignal = await getCancelSignal();
await sendMessage(message);
await onAbortedSignal(cancelSignal);
await sendMessage(cancelSignal.reason);
================================================
FILE: test/fixtures/graceful-send-fast.js
================================================
#!/usr/bin/env node
import {getCancelSignal, sendMessage} from 'execa';
const cancelSignal = await getCancelSignal();
await sendMessage(cancelSignal.aborted);
================================================
FILE: test/fixtures/graceful-send-print.js
================================================
#!/usr/bin/env node
import {getCancelSignal, sendMessage} from 'execa';
import {onAbortedSignal} from '../helpers/graceful.js';
const cancelSignal = await getCancelSignal();
await sendMessage(cancelSignal.aborted);
await onAbortedSignal(cancelSignal);
console.log(cancelSignal.reason);
================================================
FILE: test/fixtures/graceful-send-string.js
================================================
#!/usr/bin/env node
import {getCancelSignal, sendMessage} from 'execa';
import {foobarString} from '../helpers/input.js';
await getCancelSignal();
await sendMessage(foobarString);
================================================
FILE: test/fixtures/graceful-send-twice.js
================================================
#!/usr/bin/env node
import {getCancelSignal, sendMessage} from 'execa';
import {onAbortedSignal} from '../helpers/graceful.js';
const cancelSignal = await getCancelSignal();
await sendMessage(cancelSignal.aborted);
await onAbortedSignal(cancelSignal);
await sendMessage(cancelSignal.reason);
================================================
FILE: test/fixtures/graceful-send.js
================================================
#!/usr/bin/env node
import {getCancelSignal, sendMessage} from 'execa';
import {onAbortedSignal} from '../helpers/graceful.js';
const cancelSignal = await getCancelSignal();
await onAbortedSignal(cancelSignal);
await sendMessage(cancelSignal.reason);
================================================
FILE: test/fixtures/graceful-twice.js
================================================
#!/usr/bin/env node
import {getCancelSignal, sendMessage} from 'execa';
import {onAbortedSignal} from '../helpers/graceful.js';
await getCancelSignal();
const cancelSignal = await getCancelSignal();
await onAbortedSignal(cancelSignal);
await sendMessage(cancelSignal.reason);
================================================
FILE: test/fixtures/graceful-wait.js
================================================
#!/usr/bin/env node
import {getCancelSignal} from 'execa';
import {onAbortedSignal} from '../helpers/graceful.js';
const cancelSignal = await getCancelSignal();
await onAbortedSignal(cancelSignal);
================================================
FILE: test/fixtures/hello.cmd
================================================
ECHO Hello World
================================================
FILE: test/fixtures/hello.sh
================================================
echo Hello World
================================================
FILE: test/fixtures/ipc-any.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import * as execaExports from '../../index.js';
const methodName = process.argv[2];
await execaExports[methodName]();
================================================
FILE: test/fixtures/ipc-disconnect-get.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {getOneMessage} from '../../index.js';
process.disconnect();
console.log(process.channel);
await getOneMessage();
================================================
FILE: test/fixtures/ipc-disconnect.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {once} from 'node:events';
import * as execaExports from '../../index.js';
const methodName = process.argv[2];
if (process.channel !== null) {
await once(process, 'disconnect');
}
await execaExports[methodName]();
================================================
FILE: test/fixtures/ipc-echo-fail.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {sendMessage, getOneMessage} from '../../index.js';
await sendMessage(await getOneMessage());
process.exitCode = 1;
================================================
FILE: test/fixtures/ipc-echo-filter.js
================================================
#!/usr/bin/env node
import {sendMessage, getOneMessage} from '../../index.js';
import {foobarArray} from '../helpers/input.js';
const message = await getOneMessage({filter: message => message === foobarArray[1]});
await sendMessage(message);
================================================
FILE: test/fixtures/ipc-echo-item.js
================================================
#!/usr/bin/env node
import {sendMessage, getOneMessage} from '../../index.js';
const [message] = await getOneMessage();
await sendMessage(message);
================================================
FILE: test/fixtures/ipc-echo-twice-fail.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {sendMessage, getOneMessage} from '../../index.js';
const message = await getOneMessage();
await sendMessage(message);
await sendMessage(message);
process.exitCode = 1;
================================================
FILE: test/fixtures/ipc-echo-twice-wait.js
================================================
#!/usr/bin/env node
import {setTimeout} from 'node:timers/promises';
import {sendMessage, getOneMessage} from '../../index.js';
const message = await getOneMessage();
await sendMessage(message);
const secondMessage = await getOneMessage();
await sendMessage(secondMessage);
await setTimeout(1e3);
await sendMessage('.');
================================================
FILE: test/fixtures/ipc-echo-twice.js
================================================
#!/usr/bin/env node
import {sendMessage, getOneMessage} from '../../index.js';
const message = await getOneMessage();
await sendMessage(message);
const secondMessage = await getOneMessage();
await sendMessage(secondMessage);
================================================
FILE: test/fixtures/ipc-echo-wait.js
================================================
#!/usr/bin/env node
import {setTimeout} from 'node:timers/promises';
import {sendMessage, getOneMessage} from '../../index.js';
await setTimeout(1e3);
await sendMessage(await getOneMessage());
================================================
FILE: test/fixtures/ipc-echo.js
================================================
#!/usr/bin/env node
import {sendMessage, getOneMessage} from '../../index.js';
await sendMessage(await getOneMessage());
================================================
FILE: test/fixtures/ipc-get-filter-throw.js
================================================
#!/usr/bin/env node
import {getOneMessage} from '../../index.js';
import {foobarString} from '../helpers/input.js';
await getOneMessage({
filter() {
throw new Error(foobarString);
},
});
================================================
FILE: test/fixtures/ipc-get-io-error.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {getOneMessage} from '../../index.js';
import {mockSendIoError} from '../helpers/ipc.js';
mockSendIoError(process);
console.log(await getOneMessage());
================================================
FILE: test/fixtures/ipc-get-ref.js
================================================
#!/usr/bin/env node
import {getOneMessage} from '../../index.js';
getOneMessage();
================================================
FILE: test/fixtures/ipc-get-send-get.js
================================================
#!/usr/bin/env node
import {argv} from 'node:process';
import {sendMessage, getOneMessage} from '../../index.js';
import {alwaysPass} from '../helpers/ipc.js';
const filter = argv[2] === 'true' ? alwaysPass : undefined;
const message = await getOneMessage({filter});
await Promise.all([
getOneMessage({filter}),
sendMessage(message),
]);
================================================
FILE: test/fixtures/ipc-get-unref.js
================================================
#!/usr/bin/env node
import {getOneMessage} from '../../index.js';
getOneMessage({reference: false});
================================================
FILE: test/fixtures/ipc-get.js
================================================
#!/usr/bin/env node
import {getOneMessage} from '../../index.js';
await getOneMessage();
================================================
FILE: test/fixtures/ipc-iterate-back-serial.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {sendMessage, getOneMessage} from '../../index.js';
import {PARALLEL_COUNT} from '../helpers/parallel.js';
import {alwaysPass, getFirst} from '../helpers/ipc.js';
const filter = process.argv[2] === 'true' ? alwaysPass : undefined;
await sendMessage(await getOneMessage({filter}));
const promise = sendMessage(1);
process.emit('message', '.');
await promise;
const messages = Array.from({length: PARALLEL_COUNT}, (_, index) => index + 2);
for (const message of messages) {
// eslint-disable-next-line no-await-in-loop
await sendMessage(message);
}
const secondPromise = process.argv[3] === 'true'
? getFirst()
: getOneMessage({filter});
await sendMessage(await secondPromise);
================================================
FILE: test/fixtures/ipc-iterate-back.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {sendMessage, getOneMessage} from '../../index.js';
import {PARALLEL_COUNT} from '../helpers/parallel.js';
import {alwaysPass, getFirst} from '../helpers/ipc.js';
const filter = process.argv[2] === 'true' ? alwaysPass : undefined;
await sendMessage(await getOneMessage({filter}));
const messages = Array.from({length: PARALLEL_COUNT}, (_, index) => index + 1);
await Promise.all([
...messages.map(message => sendMessage(message)),
process.emit('message', '.'),
]);
const promise = process.argv[3] === 'true'
? getFirst()
: getOneMessage({filter});
await sendMessage(await promise);
================================================
FILE: test/fixtures/ipc-iterate-break.js
================================================
#!/usr/bin/env node
import {sendMessage, getEachMessage} from '../../index.js';
import {foobarString} from '../helpers/input.js';
const iterable = getEachMessage();
await sendMessage(foobarString);
// eslint-disable-next-line no-unreachable-loop
for await (const _ of iterable) {
break;
}
================================================
FILE: test/fixtures/ipc-iterate-error.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {sendMessage, getEachMessage} from '../../index.js';
import {foobarString} from '../helpers/input.js';
const echoMessages = async () => {
for await (const message of getEachMessage()) {
if (message === foobarString) {
break;
}
await sendMessage(message);
}
};
process.on('error', () => {});
// eslint-disable-next-line unicorn/prefer-top-level-await
const promise = echoMessages();
process.emit('error', new Error(foobarString));
await promise;
================================================
FILE: test/fixtures/ipc-iterate-io-error.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {getEachMessage} from '../../index.js';
import {mockSendIoError} from '../helpers/ipc.js';
mockSendIoError(process);
for await (const message of getEachMessage()) {
console.log(message);
}
================================================
FILE: test/fixtures/ipc-iterate-print.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {sendMessage, getEachMessage} from '../../index.js';
import {foobarString} from '../helpers/input.js';
const iterable = getEachMessage();
await sendMessage(foobarString);
for await (const message of iterable) {
if (message === foobarString) {
break;
}
process.stdout.write(`${message}`);
}
================================================
FILE: test/fixtures/ipc-iterate-ref.js
================================================
#!/usr/bin/env node
import {getEachMessage} from '../../index.js';
getEachMessage();
================================================
FILE: test/fixtures/ipc-iterate-send.js
================================================
#!/usr/bin/env node
import {sendMessage, getEachMessage} from '../../index.js';
import {foobarString} from '../helpers/input.js';
const iterable = getEachMessage();
await sendMessage(foobarString);
for await (const message of iterable) {
console.log(message);
}
================================================
FILE: test/fixtures/ipc-iterate-throw.js
================================================
#!/usr/bin/env node
import {sendMessage, getEachMessage} from '../../index.js';
import {foobarString} from '../helpers/input.js';
const iterable = getEachMessage();
await sendMessage(foobarString);
// eslint-disable-next-line no-unreachable-loop
for await (const message of iterable) {
throw new Error(message);
}
================================================
FILE: test/fixtures/ipc-iterate-twice.js
================================================
#!/usr/bin/env node
import {sendMessage, getEachMessage} from '../../index.js';
import {foobarString} from '../helpers/input.js';
for (let index = 0; index < 2; index += 1) {
// Intentionally not awaiting `sendMessage()` to avoid a race condition
sendMessage(foobarString);
// eslint-disable-next-line no-await-in-loop
for await (const message of getEachMessage()) {
if (message === foobarString) {
break;
}
await sendMessage(`${index}${message}`);
}
}
================================================
FILE: test/fixtures/ipc-iterate-unref.js
================================================
#!/usr/bin/env node
import {getEachMessage} from '../../index.js';
getEachMessage({reference: false});
================================================
FILE: test/fixtures/ipc-iterate.js
================================================
#!/usr/bin/env node
import {getEachMessage, sendMessage} from '../../index.js';
import {foobarString} from '../helpers/input.js';
for await (const message of getEachMessage()) {
if (message === foobarString) {
break;
}
await sendMessage(message);
}
================================================
FILE: test/fixtures/ipc-once-disconnect-get.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {getOneMessage} from '../../index.js';
await getOneMessage();
process.once('disconnect', () => {
console.log('.');
});
process.send('.');
================================================
FILE: test/fixtures/ipc-once-disconnect-send.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {sendMessage} from '../../index.js';
await sendMessage('.');
process.once('disconnect', () => {
console.log('.');
});
process.send('.');
================================================
FILE: test/fixtures/ipc-once-disconnect.js
================================================
#!/usr/bin/env node
import process from 'node:process';
process.send('.');
process.once('disconnect', () => {
console.log('.');
});
================================================
FILE: test/fixtures/ipc-once-message-get.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {getOneMessage} from '../../index.js';
await getOneMessage();
process.once('message', message => {
console.log(message);
});
process.send('.');
================================================
FILE: test/fixtures/ipc-once-message-send.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {sendMessage} from '../../index.js';
await sendMessage('.');
process.once('message', message => {
console.log(message);
});
================================================
FILE: test/fixtures/ipc-once-message.js
================================================
#!/usr/bin/env node
import process from 'node:process';
process.send('.');
process.once('message', message => {
console.log(message);
});
================================================
FILE: test/fixtures/ipc-print-many-each.js
================================================
#!/usr/bin/env node
import {argv} from 'node:process';
import {getEachMessage} from '../../index.js';
const count = Number(argv[2]);
for (let index = 0; index < count; index += 1) {
// eslint-disable-next-line no-await-in-loop, no-unreachable-loop
for await (const message of getEachMessage()) {
console.log(message);
break;
}
}
================================================
FILE: test/fixtures/ipc-print-many.js
================================================
#!/usr/bin/env node
import {argv} from 'node:process';
import {getOneMessage} from '../../index.js';
import {alwaysPass} from '../helpers/ipc.js';
const count = Number(argv[2]);
const filter = argv[3] === 'true' ? alwaysPass : undefined;
for (let index = 0; index < count; index += 1) {
// eslint-disable-next-line no-await-in-loop
console.log(await getOneMessage({filter}));
}
================================================
FILE: test/fixtures/ipc-process-error.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {getOneMessage, sendMessage} from '../../index.js';
import {foobarString} from '../helpers/input.js';
import {alwaysPass} from '../helpers/ipc.js';
process.on('error', () => {});
const filter = process.argv[2] === 'true' ? alwaysPass : undefined;
const promise = getOneMessage({filter});
process.emit('error', new Error(foobarString));
await sendMessage(await promise);
================================================
FILE: test/fixtures/ipc-process-send-get.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {foobarString} from '../helpers/input.js';
import {getOneMessage} from '../../index.js';
await getOneMessage();
process.send(foobarString, () => {
console.log('.');
});
================================================
FILE: test/fixtures/ipc-process-send-send.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {foobarString} from '../helpers/input.js';
import {sendMessage} from '../../index.js';
await sendMessage('.');
process.send(foobarString, () => {
console.log('.');
});
================================================
FILE: test/fixtures/ipc-process-send.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {foobarString} from '../helpers/input.js';
process.send(foobarString, () => {
console.log('.');
});
================================================
FILE: test/fixtures/ipc-replay.js
================================================
#!/usr/bin/env node
import {setTimeout} from 'node:timers/promises';
import {sendMessage, getOneMessage} from '../../index.js';
await sendMessage(await getOneMessage());
await setTimeout(1e3);
await sendMessage(await getOneMessage());
================================================
FILE: test/fixtures/ipc-send-argv.js
================================================
#!/usr/bin/env node
import {argv} from 'node:process';
import {sendMessage} from '../../index.js';
const message = argv[2];
await sendMessage(message);
================================================
FILE: test/fixtures/ipc-send-disconnect.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {sendMessage} from '../../index.js';
import {foobarString} from '../helpers/input.js';
await sendMessage(foobarString);
process.disconnect();
================================================
FILE: test/fixtures/ipc-send-echo-strict.js
================================================
#!/usr/bin/env node
import {setTimeout} from 'node:timers/promises';
import {sendMessage, getOneMessage} from '../../index.js';
await sendMessage('.', {strict: true});
await setTimeout(10);
await sendMessage(await getOneMessage());
================================================
FILE: test/fixtures/ipc-send-echo-wait.js
================================================
#!/usr/bin/env node
import {setTimeout} from 'node:timers/promises';
import {sendMessage, getOneMessage} from '../../index.js';
await sendMessage('.');
await setTimeout(1e3);
await sendMessage(await getOneMessage());
================================================
FILE: test/fixtures/ipc-send-error.js
================================================
#!/usr/bin/env node
import {sendMessage} from '../../index.js';
await sendMessage(0n);
================================================
FILE: test/fixtures/ipc-send-fail.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {sendMessage} from '../../index.js';
import {foobarString} from '../helpers/input.js';
await sendMessage(foobarString);
process.exitCode = 1;
================================================
FILE: test/fixtures/ipc-send-forever.js
================================================
#!/usr/bin/env node
import {sendMessage} from '../../index.js';
import {foobarString} from '../helpers/input.js';
await sendMessage(foobarString);
setTimeout(() => {}, 1e8);
================================================
FILE: test/fixtures/ipc-send-get.js
================================================
#!/usr/bin/env node
import {sendMessage, getOneMessage} from '../../index.js';
import {foobarString} from '../helpers/input.js';
await Promise.all([
getOneMessage(),
sendMessage(foobarString),
]);
================================================
FILE: test/fixtures/ipc-send-io-error.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {sendMessage} from '../../index.js';
import {mockSendIoError} from '../helpers/ipc.js';
mockSendIoError(process);
await sendMessage('.');
================================================
FILE: test/fixtures/ipc-send-json.js
================================================
#!/usr/bin/env node
import {argv} from 'node:process';
import {sendMessage} from '../../index.js';
const message = JSON.parse(argv[2]);
await sendMessage(message);
================================================
FILE: test/fixtures/ipc-send-many.js
================================================
#!/usr/bin/env node
import {argv} from 'node:process';
import {sendMessage} from '../../index.js';
const count = Number(argv[2]);
await Promise.all(Array.from({length: count}, (_, index) => sendMessage(index)));
================================================
FILE: test/fixtures/ipc-send-native.js
================================================
#!/usr/bin/env node
import process from 'node:process';
process.send('.');
================================================
FILE: test/fixtures/ipc-send-pid.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {execa, sendMessage} from '../../index.js';
const cleanup = process.argv[2] === 'true';
const detached = process.argv[3] === 'true';
const subprocess = execa('forever.js', {cleanup, detached});
await sendMessage(subprocess.pid);
await subprocess;
================================================
FILE: test/fixtures/ipc-send-print.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {sendMessage, getOneMessage} from '../../index.js';
import {foobarString} from '../helpers/input.js';
await sendMessage(foobarString);
process.stdout.write('.');
await getOneMessage();
================================================
FILE: test/fixtures/ipc-send-repeat.js
================================================
#!/usr/bin/env node
import {argv} from 'node:process';
import {sendMessage} from '../../index.js';
const count = Number(argv[2]);
for (let index = 0; index < count; index += 1) {
// eslint-disable-next-line no-await-in-loop
await sendMessage(index);
}
================================================
FILE: test/fixtures/ipc-send-strict-catch.js
================================================
#!/usr/bin/env node
import {sendMessage} from '../../index.js';
import {foobarString} from '../helpers/input.js';
try {
await sendMessage(foobarString, {strict: true});
} catch {
await sendMessage(foobarString);
}
================================================
FILE: test/fixtures/ipc-send-strict-get.js
================================================
#!/usr/bin/env node
import {sendMessage, getOneMessage} from '../../index.js';
import {foobarString} from '../helpers/input.js';
await sendMessage(foobarString, {strict: true});
await sendMessage(await getOneMessage());
================================================
FILE: test/fixtures/ipc-send-strict-listen.js
================================================
#!/usr/bin/env node
import {sendMessage, getOneMessage} from '../../index.js';
import {foobarString} from '../helpers/input.js';
const [message] = await Promise.all([
getOneMessage(),
sendMessage(foobarString, {strict: true}),
]);
await sendMessage(message);
================================================
FILE: test/fixtures/ipc-send-strict.js
================================================
#!/usr/bin/env node
import {sendMessage} from '../../index.js';
import {foobarString} from '../helpers/input.js';
await sendMessage(foobarString, {strict: true});
================================================
FILE: test/fixtures/ipc-send-twice.js
================================================
#!/usr/bin/env node
import {sendMessage} from '../../index.js';
import {foobarArray} from '../helpers/input.js';
await sendMessage(foobarArray[0]);
await sendMessage(foobarArray[1]);
================================================
FILE: test/fixtures/ipc-send-wait-print.js
================================================
#!/usr/bin/env node
import {setTimeout} from 'node:timers/promises';
import {sendMessage} from '../../index.js';
import {foobarString} from '../helpers/input.js';
await sendMessage(foobarString);
await setTimeout(100);
console.log('.');
================================================
FILE: test/fixtures/ipc-send.js
================================================
#!/usr/bin/env node
import {argv} from 'node:process';
import {sendMessage} from '../../index.js';
import {foobarString} from '../helpers/input.js';
const message = argv[2] || foobarString;
await sendMessage(message);
================================================
FILE: test/fixtures/max-buffer.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {getWriteStream} from '../helpers/fs.js';
const fdNumber = Number(process.argv[2]);
const bytes = '.'.repeat(Number(process.argv[3] || 1e7));
getWriteStream(fdNumber).write(bytes);
================================================
FILE: test/fixtures/nested/custom-event.js
================================================
export const getOptions = ({type, eventProperty}) => ({
verbose: (verboseLine, verboseObject) => verboseObject.type === type ? `${verboseObject[eventProperty]}` : undefined,
});
================================================
FILE: test/fixtures/nested/custom-json.js
================================================
export const getOptions = ({type}) => ({
verbose: (verboseLine, verboseObject) => verboseObject.type === type ? JSON.stringify(verboseObject) : undefined,
});
================================================
FILE: test/fixtures/nested/custom-object-stdout.js
================================================
import {foobarObject} from '../../helpers/input.js';
export const getOptions = () => ({
verbose: (verboseLine, {type}) => type === 'output' ? verboseLine : undefined,
stdout: {
* transform() {
yield foobarObject;
},
objectMode: true,
},
});
================================================
FILE: test/fixtures/nested/custom-option.js
================================================
export const getOptions = ({type, optionName}) => ({
verbose: (verboseLine, verboseObject) => verboseObject.type === type ? `${verboseObject.options[optionName]}` : undefined,
});
================================================
FILE: test/fixtures/nested/custom-print-function.js
================================================
export const getOptions = ({type, fdNumber, secondFdNumber}) => ({
verbose: {
[fdNumber](verboseLine, verboseObject) {
if (verboseObject.type === type) {
console.warn(verboseLine);
}
},
[secondFdNumber]: 'none',
},
});
================================================
FILE: test/fixtures/nested/custom-print-multiple.js
================================================
export const getOptions = ({type, fdNumber, secondFdNumber}) => ({
verbose: {
[fdNumber](verboseLine, verboseObject) {
if (verboseObject.type === type) {
console.warn(verboseLine);
}
},
[secondFdNumber]() {},
},
});
================================================
FILE: test/fixtures/nested/custom-print.js
================================================
export const getOptions = ({type, fdNumber}) => ({
verbose: setFdSpecific(
fdNumber,
(verboseLine, verboseObject) => verboseObject.type === type ? verboseLine : undefined,
),
});
const setFdSpecific = (fdNumber, option) => fdNumber === undefined
? option
: {[fdNumber]: option};
================================================
FILE: test/fixtures/nested/custom-result.js
================================================
export const getOptions = ({type}) => ({
verbose: (verboseLine, verboseObject) => verboseObject.type === type ? JSON.stringify(verboseObject.result) : undefined,
});
================================================
FILE: test/fixtures/nested/custom-return.js
================================================
export const getOptions = ({verboseOutput}) => ({
verbose(verboseLine, {type}) {
return type === 'command' ? verboseOutput : undefined;
},
});
================================================
FILE: test/fixtures/nested/custom-throw.js
================================================
export const getOptions = ({type, errorMessage}) => ({
verbose(verboseLine, verboseObject) {
if (verboseObject.type === type) {
throw new Error(errorMessage);
}
},
});
================================================
FILE: test/fixtures/nested/custom-uppercase.js
================================================
export const getOptions = () => ({
verbose(verboseLine, {type}) {
return type === 'command' ? verboseLine.replace('noop', 'NOOP') : undefined;
},
});
================================================
FILE: test/fixtures/nested/file-url.js
================================================
import {pathToFileURL} from 'node:url';
export const getOptions = ({stdout: {file}}) => ({stdout: pathToFileURL(file)});
================================================
FILE: test/fixtures/nested/generator-big-array.js
================================================
import {getOutputGenerator} from '../../helpers/generator.js';
const bigArray = Array.from({length: 100}, (_, index) => index);
export const getOptions = () => ({stdout: getOutputGenerator(bigArray)(true)});
================================================
FILE: test/fixtures/nested/generator-duplex.js
================================================
import {uppercaseBufferDuplex} from '../../helpers/duplex.js';
export const getOptions = () => ({stdout: uppercaseBufferDuplex()});
================================================
FILE: test/fixtures/nested/generator-object.js
================================================
import {outputObjectGenerator} from '../../helpers/generator.js';
export const getOptions = () => ({stdout: outputObjectGenerator()});
================================================
FILE: test/fixtures/nested/generator-string-object.js
================================================
import {getOutputGenerator} from '../../helpers/generator.js';
import {simpleFull} from '../../helpers/lines.js';
export const getOptions = () => ({stdout: getOutputGenerator(simpleFull)(true)});
================================================
FILE: test/fixtures/nested/generator-uppercase.js
================================================
import {uppercaseGenerator} from '../../helpers/generator.js';
export const getOptions = () => ({stdout: uppercaseGenerator()});
================================================
FILE: test/fixtures/nested/writable-web.js
================================================
export const getOptions = () => ({stdout: new WritableStream()});
================================================
FILE: test/fixtures/nested/writable.js
================================================
import process from 'node:process';
export const getOptions = () => ({stdout: process.stdout});
================================================
FILE: test/fixtures/nested-double.js
================================================
#!/usr/bin/env node
import {execa, getOneMessage} from '../../index.js';
const {file, commandArguments, options} = await getOneMessage();
const firstArguments = commandArguments.slice(0, -1);
const lastArgument = commandArguments.at(-1);
await Promise.all([
execa(file, [...firstArguments, lastArgument], options),
execa(file, [...firstArguments, lastArgument.toUpperCase()], options),
]);
================================================
FILE: test/fixtures/nested-fail.js
================================================
#!/usr/bin/env node
import {execa, getOneMessage} from '../../index.js';
const {file, commandArguments, options} = await getOneMessage();
const subprocess = execa(file, commandArguments, options);
subprocess.kill(new Error(commandArguments[0]));
await subprocess;
================================================
FILE: test/fixtures/nested-inherit.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {execa} from '../../index.js';
import {generatorsMap} from '../helpers/map.js';
const type = process.argv[2];
await execa('noop-fd.js', ['1'], {stdout: ['inherit', generatorsMap[type].uppercase()]});
================================================
FILE: test/fixtures/nested-multiple-stdin.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {execa, execaSync} from '../../index.js';
import {foobarString} from '../helpers/input.js';
import {parseStdioOption} from '../helpers/stdio.js';
const [stdioOption, isSyncString] = process.argv.slice(2);
const stdin = parseStdioOption(stdioOption);
const execaMethod = isSyncString === 'true' ? execaSync : execa;
await execaMethod('stdin.js', {input: foobarString, stdin, stdout: 'inherit'});
================================================
FILE: test/fixtures/nested-multiple-stdio-output.js
================================================
#!/usr/bin/env node
import {Buffer} from 'node:buffer';
import process from 'node:process';
import {execa, execaSync} from '../../index.js';
import {foobarString} from '../helpers/input.js';
import {getStdio, parseStdioOption} from '../helpers/stdio.js';
import {getWriteStream} from '../helpers/fs.js';
const [stdioOption, fdNumber, outerFdNumber, isSyncString, encoding] = process.argv.slice(2);
const stdioValue = parseStdioOption(stdioOption);
const execaMethod = isSyncString === 'true' ? execaSync : execa;
const {stdio} = await execaMethod('noop-fd.js', [fdNumber, foobarString], {...getStdio(Number(fdNumber), stdioValue), encoding});
getWriteStream(Number(outerFdNumber)).write(`nested ${Buffer.from(stdio[fdNumber]).toString()}`);
================================================
FILE: test/fixtures/nested-node.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {getWriteStream} from '../helpers/fs.js';
import {execa, execaNode} from '../../index.js';
const [fakeExecArgv, execaMethod, nodeOptions, file, ...commandArguments] = process.argv.slice(2);
if (fakeExecArgv !== '') {
process.execArgv = [fakeExecArgv];
}
const filteredNodeOptions = [nodeOptions].filter(Boolean);
const {stdout, stderr} = await (execaMethod === 'execaNode'
? execaNode(file, commandArguments, {nodeOptions: filteredNodeOptions})
: execa(file, commandArguments, {nodeOptions: filteredNodeOptions, node: true}));
console.log(stdout);
getWriteStream(3).write(stderr);
================================================
FILE: test/fixtures/nested-pipe-file.js
================================================
#!/usr/bin/env node
import {execa, getOneMessage} from '../../index.js';
const {
file,
commandArguments = [],
options: {
sourceOptions = {},
destinationFile,
destinationArguments = [],
destinationOptions = {},
},
} = await getOneMessage();
await execa(file, commandArguments, sourceOptions)
.pipe(destinationFile, destinationArguments, destinationOptions);
================================================
FILE: test/fixtures/nested-pipe-script.js
================================================
#!/usr/bin/env node
import {$, getOneMessage} from '../../index.js';
const {
file,
commandArguments = [],
options: {
sourceOptions = {},
destinationFile,
destinationArguments = [],
destinationOptions = {},
},
} = await getOneMessage();
await $(sourceOptions)`${file} ${commandArguments}`
.pipe(destinationOptions)`${destinationFile} ${destinationArguments}`;
================================================
FILE: test/fixtures/nested-pipe-stream.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {execa, getOneMessage} from '../../index.js';
const {file, commandArguments, options: {unpipe, ...options}} = await getOneMessage();
const subprocess = execa(file, commandArguments, options);
subprocess.stdout.pipe(process.stdout);
if (unpipe) {
subprocess.stdout.unpipe(process.stdout);
}
await subprocess;
================================================
FILE: test/fixtures/nested-pipe-subprocess.js
================================================
#!/usr/bin/env node
import {execa, getOneMessage} from '../../index.js';
const {file, commandArguments, options: {unpipe, ...options}} = await getOneMessage();
const source = execa(file, commandArguments, options);
const destination = execa('stdin.js');
const controller = new AbortController();
const subprocess = source.pipe(destination, {unpipeSignal: controller.signal});
if (unpipe) {
controller.abort();
destination.stdin.end();
}
try {
await subprocess;
} catch {}
================================================
FILE: test/fixtures/nested-pipe-subprocesses.js
================================================
#!/usr/bin/env node
import {execa, getOneMessage} from '../../index.js';
const {
file,
commandArguments = [],
options: {
sourceOptions = {},
destinationFile,
destinationArguments = [],
destinationOptions = {},
},
} = await getOneMessage();
await execa(file, commandArguments, sourceOptions)
.pipe(execa(destinationFile, destinationArguments, destinationOptions));
================================================
FILE: test/fixtures/nested-pipe-verbose.js
================================================
#!/usr/bin/env node
import {execa, getOneMessage, sendMessage} from '../../index.js';
import {getNestedOptions} from '../helpers/nested.js';
const {
file,
commandArguments = [],
options: {destinationFile, destinationArguments, ...options},
optionsFixture,
optionsInput,
} = await getOneMessage();
const commandOptions = await getNestedOptions(options, optionsFixture, optionsInput);
try {
const result = await execa(file, commandArguments, commandOptions)
.pipe(destinationFile, destinationArguments, commandOptions);
await sendMessage(result);
} catch (error) {
await sendMessage(error);
}
================================================
FILE: test/fixtures/nested-stdio.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {execa, execaSync} from '../../index.js';
import {parseStdioOption} from '../helpers/stdio.js';
const [stdioOption, fdNumber, isSyncString, file, ...commandArguments] = process.argv.slice(2);
const optionValue = parseStdioOption(stdioOption);
const isSync = isSyncString === 'true';
const stdio = ['ignore', 'inherit', 'inherit'];
stdio[fdNumber] = optionValue;
const execaMethod = isSync ? execaSync : execa;
const returnValue = execaMethod(file, [`${fdNumber}`, ...commandArguments], {stdio});
const shouldPipe = Array.isArray(optionValue) && optionValue.includes('pipe');
const fdReturnValue = returnValue.stdio[fdNumber];
const hasPipe = fdReturnValue !== undefined && fdReturnValue !== null;
if (shouldPipe && !hasPipe) {
throw new Error(`subprocess.stdio[${fdNumber}] is null.`);
}
if (!shouldPipe && hasPipe) {
throw new Error(`subprocess.stdio[${fdNumber}] should be null.`);
}
if (!isSync) {
await returnValue;
}
================================================
FILE: test/fixtures/nested-sync-tty.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import tty from 'node:tty';
import {execa, execaSync} from '../../index.js';
const mockIsatty = fdNumber => {
tty.isatty = fdNumberArgument => fdNumber === fdNumberArgument;
};
const originalIsatty = tty.isatty;
const unmockIsatty = () => {
tty.isatty = originalIsatty;
};
const [options, isSync, file, fdNumber, ...commandArguments] = process.argv.slice(2);
mockIsatty(Number(fdNumber));
try {
if (isSync === 'true') {
execaSync(file, [fdNumber, ...commandArguments], JSON.parse(options));
} else {
await execa(file, [fdNumber, ...commandArguments], JSON.parse(options));
}
} finally {
unmockIsatty();
}
================================================
FILE: test/fixtures/nested-write.js
================================================
#!/usr/bin/env node
import {writeFile} from 'node:fs/promises';
import {text} from 'node:stream/consumers';
import process from 'node:process';
const [filePath, bytes] = process.argv.slice(2);
const stdinString = await text(process.stdin);
await writeFile(filePath, `${stdinString} ${bytes}`);
================================================
FILE: test/fixtures/nested.js
================================================
#!/usr/bin/env node
import {
execa,
execaSync,
getOneMessage,
sendMessage,
} from '../../index.js';
import {getNestedOptions} from '../helpers/nested.js';
const {
isSync,
file,
commandArguments,
options,
optionsFixture,
optionsInput,
} = await getOneMessage();
const commandOptions = await getNestedOptions(options, optionsFixture, optionsInput);
try {
const result = isSync
? execaSync(file, commandArguments, commandOptions)
: await execa(file, commandArguments, commandOptions);
await sendMessage(result);
} catch (error) {
await sendMessage(error);
}
================================================
FILE: test/fixtures/no-await.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {once} from 'node:events';
import {execa} from '../../index.js';
const [options, file, ...commandArguments] = process.argv.slice(2);
execa(file, commandArguments, JSON.parse(options));
const [error] = await once(process, 'unhandledRejection');
console.log(error.shortMessage);
================================================
FILE: test/fixtures/no-killable.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {sendMessage} from '../../index.js';
const noop = () => {};
process.on('SIGTERM', noop);
process.on('SIGINT', noop);
await sendMessage('');
console.log('.');
setTimeout(noop, 1e8);
================================================
FILE: test/fixtures/non-executable.js
================================================
#!/usr/bin/env node
================================================
FILE: test/fixtures/noop-132.js
================================================
#!/usr/bin/env node
console.log(1);
console.warn(2);
setTimeout(() => {
console.log(3);
}, 1000);
================================================
FILE: test/fixtures/noop-both-fail-strict.js
================================================
#!/usr/bin/env node
import process from 'node:process';
const stdoutBytes = process.argv[2];
const stderrBytes = process.argv[3];
process.stdout.write(stdoutBytes);
process.stderr.write(stderrBytes);
process.exitCode = 1;
================================================
FILE: test/fixtures/noop-both-fail.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {foobarString} from '../helpers/input.js';
const bytes = process.argv[2] || foobarString;
console.log(bytes);
console.error(bytes);
process.exitCode = 1;
================================================
FILE: test/fixtures/noop-both.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {foobarString} from '../helpers/input.js';
const bytes = process.argv[2] || foobarString;
const bytesStderr = process.argv[3] || bytes;
console.log(bytes);
console.error(bytesStderr);
================================================
FILE: test/fixtures/noop-continuous.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {setTimeout} from 'node:timers/promises';
const bytes = process.argv[2];
for (const character of bytes) {
console.log(character);
// eslint-disable-next-line no-await-in-loop
await setTimeout(100);
}
================================================
FILE: test/fixtures/noop-delay.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {setTimeout} from 'node:timers/promises';
import {getWriteStream} from '../helpers/fs.js';
import {foobarString} from '../helpers/input.js';
const fdNumber = Number(process.argv[2]);
const bytes = process.argv[3] || foobarString;
getWriteStream(fdNumber).write(bytes);
await setTimeout(100);
================================================
FILE: test/fixtures/noop-fail.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {getWriteStream} from '../helpers/fs.js';
import {foobarString} from '../helpers/input.js';
const fdNumber = Number(process.argv[2]);
const bytes = process.argv[3] || foobarString;
getWriteStream(fdNumber).write(bytes);
process.exitCode = 2;
================================================
FILE: test/fixtures/noop-fd-ipc.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {promisify} from 'node:util';
import {sendMessage} from '../../index.js';
import {getWriteStream} from '../helpers/fs.js';
import {foobarString} from '../helpers/input.js';
const fdNumber = Number(process.argv[2]);
const bytes = process.argv[3] || foobarString;
const stream = getWriteStream(fdNumber);
await promisify(stream.write.bind(stream))(bytes);
await sendMessage('');
================================================
FILE: test/fixtures/noop-fd.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {getWriteStream} from '../helpers/fs.js';
import {foobarString} from '../helpers/input.js';
const fdNumber = Number(process.argv[2]);
const bytes = process.argv[3] || foobarString;
getWriteStream(fdNumber).write(bytes);
================================================
FILE: test/fixtures/noop-forever.js
================================================
#!/usr/bin/env node
import process from 'node:process';
const bytes = process.argv[2];
console.log(bytes);
setTimeout(() => {}, 1e8);
================================================
FILE: test/fixtures/noop-progressive.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {setTimeout} from 'node:timers/promises';
const bytes = process.argv[2];
for (const character of bytes) {
process.stdout.write(character);
// eslint-disable-next-line no-await-in-loop
await setTimeout(10);
}
process.stdout.write('\n');
================================================
FILE: test/fixtures/noop-repeat.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {getWriteStream} from '../helpers/fs.js';
import {foobarString} from '../helpers/input.js';
const fdNumber = Number(process.argv[2]) || 1;
const bytes = process.argv[3] || foobarString;
setInterval(() => {
getWriteStream(fdNumber).write(bytes);
}, 10);
================================================
FILE: test/fixtures/noop-stdin-double.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {text} from 'node:stream/consumers';
const bytes = process.argv[2];
const stdinString = await text(process.stdin);
console.log(`${stdinString} ${bytes}`);
================================================
FILE: test/fixtures/noop-stdin-fail.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {text} from 'node:stream/consumers';
import {getWriteStream} from '../helpers/fs.js';
const fdNumber = Number(process.argv[2]);
const stdinString = await text(process.stdin);
getWriteStream(fdNumber).write(stdinString);
process.exitCode = 2;
================================================
FILE: test/fixtures/noop-stdin-fd.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {getWriteStream} from '../helpers/fs.js';
const fdNumber = Number(process.argv[2]);
process.stdin.pipe(getWriteStream(fdNumber));
================================================
FILE: test/fixtures/noop-verbose.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {sendMessage} from '../../index.js';
const bytes = process.argv[2];
console.log(bytes);
try {
await sendMessage(bytes);
} catch {}
process.exitCode = 2;
================================================
FILE: test/fixtures/noop.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {foobarString} from '../helpers/input.js';
const bytes = process.argv[2] || foobarString;
console.log(bytes);
================================================
FILE: test/fixtures/stdin-both.js
================================================
#!/usr/bin/env node
import process from 'node:process';
process.stdin.pipe(process.stdout);
process.stdin.pipe(process.stderr);
================================================
FILE: test/fixtures/stdin-fail.js
================================================
#!/usr/bin/env node
import process from 'node:process';
process.stdin.pipe(process.stdout);
process.exitCode = 2;
================================================
FILE: test/fixtures/stdin-fd-both.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {getReadStream} from '../helpers/fs.js';
const fdNumber = Number(process.argv[2]);
process.stdin.pipe(process.stdout);
getReadStream(fdNumber).pipe(process.stdout);
================================================
FILE: test/fixtures/stdin-fd.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {getReadStream} from '../helpers/fs.js';
const fdNumber = Number(process.argv[2]);
getReadStream(fdNumber).pipe(process.stdout);
================================================
FILE: test/fixtures/stdin-script.js
================================================
#!/usr/bin/env node
import {$} from '../../index.js';
import {FIXTURES_DIRECTORY} from '../helpers/fixtures-directory.js';
await $({stdout: 'inherit'})`node ${`${FIXTURES_DIRECTORY}/stdin.js`}`;
================================================
FILE: test/fixtures/stdin-twice-both.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {getReadStream} from '../helpers/fs.js';
const fdNumber = Number(process.argv[2]);
process.stdin.pipe(process.stdout);
getReadStream(fdNumber).pipe(process.stderr);
================================================
FILE: test/fixtures/stdin.js
================================================
#!/usr/bin/env node
import process from 'node:process';
process.stdin.pipe(process.stdout);
================================================
FILE: test/fixtures/verbose-script.js
================================================
#!/usr/bin/env node
import {$} from '../../index.js';
const $$ = $({stdio: 'inherit'});
await $$`node -e console.error(1)`;
await $$({reject: false})`node -e process.exit(2)`;
================================================
FILE: test/fixtures/wait-fail.js
================================================
#!/usr/bin/env node
import process from 'node:process';
import {setTimeout} from 'node:timers/promises';
await setTimeout(1e3);
process.exitCode = 2;
================================================
FILE: test/fixtures/worker.js
================================================
#!/usr/bin/env node
import {workerData, parentPort} from 'node:worker_threads';
import {spawnParentProcess} from '../helpers/nested.js';
try {
const result = await spawnParentProcess(workerData);
parentPort.postMessage(result);
} catch (error) {
parentPort.postMessage(error);
}
================================================
FILE: test/helpers/convert.js
================================================
import {text} from 'node:stream/consumers';
import {finished} from 'node:stream/promises';
import getStream from 'get-stream';
import isPlainObj from 'is-plain-obj';
import {execa} from '../../index.js';
import {foobarString} from '../helpers/input.js';
export const arrayFromAsync = async (asyncIterable, lines = []) => {
for await (const line of asyncIterable) {
lines.push(line);
}
return lines;
};
export const finishedStream = stream => finished(stream, {cleanup: true});
export const assertWritableAborted = (t, writable) => {
t.false(writable.writableEnded);
t.is(writable.errored, null);
t.false(writable.writable);
};
export const assertReadableAborted = (t, readable) => {
t.false(readable.readableEnded);
t.is(readable.errored, null);
t.false(readable.readable);
};
export const assertProcessNormalExit = (t, error, exitCode = 0) => {
t.is(error.exitCode, exitCode);
t.is(error.signal, undefined);
};
export const assertStreamOutput = async (t, stream, expectedOutput = foobarString) => {
t.is(await text(stream), expectedOutput);
};
export const assertStreamDataEvents = async (t, stream, expectedOutput = foobarString) => {
t.is(await getStream(stream), expectedOutput);
};
export const assertIterableChunks = async (t, asyncIterable, expectedChunks) => {
t.deepEqual(await arrayFromAsync(asyncIterable), expectedChunks);
};
export const assertStreamChunks = async (t, stream, expectedOutput) => {
t.deepEqual(await stream.toArray(), expectedOutput);
};
export const assertSubprocessOutput = async (t, subprocess, expectedOutput = foobarString, fdNumber = 1) => {
const result = await subprocess;
t.deepEqual(result.stdio[fdNumber], expectedOutput);
};
export const assertStreamError = (t, stream, error) => assertPromiseError(t, finishedStream(stream), error);
export const assertStreamReadError = (t, stream, error) => assertPromiseError(t, text(stream), error);
export const assertSubprocessError = (t, subprocess, error) => assertPromiseError(t, subprocess, error);
export const assertPromiseError = async (t, promise, error) => {
const thrownError = await t.throwsAsync(promise);
if (isPlainObj(error) && error.cause !== undefined) {
t.is(thrownError.cause, error.cause);
} else {
t.is(thrownError, error);
}
return thrownError;
};
export const getReadableSubprocess = (output = foobarString, options = {}) => execa('noop-fd.js', ['1', output], options);
export const getWritableSubprocess = () => execa('noop-stdin-fd.js', ['2']);
export const getReadWriteSubprocess = options => execa('stdin.js', options);
================================================
FILE: test/helpers/duplex.js
================================================
import {Transform} from 'node:stream';
import {setTimeout, scheduler} from 'node:timers/promises';
import {callbackify} from 'node:util';
import {foobarObject, foobarString} from './input.js';
import {casedSuffix, prefix, suffix} from './generator.js';
const getDuplex = (transform, encoding, outerObjectMode) => objectMode => ({
transform: new Transform({
transform: callbackify(async function (value) {
return transform.call(this, value);
}),
objectMode,
encoding,
}),
objectMode: outerObjectMode,
});
export const addNoopDuplex = (duplex, addNoopTransform, objectMode) => addNoopTransform
? [duplex, noopDuplex(objectMode)]
: [duplex];
export const noopDuplex = getDuplex(value => value);
export const serializeDuplex = getDuplex(object => JSON.stringify(object));
export const getOutputDuplex = (input, outerObjectMode) => getDuplex(() => input, undefined, outerObjectMode);
export const outputObjectDuplex = () => getOutputDuplex(foobarObject)(true);
export const getOutputsDuplex = inputs => getDuplex(function () {
for (const input of inputs) {
this.push(input);
}
});
export const noYieldDuplex = getDuplex(() => {});
export const multipleYieldDuplex = getDuplex(async function (line) {
this.push(prefix);
await scheduler.yield();
this.push(line);
await scheduler.yield();
this.push(suffix);
});
export const uppercaseEncodingDuplex = (encoding, outerObjectMode) => getDuplex(buffer => buffer.toString().toUpperCase(), encoding, outerObjectMode);
export const uppercaseBufferDuplex = uppercaseEncodingDuplex();
export const throwingDuplex = cause => getDuplex(() => {
throw cause;
});
export const appendDuplex = getDuplex(string => `${string}${casedSuffix}`);
export const timeoutDuplex = timeout => getDuplex(async () => {
await setTimeout(timeout);
return foobarString;
});
================================================
FILE: test/helpers/early-error.js
================================================
import {execa, execaSync} from '../../index.js';
export const earlyErrorOptions = {detached: 'true'};
export const getEarlyErrorSubprocess = options => execa('empty.js', {...earlyErrorOptions, ...options});
export const earlyErrorOptionsSync = {maxBuffer: false};
export const getEarlyErrorSubprocessSync = options => execaSync('empty.js', {...earlyErrorOptionsSync, ...options});
export const expectedEarlyError = {code: 'ERR_INVALID_ARG_TYPE'};
export const expectedEarlyErrorSync = {code: 'ERR_OUT_OF_RANGE'};
================================================
FILE: test/helpers/encoding.js
================================================
const textEncoder = new TextEncoder();
export const multibyteChar = '\u{1F984}';
export const multibyteString = `${multibyteChar}${multibyteChar}`;
export const multibyteUint8Array = textEncoder.encode(multibyteString);
export const breakingLength = multibyteUint8Array.length * 0.75;
export const brokenSymbol = '\uFFFD';
================================================
FILE: test/helpers/file-path.js
================================================
import path from 'node:path';
import process from 'node:process';
export const getAbsolutePath = file => ({file});
export const getRelativePath = filePath => ({file: path.relative('.', filePath)});
// Defined as getter so call to toString is not cached
export const getDenoNodePath = () => Object.freeze({
__proto__: String.prototype,
toString() {
return process.execPath;
},
get length() {
return this.toString().length;
},
});
================================================
FILE: test/helpers/fixtures-directory.js
================================================
import path from 'node:path';
import process from 'node:process';
import {fileURLToPath} from 'node:url';
import logProcessErrors from 'log-process-errors';
import pathKey from 'path-key';
// Make tests fail if any warning (such as a deprecation warning) is emitted
logProcessErrors({
onError(error, event) {
if (event === 'warning') {
throw error;
}
},
});
export const PATH_KEY = pathKey();
export const FIXTURES_DIRECTORY_URL = new URL('../fixtures/', import.meta.url);
// @todo: use import.meta.dirname after dropping support for Node <20.11.0
export const FIXTURES_DIRECTORY = path.resolve(fileURLToPath(FIXTURES_DIRECTORY_URL));
// Add the fixtures directory to PATH so fixtures can be executed without adding
// `node`. This is only meant to make writing tests simpler.
export const setFixtureDirectory = () => {
process.env[PATH_KEY] = FIXTURES_DIRECTORY + path.delimiter + process.env[PATH_KEY];
};
================================================
FILE: test/helpers/fs.js
================================================
import {createReadStream, createWriteStream} from 'node:fs';
import process from 'node:process';
export const getReadStream = fdNumber => fdNumber === 0
? process.stdin
: createReadStream(undefined, {fd: fdNumber});
export const getWriteStream = fdNumber => {
if (fdNumber === 1) {
return process.stdout;
}
if (fdNumber === 2) {
return process.stderr;
}
return createWriteStream(undefined, {fd: fdNumber});
};
================================================
FILE: test/helpers/generator.js
================================================
import {
setImmediate,
setInterval,
setTimeout,
scheduler,
} from 'node:timers/promises';
import {foobarObject, foobarString} from './input.js';
const getGenerator = transform => (objectMode, binary, preserveNewlines) => ({
transform,
objectMode,
binary,
preserveNewlines,
});
export const addNoopGenerator = (transform, addNoopTransform, objectMode, binary) => addNoopTransform
? [transform, noopGenerator(objectMode, binary)]
: [transform];
export const noopGenerator = getGenerator(function * (value) {
yield value;
});
export const noopAsyncGenerator = getGenerator(async function * (value) {
yield value;
});
export const serializeGenerator = getGenerator(function * (object) {
yield JSON.stringify(object);
});
export const getOutputGenerator = input => getGenerator(function * () {
yield input;
});
export const outputObjectGenerator = () => getOutputGenerator(foobarObject)(true);
export const getOutputAsyncGenerator = input => getGenerator(async function * () {
yield input;
});
export const getOutputsGenerator = inputs => getGenerator(function * () {
yield * inputs;
});
export const getOutputsAsyncGenerator = inputs => getGenerator(async function * () {
for (const input of inputs) {
yield input;
// eslint-disable-next-line no-await-in-loop
await setImmediate();
}
});
const noYieldTransform = function * () {};
export const noYieldGenerator = getGenerator(noYieldTransform);
export const prefix = '> ';
export const suffix = ' <';
export const multipleYieldGenerator = getGenerator(async function * (line = foobarString) {
yield prefix;
await scheduler.yield();
yield line;
await scheduler.yield();
yield suffix;
});
export const convertTransformToFinal = (transform, final) => {
if (!final) {
return transform;
}
const generatorOptions = typeof transform === 'function' ? {transform} : transform;
return ({...generatorOptions, transform: noYieldTransform, final: generatorOptions.transform});
};
export const infiniteGenerator = getGenerator(async function * () {
for await (const value of setInterval(100, foobarString)) {
yield value;
}
});
const textDecoder = new TextDecoder();
const textEncoder = new TextEncoder();
export const uppercaseBufferGenerator = getGenerator(function * (buffer) {
yield textEncoder.encode(textDecoder.decode(buffer).toUpperCase());
});
export const uppercaseGenerator = getGenerator(function * (string) {
yield string.toUpperCase();
});
// eslint-disable-next-line require-yield
export const throwingGenerator = error => getGenerator(function * () {
throw error;
});
export const appendGenerator = getGenerator(function * (string) {
yield `${string}${casedSuffix}`;
});
export const appendAsyncGenerator = getGenerator(async function * (string) {
yield `${string}${casedSuffix}`;
});
export const casedSuffix = 'k';
export const resultGenerator = inputs => getGenerator(function * (input) {
inputs.push(input);
yield input;
});
export const timeoutGenerator = timeout => getGenerator(async function * () {
await setTimeout(timeout);
yield foobarString;
});
================================================
FILE: test/helpers/graceful.js
================================================
import {setTimeout} from 'node:timers/promises';
// Combines `util.aborted()` and `events.addAbortListener()`: promise-based and cleaned up with a stop signal
export const onAbortedSignal = async signal => {
try {
await setTimeout(1e8, undefined, {signal});
} catch {}
};
================================================
FILE: test/helpers/input.js
================================================
import {Buffer} from 'node:buffer';
import {inspect} from 'node:util';
const textEncoder = new TextEncoder();
export const foobarString = 'foobar';
export const foobarArray = ['foo', 'bar'];
export const foobarUint8Array = textEncoder.encode(foobarString);
export const foobarArrayBuffer = foobarUint8Array.buffer;
export const foobarUint16Array = new Uint16Array(foobarArrayBuffer);
export const foobarBuffer = Buffer.from(foobarString);
const foobarUtf16Buffer = Buffer.from(foobarString, 'utf16le');
export const foobarUtf16Uint8Array = new Uint8Array(foobarUtf16Buffer);
export const foobarDataView = new DataView(foobarArrayBuffer);
export const foobarHex = foobarBuffer.toString('hex');
export const foobarUppercase = foobarString.toUpperCase();
export const foobarUppercaseUint8Array = textEncoder.encode(foobarUppercase);
export const foobarUppercaseHex = Buffer.from(foobarUppercase).toString('hex');
export const foobarObject = {foo: 'bar'};
export const foobarObjectString = JSON.stringify(foobarObject);
export const foobarObjectInspect = inspect(foobarObject);
================================================
FILE: test/helpers/ipc.js
================================================
import {getEachMessage} from '../../index.js';
import {foobarString} from './input.js';
// @todo: replace with Array.fromAsync(subprocess.getEachMessage()) after dropping support for Node <22.0.0
export const iterateAllMessages = async subprocess => {
const messages = [];
for await (const message of subprocess.getEachMessage()) {
messages.push(message);
}
return messages;
};
export const subprocessGetFirst = async subprocess => {
const [firstMessage] = await iterateAllMessages(subprocess);
return firstMessage;
};
export const getFirst = async () => {
// eslint-disable-next-line no-unreachable-loop
for await (const message of getEachMessage()) {
return message;
}
};
export const subprocessGetOne = (subprocess, options) => subprocess.getOneMessage(options);
export const alwaysPass = () => true;
// `process.send()` can fail due to I/O errors.
// However, I/O errors are seldom and hard to trigger predictably.
// So we mock them.
export const mockSendIoError = anyProcess => {
const error = new Error(foobarString);
anyProcess.send = () => {
throw error;
};
return error;
};
================================================
FILE: test/helpers/lines.js
================================================
import {Buffer} from 'node:buffer';
import {execa} from '../../index.js';
const textEncoder = new TextEncoder();
export const stringsToUint8Arrays = strings => strings.map(string => stringToUint8Arrays(string, true));
export const stringToUint8Arrays = (string, isUint8Array) => isUint8Array
? textEncoder.encode(string)
: string;
export const simpleFull = 'aaa\nbbb\nccc';
export const simpleChunks = [simpleFull];
export const simpleFullUint8Array = textEncoder.encode(simpleFull);
export const simpleChunksUint8Array = [simpleFullUint8Array];
const simpleFullBuffer = Buffer.from(simpleFull);
export const simpleFullHex = simpleFullBuffer.toString('hex');
export const simpleChunksBuffer = [simpleFullBuffer];
export const simpleFullUtf16Inverted = simpleFullBuffer.toString('utf16le');
const simpleFullUtf16Buffer = Buffer.from(simpleFull, 'utf16le');
export const simpleFullUtf16Uint8Array = new Uint8Array(simpleFullUtf16Buffer);
export const simpleFullEnd = `${simpleFull}\n`;
const simpleFullEndBuffer = Buffer.from(simpleFullEnd);
export const simpleFullEndChunks = [simpleFullEnd];
export const simpleFullEndUtf16Inverted = simpleFullEndBuffer.toString('utf16le');
export const simpleLines = ['aaa\n', 'bbb\n', 'ccc'];
export const simpleFullEndLines = ['aaa\n', 'bbb\n', 'ccc\n'];
export const noNewlinesFull = 'aaabbbccc';
export const noNewlinesChunks = ['aaa', 'bbb', 'ccc'];
export const complexFull = '\naaa\r\nbbb\n\nccc';
const complexFullBuffer = Buffer.from(complexFull);
const complexFullUtf16Buffer = Buffer.from(complexFull, 'utf16le');
export const complexFullUtf16 = complexFullUtf16Buffer.toString();
export const complexFullUtf16Uint8Array = new Uint8Array(complexFullUtf16Buffer);
export const singleComplexBuffer = [complexFullBuffer];
export const singleComplexUtf16Buffer = [complexFullUtf16Buffer];
export const singleComplexUint8Array = textEncoder.encode(complexFull);
export const singleComplexHex = complexFullBuffer.toString('hex');
export const complexChunksEnd = ['\n', 'aaa\r\n', 'bbb\n', '\n', 'ccc'];
export const complexChunks = ['', 'aaa', 'bbb', '', 'ccc'];
export const singleFull = 'aaa';
export const singleFullEnd = `${singleFull}\n`;
export const getSimpleChunkSubprocessAsync = options => getSimpleChunkSubprocess(execa, options);
export const getSimpleChunkSubprocess = (execaMethod, options) => execaMethod('noop-fd.js', ['1', simpleFull], {lines: true, ...options});
================================================
FILE: test/helpers/listeners.js
================================================
import process from 'node:process';
export const assertMaxListeners = t => {
let warning;
const captureWarning = warningArgument => {
warning = warningArgument;
};
process.once('warning', captureWarning);
return () => {
t.is(warning, undefined);
process.removeListener('warning', captureWarning);
};
};
================================================
FILE: test/helpers/map.js
================================================
import {
addNoopGenerator,
noopGenerator,
serializeGenerator,
uppercaseBufferGenerator,
uppercaseGenerator,
getOutputGenerator,
outputObjectGenerator,
getOutputsGenerator,
noYieldGenerator,
multipleYieldGenerator,
throwingGenerator,
appendGenerator,
timeoutGenerator,
} from './generator.js';
import {
addNoopDuplex,
noopDuplex,
serializeDuplex,
uppercaseBufferDuplex,
getOutputDuplex,
outputObjectDuplex,
getOutputsDuplex,
noYieldDuplex,
multipleYieldDuplex,
throwingDuplex,
appendDuplex,
timeoutDuplex,
} from './duplex.js';
import {
addNoopWebTransform,
noopWebTransform,
serializeWebTransform,
uppercaseBufferWebTransform,
getOutputWebTransform,
outputObjectWebTransform,
getOutputsWebTransform,
noYieldWebTransform,
multipleYieldWebTransform,
throwingWebTransform,
appendWebTransform,
timeoutWebTransform,
} from './web-transform.js';
export const generatorsMap = {
generator: {
addNoop: addNoopGenerator,
noop: noopGenerator,
serialize: serializeGenerator,
uppercaseBuffer: uppercaseBufferGenerator,
uppercase: uppercaseGenerator,
getOutput: getOutputGenerator,
outputObject: outputObjectGenerator,
getOutputs: getOutputsGenerator,
noYield: noYieldGenerator,
multipleYield: multipleYieldGenerator,
throwing: throwingGenerator,
append: appendGenerator,
timeout: timeoutGenerator,
},
duplex: {
addNoop: addNoopDuplex,
noop: noopDuplex,
serialize: serializeDuplex,
uppercaseBuffer: uppercaseBufferDuplex,
uppercase: uppercaseBufferDuplex,
getOutput: getOutputDuplex,
outputObject: outputObjectDuplex,
getOutputs: getOutputsDuplex,
noYield: noYieldDuplex,
multipleYield: multipleYieldDuplex,
throwing: throwingDuplex,
append: appendDuplex,
timeout: timeoutDuplex,
},
webTransform: {
addNoop: addNoopWebTransform,
noop: noopWebTransform,
serialize: serializeWebTransform,
uppercaseBuffer: uppercaseBufferWebTransform,
uppercase: uppercaseBufferWebTransform,
getOutput: getOutputWebTransform,
outputObject: outputObjectWebTransform,
getOutputs: getOutputsWebTransform,
noYield: noYieldWebTransform,
multipleYield: multipleYieldWebTransform,
throwing: throwingWebTransform,
append: appendWebTransform,
timeout: timeoutWebTransform,
},
};
================================================
FILE: test/helpers/max-buffer.js
================================================
import {execa, execaSync} from '../../index.js';
export const maxBuffer = 10;
export const assertErrorMessage = (t, shortMessage, {execaMethod = execa, length = maxBuffer, fdNumber = 1, unit = 'characters'} = {}) => {
const [expectedStreamName, expectedUnit] = execaMethod === execaSync
? ['output', 'bytes']
: [STREAM_NAMES[fdNumber], unit];
t.true(shortMessage.includes(`${expectedStreamName} was larger than ${length} ${expectedUnit}`));
};
const STREAM_NAMES = ['stdin', 'stdout', 'stderr', 'stdio[3]'];
================================================
FILE: test/helpers/nested.js
================================================
import {once} from 'node:events';
import {Worker} from 'node:worker_threads';
import {execa} from '../../index.js';
import {FIXTURES_DIRECTORY_URL} from './fixtures-directory.js';
const WORKER_URL = new URL('worker.js', FIXTURES_DIRECTORY_URL);
const NESTED_URL = new URL('nested/', FIXTURES_DIRECTORY_URL);
// Like `execa(file, commandArguments, options)` but spawns inside another parent process.
// This is useful when testing logic where Execa modifies the global state.
// For example, when it prints to the console, with the `verbose` option.
// The parent process calls `execa(options.parentFixture, parentOptions)`
// When `options.isSync` is `true`, `execaSync()` is called instead.
// When `options.worker` is `true`, the whole flow happens inside a Worker.
export const nestedSubprocess = async (file, commandArguments, options, parentOptions) => {
const result = await nestedInstance(file, commandArguments, options, parentOptions);
const nestedResult = result.ipcOutput[0];
return {...result, nestedResult};
};
export const nestedInstance = (file, commandArguments, options, parentOptions) => {
[commandArguments, options = {}, parentOptions = {}] = Array.isArray(commandArguments)
? [commandArguments, options, parentOptions]
: [[], commandArguments, options];
const {
parentFixture = 'nested.js',
worker = false,
isSync = false,
optionsFixture,
optionsInput = {},
...otherOptions
} = options;
const normalizedArguments = {
parentFixture,
parentOptions,
isSync,
file,
commandArguments,
options: otherOptions,
optionsFixture,
optionsInput,
};
return worker
? spawnWorker(normalizedArguments)
: spawnParentProcess(normalizedArguments);
};
const spawnWorker = async workerData => {
const worker = new Worker(WORKER_URL, {workerData});
const [result] = await once(worker, 'message');
if (result instanceof Error) {
throw result;
}
return result;
};
export const spawnParentProcess = ({parentFixture, parentOptions, ...ipcInput}) => execa(parentFixture, {...parentOptions, ipcInput});
// Some subprocess options cannot be serialized between processes.
// For those, we pass a fixture filename instead, which dynamically creates the options.
export const getNestedOptions = async (options, optionsFixture, optionsInput) => {
if (optionsFixture === undefined) {
return options;
}
const {getOptions} = await import(new URL(optionsFixture, NESTED_URL));
return {...options, ...getOptions({...options, ...optionsInput})};
};
================================================
FILE: test/helpers/node-version.js
================================================
import {version} from 'node:process';
export const majorNodeVersion = Number(version.split('.')[0].slice(1));
================================================
FILE: test/helpers/override-promise.js
================================================
// Can't use `test.before`, because `ava` needs `Promise`.
const nativePromise = Promise;
globalThis.Promise = class BrokenPromise {
then() { // eslint-disable-line unicorn/no-thenable
throw new Error('error');
}
};
export function restorePromise() {
globalThis.Promise = nativePromise;
}
================================================
FILE: test/helpers/parallel.js
================================================
import isInCi from 'is-in-ci';
export const PARALLEL_COUNT = isInCi ? 10 : 100;
================================================
FILE: test/helpers/pipe.js
================================================
export const assertPipeError = async (t, pipePromise, message) => {
const error = await t.throwsAsync(pipePromise);
t.is(error.command, 'source.pipe(destination)');
t.is(error.escapedCommand, error.command);
t.is(typeof error.cwd, 'string');
t.true(error.failed);
t.false(error.timedOut);
t.false(error.isCanceled);
t.false(error.isTerminated);
t.is(error.exitCode, undefined);
t.is(error.signal, undefined);
t.is(error.signalDescription, undefined);
t.is(error.stdout, undefined);
t.is(error.stderr, undefined);
t.is(error.all, undefined);
t.deepEqual(error.stdio, Array.from({length: error.stdio.length}));
t.deepEqual(error.pipedFrom, []);
t.true(error.shortMessage.includes(`Command failed: ${error.command}`));
t.true(error.shortMessage.includes(error.originalMessage));
t.true(error.message.includes(error.shortMessage));
t.true(error.originalMessage.includes(message));
};
================================================
FILE: test/helpers/run.js
================================================
import {execa, execaSync, $} from '../../index.js';
export const runExeca = (file, options) => execa(file, options);
export const runExecaSync = (file, options) => execaSync(file, options);
export const runScript = (file, options) => $(options)`${file}`;
export const runScriptSync = (file, options) => $(options).sync`${file}`;
================================================
FILE: test/helpers/stdio.js
================================================
import process, {platform} from 'node:process';
import {noopReadable} from './stream.js';
export const identity = value => value;
export const getStdio = (fdNumberOrName, stdioOption, length = 3) => {
if (typeof fdNumberOrName === 'string') {
return {[fdNumberOrName]: stdioOption};
}
const stdio = Array.from({length}).fill('pipe');
stdio[fdNumberOrName] = stdioOption;
return {stdio};
};
export const fullStdio = getStdio(3, 'pipe');
export const fullReadableStdio = () => getStdio(3, ['pipe', noopReadable()]);
export const STANDARD_STREAMS = [process.stdin, process.stdout, process.stderr];
export const prematureClose = {code: 'ERR_STREAM_PREMATURE_CLOSE'};
const isWindows = platform === 'win32';
export const assertEpipe = (t, stderr, fdNumber = 1) => {
if (fdNumber === 1 && !isWindows) {
t.true(stderr.includes('EPIPE'));
}
};
export const parseStdioOption = stdioOption => {
const optionValue = JSON.parse(stdioOption);
if (typeof optionValue === 'string' && optionValue in process) {
return process[optionValue];
}
if (Array.isArray(optionValue) && typeof optionValue[0] === 'string' && optionValue[0] in process) {
return [process[optionValue[0]], ...optionValue.slice(1)];
}
return optionValue;
};
================================================
FILE: test/helpers/stream.js
================================================
import {
Readable,
Writable,
PassThrough,
getDefaultHighWaterMark,
} from 'node:stream';
import {foobarString} from './input.js';
export const noopReadable = () => new Readable({read() {}});
export const noopWritable = () => new Writable({write() {}});
export const noopDuplex = () => new PassThrough().resume();
export const simpleReadable = () => Readable.from([foobarString]);
export const defaultHighWaterMark = getDefaultHighWaterMark(false);
export const defaultObjectHighWaterMark = getDefaultHighWaterMark(true);
================================================
FILE: test/helpers/verbose.js
================================================
import {platform} from 'node:process';
import {stripVTControlCharacters} from 'node:util';
import {replaceSymbols} from 'figures';
import {foobarString} from './input.js';
import {nestedSubprocess} from './nested.js';
const isWindows = platform === 'win32';
export const QUOTE = isWindows ? '"' : '\'';
export const runErrorSubprocess = async (t, verbose, isSync = false, expectExitCode = true) => {
const {stderr, nestedResult} = await nestedSubprocess('noop-fail.js', ['1', foobarString], {verbose, isSync});
t.true(nestedResult instanceof Error);
if (expectExitCode) {
t.true(stderr.includes('exit code 2'));
}
return stderr;
};
export const runWarningSubprocess = async (t, isSync) => {
const {stderr, nestedResult} = await nestedSubprocess('noop-fail.js', ['1', foobarString], {verbose: 'short', reject: false, isSync});
t.true(nestedResult instanceof Error);
t.true(stderr.includes('exit code 2'));
return stderr;
};
export const runEarlyErrorSubprocess = async (t, isSync) => {
const {stderr, nestedResult} = await nestedSubprocess('noop.js', [foobarString], {verbose: 'short', cwd: true, isSync});
t.true(nestedResult instanceof Error);
t.true(nestedResult.message.startsWith('The "cwd" option must'));
return stderr;
};
export const runVerboseSubprocess = ({
isSync = false,
type,
eventProperty,
optionName,
errorMessage,
fdNumber,
secondFdNumber,
optionsFixture = 'custom-event.js',
output = '. .',
...options
}) => nestedSubprocess('noop-verbose.js', [output], {
ipc: !isSync,
optionsFixture,
optionsInput: {
type,
eventProperty,
optionName,
errorMessage,
fdNumber,
secondFdNumber,
},
isSync,
...options,
});
export const getCommandLine = stderr => getCommandLines(stderr)[0];
export const getCommandLines = stderr => getNormalizedLines(stderr).filter(line => isCommandLine(line));
const isCommandLine = line => line.includes(' $ ') || line.includes(' | ');
export const getOutputLine = stderr => getOutputLines(stderr)[0];
export const getOutputLines = stderr => getNormalizedLines(stderr).filter(line => isOutputLine(line));
const isOutputLine = line => line.includes('] ');
export const getIpcLine = stderr => getIpcLines(stderr)[0];
export const getIpcLines = stderr => getNormalizedLines(stderr).filter(line => isIpcLine(line));
const isIpcLine = line => line.includes(' * ');
export const getErrorLine = stderr => getErrorLines(stderr)[0];
export const getErrorLines = stderr => getNormalizedLines(stderr).filter(line => isErrorLine(line));
const isErrorLine = line => (line.includes(' × ') || line.includes(' ‼ ')) && !isCompletionLine(line);
export const getCompletionLine = stderr => getCompletionLines(stderr)[0];
export const getCompletionLines = stderr => getNormalizedLines(stderr).filter(line => isCompletionLine(line));
const isCompletionLine = line => line.includes('(done in');
export const getNormalizedLine = stderr => getNormalizedLines(stderr)[0];
export const getNormalizedLines = stderr => splitLines(normalizeStderr(stderr));
const splitLines = stderr => stderr.split('\n');
const normalizeStderr = stderr => replaceSymbols(normalizeDuration(normalizeTimestamp(stripVTControlCharacters(stderr))), {useFallback: true});
export const testTimestamp = '[00:00:00.000]';
const normalizeTimestamp = stderr => stderr.replaceAll(/^\[\d{2}:\d{2}:\d{2}.\d{3}]/gm, testTimestamp);
const normalizeDuration = stderr => stderr.replaceAll(/\(done in [^)]+\)/g, '(done in 0ms)');
export const getVerboseOption = (isVerbose, verbose = 'short') => ({verbose: isVerbose ? verbose : 'none'});
export const stdoutNoneOption = {stdout: 'none'};
export const stdoutShortOption = {stdout: 'short'};
export const stdoutFullOption = {stdout: 'full'};
export const stderrNoneOption = {stderr: 'none'};
export const stderrShortOption = {stderr: 'short'};
export const stderrFullOption = {stderr: 'full'};
export const fd3NoneOption = {fd3: 'none'};
export const fd3ShortOption = {fd3: 'short'};
export const fd3FullOption = {fd3: 'full'};
export const ipcNoneOption = {ipc: 'none'};
export const ipcShortOption = {ipc: 'short'};
export const ipcFullOption = {ipc: 'full'};
================================================
FILE: test/helpers/wait.js
================================================
import {getStdio} from '../helpers/stdio.js';
import {noopGenerator} from '../helpers/generator.js';
export const endOptionStream = ({stream}) => {
stream.end();
};
export const destroyOptionStream = ({stream, error}) => {
stream.destroy(error);
};
export const destroySubprocessStream = ({subprocess, fdNumber, error}) => {
subprocess.stdio[fdNumber].destroy(error);
};
export const getStreamStdio = (fdNumber, stream, useTransform) =>
getStdio(fdNumber, [stream, useTransform ? noopGenerator(false) : 'pipe']);
================================================
FILE: test/helpers/web-transform.js
================================================
import {setTimeout, scheduler} from 'node:timers/promises';
import {foobarObject, foobarString} from './input.js';
import {casedSuffix, prefix, suffix} from './generator.js';
const getWebTransform = transform => objectMode => ({
transform: new TransformStream({transform}),
objectMode,
});
export const addNoopWebTransform = (webTransform, addNoopTransform, objectMode) => addNoopTransform
? [webTransform, noopWebTransform(objectMode)]
: [webTransform];
export const noopWebTransform = getWebTransform((value, controller) => {
controller.enqueue(value);
});
export const serializeWebTransform = getWebTransform((object, controller) => {
controller.enqueue(JSON.stringify(object));
});
export const getOutputWebTransform = (input, outerObjectMode) => getWebTransform((_, controller) => {
controller.enqueue(input);
}, undefined, outerObjectMode);
export const outputObjectWebTransform = () => getOutputWebTransform(foobarObject)(true);
export const getOutputsWebTransform = inputs => getWebTransform((_, controller) => {
for (const input of inputs) {
controller.enqueue(input);
}
});
export const noYieldWebTransform = getWebTransform(() => {});
export const multipleYieldWebTransform = getWebTransform(async (line, controller) => {
controller.enqueue(prefix);
await scheduler.yield();
controller.enqueue(line);
await scheduler.yield();
controller.enqueue(suffix);
});
export const uppercaseBufferWebTransform = getWebTransform((string, controller) => {
controller.enqueue(string.toString().toUpperCase());
});
export const throwingWebTransform = cause => getWebTransform(() => {
throw cause;
});
export const appendWebTransform = getWebTransform((string, controller) => {
controller.enqueue(`${string}${casedSuffix}`);
});
export const timeoutWebTransform = timeout => getWebTransform(async (_, controller) => {
await setTimeout(timeout);
controller.enqueue(foobarString);
});
================================================
FILE: test/io/input-option.js
================================================
import {Writable} from 'node:stream';
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {
runExeca,
runExecaSync,
runScript,
runScriptSync,
} from '../helpers/run.js';
import {
foobarUint8Array,
foobarBuffer,
foobarArrayBuffer,
foobarUint16Array,
foobarDataView,
} from '../helpers/input.js';
setFixtureDirectory();
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', runExeca);
test('input option can be a Uint8Array', testInput, foobarUint8Array, runExeca);
test('input option can be a Buffer', testInput, foobarBuffer, runExeca);
test('input option can be a String - sync', testInput, 'foobar', runExecaSync);
test('input option can be a Uint8Array - sync', testInput, foobarUint8Array, runExecaSync);
test('input option can be a Buffer - sync', testInput, foobarBuffer, runExecaSync);
test('input option can be used with $', testInput, 'foobar', runScript);
test('input option can be used with $.sync', testInput, 'foobar', runScriptSync);
const testInvalidInput = async (t, input, execaMethod) => {
t.throws(() => {
execaMethod('empty.js', {input});
}, {message: /a string, a Uint8Array/});
};
test('input option cannot be an ArrayBuffer', testInvalidInput, foobarArrayBuffer, execa);
test('input option cannot be a DataView', testInvalidInput, foobarDataView, execa);
test('input option cannot be a Uint16Array', testInvalidInput, foobarUint16Array, execa);
test('input option cannot be 0', testInvalidInput, 0, execa);
test('input option cannot be false', testInvalidInput, false, execa);
test('input option cannot be null', testInvalidInput, null, execa);
test('input option cannot be a non-Readable stream', testInvalidInput, new Writable(), execa);
test('input option cannot be an ArrayBuffer - sync', testInvalidInput, foobarArrayBuffer, execaSync);
test('input option cannot be a DataView - sync', testInvalidInput, foobarDataView, execaSync);
test('input option cannot be a Uint16Array - sync', testInvalidInput, foobarUint16Array, execaSync);
test('input option cannot be 0 - sync', testInvalidInput, 0, execaSync);
test('input option cannot be false - sync', testInvalidInput, false, execaSync);
test('input option cannot be null - sync', testInvalidInput, null, execaSync);
test('input option cannot be a non-Readable stream - sync', testInvalidInput, new Writable(), execaSync);
================================================
FILE: test/io/input-sync.js
================================================
import test from 'ava';
import {execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {getStdio} from '../helpers/stdio.js';
setFixtureDirectory();
const getFd3InputMessage = type => `not \`stdio[3]\`, can be ${type}`;
const testFd3InputSync = (t, stdioOption, expectedMessage) => {
const {message} = t.throws(() => {
execaSync('empty.js', getStdio(3, stdioOption));
});
t.true(message.includes(expectedMessage));
};
test('Cannot use Uint8Array with stdio[*], sync', testFd3InputSync, new Uint8Array(), getFd3InputMessage('a Uint8Array'));
test('Cannot use iterable with stdio[*], sync', testFd3InputSync, [[]], getFd3InputMessage('an iterable'));
================================================
FILE: test/io/iterate.js
================================================
import {once} from 'node:events';
import {getDefaultHighWaterMark} from 'node:stream';
import test from 'ava';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {
assertStreamOutput,
assertIterableChunks,
assertStreamChunks,
assertSubprocessOutput,
getReadableSubprocess,
getReadWriteSubprocess,
} from '../helpers/convert.js';
import {
stringToUint8Arrays,
simpleFull,
simpleChunks,
simpleChunksBuffer,
simpleChunksUint8Array,
simpleLines,
noNewlinesFull,
complexFull,
complexFullUtf16,
complexFullUtf16Uint8Array,
singleComplexBuffer,
singleComplexUtf16Buffer,
singleComplexUint8Array,
singleComplexHex,
complexChunks,
complexChunksEnd,
} from '../helpers/lines.js';
import {outputObjectGenerator, getOutputGenerator} from '../helpers/generator.js';
import {foobarString, foobarObject} from '../helpers/input.js';
import {
multibyteChar,
multibyteUint8Array,
breakingLength,
brokenSymbol,
} from '../helpers/encoding.js';
setFixtureDirectory();
const foobarObjectChunks = [foobarObject, foobarObject, foobarObject];
const getSubprocess = (methodName, output, options) => {
if (methodName !== 'duplex') {
return getReadableSubprocess(output, options);
}
const subprocess = getReadWriteSubprocess(options);
subprocess.stdin.end(output);
return subprocess;
};
const assertChunks = async (t, streamOrIterable, expectedChunks, methodName) => {
const assertMethod = methodName === 'iterable' ? assertIterableChunks : assertStreamChunks;
await assertMethod(t, streamOrIterable, expectedChunks);
};
// eslint-disable-next-line max-params
const testText = async (t, expectedChunks, methodName, binary, preserveNewlines, encoding) => {
const subprocess = getReadWriteSubprocess({encoding});
const input = encoding === 'utf16le' ? complexFullUtf16 : complexFull;
subprocess.stdin.end(input);
const stream = subprocess[methodName]({binary, preserveNewlines});
await assertChunks(t, stream, expectedChunks, methodName);
const expectedOutput = encoding === 'hex'
? singleComplexHex
: stringToUint8Arrays(complexFull, encoding === 'buffer');
await assertSubprocessOutput(t, subprocess, expectedOutput);
};
test('.iterable() can use "binary: true"', testText, [singleComplexUint8Array], 'iterable', true, undefined, 'utf8');
test('.iterable() can use "binary: true" + "encoding: utf16le"', testText, [complexFullUtf16Uint8Array], 'iterable', true, undefined, 'utf16le');
test('.iterable() can use "binary: true" + "encoding: "buffer"', testText, [singleComplexUint8Array], 'iterable', true, undefined, 'buffer');
test('.iterable() can use "binary: true" + "encoding: "hex"', testText, [singleComplexUint8Array], 'iterable', true, undefined, 'hex');
test('.iterable() can use "binary: undefined"', testText, complexChunks, 'iterable', undefined, undefined, 'utf8');
test('.iterable() can use "binary: undefined" + "encoding: utf16le"', testText, complexChunks, 'iterable', undefined, undefined, 'utf16le');
test('.iterable() can use "binary: undefined" + "encoding: buffer"', testText, [singleComplexUint8Array], 'iterable', undefined, undefined, 'buffer');
test('.iterable() can use "binary: undefined" + "encoding: hex"', testText, [singleComplexUint8Array], 'iterable', undefined, undefined, 'hex');
test('.iterable() can use "binary: false"', testText, complexChunks, 'iterable', false, undefined, 'utf8');
test('.iterable() can use "binary: false" + "encoding: utf16le"', testText, complexChunks, 'iterable', false, undefined, 'utf16le');
test('.iterable() can use "binary: false" + "encoding: buffer"', testText, [singleComplexUint8Array], 'iterable', false, undefined, 'buffer');
test('.iterable() can use "binary: false" + "encoding: hex"', testText, [singleComplexUint8Array], 'iterable', false, undefined, 'hex');
test('.iterable() can use "binary: false" + "preserveNewlines: true"', testText, complexChunksEnd, 'iterable', false, true, 'utf8');
test('.iterable() can use "binary: false" + "preserveNewlines: false"', testText, complexChunks, 'iterable', false, false, 'utf8');
test('.readable() can use "binary: true"', testText, singleComplexBuffer, 'readable', true, undefined, 'utf8');
test('.readable() can use "binary: true" + "encoding: utf16le"', testText, singleComplexUtf16Buffer, 'readable', true, undefined, 'utf16le');
test('.readable() can use "binary: true" + "encoding: buffer"', testText, singleComplexBuffer, 'readable', true, undefined, 'buffer');
test('.readable() can use "binary: true" + "encoding: hex"', testText, singleComplexBuffer, 'readable', true, undefined, 'hex');
test('.readable() can use "binary: undefined"', testText, singleComplexBuffer, 'readable', undefined, undefined, 'utf8');
test('.readable() can use "binary: undefined" + "encoding: utf16le"', testText, singleComplexUtf16Buffer, 'readable', undefined, undefined, 'utf16le');
test('.readable() can use "binary: undefined" + "encoding: buffer"', testText, singleComplexBuffer, 'readable', undefined, undefined, 'buffer');
test('.readable() can use "binary: undefined" + "encoding: hex"', testText, singleComplexBuffer, 'readable', undefined, undefined, 'hex');
test('.readable() can use "binary: false"', testText, complexChunksEnd, 'readable', false, undefined, 'utf8');
test('.readable() can use "binary: false" + "encoding: utf16le"', testText, complexChunksEnd, 'readable', false, undefined, 'utf16le');
test('.readable() can use "binary: false" + "encoding: buffer"', testText, singleComplexBuffer, 'readable', false, undefined, 'buffer');
test('.readable() can use "binary: false" + "encoding: hex"', testText, singleComplexBuffer, 'readable', false, undefined, 'hex');
test('.readable() can use "binary: false" + "preserveNewlines: true"', testText, complexChunksEnd, 'readable', false, true, 'utf8');
test('.readable() can use "binary: false" + "preserveNewlines: false"', testText, complexChunks, 'readable', false, false, 'utf8');
test('.duplex() can use "binary: true"', testText, singleComplexBuffer, 'duplex', true, undefined, 'utf8');
test('.duplex() can use "binary: true" + "encoding: utf16le"', testText, singleComplexUtf16Buffer, 'duplex', true, undefined, 'utf16le');
test('.duplex() can use "binary: true" + "encoding: buffer"', testText, singleComplexBuffer, 'duplex', true, undefined, 'buffer');
test('.duplex() can use "binary: true" + "encoding: hex"', testText, singleComplexBuffer, 'duplex', true, undefined, 'hex');
test('.duplex() can use "binary: undefined"', testText, singleComplexBuffer, 'duplex', undefined, undefined, 'utf8');
test('.duplex() can use "binary: undefined" + "encoding: utf16le"', testText, singleComplexUtf16Buffer, 'duplex', undefined, undefined, 'utf16le');
test('.duplex() can use "binary: undefined" + "encoding: "buffer"', testText, singleComplexBuffer, 'duplex', undefined, undefined, 'buffer');
test('.duplex() can use "binary: undefined" + "encoding: "hex"', testText, singleComplexBuffer, 'duplex', undefined, undefined, 'hex');
test('.duplex() can use "binary: false"', testText, complexChunksEnd, 'duplex', false, undefined, 'utf8');
test('.duplex() can use "binary: false" + "encoding: utf16le"', testText, complexChunksEnd, 'duplex', false, undefined, 'utf16le');
test('.duplex() can use "binary: false" + "encoding: buffer"', testText, singleComplexBuffer, 'duplex', false, undefined, 'buffer');
test('.duplex() can use "binary: false" + "encoding: hex"', testText, singleComplexBuffer, 'duplex', false, undefined, 'hex');
test('.duplex() can use "binary: false" + "preserveNewlines: true"', testText, complexChunksEnd, 'duplex', false, true, 'utf8');
test('.duplex() can use "binary: false" + "preserveNewlines: false"', testText, complexChunks, 'duplex', false, false, 'utf8');
const testTextOutput = async (t, expectedOutput, methodName, preserveNewlines) => {
const subprocess = getSubprocess(methodName, complexFull);
const stream = subprocess[methodName]({binary: false, preserveNewlines});
await assertStreamOutput(t, stream, expectedOutput);
await assertSubprocessOutput(t, subprocess, complexFull);
};
test('.readable() "binary: false" keeps output as is', testTextOutput, complexFull, 'readable', undefined);
test('.readable() "binary: false" + "preserveNewlines: true" keeps output as is', testTextOutput, complexFull, 'readable', true);
test('.readable() "binary: false" + "preserveNewlines: false" removes all newlines', testTextOutput, noNewlinesFull, 'readable', false);
test('.duplex() "binary: false" keeps output as is', testTextOutput, complexFull, 'duplex', undefined);
test('.duplex() "binary: false" + "preserveNewlines: true" keeps output as is', testTextOutput, complexFull, 'duplex', true);
test('.duplex() "binary: false" + "preserveNewlines: false" removes all newlines', testTextOutput, noNewlinesFull, 'duplex', false);
// eslint-disable-next-line max-params
const testObjectMode = async (t, expectedChunks, methodName, encoding, initialObjectMode, finalObjectMode, binary, options) => {
const subprocess = getSubprocess(methodName, simpleFull, options);
if (encoding !== null) {
subprocess.stdout.setEncoding(encoding);
}
t.is(subprocess.stdout.readableEncoding, encoding);
t.is(subprocess.stdout.readableObjectMode, initialObjectMode);
t.is(subprocess.stdout.readableHighWaterMark, getDefaultHighWaterMark(initialObjectMode));
const stream = subprocess[methodName]({binary, preserveNewlines: true});
if (methodName !== 'iterable') {
t.is(stream.readableEncoding, encoding);
t.is(stream.readableObjectMode, finalObjectMode);
t.is(stream.readableHighWaterMark, getDefaultHighWaterMark(finalObjectMode));
}
t.is(subprocess.stdout.readableEncoding, encoding);
t.is(subprocess.stdout.readableObjectMode, initialObjectMode);
t.is(subprocess.stdout.readableHighWaterMark, getDefaultHighWaterMark(initialObjectMode));
await assertChunks(t, stream, expectedChunks, methodName);
await subprocess;
};
test('.iterable() uses Uint8Arrays with "binary: true"', testObjectMode, simpleChunksUint8Array, 'iterable', null, false, false, true);
test('.iterable() uses Uint8Arrays with "binary: true" and .setEncoding("utf8")', testObjectMode, simpleChunksUint8Array, 'iterable', 'utf8', false, false, true);
test('.iterable() uses Uint8Arrays with "binary: true", .setEncoding("utf8") and "encoding: buffer"', testObjectMode, simpleChunksUint8Array, 'iterable', 'utf8', false, false, true, {encoding: 'buffer'});
test('.iterable() uses strings in objectMode with "binary: true" and object transforms', testObjectMode, foobarObjectChunks, 'iterable', null, true, true, true, {stdout: outputObjectGenerator()});
test('.iterable() uses strings in objectMode with "binary: false"', testObjectMode, simpleLines, 'iterable', null, false, true, false);
test('.iterable() uses strings in objectMode with "binary: false" and .setEncoding("utf8")', testObjectMode, simpleLines, 'iterable', 'utf8', false, true, false);
test('.iterable() uses Uint8Arrays in objectMode with "binary: false", .setEncoding("utf8") and "encoding: buffer"', testObjectMode, simpleChunksUint8Array, 'iterable', 'utf8', false, true, false, {encoding: 'buffer'});
test('.iterable() uses strings in objectMode with "binary: false" and object transforms', testObjectMode, foobarObjectChunks, 'iterable', null, true, true, false, {stdout: outputObjectGenerator()});
test('.readable() uses Buffers with "binary: true"', testObjectMode, simpleChunksBuffer, 'readable', null, false, false, true);
test('.readable() uses strings with "binary: true" and .setEncoding("utf8")', testObjectMode, simpleChunks, 'readable', 'utf8', false, false, true);
test('.readable() uses strings with "binary: true", .setEncoding("utf8") and "encoding: buffer"', testObjectMode, simpleChunks, 'readable', 'utf8', false, false, true, {encoding: 'buffer'});
test('.readable() uses strings in objectMode with "binary: true" and object transforms', testObjectMode, foobarObjectChunks, 'readable', null, true, true, true, {stdout: outputObjectGenerator()});
test('.readable() uses strings in objectMode with "binary: false"', testObjectMode, simpleLines, 'readable', null, false, true, false);
test('.readable() uses strings in objectMode with "binary: false" and .setEncoding("utf8")', testObjectMode, simpleLines, 'readable', 'utf8', false, true, false);
test('.readable() uses strings in objectMode with "binary: false", .setEncoding("utf8") and "encoding: buffer"', testObjectMode, simpleChunks, 'readable', 'utf8', false, false, false, {encoding: 'buffer'});
test('.readable() uses strings in objectMode with "binary: false" and object transforms', testObjectMode, foobarObjectChunks, 'readable', null, true, true, false, {stdout: outputObjectGenerator()});
test('.duplex() uses Buffers with "binary: true"', testObjectMode, simpleChunksBuffer, 'duplex', null, false, false, true);
test('.duplex() uses strings with "binary: true" and .setEncoding("utf8")', testObjectMode, simpleChunks, 'duplex', 'utf8', false, false, true);
test('.duplex() uses strings with "binary: true", .setEncoding("utf8") and "encoding: buffer"', testObjectMode, simpleChunks, 'duplex', 'utf8', false, false, true, {encoding: 'buffer'});
test('.duplex() uses strings in objectMode with "binary: true" and object transforms', testObjectMode, foobarObjectChunks, 'duplex', null, true, true, true, {stdout: outputObjectGenerator()});
test('.duplex() uses strings in objectMode with "binary: false"', testObjectMode, simpleLines, 'duplex', null, false, true, false);
test('.duplex() uses strings in objectMode with "binary: false" and .setEncoding("utf8")', testObjectMode, simpleLines, 'duplex', 'utf8', false, true, false);
test('.duplex() uses strings in objectMode with "binary: false", .setEncoding("utf8") and "encoding: buffer"', testObjectMode, simpleChunks, 'duplex', 'utf8', false, false, false, {encoding: 'buffer'});
test('.duplex() uses strings in objectMode with "binary: false" and object transforms', testObjectMode, foobarObjectChunks, 'duplex', null, true, true, false, {stdout: outputObjectGenerator()});
const testObjectSplit = async (t, methodName) => {
const subprocess = getSubprocess(methodName, foobarString, {stdout: getOutputGenerator(simpleFull)(true)});
const stream = subprocess[methodName]({binary: false});
await assertChunks(t, stream, [simpleFull], methodName);
await subprocess;
};
test('.iterable() "binary: false" does not split lines of strings produced by object transforms', testObjectSplit, 'iterable');
test('.readable() "binary: false" does not split lines of strings produced by object transforms', testObjectSplit, 'readable');
test('.duplex() "binary: false" does not split lines of strings produced by object transforms', testObjectSplit, 'duplex');
const testMultibyteCharacters = async (t, methodName) => {
const subprocess = getReadWriteSubprocess();
const stream = subprocess[methodName]({binary: false});
const assertPromise = assertChunks(t, stream, [`${multibyteChar}${brokenSymbol}`], methodName);
subprocess.stdin.write(multibyteUint8Array.slice(0, breakingLength));
await once(subprocess.stdout, 'data');
subprocess.stdin.end();
await assertPromise;
};
test('.iterable() "binary: false" handles partial multibyte characters', testMultibyteCharacters, 'iterable');
test('.readable() "binary: false" handles partial multibyte characters', testMultibyteCharacters, 'readable');
test('.duplex() "binary: false" handles partial multibyte characters', testMultibyteCharacters, 'duplex');
================================================
FILE: test/io/max-buffer.js
================================================
import {Buffer} from 'node:buffer';
import test from 'ava';
import getStream from 'get-stream';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {fullStdio} from '../helpers/stdio.js';
import {getEarlyErrorSubprocess} from '../helpers/early-error.js';
import {maxBuffer, assertErrorMessage} from '../helpers/max-buffer.js';
import {foobarArray} from '../helpers/input.js';
setFixtureDirectory();
const maxBufferMessage = {message: /maxBuffer exceeded/};
const maxBufferCodeSync = {code: 'ENOBUFS'};
const runMaxBuffer = async (t, execaMethod, fdNumber, options) => {
const error = execaMethod === execa
? await t.throwsAsync(getMaxBufferSubprocess(execaMethod, fdNumber, options), maxBufferMessage)
: t.throws(() => {
getMaxBufferSubprocess(execaMethod, fdNumber, options);
}, maxBufferCodeSync);
t.true(error.isMaxBuffer);
t.is(error.maxBufferInfo, undefined);
return error;
};
const getMaxBufferSubprocess = (execaMethod, fdNumber, {length = maxBuffer, ...options} = {}) =>
execaMethod('max-buffer.js', [`${fdNumber}`, `${length + 1}`], {...fullStdio, maxBuffer, ...options});
const getExpectedOutput = (length = maxBuffer) => '.'.repeat(length);
const testMaxBufferSuccess = async (t, execaMethod, fdNumber, all) => {
const {isMaxBuffer} = await getMaxBufferSubprocess(execaMethod, fdNumber, {all, length: maxBuffer - 1});
t.false(isMaxBuffer);
};
test('maxBuffer does not affect stdout if too high', testMaxBufferSuccess, execa, 1, false);
test('maxBuffer does not affect stderr if too high', testMaxBufferSuccess, execa, 2, false);
test('maxBuffer does not affect stdio[*] if too high', testMaxBufferSuccess, execa, 3, false);
test('maxBuffer does not affect all if too high', testMaxBufferSuccess, execa, 1, true);
test('maxBuffer does not affect stdout if too high, sync', testMaxBufferSuccess, execaSync, 1, false);
test('maxBuffer does not affect stderr if too high, sync', testMaxBufferSuccess, execaSync, 2, false);
test('maxBuffer does not affect stdio[*] if too high, sync', testMaxBufferSuccess, execaSync, 3, false);
test('maxBuffer does not affect all if too high, sync', testMaxBufferSuccess, execaSync, 1, true);
const testGracefulExit = async (t, fixtureName, expectedExitCode) => {
const {isMaxBuffer, shortMessage, exitCode, signal, stdout} = await t.throwsAsync(
execa(fixtureName, ['1', '.'.repeat(maxBuffer + 1)], {maxBuffer}),
maxBufferMessage,
);
t.true(isMaxBuffer);
assertErrorMessage(t, shortMessage);
t.is(exitCode, expectedExitCode);
t.is(signal, undefined);
t.is(stdout, getExpectedOutput());
};
test('maxBuffer terminates stream gracefully, more writes', testGracefulExit, 'noop-repeat.js', 1);
test('maxBuffer terminates stream gracefully, no more writes', testGracefulExit, 'noop-fd.js', 0);
const testGracefulExitSync = (t, fixtureName) => {
const {isMaxBuffer, shortMessage, exitCode, signal, stdout} = t.throws(() => {
execaSync(fixtureName, ['1', '.'.repeat(maxBuffer + 1)], {maxBuffer, killSignal: 'SIGINT'});
}, maxBufferCodeSync);
t.true(isMaxBuffer);
assertErrorMessage(t, shortMessage, {execaMethod: execaSync});
t.is(exitCode, undefined);
t.is(signal, 'SIGINT');
t.is(stdout, getExpectedOutput());
};
test('maxBuffer terminate stream with killSignal, more writes, sync', testGracefulExitSync, 'noop-repeat.js');
test('maxBuffer terminate stream with killSignal, no more writes, sync', testGracefulExitSync, 'noop-fd.js');
const testMaxBufferLimit = async (t, execaMethod, fdNumber, all) => {
const length = all && execaMethod === execa ? maxBuffer * 2 : maxBuffer;
const {shortMessage, all: allOutput, stdio} = await runMaxBuffer(t, execaMethod, fdNumber, {all, length});
assertErrorMessage(t, shortMessage, {execaMethod, fdNumber});
t.is(all ? allOutput : stdio[fdNumber], getExpectedOutput(length));
};
test('maxBuffer truncates stdout', testMaxBufferLimit, execa, 1, false);
test('maxBuffer truncates stderr', testMaxBufferLimit, execa, 2, false);
test('maxBuffer truncates stdio[*]', testMaxBufferLimit, execa, 3, false);
test('maxBuffer truncates all', testMaxBufferLimit, execa, 1, true);
test('maxBuffer truncates stdout, sync', testMaxBufferLimit, execaSync, 1, false);
test('maxBuffer truncates stderr, sync', testMaxBufferLimit, execaSync, 2, false);
test('maxBuffer truncates stdio[*], sync', testMaxBufferLimit, execaSync, 3, false);
test('maxBuffer truncates all, sync', testMaxBufferLimit, execaSync, 1, true);
const MAX_BUFFER_DEFAULT = 1e8;
const testMaxBufferDefault = async (t, execaMethod, fdNumber, maxBuffer) => {
const length = MAX_BUFFER_DEFAULT;
const {shortMessage, stdio} = await runMaxBuffer(t, execaMethod, fdNumber, {length: MAX_BUFFER_DEFAULT + 1, maxBuffer});
assertErrorMessage(t, shortMessage, {execaMethod, fdNumber, length});
t.is(stdio[fdNumber], getExpectedOutput(length));
};
test('maxBuffer has a default value with stdout', testMaxBufferDefault, execa, 1, undefined);
test('maxBuffer has a default value with stderr', testMaxBufferDefault, execa, 2, undefined);
test('maxBuffer has a default value with stdio[*]', testMaxBufferDefault, execa, 3, undefined);
test('maxBuffer has a default value with stdout, sync', testMaxBufferDefault, execaSync, 1, undefined);
test('maxBuffer has a default value with stderr, sync', testMaxBufferDefault, execaSync, 2, undefined);
test('maxBuffer has a default value with stdio[*], sync', testMaxBufferDefault, execaSync, 3, undefined);
test('maxBuffer has a default value with stdout with fd-specific options', testMaxBufferDefault, execa, 1, {stderr: 1e9});
test('maxBuffer has a default value with stderr with fd-specific options', testMaxBufferDefault, execa, 2, {stdout: 1e9});
test('maxBuffer has a default value with stdio[*] with fd-specific options', testMaxBufferDefault, execa, 3, {stdout: 1e9});
test('maxBuffer has a default value with stdout with empty fd-specific options', testMaxBufferDefault, execa, 1, {});
const testFdSpecific = async (t, fdNumber, fdName, execaMethod) => {
const length = 1;
const {shortMessage, stdio} = await runMaxBuffer(t, execaMethod, fdNumber, {maxBuffer: {[fdName]: length}});
assertErrorMessage(t, shortMessage, {execaMethod, fdNumber, length});
t.is(stdio[fdNumber], getExpectedOutput(length));
};
test('maxBuffer truncates file descriptors with fd-specific options, stdout', testFdSpecific, 1, 'stdout', execa);
test('maxBuffer truncates file descriptors with fd-specific options, fd1', testFdSpecific, 1, 'fd1', execa);
test('maxBuffer truncates file descriptors with fd-specific options, stderr', testFdSpecific, 2, 'stderr', execa);
test('maxBuffer truncates file descriptors with fd-specific options, fd2', testFdSpecific, 2, 'fd2', execa);
test('maxBuffer truncates file descriptors with fd-specific options, stdout, all', testFdSpecific, 1, 'all', execa);
test('maxBuffer truncates file descriptors with fd-specific options, stderr, all', testFdSpecific, 2, 'all', execa);
test('maxBuffer truncates file descriptors with fd-specific options, fd3', testFdSpecific, 3, 'fd3', execa);
test('maxBuffer.stdout is used for stdout with fd-specific options, stdout, sync', testFdSpecific, 1, 'stdout', execaSync);
test('maxBuffer does not affect other file descriptors with fd-specific options', async t => {
const {isMaxBuffer} = await getMaxBufferSubprocess(execa, 2, {maxBuffer: {stdout: 1}});
t.false(isMaxBuffer);
});
test('maxBuffer.stdout is used for other file descriptors with fd-specific options, sync', async t => {
const length = 1;
const {shortMessage, stderr} = await runMaxBuffer(t, execaSync, 2, {maxBuffer: {stdout: length}});
assertErrorMessage(t, shortMessage, {execaMethod: execaSync, fdNumber: 2, length});
t.is(stderr, getExpectedOutput(length));
});
const testAll = async (t, shouldFail) => {
const difference = shouldFail ? 0 : 1;
const maxBufferStdout = 2;
const maxBufferStderr = 4 - difference;
const {isMaxBuffer, shortMessage, stdout, stderr, all} = await execa(
'noop-both.js',
['\n'.repeat(maxBufferStdout - 1), '\n'.repeat(maxBufferStderr - difference)],
{
maxBuffer: {stdout: maxBufferStdout, stderr: maxBufferStderr},
all: true,
stripFinalNewline: false,
reject: false,
},
);
t.is(isMaxBuffer, shouldFail);
if (shouldFail) {
assertErrorMessage(t, shortMessage, {fdNumber: 2, length: maxBufferStderr});
}
t.is(stdout, '\n'.repeat(maxBufferStdout));
t.is(stderr, '\n'.repeat(maxBufferStderr));
t.is(all, '\n'.repeat(maxBufferStdout + maxBufferStderr));
};
test('maxBuffer.stdout can differ from maxBuffer.stderr, combined with all, below threshold', testAll, false);
test('maxBuffer.stdout can differ from maxBuffer.stderr, combined with all, above threshold', testAll, true);
const testInvalidFd = async (t, fdName, execaMethod) => {
const {message} = t.throws(() => {
execaMethod('empty.js', {maxBuffer: {[fdName]: 0}});
});
t.true(message.includes(`"maxBuffer.${fdName}" is invalid`));
};
test('maxBuffer.stdin is invalid', testInvalidFd, 'stdin', execa);
test('maxBuffer.fd0 is invalid', testInvalidFd, 'fd0', execa);
test('maxBuffer.other is invalid', testInvalidFd, 'other', execa);
test('maxBuffer.fd10 is invalid', testInvalidFd, 'fd10', execa);
test('maxBuffer.stdin is invalid, sync', testInvalidFd, 'stdin', execaSync);
test('maxBuffer.fd0 is invalid, sync', testInvalidFd, 'fd0', execaSync);
test('maxBuffer.other is invalid, sync', testInvalidFd, 'other', execaSync);
test('maxBuffer.fd10 is invalid, sync', testInvalidFd, 'fd10', execaSync);
const testMaxBufferEncoding = async (t, execaMethod, fdNumber) => {
const {shortMessage, stdio} = await runMaxBuffer(t, execaMethod, fdNumber, {encoding: 'buffer'});
assertErrorMessage(t, shortMessage, {execaMethod, fdNumber, unit: 'bytes'});
const stream = stdio[fdNumber];
t.true(stream instanceof Uint8Array);
t.is(Buffer.from(stream).toString(), getExpectedOutput());
};
test('maxBuffer works with encoding buffer and stdout', testMaxBufferEncoding, execa, 1);
test('maxBuffer works with encoding buffer and stderr', testMaxBufferEncoding, execa, 2);
test('maxBuffer works with encoding buffer and stdio[*]', testMaxBufferEncoding, execa, 3);
test('maxBuffer works with encoding buffer and stdout, sync', testMaxBufferEncoding, execaSync, 1);
test('maxBuffer works with encoding buffer and stderr, sync', testMaxBufferEncoding, execaSync, 2);
test('maxBuffer works with encoding buffer and stdio[*], sync', testMaxBufferEncoding, execaSync, 3);
const testMaxBufferHex = async (t, fdNumber) => {
const length = maxBuffer / 2;
const {shortMessage, stdio} = await runMaxBuffer(t, execa, fdNumber, {length, encoding: 'hex'});
assertErrorMessage(t, shortMessage, {fdNumber});
t.is(stdio[fdNumber], Buffer.from(getExpectedOutput(length)).toString('hex'));
};
test('maxBuffer works with other encodings and stdout', testMaxBufferHex, 1);
test('maxBuffer works with other encodings and stderr', testMaxBufferHex, 2);
test('maxBuffer works with other encodings and stdio[*]', testMaxBufferHex, 3);
const testMaxBufferHexSync = async (t, fdNumber) => {
const length = maxBuffer / 2;
const {isMaxBuffer, stdio} = await getMaxBufferSubprocess(execaSync, fdNumber, {length, encoding: 'hex'});
t.false(isMaxBuffer);
t.is(stdio[fdNumber], Buffer.from(getExpectedOutput(length + 1)).toString('hex'));
};
test('maxBuffer ignores other encodings and stdout, sync', testMaxBufferHexSync, 1);
test('maxBuffer ignores other encodings and stderr, sync', testMaxBufferHexSync, 2);
test('maxBuffer ignores other encodings and stdio[*], sync', testMaxBufferHexSync, 3);
const testNoMaxBuffer = async (t, fdNumber, buffer) => {
const subprocess = getMaxBufferSubprocess(execa, fdNumber, {buffer});
const [{isMaxBuffer, stdio}, output] = await Promise.all([
subprocess,
getStream(subprocess.stdio[fdNumber]),
]);
t.false(isMaxBuffer);
t.is(stdio[fdNumber], undefined);
t.is(output, getExpectedOutput(maxBuffer + 1));
};
test('do not buffer stdout when `buffer` set to `false`', testNoMaxBuffer, 1, false);
test('do not buffer stdout when `buffer` set to `false`, fd-specific', testNoMaxBuffer, 1, {stdout: false});
test('do not buffer stderr when `buffer` set to `false`', testNoMaxBuffer, 2, false);
test('do not buffer stderr when `buffer` set to `false`, fd-specific', testNoMaxBuffer, 2, {stderr: false});
test('do not buffer stdio[*] when `buffer` set to `false`', testNoMaxBuffer, 3, false);
test('do not buffer stdio[*] when `buffer` set to `false`, fd-specific', testNoMaxBuffer, 3, {fd3: false});
const testNoMaxBufferSync = (t, fdNumber, buffer) => {
const {isMaxBuffer, stdio} = getMaxBufferSubprocess(execaSync, fdNumber, {buffer});
t.false(isMaxBuffer);
t.is(stdio[fdNumber], undefined);
};
// @todo: add tests for fd3 once the following Node.js bug is fixed.
// https://github.com/nodejs/node/issues/52422
test('do not buffer stdout when `buffer` set to `false`, sync', testNoMaxBufferSync, 1, false);
test('do not buffer stdout when `buffer` set to `false`, fd-specific, sync', testNoMaxBufferSync, 1, {stdout: false});
test('do not buffer stderr when `buffer` set to `false`, sync', testNoMaxBufferSync, 2, false);
test('do not buffer stderr when `buffer` set to `false`, fd-specific, sync', testNoMaxBufferSync, 2, {stderr: false});
const testMaxBufferAbort = async (t, fdNumber) => {
const subprocess = getMaxBufferSubprocess(execa, fdNumber);
const [{isMaxBuffer, shortMessage}] = await Promise.all([
t.throwsAsync(subprocess, maxBufferMessage),
t.throwsAsync(getStream(subprocess.stdio[fdNumber]), {code: 'ERR_STREAM_PREMATURE_CLOSE'}),
]);
t.true(isMaxBuffer);
assertErrorMessage(t, shortMessage, {execaMethod: execa, fdNumber});
};
test('abort stream when hitting maxBuffer with stdout', testMaxBufferAbort, 1);
test('abort stream when hitting maxBuffer with stderr', testMaxBufferAbort, 2);
test('abort stream when hitting maxBuffer with stdio[*]', testMaxBufferAbort, 3);
test('error.isMaxBuffer is false on early errors', async t => {
const {failed, isMaxBuffer} = await getEarlyErrorSubprocess({reject: false, maxBuffer: 1});
t.true(failed);
t.false(isMaxBuffer);
});
test('maxBuffer works with result.ipcOutput', async t => {
const {
isMaxBuffer,
shortMessage,
message,
stderr,
ipcOutput,
} = await t.throwsAsync(execa('ipc-send-twice.js', {ipc: true, maxBuffer: {ipc: 1}}));
t.true(isMaxBuffer);
t.is(shortMessage, 'Command\'s IPC output was larger than 1 messages: ipc-send-twice.js\nmaxBuffer exceeded');
t.true(message.endsWith(`\n\n${foobarArray[0]}`));
t.is(stderr, '');
t.deepEqual(ipcOutput, [foobarArray[0]]);
});
test('maxBuffer is ignored with result.ipcOutput if buffer is false', async t => {
const {ipcOutput} = await execa('ipc-send-twice.js', {ipc: true, maxBuffer: {ipc: 1}, buffer: false});
t.deepEqual(ipcOutput, []);
});
================================================
FILE: test/io/output-async.js
================================================
import {once, defaultMaxListeners} from 'node:events';
import process from 'node:process';
import {setImmediate} from 'node:timers/promises';
import test from 'ava';
import {execa} from '../../index.js';
import {STANDARD_STREAMS} from '../helpers/stdio.js';
import {foobarString} from '../helpers/input.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {assertMaxListeners} from '../helpers/listeners.js';
import {PARALLEL_COUNT} from '../helpers/parallel.js';
setFixtureDirectory();
const getStandardStreamListeners = stream => Object.fromEntries(stream.eventNames().map(eventName => [eventName, stream.listeners(eventName)]));
const getStandardStreamsListeners = () => STANDARD_STREAMS.map(stream => getStandardStreamListeners(stream));
const getComplexStdio = isMultiple => ({
stdin: ['pipe', 'inherit', ...(isMultiple ? [0, process.stdin] : [])],
stdout: ['pipe', 'inherit', ...(isMultiple ? [1, process.stdout] : [])],
stderr: ['pipe', 'inherit', ...(isMultiple ? [2, process.stderr] : [])],
});
const onStdinRemoveListener = () => once(process.stdin, 'removeListener');
const testListenersCleanup = async (t, isMultiple) => {
const streamsPreviousListeners = getStandardStreamsListeners();
const subprocess = execa('empty.js', getComplexStdio(isMultiple));
t.notDeepEqual(getStandardStreamsListeners(), streamsPreviousListeners);
await Promise.all([subprocess, onStdinRemoveListener()]);
if (isMultiple) {
await onStdinRemoveListener();
}
for (const [fdNumber, streamNewListeners] of Object.entries(getStandardStreamsListeners())) {
const defaultListeners = Object.fromEntries(Reflect.ownKeys(streamNewListeners).map(eventName => [eventName, []]));
t.deepEqual(streamNewListeners, {...defaultListeners, ...streamsPreviousListeners[fdNumber]});
}
};
test.serial('process.std* listeners are cleaned up on success with a single input', testListenersCleanup, false);
test.serial('process.std* listeners are cleaned up on success with multiple inputs', testListenersCleanup, true);
test.serial('Can spawn many subprocesses in parallel', async t => {
const results = await Promise.all(
Array.from({length: PARALLEL_COUNT}, () => execa('noop.js', [foobarString])),
);
t.true(results.every(({stdout}) => stdout === foobarString));
});
const testMaxListeners = async (t, isMultiple, maxListenersCount) => {
const checkMaxListeners = assertMaxListeners(t);
for (const standardStream of STANDARD_STREAMS) {
standardStream.setMaxListeners(maxListenersCount);
}
try {
const results = await Promise.all(
Array.from({length: PARALLEL_COUNT}, () => execa('empty.js', getComplexStdio(isMultiple))),
);
t.true(results.every(({exitCode}) => exitCode === 0));
} finally {
await setImmediate();
await setImmediate();
checkMaxListeners();
for (const standardStream of STANDARD_STREAMS) {
t.is(standardStream.getMaxListeners(), maxListenersCount);
standardStream.setMaxListeners(defaultMaxListeners);
}
}
};
test.serial('No warning with maxListeners 1 and ["pipe", "inherit"]', testMaxListeners, false, 1);
test.serial('No warning with maxListeners default and ["pipe", "inherit"]', testMaxListeners, false, defaultMaxListeners);
test.serial('No warning with maxListeners 100 and ["pipe", "inherit"]', testMaxListeners, false, 100);
test.serial('No warning with maxListeners Infinity and ["pipe", "inherit"]', testMaxListeners, false, Number.POSITIVE_INFINITY);
test.serial('No warning with maxListeners 0 and ["pipe", "inherit"]', testMaxListeners, false, 0);
test.serial('No warning with maxListeners 1 and ["pipe", "inherit"], multiple inputs', testMaxListeners, true, 1);
test.serial('No warning with maxListeners default and ["pipe", "inherit"], multiple inputs', testMaxListeners, true, defaultMaxListeners);
test.serial('No warning with maxListeners 100 and ["pipe", "inherit"], multiple inputs', testMaxListeners, true, 100);
test.serial('No warning with maxListeners Infinity and ["pipe", "inherit"], multiple inputs', testMaxListeners, true, Number.POSITIVE_INFINITY);
test.serial('No warning with maxListeners 0 and ["pipe", "inherit"], multiple inputs', testMaxListeners, true, 0);
================================================
FILE: test/io/output-sync.js
================================================
import test from 'ava';
import {execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {throwingGenerator} from '../helpers/generator.js';
import {foobarString} from '../helpers/input.js';
setFixtureDirectory();
test('Handles errors with stdout generator, sync', t => {
const cause = new Error(foobarString);
const error = t.throws(() => {
execaSync('noop.js', {stdout: throwingGenerator(cause)()});
});
t.is(error.cause, cause);
});
test('Handles errors with stdout generator, spawn failure, sync', t => {
const cause = new Error(foobarString);
const error = t.throws(() => {
execaSync('noop.js', {cwd: 'does_not_exist', stdout: throwingGenerator(cause)()});
});
t.true(error.failed);
t.is(error.cause.code, 'ENOENT');
});
test('Handles errors with stdout generator, subprocess failure, sync', t => {
const cause = new Error(foobarString);
const error = t.throws(() => {
execaSync('noop-fail.js', ['1'], {stdout: throwingGenerator(cause)()});
});
t.true(error.failed);
t.is(error.cause, cause);
});
================================================
FILE: test/io/pipeline.js
================================================
import test from 'ava';
import {execa} from '../../index.js';
import {getStdio, STANDARD_STREAMS} from '../helpers/stdio.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {getEarlyErrorSubprocess, expectedEarlyError} from '../helpers/early-error.js';
setFixtureDirectory();
const testDestroyStandard = async (t, fdNumber) => {
const subprocess = execa('forever.js', {...getStdio(fdNumber, [STANDARD_STREAMS[fdNumber], 'pipe']), timeout: 1});
await t.throwsAsync(subprocess, {message: /timed out/});
t.false(STANDARD_STREAMS[fdNumber].destroyed);
};
test('Does not destroy process.stdin on subprocess errors', testDestroyStandard, 0);
test('Does not destroy process.stdout on subprocess errors', testDestroyStandard, 1);
test('Does not destroy process.stderr on subprocess errors', testDestroyStandard, 2);
const testDestroyStandardSpawn = async (t, fdNumber) => {
const error = await t.throwsAsync(getEarlyErrorSubprocess(getStdio(fdNumber, [STANDARD_STREAMS[fdNumber], 'pipe'])));
t.like(error, expectedEarlyError);
t.false(STANDARD_STREAMS[fdNumber].destroyed);
};
test('Does not destroy process.stdin on subprocess early errors', testDestroyStandardSpawn, 0);
test('Does not destroy process.stdout on subprocess early errors', testDestroyStandardSpawn, 1);
test('Does not destroy process.stderr on subprocess early errors', testDestroyStandardSpawn, 2);
const testDestroyStandardStream = async (t, fdNumber) => {
const subprocess = execa('forever.js', getStdio(fdNumber, [STANDARD_STREAMS[fdNumber], 'pipe']));
const cause = new Error('test');
subprocess.stdio[fdNumber].destroy(cause);
subprocess.kill();
t.like(await t.throwsAsync(subprocess), {cause});
t.false(STANDARD_STREAMS[fdNumber].destroyed);
};
test('Does not destroy process.stdin on stream subprocess errors', testDestroyStandardStream, 0);
test('Does not destroy process.stdout on stream subprocess errors', testDestroyStandardStream, 1);
test('Does not destroy process.stderr on stream subprocess errors', testDestroyStandardStream, 2);
================================================
FILE: test/io/strip-newline.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {fullStdio} from '../helpers/stdio.js';
import {noopGenerator} from '../helpers/generator.js';
import {foobarString} from '../helpers/input.js';
setFixtureDirectory();
// eslint-disable-next-line max-params
const testStripFinalNewline = async (t, fdNumber, stripFinalNewline, shouldStrip, execaMethod) => {
const {stdio} = await execaMethod('noop-fd.js', [`${fdNumber}`, `${foobarString}\n`], {...fullStdio, stripFinalNewline});
t.is(stdio[fdNumber], `${foobarString}${shouldStrip ? '' : '\n'}`);
};
test('stripFinalNewline: default with stdout', testStripFinalNewline, 1, undefined, true, execa);
test('stripFinalNewline: true with stdout', testStripFinalNewline, 1, true, true, execa);
test('stripFinalNewline: false with stdout', testStripFinalNewline, 1, false, false, execa);
test('stripFinalNewline: default with stderr', testStripFinalNewline, 2, undefined, true, execa);
test('stripFinalNewline: true with stderr', testStripFinalNewline, 2, true, true, execa);
test('stripFinalNewline: false with stderr', testStripFinalNewline, 2, false, false, execa);
test('stripFinalNewline: default with stdio[*]', testStripFinalNewline, 3, undefined, true, execa);
test('stripFinalNewline: true with stdio[*]', testStripFinalNewline, 3, true, true, execa);
test('stripFinalNewline: false with stdio[*]', testStripFinalNewline, 3, false, false, execa);
test('stripFinalNewline: default with stdout, fd-specific', testStripFinalNewline, 1, {}, true, execa);
test('stripFinalNewline: true with stdout, fd-specific', testStripFinalNewline, 1, {stdout: true}, true, execa);
test('stripFinalNewline: false with stdout, fd-specific', testStripFinalNewline, 1, {stdout: false}, false, execa);
test('stripFinalNewline: default with stderr, fd-specific', testStripFinalNewline, 2, {}, true, execa);
test('stripFinalNewline: true with stderr, fd-specific', testStripFinalNewline, 2, {stderr: true}, true, execa);
test('stripFinalNewline: false with stderr, fd-specific', testStripFinalNewline, 2, {stderr: false}, false, execa);
test('stripFinalNewline: default with stdio[*], fd-specific', testStripFinalNewline, 3, {}, true, execa);
test('stripFinalNewline: true with stdio[*], fd-specific', testStripFinalNewline, 3, {fd3: true}, true, execa);
test('stripFinalNewline: false with stdio[*], fd-specific', testStripFinalNewline, 3, {fd3: false}, false, execa);
test('stripFinalNewline: default with stdout, sync', testStripFinalNewline, 1, undefined, true, execaSync);
test('stripFinalNewline: true with stdout, sync', testStripFinalNewline, 1, true, true, execaSync);
test('stripFinalNewline: false with stdout, sync', testStripFinalNewline, 1, false, false, execaSync);
test('stripFinalNewline: default with stderr, sync', testStripFinalNewline, 2, undefined, true, execaSync);
test('stripFinalNewline: true with stderr, sync', testStripFinalNewline, 2, true, true, execaSync);
test('stripFinalNewline: false with stderr, sync', testStripFinalNewline, 2, false, false, execaSync);
test('stripFinalNewline: default with stdio[*], sync', testStripFinalNewline, 3, undefined, true, execaSync);
test('stripFinalNewline: true with stdio[*], sync', testStripFinalNewline, 3, true, true, execaSync);
test('stripFinalNewline: false with stdio[*], sync', testStripFinalNewline, 3, false, false, execaSync);
test('stripFinalNewline: default with stdout, fd-specific, sync', testStripFinalNewline, 1, {}, true, execaSync);
test('stripFinalNewline: true with stdout, fd-specific, sync', testStripFinalNewline, 1, {stdout: true}, true, execaSync);
test('stripFinalNewline: false with stdout, fd-specific, sync', testStripFinalNewline, 1, {stdout: false}, false, execaSync);
test('stripFinalNewline: default with stderr, fd-specific, sync', testStripFinalNewline, 2, {}, true, execaSync);
test('stripFinalNewline: true with stderr, fd-specific, sync', testStripFinalNewline, 2, {stderr: true}, true, execaSync);
test('stripFinalNewline: false with stderr, fd-specific, sync', testStripFinalNewline, 2, {stderr: false}, false, execaSync);
test('stripFinalNewline: default with stdio[*], fd-specific, sync', testStripFinalNewline, 3, {}, true, execaSync);
test('stripFinalNewline: true with stdio[*], fd-specific, sync', testStripFinalNewline, 3, {fd3: true}, true, execaSync);
test('stripFinalNewline: false with stdio[*], fd-specific, sync', testStripFinalNewline, 3, {fd3: false}, false, execaSync);
test('stripFinalNewline is not used in objectMode', async t => {
const {stdout} = await execa('noop-fd.js', ['1', `${foobarString}\n`], {stripFinalNewline: true, stdout: noopGenerator(true, false, true)});
t.deepEqual(stdout, [`${foobarString}\n`]);
});
================================================
FILE: test/ipc/buffer-messages.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString, foobarArray} from '../helpers/input.js';
import {PARALLEL_COUNT} from '../helpers/parallel.js';
setFixtureDirectory();
const testResultIpc = async (t, options) => {
const {ipcOutput} = await execa('ipc-send-twice.js', {...options, ipc: true});
t.deepEqual(ipcOutput, foobarArray);
};
test('Sets result.ipcOutput', testResultIpc, {});
test('Sets result.ipcOutput, fd-specific buffer', testResultIpc, {buffer: {stdout: false}});
const testResultNoBuffer = async (t, options) => {
const {ipcOutput} = await execa('ipc-send.js', {...options, ipc: true});
t.deepEqual(ipcOutput, []);
};
test('Sets empty result.ipcOutput if buffer is false', testResultNoBuffer, {buffer: false});
test('Sets empty result.ipcOutput if buffer is false, fd-specific buffer', testResultNoBuffer, {buffer: {ipc: false}});
test('Can use IPC methods when buffer is false', async t => {
const subprocess = execa('ipc-send.js', {ipc: true, buffer: false});
t.is(await subprocess.getOneMessage(), foobarString);
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, []);
});
test('Sets empty result.ipcOutput if ipc is false', async t => {
const {ipcOutput} = await execa('empty.js');
t.deepEqual(ipcOutput, []);
});
test('Sets empty result.ipcOutput, sync', t => {
const {ipcOutput} = execaSync('empty.js');
t.deepEqual(ipcOutput, []);
});
const testErrorIpc = async (t, options) => {
const {ipcOutput} = await t.throwsAsync(execa('ipc-send-fail.js', {...options, ipc: true}));
t.deepEqual(ipcOutput, [foobarString]);
};
test('Sets error.ipcOutput', testErrorIpc, {});
test('Sets error.ipcOutput, fd-specific buffer', testErrorIpc, {buffer: {stdout: false}});
const testErrorNoBuffer = async (t, options) => {
const {ipcOutput} = await t.throwsAsync(execa('ipc-send-fail.js', {...options, ipc: true}));
t.deepEqual(ipcOutput, []);
};
test('Sets empty error.ipcOutput if buffer is false', testErrorNoBuffer, {buffer: false});
test('Sets empty error.ipcOutput if buffer is false, fd-specific buffer', testErrorNoBuffer, {buffer: {ipc: false}});
test('Sets empty error.ipcOutput if ipc is false', async t => {
const {ipcOutput} = await t.throwsAsync(execa('fail.js'));
t.deepEqual(ipcOutput, []);
});
test('Sets empty error.ipcOutput, sync', t => {
const {ipcOutput} = t.throws(() => execaSync('fail.js'));
t.deepEqual(ipcOutput, []);
});
test.serial('Can retrieve initial IPC messages under heavy load', async t => {
await Promise.all(
Array.from({length: PARALLEL_COUNT}, async (_, index) => {
const {ipcOutput} = await execa('ipc-send-argv.js', [`${index}`], {ipc: true});
t.deepEqual(ipcOutput, [`${index}`]);
}),
);
});
================================================
FILE: test/ipc/forward.js
================================================
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString, foobarArray} from '../helpers/input.js';
import {iterateAllMessages, alwaysPass} from '../helpers/ipc.js';
setFixtureDirectory();
const testParentErrorOne = async (t, filter, buffer) => {
const subprocess = execa('ipc-send.js', {ipc: true, buffer});
const promise = subprocess.getOneMessage({filter});
const cause = new Error(foobarString);
subprocess.emit('error', cause);
t.is(await promise, foobarString);
const error = await t.throwsAsync(subprocess);
t.is(error.exitCode, undefined);
t.false(error.isTerminated);
t.is(error.cause, cause);
if (buffer) {
t.deepEqual(error.ipcOutput, [foobarString]);
}
};
test('"error" event does not interrupt subprocess.getOneMessage(), buffer false', testParentErrorOne, undefined, false);
test('"error" event does not interrupt subprocess.getOneMessage(), buffer true', testParentErrorOne, undefined, true);
test('"error" event does not interrupt subprocess.getOneMessage(), buffer false, filter', testParentErrorOne, alwaysPass, false);
test('"error" event does not interrupt subprocess.getOneMessage(), buffer true, filter', testParentErrorOne, alwaysPass, true);
const testSubprocessErrorOne = async (t, filter, buffer) => {
const subprocess = execa('ipc-process-error.js', [`${filter}`], {ipc: true, buffer});
await subprocess.sendMessage(foobarString);
t.is(await subprocess.getOneMessage(), foobarString);
const {ipcOutput} = await subprocess;
if (buffer) {
t.deepEqual(ipcOutput, [foobarString]);
}
};
test('"error" event does not interrupt exports.getOneMessage(), buffer false', testSubprocessErrorOne, false, false);
test('"error" event does not interrupt exports.getOneMessage(), buffer true', testSubprocessErrorOne, false, true);
test('"error" event does not interrupt exports.getOneMessage(), buffer false, filter', testSubprocessErrorOne, true, false);
test('"error" event does not interrupt exports.getOneMessage(), buffer true, filter', testSubprocessErrorOne, true, true);
const testParentErrorEach = async (t, buffer) => {
const subprocess = execa('ipc-send-twice.js', {ipc: true, buffer});
const promise = iterateAllMessages(subprocess);
const cause = new Error(foobarString);
subprocess.emit('error', cause);
const error = await t.throwsAsync(subprocess);
t.is(error, await t.throwsAsync(promise));
t.is(error.exitCode, undefined);
t.false(error.isTerminated);
t.is(error.cause, cause);
if (buffer) {
t.deepEqual(error.ipcOutput, foobarArray);
}
};
test('"error" event does not interrupt subprocess.getEachMessage(), buffer false', testParentErrorEach, false);
test('"error" event does not interrupt subprocess.getEachMessage(), buffer true', testParentErrorEach, true);
const testSubprocessErrorEach = async (t, filter, buffer) => {
const subprocess = execa('ipc-iterate-error.js', [`${filter}`], {ipc: true, buffer});
await subprocess.sendMessage('.');
t.is(await subprocess.getOneMessage(), '.');
await subprocess.sendMessage(foobarString);
const {ipcOutput} = await subprocess;
if (buffer) {
t.deepEqual(ipcOutput, ['.']);
}
};
test('"error" event does not interrupt exports.getEachMessage(), buffer false', testSubprocessErrorEach, 'ipc-iterate-error.js', false);
test('"error" event does not interrupt exports.getEachMessage(), buffer true', testSubprocessErrorEach, 'ipc-iterate-error.js', true);
test('"error" event does not interrupt result.ipcOutput', async t => {
const subprocess = execa('ipc-echo-twice.js', {ipcInput: foobarString});
const cause = new Error(foobarString);
subprocess.emit('error', cause);
t.is(await subprocess.getOneMessage(), foobarString);
await subprocess.sendMessage(foobarString);
t.is(await subprocess.getOneMessage(), foobarString);
const error = await t.throwsAsync(subprocess);
t.is(error.exitCode, undefined);
t.false(error.isTerminated);
t.is(error.cause, cause);
t.deepEqual(error.ipcOutput, [foobarString, foobarString]);
});
================================================
FILE: test/ipc/get-each.js
================================================
import {scheduler} from 'node:timers/promises';
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString, foobarArray} from '../helpers/input.js';
import {PARALLEL_COUNT} from '../helpers/parallel.js';
import {iterateAllMessages} from '../helpers/ipc.js';
setFixtureDirectory();
test('Can iterate over IPC messages', async t => {
let count = 0;
const subprocess = execa('ipc-send-twice.js', {ipc: true});
for await (const message of subprocess.getEachMessage()) {
t.is(message, foobarArray[count++]);
}
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, foobarArray);
});
test('Can iterate over IPC messages in subprocess', async t => {
const subprocess = execa('ipc-iterate.js', {ipc: true});
await subprocess.sendMessage('.');
await subprocess.sendMessage('.');
await subprocess.sendMessage(foobarString);
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, ['.', '.']);
});
test('subprocess.getEachMessage() can be called twice at the same time', async t => {
const subprocess = execa('ipc-send-twice.js', {ipc: true});
t.deepEqual(
await Promise.all([iterateAllMessages(subprocess), iterateAllMessages(subprocess)]),
[foobarArray, foobarArray],
);
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, foobarArray);
});
const iterateAndBreak = async (t, subprocess) => {
// eslint-disable-next-line no-unreachable-loop
for await (const message of subprocess.getEachMessage()) {
t.is(message, foobarString);
break;
}
};
test('Breaking in subprocess.getEachMessage() disconnects', async t => {
const subprocess = execa('ipc-iterate-send.js', {ipc: true});
await iterateAndBreak(t, subprocess);
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, [foobarString]);
});
test('Breaking from subprocess.getEachMessage() awaits the subprocess', async t => {
const subprocess = execa('ipc-send-wait-print.js', {ipc: true});
await iterateAndBreak(t, subprocess);
const {ipcOutput, stdout} = await subprocess;
t.deepEqual(ipcOutput, [foobarString]);
t.is(stdout, '.');
});
test('Breaking from exports.getEachMessage() disconnects', async t => {
const subprocess = execa('ipc-iterate-break.js', {ipc: true});
t.is(await subprocess.getOneMessage(), foobarString);
await subprocess.sendMessage(foobarString);
const ipcError = await t.throwsAsync(subprocess.getOneMessage());
t.true(ipcError.message.includes('subprocess.getOneMessage() could not complete'));
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, [foobarString]);
});
const iterateAndThrow = async (t, subprocess, cause) => {
// eslint-disable-next-line no-unreachable-loop
for await (const message of subprocess.getEachMessage()) {
t.is(message, foobarString);
throw cause;
}
};
test('Throwing from subprocess.getEachMessage() disconnects', async t => {
const subprocess = execa('ipc-iterate-send.js', {ipc: true});
const cause = new Error(foobarString);
t.is(await t.throwsAsync(iterateAndThrow(t, subprocess, cause)), cause);
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, [foobarString]);
});
test('Throwing from subprocess.getEachMessage() awaits the subprocess', async t => {
const subprocess = execa('ipc-send-wait-print.js', {ipc: true});
const cause = new Error(foobarString);
t.is(await t.throwsAsync(iterateAndThrow(t, subprocess, cause)), cause);
const {ipcOutput, stdout} = await subprocess;
t.deepEqual(ipcOutput, [foobarString]);
t.is(stdout, '.');
});
test('Throwing from exports.getEachMessage() disconnects', async t => {
const subprocess = execa('ipc-iterate-throw.js', {ipc: true});
t.is(await subprocess.getOneMessage(), foobarString);
await subprocess.sendMessage(foobarString);
const ipcError = await t.throwsAsync(subprocess.getOneMessage());
t.true(ipcError.message.includes('subprocess.getOneMessage() could not complete'));
const {exitCode, isTerminated, message, ipcOutput} = await t.throwsAsync(subprocess);
t.is(exitCode, 1);
t.false(isTerminated);
t.true(message.includes(`Error: ${foobarString}`));
t.deepEqual(ipcOutput, [foobarString]);
});
test.serial('Can send many messages at once with exports.getEachMessage()', async t => {
const subprocess = execa('ipc-iterate.js', {ipc: true});
await Promise.all(Array.from({length: PARALLEL_COUNT}, (_, index) => subprocess.sendMessage(index)));
await subprocess.sendMessage(foobarString);
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, Array.from({length: PARALLEL_COUNT}, (_, index) => index));
});
test('subprocess.getOneMessage() can be called multiple times in a row, buffer true', async t => {
const subprocess = execa('ipc-print-many-each.js', [`${PARALLEL_COUNT}`], {ipc: true});
const indexes = Array.from({length: PARALLEL_COUNT}, (_, index) => `${index}`);
await Promise.all(indexes.map(index => subprocess.sendMessage(index)));
const {stdout} = await subprocess;
const expectedOutput = indexes.join('\n');
t.is(stdout, expectedOutput);
});
test('Disconnecting in the current process stops exports.getEachMessage()', async t => {
const subprocess = execa('ipc-iterate-print.js', {ipc: true});
t.is(await subprocess.getOneMessage(), foobarString);
await subprocess.sendMessage('.');
subprocess.disconnect();
const {stdout} = await subprocess;
t.is(stdout, '.');
});
test('Disconnecting in the subprocess stops subprocess.getEachMessage()', async t => {
const subprocess = execa('ipc-send-disconnect.js', {ipc: true});
for await (const message of subprocess.getEachMessage()) {
t.is(message, foobarString);
}
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, [foobarString]);
});
test('Exiting the subprocess stops subprocess.getEachMessage()', async t => {
const subprocess = execa('ipc-send.js', {ipc: true});
for await (const message of subprocess.getEachMessage()) {
t.is(message, foobarString);
}
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, [foobarString]);
});
const testCleanupListeners = async (t, buffer) => {
const subprocess = execa('ipc-send.js', {ipc: true, buffer});
t.is(subprocess.listenerCount('message'), 1);
t.is(subprocess.listenerCount('disconnect'), 1);
const promise = iterateAllMessages(subprocess);
t.is(subprocess.listenerCount('message'), 1);
t.is(subprocess.listenerCount('disconnect'), 1);
t.deepEqual(await promise, [foobarString]);
t.is(subprocess.listenerCount('message'), 0);
t.is(subprocess.listenerCount('disconnect'), 0);
};
test('Cleans up subprocess.getEachMessage() listeners, buffer false', testCleanupListeners, false);
test('Cleans up subprocess.getEachMessage() listeners, buffer true', testCleanupListeners, true);
const sendContinuousMessages = async subprocess => {
while (subprocess.connected) {
for (let index = 0; index < 10; index += 1) {
subprocess.emit('message', foobarString);
}
// eslint-disable-next-line no-await-in-loop
await scheduler.yield();
}
};
test.serial('Handles buffered messages when disconnecting', async t => {
const subprocess = execa('ipc-send-fail.js', {ipc: true, buffer: false});
const promise = subprocess.getOneMessage();
subprocess.emit('message', foobarString);
t.is(await promise, foobarString);
sendContinuousMessages(subprocess);
const {exitCode, isTerminated, ipcOutput} = await t.throwsAsync(iterateAllMessages(subprocess));
t.is(exitCode, 1);
t.false(isTerminated);
t.deepEqual(ipcOutput, []);
});
================================================
FILE: test/ipc/get-one.js
================================================
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString, foobarArray} from '../helpers/input.js';
import {alwaysPass} from '../helpers/ipc.js';
import {PARALLEL_COUNT} from '../helpers/parallel.js';
setFixtureDirectory();
test('subprocess.getOneMessage() can filter messages', async t => {
const subprocess = execa('ipc-send-twice.js', {ipc: true});
const message = await subprocess.getOneMessage({filter: message => message === foobarArray[1]});
t.is(message, foobarArray[1]);
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, foobarArray);
});
test('exports.getOneMessage() can filter messages', async t => {
const subprocess = execa('ipc-echo-filter.js', {ipc: true});
await subprocess.sendMessage(foobarArray[0]);
await subprocess.sendMessage(foobarArray[1]);
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, [foobarArray[1]]);
});
test('Throwing from subprocess.getOneMessage() filter disconnects', async t => {
const subprocess = execa('ipc-send-get.js', {ipc: true});
const error = new Error(foobarString);
t.is(await t.throwsAsync(subprocess.getOneMessage({
filter() {
throw error;
},
})), error);
const {exitCode, isTerminated, message, ipcOutput} = await t.throwsAsync(subprocess);
t.is(exitCode, 1);
t.false(isTerminated);
t.true(message.includes('Error: getOneMessage() could not complete'));
t.deepEqual(ipcOutput, [foobarString]);
});
test('Throwing from exports.getOneMessage() filter disconnects', async t => {
const subprocess = execa('ipc-get-filter-throw.js', {ipcInput: 0});
await t.throwsAsync(subprocess.getOneMessage(), {
message: /subprocess.getOneMessage\(\) could not complete/,
});
const {exitCode, isTerminated, message, ipcOutput} = await t.throwsAsync(subprocess);
t.is(exitCode, 1);
t.false(isTerminated);
t.true(message.includes(`Error: ${foobarString}`));
t.deepEqual(ipcOutput, []);
});
test.serial('Can retrieve initial IPC messages under heavy load', async t => {
await Promise.all(
Array.from({length: PARALLEL_COUNT}, async (_, index) => {
const subprocess = execa('ipc-send-argv.js', [`${index}`], {ipc: true, buffer: false});
t.is(await subprocess.getOneMessage(), `${index}`);
await subprocess;
}),
);
});
const testTwice = async (t, buffer, filter) => {
const subprocess = execa('ipc-send.js', {ipc: true, buffer});
t.deepEqual(
await Promise.all([subprocess.getOneMessage({filter}), subprocess.getOneMessage({filter})]),
[foobarString, foobarString],
);
await subprocess;
};
test('subprocess.getOneMessage() can be called twice at the same time, buffer false', testTwice, false, undefined);
test('subprocess.getOneMessage() can be called twice at the same time, buffer true', testTwice, true, undefined);
test('subprocess.getOneMessage() can be called twice at the same time, buffer false, filter', testTwice, false, alwaysPass);
test('subprocess.getOneMessage() can be called twice at the same time, buffer true, filter', testTwice, true, alwaysPass);
const testCleanupListeners = async (t, buffer, filter) => {
const subprocess = execa('ipc-send.js', {ipc: true, buffer});
t.is(subprocess.listenerCount('message'), 1);
t.is(subprocess.listenerCount('disconnect'), 1);
const promise = subprocess.getOneMessage({filter});
t.is(subprocess.listenerCount('message'), 1);
t.is(subprocess.listenerCount('disconnect'), 1);
t.is(await promise, foobarString);
await subprocess;
t.is(subprocess.listenerCount('message'), 0);
t.is(subprocess.listenerCount('disconnect'), 0);
};
test('Cleans up subprocess.getOneMessage() listeners, buffer false', testCleanupListeners, false, undefined);
test('Cleans up subprocess.getOneMessage() listeners, buffer true', testCleanupListeners, true, undefined);
test('Cleans up subprocess.getOneMessage() listeners, buffer false, filter', testCleanupListeners, false, alwaysPass);
test('Cleans up subprocess.getOneMessage() listeners, buffer true, filter', testCleanupListeners, true, alwaysPass);
const testParentDisconnect = async (t, buffer, filter) => {
const subprocess = execa('ipc-get-send-get.js', [`${filter}`], {ipc: true, buffer});
await subprocess.sendMessage(foobarString);
t.is(await subprocess.getOneMessage(), foobarString);
subprocess.disconnect();
const {exitCode, isTerminated, message} = await t.throwsAsync(subprocess);
t.is(exitCode, 1);
t.false(isTerminated);
if (buffer) {
t.true(message.includes('Error: getOneMessage() could not complete'));
}
};
test('subprocess.disconnect() interrupts exports.getOneMessage(), buffer false', testParentDisconnect, false, false);
test('subprocess.disconnect() interrupts exports.getOneMessage(), buffer true', testParentDisconnect, true, false);
test('subprocess.disconnect() interrupts exports.getOneMessage(), buffer false, filter', testParentDisconnect, false, true);
test('subprocess.disconnect() interrupts exports.getOneMessage(), buffer true, filter', testParentDisconnect, true, false);
const testSubprocessDisconnect = async (t, buffer, filter) => {
const subprocess = execa('empty.js', {ipc: true, buffer});
const {message} = await t.throwsAsync(subprocess.getOneMessage({filter}));
t.true(message.includes('subprocess.getOneMessage() could not complete'));
await subprocess;
};
test('Subprocess exit interrupts subprocess.getOneMessage(), buffer false', testSubprocessDisconnect, false, undefined);
test('Subprocess exit interrupts subprocess.getOneMessage(), buffer true', testSubprocessDisconnect, true, undefined);
test('Subprocess exit interrupts subprocess.getOneMessage(), buffer false, filter', testSubprocessDisconnect, false, alwaysPass);
test('Subprocess exit interrupts subprocess.getOneMessage(), buffer true, filter', testSubprocessDisconnect, true, alwaysPass);
================================================
FILE: test/ipc/graceful.js
================================================
import {getEventListeners} from 'node:events';
import {setTimeout} from 'node:timers/promises';
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
setFixtureDirectory();
test('Graceful cancelSignal can be already aborted', async t => {
const {isCanceled, isGracefullyCanceled, isTerminated, exitCode, ipcOutput} = await t.throwsAsync(execa('graceful-send.js', {cancelSignal: AbortSignal.abort(foobarString), gracefulCancel: true, forceKillAfterDelay: false}));
t.true(isCanceled);
t.true(isGracefullyCanceled);
t.false(isTerminated);
t.is(exitCode, 0);
t.deepEqual(ipcOutput, [foobarString]);
});
test('Graceful cancelSignal can be aborted', async t => {
const controller = new AbortController();
const subprocess = execa('graceful-send-twice.js', {cancelSignal: controller.signal, gracefulCancel: true, forceKillAfterDelay: false});
t.false(await subprocess.getOneMessage());
controller.abort(foobarString);
const {isCanceled, isGracefullyCanceled, isTerminated, exitCode, ipcOutput} = await t.throwsAsync(subprocess);
t.true(isCanceled);
t.true(isGracefullyCanceled);
t.false(isTerminated);
t.is(exitCode, 0);
t.deepEqual(ipcOutput, [false, foobarString]);
});
test('Graceful cancelSignal can be never aborted', async t => {
const controller = new AbortController();
const subprocess = execa('graceful-send-fast.js', {cancelSignal: controller.signal, gracefulCancel: true});
t.false(await subprocess.getOneMessage());
await subprocess;
});
test('Graceful cancelSignal can be already aborted but not used', async t => {
const subprocess = execa('ipc-send-get.js', {cancelSignal: AbortSignal.abort(foobarString), gracefulCancel: true, forceKillAfterDelay: false});
t.is(await subprocess.getOneMessage(), foobarString);
await setTimeout(1e3);
await subprocess.sendMessage('.');
const {isCanceled, isGracefullyCanceled, isTerminated, exitCode, ipcOutput} = await t.throwsAsync(subprocess);
t.true(isCanceled);
t.true(isGracefullyCanceled);
t.false(isTerminated);
t.is(exitCode, 0);
t.deepEqual(ipcOutput, [foobarString]);
});
test('Graceful cancelSignal can be aborted but not used', async t => {
const controller = new AbortController();
const subprocess = execa('ipc-send-get.js', {cancelSignal: controller.signal, gracefulCancel: true, forceKillAfterDelay: false});
t.is(await subprocess.getOneMessage(), foobarString);
controller.abort(foobarString);
await setTimeout(1e3);
await subprocess.sendMessage(foobarString);
const {isCanceled, isGracefullyCanceled, isTerminated, exitCode, ipcOutput} = await t.throwsAsync(subprocess);
t.true(isCanceled);
t.true(isGracefullyCanceled);
t.false(isTerminated);
t.is(exitCode, 0);
t.deepEqual(ipcOutput, [foobarString]);
});
test('Graceful cancelSignal can be never aborted nor used', async t => {
const controller = new AbortController();
const subprocess = execa('empty.js', {cancelSignal: controller.signal, gracefulCancel: true});
t.is(getEventListeners(controller.signal, 'abort').length, 1);
await subprocess;
t.is(getEventListeners(controller.signal, 'abort').length, 0);
});
test('Graceful cancelSignal can be aborted twice', async t => {
const controller = new AbortController();
const subprocess = execa('graceful-send-twice.js', {cancelSignal: controller.signal, gracefulCancel: true, forceKillAfterDelay: false});
t.false(await subprocess.getOneMessage());
controller.abort(foobarString);
controller.abort('.');
const {isCanceled, isGracefullyCanceled, isTerminated, exitCode, ipcOutput} = await t.throwsAsync(subprocess);
t.true(isCanceled);
t.true(isGracefullyCanceled);
t.false(isTerminated);
t.is(exitCode, 0);
t.deepEqual(ipcOutput, [false, foobarString]);
});
test('Graceful cancelSignal cannot be manually aborted after disconnection', async t => {
const controller = new AbortController();
const subprocess = execa('empty.js', {cancelSignal: controller.signal, gracefulCancel: true});
subprocess.disconnect();
controller.abort(foobarString);
const {isCanceled, isGracefullyCanceled, isTerminated, exitCode, ipcOutput, originalMessage} = await t.throwsAsync(subprocess);
t.false(isCanceled);
t.false(isGracefullyCanceled);
t.false(isTerminated);
t.is(exitCode, 0);
t.deepEqual(ipcOutput, []);
t.is(originalMessage, '`cancelSignal`\'s `controller.abort()` cannot be used: the subprocess has already exited or disconnected.');
});
test('Graceful cancelSignal can disconnect after being manually aborted', async t => {
const controller = new AbortController();
const subprocess = execa('graceful-disconnect.js', {cancelSignal: controller.signal, gracefulCancel: true});
controller.abort(foobarString);
t.is(await subprocess.getOneMessage(), foobarString);
subprocess.disconnect();
const {isCanceled, isGracefullyCanceled, isTerminated, exitCode, ipcOutput} = await t.throwsAsync(subprocess);
t.true(isCanceled);
t.true(isGracefullyCanceled);
t.false(isTerminated);
t.is(exitCode, 0);
t.deepEqual(ipcOutput, [foobarString]);
});
test('Graceful cancelSignal is automatically aborted on disconnection', async t => {
const controller = new AbortController();
const subprocess = execa('graceful-send-print.js', {cancelSignal: controller.signal, gracefulCancel: true});
t.false(await subprocess.getOneMessage());
subprocess.disconnect();
const {isCanceled, isGracefullyCanceled, ipcOutput, stdout} = await subprocess;
t.false(isCanceled);
t.false(isGracefullyCanceled);
t.deepEqual(ipcOutput, [false]);
t.true(stdout.includes('Error: `cancelSignal` aborted: the parent process disconnected.'));
});
test('getCancelSignal() aborts if already disconnected', async t => {
const controller = new AbortController();
const subprocess = execa('graceful-print.js', {cancelSignal: controller.signal, gracefulCancel: true});
subprocess.disconnect();
const {isCanceled, isGracefullyCanceled, ipcOutput, stdout} = await subprocess;
t.false(isCanceled);
t.false(isGracefullyCanceled);
t.deepEqual(ipcOutput, []);
t.true(stdout.includes('Error: `cancelSignal` aborted: the parent process disconnected.'));
});
test('getCancelSignal() fails if no IPC', async t => {
const {isCanceled, isGracefullyCanceled, isTerminated, exitCode, ipcOutput, stderr} = await t.throwsAsync(execa('graceful-none.js'));
t.false(isCanceled);
t.false(isGracefullyCanceled);
t.false(isTerminated);
t.is(exitCode, 1);
t.deepEqual(ipcOutput, []);
t.true(stderr.includes('Error: `getCancelSignal()` cannot be used without setting the `cancelSignal` subprocess option.'));
});
test.serial('getCancelSignal() hangs if cancelSignal without gracefulCancel', async t => {
const controller = new AbortController();
const {timedOut, isCanceled, isGracefullyCanceled, signal, ipcOutput} = await t.throwsAsync(execa('graceful-wait.js', {ipc: true, cancelSignal: controller.signal, timeout: 1e3}));
t.true(timedOut);
t.false(isCanceled);
t.false(isGracefullyCanceled);
t.is(signal, 'SIGTERM');
t.deepEqual(ipcOutput, []);
});
test('Subprocess cancelSignal does not keep subprocess alive', async t => {
const controller = new AbortController();
const {ipcOutput} = await execa('graceful-ref.js', {cancelSignal: controller.signal, gracefulCancel: true});
t.deepEqual(ipcOutput, []);
});
test('Subprocess can send a message right away', async t => {
const controller = new AbortController();
const {ipcOutput} = await execa('graceful-send-string.js', {cancelSignal: controller.signal, gracefulCancel: true});
t.deepEqual(ipcOutput, [foobarString]);
});
test('Subprocess can receive a message right away', async t => {
const controller = new AbortController();
const {ipcOutput} = await execa('graceful-echo.js', {cancelSignal: controller.signal, gracefulCancel: true, ipcInput: foobarString});
t.deepEqual(ipcOutput, [foobarString]);
});
test('getCancelSignal() can be called twice', async t => {
const {isCanceled, isGracefullyCanceled, isTerminated, exitCode, ipcOutput} = await t.throwsAsync(execa('graceful-twice.js', {cancelSignal: AbortSignal.abort(foobarString), gracefulCancel: true, forceKillAfterDelay: false}));
t.true(isCanceled);
t.true(isGracefullyCanceled);
t.false(isTerminated);
t.is(exitCode, 0);
t.deepEqual(ipcOutput, [foobarString]);
});
test('Graceful cancelSignal can use cancelSignal.onabort', async t => {
const controller = new AbortController();
const subprocess = execa('graceful-listener.js', {cancelSignal: controller.signal, gracefulCancel: true, forceKillAfterDelay: false});
t.is(await subprocess.getOneMessage(), '.');
controller.abort(foobarString);
const {isCanceled, isGracefullyCanceled, isTerminated, exitCode, ipcOutput} = await t.throwsAsync(subprocess);
t.true(isCanceled);
t.true(isGracefullyCanceled);
t.false(isTerminated);
t.is(exitCode, 0);
t.deepEqual(ipcOutput, ['.', foobarString]);
});
test('Graceful cancelSignal abort reason cannot be directly received', async t => {
const subprocess = execa('graceful-send-echo.js', {cancelSignal: AbortSignal.abort(foobarString), gracefulCancel: true, forceKillAfterDelay: false});
await setTimeout(0);
await subprocess.sendMessage('.');
const {isCanceled, isGracefullyCanceled, isTerminated, exitCode, ipcOutput} = await t.throwsAsync(subprocess);
t.true(isCanceled);
t.true(isGracefullyCanceled);
t.false(isTerminated);
t.is(exitCode, 0);
t.deepEqual(ipcOutput, ['.', foobarString]);
});
test('error.isGracefullyCanceled is always false with execaSync()', t => {
const {isCanceled, isGracefullyCanceled} = execaSync('empty.js');
t.false(isCanceled);
t.false(isGracefullyCanceled);
});
================================================
FILE: test/ipc/incoming.js
================================================
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
import {alwaysPass} from '../helpers/ipc.js';
import {PARALLEL_COUNT} from '../helpers/parallel.js';
setFixtureDirectory();
const testSeriesParent = async (t, buffer, filter) => {
const subprocess = execa('ipc-send-many.js', [`${PARALLEL_COUNT}`], {ipc: true, buffer});
for (let index = 0; index < PARALLEL_COUNT; index += 1) {
// eslint-disable-next-line no-await-in-loop
t.is(await subprocess.getOneMessage({filter}), index);
}
const {ipcOutput} = await subprocess;
if (buffer) {
t.deepEqual(ipcOutput, Array.from({length: PARALLEL_COUNT}, (_, index) => index));
}
};
test('subprocess.getOneMessage() can be called multiple times in a row, buffer false', testSeriesParent, false, undefined);
test('subprocess.getOneMessage() can be called multiple times in a row, buffer true', testSeriesParent, true, undefined);
test('subprocess.getOneMessage() can be called multiple times in a row, buffer false, filter', testSeriesParent, false, alwaysPass);
test('subprocess.getOneMessage() can be called multiple times in a row, buffer true, filter', testSeriesParent, true, alwaysPass);
const testSeriesSubprocess = async (t, filter) => {
const subprocess = execa('ipc-print-many.js', [`${PARALLEL_COUNT}`, `${filter}`], {ipc: true});
const indexes = Array.from({length: PARALLEL_COUNT}, (_, index) => `${index}`);
await Promise.all(indexes.map(index => subprocess.sendMessage(index)));
const {stdout} = await subprocess;
const expectedOutput = indexes.join('\n');
t.is(stdout, expectedOutput);
};
test('exports.getOneMessage() can be called multiple times in a row', testSeriesSubprocess, false);
test('exports.getOneMessage() can be called multiple times in a row, filter', testSeriesSubprocess, true);
test('Can iterate multiple times over IPC messages in subprocess', async t => {
const subprocess = execa('ipc-iterate-twice.js', {ipc: true});
t.is(await subprocess.getOneMessage(), foobarString);
await subprocess.sendMessage('.');
t.is(await subprocess.getOneMessage(), '0.');
await subprocess.sendMessage(foobarString);
t.is(await subprocess.getOneMessage(), foobarString);
await subprocess.sendMessage('.');
t.is(await subprocess.getOneMessage(), '1.');
await subprocess.sendMessage(foobarString);
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, [foobarString, '0.', foobarString, '1.']);
});
================================================
FILE: test/ipc/ipc-input.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
setFixtureDirectory();
const testSuccess = async (t, options) => {
const {ipcOutput} = await execa('ipc-echo.js', {ipcInput: foobarString, ...options});
t.deepEqual(ipcOutput, [foobarString]);
};
test('Sends a message with the "ipcInput" option, ipc undefined', testSuccess, {});
test('Sends a message with the "ipcInput" option, ipc true', testSuccess, {ipc: true});
test('Cannot use the "ipcInput" option with "ipc" false', t => {
t.throws(() => {
execa('empty.js', {ipcInput: foobarString, ipc: false});
}, {message: /unless the `ipc` option is `true`/});
});
test('Cannot use the "ipcInput" option with execaSync()', t => {
t.throws(() => {
execaSync('empty.js', {ipcInput: foobarString});
}, {message: /The "ipcInput" option cannot be used with synchronous/});
});
test('Invalid "ipcInput" option v8 format', t => {
const {message, cause} = t.throws(() => {
execa('empty.js', {ipcInput() {}});
});
t.is(message, 'The `ipcInput` option is not serializable with a structured clone.');
t.is(cause.message, 'ipcInput() {} could not be cloned.');
});
test('Invalid "ipcInput" option JSON format', t => {
const {message, cause} = t.throws(() => {
execa('empty.js', {ipcInput: 0n, serialization: 'json'});
});
t.is(message, 'The `ipcInput` option is not serializable with JSON.');
t.is(cause.message, 'Do not know how to serialize a BigInt');
});
test('Handles "ipcInput" option during sending', async t => {
const {message, cause} = await t.throwsAsync(execa('empty.js', {ipcInput: 0n}));
t.true(message.includes('subprocess.sendMessage()\'s argument type is invalid: the message cannot be serialized: 0.'));
t.true(cause.cause.message.includes('The "message" argument must be one of type string'));
});
test.serial('Can use "ipcInput" option even if the subprocess is not listening to messages', async t => {
const {ipcOutput} = await execa('empty.js', {ipcInput: foobarString});
t.deepEqual(ipcOutput, []);
});
================================================
FILE: test/ipc/outgoing.js
================================================
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
import {alwaysPass, subprocessGetOne, subprocessGetFirst} from '../helpers/ipc.js';
import {PARALLEL_COUNT} from '../helpers/parallel.js';
setFixtureDirectory();
const testSendHoldParent = async (t, getMessage, buffer, filter) => {
const subprocess = execa('ipc-iterate.js', {ipc: true, buffer});
await subprocess.sendMessage(0);
t.is(await subprocess.getOneMessage({filter}), 0);
const messages = Array.from({length: PARALLEL_COUNT}, (_, index) => index + 1);
await Promise.all([
...messages.map(message => subprocess.sendMessage(message)),
subprocess.sendMessage(foobarString),
subprocess.emit('message', '.'),
]);
t.is(await getMessage(subprocess, {filter}), '.');
const {ipcOutput} = await subprocess;
if (buffer) {
const expectedOutput = [0, '.', ...messages];
t.deepEqual(ipcOutput, expectedOutput);
}
};
test('Multiple parallel subprocess.sendMessage() + subprocess.getOneMessage(), buffer false', testSendHoldParent, subprocessGetOne, false, undefined);
test('Multiple parallel subprocess.sendMessage() + subprocess.getOneMessage(), buffer true', testSendHoldParent, subprocessGetOne, true, undefined);
test('Multiple parallel subprocess.sendMessage() + subprocess.getOneMessage(), buffer false, filter', testSendHoldParent, subprocessGetOne, false, alwaysPass);
test('Multiple parallel subprocess.sendMessage() + subprocess.getOneMessage(), buffer true, filter', testSendHoldParent, subprocessGetOne, true, alwaysPass);
test('Multiple parallel subprocess.sendMessage() + subprocess.getEachMessage(), buffer false', testSendHoldParent, subprocessGetFirst, false, undefined);
test('Multiple parallel subprocess.sendMessage() + subprocess.getEachMessage(), buffer true', testSendHoldParent, subprocessGetFirst, true, undefined);
const testSendHoldSubprocess = async (t, filter, isGetEach) => {
const {ipcOutput} = await execa('ipc-iterate-back.js', [`${filter}`, `${isGetEach}`], {ipcInput: 0});
const expectedOutput = [...Array.from({length: PARALLEL_COUNT + 1}, (_, index) => index), '.'];
t.deepEqual(ipcOutput, expectedOutput);
};
test('Multiple parallel exports.sendMessage() + exports.getOneMessage()', testSendHoldSubprocess, false, false);
test('Multiple parallel exports.sendMessage() + exports.getOneMessage(), filter', testSendHoldSubprocess, true, false);
test('Multiple parallel exports.sendMessage() + exports.getEachMessage()', testSendHoldSubprocess, false, true);
const testSendHoldParentSerial = async (t, getMessage, buffer, filter) => {
const subprocess = execa('ipc-iterate.js', {ipc: true, buffer});
await subprocess.sendMessage(0);
t.is(await subprocess.getOneMessage({filter}), 0);
const promise = subprocess.sendMessage(1);
subprocess.emit('message', '.');
await promise;
const messages = Array.from({length: PARALLEL_COUNT}, (_, index) => index + 2);
for (const message of messages) {
// eslint-disable-next-line no-await-in-loop
await subprocess.sendMessage(message);
}
await subprocess.sendMessage(foobarString);
const {ipcOutput} = await subprocess;
if (buffer) {
const expectedOutput = [0, '.', 1, ...messages];
t.deepEqual(ipcOutput, expectedOutput);
}
};
test('Multiple serial subprocess.sendMessage() + subprocess.getOneMessage(), buffer false', testSendHoldParentSerial, subprocessGetOne, false, undefined);
test('Multiple serial subprocess.sendMessage() + subprocess.getOneMessage(), buffer true', testSendHoldParentSerial, subprocessGetOne, true, undefined);
test('Multiple serial subprocess.sendMessage() + subprocess.getOneMessage(), buffer false, filter', testSendHoldParentSerial, subprocessGetOne, false, alwaysPass);
test('Multiple serial subprocess.sendMessage() + subprocess.getOneMessage(), buffer true, filter', testSendHoldParentSerial, subprocessGetOne, true, alwaysPass);
test('Multiple serial subprocess.sendMessage() + subprocess.getEachMessage(), buffer false', testSendHoldParentSerial, subprocessGetFirst, false, undefined);
test('Multiple serial subprocess.sendMessage() + subprocess.getEachMessage(), buffer true', testSendHoldParentSerial, subprocessGetFirst, true, undefined);
const testSendHoldSubprocessSerial = async (t, filter, isGetEach) => {
const {ipcOutput} = await execa('ipc-iterate-back-serial.js', [`${filter}`, `${isGetEach}`], {ipcInput: 0});
const expectedOutput = [...Array.from({length: PARALLEL_COUNT + 2}, (_, index) => index), '.'];
t.deepEqual(ipcOutput, expectedOutput);
};
test('Multiple serial exports.sendMessage() + exports.getOneMessage()', testSendHoldSubprocessSerial, false, false);
test('Multiple serial exports.sendMessage() + exports.getOneMessage(), filter', testSendHoldSubprocessSerial, true, false);
test('Multiple serial exports.sendMessage() + exports.getEachMessage()', testSendHoldSubprocessSerial, false, true);
================================================
FILE: test/ipc/pending.js
================================================
import {once} from 'node:events';
import {setTimeout} from 'node:timers/promises';
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
setFixtureDirectory();
const testBufferInitial = async (t, buffer) => {
const subprocess = execa('ipc-echo-wait.js', {buffer, ipcInput: foobarString});
t.is(await subprocess.getOneMessage(), foobarString);
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, buffer ? [foobarString] : []);
};
test('Buffers initial message to subprocess, buffer false', testBufferInitial, false);
test('Buffers initial message to subprocess, buffer true', testBufferInitial, true);
const testBufferInitialSend = async (t, buffer) => {
const subprocess = execa('ipc-send-echo-wait.js', {buffer, ipcInput: foobarString});
t.is(await subprocess.getOneMessage(), '.');
t.is(await subprocess.getOneMessage(), foobarString);
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, buffer ? ['.', foobarString] : []);
};
test('sendMessage() does not empty the initial message buffering, buffer false', testBufferInitialSend, false);
test('sendMessage() does not empty the initial message buffering, buffer true', testBufferInitialSend, true);
const testBufferInitialStrict = async (t, buffer) => {
const subprocess = execa('ipc-send-echo-strict.js', {buffer, ipcInput: foobarString});
t.is(await subprocess.getOneMessage(), '.');
await setTimeout(1e3);
const promise = subprocess.getOneMessage();
await subprocess.sendMessage('..');
t.is(await promise, '..');
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, buffer ? ['.', '..'] : []);
};
test('sendMessage() with "strict" empties the initial message buffering, buffer false', testBufferInitialStrict, false);
test('sendMessage() with "strict" empties the initial message buffering, buffer true', testBufferInitialStrict, true);
const testNoBufferInitial = async (t, buffer) => {
const subprocess = execa('ipc-send-print.js', {ipc: true, buffer});
const [chunk] = await once(subprocess.stdout, 'data');
t.is(chunk.toString(), '.');
await setTimeout(1e3);
t.is(await Promise.race([setTimeout(0), subprocess.getOneMessage()]), undefined);
await subprocess.sendMessage('.');
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, buffer ? [foobarString] : []);
};
test.serial('Does not buffer initial message to current process, buffer false', testNoBufferInitial, false);
test.serial('Does not buffer initial message to current process, buffer true', testNoBufferInitial, true);
const testReplay = async (t, buffer) => {
const subprocess = execa('ipc-replay.js', {buffer, ipcInput: foobarString});
t.is(await subprocess.getOneMessage(), foobarString);
await subprocess.sendMessage('.');
await setTimeout(2e3);
await subprocess.sendMessage(foobarString);
t.is(await subprocess.getOneMessage(), foobarString);
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, buffer ? [foobarString, foobarString] : []);
};
test.serial('Does not replay missed messages in subprocess, buffer false', testReplay, false);
test.serial('Does not replay missed messages in subprocess, buffer true', testReplay, true);
const testFastSend = async (t, buffer) => {
const subprocess = execa('ipc-send-native.js', {ipc: true, buffer});
t.is(await subprocess.getOneMessage(), '.');
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, buffer ? ['.'] : []);
};
test('Subprocess can send messages right away, buffer false', testFastSend, false);
test('Subprocess can send messages right away, buffer true', testFastSend, true);
================================================
FILE: test/ipc/reference.js
================================================
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
import {PARALLEL_COUNT} from '../helpers/parallel.js';
setFixtureDirectory();
const testReference = async (t, fixtureName) => {
const {timedOut} = await t.throwsAsync(execa(fixtureName, {ipc: true, timeout: 1e3}));
t.true(timedOut);
};
test('exports.getOneMessage() keeps the subprocess alive', testReference, 'ipc-get-ref.js');
test('exports.getEachMessage() keeps the subprocess alive', testReference, 'ipc-iterate-ref.js');
const testUnreference = async (t, fixtureName) => {
const {ipcOutput} = await execa(fixtureName, {ipc: true});
t.deepEqual(ipcOutput, []);
};
test('exports.getOneMessage() does not keep the subprocess alive, reference false', testUnreference, 'ipc-get-unref.js');
test('exports.getEachMessage() does not keep the subprocess alive, reference false', testUnreference, 'ipc-iterate-unref.js');
test('exports.sendMessage() keeps the subprocess alive', async t => {
const {ipcOutput} = await execa('ipc-send-repeat.js', [`${PARALLEL_COUNT}`], {ipc: true});
const expectedOutput = Array.from({length: PARALLEL_COUNT}, (_, index) => index);
t.deepEqual(ipcOutput, expectedOutput);
});
test('process.send() keeps the subprocess alive', async t => {
const {ipcOutput, stdout} = await execa('ipc-process-send.js', {ipc: true});
t.deepEqual(ipcOutput, [foobarString]);
t.is(stdout, '.');
});
test('process.send() keeps the subprocess alive, after getOneMessage()', async t => {
const {ipcOutput, stdout} = await execa('ipc-process-send-get.js', {ipcInput: 0});
t.deepEqual(ipcOutput, [foobarString]);
t.is(stdout, '.');
});
test('process.send() keeps the subprocess alive, after sendMessage()', async t => {
const {ipcOutput, stdout} = await execa('ipc-process-send-send.js', {ipc: true});
t.deepEqual(ipcOutput, ['.', foobarString]);
t.is(stdout, '.');
});
test('process.once("message") keeps the subprocess alive', async t => {
const subprocess = execa('ipc-once-message.js', {ipc: true});
t.is(await subprocess.getOneMessage(), '.');
await subprocess.sendMessage(foobarString);
const {ipcOutput, stdout} = await subprocess;
t.deepEqual(ipcOutput, ['.']);
t.is(stdout, foobarString);
});
test('process.once("message") keeps the subprocess alive, after sendMessage()', async t => {
const subprocess = execa('ipc-once-message-send.js', {ipc: true});
t.is(await subprocess.getOneMessage(), '.');
await subprocess.sendMessage(foobarString);
const {ipcOutput, stdout} = await subprocess;
t.deepEqual(ipcOutput, ['.']);
t.is(stdout, foobarString);
});
test('process.once("message") keeps the subprocess alive, after getOneMessage()', async t => {
const subprocess = execa('ipc-once-message-get.js', {ipc: true});
await subprocess.sendMessage('.');
t.is(await subprocess.getOneMessage(), '.');
await subprocess.sendMessage(foobarString);
const {ipcOutput, stdout} = await subprocess;
t.deepEqual(ipcOutput, ['.']);
t.is(stdout, foobarString);
});
test('process.once("disconnect") keeps the subprocess alive', async t => {
const subprocess = execa('ipc-once-disconnect.js', {ipc: true});
t.is(await subprocess.getOneMessage(), '.');
subprocess.disconnect();
const {ipcOutput, stdout} = await subprocess;
t.deepEqual(ipcOutput, ['.']);
t.is(stdout, '.');
});
test('process.once("disconnect") keeps the subprocess alive, after sendMessage()', async t => {
const subprocess = execa('ipc-once-disconnect-send.js', {ipc: true});
t.is(await subprocess.getOneMessage(), '.');
t.is(await subprocess.getOneMessage(), '.');
subprocess.disconnect();
const {ipcOutput, stdout} = await subprocess;
t.deepEqual(ipcOutput, ['.', '.']);
t.is(stdout, '.');
});
test('process.once("disconnect") does not keep the subprocess alive, after getOneMessage()', async t => {
const subprocess = execa('ipc-once-disconnect-get.js', {ipc: true});
await subprocess.sendMessage('.');
t.is(await subprocess.getOneMessage(), '.');
subprocess.disconnect();
const {ipcOutput, stdout} = await subprocess;
t.deepEqual(ipcOutput, ['.']);
t.is(stdout, '.');
});
test('Can call subprocess.disconnect() right away', async t => {
const subprocess = execa('ipc-send.js', {ipc: true});
subprocess.disconnect();
t.is(subprocess.channel, null);
await t.throwsAsync(subprocess.getOneMessage(), {
message: /subprocess.getOneMessage\(\) could not complete/,
});
await t.throwsAsync(subprocess, {
message: /Error: sendMessage\(\) cannot be used/,
});
});
test('Can call process.disconnect() right away', async t => {
const {stdout, stderr} = await t.throwsAsync(execa('ipc-disconnect-get.js', {ipc: true}));
t.is(stdout, 'null');
t.true(stderr.includes('Error: getOneMessage() cannot be used'));
});
================================================
FILE: test/ipc/send.js
================================================
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
import {PARALLEL_COUNT} from '../helpers/parallel.js';
import {mockSendIoError} from '../helpers/ipc.js';
setFixtureDirectory();
test('Can exchange IPC messages', async t => {
const subprocess = execa('ipc-echo.js', {ipc: true});
await subprocess.sendMessage(foobarString);
t.is(await subprocess.getOneMessage(), foobarString);
await subprocess;
});
test.serial('Can exchange IPC messages under heavy load', async t => {
await Promise.all(
Array.from({length: PARALLEL_COUNT}, async (_, index) => {
const subprocess = execa('ipc-echo.js', {ipc: true});
await subprocess.sendMessage(index);
t.is(await subprocess.getOneMessage(), index);
await subprocess;
}),
);
});
test('The "serialization" option defaults to "advanced"', async t => {
const subprocess = execa('ipc-echo.js', {ipc: true});
await subprocess.sendMessage([0n]);
const message = await subprocess.getOneMessage();
t.is(message[0], 0n);
await subprocess;
});
test('Can use "serialization: json" option', async t => {
const subprocess = execa('ipc-echo.js', {ipc: true, serialization: 'json'});
const date = new Date();
await subprocess.sendMessage(date);
t.is(await subprocess.getOneMessage(), date.toJSON());
await subprocess;
});
test('Validates JSON payload with serialization: "json"', async t => {
const subprocess = execa('ipc-echo.js', {ipc: true, serialization: 'json'});
await t.throwsAsync(subprocess.sendMessage([0n]), {message: /serialize a BigInt/});
await t.throwsAsync(subprocess);
});
const BIG_PAYLOAD_SIZE = '.'.repeat(1e6);
test('Handles backpressure', async t => {
const subprocess = execa('ipc-iterate.js', {ipc: true});
await subprocess.sendMessage(BIG_PAYLOAD_SIZE);
t.true(subprocess.send(foobarString));
t.is(await subprocess.getOneMessage(), BIG_PAYLOAD_SIZE);
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, [BIG_PAYLOAD_SIZE]);
});
test('Disconnects IPC on exports.sendMessage() error', async t => {
const subprocess = execa('ipc-get-send-get.js', ['false'], {ipc: true});
await subprocess.sendMessage(foobarString);
t.is(await subprocess.getOneMessage(), foobarString);
const {message, cause} = await t.throwsAsync(subprocess.sendMessage(0n));
t.is(message, 'subprocess.sendMessage()\'s argument type is invalid: the message cannot be serialized: 0.');
t.true(cause.message.includes('The "message" argument must be one of type string'));
const {exitCode, isTerminated, stderr} = await t.throwsAsync(subprocess);
t.is(exitCode, 1);
t.false(isTerminated);
t.true(stderr.includes('Error: getOneMessage() could not complete'));
});
test('Disconnects IPC on subprocess.sendMessage() error', async t => {
const subprocess = execa('ipc-send-error.js', {ipc: true});
const ipcError = await t.throwsAsync(subprocess.getOneMessage());
t.true(ipcError.message.includes('subprocess.getOneMessage() could not complete'));
const {exitCode, isTerminated, stderr} = await t.throwsAsync(subprocess);
t.is(exitCode, 1);
t.false(isTerminated);
t.true(stderr.includes('Error: sendMessage()\'s argument type is invalid: the message cannot be serialized: 0.'));
t.true(stderr.includes('The "message" argument must be one of type string'));
});
// EPIPE happens based on timing conditions, so we must repeat it until it happens
const findEpipeError = async t => {
// eslint-disable-next-line no-constant-condition
while (true) {
// eslint-disable-next-line no-await-in-loop
const error = await t.throwsAsync(getEpipeError());
if (error.cause?.code === 'EPIPE') {
return error;
}
}
};
const getEpipeError = async () => {
const subprocess = execa('delay.js', ['0'], {ipc: true});
// eslint-disable-next-line no-constant-condition
while (true) {
// eslint-disable-next-line no-await-in-loop
await subprocess.sendMessage('.');
}
};
test.serial('Can send messages while the subprocess is closing', async t => {
const {message} = await findEpipeError(t);
t.is(message, 'subprocess.sendMessage() cannot be used: the subprocess is disconnecting.');
});
test('subprocess.sendMessage() handles I/O errors', async t => {
const subprocess = execa('ipc-echo.js', {ipc: true});
const error = mockSendIoError(subprocess);
t.is(await t.throwsAsync(subprocess.sendMessage('.')), error);
const {exitCode, isTerminated, message, ipcOutput} = await t.throwsAsync(subprocess);
t.is(exitCode, 1);
t.false(isTerminated);
t.true(message.includes('Error: getOneMessage()'));
t.deepEqual(ipcOutput, []);
});
test('Does not hold message events on I/O errors', async t => {
const subprocess = execa('ipc-echo.js', {ipc: true});
const error = mockSendIoError(subprocess);
const promise = subprocess.sendMessage('.');
subprocess.emit('message', '.');
t.is(await t.throwsAsync(promise), error);
const {exitCode, isTerminated, message, ipcOutput} = await t.throwsAsync(subprocess);
t.is(exitCode, 1);
t.false(isTerminated);
t.true(message.includes('Error: getOneMessage()'));
t.deepEqual(ipcOutput, ['.']);
});
test('exports.sendMessage() handles I/O errors', async t => {
const {exitCode, isTerminated, message, ipcOutput} = await t.throwsAsync(execa('ipc-send-io-error.js', {ipc: true}));
t.is(exitCode, 1);
t.false(isTerminated);
t.true(message.includes(`Error: ${foobarString}`));
t.deepEqual(ipcOutput, []);
});
================================================
FILE: test/ipc/strict.js
================================================
import {once} from 'node:events';
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
import {assertMaxListeners} from '../helpers/listeners.js';
import {subprocessGetOne, subprocessGetFirst, mockSendIoError} from '../helpers/ipc.js';
import {PARALLEL_COUNT} from '../helpers/parallel.js';
setFixtureDirectory();
const testStrictSuccessParentOne = async (t, buffer) => {
const subprocess = execa('ipc-echo.js', {ipc: true, buffer});
await subprocess.sendMessage(foobarString, {strict: true});
t.is(await subprocess.getOneMessage(), foobarString);
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, buffer ? [foobarString] : []);
};
test('subprocess.sendMessage() "strict" succeeds if the subprocess uses exports.getOneMessage(), buffer false', testStrictSuccessParentOne, false);
test('subprocess.sendMessage() "strict" succeeds if the subprocess uses exports.getOneMessage(), buffer true', testStrictSuccessParentOne, true);
const testStrictSuccessParentEach = async (t, buffer) => {
const subprocess = execa('ipc-iterate.js', {ipc: true, buffer});
await subprocess.sendMessage('.', {strict: true});
t.is(await subprocess.getOneMessage(), '.');
await subprocess.sendMessage(foobarString);
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, buffer ? ['.'] : []);
};
test('subprocess.sendMessage() "strict" succeeds if the subprocess uses exports.getEachMessage(), buffer false', testStrictSuccessParentEach, false);
test('subprocess.sendMessage() "strict" succeeds if the subprocess uses exports.getEachMessage(), buffer true', testStrictSuccessParentEach, true);
const testStrictMissingParent = async (t, buffer) => {
const subprocess = execa('ipc-echo-twice.js', {ipcInput: foobarString, buffer});
const promise = subprocess.getOneMessage();
const secondPromise = subprocess.sendMessage(foobarString, {strict: true});
const {message} = await t.throwsAsync(subprocess.sendMessage(foobarString, {strict: true}));
t.is(message, 'subprocess.sendMessage() failed: the subprocess is not listening to incoming messages.');
t.is(await promise, foobarString);
await secondPromise;
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, buffer ? [foobarString, foobarString] : []);
};
test('subprocess.sendMessage() "strict" fails if the subprocess is not listening, buffer false', testStrictMissingParent, false);
test('subprocess.sendMessage() "strict" fails if the subprocess is not listening, buffer true', testStrictMissingParent, true);
const testStrictExit = async (t, buffer) => {
const subprocess = execa('ipc-send.js', {ipc: true, buffer});
const {message} = await t.throwsAsync(subprocess.sendMessage(foobarString, {strict: true}));
t.is(message, 'subprocess.sendMessage() failed: the subprocess exited without listening to incoming messages.');
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, buffer ? [foobarString] : []);
};
test('subprocess.sendMessage() "strict" fails if the subprocess exits, buffer false', testStrictExit, false);
test('subprocess.sendMessage() "strict" fails if the subprocess exits, buffer true', testStrictExit, true);
const testStrictSuccessSubprocess = async (t, getMessage, buffer) => {
const subprocess = execa('ipc-send-strict.js', {ipc: true, buffer});
t.is(await getMessage(subprocess), foobarString);
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, buffer ? [foobarString] : []);
};
test('exports.sendMessage() "strict" succeeds if the current process uses subprocess.getOneMessage(), buffer false', testStrictSuccessSubprocess, subprocessGetOne, false);
test('exports.sendMessage() "strict" succeeds if the current process uses subprocess.getOneMessage(), buffer true', testStrictSuccessSubprocess, subprocessGetOne, true);
test('exports.sendMessage() "strict" succeeds if the current process uses subprocess.getEachMessage(), buffer false', testStrictSuccessSubprocess, subprocessGetFirst, false);
test('exports.sendMessage() "strict" succeeds if the current process uses subprocess.getEachMessage(), buffer true', testStrictSuccessSubprocess, subprocessGetFirst, true);
test('exports.sendMessage() "strict" succeeds if the current process uses result.ipcOutput', async t => {
const {ipcOutput} = await execa('ipc-send-strict.js', {ipc: true});
t.deepEqual(ipcOutput, [foobarString]);
});
test('exports.sendMessage() "strict" fails if the current process is not listening, buffer false', async t => {
const {exitCode, isTerminated, stderr, ipcOutput} = await t.throwsAsync(execa('ipc-send-strict.js', {ipc: true, buffer: {ipc: false}}));
t.is(exitCode, 1);
t.false(isTerminated);
t.true(stderr.includes('Error: sendMessage() failed: the parent process is not listening to incoming messages.'));
t.deepEqual(ipcOutput, []);
});
test.serial('Multiple subprocess.sendMessage() "strict" at once', async t => {
const checkMaxListeners = assertMaxListeners(t);
const subprocess = execa('ipc-iterate.js', {ipc: true});
const messages = Array.from({length: PARALLEL_COUNT}, (_, index) => index);
await Promise.all(messages.map(message => subprocess.sendMessage(message, {strict: true})));
await subprocess.sendMessage(foobarString);
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, messages);
checkMaxListeners();
});
test('subprocess.sendMessage() "strict" fails if the subprocess uses once()', async t => {
const subprocess = execa('ipc-once-message.js', {ipc: true});
const {message} = await t.throwsAsync(subprocess.sendMessage(foobarString, {strict: true}));
t.is(message, 'subprocess.sendMessage() failed: the subprocess exited without listening to incoming messages.');
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, ['.']);
});
test('exports.sendMessage() "strict" fails if the current process uses once() and buffer false', async t => {
const subprocess = execa('ipc-send-strict.js', {ipc: true, buffer: {ipc: false}});
const [message] = await once(subprocess, 'message');
t.deepEqual(message, {
id: 0n,
type: 'execa:ipc:request',
message: foobarString,
hasListeners: false,
});
const {exitCode, isTerminated, stderr, ipcOutput} = await t.throwsAsync(subprocess);
t.is(exitCode, 1);
t.false(isTerminated);
t.true(stderr.includes('Error: sendMessage() failed: the parent process is not listening to incoming messages.'));
t.deepEqual(ipcOutput, []);
});
test('subprocess.sendMessage() "strict" failure disconnects', async t => {
const subprocess = execa('ipc-echo-twice-wait.js', {ipcInput: foobarString});
const promise = subprocess.getOneMessage();
const secondPromise = subprocess.sendMessage(foobarString, {strict: true});
const {message} = await t.throwsAsync(subprocess.sendMessage(foobarString, {strict: true}));
t.is(message, 'subprocess.sendMessage() failed: the subprocess is not listening to incoming messages.');
t.is(await promise, foobarString);
await secondPromise;
const {exitCode, isTerminated, stderr, ipcOutput} = await t.throwsAsync(subprocess);
t.is(exitCode, 1);
t.false(isTerminated);
t.true(stderr.includes('Error: sendMessage() cannot be used: the parent process has already exited or disconnected.'));
t.deepEqual(ipcOutput, [foobarString, foobarString]);
});
test('exports.sendMessage() "strict" failure disconnects', async t => {
const {exitCode, isTerminated, stderr, ipcOutput} = await t.throwsAsync(execa('ipc-send-strict-catch.js', {ipc: true, buffer: {ipc: false}}));
t.is(exitCode, 1);
t.false(isTerminated);
t.true(stderr.includes('Error: sendMessage() cannot be used: the parent process has already exited or disconnected.'));
t.deepEqual(ipcOutput, []);
});
const testIoErrorParent = async (t, getMessage) => {
const subprocess = execa('ipc-send-strict.js', {ipc: true});
const cause = mockSendIoError(subprocess);
const error = await t.throwsAsync(getMessage(subprocess));
t.true(error.message.includes('subprocess.sendMessage() failed when sending an acknowledgment response to the subprocess.'));
t.is(getMessage === subprocessGetOne ? error.cause : error.cause.cause, cause);
const {exitCode, isTerminated, stderr, ipcOutput} = await t.throwsAsync(subprocess);
t.is(exitCode, 1);
t.false(isTerminated);
t.true(stderr.includes('Error: sendMessage() failed: the parent process exited without listening to incoming messages.'));
t.deepEqual(ipcOutput, []);
};
test('subprocess.getOneMessage() acknowledgment I/O error', testIoErrorParent, subprocessGetOne);
test('subprocess.getEachMessage() acknowledgment I/O error', testIoErrorParent, subprocessGetFirst);
const testIoErrorSubprocess = async (t, fixtureName) => {
const subprocess = execa(fixtureName, {ipc: true});
const {message} = await t.throwsAsync(subprocess.sendMessage(foobarString, {strict: true}));
t.is(message, 'subprocess.sendMessage() failed: the subprocess exited without listening to incoming messages.');
const {exitCode, isTerminated, stdout, stderr, ipcOutput} = await t.throwsAsync(subprocess);
t.is(exitCode, 1);
t.false(isTerminated);
t.is(stdout, '');
t.true(stderr.includes('Error: sendMessage() failed when sending an acknowledgment response to the parent process.'));
t.true(stderr.includes(`Error: ${foobarString}`));
t.deepEqual(ipcOutput, []);
};
test('exports.getOneMessage() acknowledgment I/O error', testIoErrorSubprocess, 'ipc-get-io-error.js');
test('exports.getEachMessage() acknowledgment I/O error', testIoErrorSubprocess, 'ipc-iterate-io-error.js');
test('Opposite sendMessage() "strict", buffer true', async t => {
const subprocess = execa('ipc-send-strict-get.js', {ipc: true});
await subprocess.sendMessage(foobarString, {strict: true});
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, [foobarString, foobarString]);
});
test('Opposite sendMessage() "strict", current process listening, buffer false', async t => {
const subprocess = execa('ipc-send-strict-get.js', {ipc: true, buffer: {ipc: false}});
const [message] = await Promise.all([
subprocess.getOneMessage(),
subprocess.sendMessage(foobarString, {strict: true}),
]);
t.is(message, foobarString);
t.is(await subprocess.getOneMessage(), foobarString);
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, []);
});
test('Opposite sendMessage() "strict", subprocess listening, buffer false', async t => {
const subprocess = execa('ipc-send-strict-listen.js', {ipc: true, buffer: {ipc: false}});
await subprocess.sendMessage(foobarString, {strict: true});
t.is(await subprocess.getOneMessage(), foobarString);
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, []);
});
test('Opposite sendMessage() "strict", not listening, buffer false', async t => {
const subprocess = execa('ipc-send-strict.js', {ipc: true, buffer: {ipc: false}});
const {message} = await t.throwsAsync(subprocess.sendMessage(foobarString, {strict: true}));
t.true(message.startsWith('subprocess.sendMessage() failed: the subprocess is sending a message too, instead of listening to incoming messages.'));
const {exitCode, isTerminated, stderr, ipcOutput} = await t.throwsAsync(subprocess);
t.is(exitCode, 1);
t.false(isTerminated);
t.true(stderr.includes('Error: sendMessage() failed: the parent process is sending a message too, instead of listening to incoming messages.'));
t.deepEqual(ipcOutput, []);
});
================================================
FILE: test/ipc/validation.js
================================================
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
import {getStdio} from '../helpers/stdio.js';
setFixtureDirectory();
const stdioIpc = getStdio(3, 'ipc');
const testRequiredIpcSubprocess = async (t, methodName, options) => {
const subprocess = execa('empty.js', options);
const {message} = await t.throws(() => subprocess[methodName]());
t.true(message.includes(`subprocess.${methodName}() can only be used`));
await subprocess;
};
test('Cannot use subprocess.sendMessage() without ipc option', testRequiredIpcSubprocess, 'sendMessage', {});
test('Cannot use subprocess.sendMessage() with ipc: false', testRequiredIpcSubprocess, 'sendMessage', {ipc: false});
test('Cannot use subprocess.sendMessage() with stdio: [..., "ipc"]', testRequiredIpcSubprocess, 'sendMessage', stdioIpc);
test('Cannot use subprocess.getOneMessage() without ipc option', testRequiredIpcSubprocess, 'getOneMessage', {});
test('Cannot use subprocess.getOneMessage() with ipc: false', testRequiredIpcSubprocess, 'getOneMessage', {ipc: false});
test('Cannot use subprocess.getOneMessage() with stdio: [..., "ipc"]', testRequiredIpcSubprocess, 'getOneMessage', stdioIpc);
test('Cannot use subprocess.getEachMessage() without ipc option', testRequiredIpcSubprocess, 'getEachMessage', {});
test('Cannot use subprocess.getEachMessage() with ipc: false', testRequiredIpcSubprocess, 'getEachMessage', {ipc: false});
test('Cannot use subprocess.getEachMessage() with stdio: [..., "ipc"]', testRequiredIpcSubprocess, 'getEachMessage', stdioIpc);
const testRequiredIpcExports = async (t, methodName, options) => {
const {message} = await t.throwsAsync(execa('ipc-any.js', [methodName], options));
t.true(message.includes(`${methodName}() can only be used`));
};
test('Cannot use exports.sendMessage() without ipc option', testRequiredIpcExports, 'sendMessage', {});
test('Cannot use exports.sendMessage() with ipc: false', testRequiredIpcExports, 'sendMessage', {ipc: false});
test('Cannot use exports.getOneMessage() without ipc option', testRequiredIpcExports, 'getOneMessage', {});
test('Cannot use exports.getOneMessage() with ipc: false', testRequiredIpcExports, 'getOneMessage', {ipc: false});
test('Cannot use exports.getEachMessage() without ipc option', testRequiredIpcExports, 'getEachMessage', {});
test('Cannot use exports.getEachMessage() with ipc: false', testRequiredIpcExports, 'getEachMessage', {ipc: false});
const testPostDisconnection = async (t, methodName) => {
const subprocess = execa('empty.js', {ipc: true});
await subprocess;
const {message} = t.throws(() => subprocess[methodName](foobarString));
t.true(message.includes(`subprocess.${methodName}() cannot be used`));
};
test('subprocess.sendMessage() after disconnection', testPostDisconnection, 'sendMessage');
test('subprocess.getOneMessage() after disconnection', testPostDisconnection, 'getOneMessage');
test('subprocess.getEachMessage() after disconnection', testPostDisconnection, 'getEachMessage');
const testPostDisconnectionSubprocess = async (t, methodName) => {
const subprocess = execa('ipc-disconnect.js', [methodName], {ipc: true});
subprocess.disconnect();
const {message} = await t.throwsAsync(subprocess);
t.true(message.includes(`${methodName}() cannot be used`));
};
test('exports.sendMessage() after disconnection', testPostDisconnectionSubprocess, 'sendMessage');
test('exports.getOneMessage() after disconnection', testPostDisconnectionSubprocess, 'getOneMessage');
test('exports.getEachMessage() after disconnection', testPostDisconnectionSubprocess, 'getEachMessage');
const INVALID_TYPE_MESSAGE = 'The "message" argument must be one of type string';
const UNDEFINED_MESSAGE = 'The "message" argument must be specified';
const CLONE_MESSAGE = 'could not be cloned';
const CYCLE_MESSAGE = 'Converting circular structure to JSON';
const MAX_CALL_STACK_MESSAGE = 'Maximum call stack size exceeded';
const testInvalidPayload = async (t, serialization, message, expectedMessage) => {
const subprocess = execa('empty.js', {ipc: true, serialization});
const error = await t.throwsAsync(subprocess.sendMessage(message));
t.true(error.message.includes('subprocess.sendMessage()\'s argument type is invalid: the message cannot be serialized'));
t.true(error.cause.message.includes(expectedMessage));
await subprocess;
};
const cycleObject = {};
cycleObject.self = cycleObject;
const toJsonCycle = {toJSON: () => ({test: true, toJsonCycle})};
test('subprocess.sendMessage() cannot send undefined', testInvalidPayload, 'advanced', undefined, UNDEFINED_MESSAGE);
test('subprocess.sendMessage() cannot send bigints', testInvalidPayload, 'advanced', 0n, INVALID_TYPE_MESSAGE);
test('subprocess.sendMessage() cannot send symbols', testInvalidPayload, 'advanced', Symbol('test'), INVALID_TYPE_MESSAGE);
test('subprocess.sendMessage() cannot send functions', testInvalidPayload, 'advanced', () => {}, INVALID_TYPE_MESSAGE);
test('subprocess.sendMessage() cannot send promises', testInvalidPayload, 'advanced', Promise.resolve(), CLONE_MESSAGE);
test('subprocess.sendMessage() cannot send proxies', testInvalidPayload, 'advanced', new Proxy({}, {}), CLONE_MESSAGE);
test('subprocess.sendMessage() cannot send Intl', testInvalidPayload, 'advanced', new Intl.Collator(), CLONE_MESSAGE);
test('subprocess.sendMessage() cannot send undefined, JSON', testInvalidPayload, 'json', undefined, UNDEFINED_MESSAGE);
test('subprocess.sendMessage() cannot send bigints, JSON', testInvalidPayload, 'json', 0n, INVALID_TYPE_MESSAGE);
test('subprocess.sendMessage() cannot send symbols, JSON', testInvalidPayload, 'json', Symbol('test'), INVALID_TYPE_MESSAGE);
test('subprocess.sendMessage() cannot send functions, JSON', testInvalidPayload, 'json', () => {}, INVALID_TYPE_MESSAGE);
test('subprocess.sendMessage() cannot send cycles, JSON', testInvalidPayload, 'json', cycleObject, CYCLE_MESSAGE);
test('subprocess.sendMessage() cannot send cycles in toJSON(), JSON', testInvalidPayload, 'json', toJsonCycle, MAX_CALL_STACK_MESSAGE);
test('exports.sendMessage() validates payload', async t => {
const subprocess = execa('ipc-echo-item.js', {ipc: true});
await subprocess.sendMessage([undefined]);
const {message} = await t.throwsAsync(subprocess);
t.true(message.includes('Error: sendMessage()\'s argument type is invalid: the message cannot be serialized'));
t.true(message.includes(UNDEFINED_MESSAGE));
});
================================================
FILE: test/methods/bind.js
================================================
import path from 'node:path';
import test from 'ava';
import {
execa,
execaSync,
execaNode,
$,
} from '../../index.js';
import {foobarString, foobarUppercase} from '../helpers/input.js';
import {uppercaseGenerator} from '../helpers/generator.js';
import {setFixtureDirectory, FIXTURES_DIRECTORY} from '../helpers/fixtures-directory.js';
setFixtureDirectory();
const NOOP_PATH = path.join(FIXTURES_DIRECTORY, 'noop.js');
const PRINT_ENV_PATH = path.join(FIXTURES_DIRECTORY, 'environment.js');
const testBindOptions = async (t, execaMethod) => {
const {stdout} = await execaMethod({stripFinalNewline: false})(NOOP_PATH, [foobarString]);
t.is(stdout, `${foobarString}\n`);
};
test('execa() can bind options', testBindOptions, execa);
test('execaNode() can bind options', testBindOptions, execaNode);
test('$ can bind options', testBindOptions, $);
const testBindOptionsSync = (t, execaMethod) => {
const {stdout} = execaMethod({stripFinalNewline: false})(NOOP_PATH, [foobarString]);
t.is(stdout, `${foobarString}\n`);
};
test('execaSync() can bind options', testBindOptionsSync, execaSync);
test('$.sync can bind options', testBindOptionsSync, $.sync);
const testBindPriority = async (t, execaMethod) => {
const {stdout} = await execaMethod({stripFinalNewline: false})(NOOP_PATH, [foobarString], {stripFinalNewline: true});
t.is(stdout, foobarString);
};
test('execa() bound options have lower priority', testBindPriority, execa);
test('execaSync() bound options have lower priority', testBindPriority, execaSync);
test('execaNode() bound options have lower priority', testBindPriority, execaNode);
test('$ bound options have lower priority', testBindPriority, $);
test('$.sync bound options have lower priority', testBindPriority, $.sync);
const testBindUndefined = async (t, execaMethod) => {
const {stdout} = await execaMethod({stripFinalNewline: false})(NOOP_PATH, [foobarString], {stripFinalNewline: undefined});
t.is(stdout, foobarString);
};
test('execa() undefined options use default value', testBindUndefined, execa);
test('execaSync() undefined options use default value', testBindUndefined, execaSync);
test('execaNode() undefined options use default value', testBindUndefined, execaNode);
test('$ undefined options use default value', testBindUndefined, $);
test('$.sync undefined options use default value', testBindUndefined, $.sync);
const testMergeEnv = async (t, execaMethod) => {
const {stdout} = await execaMethod({env: {FOO: 'foo'}})(PRINT_ENV_PATH, {env: {BAR: 'bar'}});
t.is(stdout, 'foo\nbar');
};
test('execa() bound options are merged', testMergeEnv, execa);
test('execaSync() bound options are merged', testMergeEnv, execaSync);
test('execaNode() bound options are merged', testMergeEnv, execaNode);
test('$ bound options are merged', testMergeEnv, $);
test('$.sync bound options are merged', testMergeEnv, $.sync);
const testMergeMultiple = async (t, execaMethod) => {
const {stdout} = await execaMethod({env: {FOO: 'baz'}})({env: {BAR: 'bar'}})(PRINT_ENV_PATH, {env: {FOO: 'foo'}});
t.is(stdout, 'foo\nbar');
};
test('execa() bound options are merged multiple times', testMergeMultiple, execa);
test('execaSync() bound options are merged multiple times', testMergeMultiple, execaSync);
test('execaNode() bound options are merged multiple times', testMergeMultiple, execaNode);
test('$ bound options are merged multiple times', testMergeMultiple, $);
test('$.sync bound options are merged multiple times', testMergeMultiple, $.sync);
const testMergeFdSpecific = async (t, execaMethod) => {
const {isMaxBuffer, shortMessage} = await t.throwsAsync(execaMethod({maxBuffer: {stdout: 1}})(NOOP_PATH, [foobarString], {maxBuffer: {stderr: 100}}));
t.true(isMaxBuffer);
t.true(shortMessage.includes('Command\'s stdout was larger than 1'));
};
test('execa() bound options merge fd-specific ones', testMergeFdSpecific, execa);
test('execaNode() bound options merge fd-specific ones', testMergeFdSpecific, execaNode);
test('$ bound options merge fd-specific ones', testMergeFdSpecific, $);
const testMergeEnvUndefined = async (t, execaMethod) => {
const {stdout} = await execaMethod({env: {FOO: 'foo'}})(PRINT_ENV_PATH, {env: {BAR: undefined}});
t.is(stdout, 'foo\nundefined');
};
test('execa() bound options are merged even if undefined', testMergeEnvUndefined, execa);
test('execaSync() bound options are merged even if undefined', testMergeEnvUndefined, execaSync);
test('execaNode() bound options are merged even if undefined', testMergeEnvUndefined, execaNode);
test('$ bound options are merged even if undefined', testMergeEnvUndefined, $);
test('$.sync bound options are merged even if undefined', testMergeEnvUndefined, $.sync);
const testMergeSpecific = async (t, execaMethod) => {
const {stdout} = await execaMethod({stdout: {transform: uppercaseGenerator().transform, objectMode: true}})(NOOP_PATH, {stdout: {transform: uppercaseGenerator().transform}});
t.is(stdout, foobarUppercase);
};
test('execa() bound options only merge specific ones', testMergeSpecific, execa);
test('execaSync() bound options only merge specific ones', testMergeSpecific, execaSync);
test('execaNode() bound options only merge specific ones', testMergeSpecific, execaNode);
test('$ bound options only merge specific ones', testMergeSpecific, $);
test('$.sync bound options only merge specific ones', testMergeSpecific, $.sync);
================================================
FILE: test/methods/command.js
================================================
import path from 'node:path';
import test from 'ava';
import {
execa,
execaSync,
$,
execaNode,
execaCommand,
execaCommandSync,
parseCommandString,
} from '../../index.js';
import {
setFixtureDirectory,
FIXTURES_DIRECTORY,
FIXTURES_DIRECTORY_URL,
} from '../helpers/fixtures-directory.js';
import {QUOTE} from '../helpers/verbose.js';
setFixtureDirectory();
const STDIN_FIXTURE = path.join(FIXTURES_DIRECTORY, 'stdin.js');
const ECHO_FIXTURE_URL = new URL('echo.js', FIXTURES_DIRECTORY_URL);
const parseAndRunCommand = command => execa`${parseCommandString(command)}`;
test('execaCommand()', async t => {
const {stdout} = await execaCommand('echo.js foo bar');
t.is(stdout, 'foo\nbar');
});
test('parseCommandString() + execa()', async t => {
const {stdout} = await execa('echo.js', parseCommandString('foo bar'));
t.is(stdout, 'foo\nbar');
});
test('execaCommandSync()', t => {
const {stdout} = execaCommandSync('echo.js foo bar');
t.is(stdout, 'foo\nbar');
});
test('parseCommandString() + execaSync()', t => {
const {stdout} = execaSync('echo.js', parseCommandString('foo bar'));
t.is(stdout, 'foo\nbar');
});
test('execaCommand`...`', async t => {
const {stdout} = await execaCommand`${'echo.js foo bar'}`;
t.is(stdout, 'foo\nbar');
});
test('parseCommandString() + execa`...`', async t => {
const {stdout} = await execa`${parseCommandString('echo.js foo bar')}`;
t.is(stdout, 'foo\nbar');
});
test('parseCommandString() + execa`...`, only arguments', async t => {
const {stdout} = await execa`echo.js ${parseCommandString('foo bar')}`;
t.is(stdout, 'foo\nbar');
});
test('parseCommandString() + execa`...`, only some arguments', async t => {
const {stdout} = await execa`echo.js ${'foo bar'} ${parseCommandString('foo bar')}`;
t.is(stdout, 'foo bar\nfoo\nbar');
});
test('execaCommandSync`...`', t => {
const {stdout} = execaCommandSync`${'echo.js foo bar'}`;
t.is(stdout, 'foo\nbar');
});
test('parseCommandString() + execaSync`...`', t => {
const {stdout} = execaSync`${parseCommandString('echo.js foo bar')}`;
t.is(stdout, 'foo\nbar');
});
test('parseCommandString() + execaSync`...`, only arguments', t => {
const {stdout} = execaSync`echo.js ${parseCommandString('foo bar')}`;
t.is(stdout, 'foo\nbar');
});
test('parseCommandString() + execaSync`...`, only some arguments', t => {
const {stdout} = execaSync`echo.js ${'foo bar'} ${parseCommandString('foo bar')}`;
t.is(stdout, 'foo bar\nfoo\nbar');
});
test('parseCommandString() + $', async t => {
const {stdout} = await $`${parseCommandString('echo.js foo bar')}`;
t.is(stdout, 'foo\nbar');
});
test('parseCommandString() + $.sync', t => {
const {stdout} = $.sync`${parseCommandString('echo.js foo bar')}`;
t.is(stdout, 'foo\nbar');
});
test('parseCommandString() + execaNode', async t => {
const {stdout} = await execaNode(ECHO_FIXTURE_URL, parseCommandString('foo bar'));
t.is(stdout, 'foo\nbar');
});
test('execaCommand(options)`...`', async t => {
const {stdout} = await execaCommand({stripFinalNewline: false})`${'echo.js foo bar'}`;
t.is(stdout, 'foo\nbar\n');
});
test('execaCommandSync(options)`...`', t => {
const {stdout} = execaCommandSync({stripFinalNewline: false})`${'echo.js foo bar'}`;
t.is(stdout, 'foo\nbar\n');
});
test('execaCommand(options)()', async t => {
const {stdout} = await execaCommand({stripFinalNewline: false})('echo.js foo bar');
t.is(stdout, 'foo\nbar\n');
});
test('execaCommandSync(options)()', t => {
const {stdout} = execaCommandSync({stripFinalNewline: false})('echo.js foo bar');
t.is(stdout, 'foo\nbar\n');
});
test('execaCommand().pipe(execaCommand())', async t => {
const {stdout} = await execaCommand('echo.js foo bar').pipe(execaCommand(`node ${STDIN_FIXTURE}`));
t.is(stdout, 'foo\nbar');
});
test('execaCommand().pipe(...) does not use execaCommand', async t => {
const {escapedCommand} = await execaCommand('echo.js foo bar').pipe(`node ${STDIN_FIXTURE}`, {reject: false});
t.true(escapedCommand.startsWith(`${QUOTE}node `));
});
test('execaCommand() bound options have lower priority', async t => {
const {stdout} = await execaCommand({stripFinalNewline: false})('echo.js foo bar', {stripFinalNewline: true});
t.is(stdout, 'foo\nbar');
});
test('execaCommandSync() bound options have lower priority', t => {
const {stdout} = execaCommandSync({stripFinalNewline: false})('echo.js foo bar', {stripFinalNewline: true});
t.is(stdout, 'foo\nbar');
});
const testInvalidArgumentsArray = (t, execaMethod) => {
t.throws(() => execaMethod('echo', ['foo']), {
message: /The command and its arguments must be passed as a single string/,
});
};
test('execaCommand() must not pass an array of arguments', testInvalidArgumentsArray, execaCommand);
test('execaCommandSync() must not pass an array of arguments', testInvalidArgumentsArray, execaCommandSync);
const testInvalidArgumentsTemplate = (t, execaMethod) => {
t.throws(() => execaMethod`echo foo`, {
message: /The command and its arguments must be passed as a single string/,
});
};
test('execaCommand() must not pass an array of arguments with a template string', testInvalidArgumentsTemplate, execaCommand);
test('execaCommandSync() must not pass an array of arguments with a template string', testInvalidArgumentsTemplate, execaCommandSync);
const testInvalidArgumentsParse = (t, command) => {
t.throws(() => parseCommandString(command), {
message: /The command must be a string/,
});
};
test('execaCommand() must not pass a number', testInvalidArgumentsParse, 0);
test('execaCommand() must not pass undefined', testInvalidArgumentsParse, undefined);
test('execaCommand() must not pass null', testInvalidArgumentsParse, null);
test('execaCommand() must not pass a symbol', testInvalidArgumentsParse, Symbol('test'));
test('execaCommand() must not pass an object', testInvalidArgumentsParse, {});
test('execaCommand() must not pass an array', testInvalidArgumentsParse, []);
const testExecaCommandOutput = async (t, command, expectedOutput, execaMethod) => {
const {stdout} = await execaMethod(command);
t.is(stdout, expectedOutput);
};
test('execaCommand() allows escaping spaces in commands', testExecaCommandOutput, 'command\\ with\\ space.js foo bar', 'foo\nbar', execaCommand);
test('execaCommand() trims', testExecaCommandOutput, ' echo.js foo bar ', 'foo\nbar', execaCommand);
test('execaCommand() ignores consecutive spaces', testExecaCommandOutput, 'echo.js foo bar', 'foo\nbar', execaCommand);
test('execaCommand() escapes other whitespaces', testExecaCommandOutput, 'echo.js foo\tbar', 'foo\tbar', execaCommand);
test('execaCommand() allows escaping spaces', testExecaCommandOutput, 'echo.js foo\\ bar', 'foo bar', execaCommand);
test('execaCommand() allows escaping backslashes before spaces', testExecaCommandOutput, 'echo.js foo\\\\ bar', 'foo\\ bar', execaCommand);
test('execaCommand() allows escaping multiple backslashes before spaces', testExecaCommandOutput, 'echo.js foo\\\\\\\\ bar', 'foo\\\\\\ bar', execaCommand);
test('execaCommand() allows escaping backslashes not before spaces', testExecaCommandOutput, 'echo.js foo\\bar baz', 'foo\\bar\nbaz', execaCommand);
test('parseCommandString() allows escaping spaces in commands', testExecaCommandOutput, 'command\\ with\\ space.js foo bar', 'foo\nbar', parseAndRunCommand);
test('parseCommandString() trims', testExecaCommandOutput, ' echo.js foo bar ', 'foo\nbar', parseAndRunCommand);
test('parseCommandString() ignores consecutive spaces', testExecaCommandOutput, 'echo.js foo bar', 'foo\nbar', parseAndRunCommand);
test('parseCommandString() escapes other whitespaces', testExecaCommandOutput, 'echo.js foo\tbar', 'foo\tbar', parseAndRunCommand);
test('parseCommandString() allows escaping spaces', testExecaCommandOutput, 'echo.js foo\\ bar', 'foo bar', parseAndRunCommand);
test('parseCommandString() allows escaping backslashes before spaces', testExecaCommandOutput, 'echo.js foo\\\\ bar', 'foo\\ bar', parseAndRunCommand);
test('parseCommandString() allows escaping multiple backslashes before spaces', testExecaCommandOutput, 'echo.js foo\\\\\\\\ bar', 'foo\\\\\\ bar', parseAndRunCommand);
test('parseCommandString() allows escaping backslashes not before spaces', testExecaCommandOutput, 'echo.js foo\\bar baz', 'foo\\bar\nbaz', parseAndRunCommand);
test('parseCommandString() can get empty strings', t => {
t.deepEqual(parseCommandString(''), []);
});
test('parseCommandString() can get only whitespaces', t => {
t.deepEqual(parseCommandString(' '), []);
});
================================================
FILE: test/methods/create.js
================================================
import path from 'node:path';
import test from 'ava';
import {
execa,
execaSync,
execaNode,
$,
} from '../../index.js';
import {foobarString, foobarArray} from '../helpers/input.js';
import {setFixtureDirectory, FIXTURES_DIRECTORY} from '../helpers/fixtures-directory.js';
setFixtureDirectory();
const NOOP_PATH = path.join(FIXTURES_DIRECTORY, 'noop.js');
const testTemplate = async (t, execaMethod) => {
const {stdout} = await execaMethod`${NOOP_PATH} ${foobarString}`;
t.is(stdout, foobarString);
};
test('execa() can use template strings', testTemplate, execa);
test('execaNode() can use template strings', testTemplate, execaNode);
test('$ can use template strings', testTemplate, $);
const testTemplateSync = (t, execaMethod) => {
const {stdout} = execaMethod`${NOOP_PATH} ${foobarString}`;
t.is(stdout, foobarString);
};
test('execaSync() can use template strings', testTemplateSync, execaSync);
test('$.sync can use template strings', testTemplateSync, $.sync);
const testTemplateOptions = async (t, execaMethod) => {
const {stdout} = await execaMethod({stripFinalNewline: false})`${NOOP_PATH} ${foobarString}`;
t.is(stdout, `${foobarString}\n`);
};
test('execa() can use template strings with options', testTemplateOptions, execa);
test('execaNode() can use template strings with options', testTemplateOptions, execaNode);
test('$ can use template strings with options', testTemplateOptions, $);
const testTemplateOptionsSync = (t, execaMethod) => {
const {stdout} = execaMethod({stripFinalNewline: false})`${NOOP_PATH} ${foobarString}`;
t.is(stdout, `${foobarString}\n`);
};
test('execaSync() can use template strings with options', testTemplateOptionsSync, execaSync);
test('$.sync can use template strings with options', testTemplateOptionsSync, $.sync);
const testSpacedCommand = async (t, commandArguments, execaMethod) => {
const {stdout} = await execaMethod('command with space.js', commandArguments);
const expectedStdout = commandArguments === undefined ? '' : commandArguments.join('\n');
t.is(stdout, expectedStdout);
};
test('allow commands with spaces and no array arguments', testSpacedCommand, undefined, execa);
test('allow commands with spaces and array arguments', testSpacedCommand, foobarArray, execa);
test('allow commands with spaces and no array arguments, execaSync', testSpacedCommand, undefined, execaSync);
test('allow commands with spaces and array arguments, execaSync', testSpacedCommand, foobarArray, execaSync);
test('allow commands with spaces and no array arguments, $', testSpacedCommand, undefined, $);
test('allow commands with spaces and array arguments, $', testSpacedCommand, foobarArray, $);
test('allow commands with spaces and no array arguments, $.sync', testSpacedCommand, undefined, $.sync);
test('allow commands with spaces and array arguments, $.sync', testSpacedCommand, foobarArray, $.sync);
================================================
FILE: test/methods/main-async.js
================================================
import process from 'node:process';
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
setFixtureDirectory();
const isWindows = process.platform === 'win32';
if (isWindows) {
test('execa() - cmd file', async t => {
const {stdout} = await execa('hello.cmd');
t.is(stdout, 'Hello World');
});
test('execa() - run cmd command', async t => {
const {stdout} = await execa('cmd', ['/c', 'hello.cmd']);
t.is(stdout, 'Hello World');
});
}
test('execa() returns a promise with pid', async t => {
const subprocess = execa('noop.js', ['foo']);
t.is(typeof subprocess.pid, 'number');
await subprocess;
});
================================================
FILE: test/methods/node.js
================================================
import path from 'node:path';
import process, {version} from 'node:process';
import {pathToFileURL} from 'node:url';
import test from 'ava';
import getNode from 'get-node';
import {execa, execaSync, execaNode} from '../../index.js';
import {FIXTURES_DIRECTORY} from '../helpers/fixtures-directory.js';
import {identity, fullStdio} from '../helpers/stdio.js';
import {foobarString} from '../helpers/input.js';
import {getDenoNodePath} from '../helpers/file-path.js';
process.chdir(FIXTURES_DIRECTORY);
const runWithNodeOption = (file, commandArguments, options) => Array.isArray(commandArguments)
? execa(file, commandArguments, {...options, node: true})
: execa(file, {...options, node: true});
const runWithNodeOptionSync = (file, commandArguments, options) => Array.isArray(commandArguments)
? execaSync(file, commandArguments, {...options, node: true})
: execaSync(file, {...options, node: true});
const runWithIpc = (file, options) => execa('node', [file], {...options, ipc: true});
const testNodeSuccess = async (t, execaMethod) => {
const {exitCode, stdout} = await execaMethod('noop.js', [foobarString]);
t.is(exitCode, 0);
t.is(stdout, foobarString);
};
test('execaNode() succeeds', testNodeSuccess, execaNode);
test('The "node" option succeeds', testNodeSuccess, runWithNodeOption);
test('The "node" option succeeds - sync', testNodeSuccess, runWithNodeOptionSync);
test('execaNode(options) succeeds', async t => {
const {stdout} = await execaNode({stripFinalNewline: false})('noop.js', [foobarString]);
t.is(stdout, `${foobarString}\n`);
});
test('execaNode`...` succeeds', async t => {
const {stdout} = await execaNode`noop.js ${foobarString}`;
t.is(stdout, foobarString);
});
test('execaNode().pipe(execaNode()) succeeds', async t => {
const {stdout} = await execaNode('noop.js').pipe(execaNode('--version'));
t.is(stdout, version);
});
test('execaNode().pipe(execa()) requires using "node"', async t => {
await t.throwsAsync(execaNode('noop.js').pipe(execa('--version')));
});
test('execaNode().pipe(...) requires using "node"', async t => {
await t.throwsAsync(execaNode('noop.js').pipe('--version'));
});
test('execaNode().pipe`...` requires using "node"', async t => {
await t.throwsAsync(execaNode('noop.js').pipe`--version`);
});
test('execaNode() cannot set the "node" option to false', t => {
t.throws(() => {
execaNode('empty.js', {node: false});
}, {message: /The "node" option cannot be false/});
});
const testDoubleNode = (t, nodePath, execaMethod) => {
t.throws(() => {
execaMethod(nodePath, ['noop.js']);
}, {message: /does not need to be "node"/});
};
test('Cannot use "node" as binary - execaNode()', testDoubleNode, 'node', execaNode);
test('Cannot use "node" as binary - "node" option', testDoubleNode, 'node', runWithNodeOption);
test('Cannot use "node" as binary - "node" option sync', testDoubleNode, 'node', runWithNodeOptionSync);
test('Cannot use path to "node" as binary - execaNode()', testDoubleNode, process.execPath, execaNode);
test('Cannot use path to "node" as binary - "node" option', testDoubleNode, process.execPath, runWithNodeOption);
test('Cannot use path to "node" as binary - "node" option sync', testDoubleNode, process.execPath, runWithNodeOptionSync);
test('Cannot use deno style nodePath as binary - execaNode()', testDoubleNode, getDenoNodePath(), execaNode);
test('Cannot use deno style nodePath as binary - "node" option', testDoubleNode, getDenoNodePath(), runWithNodeOption);
test('Cannot use deno style nodePath as binary - "node" option sync', testDoubleNode, getDenoNodePath(), runWithNodeOptionSync);
const getNodePath = async () => {
const {path} = await getNode(TEST_NODE_VERSION);
return path;
};
const TEST_NODE_VERSION = '16.0.0';
const testNodePath = async (t, execaMethod, mapPath) => {
const nodePath = mapPath(await getNodePath());
const {stdout} = await execaMethod('--version', [], {nodePath});
t.is(stdout, `v${TEST_NODE_VERSION}`);
};
test.serial('The "nodePath" option can be used - execaNode()', testNodePath, execaNode, identity);
test.serial('The "nodePath" option can be a file URL - execaNode()', testNodePath, execaNode, pathToFileURL);
test.serial('The "nodePath" option can be used - "node" option', testNodePath, runWithNodeOption, identity);
test.serial('The "nodePath" option can be a file URL - "node" option', testNodePath, runWithNodeOption, pathToFileURL);
test.serial('The "nodePath" option can be used - "node" option sync', testNodePath, runWithNodeOptionSync, identity);
test.serial('The "nodePath" option can be a file URL - "node" option sync', testNodePath, runWithNodeOptionSync, pathToFileURL);
const testNodePathDefault = async (t, execaMethod) => {
const {stdout} = await execaMethod('--version');
t.is(stdout, process.version);
};
test('The "nodePath" option defaults to the current Node.js binary - execaNode()', testNodePathDefault, execaNode);
test('The "nodePath" option defaults to the current Node.js binary - "node" option', testNodePathDefault, runWithNodeOption);
test('The "nodePath" option defaults to the current Node.js binary - "node" option sync', testNodePathDefault, runWithNodeOptionSync);
const testNodePathInvalid = (t, execaMethod) => {
t.throws(() => {
execaMethod('noop.js', [], {nodePath: true});
}, {message: /The "nodePath" option must be a string or a file URL/});
};
test('The "nodePath" option must be a string or URL - execaNode()', testNodePathInvalid, execaNode);
test('The "nodePath" option must be a string or URL - "node" option', testNodePathInvalid, runWithNodeOption);
test('The "nodePath" option must be a string or URL - "node" option sync', testNodePathInvalid, runWithNodeOptionSync);
const testFormerNodePath = (t, execaMethod) => {
t.throws(() => {
execaMethod('noop.js', [], {execPath: process.execPath});
}, {message: /The "execPath" option has been removed/});
};
test('The "execPath" option cannot be used - execaNode()', testFormerNodePath, execaNode);
test('The "execPath" option cannot be used - "node" option', testFormerNodePath, runWithNodeOption);
test('The "execPath" option cannot be used - "node" option sync', testFormerNodePath, runWithNodeOptionSync);
const nodePathArguments = ['-p', ['process.env.Path || process.env.PATH']];
const testSubprocessNodePath = async (t, execaMethod, mapPath) => {
const nodePath = mapPath(await getNodePath());
const {stdout} = await execaMethod(...nodePathArguments, {nodePath});
t.true(stdout.includes(TEST_NODE_VERSION));
};
test.serial('The "nodePath" option impacts the subprocess - execaNode()', testSubprocessNodePath, execaNode, identity);
test.serial('The "nodePath" option impacts the subprocess - "node" option', testSubprocessNodePath, runWithNodeOption, identity);
test.serial('The "nodePath" option impacts the subprocess - "node" option sync', testSubprocessNodePath, runWithNodeOptionSync, identity);
const testSubprocessNodePathDefault = async (t, execaMethod) => {
const {stdout} = await execaMethod(...nodePathArguments);
t.true(stdout.includes(path.dirname(process.execPath)));
};
test('The "nodePath" option defaults to the current Node.js binary in the subprocess - execaNode()', testSubprocessNodePathDefault, execaNode);
test('The "nodePath" option defaults to the current Node.js binary in the subprocess - "node" option', testSubprocessNodePathDefault, runWithNodeOption);
test('The "nodePath" option defaults to the current Node.js binary in the subprocess - "node" option sync', testSubprocessNodePathDefault, runWithNodeOptionSync);
test.serial('The "nodePath" option requires "node: true" to impact the subprocess', async t => {
const nodePath = await getNodePath();
const {stdout} = await execa('node', nodePathArguments.flat(), {nodePath});
t.false(stdout.includes(TEST_NODE_VERSION));
});
const testSubprocessNodePathCwd = async (t, execaMethod) => {
const nodePath = await getNodePath();
const cwd = path.dirname(path.dirname(nodePath));
const relativeExecPath = path.relative(cwd, nodePath);
const {stdout} = await execaMethod(...nodePathArguments, {nodePath: relativeExecPath, cwd});
t.true(stdout.includes(TEST_NODE_VERSION));
};
test.serial('The "nodePath" option is relative to "cwd" when used in the subprocess - execaNode()', testSubprocessNodePathCwd, execaNode);
test.serial('The "nodePath" option is relative to "cwd" when used in the subprocess - "node" option', testSubprocessNodePathCwd, runWithNodeOption);
test.serial('The "nodePath" option is relative to "cwd" when used in the subprocess - "node" option sync', testSubprocessNodePathCwd, runWithNodeOptionSync);
const testCwdNodePath = async (t, execaMethod) => {
const nodePath = await getNodePath();
const cwd = path.dirname(path.dirname(nodePath));
const relativeExecPath = path.relative(cwd, nodePath);
const {stdout} = await execaMethod('--version', [], {nodePath: relativeExecPath, cwd});
t.is(stdout, `v${TEST_NODE_VERSION}`);
};
test.serial('The "nodePath" option is relative to "cwd" - execaNode()', testCwdNodePath, execaNode);
test.serial('The "nodePath" option is relative to "cwd" - "node" option', testCwdNodePath, runWithNodeOption);
test.serial('The "nodePath" option is relative to "cwd" - "node" option sync', testCwdNodePath, runWithNodeOptionSync);
const testDenoExecPath = async (t, execaMethod) => {
const {exitCode, stdout} = await execaMethod('noop.js', [], {nodePath: getDenoNodePath()});
t.is(exitCode, 0);
t.is(stdout, foobarString);
};
test('The deno style "nodePath" option can be used - execaNode()', testDenoExecPath, execaNode);
test('The deno style "nodePath" option can be used - "node" option', testDenoExecPath, runWithNodeOption);
test('The deno style "nodePath" option can be used - "node" option sync', testDenoExecPath, runWithNodeOptionSync);
const testNodeOptions = async (t, execaMethod) => {
const {stdout} = await execaMethod('empty.js', [], {nodeOptions: ['--version']});
t.is(stdout, process.version);
};
test('The "nodeOptions" option can be used - execaNode()', testNodeOptions, execaNode);
test('The "nodeOptions" option can be used - "node" option', testNodeOptions, runWithNodeOption);
test('The "nodeOptions" option can be used - "node" option sync', testNodeOptions, runWithNodeOptionSync);
const spawnNestedExecaNode = (realExecArgv, fakeExecArgv, execaMethod, nodeOptions) => execa(
'node',
[...realExecArgv, 'nested-node.js', fakeExecArgv, execaMethod, nodeOptions, 'noop.js', foobarString],
{...fullStdio, cwd: FIXTURES_DIRECTORY},
);
const testInspectRemoval = async (t, fakeExecArgv, execaMethod) => {
const {stdout, stdio} = await spawnNestedExecaNode([], fakeExecArgv, execaMethod, '');
t.is(stdout, foobarString);
t.is(stdio[3], '');
};
test('The "nodeOptions" option removes --inspect without a port when defined by current process - execaNode()', testInspectRemoval, '--inspect', 'execaNode');
test('The "nodeOptions" option removes --inspect without a port when defined by current process - "node" option', testInspectRemoval, '--inspect', 'nodeOption');
test('The "nodeOptions" option removes --inspect with a port when defined by current process - execaNode()', testInspectRemoval, '--inspect=9222', 'execaNode');
test('The "nodeOptions" option removes --inspect with a port when defined by current process - "node" option', testInspectRemoval, '--inspect=9222', 'nodeOption');
test('The "nodeOptions" option removes --inspect-brk without a port when defined by current process - execaNode()', testInspectRemoval, '--inspect-brk', 'execaNode');
test('The "nodeOptions" option removes --inspect-brk without a port when defined by current process - "node" option', testInspectRemoval, '--inspect-brk', 'nodeOption');
test('The "nodeOptions" option removes --inspect-brk with a port when defined by current process - execaNode()', testInspectRemoval, '--inspect-brk=9223', 'execaNode');
test('The "nodeOptions" option removes --inspect-brk with a port when defined by current process - "node" option', testInspectRemoval, '--inspect-brk=9223', 'nodeOption');
const testInspectDifferentPort = async (t, execaMethod) => {
const {stdout, stdio} = await spawnNestedExecaNode(['--inspect=9225'], '', execaMethod, '--inspect=9224');
t.is(stdout, foobarString);
t.true(stdio[3].includes('Debugger listening'));
};
test.serial('The "nodeOptions" option allows --inspect with a different port even when defined by current process - execaNode()', testInspectDifferentPort, 'execaNode');
test.serial('The "nodeOptions" option allows --inspect with a different port even when defined by current process - "node" option', testInspectDifferentPort, 'nodeOption');
const testInspectSamePort = async (t, execaMethod) => {
const {stdout, stdio} = await spawnNestedExecaNode(['--inspect=9226'], '', execaMethod, '--inspect=9226');
t.is(stdout, foobarString);
t.true(stdio[3].includes('address already in use'));
};
test.serial('The "nodeOptions" option forbids --inspect with the same port when defined by current process - execaNode()', testInspectSamePort, 'execaNode');
test.serial('The "nodeOptions" option forbids --inspect with the same port when defined by current process - "node" option', testInspectSamePort, 'nodeOption');
const testIpc = async (t, execaMethod, options) => {
const subprocess = execaMethod('ipc-echo.js', [], options);
await subprocess.sendMessage(foobarString);
t.is(await subprocess.getOneMessage(), foobarString);
const {stdio} = await subprocess;
t.is(stdio.length, 4);
t.is(stdio[3], undefined);
};
test('execaNode() adds an ipc channel', testIpc, execaNode, {});
test('The "node" option adds an ipc channel', testIpc, runWithNodeOption, {});
test('The "ipc" option adds an ipc channel', testIpc, runWithIpc, {});
test('The "ipc" option works with "stdio: \'pipe\'"', testIpc, runWithIpc, {stdio: 'pipe'});
test('The "ipc" option works with "stdio: [\'pipe\', \'pipe\', \'pipe\']"', testIpc, runWithIpc, {stdio: ['pipe', 'pipe', 'pipe']});
test('The "ipc" option works with "stdio: [\'pipe\', \'pipe\', \'pipe\', \'ipc\']"', testIpc, runWithIpc, {stdio: ['pipe', 'pipe', 'pipe', 'ipc']});
test('The "ipc" option works with "stdout: \'pipe\'"', testIpc, runWithIpc, {stdout: 'pipe'});
const NO_SEND_MESSAGE = 'sendMessage() can only be used';
test('No ipc channel is added by default', async t => {
const {message, stdio} = await t.throwsAsync(execa('node', ['ipc-send.js']));
t.true(message.includes(NO_SEND_MESSAGE));
t.is(stdio.length, 3);
});
const testDisableIpc = async (t, execaMethod) => {
const {failed, message, stdio} = await execaMethod('ipc-send.js', [], {ipc: false, reject: false});
t.true(failed);
t.true(message.includes(NO_SEND_MESSAGE));
t.is(stdio.length, 3);
};
test('Can disable "ipc" - execaNode()', testDisableIpc, execaNode);
test('Can disable "ipc" - "node" option', testDisableIpc, runWithNodeOption);
test('Can disable "ipc" - "node" option sync', testDisableIpc, runWithNodeOptionSync);
const NO_IPC_MESSAGE = /The "ipc: true" option cannot be used/;
const testNoIpcSync = (t, node) => {
t.throws(() => {
execaSync('node', ['ipc-send.js'], {ipc: true, node});
}, {message: NO_IPC_MESSAGE});
};
test('Cannot use "ipc: true" with execaSync()', testNoIpcSync, undefined);
test('Cannot use "ipc: true" with execaSync() - "node: false"', testNoIpcSync, false);
test('Cannot use "ipc: true" with execaSync() - "node: true"', t => {
t.throws(() => {
execaSync('ipc-send.js', {ipc: true, node: true});
}, {message: NO_IPC_MESSAGE});
});
const testNoShell = async (t, execaMethod) => {
const {failed, message} = await execaMethod('node --version', [], {shell: true, reject: false});
t.true(failed);
t.true(message.includes('MODULE_NOT_FOUND'));
};
test('Cannot use "shell: true" - execaNode()', testNoShell, execaNode);
test('Cannot use "shell: true" - "node" option', testNoShell, runWithNodeOption);
test('Cannot use "shell: true" - "node" option sync', testNoShell, runWithNodeOptionSync);
================================================
FILE: test/methods/override-promise.js
================================================
import test from 'ava';
// The helper module overrides Promise on import so has to be imported before `execa`.
import {restorePromise} from '../helpers/override-promise.js';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
restorePromise();
setFixtureDirectory();
test('should work with third-party Promise', async t => {
const {stdout} = await execa('noop.js', ['foo']);
t.is(stdout, 'foo');
});
================================================
FILE: test/methods/parameters-args.js
================================================
import test from 'ava';
import {
execa,
execaSync,
execaCommand,
execaCommandSync,
execaNode,
$,
} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
setFixtureDirectory();
const testInvalidArguments = async (t, execaMethod) => {
t.throws(() => {
execaMethod('echo', true);
}, {message: /Second argument must be either/});
};
test('execa()\'s second argument must be valid', testInvalidArguments, execa);
test('execaSync()\'s second argument must be valid', testInvalidArguments, execaSync);
test('execaCommand()\'s second argument must be valid', testInvalidArguments, execaCommand);
test('execaCommandSync()\'s second argument must be valid', testInvalidArguments, execaCommandSync);
test('execaNode()\'s second argument must be valid', testInvalidArguments, execaNode);
test('$\'s second argument must be valid', testInvalidArguments, $);
test('$.sync\'s second argument must be valid', testInvalidArguments, $.sync);
const testInvalidArgumentsItems = async (t, execaMethod) => {
t.throws(() => {
execaMethod('echo', [{}]);
}, {message: 'Second argument must be an array of strings: [object Object]'});
};
test('execa()\'s second argument must not be objects', testInvalidArgumentsItems, execa);
test('execaSync()\'s second argument must not be objects', testInvalidArgumentsItems, execaSync);
test('execaCommand()\'s second argument must not be objects', testInvalidArgumentsItems, execaCommand);
test('execaCommandSync()\'s second argument must not be objects', testInvalidArgumentsItems, execaCommandSync);
test('execaNode()\'s second argument must not be objects', testInvalidArgumentsItems, execaNode);
test('$\'s second argument must not be objects', testInvalidArgumentsItems, $);
test('$.sync\'s second argument must not be objects', testInvalidArgumentsItems, $.sync);
const testNullByteArgument = async (t, execaMethod) => {
t.throws(() => {
execaMethod('echo', ['a\0b']);
}, {message: /null bytes/});
};
test('execa()\'s second argument must not include \\0', testNullByteArgument, execa);
test('execaSync()\'s second argument must not include \\0', testNullByteArgument, execaSync);
test('execaCommand()\'s second argument must not include \\0', testNullByteArgument, execaCommand);
test('execaCommandSync()\'s second argument must not include \\0', testNullByteArgument, execaCommandSync);
test('execaNode()\'s second argument must not include \\0', testNullByteArgument, execaNode);
test('$\'s second argument must not include \\0', testNullByteArgument, $);
test('$.sync\'s second argument must not include \\0', testNullByteArgument, $.sync);
================================================
FILE: test/methods/parameters-command.js
================================================
import path from 'node:path';
import {fileURLToPath} from 'node:url';
import test from 'ava';
import {
execa,
execaSync,
execaCommand,
execaCommandSync,
execaNode,
$,
} from '../../index.js';
import {setFixtureDirectory, FIXTURES_DIRECTORY_URL} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
setFixtureDirectory();
const testFileUrl = async (t, execaMethod) => {
const command = new URL('noop.js', FIXTURES_DIRECTORY_URL);
const {stdout} = await execaMethod(command);
t.is(stdout, foobarString);
};
test('execa()\'s command argument can be a file URL', testFileUrl, execa);
test('execaSync()\'s command argument can be a file URL', testFileUrl, execaSync);
test('execaCommand()\'s command argument can be a file URL', testFileUrl, execaCommand);
test('execaCommandSync()\'s command argument can be a file URL', testFileUrl, execaCommandSync);
test('execaNode()\'s command argument can be a file URL', testFileUrl, execaNode);
test('$\'s command argument can be a file URL', testFileUrl, $);
test('$.sync\'s command argument can be a file URL', testFileUrl, $.sync);
const testInvalidFileUrl = async (t, execaMethod) => {
const invalidUrl = new URL('https://invalid.com');
t.throws(() => {
execaMethod(invalidUrl);
}, {code: 'ERR_INVALID_URL_SCHEME'});
};
test('execa()\'s command argument cannot be a non-file URL', testInvalidFileUrl, execa);
test('execaSync()\'s command argument cannot be a non-file URL', testInvalidFileUrl, execaSync);
test('execaCommand()\'s command argument cannot be a non-file URL', testInvalidFileUrl, execaCommand);
test('execaCommandSync()\'s command argument cannot be a non-file URL', testInvalidFileUrl, execaCommandSync);
test('execaNode()\'s command argument cannot be a non-file URL', testInvalidFileUrl, execaNode);
test('$\'s command argument cannot be a non-file URL', testInvalidFileUrl, $);
test('$.sync\'s command argument cannot be a non-file URL', testInvalidFileUrl, $.sync);
const testInvalidCommand = async (t, commandArgument, execaMethod) => {
t.throws(() => {
execaMethod(commandArgument);
}, {message: /First argument must be a string or a file URL/});
};
test('execa()\'s first argument must be defined', testInvalidCommand, undefined, execa);
test('execaSync()\'s first argument must be defined', testInvalidCommand, undefined, execaSync);
test('execaCommand()\'s first argument must be defined', testInvalidCommand, undefined, execaCommand);
test('execaCommandSync()\'s first argument must be defined', testInvalidCommand, undefined, execaCommandSync);
test('execaNode()\'s first argument must be defined', testInvalidCommand, undefined, execaNode);
test('$\'s first argument must be defined', testInvalidCommand, undefined, $);
test('$.sync\'s first argument must be defined', testInvalidCommand, undefined, $.sync);
test('execa()\'s first argument must be valid', testInvalidCommand, true, execa);
test('execaSync()\'s first argument must be valid', testInvalidCommand, true, execaSync);
test('execaCommand()\'s first argument must be valid', testInvalidCommand, true, execaCommand);
test('execaCommandSync()\'s first argument must be valid', testInvalidCommand, true, execaCommandSync);
test('execaNode()\'s first argument must be valid', testInvalidCommand, true, execaNode);
test('$\'s first argument must be valid', testInvalidCommand, true, $);
test('$.sync\'s first argument must be valid', testInvalidCommand, true, $.sync);
test('execa()\'s command argument must be a string or file URL', testInvalidCommand, ['command', 'arg'], execa);
test('execaSync()\'s command argument must be a string or file URL', testInvalidCommand, ['command', 'arg'], execaSync);
test('execaCommand()\'s command argument must be a string or file URL', testInvalidCommand, ['command', 'arg'], execaCommand);
test('execaCommandSync()\'s command argument must be a string or file URL', testInvalidCommand, ['command', 'arg'], execaCommandSync);
test('execaNode()\'s command argument must be a string or file URL', testInvalidCommand, ['command', 'arg'], execaNode);
test('$\'s command argument must be a string or file URL', testInvalidCommand, ['command', 'arg'], $);
test('$.sync\'s command argument must be a string or file URL', testInvalidCommand, ['command', 'arg'], $.sync);
const testRelativePath = async (t, execaMethod) => {
// @todo: use import.meta.dirname after dropping support for Node <20.11.0
const rootDirectory = path.basename(fileURLToPath(new URL('../..', import.meta.url)));
const pathViaParentDirectory = path.join('..', rootDirectory, 'test', 'fixtures', 'noop.js');
const {stdout} = await execaMethod(pathViaParentDirectory);
t.is(stdout, foobarString);
};
test('execa() use relative path with \'..\' chars', testRelativePath, execa);
test('execaSync() use relative path with \'..\' chars', testRelativePath, execaSync);
test('execaCommand() use relative path with \'..\' chars', testRelativePath, execaCommand);
test('execaCommandSync() use relative path with \'..\' chars', testRelativePath, execaCommandSync);
test('execaNode() use relative path with \'..\' chars', testRelativePath, execaNode);
test('$ use relative path with \'..\' chars', testRelativePath, $);
test('$.sync use relative path with \'..\' chars', testRelativePath, $.sync);
================================================
FILE: test/methods/parameters-options.js
================================================
import path from 'node:path';
import test from 'ava';
import {
execa,
execaSync,
execaCommand,
execaCommandSync,
execaNode,
$,
} from '../../index.js';
import {setFixtureDirectory, FIXTURES_DIRECTORY} from '../helpers/fixtures-directory.js';
setFixtureDirectory();
const NOOP_PATH = path.join(FIXTURES_DIRECTORY, 'noop.js');
const testSerializeArgument = async (t, commandArgument, execaMethod) => {
const {stdout} = await execaMethod(NOOP_PATH, [commandArgument]);
t.is(stdout, String(commandArgument));
};
test('execa()\'s arguments can be numbers', testSerializeArgument, 1, execa);
test('execa()\'s arguments can be booleans', testSerializeArgument, true, execa);
test('execa()\'s arguments can be NaN', testSerializeArgument, Number.NaN, execa);
test('execa()\'s arguments can be Infinity', testSerializeArgument, Number.POSITIVE_INFINITY, execa);
test('execa()\'s arguments can be null', testSerializeArgument, null, execa);
test('execa()\'s arguments can be undefined', testSerializeArgument, undefined, execa);
test('execa()\'s arguments can be bigints', testSerializeArgument, 1n, execa);
test('execa()\'s arguments can be symbols', testSerializeArgument, Symbol('test'), execa);
test('execaSync()\'s arguments can be numbers', testSerializeArgument, 1, execaSync);
test('execaSync()\'s arguments can be booleans', testSerializeArgument, true, execaSync);
test('execaSync()\'s arguments can be NaN', testSerializeArgument, Number.NaN, execaSync);
test('execaSync()\'s arguments can be Infinity', testSerializeArgument, Number.POSITIVE_INFINITY, execaSync);
test('execaSync()\'s arguments can be null', testSerializeArgument, null, execaSync);
test('execaSync()\'s arguments can be undefined', testSerializeArgument, undefined, execaSync);
test('execaSync()\'s arguments can be bigints', testSerializeArgument, 1n, execaSync);
test('execaSync()\'s arguments can be symbols', testSerializeArgument, Symbol('test'), execaSync);
test('execaNode()\'s arguments can be numbers', testSerializeArgument, 1, execaNode);
test('execaNode()\'s arguments can be booleans', testSerializeArgument, true, execaNode);
test('execaNode()\'s arguments can be NaN', testSerializeArgument, Number.NaN, execaNode);
test('execaNode()\'s arguments can be Infinity', testSerializeArgument, Number.POSITIVE_INFINITY, execaNode);
test('execaNode()\'s arguments can be null', testSerializeArgument, null, execaNode);
test('execaNode()\'s arguments can be undefined', testSerializeArgument, undefined, execaNode);
test('execaNode()\'s arguments can be bigints', testSerializeArgument, 1n, execaNode);
test('execaNode()\'s arguments can be symbols', testSerializeArgument, Symbol('test'), execaNode);
test('$\'s arguments can be numbers', testSerializeArgument, 1, $);
test('$\'s arguments can be booleans', testSerializeArgument, true, $);
test('$\'s arguments can be NaN', testSerializeArgument, Number.NaN, $);
test('$\'s arguments can be Infinity', testSerializeArgument, Number.POSITIVE_INFINITY, $);
test('$\'s arguments can be null', testSerializeArgument, null, $);
test('$\'s arguments can be undefined', testSerializeArgument, undefined, $);
test('$\'s arguments can be bigints', testSerializeArgument, 1n, $);
test('$\'s arguments can be symbols', testSerializeArgument, Symbol('test'), $);
test('$.sync\'s arguments can be numbers', testSerializeArgument, 1, $.sync);
test('$.sync\'s arguments can be booleans', testSerializeArgument, true, $.sync);
test('$.sync\'s arguments can be NaN', testSerializeArgument, Number.NaN, $.sync);
test('$.sync\'s arguments can be Infinity', testSerializeArgument, Number.POSITIVE_INFINITY, $.sync);
test('$.sync\'s arguments can be null', testSerializeArgument, null, $.sync);
test('$.sync\'s arguments can be undefined', testSerializeArgument, undefined, $.sync);
test('$.sync\'s arguments can be bigints', testSerializeArgument, 1n, $.sync);
test('$.sync\'s arguments can be symbols', testSerializeArgument, Symbol('test'), $.sync);
const testInvalidOptions = async (t, execaMethod) => {
t.throws(() => {
execaMethod('echo', [], new Map());
}, {message: /Last argument must be an options object/});
};
test('execa()\'s third argument must be a plain object', testInvalidOptions, execa);
test('execaSync()\'s third argument must be a plain object', testInvalidOptions, execaSync);
test('execaCommand()\'s third argument must be a plain object', testInvalidOptions, execaCommand);
test('execaCommandSync()\'s third argument must be a plain object', testInvalidOptions, execaCommandSync);
test('execaNode()\'s third argument must be a plain object', testInvalidOptions, execaNode);
test('$\'s third argument must be a plain object', testInvalidOptions, $);
test('$.sync\'s third argument must be a plain object', testInvalidOptions, $.sync);
================================================
FILE: test/methods/promise.js
================================================
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
setFixtureDirectory();
test('promise methods are not enumerable', t => {
const descriptors = Object.getOwnPropertyDescriptors(execa('noop.js'));
t.false(descriptors.then.enumerable);
t.false(descriptors.catch.enumerable);
t.false(descriptors.finally.enumerable);
});
test('finally function is executed on success', async t => {
let isCalled = false;
const {stdout} = await execa('noop.js', ['foo']).finally(() => {
isCalled = true;
});
t.is(isCalled, true);
t.is(stdout, 'foo');
});
test('finally function is executed on failure', async t => {
let isError = false;
const {stdout, stderr} = await t.throwsAsync(execa('exit.js', ['2']).finally(() => {
isError = true;
}));
t.is(isError, true);
t.is(typeof stdout, 'string');
t.is(typeof stderr, 'string');
});
test('throw in finally function bubbles up on success', async t => {
const {message} = await t.throwsAsync(execa('noop.js', ['foo']).finally(() => {
throw new Error('called');
}));
t.is(message, 'called');
});
test('throw in finally bubbles up on error', async t => {
const {message} = await t.throwsAsync(execa('exit.js', ['2']).finally(() => {
throw new Error('called');
}));
t.is(message, 'called');
});
const testNoAwait = async (t, fixtureName, options, message) => {
const {stdout} = await execa('no-await.js', [JSON.stringify(options), fixtureName]);
t.true(stdout.includes(message));
};
test('Throws if promise is not awaited and subprocess fails', testNoAwait, 'fail.js', {}, 'exit code 2');
test('Throws if promise is not awaited and subprocess times out', testNoAwait, 'forever.js', {timeout: 1}, 'timed out');
================================================
FILE: test/methods/script.js
================================================
import test from 'ava';
import {isStream} from 'is-stream';
import {$} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
setFixtureDirectory();
const testScriptStdoutSync = (t, getSubprocess, expectedStdout) => {
const {stdout} = getSubprocess();
t.is(stdout, expectedStdout);
};
test('$.sync`...`', testScriptStdoutSync, () => $.sync`echo.js foo bar`, 'foo\nbar');
test('$.s`...`', testScriptStdoutSync, () => $.s`echo.js foo bar`, 'foo\nbar');
test('$(options).sync`...`', testScriptStdoutSync, () => $({stripFinalNewline: false}).sync`echo.js ${foobarString}`, `${foobarString}\n`);
test('$.sync(options)`...`', testScriptStdoutSync, () => $.sync({stripFinalNewline: false})`echo.js ${foobarString}`, `${foobarString}\n`);
test('Cannot call $.sync.sync', t => {
t.false('sync' in $.sync);
});
test('Cannot call $.sync(options).sync', t => {
t.false('sync' in $.sync({}));
});
test('$(options)() stdin defaults to "inherit"', async t => {
const {stdout} = await $({input: foobarString})('stdin-script.js');
t.is(stdout, foobarString);
});
test('$.sync(options)() stdin defaults to "inherit"', t => {
const {stdout} = $.sync({input: foobarString})('stdin-script.js');
t.is(stdout, foobarString);
});
test('$(options).sync() stdin defaults to "inherit"', t => {
const {stdout} = $({input: foobarString}).sync('stdin-script.js');
t.is(stdout, foobarString);
});
test('$(options)`...` stdin defaults to "inherit"', async t => {
const {stdout} = await $({input: foobarString})`stdin-script.js`;
t.is(stdout, foobarString);
});
test('$.sync(options)`...` stdin defaults to "inherit"', t => {
const {stdout} = $.sync({input: foobarString})`stdin-script.js`;
t.is(stdout, foobarString);
});
test('$(options).sync`...` stdin defaults to "inherit"', t => {
const {stdout} = $({input: foobarString}).sync`stdin-script.js`;
t.is(stdout, foobarString);
});
test('$ stdin has no default value when stdio is set', t => {
t.true(isStream($({stdio: 'pipe'})`noop.js`.stdin));
});
================================================
FILE: test/methods/template.js
================================================
import test from 'ava';
import {$} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
setFixtureDirectory();
// Workaround since some text editors or IDEs do not allow inputting \r directly
const escapedCall = string => {
const templates = [string];
templates.raw = [string];
return $(templates);
};
const testScriptStdout = async (t, getSubprocess, expectedStdout) => {
const {stdout} = await getSubprocess();
t.is(stdout, expectedStdout);
};
test('$ allows number interpolation', testScriptStdout, () => $`echo.js 1 ${2}`, '1\n2');
test('$ can concatenate multiple tokens', testScriptStdout, () => $`echo.js ${'foo'}bar${'foo'}`, 'foobarfoo');
test('$ can use newlines and tab indentations', testScriptStdout, () => $`echo.js foo
bar`, 'foo\nbar');
test('$ can use newlines and space indentations', testScriptStdout, () => $`echo.js foo
bar`, 'foo\nbar');
test('$ can use escaped newlines and space indentations', testScriptStdout, () => $`echo.js foo\
bar`, 'foo\nbar');
test('$ can use escaped newlines and inline tab indentations', testScriptStdout, () => $`echo.js foo\
bar`, 'foo\nbar');
test('$ can use escaped newlines and character escaped tab indentations', testScriptStdout, () => $`echo.js foo\
\tbar`, 'foo\tbar');
test('$ can use escaped newlines and character escaped newlines', testScriptStdout, () => $`echo.js foo\
\n\nbar`, 'foo\n\nbar');
test('$ can use Windows newlines and tab indentations', testScriptStdout, () => escapedCall('echo.js foo\r\n\tbar'), 'foo\nbar');
test('$ can use Windows newlines and space indentations', testScriptStdout, () => escapedCall('echo.js foo\r\n bar'), 'foo\nbar');
test('$ does not ignore comments in expressions', testScriptStdout, () => $`echo.js foo
${/* This is a comment */''}
bar
${/* This is another comment */''}
baz
`, 'foo\n\nbar\n\nbaz');
test('$ allows escaping spaces with interpolation', testScriptStdout, () => $`echo.js ${'foo bar'}`, 'foo bar');
test('$ allows escaping spaces in commands with interpolation', testScriptStdout, () => $`${'command with space.js'} foo bar`, 'foo\nbar');
test('$ trims', testScriptStdout, () => $` echo.js foo bar `, 'foo\nbar');
test('$ allows array interpolation', testScriptStdout, () => $`echo.js ${['foo', 'bar']}`, 'foo\nbar');
test('$ allows empty array interpolation', testScriptStdout, () => $`echo.js foo ${[]} bar`, 'foo\nbar');
test('$ allows space escaped values in array interpolation', testScriptStdout, () => $`echo.js ${['foo', 'bar baz']}`, 'foo\nbar baz');
test('$ can concatenate at the end of tokens followed by an array', testScriptStdout, () => $`echo.js foo${['bar', 'foo']}`, 'foobar\nfoo');
test('$ can concatenate at the start of tokens followed by an array', testScriptStdout, () => $`echo.js ${['foo', 'bar']}foo`, 'foo\nbarfoo');
test('$ can concatenate at the start and end of tokens followed by an array', testScriptStdout, () => $`echo.js foo${['bar', 'foo']}bar`, 'foobar\nfoobar');
test('$ handles escaped newlines', testScriptStdout, () => $`echo.js a\
b`, 'ab');
test('$ handles backslashes at end of lines', testScriptStdout, () => $`echo.js a\\
b`, 'a\\\nb');
test('$ handles double backslashes at end of lines', testScriptStdout, () => $`echo.js a\\\\
b`, 'a\\\\\nb');
test('$ handles tokens - a', testScriptStdout, () => $`echo.js a`, 'a');
test('$ handles expressions - a', testScriptStdout, () => $`echo.js ${'a'}`, 'a');
test('$ handles tokens - abc', testScriptStdout, () => $`echo.js abc`, 'abc');
test('$ handles expressions - abc', testScriptStdout, () => $`echo.js ${'abc'}`, 'abc');
test('$ handles tokens - ""', testScriptStdout, () => $`echo.js`, '');
test('$ handles expressions - ""', testScriptStdout, () => $`echo.js a ${''} b`, 'a\n\nb');
test('$ splits tokens - ""', testScriptStdout, () => $`echo.js ab`, 'ab');
test('$ splits expressions - ""', testScriptStdout, () => $`echo.js ${'a'}${'b'}`, 'ab');
test('$ concatenates expressions - ""', testScriptStdout, () => $`echo.js a${'b'}c`, 'abc');
test('$ handles tokens - " "', testScriptStdout, () => $`echo.js `, '');
test('$ handles expressions - " "', testScriptStdout, () => $`echo.js ${' '}`, ' ');
test('$ splits tokens - " "', testScriptStdout, () => $`echo.js a b`, 'a\nb');
test('$ splits expressions - " "', testScriptStdout, () => $`echo.js ${'a'} ${'b'}`, 'a\nb');
test('$ concatenates tokens - " "', testScriptStdout, () => $`echo.js a `, 'a');
test('$ concatenates expressions - " "', testScriptStdout, () => $`echo.js ${'a'} `, 'a');
test('$ handles tokens - " " (2 spaces)', testScriptStdout, () => $`echo.js `, '');
test('$ handles expressions - " " (2 spaces)', testScriptStdout, () => $`echo.js ${' '}`, ' ');
test('$ splits tokens - " " (2 spaces)', testScriptStdout, () => $`echo.js a b`, 'a\nb');
test('$ splits expressions - " " (2 spaces)', testScriptStdout, () => $`echo.js ${'a'} ${'b'}`, 'a\nb');
test('$ concatenates tokens - " " (2 spaces)', testScriptStdout, () => $`echo.js a `, 'a');
test('$ concatenates expressions - " " (2 spaces)', testScriptStdout, () => $`echo.js ${'a'} `, 'a');
test('$ handles tokens - " " (3 spaces)', testScriptStdout, () => $`echo.js `, '');
test('$ handles expressions - " " (3 spaces)', testScriptStdout, () => $`echo.js ${' '}`, ' ');
test('$ splits tokens - " " (3 spaces)', testScriptStdout, () => $`echo.js a b`, 'a\nb');
test('$ splits expressions - " " (3 spaces)', testScriptStdout, () => $`echo.js ${'a'} ${'b'}`, 'a\nb');
test('$ concatenates tokens - " " (3 spaces)', testScriptStdout, () => $`echo.js a `, 'a');
test('$ concatenates expressions - " " (3 spaces)', testScriptStdout, () => $`echo.js ${'a'} `, 'a');
test('$ handles tokens - \\t (no escape)', testScriptStdout, () => $`echo.js `, '');
test('$ handles expressions - \\t (no escape)', testScriptStdout, () => $`echo.js ${' '}`, '\t');
test('$ splits tokens - \\t (no escape)', testScriptStdout, () => $`echo.js a b`, 'a\nb');
test('$ splits expressions - \\t (no escape)', testScriptStdout, () => $`echo.js ${'a'} ${'b'}`, 'a\nb');
test('$ concatenates tokens - \\t (no escape)', testScriptStdout, () => $`echo.js a b`, 'a\nb');
test('$ concatenates expressions - \\t (no escape)', testScriptStdout, () => $`echo.js ${'a'} b`, 'a\nb');
test('$ handles tokens - \\t (escape)', testScriptStdout, () => $`echo.js \t`, '\t');
test('$ handles expressions - \\t (escape)', testScriptStdout, () => $`echo.js ${'\t'}`, '\t');
test('$ splits tokens - \\t (escape)', testScriptStdout, () => $`echo.js a\tb`, 'a\tb');
test('$ splits expressions - \\t (escape)', testScriptStdout, () => $`echo.js ${'a'}\t${'b'}`, 'a\tb');
test('$ concatenates tokens - \\t (escape)', testScriptStdout, () => $`echo.js \ta\t b`, '\ta\t\nb');
test('$ concatenates expressions - \\t (escape)', testScriptStdout, () => $`echo.js \t${'a'}\t b`, '\ta\t\nb');
test('$ handles tokens - \\n (no escape)', testScriptStdout, () => $`echo.js
`, '');
test('$ handles expressions - \\n (no escape)', testScriptStdout, () => $`echo.js ${`
`} `, '\n');
test('$ splits tokens - \\n (no escape)', testScriptStdout, () => $`echo.js a
b`, 'a\nb');
test('$ splits expressions - \\n (no escape)', testScriptStdout, () => $`echo.js ${'a'}
${'b'}`, 'a\nb');
test('$ concatenates tokens - \\n (no escape)', testScriptStdout, () => $`echo.js
a
b`, 'a\nb');
test('$ concatenates expressions - \\n (no escape)', testScriptStdout, () => $`echo.js
${'a'}
b`, 'a\nb');
test('$ handles tokens - \\n (escape)', testScriptStdout, () => $`echo.js \n `, '\n');
test('$ handles expressions - \\n (escape)', testScriptStdout, () => $`echo.js ${'\n'} `, '\n');
test('$ splits tokens - \\n (escape)', testScriptStdout, () => $`echo.js a\n b`, 'a\n\nb');
test('$ splits expressions - \\n (escape)', testScriptStdout, () => $`echo.js ${'a'}\n ${'b'}`, 'a\n\nb');
test('$ concatenates tokens - \\n (escape)', testScriptStdout, () => $`echo.js \na\n b`, '\na\n\nb');
test('$ concatenates expressions - \\n (escape)', testScriptStdout, () => $`echo.js \n${'a'}\n b`, '\na\n\nb');
test('$ handles tokens - \\r (no escape)', testScriptStdout, () => escapedCall('echo.js \r '), '');
test('$ splits tokens - \\r (no escape)', testScriptStdout, () => escapedCall('echo.js a\rb'), 'a\nb');
test('$ splits expressions - \\r (no escape)', testScriptStdout, () => escapedCall(`echo.js ${'a'}\r${'b'}`), 'a\nb');
test('$ concatenates tokens - \\r (no escape)', testScriptStdout, () => escapedCall('echo.js \ra\r b'), 'a\nb');
test('$ concatenates expressions - \\r (no escape)', testScriptStdout, () => escapedCall(`echo.js \r${'a'}\r b`), 'a\nb');
test('$ splits tokens - \\r (escape)', testScriptStdout, () => $`echo.js a\r b`, 'a\r\nb');
test('$ splits expressions - \\r (escape)', testScriptStdout, () => $`echo.js ${'a'}\r ${'b'}`, 'a\r\nb');
test('$ concatenates tokens - \\r (escape)', testScriptStdout, () => $`echo.js \ra\r b`, '\ra\r\nb');
test('$ concatenates expressions - \\r (escape)', testScriptStdout, () => $`echo.js \r${'a'}\r b`, '\ra\r\nb');
test('$ handles tokens - \\r\\n (no escape)', testScriptStdout, () => escapedCall('echo.js \r\n '), '');
test('$ splits tokens - \\r\\n (no escape)', testScriptStdout, () => escapedCall('echo.js a\r\nb'), 'a\nb');
test('$ splits expressions - \\r\\n (no escape)', testScriptStdout, () => escapedCall(`echo.js ${'a'}\r\n${'b'}`), 'a\nb');
test('$ concatenates tokens - \\r\\n (no escape)', testScriptStdout, () => escapedCall('echo.js \r\na\r\n b'), 'a\nb');
test('$ concatenates expressions - \\r\\n (no escape)', testScriptStdout, () => escapedCall(`echo.js \r\n${'a'}\r\n b`), 'a\nb');
test('$ handles tokens - \\r\\n (escape)', testScriptStdout, () => $`echo.js \r\n `, '\r\n');
test('$ handles expressions - \\r\\n (escape)', testScriptStdout, () => $`echo.js ${'\r\n'} `, '\r\n');
test('$ splits tokens - \\r\\n (escape)', testScriptStdout, () => $`echo.js a\r\n b`, 'a\r\n\nb');
test('$ splits expressions - \\r\\n (escape)', testScriptStdout, () => $`echo.js ${'a'}\r\n ${'b'}`, 'a\r\n\nb');
test('$ concatenates tokens - \\r\\n (escape)', testScriptStdout, () => $`echo.js \r\na\r\n b`, '\r\na\r\n\nb');
test('$ concatenates expressions - \\r\\n (escape)', testScriptStdout, () => $`echo.js \r\n${'a'}\r\n b`, '\r\na\r\n\nb');
/* eslint-disable no-irregular-whitespace */
test('$ handles expressions - \\f (no escape)', testScriptStdout, () => $`echo.js ${''}`, '\f');
test('$ splits tokens - \\f (no escape)', testScriptStdout, () => $`echo.js ab`, 'a\fb');
test('$ splits expressions - \\f (no escape)', testScriptStdout, () => $`echo.js ${'a'}${'b'}`, 'a\fb');
test('$ concatenates tokens - \\f (no escape)', testScriptStdout, () => $`echo.js a b`, '\fa\f\nb');
test('$ concatenates expressions - \\f (no escape)', testScriptStdout, () => $`echo.js ${'a'} b`, '\fa\f\nb');
/* eslint-enable no-irregular-whitespace */
test('$ handles tokens - \\f (escape)', testScriptStdout, () => $`echo.js \f`, '\f');
test('$ handles expressions - \\f (escape)', testScriptStdout, () => $`echo.js ${'\f'}`, '\f');
test('$ splits tokens - \\f (escape)', testScriptStdout, () => $`echo.js a\fb`, 'a\fb');
test('$ splits expressions - \\f (escape)', testScriptStdout, () => $`echo.js ${'a'}\f${'b'}`, 'a\fb');
test('$ concatenates tokens - \\f (escape)', testScriptStdout, () => $`echo.js \fa\f b`, '\fa\f\nb');
test('$ concatenates expressions - \\f (escape)', testScriptStdout, () => $`echo.js \f${'a'}\f b`, '\fa\f\nb');
test('$ handles tokens - \\', testScriptStdout, () => $`echo.js \\`, '\\');
test('$ handles expressions - \\', testScriptStdout, () => $`echo.js ${'\\'}`, '\\');
test('$ splits tokens - \\', testScriptStdout, () => $`echo.js a\\b`, 'a\\b');
test('$ splits expressions - \\', testScriptStdout, () => $`echo.js ${'a'}\\${'b'}`, 'a\\b');
test('$ concatenates tokens - \\', testScriptStdout, () => $`echo.js \\a\\ b`, '\\a\\\nb');
test('$ concatenates expressions - \\', testScriptStdout, () => $`echo.js \\${'a'}\\ b`, '\\a\\\nb');
test('$ handles tokens - \\\\', testScriptStdout, () => $`echo.js \\\\`, '\\\\');
test('$ handles expressions - \\\\', testScriptStdout, () => $`echo.js ${'\\\\'}`, '\\\\');
test('$ splits tokens - \\\\', testScriptStdout, () => $`echo.js a\\\\b`, 'a\\\\b');
test('$ splits expressions - \\\\', testScriptStdout, () => $`echo.js ${'a'}\\\\${'b'}`, 'a\\\\b');
test('$ concatenates tokens - \\\\', testScriptStdout, () => $`echo.js \\\\a\\\\ b`, '\\\\a\\\\\nb');
test('$ concatenates expressions - \\\\', testScriptStdout, () => $`echo.js \\\\${'a'}\\\\ b`, '\\\\a\\\\\nb');
test('$ handles tokens - `', testScriptStdout, () => $`echo.js \``, '`');
test('$ handles expressions - `', testScriptStdout, () => $`echo.js ${'`'}`, '`');
test('$ splits tokens - `', testScriptStdout, () => $`echo.js a\`b`, 'a`b');
test('$ splits expressions - `', testScriptStdout, () => $`echo.js ${'a'}\`${'b'}`, 'a`b');
test('$ concatenates tokens - `', testScriptStdout, () => $`echo.js \`a\` b`, '`a`\nb');
test('$ concatenates expressions - `', testScriptStdout, () => $`echo.js \`${'a'}\` b`, '`a`\nb');
test('$ handles tokens - \\v', testScriptStdout, () => $`echo.js \v`, '\v');
test('$ handles expressions - \\v', testScriptStdout, () => $`echo.js ${'\v'}`, '\v');
test('$ splits tokens - \\v', testScriptStdout, () => $`echo.js a\vb`, 'a\vb');
test('$ splits expressions - \\v', testScriptStdout, () => $`echo.js ${'a'}\v${'b'}`, 'a\vb');
test('$ concatenates tokens - \\v', testScriptStdout, () => $`echo.js \va\v b`, '\va\v\nb');
test('$ concatenates expressions - \\v', testScriptStdout, () => $`echo.js \v${'a'}\v b`, '\va\v\nb');
test('$ handles tokens - \\u2028', testScriptStdout, () => $`echo.js \u2028`, '\u2028');
test('$ handles expressions - \\u2028', testScriptStdout, () => $`echo.js ${'\u2028'}`, '\u2028');
test('$ splits tokens - \\u2028', testScriptStdout, () => $`echo.js a\u2028b`, 'a\u2028b');
test('$ splits expressions - \\u2028', testScriptStdout, () => $`echo.js ${'a'}\u2028${'b'}`, 'a\u2028b');
test('$ concatenates tokens - \\u2028', testScriptStdout, () => $`echo.js \u2028a\u2028 b`, '\u2028a\u2028\nb');
test('$ concatenates expressions - \\u2028', testScriptStdout, () => $`echo.js \u2028${'a'}\u2028 b`, '\u2028a\u2028\nb');
test('$ handles tokens - \\a', testScriptStdout, () => $`echo.js \a`, 'a');
test('$ splits tokens - \\a', testScriptStdout, () => $`echo.js a\ab`, 'aab');
test('$ splits expressions - \\a', testScriptStdout, () => $`echo.js ${'a'}\a${'b'}`, 'aab');
test('$ concatenates tokens - \\a', testScriptStdout, () => $`echo.js \aa\a b`, 'aaa\nb');
test('$ concatenates expressions - \\a', testScriptStdout, () => $`echo.js \a${'a'}\a b`, 'aaa\nb');
test('$ handles tokens - \\cJ', testScriptStdout, () => $`echo.js \cJ`, 'cJ');
test('$ splits tokens - \\cJ', testScriptStdout, () => $`echo.js a\cJb`, 'acJb');
test('$ splits expressions - \\cJ', testScriptStdout, () => $`echo.js ${'a'}\cJ${'b'}`, 'acJb');
test('$ concatenates tokens - \\cJ', testScriptStdout, () => $`echo.js \cJa\cJ b`, 'cJacJ\nb');
test('$ concatenates expressions - \\cJ', testScriptStdout, () => $`echo.js \cJ${'a'}\cJ b`, 'cJacJ\nb');
test('$ handles tokens - \\.', testScriptStdout, () => $`echo.js \.`, '.');
test('$ splits tokens - \\.', testScriptStdout, () => $`echo.js a\.b`, 'a.b');
test('$ splits expressions - \\.', testScriptStdout, () => $`echo.js ${'a'}\.${'b'}`, 'a.b');
test('$ concatenates tokens - \\.', testScriptStdout, () => $`echo.js \.a\. b`, '.a.\nb');
test('$ concatenates expressions - \\.', testScriptStdout, () => $`echo.js \.${'a'}\. b`, '.a.\nb');
/* eslint-disable unicorn/no-hex-escape */
test('$ handles tokens - \\x63', testScriptStdout, () => $`echo.js \x63`, 'c');
test('$ splits tokens - \\x63', testScriptStdout, () => $`echo.js a\x63b`, 'acb');
test('$ splits expressions - \\x63', testScriptStdout, () => $`echo.js ${'a'}\x63${'b'}`, 'acb');
test('$ concatenates tokens - \\x63', testScriptStdout, () => $`echo.js \x63a\x63 b`, 'cac\nb');
test('$ concatenates expressions - \\x63', testScriptStdout, () => $`echo.js \x63${'a'}\x63 b`, 'cac\nb');
/* eslint-enable unicorn/no-hex-escape */
test('$ handles tokens - \\u0063', testScriptStdout, () => $`echo.js \u0063`, 'c');
test('$ splits tokens - \\u0063', testScriptStdout, () => $`echo.js a\u0063b`, 'acb');
test('$ splits expressions - \\u0063', testScriptStdout, () => $`echo.js ${'a'}\u0063${'b'}`, 'acb');
test('$ concatenates tokens - \\u0063', testScriptStdout, () => $`echo.js \u0063a\u0063 b`, 'cac\nb');
test('$ concatenates expressions - \\u0063', testScriptStdout, () => $`echo.js \u0063${'a'}\u0063 b`, 'cac\nb');
test('$ handles tokens - \\u{1}', testScriptStdout, () => $`echo.js \u{1}`, '\u0001');
test('$ splits tokens - \\u{1}', testScriptStdout, () => $`echo.js a\u{1}b`, 'a\u0001b');
test('$ splits expressions - \\u{1}', testScriptStdout, () => $`echo.js ${'a'}\u{1}${'b'}`, 'a\u0001b');
test('$ concatenates tokens - \\u{1}', testScriptStdout, () => $`echo.js \u{1}a\u{1} b`, '\u0001a\u0001\nb');
test('$ concatenates expressions - \\u{1}', testScriptStdout, () => $`echo.js \u{1}${'a'}\u{1} b`, '\u0001a\u0001\nb');
test('$ handles tokens - \\u{63}', testScriptStdout, () => $`echo.js \u{63}`, 'c');
test('$ splits tokens - \\u{63}', testScriptStdout, () => $`echo.js a\u{63}b`, 'acb');
test('$ splits expressions - \\u{63}', testScriptStdout, () => $`echo.js ${'a'}\u{63}${'b'}`, 'acb');
test('$ concatenates tokens - \\u{63}', testScriptStdout, () => $`echo.js \u{63}a\u{63} b`, 'cac\nb');
test('$ concatenates expressions - \\u{63}', testScriptStdout, () => $`echo.js \u{63}${'a'}\u{63} b`, 'cac\nb');
test('$ handles tokens - \\u{063}', testScriptStdout, () => $`echo.js \u{063}`, 'c');
test('$ splits tokens - \\u{063}', testScriptStdout, () => $`echo.js a\u{063}b`, 'acb');
test('$ splits expressions - \\u{063}', testScriptStdout, () => $`echo.js ${'a'}\u{063}${'b'}`, 'acb');
test('$ concatenates tokens - \\u{063}', testScriptStdout, () => $`echo.js \u{063}a\u{063} b`, 'cac\nb');
test('$ concatenates expressions - \\u{063}', testScriptStdout, () => $`echo.js \u{063}${'a'}\u{063} b`, 'cac\nb');
test('$ handles tokens - \\u{0063}', testScriptStdout, () => $`echo.js \u{0063}`, 'c');
test('$ splits tokens - \\u{0063}', testScriptStdout, () => $`echo.js a\u{0063}b`, 'acb');
test('$ splits expressions - \\u{0063}', testScriptStdout, () => $`echo.js ${'a'}\u{0063}${'b'}`, 'acb');
test('$ concatenates tokens - \\u{0063}', testScriptStdout, () => $`echo.js \u{0063}a\u{0063} b`, 'cac\nb');
test('$ concatenates expressions - \\u{0063}', testScriptStdout, () => $`echo.js \u{0063}${'a'}\u{0063} b`, 'cac\nb');
test('$ handles tokens - \\u{00063}', testScriptStdout, () => $`echo.js \u{00063}`, 'c');
test('$ splits tokens - \\u{00063}', testScriptStdout, () => $`echo.js a\u{00063}b`, 'acb');
test('$ splits expressions - \\u{00063}', testScriptStdout, () => $`echo.js ${'a'}\u{00063}${'b'}`, 'acb');
test('$ concatenates tokens - \\u{00063}', testScriptStdout, () => $`echo.js \u{00063}a\u{00063} b`, 'cac\nb');
test('$ concatenates expressions - \\u{00063}', testScriptStdout, () => $`echo.js \u{00063}${'a'}\u{00063} b`, 'cac\nb');
test('$ handles tokens - \\u{000063}', testScriptStdout, () => $`echo.js \u{000063}`, 'c');
test('$ splits tokens - \\u{000063}', testScriptStdout, () => $`echo.js a\u{000063}b`, 'acb');
test('$ splits expressions - \\u{000063}', testScriptStdout, () => $`echo.js ${'a'}\u{000063}${'b'}`, 'acb');
test('$ concatenates tokens - \\u{000063}', testScriptStdout, () => $`echo.js \u{000063}a\u{000063} b`, 'cac\nb');
test('$ concatenates expressions - \\u{000063}', testScriptStdout, () => $`echo.js \u{000063}${'a'}\u{000063} b`, 'cac\nb');
test('$ handles tokens - \\u{0000063}', testScriptStdout, () => $`echo.js \u{0000063}`, 'c');
test('$ splits tokens - \\u{0000063}', testScriptStdout, () => $`echo.js a\u{0000063}b`, 'acb');
test('$ splits expressions - \\u{0000063}', testScriptStdout, () => $`echo.js ${'a'}\u{0000063}${'b'}`, 'acb');
test('$ concatenates tokens - \\u{0000063}', testScriptStdout, () => $`echo.js \u{0000063}a\u{0000063} b`, 'cac\nb');
test('$ concatenates expressions - \\u{0000063}', testScriptStdout, () => $`echo.js \u{0000063}${'a'}\u{0000063} b`, 'cac\nb');
test('$ handles tokens - \\u{0063}}', testScriptStdout, () => $`echo.js \u{0063}}`, 'c}');
test('$ splits tokens - \\u{0063}}', testScriptStdout, () => $`echo.js a\u{0063}}b`, 'ac}b');
test('$ splits expressions - \\u{0063}}', testScriptStdout, () => $`echo.js ${'a'}\u{0063}}${'b'}`, 'ac}b');
test('$ concatenates tokens - \\u{0063}}', testScriptStdout, () => $`echo.js \u{0063}}a\u{0063}} b`, 'c}ac}\nb');
test('$ concatenates expressions - \\u{0063}}', testScriptStdout, () => $`echo.js \u{0063}}${'a'}\u{0063}} b`, 'c}ac}\nb');
const testScriptErrorStdout = async (t, getSubprocess) => {
t.throws(getSubprocess, {message: /null bytes/});
};
test('$ handles tokens - \\0', testScriptErrorStdout, () => $`echo.js \0`);
test('$ splits tokens - \\0', testScriptErrorStdout, () => $`echo.js a\0b`);
test('$ splits expressions - \\0', testScriptErrorStdout, () => $`echo.js ${'a'}\0${'b'}`);
test('$ concatenates tokens - \\0', testScriptErrorStdout, () => $`echo.js \0a\0 b`);
test('$ concatenates expressions - \\0', testScriptErrorStdout, () => $`echo.js \0${'a'}\0 b`);
const testReturnInterpolate = async (t, getSubprocess, expectedStdout, options = {}) => {
const foo = await $(options)`echo.js foo`;
const {stdout} = await getSubprocess(foo);
t.is(stdout, expectedStdout);
};
test('$ allows execa return value interpolation', testReturnInterpolate, foo => $`echo.js ${foo} bar`, 'foo\nbar');
test('$ allows execa return value buffer interpolation', testReturnInterpolate, foo => $`echo.js ${foo} bar`, 'foo\nbar', {encoding: 'buffer'});
test('$ allows execa return value array interpolation', testReturnInterpolate, foo => $`echo.js ${[foo, 'bar']}`, 'foo\nbar');
test('$ allows execa return value buffer array interpolation', testReturnInterpolate, foo => $`echo.js ${[foo, 'bar']}`, 'foo\nbar', {encoding: 'buffer'});
const testReturnInterpolateSync = (t, getSubprocess, expectedStdout, options = {}) => {
const foo = $(options).sync`echo.js foo`;
const {stdout} = getSubprocess(foo);
t.is(stdout, expectedStdout);
};
test('$.sync allows execa return value interpolation', testReturnInterpolateSync, foo => $.sync`echo.js ${foo} bar`, 'foo\nbar');
test('$.sync allows execa return value buffer interpolation', testReturnInterpolateSync, foo => $.sync`echo.js ${foo} bar`, 'foo\nbar', {encoding: 'buffer'});
test('$.sync allows execa return value array interpolation', testReturnInterpolateSync, foo => $.sync`echo.js ${[foo, 'bar']}`, 'foo\nbar');
test('$.sync allows execa return value buffer array interpolation', testReturnInterpolateSync, foo => $.sync`echo.js ${[foo, 'bar']}`, 'foo\nbar', {encoding: 'buffer'});
const testInvalidSequence = (t, getSubprocess) => {
t.throws(getSubprocess, {message: /Invalid backslash sequence/});
};
test('$ handles invalid escape sequence - \\1', testInvalidSequence, () => $`echo.js \1`);
test('$ handles invalid escape sequence - \\u', testInvalidSequence, () => $`echo.js \u`);
test('$ handles invalid escape sequence - \\u0', testInvalidSequence, () => $`echo.js \u0`);
test('$ handles invalid escape sequence - \\u00', testInvalidSequence, () => $`echo.js \u00`);
test('$ handles invalid escape sequence - \\u000', testInvalidSequence, () => $`echo.js \u000`);
test('$ handles invalid escape sequence - \\ug', testInvalidSequence, () => $`echo.js \ug`);
test('$ handles invalid escape sequence - \\u{', testInvalidSequence, () => $`echo.js \u{`);
test('$ handles invalid escape sequence - \\u{0000', testInvalidSequence, () => $`echo.js \u{0000`);
test('$ handles invalid escape sequence - \\u{g}', testInvalidSequence, () => $`echo.js \u{g}`);
/* eslint-disable unicorn/no-hex-escape */
test('$ handles invalid escape sequence - \\x', testInvalidSequence, () => $`echo.js \x`);
test('$ handles invalid escape sequence - \\x0', testInvalidSequence, () => $`echo.js \x0`);
test('$ handles invalid escape sequence - \\xgg', testInvalidSequence, () => $`echo.js \xgg`);
/* eslint-enable unicorn/no-hex-escape */
const testEmptyScript = (t, getSubprocess) => {
t.throws(getSubprocess, {message: /Template script must not be empty/});
};
test('$``', testEmptyScript, () => $``);
test('$` `', testEmptyScript, () => $` `);
test('$` ` (2 spaces)', testEmptyScript, () => $` `);
test('$`\\t`', testEmptyScript, () => $` `);
test('$`\\n`', testEmptyScript, () => $`
`);
const testInvalidExpression = (t, invalidExpression) => {
t.throws(
() => $`echo.js ${invalidExpression}`,
{message: /in template expression/},
);
};
test('$ throws on invalid expression - undefined', testInvalidExpression, undefined);
test('$ throws on invalid expression - null', testInvalidExpression, null);
test('$ throws on invalid expression - true', testInvalidExpression, true);
test('$ throws on invalid expression - {}', testInvalidExpression, {});
test('$ throws on invalid expression - {foo: "bar"}', testInvalidExpression, {foo: 'bar'});
test('$ throws on invalid expression - {stdout: 1}', testInvalidExpression, {stdout: 1});
test('$ throws on invalid expression - [undefined]', testInvalidExpression, [undefined]);
test('$ throws on invalid expression - [null]', testInvalidExpression, [null]);
test('$ throws on invalid expression - [true]', testInvalidExpression, [true]);
test('$ throws on invalid expression - [{}]', testInvalidExpression, [{}]);
test('$ throws on invalid expression - [{foo: "bar"}]', testInvalidExpression, [{foo: 'bar'}]);
test('$ throws on invalid expression - [{stdout: 1}]', testInvalidExpression, [{stdout: 1}]);
const testMissingOutput = (t, invalidExpression) => {
t.throws(
() => $`echo.js ${invalidExpression()}`,
{message: /Missing result.stdout/},
);
};
test('$ throws on invalid expression - {stdout: undefined}', testMissingOutput, () => ({stdout: undefined}));
test('$ throws on invalid expression - [{stdout: undefined}]', testMissingOutput, () => [{stdout: undefined}]);
test('$ throws on invalid expression - $(options).sync', testMissingOutput, () => $({stdio: 'ignore'}).sync`noop.js`);
test('$ throws on invalid expression - [$(options).sync]', testMissingOutput, () => [$({stdio: 'ignore'}).sync`noop.js`]);
const testInvalidPromise = (t, invalidExpression) => {
t.throws(
() => $`echo.js ${invalidExpression()}`,
{message: /Please use \${await subprocess}/},
);
};
test('$ throws on invalid expression - Promise.resolve()', testInvalidPromise, async () => ({}));
test('$ throws on invalid expression - Promise.resolve({stdout: "foo"})', testInvalidPromise, async () => ({foo: 'bar'}));
test('$ throws on invalid expression - [Promise.resolve()]', testInvalidPromise, () => [Promise.resolve()]);
test('$ throws on invalid expression - [Promise.resolve({stdout: "foo"})]', testInvalidPromise, () => [Promise.resolve({stdout: 'foo'})]);
test('$ throws on invalid expression - $', testInvalidPromise, () => $`noop.js`);
test('$ throws on invalid expression - [$]', testInvalidPromise, () => [$`noop.js`]);
================================================
FILE: test/pipe/abort.js
================================================
import {once} from 'node:events';
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
setFixtureDirectory();
const assertUnPipeError = async (t, pipePromise) => {
const error = await t.throwsAsync(pipePromise);
t.is(error.command, 'source.pipe(destination)');
t.is(error.escapedCommand, error.command);
t.is(typeof error.cwd, 'string');
t.true(error.failed);
t.false(error.timedOut);
t.false(error.isCanceled);
t.false(error.isTerminated);
t.is(error.exitCode, undefined);
t.is(error.signal, undefined);
t.is(error.signalDescription, undefined);
t.is(error.stdout, undefined);
t.is(error.stderr, undefined);
t.is(error.all, undefined);
t.deepEqual(error.stdio, Array.from({length: error.stdio.length}));
t.deepEqual(error.pipedFrom, []);
t.true(error.originalMessage.includes('Pipe canceled'));
t.true(error.shortMessage.includes(`Command failed: ${error.command}`));
t.true(error.shortMessage.includes(error.originalMessage));
t.true(error.message.includes(error.shortMessage));
};
test('Can unpipe a single subprocess', async t => {
const abortController = new AbortController();
const source = execa('stdin.js');
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination, {unpipeSignal: abortController.signal});
abortController.abort();
await assertUnPipeError(t, pipePromise);
source.stdin.end(foobarString);
destination.stdin.end('.');
t.like(await destination, {stdout: '.'});
t.like(await source, {stdout: foobarString});
});
test('Can use an already aborted signal', async t => {
const abortController = new AbortController();
abortController.abort();
const source = execa('empty.js');
const destination = execa('empty.js');
const pipePromise = source.pipe(destination, {unpipeSignal: abortController.signal});
await assertUnPipeError(t, pipePromise);
});
test('Can unpipe a subprocess among other sources', async t => {
const abortController = new AbortController();
const source = execa('stdin.js');
const secondSource = execa('noop.js', [foobarString]);
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination, {unpipeSignal: abortController.signal});
const secondPipePromise = secondSource.pipe(destination);
abortController.abort();
await assertUnPipeError(t, pipePromise);
source.stdin.end('.');
t.is(await secondPipePromise, await destination);
t.like(await destination, {stdout: foobarString});
t.like(await source, {stdout: '.'});
t.like(await secondSource, {stdout: foobarString});
});
test('Can unpipe a subprocess among other sources on the same subprocess', async t => {
const abortController = new AbortController();
const source = execa('stdin-both.js');
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination, {unpipeSignal: abortController.signal});
const secondPipePromise = source.pipe(destination, {from: 'stderr'});
abortController.abort();
await assertUnPipeError(t, pipePromise);
source.stdin.end(foobarString);
t.is(await secondPipePromise, await destination);
t.like(await destination, {stdout: foobarString});
t.like(await source, {stdout: foobarString, stderr: foobarString});
});
test('Can unpipe a subprocess among other destinations', async t => {
const abortController = new AbortController();
const source = execa('stdin.js');
const destination = execa('stdin.js');
const secondDestination = execa('stdin.js');
const pipePromise = source.pipe(destination, {unpipeSignal: abortController.signal});
const secondPipePromise = source.pipe(secondDestination);
abortController.abort();
await assertUnPipeError(t, pipePromise);
source.stdin.end(foobarString);
destination.stdin.end('.');
t.is(await secondPipePromise, await secondDestination);
t.like(await destination, {stdout: '.'});
t.like(await source, {stdout: foobarString});
t.like(await secondDestination, {stdout: foobarString});
});
test('Can unpipe then re-pipe a subprocess', async t => {
const abortController = new AbortController();
const source = execa('stdin.js');
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination, {unpipeSignal: abortController.signal});
source.stdin.write('.');
const [firstWrite] = await once(source.stdout, 'data');
t.is(firstWrite.toString(), '.');
abortController.abort();
await assertUnPipeError(t, pipePromise);
source.pipe(destination);
source.stdin.end('.');
t.like(await destination, {stdout: '..'});
t.like(await source, {stdout: '..'});
});
test('Can unpipe to prevent termination to propagate to source', async t => {
const abortController = new AbortController();
const source = execa('stdin.js');
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination, {unpipeSignal: abortController.signal});
abortController.abort();
await assertUnPipeError(t, pipePromise);
destination.kill();
t.like(await t.throwsAsync(destination), {signal: 'SIGTERM'});
source.stdin.end(foobarString);
t.like(await source, {stdout: foobarString});
});
test('Can unpipe to prevent termination to propagate to destination', async t => {
const abortController = new AbortController();
const source = execa('noop-forever.js', [foobarString]);
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination, {unpipeSignal: abortController.signal});
abortController.abort();
await assertUnPipeError(t, pipePromise);
source.kill();
t.like(await t.throwsAsync(source), {signal: 'SIGTERM'});
destination.stdin.end(foobarString);
t.like(await destination, {stdout: foobarString});
});
================================================
FILE: test/pipe/pipe-arguments.js
================================================
import {spawn} from 'node:child_process';
import {pathToFileURL} from 'node:url';
import test from 'ava';
import {$, execa} from '../../index.js';
import {setFixtureDirectory, FIXTURES_DIRECTORY} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
import {getDenoNodePath} from '../helpers/file-path.js';
setFixtureDirectory();
test('$.pipe(subprocess)', async t => {
const {stdout} = await $`noop.js ${foobarString}`.pipe($({stdin: 'pipe'})`stdin.js`);
t.is(stdout, foobarString);
});
test('execa.$.pipe(subprocess)', async t => {
const {stdout} = await execa('noop.js', [foobarString]).pipe($({stdin: 'pipe'})`stdin.js`);
t.is(stdout, foobarString);
});
test('$.pipe.pipe(subprocess)', async t => {
const {stdout} = await $`noop.js ${foobarString}`
.pipe($({stdin: 'pipe'})`stdin.js`)
.pipe($({stdin: 'pipe'})`stdin.js`);
t.is(stdout, foobarString);
});
test('$.pipe`command`', async t => {
const {stdout} = await $`noop.js ${foobarString}`.pipe`stdin.js`;
t.is(stdout, foobarString);
});
test('execa.$.pipe`command`', async t => {
const {stdout} = await execa('noop.js', [foobarString]).pipe`stdin.js`;
t.is(stdout, foobarString);
});
test('$.pipe.pipe`command`', async t => {
const {stdout} = await $`noop.js ${foobarString}`
.pipe`stdin.js`
.pipe`stdin.js`;
t.is(stdout, foobarString);
});
test('$.pipe("file")', async t => {
const {stdout} = await $`noop.js ${foobarString}`.pipe('stdin.js');
t.is(stdout, foobarString);
});
test('execa.$.pipe("file")`', async t => {
const {stdout} = await execa('noop.js', [foobarString]).pipe('stdin.js');
t.is(stdout, foobarString);
});
test('$.pipe.pipe("file")', async t => {
const {stdout} = await $`noop.js ${foobarString}`
.pipe`stdin.js`
.pipe('stdin.js');
t.is(stdout, foobarString);
});
test('execa.$.pipe(fileUrl)`', async t => {
const {stdout} = await execa('noop.js', [foobarString]).pipe(pathToFileURL(`${FIXTURES_DIRECTORY}/stdin.js`));
t.is(stdout, foobarString);
});
test('$.pipe("file", commandArguments, options)', async t => {
const {stdout} = await $`noop.js ${foobarString}`.pipe('node', ['stdin.js'], {cwd: FIXTURES_DIRECTORY});
t.is(stdout, foobarString);
});
test('execa.$.pipe("file", commandArguments, options)`', async t => {
const {stdout} = await execa('noop.js', [foobarString]).pipe('node', ['stdin.js'], {cwd: FIXTURES_DIRECTORY});
t.is(stdout, foobarString);
});
test('execa.$.pipe("file", commandArguments, options with denoNodePath)`', async t => {
const {stdout} = await execa('noop.js', [foobarString]).pipe('node', ['stdin.js'], {cwd: FIXTURES_DIRECTORY, nodePath: getDenoNodePath()});
t.is(stdout, foobarString);
});
test('execa.$.pipe("file", commandArguments, denoNodePath)`', async t => {
const {stdout} = await execa('noop.js', [foobarString]).pipe(getDenoNodePath(), ['stdin.js'], {cwd: FIXTURES_DIRECTORY});
t.is(stdout, foobarString);
});
test('$.pipe.pipe("file", commandArguments, options)', async t => {
const {stdout} = await $`noop.js ${foobarString}`
.pipe`stdin.js`
.pipe('node', ['stdin.js'], {cwd: FIXTURES_DIRECTORY});
t.is(stdout, foobarString);
});
test('$.pipe(subprocess, pipeOptions)', async t => {
const {stdout} = await $`noop-fd.js 2 ${foobarString}`.pipe($({stdin: 'pipe'})`stdin.js`, {from: 'stderr'});
t.is(stdout, foobarString);
});
test('execa.$.pipe(subprocess, pipeOptions)', async t => {
const {stdout} = await execa('noop-fd.js', ['2', foobarString]).pipe($({stdin: 'pipe'})`stdin.js`, {from: 'stderr'});
t.is(stdout, foobarString);
});
test('$.pipe.pipe(subprocess, pipeOptions)', async t => {
const {stdout} = await $`noop-fd.js 2 ${foobarString}`
.pipe($({stdin: 'pipe'})`noop-stdin-fd.js 2`, {from: 'stderr'})
.pipe($({stdin: 'pipe'})`stdin.js`, {from: 'stderr'});
t.is(stdout, foobarString);
});
test('$.pipe(pipeOptions)`command`', async t => {
const {stdout} = await $`noop-fd.js 2 ${foobarString}`.pipe({from: 'stderr'})`stdin.js`;
t.is(stdout, foobarString);
});
test('execa.$.pipe(pipeOptions)`command`', async t => {
const {stdout} = await execa('noop-fd.js', ['2', foobarString]).pipe({from: 'stderr'})`stdin.js`;
t.is(stdout, foobarString);
});
test('$.pipe.pipe(pipeOptions)`command`', async t => {
const {stdout} = await $`noop-fd.js 2 ${foobarString}`
.pipe({from: 'stderr'})`noop-stdin-fd.js 2`
.pipe({from: 'stderr'})`stdin.js`;
t.is(stdout, foobarString);
});
test('$.pipe("file", pipeOptions)', async t => {
const {stdout} = await $`noop-fd.js 2 ${foobarString}`.pipe('stdin.js', {from: 'stderr'});
t.is(stdout, foobarString);
});
test('execa.$.pipe("file", pipeOptions)', async t => {
const {stdout} = await execa('noop-fd.js', ['2', foobarString]).pipe('stdin.js', {from: 'stderr'});
t.is(stdout, foobarString);
});
test('$.pipe.pipe("file", pipeOptions)', async t => {
const {stdout} = await $`noop-fd.js 2 ${foobarString}`
.pipe({from: 'stderr'})`noop-stdin-fd.js 2`
.pipe('stdin.js', {from: 'stderr'});
t.is(stdout, foobarString);
});
test('$.pipe(options)`command`', async t => {
const {stdout} = await $`noop.js ${foobarString}`.pipe({stripFinalNewline: false})`stdin.js`;
t.is(stdout, `${foobarString}\n`);
});
test('execa.$.pipe(options)`command`', async t => {
const {stdout} = await execa('noop.js', [foobarString]).pipe({stripFinalNewline: false})`stdin.js`;
t.is(stdout, `${foobarString}\n`);
});
test('$.pipe.pipe(options)`command`', async t => {
const {stdout} = await $`noop.js ${foobarString}`
.pipe({})`stdin.js`
.pipe({stripFinalNewline: false})`stdin.js`;
t.is(stdout, `${foobarString}\n`);
});
test('$.pipe("file", options)', async t => {
const {stdout} = await $`noop.js ${foobarString}`.pipe('stdin.js', {stripFinalNewline: false});
t.is(stdout, `${foobarString}\n`);
});
test('execa.$.pipe("file", options)', async t => {
const {stdout} = await execa('noop.js', [foobarString]).pipe('stdin.js', {stripFinalNewline: false});
t.is(stdout, `${foobarString}\n`);
});
test('$.pipe.pipe("file", options)', async t => {
const {stdout} = await $`noop.js ${foobarString}`
.pipe({})`stdin.js`
.pipe('stdin.js', {stripFinalNewline: false});
t.is(stdout, `${foobarString}\n`);
});
test('$.pipe(pipeAndSubprocessOptions)`command`', async t => {
const {stdout} = await $`noop-fd.js 2 ${foobarString}\n`.pipe({from: 'stderr', stripFinalNewline: false})`stdin.js`;
t.is(stdout, `${foobarString}\n`);
});
test('execa.$.pipe(pipeAndSubprocessOptions)`command`', async t => {
const {stdout} = await execa('noop-fd.js', ['2', `${foobarString}\n`]).pipe({from: 'stderr', stripFinalNewline: false})`stdin.js`;
t.is(stdout, `${foobarString}\n`);
});
test('$.pipe.pipe(pipeAndSubprocessOptions)`command`', async t => {
const {stdout} = await $`noop-fd.js 2 ${foobarString}\n`
.pipe({from: 'stderr'})`noop-stdin-fd.js 2`
.pipe({from: 'stderr', stripFinalNewline: false})`stdin.js`;
t.is(stdout, `${foobarString}\n`);
});
test('$.pipe("file", pipeAndSubprocessOptions)', async t => {
const {stdout} = await $`noop-fd.js 2 ${foobarString}\n`.pipe('stdin.js', {from: 'stderr', stripFinalNewline: false});
t.is(stdout, `${foobarString}\n`);
});
test('execa.$.pipe("file", pipeAndSubprocessOptions)', async t => {
const {stdout} = await execa('noop-fd.js', ['2', `${foobarString}\n`]).pipe('stdin.js', {from: 'stderr', stripFinalNewline: false});
t.is(stdout, `${foobarString}\n`);
});
test('$.pipe.pipe("file", pipeAndSubprocessOptions)', async t => {
const {stdout} = await $`noop-fd.js 2 ${foobarString}\n`
.pipe({from: 'stderr'})`noop-stdin-fd.js 2`
.pipe('stdin.js', {from: 'stderr', stripFinalNewline: false});
t.is(stdout, `${foobarString}\n`);
});
test('$.pipe(options)(secondOptions)`command`', async t => {
const {stdout} = await $`noop.js ${foobarString}`.pipe({stripFinalNewline: false})({stripFinalNewline: true})`stdin.js`;
t.is(stdout, foobarString);
});
test('execa.$.pipe(options)(secondOptions)`command`', async t => {
const {stdout} = await execa('noop.js', [foobarString]).pipe({stripFinalNewline: false})({stripFinalNewline: true})`stdin.js`;
t.is(stdout, foobarString);
});
test('$.pipe.pipe(options)(secondOptions)`command`', async t => {
const {stdout} = await $`noop.js ${foobarString}`
.pipe({})({})`stdin.js`
.pipe({stripFinalNewline: false})({stripFinalNewline: true})`stdin.js`;
t.is(stdout, foobarString);
});
test('$.pipe`command` forces "stdin: pipe"', async t => {
const {stdout} = await $`noop.js ${foobarString}`.pipe({stdin: 'ignore'})`stdin.js`;
t.is(stdout, foobarString);
});
test('execa.pipe("file") forces "stdin: "pipe"', async t => {
const {stdout} = await execa('noop.js', [foobarString]).pipe('stdin.js', {stdin: 'ignore'});
t.is(stdout, foobarString);
});
test('execa.pipe(subprocess) does not force "stdin: pipe"', async t => {
await t.throwsAsync(
execa('noop.js', [foobarString]).pipe(execa('stdin.js', {stdin: 'ignore'})),
{message: /"stdin: 'ignore'" option is incompatible/},
);
});
test('$.pipe(options)(subprocess) fails', async t => {
await t.throwsAsync(
$`empty.js`.pipe({stdout: 'pipe'})($`empty.js`),
{message: /Please use \.pipe/},
);
});
test('execa.$.pipe(options)(subprocess) fails', async t => {
await t.throwsAsync(
execa('empty.js').pipe({stdout: 'pipe'})($`empty.js`),
{message: /Please use \.pipe/},
);
});
test('$.pipe(options)("file") fails', async t => {
await t.throwsAsync(
$`empty.js`.pipe({stdout: 'pipe'})('empty.js'),
{message: /Please use \.pipe/},
);
});
test('execa.$.pipe(options)("file") fails', async t => {
await t.throwsAsync(
execa('empty.js').pipe({stdout: 'pipe'})('empty.js'),
{message: /Please use \.pipe/},
);
});
const testInvalidPipe = async (t, ...pipeArguments) => {
await t.throwsAsync(
$`empty.js`.pipe(...pipeArguments),
{message: /must be a template string/},
);
};
test('$.pipe(nonExecaSubprocess) fails', testInvalidPipe, spawn('node', ['--version']));
test('$.pipe(false) fails', testInvalidPipe, false);
================================================
FILE: test/pipe/sequence.js
================================================
import {once} from 'node:events';
import process from 'node:process';
import {PassThrough} from 'node:stream';
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
import {noopGenerator} from '../helpers/generator.js';
import {prematureClose} from '../helpers/stdio.js';
setFixtureDirectory();
const isLinux = process.platform === 'linux';
test('Source stream abort -> destination success', async t => {
const source = execa('noop-repeat.js');
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination);
source.stdout.destroy();
t.is(await t.throwsAsync(pipePromise), await t.throwsAsync(source));
t.like(await t.throwsAsync(source), {exitCode: 1});
await destination;
});
test('Source stream error -> destination success', async t => {
const source = execa('noop-repeat.js');
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination);
const cause = new Error('test');
source.stdout.destroy(cause);
t.is(await t.throwsAsync(pipePromise), await t.throwsAsync(source));
t.like(await t.throwsAsync(source), {originalMessage: cause.message, exitCode: 1});
await destination;
});
test('Destination stream abort -> source failure', async t => {
const source = execa('noop-repeat.js');
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination);
destination.stdin.destroy();
t.is(await t.throwsAsync(pipePromise), await t.throwsAsync(destination));
t.like(await t.throwsAsync(destination), prematureClose);
t.like(await t.throwsAsync(source), {exitCode: 1});
});
test('Destination stream error -> source failure', async t => {
const source = execa('noop-repeat.js');
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination);
const cause = new Error('test');
destination.stdin.destroy(cause);
t.is(await t.throwsAsync(pipePromise), await t.throwsAsync(destination));
t.like(await t.throwsAsync(destination), {originalMessage: cause.message, exitCode: 0});
t.like(await t.throwsAsync(source), {exitCode: 1});
});
test('Source success -> destination success', async t => {
const source = execa('noop.js', [foobarString]);
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination);
t.like(await source, {stdout: foobarString});
t.like(await destination, {stdout: foobarString});
t.is(await pipePromise, await destination);
});
test('Destination stream end -> source failure', async t => {
const source = execa('noop-repeat.js');
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination);
destination.stdin.end();
t.is(await t.throwsAsync(pipePromise), await t.throwsAsync(source));
await destination;
t.like(await t.throwsAsync(source), {exitCode: 1});
});
test('Source normal failure -> destination success', async t => {
const source = execa('noop-fail.js', ['1', foobarString]);
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination);
t.is(await t.throwsAsync(pipePromise), await t.throwsAsync(source));
t.like(await t.throwsAsync(source), {stdout: foobarString, exitCode: 2});
await destination;
});
test('Source normal failure -> deep destination success', async t => {
const source = execa('noop-fail.js', ['1', foobarString]);
const destination = execa('stdin.js');
const secondDestination = execa('stdin.js');
const pipePromise = source.pipe(destination);
const secondPipePromise = pipePromise.pipe(secondDestination);
t.is(await t.throwsAsync(pipePromise), await t.throwsAsync(source));
t.is(await t.throwsAsync(secondPipePromise), await t.throwsAsync(source));
t.like(await t.throwsAsync(source), {stdout: foobarString, exitCode: 2});
t.like(await destination, {stdout: foobarString});
t.like(await secondDestination, {stdout: foobarString});
});
const testSourceTerminated = async (t, signal) => {
const source = execa('noop-repeat.js');
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination);
source.kill(signal);
t.is(await t.throwsAsync(pipePromise), await t.throwsAsync(source));
t.like(await t.throwsAsync(source), {signal});
await destination;
};
test('Source SIGTERM -> destination success', testSourceTerminated, 'SIGTERM');
test('Source SIGKILL -> destination success', testSourceTerminated, 'SIGKILL');
test('Destination success before source -> source success', async t => {
const passThroughStream = new PassThrough();
const source = execa('stdin.js', {stdin: ['pipe', passThroughStream]});
const destination = execa('empty.js');
const pipePromise = source.pipe(destination);
await destination;
passThroughStream.end();
await source;
t.is(await pipePromise, await destination);
});
test('Destination normal failure -> source failure', async t => {
const source = execa('noop-repeat.js');
const destination = execa('fail.js');
const pipePromise = source.pipe(destination);
t.is(await t.throwsAsync(pipePromise), await t.throwsAsync(destination));
t.like(await t.throwsAsync(destination), {exitCode: 2});
t.like(await t.throwsAsync(source), {exitCode: 1});
});
test('Destination normal failure -> deep source failure', async t => {
const source = execa('noop-repeat.js');
const destination = execa('stdin.js');
const secondDestination = execa('fail.js');
const pipePromise = source.pipe(destination);
const secondPipePromise = pipePromise.pipe(secondDestination);
t.is(await t.throwsAsync(pipePromise), await t.throwsAsync(destination));
t.is(await t.throwsAsync(secondPipePromise), await t.throwsAsync(secondDestination));
t.like(await t.throwsAsync(secondDestination), {exitCode: 2});
t.like(await t.throwsAsync(destination), {exitCode: 1});
t.like(await t.throwsAsync(source), {exitCode: 1});
});
const testDestinationTerminated = async (t, signal) => {
const source = execa('noop-repeat.js');
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination);
destination.kill(signal);
t.is(await t.throwsAsync(pipePromise), await t.throwsAsync(destination));
t.like(await t.throwsAsync(destination), {signal});
t.like(await t.throwsAsync(source), {exitCode: 1});
};
test('Destination SIGTERM -> source abort', testDestinationTerminated, 'SIGTERM');
test('Destination SIGKILL -> source abort', testDestinationTerminated, 'SIGKILL');
test('Source already ended -> ignore source', async t => {
const source = execa('noop.js', [foobarString]);
await source;
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination);
t.is(await pipePromise, await destination);
t.like(await source, {stdout: foobarString});
t.like(await destination, {stdout: ''});
});
test('Source already aborted -> ignore source', async t => {
const source = execa('noop.js', [foobarString]);
source.stdout.destroy();
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination);
t.is(await pipePromise, await destination);
t.like(await source, {stdout: ''});
t.like(await destination, {stdout: ''});
});
test('Source already errored -> failure', async t => {
const source = execa('noop.js', [foobarString]);
const cause = new Error('test');
source.stdout.destroy(cause);
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination);
t.is(await t.throwsAsync(pipePromise), await t.throwsAsync(source));
t.like(await t.throwsAsync(source), {cause});
t.like(await destination, {stdout: ''});
});
test('Destination already ended -> ignore source', async t => {
const destination = execa('stdin.js');
destination.stdin.end('.');
await destination;
const source = execa('noop.js', [foobarString]);
const pipePromise = source.pipe(destination);
t.is(await pipePromise, await destination);
t.like(await destination, {stdout: '.'});
t.like(await source, {stdout: ''});
});
test('Destination already aborted -> failure', async t => {
const destination = execa('stdin.js');
destination.stdin.destroy();
t.like(await t.throwsAsync(destination), prematureClose);
const source = execa('noop.js', [foobarString]);
const pipePromise = source.pipe(destination);
t.is(await t.throwsAsync(pipePromise), await t.throwsAsync(destination));
t.like(await source, {stdout: ''});
});
test('Destination already errored -> failure', async t => {
const destination = execa('stdin.js');
const cause = new Error('test');
destination.stdin.destroy(cause);
t.like(await t.throwsAsync(destination), {cause});
const source = execa('noop.js', [foobarString]);
const pipePromise = source.pipe(destination);
t.is(await t.throwsAsync(pipePromise), await t.throwsAsync(destination));
t.like(await source, {stdout: ''});
});
test('Source normal failure + destination normal failure', async t => {
const source = execa('noop-fail.js', ['1', foobarString]);
const destination = execa('stdin-fail.js');
const pipePromise = source.pipe(destination);
t.is(await t.throwsAsync(pipePromise), await t.throwsAsync(destination));
t.like(await t.throwsAsync(source), {stdout: foobarString, exitCode: 2});
t.like(await t.throwsAsync(destination), {stdout: foobarString, exitCode: 2});
});
test('Simultaneous error on source and destination', async t => {
const source = execa('noop.js', ['']);
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination);
const sourceCause = new Error(foobarString);
source.emit('error', sourceCause);
const destinationCause = new Error('other');
destination.emit('error', destinationCause);
t.is(await t.throwsAsync(pipePromise), await t.throwsAsync(destination));
t.like(await t.throwsAsync(source), {cause: {originalMessage: sourceCause.originalMessage}});
t.like(await t.throwsAsync(destination), {cause: {originalMessage: destinationCause.originalMessage}});
});
test('Does not need to await individual promises', async t => {
const source = execa('fail.js');
const destination = execa('fail.js');
await t.throwsAsync(source.pipe(destination));
});
test('Need to await .pipe() return value', async t => {
const source = execa('fail.js');
const destination = execa('fail.js');
const pipePromise = source.pipe(destination);
await Promise.all([
once(process, 'unhandledRejection'),
t.throwsAsync(source),
t.throwsAsync(destination),
]);
await t.throwsAsync(pipePromise);
});
if (isLinux) {
const testYesHead = async (t, useStdoutTransform, useStdinTransform, all) => {
const source = execa('yes', {stdout: useStdoutTransform ? noopGenerator(false) : 'pipe', all});
const destination = execa('head', ['-n', '1'], {stdin: useStdinTransform ? noopGenerator(false) : 'pipe'});
const pipePromise = source.pipe(destination);
t.is(await t.throwsAsync(pipePromise), await t.throwsAsync(source));
t.like(await destination, {stdout: 'y'});
t.like(await t.throwsAsync(source), {exitCode: 1, stderr: 'yes: standard output: Connection reset by peer'});
t.false(source.stdout.readableEnded);
t.is(source.stdout.errored, null);
t.true(source.stdout.destroyed);
t.true(source.stderr.readableEnded);
t.is(source.stderr.errored, null);
t.true(source.stderr.destroyed);
if (all) {
t.true(source.all.readableEnded);
t.is(source.all.errored, null);
t.true(source.all.destroyed);
}
};
test.serial('Works with yes | head', testYesHead, false, false, false);
test.serial('Works with yes | head, input transform', testYesHead, false, true, false);
test.serial('Works with yes | head, output transform', testYesHead, true, false, false);
test.serial('Works with yes | head, input/output transform', testYesHead, true, true, false);
test.serial('Works with yes | head, "all" option', testYesHead, false, false, true);
test.serial('Works with yes | head, "all" option, input transform', testYesHead, false, true, true);
test.serial('Works with yes | head, "all" option, output transform', testYesHead, true, false, true);
test.serial('Works with yes | head, "all" option, input/output transform', testYesHead, true, true, true);
}
================================================
FILE: test/pipe/setup.js
================================================
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {fullStdio, fullReadableStdio} from '../helpers/stdio.js';
import {foobarString} from '../helpers/input.js';
setFixtureDirectory();
// eslint-disable-next-line max-params
const pipeToSubprocess = async (t, readableFdNumber, writableFdNumber, from, to, readableOptions = {}, writableOptions = {}) => {
const {stdout} = await execa('noop-fd.js', [`${readableFdNumber}`, foobarString], readableOptions)
.pipe(execa('stdin-fd.js', [`${writableFdNumber}`], writableOptions), {from, to});
t.is(stdout, foobarString);
};
test('pipe(...) can pipe', pipeToSubprocess, 1, 0);
test('pipe(..., {from: "stdout"}) can pipe', pipeToSubprocess, 1, 0, 'stdout');
test('pipe(..., {from: "fd1"}) can pipe', pipeToSubprocess, 1, 0, 'fd1');
test('pipe(..., {from: "stderr"}) can pipe stderr', pipeToSubprocess, 2, 0, 'stderr');
test('pipe(..., {from: "fd2"}) can pipe', pipeToSubprocess, 2, 0, 'fd2');
test('pipe(..., {from: "fd3"}) can pipe', pipeToSubprocess, 3, 0, 'fd3', undefined, fullStdio);
test('pipe(..., {from: "all"}) can pipe stdout', pipeToSubprocess, 1, 0, 'all', undefined, {all: true});
test('pipe(..., {from: "all"}) can pipe stderr', pipeToSubprocess, 2, 0, 'all', undefined, {all: true});
test('pipe(..., {from: "all"}) can pipe stdout even with "stderr: ignore"', pipeToSubprocess, 1, 0, 'all', undefined, {all: true, stderr: 'ignore'});
test('pipe(..., {from: "all"}) can pipe stderr even with "stdout: ignore"', pipeToSubprocess, 2, 0, 'all', undefined, {all: true, stdout: 'ignore'});
test('pipe(..., {to: "stdin"}) can pipe', pipeToSubprocess, 1, 0, undefined, 'stdin');
test('pipe(..., {to: "fd0"}) can pipe', pipeToSubprocess, 1, 0, undefined, 'fd0');
test('pipe(..., {to: "fd3"}) can pipe', pipeToSubprocess, 1, 3, undefined, 'fd3', {}, fullReadableStdio());
================================================
FILE: test/pipe/streaming.js
================================================
import {once} from 'node:events';
import {PassThrough} from 'node:stream';
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
import {assertMaxListeners} from '../helpers/listeners.js';
import {fullReadableStdio} from '../helpers/stdio.js';
import {PARALLEL_COUNT} from '../helpers/parallel.js';
setFixtureDirectory();
test('Can pipe two sources to same destination', async t => {
const source = execa('noop.js', [foobarString]);
const secondSource = execa('noop.js', [foobarString]);
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination);
const secondPipePromise = secondSource.pipe(destination);
t.like(await source, {stdout: foobarString});
t.like(await secondSource, {stdout: foobarString});
t.like(await destination, {stdout: `${foobarString}\n${foobarString}`});
t.is(await pipePromise, await destination);
t.is(await secondPipePromise, await destination);
});
test('Can pipe three sources to same destination', async t => {
const source = execa('noop.js', [foobarString]);
const secondSource = execa('noop.js', [foobarString]);
const thirdSource = execa('noop.js', [foobarString]);
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination);
const secondPipePromise = secondSource.pipe(destination);
const thirdPromise = thirdSource.pipe(destination);
t.like(await source, {stdout: foobarString});
t.like(await secondSource, {stdout: foobarString});
t.like(await thirdSource, {stdout: foobarString});
t.like(await destination, {stdout: `${foobarString}\n${foobarString}\n${foobarString}`});
t.is(await pipePromise, await destination);
t.is(await secondPipePromise, await destination);
t.is(await thirdPromise, await destination);
});
test.serial('Can pipe many sources to same destination', async t => {
const checkMaxListeners = assertMaxListeners(t);
const expectedResults = Array.from({length: PARALLEL_COUNT}, (_, index) => `${index}`).sort();
const sources = expectedResults.map(expectedResult => execa('noop.js', [expectedResult]));
const destination = execa('stdin.js');
const pipePromises = sources.map(source => source.pipe(destination));
const results = await Promise.all(sources);
t.deepEqual(results.map(({stdout}) => stdout), expectedResults);
const destinationResult = await destination;
t.deepEqual(destinationResult.stdout.split('\n').sort(), expectedResults);
t.deepEqual(await Promise.all(pipePromises), sources.map(() => destinationResult));
checkMaxListeners();
});
test.serial('Can pipe same source to many destinations', async t => {
const checkMaxListeners = assertMaxListeners(t);
const source = execa('noop-fd.js', ['1', foobarString]);
const expectedResults = Array.from({length: PARALLEL_COUNT}, (_, index) => `${index}`);
const destinations = expectedResults.map(expectedResult => execa('noop-stdin-double.js', [expectedResult]));
const pipePromises = destinations.map(destination => source.pipe(destination));
t.like(await source, {stdout: foobarString});
const results = await Promise.all(destinations);
t.deepEqual(results.map(({stdout}) => stdout), expectedResults.map(result => `${foobarString} ${result}`));
t.deepEqual(await Promise.all(pipePromises), results);
checkMaxListeners();
});
test('Can pipe two streams from same subprocess to same destination', async t => {
const source = execa('noop-both.js', [foobarString]);
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination);
const secondPipePromise = source.pipe(destination, {from: 'stderr'});
t.like(await source, {stdout: foobarString, stderr: foobarString});
t.like(await destination, {stdout: `${foobarString}\n${foobarString}`});
t.is(await pipePromise, await destination);
t.is(await secondPipePromise, await destination);
});
test('Can pipe same source to two streams from same subprocess', async t => {
const source = execa('noop-fd.js', ['1', foobarString]);
const destination = execa('stdin-fd-both.js', ['3'], fullReadableStdio());
const pipePromise = source.pipe(destination);
const secondPipePromise = source.pipe(destination, {to: 'fd3'});
t.like(await source, {stdout: foobarString});
t.like(await destination, {stdout: `${foobarString}${foobarString}`});
t.is(await pipePromise, await destination);
t.is(await secondPipePromise, await destination);
});
test('Can pipe a new source to same destination after some source has already written', async t => {
const passThroughStream = new PassThrough();
const source = execa('stdin.js', {stdin: ['pipe', passThroughStream]});
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination);
passThroughStream.write('foo');
const firstWrite = await once(destination.stdout, 'data');
t.is(firstWrite.toString(), 'foo');
const secondSource = execa('noop.js', ['bar']);
const secondPipePromise = secondSource.pipe(destination);
passThroughStream.end();
t.like(await source, {stdout: 'foo'});
t.like(await secondSource, {stdout: 'bar'});
t.like(await destination, {stdout: 'foobar'});
t.is(await pipePromise, await destination);
t.is(await secondPipePromise, await destination);
});
test('Can pipe a second source to same destination after destination has already ended', async t => {
const source = execa('noop.js', [foobarString]);
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination);
t.like(await source, {stdout: foobarString});
t.like(await destination, {stdout: foobarString});
t.is(await pipePromise, await destination);
const secondSource = execa('noop.js', [foobarString]);
const secondPipePromise = secondSource.pipe(destination);
t.like(await secondSource, {stdout: ''});
t.is(await secondPipePromise, await destination);
});
test('Can pipe same source to a second destination after source has already ended', async t => {
const source = execa('noop.js', [foobarString]);
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination);
t.like(await source, {stdout: foobarString});
t.like(await destination, {stdout: foobarString});
t.is(await pipePromise, await destination);
const secondDestination = execa('stdin.js');
const secondPipePromise = source.pipe(secondDestination);
t.like(await secondDestination, {stdout: ''});
t.is(await secondPipePromise, await secondDestination);
});
test('Can pipe a new source to same destination after some but not all sources have ended', async t => {
const source = execa('noop.js', [foobarString]);
const passThroughStream = new PassThrough();
const secondSource = execa('stdin.js', {stdin: ['pipe', passThroughStream]});
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination);
const secondPipePromise = secondSource.pipe(destination);
t.like(await source, {stdout: foobarString});
const thirdSource = execa('noop.js', [foobarString]);
const thirdPipePromise = thirdSource.pipe(destination);
passThroughStream.end(`${foobarString}\n`);
t.like(await secondSource, {stdout: foobarString});
t.like(await thirdSource, {stdout: foobarString});
t.like(await destination, {stdout: `${foobarString}\n${foobarString}\n${foobarString}`});
t.is(await pipePromise, await destination);
t.is(await secondPipePromise, await destination);
t.is(await thirdPipePromise, await destination);
});
test('Can pipe two subprocesses already ended', async t => {
const source = execa('noop.js', [foobarString]);
const destination = execa('stdin.js');
destination.stdin.end('.');
await Promise.all([source, destination]);
const pipePromise = source.pipe(destination);
t.like(await source, {stdout: foobarString});
t.like(await destination, {stdout: '.'});
t.is(await pipePromise, await destination);
});
test('Can pipe to same destination through multiple paths', async t => {
const source = execa('noop.js', [foobarString]);
const destination = execa('stdin.js');
const secondDestination = execa('stdin.js');
const pipePromise = source.pipe(destination);
const secondPipePromise = pipePromise.pipe(secondDestination);
const thirdPipePromise = source.pipe(secondDestination);
t.like(await source, {stdout: foobarString});
t.like(await destination, {stdout: foobarString});
t.like(await secondDestination, {stdout: `${foobarString}\n${foobarString}`});
t.is(await pipePromise, await destination);
t.is(await secondPipePromise, await secondDestination);
t.is(await thirdPipePromise, await secondDestination);
});
test('Can pipe two sources to same destination in objectMode', async t => {
const stdoutTransform = {
* transform() {
yield [foobarString];
},
objectMode: true,
};
const source = execa('noop.js', [''], {stdout: stdoutTransform});
const secondSource = execa('noop.js', [''], {stdout: stdoutTransform});
t.true(source.stdout.readableObjectMode);
t.true(secondSource.stdout.readableObjectMode);
const stdinTransform = {
* transform([chunk]) {
yield chunk;
},
objectMode: true,
};
const destination = execa('stdin.js', {stdin: stdinTransform});
const pipePromise = source.pipe(destination);
const secondPipePromise = secondSource.pipe(destination);
t.like(await source, {stdout: [[foobarString]]});
t.like(await secondSource, {stdout: [[foobarString]]});
t.like(await destination, {stdout: `${foobarString}\n${foobarString}`});
t.is(await pipePromise, await destination);
t.is(await secondPipePromise, await destination);
});
test('Can pipe one source to two destinations', async t => {
const source = execa('noop.js', [foobarString]);
const destination = execa('stdin.js');
const secondDestination = execa('stdin.js');
const pipePromise = source.pipe(destination);
const secondPipePromise = source.pipe(secondDestination);
t.like(await source, {stdout: foobarString});
t.like(await destination, {stdout: foobarString});
t.like(await secondDestination, {stdout: foobarString});
t.is(await pipePromise, await destination);
t.is(await secondPipePromise, await secondDestination);
});
test('Can pipe one source to three destinations', async t => {
const source = execa('noop.js', [foobarString]);
const destination = execa('stdin.js');
const secondDestination = execa('stdin.js');
const thirdDestination = execa('stdin.js');
const pipePromise = source.pipe(destination);
const secondPipePromise = source.pipe(secondDestination);
const thirdPipePromise = source.pipe(thirdDestination);
t.like(await source, {stdout: foobarString});
t.like(await destination, {stdout: foobarString});
t.like(await secondDestination, {stdout: foobarString});
t.like(await thirdDestination, {stdout: foobarString});
t.is(await pipePromise, await destination);
t.is(await secondPipePromise, await secondDestination);
t.is(await thirdPipePromise, await thirdDestination);
});
test('Can create a series of pipes', async t => {
const source = execa('noop.js', [foobarString]);
const destination = execa('stdin.js');
const secondDestination = execa('stdin.js');
const pipePromise = source.pipe(destination);
const secondPipePromise = pipePromise.pipe(secondDestination);
t.like(await source, {stdout: foobarString});
t.like(await destination, {stdout: foobarString});
t.like(await secondDestination, {stdout: foobarString});
t.is(await pipePromise, await destination);
t.is(await secondPipePromise, await secondDestination);
});
test('Returns pipedFrom on success', async t => {
const source = execa('noop.js', [foobarString]);
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination);
const destinationResult = await destination;
t.deepEqual(destinationResult.pipedFrom, []);
const sourceResult = await source;
t.like(await pipePromise, {pipedFrom: [sourceResult]});
t.deepEqual(destinationResult.pipedFrom, [sourceResult]);
t.deepEqual(sourceResult.pipedFrom, []);
});
test('Returns pipedFrom on deep success', async t => {
const source = execa('noop.js', [foobarString]);
const destination = execa('stdin.js');
const secondDestination = execa('stdin.js');
const pipePromise = source.pipe(destination);
const secondPipePromise = pipePromise.pipe(secondDestination);
const destinationResult = await destination;
t.deepEqual(destinationResult.pipedFrom, []);
const secondDestinationResult = await secondDestination;
t.deepEqual(secondDestinationResult.pipedFrom, []);
const sourceResult = await source;
t.like(await secondPipePromise, {pipedFrom: [destinationResult]});
t.deepEqual(secondDestinationResult.pipedFrom, [destinationResult]);
t.like(await pipePromise, {pipedFrom: [sourceResult]});
t.deepEqual(destinationResult.pipedFrom, [sourceResult]);
t.deepEqual(sourceResult.pipedFrom, []);
});
test('Returns pipedFrom on source failure', async t => {
const source = execa('noop-fail.js', ['1', foobarString]);
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination);
const destinationResult = await destination;
t.deepEqual(destinationResult.pipedFrom, []);
const sourceResult = await t.throwsAsync(source);
t.like(await t.throwsAsync(pipePromise), {pipedFrom: []});
t.deepEqual(destinationResult.pipedFrom, [sourceResult]);
t.deepEqual(sourceResult.pipedFrom, []);
});
test('Returns pipedFrom on destination failure', async t => {
const source = execa('noop.js', [foobarString]);
const destination = execa('stdin-fail.js');
const pipePromise = source.pipe(destination);
const destinationResult = await t.throwsAsync(destination);
const sourceResult = await source;
t.like(await t.throwsAsync(pipePromise), {pipedFrom: [sourceResult]});
t.deepEqual(destinationResult.pipedFrom, [sourceResult]);
t.deepEqual(sourceResult.pipedFrom, []);
});
test('Returns pipedFrom on source + destination failure', async t => {
const source = execa('noop-fail.js', ['1', foobarString]);
const destination = execa('stdin-fail.js');
const pipePromise = source.pipe(destination);
const destinationResult = await t.throwsAsync(destination);
const sourceResult = await t.throwsAsync(source);
t.like(await t.throwsAsync(pipePromise), {pipedFrom: [sourceResult]});
t.deepEqual(destinationResult.pipedFrom, [sourceResult]);
t.deepEqual(sourceResult.pipedFrom, []);
});
test('Returns pipedFrom on deep failure', async t => {
const source = execa('noop-fail.js', ['1', foobarString]);
const destination = execa('stdin-fail.js');
const secondDestination = execa('stdin.js');
const pipePromise = source.pipe(destination);
const secondPipePromise = pipePromise.pipe(secondDestination);
const destinationResult = await t.throwsAsync(destination);
const secondDestinationResult = await secondDestination;
t.deepEqual(secondDestinationResult.pipedFrom, []);
const sourceResult = await t.throwsAsync(source);
t.like(await t.throwsAsync(secondPipePromise), {pipedFrom: [sourceResult]});
t.deepEqual(secondDestinationResult.pipedFrom, [destinationResult]);
t.like(await t.throwsAsync(pipePromise), {pipedFrom: [sourceResult]});
t.deepEqual(destinationResult.pipedFrom, [sourceResult]);
t.deepEqual(sourceResult.pipedFrom, []);
});
test('Returns pipedFrom from multiple sources', async t => {
const source = execa('noop.js', [foobarString]);
const secondSource = execa('noop.js', [foobarString]);
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination);
const secondPipePromise = secondSource.pipe(destination);
const destinationResult = await destination;
t.deepEqual(destinationResult.pipedFrom, []);
const sourceResult = await source;
const secondSourceResult = await secondSource;
t.like(await pipePromise, {pipedFrom: [sourceResult, secondSourceResult]});
t.like(await secondPipePromise, {pipedFrom: [sourceResult, secondSourceResult]});
t.deepEqual(destinationResult.pipedFrom, [sourceResult, secondSourceResult]);
t.deepEqual(sourceResult.pipedFrom, []);
t.deepEqual(secondSourceResult.pipedFrom, []);
});
test('Returns pipedFrom from already ended subprocesses', async t => {
const source = execa('noop.js', [foobarString]);
const destination = execa('stdin.js');
destination.stdin.end('.');
await Promise.all([source, destination]);
const pipePromise = source.pipe(destination);
const destinationResult = await destination;
t.deepEqual(destinationResult.pipedFrom, []);
const sourceResult = await source;
t.deepEqual(sourceResult.pipedFrom, []);
t.like(await pipePromise, {pipedFrom: [sourceResult]});
t.deepEqual(destinationResult.pipedFrom, [sourceResult]);
t.deepEqual(sourceResult.pipedFrom, []);
});
test('Does not return nor set pipedFrom on signal abort', async t => {
const abortController = new AbortController();
const source = execa('empty.js');
const destination = execa('empty.js');
const pipePromise = source.pipe(destination, {unpipeSignal: abortController.signal});
abortController.abort();
t.like(await t.throwsAsync(pipePromise), {pipedFrom: []});
const destinationResult = await destination;
t.deepEqual(destinationResult.pipedFrom, []);
const sourceResult = await source;
t.deepEqual(sourceResult.pipedFrom, []);
});
test('Can pipe same source to same destination twice', async t => {
const source = execa('noop.js', [foobarString]);
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination);
const secondPipePromise = source.pipe(destination);
const destinationResult = await destination;
t.like(destinationResult, {pipedFrom: []});
const sourceResult = await source;
t.like(sourceResult, {pipedFrom: []});
t.like(await source, {stdout: foobarString});
t.like(await destination, {stdout: foobarString});
t.is(await pipePromise, destinationResult);
t.is(await secondPipePromise, destinationResult);
t.deepEqual(destinationResult.pipedFrom, [sourceResult]);
t.deepEqual(sourceResult.pipedFrom, []);
});
================================================
FILE: test/pipe/throw.js
================================================
import test from 'ava';
import {execa} from '../../index.js';
import {foobarString} from '../helpers/input.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {assertPipeError} from '../helpers/pipe.js';
setFixtureDirectory();
test('Destination stream is ended when first argument is invalid', async t => {
const source = execa('empty.js', {stdout: 'ignore'});
const destination = execa('stdin.js');
const pipePromise = source.pipe(destination);
await assertPipeError(t, pipePromise, 'option is incompatible');
await source;
t.like(await destination, {stdout: ''});
});
test('Destination stream is ended when first argument is invalid - $', async t => {
const pipePromise = execa('empty.js', {stdout: 'ignore'}).pipe`stdin.js`;
await assertPipeError(t, pipePromise, 'option is incompatible');
});
test('Source stream is aborted when second argument is invalid', async t => {
const source = execa('noop.js', [foobarString]);
const pipePromise = source.pipe(false);
await assertPipeError(t, pipePromise, 'an Execa subprocess');
t.like(await source, {stdout: ''});
});
test('Both arguments might be invalid', async t => {
const source = execa('empty.js', {stdout: 'ignore'});
const pipePromise = source.pipe(false);
await assertPipeError(t, pipePromise, 'an Execa subprocess');
t.like(await source, {stdout: undefined});
});
================================================
FILE: test/resolve/all.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {defaultHighWaterMark} from '../helpers/stream.js';
import {foobarString} from '../helpers/input.js';
setFixtureDirectory();
const textEncoder = new TextEncoder();
const foobarStringFull = `${foobarString}\n`;
const doubleFoobarStringFull = `${foobarStringFull}${foobarStringFull}`;
const doubleFoobarString = `${foobarStringFull}${foobarString}`;
const doubleFoobarUint8ArrayFull = textEncoder.encode(doubleFoobarStringFull);
const doubleFoobarUint8Array = textEncoder.encode(doubleFoobarString);
const doubleFoobarArrayFull = [foobarStringFull, foobarStringFull];
const doubleFoobarArray = [foobarString, foobarString];
// eslint-disable-next-line max-params
const testAllBoth = async (t, expectedOutput, encoding, lines, stripFinalNewline, isFailure, execaMethod) => {
const fixtureName = isFailure ? 'noop-both-fail.js' : 'noop-both.js';
const {exitCode, all} = await execaMethod(fixtureName, [foobarString], {
all: true,
encoding,
lines,
stripFinalNewline,
reject: !isFailure,
});
t.is(exitCode, isFailure ? 1 : 0);
t.deepEqual(all, expectedOutput);
};
const fdOne = {stderr: true};
const fdBoth = {stdout: true, stderr: true};
test('result.all is defined', testAllBoth, doubleFoobarStringFull, 'utf8', false, false, false, execa);
test('result.all is defined, encoding "buffer"', testAllBoth, doubleFoobarUint8ArrayFull, 'buffer', false, false, false, execa);
test('result.all is defined, lines', testAllBoth, doubleFoobarArrayFull, 'utf8', true, false, false, execa);
test('result.all is defined, lines, fd-specific one', testAllBoth, doubleFoobarArrayFull, 'utf8', fdOne, false, false, execa);
test('result.all is defined, lines, fd-specific both', testAllBoth, doubleFoobarArrayFull, 'utf8', fdBoth, false, false, execa);
test('result.all is defined, stripFinalNewline', testAllBoth, doubleFoobarString, 'utf8', false, true, false, execa);
test('result.all is defined, stripFinalNewline, fd-specific one', testAllBoth, doubleFoobarString, 'utf8', false, fdOne, false, execa);
test('result.all is defined, stripFinalNewline, fd-specific both', testAllBoth, doubleFoobarString, 'utf8', false, fdBoth, false, execa);
test('result.all is defined, encoding "buffer", stripFinalNewline', testAllBoth, doubleFoobarUint8Array, 'buffer', false, true, false, execa);
test('result.all is defined, encoding "buffer", stripFinalNewline, fd-specific one', testAllBoth, doubleFoobarUint8Array, 'buffer', false, fdOne, false, execa);
test('result.all is defined, encoding "buffer", stripFinalNewline, fd-specific both', testAllBoth, doubleFoobarUint8Array, 'buffer', false, fdBoth, false, execa);
test('result.all is defined, lines, stripFinalNewline', testAllBoth, doubleFoobarArray, 'utf8', true, true, false, execa);
test('result.all is defined, lines, fd-specific one, stripFinalNewline', testAllBoth, doubleFoobarArray, 'utf8', fdOne, true, false, execa);
test('result.all is defined, lines, fd-specific both, stripFinalNewline', testAllBoth, doubleFoobarArray, 'utf8', fdBoth, true, false, execa);
test('result.all is defined, lines, stripFinalNewline, fd-specific one', testAllBoth, doubleFoobarArray, 'utf8', true, fdOne, false, execa);
test('result.all is defined, lines, stripFinalNewline, fd-specific both', testAllBoth, doubleFoobarArray, 'utf8', true, fdBoth, false, execa);
test('result.all is defined, failure', testAllBoth, doubleFoobarStringFull, 'utf8', false, false, true, execa);
test('result.all is defined, encoding "buffer", failure', testAllBoth, doubleFoobarUint8ArrayFull, 'buffer', false, false, true, execa);
test('result.all is defined, lines, failure', testAllBoth, doubleFoobarArrayFull, 'utf8', true, false, true, execa);
test('result.all is defined, lines, fd-specific one, failure', testAllBoth, doubleFoobarArrayFull, 'utf8', fdOne, false, true, execa);
test('result.all is defined, lines, fd-specific both, failure', testAllBoth, doubleFoobarArrayFull, 'utf8', fdBoth, false, true, execa);
test('result.all is defined, stripFinalNewline, failure', testAllBoth, doubleFoobarString, 'utf8', false, true, true, execa);
test('result.all is defined, stripFinalNewline, fd-specific one, failure', testAllBoth, doubleFoobarString, 'utf8', false, fdOne, true, execa);
test('result.all is defined, stripFinalNewline, fd-specific both, failure', testAllBoth, doubleFoobarString, 'utf8', false, fdBoth, true, execa);
test('result.all is defined, encoding "buffer", stripFinalNewline, failure', testAllBoth, doubleFoobarUint8Array, 'buffer', false, true, true, execa);
test('result.all is defined, encoding "buffer", stripFinalNewline, fd-specific one, failure', testAllBoth, doubleFoobarUint8Array, 'buffer', false, fdOne, true, execa);
test('result.all is defined, encoding "buffer", stripFinalNewline, fd-specific both, failure', testAllBoth, doubleFoobarUint8Array, 'buffer', false, fdBoth, true, execa);
test('result.all is defined, lines, stripFinalNewline, failure', testAllBoth, doubleFoobarArray, 'utf8', true, true, true, execa);
test('result.all is defined, lines, fd-specific one, stripFinalNewline, failure', testAllBoth, doubleFoobarArray, 'utf8', fdOne, true, true, execa);
test('result.all is defined, lines, fd-specific both, stripFinalNewline, failure', testAllBoth, doubleFoobarArray, 'utf8', fdBoth, true, true, execa);
test('result.all is defined, lines, stripFinalNewline, fd-specific one, failure', testAllBoth, doubleFoobarArray, 'utf8', true, fdOne, true, execa);
test('result.all is defined, lines, stripFinalNewline, fd-specific both, failure', testAllBoth, doubleFoobarArray, 'utf8', true, fdBoth, true, execa);
test('result.all is defined, sync', testAllBoth, doubleFoobarStringFull, 'utf8', false, false, false, execaSync);
test('result.all is defined, encoding "buffer", sync', testAllBoth, doubleFoobarUint8ArrayFull, 'buffer', false, false, false, execaSync);
test('result.all is defined, lines, sync', testAllBoth, doubleFoobarArrayFull, 'utf8', true, false, false, execaSync);
test('result.all is defined, lines, fd-specific one, sync', testAllBoth, doubleFoobarArrayFull, 'utf8', fdOne, false, false, execaSync);
test('result.all is defined, lines, fd-specific both, sync', testAllBoth, doubleFoobarArrayFull, 'utf8', fdBoth, false, false, execaSync);
test('result.all is defined, stripFinalNewline, sync', testAllBoth, doubleFoobarString, 'utf8', false, true, false, execaSync);
test('result.all is defined, stripFinalNewline, fd-specific one, sync', testAllBoth, doubleFoobarString, 'utf8', false, fdOne, false, execaSync);
test('result.all is defined, stripFinalNewline, fd-specific both, sync', testAllBoth, doubleFoobarString, 'utf8', false, fdBoth, false, execaSync);
test('result.all is defined, encoding "buffer", stripFinalNewline, sync', testAllBoth, doubleFoobarUint8Array, 'buffer', false, true, false, execaSync);
test('result.all is defined, encoding "buffer", stripFinalNewline, fd-specific one, sync', testAllBoth, doubleFoobarUint8Array, 'buffer', false, fdOne, false, execaSync);
test('result.all is defined, encoding "buffer", stripFinalNewline, fd-specific both, sync', testAllBoth, doubleFoobarUint8Array, 'buffer', false, fdBoth, false, execaSync);
test('result.all is defined, lines, stripFinalNewline, sync', testAllBoth, doubleFoobarArray, 'utf8', true, true, false, execaSync);
test('result.all is defined, lines, fd-specific one, stripFinalNewline, sync', testAllBoth, doubleFoobarArray, 'utf8', fdOne, true, false, execaSync);
test('result.all is defined, lines, fd-specific both, stripFinalNewline, sync', testAllBoth, doubleFoobarArray, 'utf8', fdBoth, true, false, execaSync);
test('result.all is defined, lines, stripFinalNewline, fd-specific one, sync', testAllBoth, doubleFoobarArray, 'utf8', true, fdOne, false, execaSync);
test('result.all is defined, lines, stripFinalNewline, fd-specific both, sync', testAllBoth, doubleFoobarArray, 'utf8', true, fdBoth, false, execaSync);
test('result.all is defined, failure, sync', testAllBoth, doubleFoobarStringFull, 'utf8', false, false, true, execaSync);
test('result.all is defined, encoding "buffer", failure, sync', testAllBoth, doubleFoobarUint8ArrayFull, 'buffer', false, false, true, execaSync);
test('result.all is defined, lines, failure, sync', testAllBoth, doubleFoobarArrayFull, 'utf8', true, false, true, execaSync);
test('result.all is defined, lines, fd-specific one, failure, sync', testAllBoth, doubleFoobarArrayFull, 'utf8', fdOne, false, true, execaSync);
test('result.all is defined, lines, fd-specific both, failure, sync', testAllBoth, doubleFoobarArrayFull, 'utf8', fdBoth, false, true, execaSync);
test('result.all is defined, stripFinalNewline, failure, sync', testAllBoth, doubleFoobarString, 'utf8', false, true, true, execaSync);
test('result.all is defined, stripFinalNewline, fd-specific one, failure, sync', testAllBoth, doubleFoobarString, 'utf8', false, fdOne, true, execaSync);
test('result.all is defined, stripFinalNewline, fd-specific both, failure, sync', testAllBoth, doubleFoobarString, 'utf8', false, fdBoth, true, execaSync);
test('result.all is defined, encoding "buffer", stripFinalNewline, failure, sync', testAllBoth, doubleFoobarUint8Array, 'buffer', false, true, true, execaSync);
test('result.all is defined, encoding "buffer", stripFinalNewline, fd-specific one, failure, sync', testAllBoth, doubleFoobarUint8Array, 'buffer', false, fdOne, true, execaSync);
test('result.all is defined, encoding "buffer", stripFinalNewline, fd-specific both, failure, sync', testAllBoth, doubleFoobarUint8Array, 'buffer', false, fdBoth, true, execaSync);
test('result.all is defined, lines, stripFinalNewline, failure, sync', testAllBoth, doubleFoobarArray, 'utf8', true, true, true, execaSync);
test('result.all is defined, lines, fd-specific one, stripFinalNewline, failure, sync', testAllBoth, doubleFoobarArray, 'utf8', fdOne, true, true, execaSync);
test('result.all is defined, lines, fd-specific both, stripFinalNewline, failure, sync', testAllBoth, doubleFoobarArray, 'utf8', fdBoth, true, true, execaSync);
test('result.all is defined, lines, stripFinalNewline, fd-specific one, failure, sync', testAllBoth, doubleFoobarArray, 'utf8', true, fdOne, true, execaSync);
test('result.all is defined, lines, stripFinalNewline, fd-specific both, failure, sync', testAllBoth, doubleFoobarArray, 'utf8', true, fdBoth, true, execaSync);
test.serial('result.all shows both `stdout` and `stderr` intermixed', async t => {
const {all} = await execa('noop-132.js', {all: true});
t.is(all, '1\n2\n3');
});
test.serial('result.all shows both `stdout` and `stderr` not intermixed, sync', t => {
const {all} = execaSync('noop-132.js', {all: true});
t.is(all, '1\n3\n2');
});
const testAllIgnored = async (t, options, execaMethod) => {
const {all} = await execaMethod('noop.js');
t.is(all, undefined);
};
test('result.all is undefined unless opts.all is true', testAllIgnored, {}, execa);
test('result.all is undefined if opts.all is false', testAllIgnored, {all: false}, execa);
test('result.all is undefined if ignored', testAllIgnored, {stdio: 'ignore', all: true}, execa);
test('result.all is undefined unless opts.all is true, sync', testAllIgnored, {}, execaSync);
test('result.all is undefined if opts.all is false, sync', testAllIgnored, {all: false}, execaSync);
test('result.all is undefined if ignored, sync', testAllIgnored, {stdio: 'ignore', all: true}, execaSync);
const testAllProperties = async (t, options) => {
const subprocess = execa('empty.js', {...options, all: true});
t.is(subprocess.all.readableObjectMode, false);
t.is(subprocess.all.readableHighWaterMark, defaultHighWaterMark);
await subprocess;
};
test('subprocess.all has the right objectMode and highWaterMark - stdout + stderr', testAllProperties, {});
test('subprocess.all has the right objectMode and highWaterMark - stdout only', testAllProperties, {stderr: 'ignore'});
test('subprocess.all has the right objectMode and highWaterMark - stderr only', testAllProperties, {stdout: 'ignore'});
const testAllIgnore = async (t, streamName, otherStreamName) => {
const subprocess = execa('noop-both.js', {[otherStreamName]: 'ignore', all: true});
t.is(subprocess[otherStreamName], null);
t.not(subprocess[streamName], null);
t.not(subprocess.all, null);
t.is(subprocess.all.readableObjectMode, subprocess[streamName].readableObjectMode);
t.is(subprocess.all.readableHighWaterMark, subprocess[streamName].readableHighWaterMark);
const result = await subprocess;
t.is(result[otherStreamName], undefined);
t.is(result[streamName], foobarString);
t.is(result.all, foobarString);
};
test('can use all: true with stdout: ignore', testAllIgnore, 'stderr', 'stdout');
test('can use all: true with stderr: ignore', testAllIgnore, 'stdout', 'stderr');
const testAllIgnoreSync = (t, streamName, otherStreamName) => {
const result = execaSync('noop-both.js', {[otherStreamName]: 'ignore', all: true});
t.is(result[otherStreamName], undefined);
t.is(result[streamName], foobarString);
t.is(result.all, foobarString);
};
test('can use all: true with stdout: ignore, sync', testAllIgnoreSync, 'stderr', 'stdout');
test('can use all: true with stderr: ignore, sync', testAllIgnoreSync, 'stdout', 'stderr');
test('can use all: true with stdout: ignore + stderr: ignore', async t => {
const subprocess = execa('noop-both.js', {stdout: 'ignore', stderr: 'ignore', all: true});
t.is(subprocess.stdout, null);
t.is(subprocess.stderr, null);
t.is(subprocess.all, undefined);
const {stdout, stderr, all} = await subprocess;
t.is(stdout, undefined);
t.is(stderr, undefined);
t.is(all, undefined);
});
test('can use all: true with stdout: ignore + stderr: ignore, sync', t => {
const {stdout, stderr, all} = execaSync('noop-both.js', {stdout: 'ignore', stderr: 'ignore', all: true});
t.is(stdout, undefined);
t.is(stderr, undefined);
t.is(all, undefined);
});
================================================
FILE: test/resolve/buffer-end.js
================================================
import {once} from 'node:events';
import {setTimeout} from 'node:timers/promises';
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {fullStdio, getStdio} from '../helpers/stdio.js';
setFixtureDirectory();
const testBufferIgnore = async (t, fdNumber, all) => {
await t.notThrowsAsync(execa('max-buffer.js', [`${fdNumber}`], {...getStdio(fdNumber, 'ignore'), buffer: false, all}));
};
test('Subprocess buffers stdout, which does not prevent exit if ignored', testBufferIgnore, 1, false);
test('Subprocess buffers stderr, which does not prevent exit if ignored', testBufferIgnore, 2, false);
test('Subprocess buffers all, which does not prevent exit if ignored', testBufferIgnore, 1, true);
const testBufferNotRead = async (t, fdNumber, all) => {
const subprocess = execa('max-buffer.js', [`${fdNumber}`], {...fullStdio, buffer: false, all});
await t.notThrowsAsync(subprocess);
};
test('Subprocess buffers stdout, which does not prevent exit if not read and buffer is false', testBufferNotRead, 1, false);
test('Subprocess buffers stderr, which does not prevent exit if not read and buffer is false', testBufferNotRead, 2, false);
test('Subprocess buffers stdio[*], which does not prevent exit if not read and buffer is false', testBufferNotRead, 3, false);
test('Subprocess buffers all, which does not prevent exit if not read and buffer is false', testBufferNotRead, 1, true);
const testBufferRead = async (t, fdNumber, all) => {
const subprocess = execa('max-buffer.js', [`${fdNumber}`], {...fullStdio, buffer: false, all});
const stream = all ? subprocess.all : subprocess.stdio[fdNumber];
stream.resume();
await t.notThrowsAsync(subprocess);
};
test('Subprocess buffers stdout, which does not prevent exit if read and buffer is false', testBufferRead, 1, false);
test('Subprocess buffers stderr, which does not prevent exit if read and buffer is false', testBufferRead, 2, false);
test('Subprocess buffers stdio[*], which does not prevent exit if read and buffer is false', testBufferRead, 3, false);
test('Subprocess buffers all, which does not prevent exit if read and buffer is false', testBufferRead, 1, true);
const testBufferExit = async (t, fdNumber, fixtureName, reject) => {
const subprocess = execa(fixtureName, [`${fdNumber}`], {...fullStdio, reject});
await setTimeout(100);
const {stdio} = await subprocess;
t.is(stdio[fdNumber], 'foobar');
};
test('Subprocess buffers stdout before it is read', testBufferExit, 1, 'noop-delay.js', true);
test('Subprocess buffers stderr before it is read', testBufferExit, 2, 'noop-delay.js', true);
test('Subprocess buffers stdio[*] before it is read', testBufferExit, 3, 'noop-delay.js', true);
test('Subprocess buffers stdout right away, on successfully exit', testBufferExit, 1, 'noop-fd.js', true);
test('Subprocess buffers stderr right away, on successfully exit', testBufferExit, 2, 'noop-fd.js', true);
test('Subprocess buffers stdio[*] right away, on successfully exit', testBufferExit, 3, 'noop-fd.js', true);
test('Subprocess buffers stdout right away, on failure', testBufferExit, 1, 'noop-fail.js', false);
test('Subprocess buffers stderr right away, on failure', testBufferExit, 2, 'noop-fail.js', false);
test('Subprocess buffers stdio[*] right away, on failure', testBufferExit, 3, 'noop-fail.js', false);
const testBufferDirect = async (t, fdNumber) => {
const subprocess = execa('noop-fd.js', [`${fdNumber}`], fullStdio);
const data = await once(subprocess.stdio[fdNumber], 'data');
t.is(data.toString().trim(), 'foobar');
const result = await subprocess;
t.is(result.stdio[fdNumber], 'foobar');
};
test('Subprocess buffers stdout right away, even if directly read', testBufferDirect, 1);
test('Subprocess buffers stderr right away, even if directly read', testBufferDirect, 2);
test('Subprocess buffers stdio[*] right away, even if directly read', testBufferDirect, 3);
const testBufferDestroyOnEnd = async (t, fdNumber) => {
const subprocess = execa('noop-fd.js', [`${fdNumber}`], fullStdio);
const result = await subprocess;
t.is(result.stdio[fdNumber], 'foobar');
t.true(subprocess.stdio[fdNumber].destroyed);
};
test('subprocess.stdout must be read right away', testBufferDestroyOnEnd, 1);
test('subprocess.stderr must be read right away', testBufferDestroyOnEnd, 2);
test('subprocess.stdio[*] must be read right away', testBufferDestroyOnEnd, 3);
================================================
FILE: test/resolve/exit.js
================================================
import process from 'node:process';
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
const isWindows = process.platform === 'win32';
setFixtureDirectory();
test('exitCode is 0 on success', async t => {
const {exitCode} = await execa('noop.js', ['foo']);
t.is(exitCode, 0);
});
const testExitCode = async (t, expectedExitCode) => {
const {exitCode, originalMessage, shortMessage, message} = await t.throwsAsync(
execa('exit.js', [`${expectedExitCode}`]),
);
t.is(exitCode, expectedExitCode);
t.is(originalMessage, undefined);
t.is(shortMessage, `Command failed with exit code ${expectedExitCode}: exit.js ${expectedExitCode}`);
t.is(message, shortMessage);
};
test('exitCode is 2', testExitCode, 2);
test('exitCode is 3', testExitCode, 3);
test('exitCode is 4', testExitCode, 4);
if (!isWindows) {
test('error.signal is SIGINT', async t => {
const subprocess = execa('forever.js');
process.kill(subprocess.pid, 'SIGINT');
const {signal} = await t.throwsAsync(subprocess, {message: /was killed with SIGINT/});
t.is(signal, 'SIGINT');
});
test('error.signalDescription is defined', async t => {
const subprocess = execa('forever.js');
process.kill(subprocess.pid, 'SIGINT');
const {signalDescription} = await t.throwsAsync(subprocess, {message: /User interruption with CTRL-C/});
t.is(signalDescription, 'User interruption with CTRL-C');
});
test('error.signal is SIGTERM', async t => {
const subprocess = execa('forever.js');
process.kill(subprocess.pid, 'SIGTERM');
const {signal} = await t.throwsAsync(subprocess, {message: /was killed with SIGTERM/});
t.is(signal, 'SIGTERM');
});
test('error.signal uses killSignal', async t => {
const {signal} = await t.throwsAsync(execa('forever.js', {killSignal: 'SIGINT', timeout: 1, message: /timed out after/}));
t.is(signal, 'SIGINT');
});
test('exitCode is undefined on signal termination', async t => {
const subprocess = execa('forever.js');
process.kill(subprocess.pid);
const {exitCode} = await t.throwsAsync(subprocess);
t.is(exitCode, undefined);
});
}
test('result.signal is undefined for successful execution', async t => {
const {signal} = await execa('noop.js');
t.is(signal, undefined);
});
test('result.signal is undefined if subprocess failed, but was not killed', async t => {
const {signal} = await t.throwsAsync(execa('fail.js'));
t.is(signal, undefined);
});
test('result.signalDescription is undefined for successful execution', async t => {
const {signalDescription} = await execa('noop.js');
t.is(signalDescription, undefined);
});
================================================
FILE: test/resolve/no-buffer.js
================================================
import {once} from 'node:events';
import test from 'ava';
import getStream from 'get-stream';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {fullStdio} from '../helpers/stdio.js';
import {foobarString, foobarUppercase, foobarUppercaseUint8Array} from '../helpers/input.js';
import {resultGenerator, uppercaseGenerator, uppercaseBufferGenerator} from '../helpers/generator.js';
setFixtureDirectory();
const testLateStream = async (t, fdNumber, all) => {
const subprocess = execa('noop-fd-ipc.js', [`${fdNumber}`, foobarString], {
...fullStdio,
ipc: true,
buffer: false,
all,
});
await subprocess.getOneMessage();
const [output, allOutput] = await Promise.all([
getStream(subprocess.stdio[fdNumber]),
all ? getStream(subprocess.all) : undefined,
subprocess,
]);
t.is(output, '');
if (all) {
t.is(allOutput, '');
}
};
test('Lacks some data when stdout is read too late `buffer` set to `false`', testLateStream, 1, false);
test('Lacks some data when stderr is read too late `buffer` set to `false`', testLateStream, 2, false);
test('Lacks some data when stdio[*] is read too late `buffer` set to `false`', testLateStream, 3, false);
test('Lacks some data when all is read too late `buffer` set to `false`', testLateStream, 1, true);
const getFirstDataEvent = async stream => {
const [output] = await once(stream, 'data');
return output.toString();
};
// eslint-disable-next-line max-params
const testIterationBuffer = async (t, fdNumber, buffer, useDataEvents, all) => {
const subprocess = execa('noop-fd.js', [`${fdNumber}`, foobarString], {...fullStdio, buffer, all});
const getOutput = useDataEvents ? getFirstDataEvent : getStream;
const [result, output, allOutput] = await Promise.all([
subprocess,
getOutput(subprocess.stdio[fdNumber]),
all ? getOutput(subprocess.all) : undefined,
]);
const expectedResult = buffer ? foobarString : undefined;
t.is(result.stdio[fdNumber], expectedResult);
t.is(output, foobarString);
if (all) {
t.is(result.all, expectedResult);
t.is(allOutput, foobarString);
}
};
test('Can iterate stdout when `buffer` set to `false`', testIterationBuffer, 1, false, false, false);
test('Can iterate stderr when `buffer` set to `false`', testIterationBuffer, 2, false, false, false);
test('Can iterate stdio[*] when `buffer` set to `false`', testIterationBuffer, 3, false, false, false);
test('Can iterate all when `buffer` set to `false`', testIterationBuffer, 1, false, false, true);
test('Can iterate stdout when `buffer` set to `true`', testIterationBuffer, 1, true, false, false);
test('Can iterate stderr when `buffer` set to `true`', testIterationBuffer, 2, true, false, false);
test('Can iterate stdio[*] when `buffer` set to `true`', testIterationBuffer, 3, true, false, false);
test('Can iterate all when `buffer` set to `true`', testIterationBuffer, 1, true, false, true);
test('Can listen to `data` events on stdout when `buffer` set to `false`', testIterationBuffer, 1, false, true, false);
test('Can listen to `data` events on stderr when `buffer` set to `false`', testIterationBuffer, 2, false, true, false);
test('Can listen to `data` events on stdio[*] when `buffer` set to `false`', testIterationBuffer, 3, false, true, false);
test('Can listen to `data` events on all when `buffer` set to `false`', testIterationBuffer, 1, false, true, true);
test('Can listen to `data` events on stdout when `buffer` set to `true`', testIterationBuffer, 1, true, true, false);
test('Can listen to `data` events on stderr when `buffer` set to `true`', testIterationBuffer, 2, true, true, false);
test('Can listen to `data` events on stdio[*] when `buffer` set to `true`', testIterationBuffer, 3, true, true, false);
test('Can listen to `data` events on all when `buffer` set to `true`', testIterationBuffer, 1, true, true, true);
const testNoBufferStreamError = async (t, fdNumber, all) => {
const subprocess = execa('noop-fd.js', [`${fdNumber}`], {...fullStdio, buffer: false, all});
const stream = all ? subprocess.all : subprocess.stdio[fdNumber];
const cause = new Error('test');
stream.destroy(cause);
t.like(await t.throwsAsync(subprocess), {cause});
};
test('Listen to stdout errors even when `buffer` is `false`', testNoBufferStreamError, 1, false);
test('Listen to stderr errors even when `buffer` is `false`', testNoBufferStreamError, 2, false);
test('Listen to stdio[*] errors even when `buffer` is `false`', testNoBufferStreamError, 3, false);
test('Listen to all errors even when `buffer` is `false`', testNoBufferStreamError, 1, true);
const testOutput = async (t, buffer, execaMethod) => {
const {stdout} = await execaMethod('noop-fd.js', ['1', foobarString], {buffer});
t.is(stdout, foobarString);
};
test('buffer: true returns output', testOutput, true, execa);
test('buffer: true returns output, fd-specific', testOutput, {stderr: false}, execa);
test('buffer: default returns output', testOutput, undefined, execa);
test('buffer: default returns output, fd-specific', testOutput, {}, execa);
const testNoOutput = async (t, stdioOption, buffer, execaMethod) => {
const {stdout} = await execaMethod('noop.js', {stdout: stdioOption, buffer});
t.is(stdout, undefined);
};
test('buffer: false does not return output', testNoOutput, 'pipe', false, execa);
test('buffer: false does not return output, fd-specific', testNoOutput, 'pipe', {stdout: false}, execa);
test('buffer: false does not return output, stdout undefined', testNoOutput, undefined, false, execa);
test('buffer: false does not return output, stdout null', testNoOutput, null, false, execa);
test('buffer: false does not return output, stdout ["pipe"]', testNoOutput, ['pipe'], false, execa);
test('buffer: false does not return output, stdout [undefined]', testNoOutput, [undefined], false, execa);
test('buffer: false does not return output, stdout [null]', testNoOutput, [null], false, execa);
test('buffer: false does not return output, stdout ["pipe", undefined]', testNoOutput, ['pipe', undefined], false, execa);
test('buffer: false does not return output, sync', testNoOutput, 'pipe', false, execaSync);
test('buffer: false does not return output, fd-specific, sync', testNoOutput, 'pipe', {stdout: false}, execaSync);
test('buffer: false does not return output, stdout undefined, sync', testNoOutput, undefined, false, execaSync);
test('buffer: false does not return output, stdout null, sync', testNoOutput, null, false, execaSync);
test('buffer: false does not return output, stdout ["pipe"], sync', testNoOutput, ['pipe'], false, execaSync);
test('buffer: false does not return output, stdout [undefined], sync', testNoOutput, [undefined], false, execaSync);
test('buffer: false does not return output, stdout [null], sync', testNoOutput, [null], false, execaSync);
test('buffer: false does not return output, stdout ["pipe", undefined], sync', testNoOutput, ['pipe', undefined], false, execaSync);
const testNoOutputFail = async (t, execaMethod) => {
const {exitCode, stdout} = await execaMethod('fail.js', {buffer: false, reject: false});
t.is(exitCode, 2);
t.is(stdout, undefined);
};
test('buffer: false does not return output, failure', testNoOutputFail, execa);
test('buffer: false does not return output, failure, sync', testNoOutputFail, execaSync);
// eslint-disable-next-line max-params
const testNoOutputAll = async (t, buffer, bufferStdout, bufferStderr, execaMethod) => {
const {stdout, stderr, all} = await execaMethod('noop-both.js', {all: true, buffer, stripFinalNewline: false});
t.is(stdout, bufferStdout ? `${foobarString}\n` : undefined);
t.is(stderr, bufferStderr ? `${foobarString}\n` : undefined);
const stdoutStderr = [stdout, stderr].filter(Boolean);
t.is(all, stdoutStderr.length === 0 ? undefined : stdoutStderr.join(''));
};
test('buffer: {}, all: true', testNoOutputAll, {}, true, true, execa);
test('buffer: {stdout: false}, all: true', testNoOutputAll, {stdout: false}, false, true, execa);
test('buffer: {stderr: false}, all: true', testNoOutputAll, {stderr: false}, true, false, execa);
test('buffer: {all: false}, all: true', testNoOutputAll, {all: false}, false, false, execa);
test('buffer: {}, all: true, sync', testNoOutputAll, {}, true, true, execaSync);
test('buffer: {stdout: false}, all: true, sync', testNoOutputAll, {stdout: false}, false, true, execaSync);
test('buffer: {stderr: false}, all: true, sync', testNoOutputAll, {stderr: false}, true, false, execaSync);
test('buffer: {all: false}, all: true, sync', testNoOutputAll, {all: false}, false, false, execaSync);
const testTransform = async (t, objectMode, execaMethod) => {
const lines = [];
const {stdout} = await execaMethod('noop.js', {
buffer: false,
stdout: [uppercaseGenerator(objectMode), resultGenerator(lines)(objectMode)],
});
t.is(stdout, undefined);
t.deepEqual(lines, [foobarUppercase]);
};
test('buffer: false still runs transforms', testTransform, false, execa);
test('buffer: false still runs transforms, objectMode', testTransform, true, execa);
test('buffer: false still runs transforms, sync', testTransform, false, execaSync);
test('buffer: false still runs transforms, objectMode, sync', testTransform, true, execaSync);
const testTransformBinary = async (t, objectMode, execaMethod) => {
const lines = [];
const {stdout} = await execaMethod('noop-fd.js', ['1', foobarString], {
buffer: false,
stdout: [uppercaseBufferGenerator(objectMode, true), resultGenerator(lines)(objectMode)],
encoding: 'buffer',
});
t.is(stdout, undefined);
t.deepEqual(lines, [foobarUppercaseUint8Array]);
};
test('buffer: false still runs transforms, encoding "buffer"', testTransformBinary, false, execa);
test('buffer: false still runs transforms, encoding "buffer", objectMode', testTransformBinary, true, execa);
test('buffer: false still runs transforms, encoding "buffer", sync', testTransformBinary, false, execaSync);
test('buffer: false still runs transforms, encoding "buffer", objectMode, sync', testTransformBinary, true, execaSync);
const testStreamEnd = async (t, fdNumber, buffer) => {
const subprocess = execa('wrong command', {...fullStdio, buffer});
await Promise.all([
t.throwsAsync(subprocess, {message: /wrong command/}),
once(subprocess.stdio[fdNumber], 'end'),
]);
};
test('buffer: false > emits end event on stdout when promise is rejected', testStreamEnd, 1, false);
test('buffer: false > emits end event on stderr when promise is rejected', testStreamEnd, 2, false);
test('buffer: false > emits end event on stdio[*] when promise is rejected', testStreamEnd, 3, false);
test('buffer: true > emits end event on stdout when promise is rejected', testStreamEnd, 1, true);
test('buffer: true > emits end event on stderr when promise is rejected', testStreamEnd, 2, true);
test('buffer: true > emits end event on stdio[*] when promise is rejected', testStreamEnd, 3, true);
================================================
FILE: test/resolve/stdio.js
================================================
import {setTimeout} from 'node:timers/promises';
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {
fullStdio,
getStdio,
prematureClose,
assertEpipe,
} from '../helpers/stdio.js';
import {infiniteGenerator} from '../helpers/generator.js';
setFixtureDirectory();
const getStreamInputSubprocess = fdNumber => execa('stdin-fd.js', [`${fdNumber}`], fdNumber === 3
? getStdio(3, [new Uint8Array(), infiniteGenerator()])
: {});
const getStreamOutputSubprocess = fdNumber => execa('noop-repeat.js', [`${fdNumber}`], fdNumber === 3 ? fullStdio : {});
const assertStreamInputError = (t, {exitCode, signal, isTerminated, failed}) => {
t.is(exitCode, 0);
t.is(signal, undefined);
t.false(isTerminated);
t.true(failed);
};
const assertStreamOutputError = (t, fdNumber, {exitCode, signal, isTerminated, failed, stderr}) => {
if (fdNumber !== 3) {
t.is(exitCode, 1);
}
t.is(signal, undefined);
t.false(isTerminated);
t.true(failed);
assertEpipe(t, stderr, fdNumber);
};
const testStreamInputAbort = async (t, fdNumber) => {
const subprocess = getStreamInputSubprocess(fdNumber);
subprocess.stdio[fdNumber].destroy();
const error = await t.throwsAsync(subprocess, prematureClose);
assertStreamInputError(t, error);
};
test('Aborting stdin should not make the subprocess exit', testStreamInputAbort, 0);
test('Aborting input stdio[*] should not make the subprocess exit', testStreamInputAbort, 3);
const testStreamOutputAbort = async (t, fdNumber) => {
const subprocess = getStreamOutputSubprocess(fdNumber);
subprocess.stdio[fdNumber].destroy();
const error = await t.throwsAsync(subprocess);
assertStreamOutputError(t, fdNumber, error);
};
test('Aborting stdout should not make the subprocess exit', testStreamOutputAbort, 1);
test('Aborting stderr should not make the subprocess exit', testStreamOutputAbort, 2);
test('Aborting output stdio[*] should not make the subprocess exit', testStreamOutputAbort, 3);
const testStreamInputDestroy = async (t, fdNumber) => {
const subprocess = getStreamInputSubprocess(fdNumber);
const cause = new Error('test');
subprocess.stdio[fdNumber].destroy(cause);
const error = await t.throwsAsync(subprocess);
t.is(error.cause, cause);
assertStreamInputError(t, error);
};
test('Destroying stdin should not make the subprocess exit', testStreamInputDestroy, 0);
test('Destroying input stdio[*] should not make the subprocess exit', testStreamInputDestroy, 3);
const testStreamOutputDestroy = async (t, fdNumber) => {
const subprocess = getStreamOutputSubprocess(fdNumber);
const cause = new Error('test');
subprocess.stdio[fdNumber].destroy(cause);
const error = await t.throwsAsync(subprocess);
t.is(error.cause, cause);
assertStreamOutputError(t, fdNumber, error);
};
test('Destroying stdout should not make the subprocess exit', testStreamOutputDestroy, 1);
test('Destroying stderr should not make the subprocess exit', testStreamOutputDestroy, 2);
test('Destroying output stdio[*] should not make the subprocess exit', testStreamOutputDestroy, 3);
const testStreamInputError = async (t, fdNumber) => {
const subprocess = getStreamInputSubprocess(fdNumber);
const cause = new Error('test');
const stream = subprocess.stdio[fdNumber];
stream.emit('error', cause);
stream.end();
const error = await t.throwsAsync(subprocess);
t.is(error.cause, cause);
assertStreamInputError(t, error);
};
test('Errors on stdin should not make the subprocess exit', testStreamInputError, 0);
test('Errors on input stdio[*] should not make the subprocess exit', testStreamInputError, 3);
const testStreamOutputError = async (t, fdNumber) => {
const subprocess = getStreamOutputSubprocess(fdNumber);
const cause = new Error('test');
const stream = subprocess.stdio[fdNumber];
stream.emit('error', cause);
const error = await t.throwsAsync(subprocess);
t.is(error.cause, cause);
assertStreamOutputError(t, fdNumber, error);
};
test('Errors on stdout should make the subprocess exit', testStreamOutputError, 1);
test('Errors on stderr should make the subprocess exit', testStreamOutputError, 2);
test('Errors on output stdio[*] should make the subprocess exit', testStreamOutputError, 3);
const testWaitOnStreamEnd = async (t, fdNumber) => {
const subprocess = execa('stdin-fd.js', [`${fdNumber}`], fullStdio);
await setTimeout(100);
subprocess.stdio[fdNumber].end('foobar');
const {stdout} = await subprocess;
t.is(stdout, 'foobar');
};
test('Subprocess waits on stdin before exiting', testWaitOnStreamEnd, 0);
test('Subprocess waits on stdio[*] before exiting', testWaitOnStreamEnd, 3);
================================================
FILE: test/resolve/wait-abort.js
================================================
import {setImmediate} from 'node:timers/promises';
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {prematureClose} from '../helpers/stdio.js';
import {noopReadable, noopWritable, noopDuplex} from '../helpers/stream.js';
import {
endOptionStream,
destroyOptionStream,
destroySubprocessStream,
getStreamStdio,
} from '../helpers/wait.js';
setFixtureDirectory();
const noop = () => {};
// eslint-disable-next-line max-params
const testStreamAbortWait = async (t, streamMethod, stream, fdNumber, useTransform) => {
const subprocess = execa('noop-stdin-fd.js', [`${fdNumber}`], getStreamStdio(fdNumber, stream, useTransform));
streamMethod({stream, subprocess, fdNumber});
subprocess.stdin.end();
await setImmediate();
stream.destroy();
const {stdout} = await subprocess;
t.is(stdout, '');
t.true(stream.destroyed);
};
test('Keeps running when stdin option is used and subprocess.stdin ends', testStreamAbortWait, noop, noopReadable(), 0, false);
test('Keeps running when stdin option is used and subprocess.stdin Duplex ends', testStreamAbortWait, noop, noopDuplex(), 0, false);
test('Keeps running when input stdio[*] option is used and input subprocess.stdio[*] ends', testStreamAbortWait, noop, noopReadable(), 3, false);
test('Keeps running when stdin option is used and subprocess.stdin ends, with a transform', testStreamAbortWait, noop, noopReadable(), 0, true);
test('Keeps running when stdin option is used and subprocess.stdin Duplex ends, with a transform', testStreamAbortWait, noop, noopDuplex(), 0, true);
test('Keeps running when input stdio[*] option is used and input subprocess.stdio[*] ends, with a transform', testStreamAbortWait, noop, noopReadable(), 3, true);
// eslint-disable-next-line max-params
const testStreamAbortSuccess = async (t, streamMethod, stream, fdNumber, useTransform) => {
const subprocess = execa('noop-stdin-fd.js', [`${fdNumber}`], getStreamStdio(fdNumber, stream, useTransform));
streamMethod({stream, subprocess, fdNumber});
subprocess.stdin.end();
const {stdout} = await subprocess;
t.is(stdout, '');
t.true(stream.destroyed);
};
test('Passes when stdin option aborts', testStreamAbortSuccess, destroyOptionStream, noopReadable(), 0, false);
test('Passes when stdin option Duplex aborts', testStreamAbortSuccess, destroyOptionStream, noopDuplex(), 0, false);
test('Passes when input stdio[*] option aborts', testStreamAbortSuccess, destroyOptionStream, noopReadable(), 3, false);
test('Passes when stdout option ends with no more writes', testStreamAbortSuccess, endOptionStream, noopWritable(), 1, false);
test('Passes when stderr option ends with no more writes', testStreamAbortSuccess, endOptionStream, noopWritable(), 2, false);
test('Passes when output stdio[*] option ends with no more writes', testStreamAbortSuccess, endOptionStream, noopWritable(), 3, false);
test('Passes when subprocess.stdout aborts with no more writes', testStreamAbortSuccess, destroySubprocessStream, noopWritable(), 1, false);
test('Passes when subprocess.stdout Duplex aborts with no more writes', testStreamAbortSuccess, destroySubprocessStream, noopDuplex(), 1, false);
test('Passes when subprocess.stderr aborts with no more writes', testStreamAbortSuccess, destroySubprocessStream, noopWritable(), 2, false);
test('Passes when subprocess.stderr Duplex aborts with no more writes', testStreamAbortSuccess, destroySubprocessStream, noopDuplex(), 2, false);
test('Passes when output subprocess.stdio[*] aborts with no more writes', testStreamAbortSuccess, destroySubprocessStream, noopWritable(), 3, false);
test('Passes when output subprocess.stdio[*] Duplex aborts with no more writes', testStreamAbortSuccess, destroySubprocessStream, noopDuplex(), 3, false);
test('Passes when stdin option aborts, with a transform', testStreamAbortSuccess, destroyOptionStream, noopReadable(), 0, true);
test('Passes when stdin option Duplex aborts, with a transform', testStreamAbortSuccess, destroyOptionStream, noopDuplex(), 0, true);
test('Passes when input stdio[*] option aborts, with a transform', testStreamAbortSuccess, destroyOptionStream, noopReadable(), 3, true);
test('Passes when stdout option ends with no more writes, with a transform', testStreamAbortSuccess, endOptionStream, noopWritable(), 1, true);
test('Passes when stderr option ends with no more writes, with a transform', testStreamAbortSuccess, endOptionStream, noopWritable(), 2, true);
test('Passes when output stdio[*] option ends with no more writes, with a transform', testStreamAbortSuccess, endOptionStream, noopWritable(), 3, true);
test('Passes when subprocess.stdout aborts with no more writes, with a transform', testStreamAbortSuccess, destroySubprocessStream, noopWritable(), 1, true);
test('Passes when subprocess.stdout Duplex aborts with no more writes, with a transform', testStreamAbortSuccess, destroySubprocessStream, noopDuplex(), 1, true);
test('Passes when subprocess.stderr aborts with no more writes, with a transform', testStreamAbortSuccess, destroySubprocessStream, noopWritable(), 2, true);
test('Passes when subprocess.stderr Duplex aborts with no more writes, with a transform', testStreamAbortSuccess, destroySubprocessStream, noopDuplex(), 2, true);
test('Passes when output subprocess.stdio[*] aborts with no more writes, with a transform', testStreamAbortSuccess, destroySubprocessStream, noopWritable(), 3, true);
test('Passes when output subprocess.stdio[*] Duplex aborts with no more writes, with a transform', testStreamAbortSuccess, destroySubprocessStream, noopDuplex(), 3, true);
// eslint-disable-next-line max-params
const testStreamAbortFail = async (t, streamMethod, stream, fdNumber, useTransform) => {
const subprocess = execa('noop-stdin-fd.js', [`${fdNumber}`], getStreamStdio(fdNumber, stream, useTransform));
streamMethod({stream, subprocess, fdNumber});
if (fdNumber !== 0) {
subprocess.stdin.end();
}
const error = await t.throwsAsync(subprocess);
t.like(error, {...prematureClose, exitCode: 0});
t.true(stream.destroyed);
};
test('Throws abort error when subprocess.stdin aborts', testStreamAbortFail, destroySubprocessStream, noopReadable(), 0, false);
test('Throws abort error when subprocess.stdin Duplex aborts', testStreamAbortFail, destroySubprocessStream, noopDuplex(), 0, false);
test('Throws abort error when input subprocess.stdio[*] aborts', testStreamAbortFail, destroySubprocessStream, noopReadable(), 3, false);
test('Throws abort error when stdout option aborts with no more writes', testStreamAbortFail, destroyOptionStream, noopWritable(), 1, false);
test('Throws abort error when stdout option Duplex aborts with no more writes', testStreamAbortFail, destroyOptionStream, noopDuplex(), 1, false);
test('Throws abort error when stderr option aborts with no more writes', testStreamAbortFail, destroyOptionStream, noopWritable(), 2, false);
test('Throws abort error when stderr option Duplex aborts with no more writes', testStreamAbortFail, destroyOptionStream, noopDuplex(), 2, false);
test('Throws abort error when output stdio[*] option aborts with no more writes', testStreamAbortFail, destroyOptionStream, noopWritable(), 3, false);
test('Throws abort error when output stdio[*] Duplex option aborts with no more writes', testStreamAbortFail, destroyOptionStream, noopDuplex(), 3, false);
test('Throws abort error when subprocess.stdin aborts, with a transform', testStreamAbortFail, destroySubprocessStream, noopReadable(), 0, true);
test('Throws abort error when subprocess.stdin Duplex aborts, with a transform', testStreamAbortFail, destroySubprocessStream, noopDuplex(), 0, true);
test('Throws abort error when input subprocess.stdio[*] aborts, with a transform', testStreamAbortFail, destroySubprocessStream, noopReadable(), 3, true);
test('Throws abort error when stdout option aborts with no more writes, with a transform', testStreamAbortFail, destroyOptionStream, noopWritable(), 1, true);
test('Throws abort error when stdout option Duplex aborts with no more writes, with a transform', testStreamAbortFail, destroyOptionStream, noopDuplex(), 1, true);
test('Throws abort error when stderr option aborts with no more writes, with a transform', testStreamAbortFail, destroyOptionStream, noopWritable(), 2, true);
test('Throws abort error when stderr option Duplex aborts with no more writes, with a transform', testStreamAbortFail, destroyOptionStream, noopDuplex(), 2, true);
test('Throws abort error when output stdio[*] option aborts with no more writes, with a transform', testStreamAbortFail, destroyOptionStream, noopWritable(), 3, true);
test('Throws abort error when output stdio[*] Duplex option aborts with no more writes, with a transform', testStreamAbortFail, destroyOptionStream, noopDuplex(), 3, true);
================================================
FILE: test/resolve/wait-epipe.js
================================================
import {setImmediate} from 'node:timers/promises';
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {assertEpipe} from '../helpers/stdio.js';
import {foobarString} from '../helpers/input.js';
import {noopWritable, noopDuplex} from '../helpers/stream.js';
import {
endOptionStream,
destroyOptionStream,
destroySubprocessStream,
getStreamStdio,
} from '../helpers/wait.js';
setFixtureDirectory();
// eslint-disable-next-line max-params
const testStreamEpipeFail = async (t, streamMethod, stream, fdNumber, useTransform) => {
const subprocess = execa('noop-stdin-fd.js', [`${fdNumber}`], getStreamStdio(fdNumber, stream, useTransform));
streamMethod({stream, subprocess, fdNumber});
await setImmediate();
subprocess.stdin.end(foobarString);
const {exitCode, stdio, stderr} = await t.throwsAsync(subprocess);
t.is(exitCode, 1);
t.is(stdio[fdNumber], '');
t.true(stream.destroyed);
assertEpipe(t, stderr, fdNumber);
};
test('Throws EPIPE when stdout option ends with more writes', testStreamEpipeFail, endOptionStream, noopWritable(), 1, false);
test('Throws EPIPE when stdout option aborts with more writes', testStreamEpipeFail, destroyOptionStream, noopWritable(), 1, false);
test('Throws EPIPE when stdout option Duplex aborts with more writes', testStreamEpipeFail, destroyOptionStream, noopDuplex(), 1, false);
test('Throws EPIPE when stderr option ends with more writes', testStreamEpipeFail, endOptionStream, noopWritable(), 2, false);
test('Throws EPIPE when stderr option aborts with more writes', testStreamEpipeFail, destroyOptionStream, noopWritable(), 2, false);
test('Throws EPIPE when stderr option Duplex aborts with more writes', testStreamEpipeFail, destroyOptionStream, noopDuplex(), 2, false);
test('Throws EPIPE when output stdio[*] option ends with more writes', testStreamEpipeFail, endOptionStream, noopWritable(), 3, false);
test('Throws EPIPE when output stdio[*] option aborts with more writes', testStreamEpipeFail, destroyOptionStream, noopWritable(), 3, false);
test('Throws EPIPE when output stdio[*] option Duplex aborts with more writes', testStreamEpipeFail, destroyOptionStream, noopDuplex(), 3, false);
test('Throws EPIPE when subprocess.stdout aborts with more writes', testStreamEpipeFail, destroySubprocessStream, noopWritable(), 1, false);
test('Throws EPIPE when subprocess.stdout Duplex aborts with more writes', testStreamEpipeFail, destroySubprocessStream, noopDuplex(), 1, false);
test('Throws EPIPE when subprocess.stderr aborts with more writes', testStreamEpipeFail, destroySubprocessStream, noopWritable(), 2, false);
test('Throws EPIPE when subprocess.stderr Duplex aborts with more writes', testStreamEpipeFail, destroySubprocessStream, noopDuplex(), 2, false);
test('Throws EPIPE when output subprocess.stdio[*] aborts with more writes', testStreamEpipeFail, destroySubprocessStream, noopWritable(), 3, false);
test('Throws EPIPE when output subprocess.stdio[*] Duplex aborts with more writes', testStreamEpipeFail, destroySubprocessStream, noopDuplex(), 3, false);
test('Throws EPIPE when stdout option ends with more writes, with a transform', testStreamEpipeFail, endOptionStream, noopWritable(), 1, true);
test('Throws EPIPE when stdout option aborts with more writes, with a transform', testStreamEpipeFail, destroyOptionStream, noopWritable(), 1, true);
test('Throws EPIPE when stdout option Duplex aborts with more writes, with a transform', testStreamEpipeFail, destroyOptionStream, noopDuplex(), 1, true);
test('Throws EPIPE when stderr option ends with more writes, with a transform', testStreamEpipeFail, endOptionStream, noopWritable(), 2, true);
test('Throws EPIPE when stderr option aborts with more writes, with a transform', testStreamEpipeFail, destroyOptionStream, noopWritable(), 2, true);
test('Throws EPIPE when stderr option Duplex aborts with more writes, with a transform', testStreamEpipeFail, destroyOptionStream, noopDuplex(), 2, true);
test('Throws EPIPE when output stdio[*] option ends with more writes, with a transform', testStreamEpipeFail, endOptionStream, noopWritable(), 3, true);
test('Throws EPIPE when output stdio[*] option aborts with more writes, with a transform', testStreamEpipeFail, destroyOptionStream, noopWritable(), 3, true);
test('Throws EPIPE when output stdio[*] option Duplex aborts with more writes, with a transform', testStreamEpipeFail, destroyOptionStream, noopDuplex(), 3, true);
test('Throws EPIPE when subprocess.stdout aborts with more writes, with a transform', testStreamEpipeFail, destroySubprocessStream, noopWritable(), 1, true);
test('Throws EPIPE when subprocess.stdout Duplex aborts with more writes, with a transform', testStreamEpipeFail, destroySubprocessStream, noopDuplex(), 1, true);
test('Throws EPIPE when subprocess.stderr aborts with more writes, with a transform', testStreamEpipeFail, destroySubprocessStream, noopWritable(), 2, true);
test('Throws EPIPE when subprocess.stderr Duplex aborts with more writes, with a transform', testStreamEpipeFail, destroySubprocessStream, noopDuplex(), 2, true);
test('Throws EPIPE when output subprocess.stdio[*] aborts with more writes, with a transform', testStreamEpipeFail, destroySubprocessStream, noopWritable(), 3, true);
test('Throws EPIPE when output subprocess.stdio[*] Duplex aborts with more writes, with a transform', testStreamEpipeFail, destroySubprocessStream, noopDuplex(), 3, true);
================================================
FILE: test/resolve/wait-error.js
================================================
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {noopReadable, noopWritable, noopDuplex} from '../helpers/stream.js';
import {destroyOptionStream, destroySubprocessStream, getStreamStdio} from '../helpers/wait.js';
setFixtureDirectory();
// eslint-disable-next-line max-params
const testStreamError = async (t, streamMethod, stream, fdNumber, useTransform) => {
const subprocess = execa('empty.js', getStreamStdio(fdNumber, stream, useTransform));
const cause = new Error('test');
streamMethod({
stream,
subprocess,
fdNumber,
error: cause,
});
const error = await t.throwsAsync(subprocess);
t.is(error.cause, cause);
t.is(error.exitCode, 0);
t.is(error.signal, undefined);
t.false(error.isTerminated);
t.true(error.failed);
t.true(stream.destroyed);
};
test('Throws stream error when stdin option errors', testStreamError, destroyOptionStream, noopReadable(), 0, false);
test('Throws stream error when stdin option Duplex errors', testStreamError, destroyOptionStream, noopDuplex(), 0, false);
test('Throws stream error when stdout option errors', testStreamError, destroyOptionStream, noopWritable(), 1, false);
test('Throws stream error when stdout option Duplex errors', testStreamError, destroyOptionStream, noopDuplex(), 1, false);
test('Throws stream error when stderr option errors', testStreamError, destroyOptionStream, noopWritable(), 2, false);
test('Throws stream error when stderr option Duplex errors', testStreamError, destroyOptionStream, noopDuplex(), 2, false);
test('Throws stream error when output stdio[*] option errors', testStreamError, destroyOptionStream, noopWritable(), 3, false);
test('Throws stream error when output stdio[*] Duplex option errors', testStreamError, destroyOptionStream, noopDuplex(), 3, false);
test('Throws stream error when input stdio[*] option errors', testStreamError, destroyOptionStream, noopReadable(), 3, false);
test('Throws stream error when subprocess.stdin errors', testStreamError, destroySubprocessStream, noopReadable(), 0, false);
test('Throws stream error when subprocess.stdin Duplex errors', testStreamError, destroySubprocessStream, noopDuplex(), 0, false);
test('Throws stream error when subprocess.stdout errors', testStreamError, destroySubprocessStream, noopWritable(), 1, false);
test('Throws stream error when subprocess.stdout Duplex errors', testStreamError, destroySubprocessStream, noopDuplex(), 1, false);
test('Throws stream error when subprocess.stderr errors', testStreamError, destroySubprocessStream, noopWritable(), 2, false);
test('Throws stream error when subprocess.stderr Duplex errors', testStreamError, destroySubprocessStream, noopDuplex(), 2, false);
test('Throws stream error when output subprocess.stdio[*] errors', testStreamError, destroySubprocessStream, noopWritable(), 3, false);
test('Throws stream error when output subprocess.stdio[*] Duplex errors', testStreamError, destroySubprocessStream, noopDuplex(), 3, false);
test('Throws stream error when input subprocess.stdio[*] errors', testStreamError, destroySubprocessStream, noopReadable(), 3, false);
test('Throws stream error when stdin option errors, with a transform', testStreamError, destroyOptionStream, noopReadable(), 0, true);
test('Throws stream error when stdin option Duplex errors, with a transform', testStreamError, destroyOptionStream, noopDuplex(), 0, true);
test('Throws stream error when stdout option errors, with a transform', testStreamError, destroyOptionStream, noopWritable(), 1, true);
test('Throws stream error when stdout option Duplex errors, with a transform', testStreamError, destroyOptionStream, noopDuplex(), 1, true);
test('Throws stream error when stderr option errors, with a transform', testStreamError, destroyOptionStream, noopWritable(), 2, true);
test('Throws stream error when stderr option Duplex errors, with a transform', testStreamError, destroyOptionStream, noopDuplex(), 2, true);
test('Throws stream error when output stdio[*] option errors, with a transform', testStreamError, destroyOptionStream, noopWritable(), 3, true);
test('Throws stream error when output stdio[*] Duplex option errors, with a transform', testStreamError, destroyOptionStream, noopDuplex(), 3, true);
test('Throws stream error when input stdio[*] option errors, with a transform', testStreamError, destroyOptionStream, noopReadable(), 3, true);
test('Throws stream error when subprocess.stdin errors, with a transform', testStreamError, destroySubprocessStream, noopReadable(), 0, true);
test('Throws stream error when subprocess.stdin Duplex errors, with a transform', testStreamError, destroySubprocessStream, noopDuplex(), 0, true);
test('Throws stream error when subprocess.stdout errors, with a transform', testStreamError, destroySubprocessStream, noopWritable(), 1, true);
test('Throws stream error when subprocess.stdout Duplex errors, with a transform', testStreamError, destroySubprocessStream, noopDuplex(), 1, true);
test('Throws stream error when subprocess.stderr errors, with a transform', testStreamError, destroySubprocessStream, noopWritable(), 2, true);
test('Throws stream error when subprocess.stderr Duplex errors, with a transform', testStreamError, destroySubprocessStream, noopDuplex(), 2, true);
test('Throws stream error when output subprocess.stdio[*] errors, with a transform', testStreamError, destroySubprocessStream, noopWritable(), 3, true);
test('Throws stream error when output subprocess.stdio[*] Duplex errors, with a transform', testStreamError, destroySubprocessStream, noopDuplex(), 3, true);
test('Throws stream error when input subprocess.stdio[*] errors, with a transform', testStreamError, destroySubprocessStream, noopReadable(), 3, true);
================================================
FILE: test/resolve/wait-subprocess.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {getStdio} from '../helpers/stdio.js';
setFixtureDirectory();
const testIgnore = async (t, fdNumber, execaMethod) => {
const result = await execaMethod('noop.js', getStdio(fdNumber, 'ignore'));
t.is(result.stdio[fdNumber], undefined);
};
test('stdout is undefined if ignored', testIgnore, 1, execa);
test('stderr is undefined if ignored', testIgnore, 2, execa);
test('stdio[*] is undefined if ignored', testIgnore, 3, execa);
test('stdout is undefined if ignored - sync', testIgnore, 1, execaSync);
test('stderr is undefined if ignored - sync', testIgnore, 2, execaSync);
test('stdio[*] is undefined if ignored - sync', testIgnore, 3, execaSync);
const testSubprocessEventsCleanup = async (t, fixtureName) => {
const subprocess = execa(fixtureName, {reject: false});
t.deepEqual(subprocess.eventNames().map(String).sort(), ['error', 'exit', 'spawn']);
await subprocess;
t.deepEqual(subprocess.eventNames(), []);
};
test('subprocess listeners are cleaned up on success', testSubprocessEventsCleanup, 'empty.js');
test('subprocess listeners are cleaned up on failure', testSubprocessEventsCleanup, 'fail.js');
test('Aborting stdout should not abort stderr nor all', async t => {
const subprocess = execa('empty.js', {all: true});
subprocess.stdout.destroy();
t.false(subprocess.stdout.readable);
t.true(subprocess.stderr.readable);
t.true(subprocess.all.readable);
await subprocess;
t.false(subprocess.stdout.readableEnded);
t.is(subprocess.stdout.errored, null);
t.true(subprocess.stdout.destroyed);
t.true(subprocess.stderr.readableEnded);
t.is(subprocess.stderr.errored, null);
t.true(subprocess.stderr.destroyed);
t.true(subprocess.all.readableEnded);
t.is(subprocess.all.errored, null);
t.true(subprocess.all.destroyed);
});
================================================
FILE: test/return/duration.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {getEarlyErrorSubprocess, getEarlyErrorSubprocessSync} from '../helpers/early-error.js';
setFixtureDirectory();
const assertDurationMs = (t, durationMs) => {
t.is(typeof durationMs, 'number');
t.true(Number.isFinite(durationMs));
t.not(durationMs, 0);
t.true(durationMs > 0);
};
test('result.durationMs', async t => {
const {durationMs} = await execa('empty.js');
assertDurationMs(t, durationMs);
});
test('result.durationMs - sync', t => {
const {durationMs} = execaSync('empty.js');
assertDurationMs(t, durationMs);
});
test('error.durationMs', async t => {
const {durationMs} = await t.throwsAsync(execa('fail.js'));
assertDurationMs(t, durationMs);
});
test('error.durationMs - sync', t => {
const {durationMs} = t.throws(() => {
execaSync('fail.js');
});
assertDurationMs(t, durationMs);
});
test('error.durationMs - early validation', async t => {
const {durationMs} = await t.throwsAsync(getEarlyErrorSubprocess());
assertDurationMs(t, durationMs);
});
test('error.durationMs - early validation, sync', t => {
const {durationMs} = t.throws(getEarlyErrorSubprocessSync);
assertDurationMs(t, durationMs);
});
test('error.durationMs - unpipeSignal', async t => {
const {durationMs} = await t.throwsAsync(execa('noop.js').pipe('stdin.js', {signal: AbortSignal.abort()}));
assertDurationMs(t, durationMs);
});
test('error.durationMs - pipe validation', async t => {
const {durationMs} = await t.throwsAsync(execa('noop.js').pipe(false));
assertDurationMs(t, durationMs);
});
test.serial('result.durationMs is accurate', async t => {
const minDurationMs = 1e3;
const {durationMs} = await execa('delay.js', [minDurationMs]);
t.true(durationMs >= minDurationMs);
});
================================================
FILE: test/return/early-error.js
================================================
import {arch} from 'node:os';
import process from 'node:process';
import test from 'ava';
import {execa, execaSync, $} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {fullStdio} from '../helpers/stdio.js';
import {
earlyErrorOptions,
getEarlyErrorSubprocess,
getEarlyErrorSubprocessSync,
expectedEarlyError,
expectedEarlyErrorSync,
} from '../helpers/early-error.js';
setFixtureDirectory();
const isWindows = process.platform === 'win32';
const ENOENT_REGEXP = isWindows ? /failed with exit code 1/ : /spawn.* ENOENT/;
test('execaSync() throws error if ENOENT', t => {
t.throws(() => {
execaSync('foo');
}, {message: ENOENT_REGEXP});
});
const testEarlyErrorShape = async (t, reject) => {
const subprocess = getEarlyErrorSubprocess({reject});
t.notThrows(() => {
// eslint-disable-next-line promise/prefer-await-to-then
subprocess.catch(() => {});
subprocess.unref();
subprocess.on('error', () => {});
});
};
test('child_process.spawn() early errors have correct shape', testEarlyErrorShape, true);
test('child_process.spawn() early errors have correct shape - reject false', testEarlyErrorShape, false);
test('child_process.spawn() early errors are propagated', async t => {
await t.throwsAsync(getEarlyErrorSubprocess(), expectedEarlyError);
});
test('child_process.spawn() early errors are returned', async t => {
const {failed} = await getEarlyErrorSubprocess({reject: false});
t.true(failed);
});
test('child_process.spawnSync() early errors are propagated with a correct shape', t => {
t.throws(getEarlyErrorSubprocessSync, expectedEarlyErrorSync);
});
test('child_process.spawnSync() early errors are propagated with a correct shape - reject false', t => {
const {failed} = getEarlyErrorSubprocessSync({reject: false});
t.true(failed);
});
if (!isWindows) {
test('execa() rejects if running non-executable', async t => {
await t.throwsAsync(execa('non-executable.js'));
});
test('execa() rejects with correct error and doesn\'t throw if running non-executable with input', async t => {
await t.throwsAsync(execa('non-executable.js', {input: 'Hey!'}), {message: /EACCES/});
});
if (arch() === 'x64') {
test('write to fast-exit subprocess', async t => {
// Try-catch here is necessary, because this test is not 100% accurate
// Sometimes subprocess can manage to accept input before exiting
try {
await execa(`fast-exit-${process.platform}`, [], {input: 'data'});
t.pass();
} catch (error) {
t.is(error.code, 'EPIPE');
}
});
}
}
const testEarlyErrorPipe = async (t, getSubprocess) => {
await t.throwsAsync(getSubprocess(), expectedEarlyError);
};
test('child_process.spawn() early errors on source can use .pipe()', testEarlyErrorPipe, () => getEarlyErrorSubprocess().pipe(execa('empty.js')));
test('child_process.spawn() early errors on destination can use .pipe()', testEarlyErrorPipe, () => execa('empty.js').pipe(getEarlyErrorSubprocess()));
test('child_process.spawn() early errors on source and destination can use .pipe()', testEarlyErrorPipe, () => getEarlyErrorSubprocess().pipe(getEarlyErrorSubprocess()));
test('child_process.spawn() early errors can use .pipe() multiple times', testEarlyErrorPipe, () => getEarlyErrorSubprocess().pipe(getEarlyErrorSubprocess()).pipe(getEarlyErrorSubprocess()));
test('child_process.spawn() early errors can use .pipe``', testEarlyErrorPipe, () => $(earlyErrorOptions)`empty.js`.pipe(earlyErrorOptions)`empty.js`);
test('child_process.spawn() early errors can use .pipe`` multiple times', testEarlyErrorPipe, () => $(earlyErrorOptions)`empty.js`.pipe(earlyErrorOptions)`empty.js`.pipe`empty.js`);
const testEarlyErrorConvertor = async (t, streamMethod) => {
const subprocess = getEarlyErrorSubprocess();
const stream = subprocess[streamMethod]();
stream.on('close', () => {});
stream.read?.();
stream.write?.('.');
await t.throwsAsync(subprocess);
};
test('child_process.spawn() early errors can use .readable()', testEarlyErrorConvertor, 'readable');
test('child_process.spawn() early errors can use .writable()', testEarlyErrorConvertor, 'writable');
test('child_process.spawn() early errors can use .duplex()', testEarlyErrorConvertor, 'duplex');
const testEarlyErrorStream = async (t, getStreamProperty, options) => {
const subprocess = getEarlyErrorSubprocess(options);
const stream = getStreamProperty(subprocess);
stream.on('close', () => {});
stream.read?.();
stream.end?.();
await t.throwsAsync(subprocess);
};
test('child_process.spawn() early errors can use .stdin', testEarlyErrorStream, ({stdin}) => stdin);
test('child_process.spawn() early errors can use .stdout', testEarlyErrorStream, ({stdout}) => stdout);
test('child_process.spawn() early errors can use .stderr', testEarlyErrorStream, ({stderr}) => stderr);
test('child_process.spawn() early errors can use .stdio[1]', testEarlyErrorStream, ({stdio}) => stdio[1]);
test('child_process.spawn() early errors can use .stdio[3]', testEarlyErrorStream, ({stdio}) => stdio[3], fullStdio);
test('child_process.spawn() early errors can use .all', testEarlyErrorStream, ({all}) => all, {all: true});
================================================
FILE: test/return/final-error.js
================================================
import test from 'ava';
import {
execa,
execaSync,
ExecaError,
ExecaSyncError,
} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
import {getEarlyErrorSubprocess, getEarlyErrorSubprocessSync} from '../helpers/early-error.js';
setFixtureDirectory();
const testUnusualError = async (t, error, expectedOriginalMessage = String(error)) => {
const subprocess = execa('empty.js');
subprocess.emit('error', error);
const {originalMessage, shortMessage, message} = await t.throwsAsync(subprocess);
t.is(originalMessage, expectedOriginalMessage === '' ? undefined : expectedOriginalMessage);
t.true(shortMessage.includes(expectedOriginalMessage));
t.is(message, shortMessage);
};
test('error instance can be null', testUnusualError, null);
test('error instance can be false', testUnusualError, false);
test('error instance can be a string', testUnusualError, 'test');
test('error instance can be a number', testUnusualError, 0);
test('error instance can be a BigInt', testUnusualError, 0n);
test('error instance can be a symbol', testUnusualError, Symbol('test'));
test('error instance can be a function', testUnusualError, () => {});
test('error instance can be an array', testUnusualError, ['test', 'test']);
// eslint-disable-next-line unicorn/error-message
test('error instance can be an error with an empty message', testUnusualError, new Error(''), '');
test('error instance can be undefined', testUnusualError, undefined, 'undefined');
test('error instance can be a plain object', async t => {
const subprocess = execa('empty.js');
subprocess.emit('error', {message: foobarString});
await t.throwsAsync(subprocess, {message: new RegExp(foobarString)});
});
const runAndFail = (t, fixtureName, argument, error) => {
const subprocess = execa(fixtureName, [argument]);
subprocess.emit('error', error);
return t.throwsAsync(subprocess);
};
const testErrorCopy = async (t, getPreviousArgument, argument = 'two') => {
const fixtureName = 'empty.js';
const firstArgument = 'foo';
const previousArgument = await getPreviousArgument(t, fixtureName);
const previousError = await runAndFail(t, fixtureName, firstArgument, previousArgument);
const error = await runAndFail(t, fixtureName, argument, previousError);
const message = `Command failed: ${fixtureName} ${argument}\n${foobarString}`;
t.not(error, previousError);
t.is(error.cause, previousError);
t.is(error.command, `${fixtureName} ${argument}`);
t.is(error.message, message);
t.true(error.stack.includes(message));
t.is(error.shortMessage, message);
t.is(error.originalMessage, foobarString);
};
test('error instance can be shared', testErrorCopy, () => new Error(foobarString));
test('error TypeError can be shared', testErrorCopy, () => new TypeError(foobarString));
test('error string can be shared', testErrorCopy, () => foobarString);
test('error copy can be shared', testErrorCopy, (t, fixtureName) => runAndFail(t, fixtureName, 'bar', new Error(foobarString)));
test('error with same message can be shared', testErrorCopy, () => new Error(foobarString), 'foo');
test('error.cause is not set if error.exitCode is not 0', async t => {
const {exitCode, cause} = await t.throwsAsync(execa('fail.js'));
t.is(exitCode, 2);
t.is(cause, undefined);
});
test('error.cause is not set if error.isTerminated', async t => {
const subprocess = execa('forever.js');
subprocess.kill();
const {isTerminated, cause} = await t.throwsAsync(subprocess);
t.true(isTerminated);
t.is(cause, undefined);
});
test('error.cause is not set if error.timedOut', async t => {
const {timedOut, cause} = await t.throwsAsync(execa('forever.js', {timeout: 1}));
t.true(timedOut);
t.is(cause, undefined);
});
test('error.cause is set on error event', async t => {
const subprocess = execa('empty.js');
const error = new Error(foobarString);
subprocess.emit('error', error);
const {cause} = await t.throwsAsync(subprocess);
t.is(cause, error);
});
test('error.cause is set if error.isCanceled', async t => {
const controller = new AbortController();
const subprocess = execa('forever.js', {cancelSignal: controller.signal});
const cause = new Error('test');
controller.abort(cause);
const error = await t.throwsAsync(subprocess);
t.true(error.isCanceled);
t.true(error.isTerminated);
t.is(error.signal, 'SIGTERM');
t.is(error.cause, cause);
});
test('error.cause is not set if error.isTerminated with .kill(error)', async t => {
const subprocess = execa('forever.js');
const error = new Error('test');
subprocess.kill(error);
const {isTerminated, cause} = await t.throwsAsync(subprocess);
t.true(isTerminated);
t.is(cause, error);
});
test('Error is instanceof ExecaError', async t => {
await t.throwsAsync(execa('fail.js'), {instanceOf: ExecaError});
});
test('Early error is instanceof ExecaError', async t => {
await t.throwsAsync(getEarlyErrorSubprocess(), {instanceOf: ExecaError});
});
test('Error is instanceof ExecaSyncError', t => {
t.throws(() => {
execaSync('fail.js');
}, {instanceOf: ExecaSyncError});
});
test('Early error is instanceof ExecaSyncError', t => {
t.throws(() => {
getEarlyErrorSubprocessSync();
}, {instanceOf: ExecaSyncError});
});
test('Pipe error is instanceof ExecaError', async t => {
await t.throwsAsync(execa('empty.js').pipe(false), {instanceOf: ExecaError});
});
const assertNameShape = (t, error) => {
t.false(Object.hasOwn(error, 'name'));
t.true(Object.hasOwn(Object.getPrototypeOf(error), 'name'));
t.false(propertyIsEnumerable.call(Object.getPrototypeOf(error), 'name'));
};
const {propertyIsEnumerable} = Object.prototype;
test('error.name is properly set', async t => {
const error = await t.throwsAsync(execa('fail.js'));
t.is(error.name, 'ExecaError');
assertNameShape(t, error);
});
test('error.name is properly set - sync', async t => {
const error = await t.throws(() => {
execaSync('fail.js');
});
t.is(error.name, 'ExecaSyncError');
assertNameShape(t, error);
});
================================================
FILE: test/return/message.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {fullStdio, getStdio} from '../helpers/stdio.js';
import {foobarString, foobarObject, foobarObjectInspect} from '../helpers/input.js';
import {QUOTE} from '../helpers/verbose.js';
import {noopGenerator, outputObjectGenerator} from '../helpers/generator.js';
setFixtureDirectory();
test('error.message contains the command', async t => {
await t.throwsAsync(execa('exit.js', ['2', 'foo', 'bar']), {message: /exit.js 2 foo bar/});
});
// eslint-disable-next-line max-params
const testStdioMessage = async (t, encoding, all, objectMode, execaMethod) => {
const {exitCode, message} = await execaMethod('echo-fail.js', {
...getStdio(1, noopGenerator(objectMode, false, true), 4),
encoding,
all,
reject: false,
});
t.is(exitCode, 1);
const output = all ? 'stdout\nstderr' : 'stderr\n\nstdout';
t.true(message.endsWith(`echo-fail.js\n\n${output}\n\nfd3`));
};
test('error.message contains stdout/stderr/stdio if available', testStdioMessage, 'utf8', false, false, execa);
test('error.message contains stdout/stderr/stdio even with encoding "buffer"', testStdioMessage, 'buffer', false, false, execa);
test('error.message contains all if available', testStdioMessage, 'utf8', true, false, execa);
test('error.message contains all even with encoding "buffer"', testStdioMessage, 'buffer', true, false, execa);
test('error.message contains stdout/stderr/stdio if available, objectMode', testStdioMessage, 'utf8', false, true, execa);
test('error.message contains stdout/stderr/stdio even with encoding "buffer", objectMode', testStdioMessage, 'buffer', false, true, execa);
test('error.message contains all if available, objectMode', testStdioMessage, 'utf8', true, true, execa);
test('error.message contains all even with encoding "buffer", objectMode', testStdioMessage, 'buffer', true, true, execa);
test('error.message contains stdout/stderr/stdio if available, sync', testStdioMessage, 'utf8', false, false, execaSync);
test('error.message contains stdout/stderr/stdio even with encoding "buffer", sync', testStdioMessage, 'buffer', false, false, execaSync);
test('error.message contains all if available, sync', testStdioMessage, 'utf8', true, false, execaSync);
test('error.message contains all even with encoding "buffer", sync', testStdioMessage, 'buffer', true, false, execaSync);
test('error.message contains stdout/stderr/stdio if available, objectMode, sync', testStdioMessage, 'utf8', false, true, execaSync);
test('error.message contains stdout/stderr/stdio even with encoding "buffer", objectMode, sync', testStdioMessage, 'buffer', false, true, execaSync);
test('error.message contains all if available, objectMode, sync', testStdioMessage, 'utf8', true, true, execaSync);
test('error.message contains all even with encoding "buffer", objectMode, sync', testStdioMessage, 'buffer', true, true, execaSync);
const testLinesMessage = async (t, encoding, stripFinalNewline, execaMethod) => {
const {failed, message} = await execaMethod('noop-fail.js', ['1', `${foobarString}\n${foobarString}\n`], {
lines: true,
encoding,
stripFinalNewline,
reject: false,
});
t.true(failed);
t.true(message.endsWith(`noop-fail.js 1 ${QUOTE}${foobarString}\\n${foobarString}\\n${QUOTE}\n\n${foobarString}\n${foobarString}`));
};
test('error.message handles "lines: true"', testLinesMessage, 'utf8', false, execa);
test('error.message handles "lines: true", stripFinalNewline', testLinesMessage, 'utf8', true, execa);
test('error.message handles "lines: true", buffer', testLinesMessage, 'buffer', false, execa);
test('error.message handles "lines: true", buffer, stripFinalNewline', testLinesMessage, 'buffer', true, execa);
test('error.message handles "lines: true", sync', testLinesMessage, 'utf8', false, execaSync);
test('error.message handles "lines: true", stripFinalNewline, sync', testLinesMessage, 'utf8', true, execaSync);
test('error.message handles "lines: true", buffer, sync', testLinesMessage, 'buffer', false, execaSync);
test('error.message handles "lines: true", buffer, stripFinalNewline, sync', testLinesMessage, 'buffer', true, execaSync);
const testPartialIgnoreMessage = async (t, fdNumber, stdioOption, output) => {
const {message} = await t.throwsAsync(execa('echo-fail.js', getStdio(fdNumber, stdioOption, 4)));
t.true(message.endsWith(`echo-fail.js\n\n${output}\n\nfd3`));
};
test('error.message does not contain stdout if not available', testPartialIgnoreMessage, 1, 'ignore', 'stderr');
test('error.message does not contain stderr if not available', testPartialIgnoreMessage, 2, 'ignore', 'stdout');
test('error.message does not contain stdout if it is an object', testPartialIgnoreMessage, 1, outputObjectGenerator(), 'stderr');
test('error.message does not contain stderr if it is an object', testPartialIgnoreMessage, 2, outputObjectGenerator(), 'stdout');
const testFullIgnoreMessage = async (t, options, resultProperty) => {
const {[resultProperty]: message} = await t.throwsAsync(execa('echo-fail.js', options));
t.false(message.includes('stderr'));
t.false(message.includes('stdout'));
t.false(message.includes('fd3'));
};
test('error.message does not contain stdout/stderr/stdio if not available', testFullIgnoreMessage, {stdio: 'ignore'}, 'message');
test('error.shortMessage does not contain stdout/stderr/stdio', testFullIgnoreMessage, fullStdio, 'shortMessage');
const testErrorMessageConsistent = async (t, stdout) => {
const {message} = await t.throwsAsync(execa('noop-both-fail-strict.js', [stdout, 'stderr']));
t.true(message.endsWith(' stderr\n\nstderr\n\nstdout'));
};
test('error.message newlines are consistent - no newline', testErrorMessageConsistent, 'stdout');
test('error.message newlines are consistent - newline', testErrorMessageConsistent, 'stdout\n');
test('Original error.message is kept', async t => {
const {originalMessage} = await t.throwsAsync(execa('noop.js', {uid: true}));
t.is(originalMessage, 'The "options.uid" property must be int32. Received type boolean (true)');
});
const testIpcOutput = async (t, doubles, ipcInput, returnedMessage) => {
const fixtureName = doubles ? 'ipc-echo-twice-fail.js' : 'ipc-echo-fail.js';
const {exitCode, message, ipcOutput} = await t.throwsAsync(execa(fixtureName, {ipcInput}));
t.is(exitCode, 1);
t.true(message.endsWith(`\n\n${doubles ? `${returnedMessage}\n${returnedMessage}` : returnedMessage}`));
t.deepEqual(ipcOutput, doubles ? [ipcInput, ipcInput] : [ipcInput]);
};
test('error.message contains IPC messages, single string', testIpcOutput, false, foobarString, foobarString);
test('error.message contains IPC messages, two strings', testIpcOutput, true, foobarString, foobarString);
test('error.message contains IPC messages, single object', testIpcOutput, false, foobarObject, foobarObjectInspect);
test('error.message contains IPC messages, two objects', testIpcOutput, true, foobarObject, foobarObjectInspect);
test('error.message contains IPC messages, multiline string', testIpcOutput, false, `${foobarString}\n${foobarString}`, `${foobarString}\n${foobarString}`);
test('error.message contains IPC messages, control characters', testIpcOutput, false, '\0', '\\u0000');
test('error.message does not contain IPC messages, buffer false', async t => {
const {exitCode, message, ipcOutput} = await t.throwsAsync(execa('ipc-echo-fail.js', {ipcInput: foobarString, buffer: false}));
t.is(exitCode, 1);
t.true(message.endsWith('ipc-echo-fail.js'));
t.deepEqual(ipcOutput, []);
});
================================================
FILE: test/return/output.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {fullStdio, getStdio} from '../helpers/stdio.js';
import {foobarString} from '../helpers/input.js';
import {
getEarlyErrorSubprocess,
getEarlyErrorSubprocessSync,
expectedEarlyError,
expectedEarlyErrorSync,
} from '../helpers/early-error.js';
setFixtureDirectory();
const testOutput = async (t, fdNumber, execaMethod) => {
const {stdout, stderr, stdio} = await execaMethod('noop-fd.js', [`${fdNumber}`, foobarString], fullStdio);
t.is(stdio[fdNumber], foobarString);
if (fdNumber === 1) {
t.is(stdio[fdNumber], stdout);
} else if (fdNumber === 2) {
t.is(stdio[fdNumber], stderr);
}
};
test('can return stdout', testOutput, 1, execa);
test('can return stderr', testOutput, 2, execa);
test('can return output stdio[*]', testOutput, 3, execa);
test('can return stdout, sync', testOutput, 1, execaSync);
test('can return stderr, sync', testOutput, 2, execaSync);
test('can return output stdio[*], sync', testOutput, 3, execaSync);
const testNoStdin = async (t, execaMethod) => {
const {stdio} = await execaMethod('noop.js', [foobarString]);
t.is(stdio[0], undefined);
};
test('cannot return stdin', testNoStdin, execa);
test('cannot return stdin, sync', testNoStdin, execaSync);
test('cannot return input stdio[*]', async t => {
const {stdio} = await execa('stdin-fd.js', ['3'], getStdio(3, [[foobarString]]));
t.is(stdio[3], undefined);
});
test('do not try to consume streams twice', async t => {
const subprocess = execa('noop.js', ['foo']);
const {stdout} = await subprocess;
const {stdout: stdout2} = await subprocess;
t.is(stdout, 'foo');
t.is(stdout2, 'foo');
});
const testEmptyErrorStdio = async (t, execaMethod) => {
const {failed, stdout, stderr, stdio} = await execaMethod('fail.js', {reject: false});
t.true(failed);
t.is(stdout, '');
t.is(stderr, '');
t.deepEqual(stdio, [undefined, '', '']);
};
test('empty error.stdout/stderr/stdio', testEmptyErrorStdio, execa);
test('empty error.stdout/stderr/stdio, sync', testEmptyErrorStdio, execaSync);
const testUndefinedErrorStdio = async (t, execaMethod) => {
const {stdout, stderr, stdio} = await execaMethod('empty.js', {stdio: 'ignore'});
t.is(stdout, undefined);
t.is(stderr, undefined);
t.deepEqual(stdio, [undefined, undefined, undefined]);
};
test('undefined error.stdout/stderr/stdio', testUndefinedErrorStdio, execa);
test('undefined error.stdout/stderr/stdio, sync', testUndefinedErrorStdio, execaSync);
const testEmptyAll = async (t, options, expectedValue, execaMethod) => {
const {all} = await execaMethod('empty.js', options);
t.is(all, expectedValue);
};
test('empty error.all', testEmptyAll, {all: true}, '', execa);
test('undefined error.all', testEmptyAll, {}, undefined, execa);
test('ignored error.all', testEmptyAll, {all: true, stdio: 'ignore'}, undefined, execa);
test('empty error.all, sync', testEmptyAll, {all: true}, '', execaSync);
test('undefined error.all, sync', testEmptyAll, {}, undefined, execaSync);
test('ignored error.all, sync', testEmptyAll, {all: true, stdio: 'ignore'}, undefined, execaSync);
test('empty error.stdio[0] even with input', async t => {
const {stdio} = await t.throwsAsync(execa('fail.js', {input: 'test'}));
t.is(stdio[0], undefined);
});
const validateSpawnErrorStdio = (t, {stdout, stderr, stdio, all}) => {
t.is(stdout, undefined);
t.is(stderr, undefined);
t.is(all, undefined);
t.deepEqual(stdio, [undefined, undefined, undefined]);
};
test('stdout/stderr/all/stdio on subprocess spawning errors', async t => {
const error = await t.throwsAsync(getEarlyErrorSubprocess({all: true}));
t.like(error, expectedEarlyError);
validateSpawnErrorStdio(t, error);
});
test('stdout/stderr/all/stdio on subprocess spawning errors, sync', t => {
const error = t.throws(() => getEarlyErrorSubprocessSync({all: true}));
t.like(error, expectedEarlyErrorSync);
validateSpawnErrorStdio(t, error);
});
const testErrorOutput = async (t, execaMethod) => {
const {failed, stdout, stderr, stdio} = await execaMethod('echo-fail.js', {...fullStdio, reject: false});
t.true(failed);
t.is(stdout, 'stdout');
t.is(stderr, 'stderr');
t.deepEqual(stdio, [undefined, 'stdout', 'stderr', 'fd3']);
};
test('error.stdout/stderr/stdio is defined', testErrorOutput, execa);
test('error.stdout/stderr/stdio is defined, sync', testErrorOutput, execaSync);
test('ipc on subprocess spawning errors', async t => {
const error = await t.throwsAsync(getEarlyErrorSubprocess({ipc: true}));
t.like(error, expectedEarlyError);
t.deepEqual(error.ipcOutput, []);
});
const testEarlyErrorNoIpc = async (t, options) => {
const error = await t.throwsAsync(getEarlyErrorSubprocess(options));
t.like(error, expectedEarlyError);
t.deepEqual(error.ipcOutput, []);
};
test('ipc on subprocess spawning errors, ipc false', testEarlyErrorNoIpc, {ipc: false});
test('ipc on subprocess spawning errors, buffer false', testEarlyErrorNoIpc, {buffer: false});
================================================
FILE: test/return/reject.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
setFixtureDirectory();
test('skip throwing when using reject option', async t => {
const {exitCode} = await execa('fail.js', {reject: false});
t.is(exitCode, 2);
});
test('skip throwing when using reject option in sync mode', t => {
const {exitCode} = execaSync('fail.js', {reject: false});
t.is(exitCode, 2);
});
================================================
FILE: test/return/result.js
================================================
import process from 'node:process';
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {fullStdio} from '../helpers/stdio.js';
const isWindows = process.platform === 'win32';
setFixtureDirectory();
const testSuccessShape = async (t, execaMethod) => {
const result = await execaMethod('empty.js', {...fullStdio, all: true});
t.deepEqual(Reflect.ownKeys(result), [
'command',
'escapedCommand',
'cwd',
'durationMs',
'failed',
'timedOut',
'isCanceled',
'isGracefullyCanceled',
'isTerminated',
'isMaxBuffer',
'isForcefullyTerminated',
'exitCode',
'stdout',
'stderr',
'all',
'stdio',
'ipcOutput',
'pipedFrom',
]);
};
test('Return value properties are not missing and are ordered', testSuccessShape, execa);
test('Return value properties are not missing and are ordered, sync', testSuccessShape, execaSync);
const testErrorShape = async (t, execaMethod) => {
const error = await execaMethod('fail.js', {...fullStdio, all: true, reject: false});
t.is(error.exitCode, 2);
t.deepEqual(Reflect.ownKeys(error), [
'stack',
'message',
'shortMessage',
'command',
'escapedCommand',
'cwd',
'durationMs',
'failed',
'timedOut',
'isCanceled',
'isGracefullyCanceled',
'isTerminated',
'isMaxBuffer',
'isForcefullyTerminated',
'exitCode',
'stdout',
'stderr',
'all',
'stdio',
'ipcOutput',
'pipedFrom',
]);
};
test('Error properties are not missing and are ordered', testErrorShape, execa);
test('Error properties are not missing and are ordered, sync', testErrorShape, execaSync);
test('failed is false on success', async t => {
const {failed} = await execa('noop.js', ['foo']);
t.false(failed);
});
test('failed is true on failure', async t => {
const {failed} = await t.throwsAsync(execa('fail.js'));
t.true(failed);
});
test('error.isTerminated is true if subprocess was killed directly', async t => {
const subprocess = execa('forever.js', {killSignal: 'SIGINT'});
subprocess.kill();
const {isTerminated, signal, originalMessage, message, shortMessage} = await t.throwsAsync(subprocess, {message: /was killed with SIGINT/});
t.true(isTerminated);
t.is(signal, 'SIGINT');
t.is(originalMessage, undefined);
t.is(shortMessage, 'Command was killed with SIGINT (User interruption with CTRL-C): forever.js');
t.is(message, shortMessage);
});
test('error.isTerminated is true if subprocess was killed indirectly', async t => {
const subprocess = execa('forever.js', {killSignal: 'SIGHUP'});
process.kill(subprocess.pid, 'SIGINT');
// `subprocess.kill()` is emulated by Node.js on Windows
if (isWindows) {
const {isTerminated, signal} = await t.throwsAsync(subprocess, {message: /failed with exit code 1/});
t.is(isTerminated, false);
t.is(signal, undefined);
} else {
const {isTerminated, signal} = await t.throwsAsync(subprocess, {message: /was killed with SIGINT/});
t.is(isTerminated, true);
t.is(signal, 'SIGINT');
}
});
test('result.isTerminated is false if not killed', async t => {
const {isTerminated} = await execa('noop.js');
t.false(isTerminated);
});
test('result.isTerminated is false if not killed and subprocess.kill() was called', async t => {
const subprocess = execa('noop.js');
subprocess.kill(0);
t.true(subprocess.killed);
const {isTerminated} = await subprocess;
t.false(isTerminated);
});
test('result.isTerminated is false if not killed, in sync mode', t => {
const {isTerminated} = execaSync('noop.js');
t.false(isTerminated);
});
test('result.isTerminated is false on subprocess error', async t => {
const {isTerminated} = await t.throwsAsync(execa('wrong command'));
t.false(isTerminated);
});
test('result.isTerminated is false on subprocess error, in sync mode', t => {
const {isTerminated} = t.throws(() => {
execaSync('wrong command');
});
t.false(isTerminated);
});
test('error.code is undefined on success', async t => {
const {code} = await execa('noop.js');
t.is(code, undefined);
});
test('error.code is defined on failure if applicable', async t => {
const {code} = await t.throwsAsync(execa('noop.js', {uid: true}));
t.is(code, 'ERR_INVALID_ARG_TYPE');
});
================================================
FILE: test/stdio/direction.js
================================================
import {readFile, writeFile, rm} from 'node:fs/promises';
import process from 'node:process';
import test from 'ava';
import tempfile from 'tempfile';
import {execa, execaSync} from '../../index.js';
import {getStdio} from '../helpers/stdio.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
setFixtureDirectory();
const testInputOutput = (t, stdioOption, execaMethod) => {
t.throws(() => {
execaMethod('empty.js', getStdio(3, [new ReadableStream(), stdioOption]));
}, {message: /readable and writable/});
};
test('Cannot pass both readable and writable values to stdio[*] - WritableStream', testInputOutput, new WritableStream(), execa);
test('Cannot pass both readable and writable values to stdio[*] - 1', testInputOutput, 1, execa);
test('Cannot pass both readable and writable values to stdio[*] - 2', testInputOutput, 2, execa);
test('Cannot pass both readable and writable values to stdio[*] - process.stdout', testInputOutput, process.stdout, execa);
test('Cannot pass both readable and writable values to stdio[*] - process.stderr', testInputOutput, process.stderr, execa);
test('Cannot pass both readable and writable values to stdio[*] - WritableStream - sync', testInputOutput, new WritableStream(), execaSync);
test('Cannot pass both readable and writable values to stdio[*] - 1 - sync', testInputOutput, 1, execaSync);
test('Cannot pass both readable and writable values to stdio[*] - 2 - sync', testInputOutput, 2, execaSync);
test('Cannot pass both readable and writable values to stdio[*] - process.stdout - sync', testInputOutput, process.stdout, execaSync);
test('Cannot pass both readable and writable values to stdio[*] - process.stderr - sync', testInputOutput, process.stderr, execaSync);
const testAmbiguousDirection = async (t, execaMethod) => {
const [filePathOne, filePathTwo] = [tempfile(), tempfile()];
await execaMethod('noop-fd.js', ['3', foobarString], getStdio(3, [{file: filePathOne}, {file: filePathTwo}]));
t.deepEqual(
await Promise.all([readFile(filePathOne, 'utf8'), readFile(filePathTwo, 'utf8')]),
[foobarString, foobarString],
);
await Promise.all([rm(filePathOne), rm(filePathTwo)]);
};
test('stdio[*] default direction is output', testAmbiguousDirection, execa);
test('stdio[*] default direction is output - sync', testAmbiguousDirection, execaSync);
const testAmbiguousMultiple = async (t, fdNumber) => {
const filePath = tempfile();
await writeFile(filePath, foobarString);
const {stdout} = await execa('stdin-fd.js', [`${fdNumber}`], getStdio(fdNumber, [{file: filePath}, ['foo', 'bar']]));
t.is(stdout, `${foobarString}${foobarString}`);
await rm(filePath);
};
test('stdin ambiguous direction is influenced by other values', testAmbiguousMultiple, 0);
test('stdio[*] ambiguous direction is influenced by other values', testAmbiguousMultiple, 3);
================================================
FILE: test/stdio/duplex.js
================================================
import {createHash} from 'node:crypto';
import {promisify} from 'node:util';
import {createGzip, gunzip} from 'node:zlib';
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {
foobarString,
foobarObject,
foobarUppercase,
foobarUppercaseHex,
} from '../helpers/input.js';
import {uppercaseEncodingDuplex, getOutputDuplex} from '../helpers/duplex.js';
setFixtureDirectory();
test('Can use crypto.createHash()', async t => {
const {stdout} = await execa('noop-fd.js', ['1', foobarString], {stdout: {transform: createHash('sha1')}, encoding: 'hex'});
const expectedStdout = createHash('sha1').update(foobarString).digest('hex');
t.is(stdout, expectedStdout);
});
test('Can use zlib.createGzip()', async t => {
const {stdout} = await execa('noop-fd.js', ['1', foobarString], {stdout: {transform: createGzip()}, encoding: 'buffer'});
const decompressedStdout = await promisify(gunzip)(stdout);
t.is(decompressedStdout.toString(), foobarString);
});
test('Can use encoding "hex"', async t => {
const {transform} = uppercaseEncodingDuplex('hex')();
t.is(transform.readableEncoding, 'hex');
const {stdout} = await execa('noop-fd.js', ['1', foobarString], {stdout: {transform}});
t.is(stdout, foobarUppercaseHex);
});
test('Cannot use objectMode: true with duplex.readableObjectMode: false', t => {
t.throws(() => {
execa('noop-fd.js', ['1', foobarString], {stdout: uppercaseEncodingDuplex(undefined, false)(true)});
}, {message: /cannot be `false` if `new Duplex\({objectMode: true}\)`/});
});
test('Cannot use objectMode: false with duplex.readableObjectMode: true', t => {
t.throws(() => {
execa('noop-fd.js', ['1', foobarString], {stdout: uppercaseEncodingDuplex(undefined, true)(false)});
}, {message: /can only be `true` if `new Duplex\({objectMode: true}\)`/});
});
const testObjectModeFalse = async (t, objectMode) => {
const {stdout} = await execa('noop-fd.js', ['1', foobarString], {stdout: uppercaseEncodingDuplex(undefined, objectMode)(false)});
t.is(stdout, foobarUppercase);
};
test('Can use objectMode: false with duplex.readableObjectMode: false', testObjectModeFalse, false);
test('Can use objectMode: undefined with duplex.readableObjectMode: false', testObjectModeFalse, undefined);
const testObjectModeTrue = async (t, objectMode) => {
const {stdout} = await execa('noop-fd.js', ['1', foobarString], {stdout: getOutputDuplex(foobarObject, objectMode)(true)});
t.deepEqual(stdout, [foobarObject]);
};
test('Can use objectMode: true with duplex.readableObjectMode: true', testObjectModeTrue, true);
test('Can use objectMode: undefined with duplex.readableObjectMode: true', testObjectModeTrue, undefined);
================================================
FILE: test/stdio/duplicate.js
================================================
import {once} from 'node:events';
import {createReadStream, createWriteStream} from 'node:fs';
import {readFile, writeFile, rm} from 'node:fs/promises';
import {Readable, Writable} from 'node:stream';
import {pathToFileURL} from 'node:url';
import test from 'ava';
import tempfile from 'tempfile';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {
uppercaseGenerator,
appendGenerator,
appendAsyncGenerator,
casedSuffix,
} from '../helpers/generator.js';
import {appendDuplex} from '../helpers/duplex.js';
import {appendWebTransform} from '../helpers/web-transform.js';
import {foobarString, foobarUint8Array, foobarUppercase} from '../helpers/input.js';
import {fullStdio} from '../helpers/stdio.js';
import {nestedSubprocess} from '../helpers/nested.js';
import {getAbsolutePath} from '../helpers/file-path.js';
import {noopDuplex} from '../helpers/stream.js';
setFixtureDirectory();
const getNativeStream = stream => stream;
const getNonNativeStream = stream => ['pipe', stream];
const getWebWritableStream = stream => Writable.toWeb(stream);
const getDummyDuplex = () => ({transform: noopDuplex()});
const getDummyWebTransformStream = () => new TransformStream();
const getDummyPath = async () => {
const filePath = tempfile();
await writeFile(filePath, '');
return filePath;
};
const getDummyFilePath = async () => ({file: await getDummyPath()});
const getDummyFileURL = async () => pathToFileURL((await getDummyPath()));
const duplexName = 'a Duplex stream';
const webTransformName = 'a web TransformStream';
const filePathName = 'a file path string';
const fileURLName = 'a file URL';
const getDifferentInputs = stdioOption => ({stdio: [stdioOption, 'pipe', 'pipe', stdioOption]});
const getDifferentOutputs = stdioOption => ({stdout: stdioOption, stderr: stdioOption});
const getDifferentInputsOutputs = stdioOption => ({stdin: stdioOption, stdout: stdioOption});
const differentInputsName = '`stdin` and `stdio[3]`';
const differentOutputsName = '`stdout` and `stderr`';
const differentInputsOutputsName = '`stdin` and `stdout`';
test('Can use multiple "pipe" on same input file descriptor', async t => {
const subprocess = execa('stdin.js', {stdin: ['pipe', 'pipe']});
subprocess.stdin.end(foobarString);
const {stdout} = await subprocess;
t.is(stdout, foobarString);
});
const testTwoPipeOutput = async (t, execaMethod) => {
const {stdout} = await execaMethod('noop.js', [foobarString], {stdout: ['pipe', 'pipe']});
t.is(stdout, foobarString);
};
test('Can use multiple "pipe" on same output file descriptor', testTwoPipeOutput, execa);
test('Can use multiple "pipe" on same output file descriptor, sync', testTwoPipeOutput, execaSync);
test('Can repeat same stream on same input file descriptor', async t => {
const stream = Readable.from([foobarString]);
const {stdout} = await execa('stdin.js', {stdin: ['pipe', stream, stream]});
t.is(stdout, foobarString);
});
test('Can repeat same stream on same output file descriptor', async t => {
let stdout = '';
const stream = new Writable({
write(chunk, encoding, done) {
stdout += chunk.toString();
done();
},
});
await execa('noop-fd.js', ['1', foobarString], {stdout: ['pipe', stream, stream]});
t.is(stdout, foobarString);
});
// eslint-disable-next-line max-params
const testTwoGenerators = async (t, producesTwo, execaMethod, firstGenerator, secondGenerator = firstGenerator) => {
const {stdout} = await execaMethod('noop-fd.js', ['1', foobarString], {stdout: [firstGenerator, secondGenerator]});
const expectedSuffix = producesTwo ? `${casedSuffix}${casedSuffix}` : casedSuffix;
t.is(stdout, `${foobarString}${expectedSuffix}`);
};
test('Can use multiple identical generators', testTwoGenerators, true, execa, appendGenerator().transform);
test('Can use multiple identical generators, options object', testTwoGenerators, true, execa, appendGenerator());
test('Can use multiple identical generators, async', testTwoGenerators, true, execa, appendAsyncGenerator().transform);
test('Can use multiple identical generators, options object, async', testTwoGenerators, true, execa, appendAsyncGenerator());
test('Can use multiple identical generators, sync', testTwoGenerators, true, execaSync, appendGenerator().transform);
test('Can use multiple identical generators, options object, sync', testTwoGenerators, true, execaSync, appendGenerator());
test('Ignore duplicate identical duplexes', testTwoGenerators, false, execa, appendDuplex());
test('Ignore duplicate identical webTransforms', testTwoGenerators, false, execa, appendWebTransform());
test('Can use multiple generators with duplexes', testTwoGenerators, true, execa, appendGenerator(false, false, true), appendDuplex());
test('Can use multiple generators with webTransforms', testTwoGenerators, true, execa, appendGenerator(false, false, true), appendWebTransform());
test('Can use multiple duplexes with webTransforms', testTwoGenerators, true, execa, appendDuplex(), appendWebTransform());
const testMultiplePipeOutput = async (t, execaMethod) => {
const {stdout, stderr} = await execaMethod('noop-both.js', [foobarString], fullStdio);
t.is(stdout, foobarString);
t.is(stderr, foobarString);
};
test('Can use multiple "pipe" on different output file descriptors', testMultiplePipeOutput, execa);
test('Can use multiple "pipe" on different output file descriptors, sync', testMultiplePipeOutput, execaSync);
test('Can re-use same generator on different input file descriptors', async t => {
const {stdout} = await execa('stdin-fd-both.js', ['3'], getDifferentInputs([foobarUint8Array, uppercaseGenerator(false, false, true)]));
t.is(stdout, `${foobarUppercase}${foobarUppercase}`);
});
test('Can re-use same generator on different output file descriptors', async t => {
const {stdout, stderr} = await execa('noop-both.js', [foobarString], getDifferentOutputs(uppercaseGenerator(false, false, true)));
t.is(stdout, foobarUppercase);
t.is(stderr, foobarUppercase);
});
test('Can re-use same non-native Readable stream on different input file descriptors', async t => {
const filePath = tempfile();
await writeFile(filePath, foobarString);
const stream = createReadStream(filePath);
await once(stream, 'open');
const {stdout} = await execa('stdin-fd-both.js', ['3'], getDifferentInputs([new Uint8Array(0), stream]));
t.is(stdout, `${foobarString}${foobarString}`);
await rm(filePath);
});
const testMultipleStreamOutput = async (t, getStreamOption) => {
const filePath = tempfile();
const stream = createWriteStream(filePath);
await once(stream, 'open');
await execa('noop-both.js', [foobarString], getDifferentOutputs(getStreamOption(stream)));
t.is(await readFile(filePath, 'utf8'), `${foobarString}\n${foobarString}\n`);
await rm(filePath);
};
test('Can re-use same native Writable stream on different output file descriptors', testMultipleStreamOutput, getNativeStream);
test('Can re-use same non-native Writable stream on different output file descriptors', testMultipleStreamOutput, getNonNativeStream);
test('Can re-use same web Writable stream on different output file descriptors', testMultipleStreamOutput, getWebWritableStream);
const testMultipleInheritOutput = async (t, isSync) => {
const {stdout} = await nestedSubprocess('noop-both.js', [foobarString], {...getDifferentOutputs(1), isSync});
t.is(stdout, `${foobarString}\n${foobarString}`);
};
test('Can re-use same parent file descriptor on different output file descriptors', testMultipleInheritOutput, false);
test('Can re-use same parent file descriptor on different output file descriptors, sync', testMultipleInheritOutput, true);
const testMultipleFileInput = async (t, mapFile) => {
const filePath = tempfile();
await writeFile(filePath, foobarString);
const {stdout} = await execa('stdin-fd-both.js', ['3'], getDifferentInputs([new Uint8Array(0), mapFile(filePath)]));
t.is(stdout, `${foobarString}${foobarString}`);
await rm(filePath);
};
test('Can re-use same file path on different input file descriptors', testMultipleFileInput, getAbsolutePath);
test('Can re-use same file URL on different input file descriptors', testMultipleFileInput, pathToFileURL);
const testMultipleFileOutput = async (t, mapFile, execaMethod) => {
const filePath = tempfile();
await execaMethod('noop-both.js', [foobarString], getDifferentOutputs(mapFile(filePath)));
t.is(await readFile(filePath, 'utf8'), `${foobarString}\n${foobarString}\n`);
await rm(filePath);
};
test('Can re-use same file path on different output file descriptors', testMultipleFileOutput, getAbsolutePath, execa);
test('Can re-use same file path on different output file descriptors, sync', testMultipleFileOutput, getAbsolutePath, execaSync);
test('Can re-use same file URL on different output file descriptors', testMultipleFileOutput, pathToFileURL, execa);
test('Can re-use same file URL on different output file descriptors, sync', testMultipleFileOutput, pathToFileURL, execaSync);
const testMultipleFileInputOutput = async (t, mapFile, execaMethod) => {
const inputFilePath = tempfile();
const outputFilePath = tempfile();
await writeFile(inputFilePath, foobarString);
await execaMethod('stdin.js', {stdin: mapFile(inputFilePath), stdout: mapFile(outputFilePath)});
t.is(await readFile(outputFilePath, 'utf8'), foobarString);
await Promise.all([rm(inputFilePath), rm(outputFilePath)]);
};
test('Can use different file paths on different input/output file descriptors', testMultipleFileInputOutput, getAbsolutePath, execa);
test('Can use different file paths on different input/output file descriptors, sync', testMultipleFileInputOutput, getAbsolutePath, execaSync);
test('Can use different file URL on different input/output file descriptors', testMultipleFileInputOutput, pathToFileURL, execa);
test('Can use different file URL on different input/output file descriptors, sync', testMultipleFileInputOutput, pathToFileURL, execaSync);
// eslint-disable-next-line max-params
const testMultipleInvalid = async (t, getDummyStream, typeName, getStdio, fdName, execaMethod) => {
const stdioOption = await getDummyStream();
t.throws(() => {
execaMethod('empty.js', getStdio(stdioOption));
}, {message: `The ${fdName} options must not target ${typeName} that is the same.`});
if (stdioOption.transform !== undefined) {
t.true(stdioOption.transform.destroyed);
}
};
test('Cannot use same Duplex on different input file descriptors', testMultipleInvalid, getDummyDuplex, duplexName, getDifferentInputs, differentInputsName, execa);
test('Cannot use same Duplex on different output file descriptors', testMultipleInvalid, getDummyDuplex, duplexName, getDifferentOutputs, differentOutputsName, execa);
test('Cannot use same Duplex on both input and output file descriptors', testMultipleInvalid, getDummyDuplex, duplexName, getDifferentInputsOutputs, differentInputsOutputsName, execa);
test('Cannot use same TransformStream on different input file descriptors', testMultipleInvalid, getDummyWebTransformStream, webTransformName, getDifferentInputs, differentInputsName, execa);
test('Cannot use same TransformStream on different output file descriptors', testMultipleInvalid, getDummyWebTransformStream, webTransformName, getDifferentOutputs, differentOutputsName, execa);
test('Cannot use same TransformStream on both input and output file descriptors', testMultipleInvalid, getDummyWebTransformStream, webTransformName, getDifferentInputsOutputs, differentInputsOutputsName, execa);
test('Cannot use same file path on both input and output file descriptors', testMultipleInvalid, getDummyFilePath, filePathName, getDifferentInputsOutputs, differentInputsOutputsName, execa);
test('Cannot use same file URL on both input and output file descriptors', testMultipleInvalid, getDummyFileURL, fileURLName, getDifferentInputsOutputs, differentInputsOutputsName, execa);
test('Cannot use same file path on both input and output file descriptors, sync', testMultipleInvalid, getDummyFilePath, filePathName, getDifferentInputsOutputs, differentInputsOutputsName, execaSync);
test('Cannot use same file URL on both input and output file descriptors, sync', testMultipleInvalid, getDummyFileURL, fileURLName, getDifferentInputsOutputs, differentInputsOutputsName, execaSync);
================================================
FILE: test/stdio/file-descriptor.js
================================================
import {readFile, open, rm} from 'node:fs/promises';
import test from 'ava';
import tempfile from 'tempfile';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {getStdio} from '../helpers/stdio.js';
setFixtureDirectory();
const testFileDescriptorOption = async (t, fdNumber, execaMethod) => {
const filePath = tempfile();
const fileDescriptor = await open(filePath, 'w');
await execaMethod('noop-fd.js', [`${fdNumber}`, 'foobar'], getStdio(fdNumber, fileDescriptor));
t.is(await readFile(filePath, 'utf8'), 'foobar');
await rm(filePath);
await fileDescriptor.close();
};
test('pass `stdout` to a file descriptor', testFileDescriptorOption, 1, execa);
test('pass `stderr` to a file descriptor', testFileDescriptorOption, 2, execa);
test('pass `stdio[*]` to a file descriptor', testFileDescriptorOption, 3, execa);
test('pass `stdout` to a file descriptor - sync', testFileDescriptorOption, 1, execaSync);
test('pass `stderr` to a file descriptor - sync', testFileDescriptorOption, 2, execaSync);
test('pass `stdio[*]` to a file descriptor - sync', testFileDescriptorOption, 3, execaSync);
const testStdinWrite = async (t, fdNumber) => {
const subprocess = execa('stdin-fd.js', [`${fdNumber}`], getStdio(fdNumber, 'pipe'));
subprocess.stdio[fdNumber].end('unicorns');
const {stdout} = await subprocess;
t.is(stdout, 'unicorns');
};
test('you can write to subprocess.stdin', testStdinWrite, 0);
test('you can write to subprocess.stdio[*]', testStdinWrite, 3);
================================================
FILE: test/stdio/file-path-error.js
================================================
import {readFile, writeFile, rm} from 'node:fs/promises';
import {pathToFileURL} from 'node:url';
import test from 'ava';
import {pathExists} from 'path-exists';
import tempfile from 'tempfile';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {identity, getStdio} from '../helpers/stdio.js';
import {foobarString, foobarUppercase} from '../helpers/input.js';
import {
outputObjectGenerator,
uppercaseGenerator,
serializeGenerator,
throwingGenerator,
} from '../helpers/generator.js';
import {getAbsolutePath} from '../helpers/file-path.js';
setFixtureDirectory();
const nonFileUrl = new URL('https://example.com');
const testStdioNonFileUrl = (t, fdNumber, execaMethod) => {
t.throws(() => {
execaMethod('empty.js', getStdio(fdNumber, nonFileUrl));
}, {message: /pathToFileURL/});
};
test('inputFile cannot be a non-file URL', testStdioNonFileUrl, 'inputFile', execa);
test('stdin cannot be a non-file URL', testStdioNonFileUrl, 0, execa);
test('stdout cannot be a non-file URL', testStdioNonFileUrl, 1, execa);
test('stderr cannot be a non-file URL', testStdioNonFileUrl, 2, execa);
test('stdio[*] cannot be a non-file URL', testStdioNonFileUrl, 3, execa);
test('inputFile cannot be a non-file URL - sync', testStdioNonFileUrl, 'inputFile', execaSync);
test('stdin cannot be a non-file URL - sync', testStdioNonFileUrl, 0, execaSync);
test('stdout cannot be a non-file URL - sync', testStdioNonFileUrl, 1, execaSync);
test('stderr cannot be a non-file URL - sync', testStdioNonFileUrl, 2, execaSync);
test('stdio[*] cannot be a non-file URL - sync', testStdioNonFileUrl, 3, execaSync);
const testInvalidInputFile = (t, execaMethod) => {
t.throws(() => {
execaMethod('empty.js', getStdio('inputFile', false));
}, {message: /a file path string or a file URL/});
};
test('inputFile must be a file URL or string', testInvalidInputFile, execa);
test('inputFile must be a file URL or string - sync', testInvalidInputFile, execaSync);
const testFilePathObject = (t, fdNumber, execaMethod) => {
t.throws(() => {
execaMethod('empty.js', getStdio(fdNumber, foobarString));
}, {message: /must be used/});
};
test('stdin must be an object when it is a file path string', testFilePathObject, 0, execa);
test('stdout must be an object when it is a file path string', testFilePathObject, 1, execa);
test('stderr must be an object when it is a file path string', testFilePathObject, 2, execa);
test('stdio[*] must be an object when it is a file path string', testFilePathObject, 3, execa);
test('stdin be an object when it is a file path string - sync', testFilePathObject, 0, execaSync);
test('stdout be an object when it is a file path string - sync', testFilePathObject, 1, execaSync);
test('stderr be an object when it is a file path string - sync', testFilePathObject, 2, execaSync);
test('stdio[*] must be an object when it is a file path string - sync', testFilePathObject, 3, execaSync);
const testFileError = async (t, fixtureName, mapFile, fdNumber) => {
await t.throwsAsync(
execa(fixtureName, [`${fdNumber}`], getStdio(fdNumber, mapFile('./unknown/file'))),
{code: 'ENOENT'},
);
};
test.serial('inputFile file URL errors should be handled', testFileError, 'stdin-fd.js', pathToFileURL, 'inputFile');
test.serial('stdin file URL errors should be handled', testFileError, 'stdin-fd.js', pathToFileURL, 0);
test.serial('stdout file URL errors should be handled', testFileError, 'noop-fd.js', pathToFileURL, 1);
test.serial('stderr file URL errors should be handled', testFileError, 'noop-fd.js', pathToFileURL, 2);
test.serial('stdio[*] file URL errors should be handled', testFileError, 'noop-fd.js', pathToFileURL, 3);
test.serial('inputFile file path errors should be handled', testFileError, 'stdin-fd.js', identity, 'inputFile');
test.serial('stdin file path errors should be handled', testFileError, 'stdin-fd.js', getAbsolutePath, 0);
test.serial('stdout file path errors should be handled', testFileError, 'noop-fd.js', getAbsolutePath, 1);
test.serial('stderr file path errors should be handled', testFileError, 'noop-fd.js', getAbsolutePath, 2);
test.serial('stdio[*] file path errors should be handled', testFileError, 'noop-fd.js', getAbsolutePath, 3);
const testFileErrorSync = (t, mapFile, fdNumber) => {
t.throws(() => {
execaSync('empty.js', getStdio(fdNumber, mapFile('./unknown/file')));
}, {code: 'ENOENT'});
};
test('inputFile file URL errors should be handled - sync', testFileErrorSync, pathToFileURL, 'inputFile');
test('stdin file URL errors should be handled - sync', testFileErrorSync, pathToFileURL, 0);
test('stdout file URL errors should be handled - sync', testFileErrorSync, pathToFileURL, 1);
test('stderr file URL errors should be handled - sync', testFileErrorSync, pathToFileURL, 2);
test('stdio[*] file URL errors should be handled - sync', testFileErrorSync, pathToFileURL, 3);
test('inputFile file path errors should be handled - sync', testFileErrorSync, identity, 'inputFile');
test('stdin file path errors should be handled - sync', testFileErrorSync, getAbsolutePath, 0);
test('stdout file path errors should be handled - sync', testFileErrorSync, getAbsolutePath, 1);
test('stderr file path errors should be handled - sync', testFileErrorSync, getAbsolutePath, 2);
test('stdio[*] file path errors should be handled - sync', testFileErrorSync, getAbsolutePath, 3);
const testInputFileObject = async (t, fdNumber, mapFile, execaMethod) => {
const filePath = tempfile();
await writeFile(filePath, foobarString);
t.throws(() => {
execaMethod('stdin-fd.js', [`${fdNumber}`], getStdio(fdNumber, [
new Uint8Array(),
mapFile(filePath),
serializeGenerator(true),
]));
}, {message: /cannot use both files and transforms in objectMode/});
await rm(filePath);
};
test('stdin cannot use objectMode together with input file paths', testInputFileObject, 0, getAbsolutePath, execa);
test('stdin cannot use objectMode together with input file URLs', testInputFileObject, 0, pathToFileURL, execa);
test('stdio[*] cannot use objectMode together with input file paths', testInputFileObject, 3, getAbsolutePath, execa);
test('stdio[*] cannot use objectMode together with input file URLs', testInputFileObject, 3, pathToFileURL, execa);
test('stdin cannot use objectMode together with input file paths, sync', testInputFileObject, 0, getAbsolutePath, execaSync);
test('stdin cannot use objectMode together with input file URLs, sync', testInputFileObject, 0, pathToFileURL, execaSync);
const testOutputFileObject = async (t, fdNumber, mapFile, execaMethod) => {
const filePath = tempfile();
t.throws(() => {
execaMethod('noop-fd.js', [`${fdNumber}`, foobarString], getStdio(fdNumber, [
outputObjectGenerator(),
mapFile(filePath),
]));
}, {message: /cannot use both files and transforms in objectMode/});
t.false(await pathExists(filePath));
};
test('stdout cannot use objectMode together with output file paths', testOutputFileObject, 1, getAbsolutePath, execa);
test('stdout cannot use objectMode together with output file URLs', testOutputFileObject, 1, pathToFileURL, execa);
test('stderr cannot use objectMode together with output file paths', testOutputFileObject, 2, getAbsolutePath, execa);
test('stderr cannot use objectMode together with output file URLs', testOutputFileObject, 2, pathToFileURL, execa);
test('stdio[*] cannot use objectMode together with output file paths', testOutputFileObject, 3, getAbsolutePath, execa);
test('stdio[*] cannot use objectMode together with output file URLs', testOutputFileObject, 3, pathToFileURL, execa);
test('stdout cannot use objectMode together with output file paths, sync', testOutputFileObject, 1, getAbsolutePath, execaSync);
test('stdout cannot use objectMode together with output file URLs, sync', testOutputFileObject, 1, pathToFileURL, execaSync);
test('stderr cannot use objectMode together with output file paths, sync', testOutputFileObject, 2, getAbsolutePath, execaSync);
test('stderr cannot use objectMode together with output file URLs, sync', testOutputFileObject, 2, pathToFileURL, execaSync);
test('stdio[*] cannot use objectMode together with output file paths, sync', testOutputFileObject, 3, getAbsolutePath, execaSync);
test('stdio[*] cannot use objectMode together with output file URLs, sync', testOutputFileObject, 3, pathToFileURL, execaSync);
test('Generator error stops writing to output file', async t => {
const filePath = tempfile();
const cause = new Error(foobarString);
const error = await t.throwsAsync(execa('noop.js', {
stdout: [throwingGenerator(cause)(), getAbsolutePath(filePath)],
}));
t.is(error.cause, cause);
t.is(await readFile(filePath, 'utf8'), '');
});
test('Generator error does not create output file, sync', async t => {
const filePath = tempfile();
const cause = new Error(foobarString);
const error = t.throws(() => {
execaSync('noop.js', {
stdout: [throwingGenerator(cause)(), getAbsolutePath(filePath)],
});
});
t.is(error.cause, cause);
t.false(await pathExists(filePath));
});
test('Output file error still returns transformed output, sync', async t => {
const filePath = tempfile();
const {stdout} = t.throws(() => {
execaSync('noop-fd.js', ['1', foobarString], {
stdout: [uppercaseGenerator(), getAbsolutePath('./unknown/file')],
});
}, {code: 'ENOENT'});
t.false(await pathExists(filePath));
t.is(stdout, foobarUppercase);
});
================================================
FILE: test/stdio/file-path-main.js
================================================
import {readFile, writeFile, rm} from 'node:fs/promises';
import path 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 {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {identity, getStdio} from '../helpers/stdio.js';
import {
runExeca,
runExecaSync,
runScript,
runScriptSync,
} from '../helpers/run.js';
import {foobarString, foobarUint8Array} from '../helpers/input.js';
import {getAbsolutePath, getRelativePath} from '../helpers/file-path.js';
setFixtureDirectory();
const getStdioInput = (fdNumberOrName, file) => {
if (fdNumberOrName === 'string') {
return {input: foobarString};
}
if (fdNumberOrName === 'binary') {
return {input: foobarUint8Array};
}
return getStdioInputFile(fdNumberOrName, file);
};
const getStdioInputFile = (fdNumberOrName, file) => getStdio(fdNumberOrName, typeof fdNumberOrName === 'string' ? file : {file});
const testStdinFile = async (t, mapFilePath, fdNumber, execaMethod) => {
const filePath = tempfile();
await writeFile(filePath, foobarString);
const {stdout} = await execaMethod('stdin.js', getStdio(fdNumber, mapFilePath(filePath)));
t.is(stdout, foobarString);
await rm(filePath);
};
test('inputFile can be a file URL', testStdinFile, pathToFileURL, 'inputFile', execa);
test('stdin can be a file URL', testStdinFile, pathToFileURL, 0, execa);
test('inputFile can be an absolute file path', testStdinFile, identity, 'inputFile', execa);
test('stdin can be an absolute file path', testStdinFile, getAbsolutePath, 0, execa);
test('inputFile can be a relative file path', testStdinFile, identity, 'inputFile', execa);
test('stdin can be a relative file path', testStdinFile, getRelativePath, 0, execa);
test('inputFile can be a file URL - sync', testStdinFile, pathToFileURL, 'inputFile', execaSync);
test('stdin can be a file URL - sync', testStdinFile, pathToFileURL, 0, execaSync);
test('inputFile can be an absolute file path - sync', testStdinFile, identity, 'inputFile', execaSync);
test('stdin can be an absolute file path - sync', testStdinFile, getAbsolutePath, 0, execaSync);
test('inputFile can be a relative file path - sync', testStdinFile, identity, 'inputFile', execaSync);
test('stdin can be a relative file path - sync', testStdinFile, getRelativePath, 0, execaSync);
const testOutputFile = async (t, mapFile, fdNumber, execaMethod) => {
const filePath = tempfile();
await execaMethod('noop-fd.js', [`${fdNumber}`, foobarString], getStdio(fdNumber, mapFile(filePath)));
t.is(await readFile(filePath, 'utf8'), foobarString);
await rm(filePath);
};
test('stdout can be a file URL', testOutputFile, pathToFileURL, 1, execa);
test('stderr can be a file URL', testOutputFile, pathToFileURL, 2, execa);
test('stdio[*] can be a file URL', testOutputFile, pathToFileURL, 3, execa);
test('stdout can be an absolute file path', testOutputFile, getAbsolutePath, 1, execa);
test('stderr can be an absolute file path', testOutputFile, getAbsolutePath, 2, execa);
test('stdio[*] can be an absolute file path', testOutputFile, getAbsolutePath, 3, execa);
test('stdout can be a relative file path', testOutputFile, getRelativePath, 1, execa);
test('stderr can be a relative file path', testOutputFile, getRelativePath, 2, execa);
test('stdio[*] can be a relative file path', testOutputFile, getRelativePath, 3, execa);
test('stdout can be a file URL - sync', testOutputFile, pathToFileURL, 1, execaSync);
test('stderr can be a file URL - sync', testOutputFile, pathToFileURL, 2, execaSync);
test('stdio[*] can be a file URL - sync', testOutputFile, pathToFileURL, 3, execaSync);
test('stdout can be an absolute file path - sync', testOutputFile, getAbsolutePath, 1, execaSync);
test('stderr can be an absolute file path - sync', testOutputFile, getAbsolutePath, 2, execaSync);
test('stdio[*] can be an absolute file path - sync', testOutputFile, getAbsolutePath, 3, execaSync);
test('stdout can be a relative file path - sync', testOutputFile, getRelativePath, 1, execaSync);
test('stderr can be a relative file path - sync', testOutputFile, getRelativePath, 2, execaSync);
test('stdio[*] can be a relative file path - sync', testOutputFile, getRelativePath, 3, execaSync);
const testInputFileValidUrl = async (t, fdNumber, execaMethod) => {
const filePath = tempfile();
await writeFile(filePath, foobarString);
const currentCwd = process.cwd();
process.chdir(path.dirname(filePath));
try {
const {stdout} = await execaMethod('stdin.js', getStdioInputFile(fdNumber, path.basename(filePath)));
t.is(stdout, foobarString);
} finally {
process.chdir(currentCwd);
await rm(filePath);
}
};
test.serial('inputFile does not need to start with . when being a relative file path', testInputFileValidUrl, 'inputFile', execa);
test.serial('stdin does not need to start with . when being a relative file path', testInputFileValidUrl, 0, execa);
test.serial('inputFile does not need to start with . when being a relative file path - sync', testInputFileValidUrl, 'inputFile', execaSync);
test.serial('stdin does not need to start with . when being a relative file path - sync', testInputFileValidUrl, 0, execaSync);
const testMultipleInputs = async (t, indices, execaMethod) => {
const filePath = tempfile();
await writeFile(filePath, foobarString);
const options = Object.assign({}, ...indices.map(fdNumber => getStdioInput(fdNumber, filePath)));
const {stdout} = await execaMethod('stdin.js', options);
t.is(stdout, foobarString.repeat(indices.length));
await rm(filePath);
};
test('inputFile can be set', testMultipleInputs, ['inputFile'], runExeca);
test('inputFile can be set - sync', testMultipleInputs, ['inputFile'], runExecaSync);
test('inputFile can be set with $', testMultipleInputs, ['inputFile'], runScript);
test('inputFile can be set with $.sync', testMultipleInputs, ['inputFile'], runScriptSync);
test('input String and inputFile can be both set', testMultipleInputs, ['inputFile', 'string'], execa);
test('input String and stdin can be both set', testMultipleInputs, [0, 'string'], execa);
test('input Uint8Array and inputFile can be both set', testMultipleInputs, ['inputFile', 'binary'], execa);
test('input Uint8Array and stdin can be both set', testMultipleInputs, [0, 'binary'], execa);
test('stdin and inputFile can be both set', testMultipleInputs, [0, 'inputFile'], execa);
test('input String, stdin and inputFile can be all set', testMultipleInputs, ['inputFile', 0, 'string'], execa);
test('input Uint8Array, stdin and inputFile can be all set', testMultipleInputs, ['inputFile', 0, 'binary'], execa);
test('input String and inputFile can be both set - sync', testMultipleInputs, ['inputFile', 'string'], execaSync);
test('input String and stdin can be both set - sync', testMultipleInputs, [0, 'string'], execaSync);
test('input Uint8Array and inputFile can be both set - sync', testMultipleInputs, ['inputFile', 'binary'], execaSync);
test('input Uint8Array and stdin can be both set - sync', testMultipleInputs, [0, 'binary'], execaSync);
test('stdin and inputFile can be both set - sync', testMultipleInputs, [0, 'inputFile'], execaSync);
test('input String, stdin and inputFile can be all set - sync', testMultipleInputs, ['inputFile', 0, 'string'], execaSync);
test('input Uint8Array, stdin and inputFile can be all set - sync', testMultipleInputs, ['inputFile', 0, 'binary'], execaSync);
const testMultipleOutputs = async (t, mapFile, fdNumber, execaMethod) => {
const filePath = tempfile();
const filePathTwo = tempfile();
await execaMethod('noop-fd.js', [`${fdNumber}`, foobarString], getStdio(fdNumber, [mapFile(filePath), mapFile(filePathTwo)]));
t.is(await readFile(filePath, 'utf8'), foobarString);
t.is(await readFile(filePathTwo, 'utf8'), foobarString);
await Promise.all([rm(filePath), rm(filePathTwo)]);
};
test('stdout can be two file URLs', testMultipleOutputs, pathToFileURL, 1, execa);
test('stdout can be two file paths', testMultipleOutputs, getAbsolutePath, 1, execa);
test('stdout can be two file URLs - sync', testMultipleOutputs, pathToFileURL, 1, execaSync);
test('stdout can be two file paths - sync', testMultipleOutputs, getAbsolutePath, 1, execaSync);
test('stderr can be two file URLs', testMultipleOutputs, pathToFileURL, 2, execa);
test('stderr can be two file paths', testMultipleOutputs, getAbsolutePath, 2, execa);
test('stderr can be two file URLs - sync', testMultipleOutputs, pathToFileURL, 2, execaSync);
test('stderr can be two file paths - sync', testMultipleOutputs, getAbsolutePath, 2, execaSync);
test('stdio[*] can be two file URLs', testMultipleOutputs, pathToFileURL, 3, execa);
test('stdio[*] can be two file paths', testMultipleOutputs, getAbsolutePath, 3, execa);
test('stdio[*] can be two file URLs - sync', testMultipleOutputs, pathToFileURL, 3, execaSync);
test('stdio[*] can be two file paths - sync', testMultipleOutputs, getAbsolutePath, 3, execaSync);
const testInputFileHanging = async (t, mapFilePath) => {
const filePath = tempfile();
await writeFile(filePath, foobarString);
await t.throwsAsync(execa('stdin.js', {stdin: mapFilePath(filePath), timeout: 1}), {message: /timed out/});
await rm(filePath);
};
test('Passing an input file path when subprocess exits does not make promise hang', testInputFileHanging, getAbsolutePath);
test('Passing an input file URL when subprocess exits does not make promise hang', testInputFileHanging, pathToFileURL);
const testOverwriteFile = async (t, fdNumber, execaMethod, append) => {
const filePath = tempfile();
await writeFile(filePath, '.');
await execaMethod('noop-fd.js', [`${fdNumber}`, foobarString], getStdio(fdNumber, {file: filePath, append}));
t.is(await readFile(filePath, 'utf8'), foobarString);
await rm(filePath);
};
test('Overwrite by default to stdout', testOverwriteFile, 1, execa, undefined);
test('Overwrite by default to stderr', testOverwriteFile, 2, execa, undefined);
test('Overwrite by default to stdio[*]', testOverwriteFile, 3, execa, undefined);
test('Overwrite by default to stdout - sync', testOverwriteFile, 1, execaSync, undefined);
test('Overwrite by default to stderr - sync', testOverwriteFile, 2, execaSync, undefined);
test('Overwrite by default to stdio[*] - sync', testOverwriteFile, 3, execaSync, undefined);
test('Overwrite with append false to stdout', testOverwriteFile, 1, execa, false);
test('Overwrite with append false to stderr', testOverwriteFile, 2, execa, false);
test('Overwrite with append false to stdio[*]', testOverwriteFile, 3, execa, false);
test('Overwrite with append false to stdout - sync', testOverwriteFile, 1, execaSync, false);
test('Overwrite with append false to stderr - sync', testOverwriteFile, 2, execaSync, false);
test('Overwrite with append false to stdio[*] - sync', testOverwriteFile, 3, execaSync, false);
const testAppendFile = async (t, fdNumber, execaMethod) => {
const filePath = tempfile();
await writeFile(filePath, '.');
await execaMethod('noop-fd.js', [`${fdNumber}`, foobarString], getStdio(fdNumber, {file: filePath, append: true}));
t.is(await readFile(filePath, 'utf8'), `.${foobarString}`);
await rm(filePath);
};
test('Can append to stdout', testAppendFile, 1, execa);
test('Can append to stderr', testAppendFile, 2, execa);
test('Can append to stdio[*]', testAppendFile, 3, execa);
test('Can append to stdout - sync', testAppendFile, 1, execaSync);
test('Can append to stderr - sync', testAppendFile, 2, execaSync);
test('Can append to stdio[*] - sync', testAppendFile, 3, execaSync);
================================================
FILE: test/stdio/file-path-mixed.js
================================================
import {readFile, writeFile, rm} from 'node:fs/promises';
import {pathToFileURL} from 'node:url';
import test from 'ava';
import tempfile from 'tempfile';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {getStdio} from '../helpers/stdio.js';
import {foobarString, foobarUppercase} from '../helpers/input.js';
import {uppercaseGenerator} from '../helpers/generator.js';
import {getAbsolutePath} from '../helpers/file-path.js';
setFixtureDirectory();
const testInputFileTransform = async (t, fdNumber, mapFile, execaMethod) => {
const filePath = tempfile();
await writeFile(filePath, foobarString);
const {stdout} = await execaMethod('stdin-fd.js', [`${fdNumber}`], getStdio(fdNumber, [
new Uint8Array(),
mapFile(filePath),
uppercaseGenerator(),
]));
t.is(stdout, foobarUppercase);
await rm(filePath);
};
test('stdin can use generators together with input file paths', testInputFileTransform, 0, getAbsolutePath, execa);
test('stdin can use generators together with input file URLs', testInputFileTransform, 0, pathToFileURL, execa);
test('stdio[*] can use generators together with input file paths', testInputFileTransform, 3, getAbsolutePath, execa);
test('stdio[*] can use generators together with input file URLs', testInputFileTransform, 3, pathToFileURL, execa);
test('stdin can use generators together with input file paths, sync', testInputFileTransform, 0, getAbsolutePath, execaSync);
test('stdin can use generators together with input file URLs, sync', testInputFileTransform, 0, pathToFileURL, execaSync);
const testOutputFileTransform = async (t, fdNumber, mapFile, execaMethod) => {
const filePath = tempfile();
await execaMethod('noop-fd.js', [`${fdNumber}`, foobarString], getStdio(fdNumber, [
uppercaseGenerator(),
mapFile(filePath),
]));
t.is(await readFile(filePath, 'utf8'), `${foobarUppercase}\n`);
await rm(filePath);
};
test('stdout can use generators together with output file paths', testOutputFileTransform, 1, getAbsolutePath, execa);
test('stdout can use generators together with output file URLs', testOutputFileTransform, 1, pathToFileURL, execa);
test('stderr can use generators together with output file paths', testOutputFileTransform, 2, getAbsolutePath, execa);
test('stderr can use generators together with output file URLs', testOutputFileTransform, 2, pathToFileURL, execa);
test('stdio[*] can use generators together with output file paths', testOutputFileTransform, 3, getAbsolutePath, execa);
test('stdio[*] can use generators together with output file URLs', testOutputFileTransform, 3, pathToFileURL, execa);
test('stdout can use generators together with output file paths, sync', testOutputFileTransform, 1, getAbsolutePath, execaSync);
test('stdout can use generators together with output file URLs, sync', testOutputFileTransform, 1, pathToFileURL, execaSync);
test('stderr can use generators together with output file paths, sync', testOutputFileTransform, 2, getAbsolutePath, execaSync);
test('stderr can use generators together with output file URLs, sync', testOutputFileTransform, 2, pathToFileURL, execaSync);
test('stdio[*] can use generators together with output file paths, sync', testOutputFileTransform, 3, getAbsolutePath, execaSync);
test('stdio[*] can use generators together with output file URLs, sync', testOutputFileTransform, 3, pathToFileURL, execaSync);
const testOutputFileLines = async (t, fdNumber, mapFile, execaMethod) => {
const filePath = tempfile();
const {stdio} = await execaMethod('noop-fd.js', [`${fdNumber}`, foobarString], {
...getStdio(fdNumber, mapFile(filePath)),
lines: true,
});
t.deepEqual(stdio[fdNumber], [foobarString]);
t.is(await readFile(filePath, 'utf8'), foobarString);
await rm(filePath);
};
test('stdout can use "lines: true" together with output file paths', testOutputFileLines, 1, getAbsolutePath, execa);
test('stdout can use "lines: true" together with output file URLs', testOutputFileLines, 1, pathToFileURL, execa);
test('stderr can use "lines: true" together with output file paths', testOutputFileLines, 2, getAbsolutePath, execa);
test('stderr can use "lines: true" together with output file URLs', testOutputFileLines, 2, pathToFileURL, execa);
test('stdio[*] can use "lines: true" together with output file paths', testOutputFileLines, 3, getAbsolutePath, execa);
test('stdio[*] can use "lines: true" together with output file URLs', testOutputFileLines, 3, pathToFileURL, execa);
test('stdout can use "lines: true" together with output file paths, sync', testOutputFileLines, 1, getAbsolutePath, execaSync);
test('stdout can use "lines: true" together with output file URLs, sync', testOutputFileLines, 1, pathToFileURL, execaSync);
test('stderr can use "lines: true" together with output file paths, sync', testOutputFileLines, 2, getAbsolutePath, execaSync);
test('stderr can use "lines: true" together with output file URLs, sync', testOutputFileLines, 2, pathToFileURL, execaSync);
test('stdio[*] can use "lines: true" together with output file paths, sync', testOutputFileLines, 3, getAbsolutePath, execaSync);
test('stdio[*] can use "lines: true" together with output file URLs, sync', testOutputFileLines, 3, pathToFileURL, execaSync);
const testOutputFileNoBuffer = async (t, buffer, execaMethod) => {
const filePath = tempfile();
const {stdout} = await execaMethod('noop-fd.js', ['1', foobarString], {
stdout: getAbsolutePath(filePath),
buffer,
});
t.is(stdout, undefined);
t.is(await readFile(filePath, 'utf8'), foobarString);
await rm(filePath);
};
test('stdout can use "buffer: false" together with output file paths', testOutputFileNoBuffer, false, execa);
test('stdout can use "buffer: false" together with output file paths, fd-specific', testOutputFileNoBuffer, {stdout: false}, execa);
test('stdout can use "buffer: false" together with output file paths, sync', testOutputFileNoBuffer, false, execaSync);
test('stdout can use "buffer: false" together with output file paths, fd-specific, sync', testOutputFileNoBuffer, {stdout: false}, execaSync);
================================================
FILE: test/stdio/handle-invalid.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {getStdio} from '../helpers/stdio.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
setFixtureDirectory();
const testEmptyArray = (t, fdNumber, optionName, execaMethod) => {
t.throws(() => {
execaMethod('empty.js', getStdio(fdNumber, []));
}, {message: `The \`${optionName}\` option must not be an empty array.`});
};
test('Cannot pass an empty array to stdin', testEmptyArray, 0, 'stdin', execa);
test('Cannot pass an empty array to stdout', testEmptyArray, 1, 'stdout', execa);
test('Cannot pass an empty array to stderr', testEmptyArray, 2, 'stderr', execa);
test('Cannot pass an empty array to stdio[*]', testEmptyArray, 3, 'stdio[3]', execa);
test('Cannot pass an empty array to stdin - sync', testEmptyArray, 0, 'stdin', execaSync);
test('Cannot pass an empty array to stdout - sync', testEmptyArray, 1, 'stdout', execaSync);
test('Cannot pass an empty array to stderr - sync', testEmptyArray, 2, 'stderr', execaSync);
test('Cannot pass an empty array to stdio[*] - sync', testEmptyArray, 3, 'stdio[3]', execaSync);
const testInvalidValueSync = (t, fdNumber, stdioOption) => {
const {message} = t.throws(() => {
execaSync('empty.js', getStdio(fdNumber, stdioOption));
});
t.true(message.includes(`cannot be "${stdioOption}" with synchronous methods`));
};
test('stdin cannot be "ipc", sync', testInvalidValueSync, 0, 'ipc');
test('stdout cannot be "ipc", sync', testInvalidValueSync, 1, 'ipc');
test('stderr cannot be "ipc", sync', testInvalidValueSync, 2, 'ipc');
test('stdio[*] cannot be "ipc", sync', testInvalidValueSync, 3, 'ipc');
test('stdin cannot be "overlapped", sync', testInvalidValueSync, 0, 'overlapped');
test('stdout cannot be "overlapped", sync', testInvalidValueSync, 1, 'overlapped');
test('stderr cannot be "overlapped", sync', testInvalidValueSync, 2, 'overlapped');
test('stdio[*] cannot be "overlapped", sync', testInvalidValueSync, 3, 'overlapped');
const testInvalidArrayValue = (t, invalidStdio, fdNumber, execaMethod) => {
t.throws(() => {
execaMethod('empty.js', getStdio(fdNumber, ['pipe', invalidStdio]));
}, {message: /must not include/});
};
test('Cannot pass "ignore" and another value to stdin', testInvalidArrayValue, 'ignore', 0, execa);
test('Cannot pass "ignore" and another value to stdout', testInvalidArrayValue, 'ignore', 1, execa);
test('Cannot pass "ignore" and another value to stderr', testInvalidArrayValue, 'ignore', 2, execa);
test('Cannot pass "ignore" and another value to stdio[*]', testInvalidArrayValue, 'ignore', 3, execa);
test('Cannot pass "ignore" and another value to stdin - sync', testInvalidArrayValue, 'ignore', 0, execaSync);
test('Cannot pass "ignore" and another value to stdout - sync', testInvalidArrayValue, 'ignore', 1, execaSync);
test('Cannot pass "ignore" and another value to stderr - sync', testInvalidArrayValue, 'ignore', 2, execaSync);
test('Cannot pass "ignore" and another value to stdio[*] - sync', testInvalidArrayValue, 'ignore', 3, execaSync);
test('Cannot pass "ipc" and another value to stdin', testInvalidArrayValue, 'ipc', 0, execa);
test('Cannot pass "ipc" and another value to stdout', testInvalidArrayValue, 'ipc', 1, execa);
test('Cannot pass "ipc" and another value to stderr', testInvalidArrayValue, 'ipc', 2, execa);
test('Cannot pass "ipc" and another value to stdio[*]', testInvalidArrayValue, 'ipc', 3, execa);
test('Cannot pass "ipc" and another value to stdin - sync', testInvalidArrayValue, 'ipc', 0, execaSync);
test('Cannot pass "ipc" and another value to stdout - sync', testInvalidArrayValue, 'ipc', 1, execaSync);
test('Cannot pass "ipc" and another value to stderr - sync', testInvalidArrayValue, 'ipc', 2, execaSync);
test('Cannot pass "ipc" and another value to stdio[*] - sync', testInvalidArrayValue, 'ipc', 3, execaSync);
================================================
FILE: test/stdio/handle-normal.js
================================================
import test from 'ava';
import {execa} from '../../index.js';
import {getStdio} from '../helpers/stdio.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString, foobarUint8Array} from '../helpers/input.js';
setFixtureDirectory();
const testInputOverlapped = async (t, fdNumber) => {
const {stdout} = await execa('stdin-fd.js', [`${fdNumber}`, foobarString], getStdio(fdNumber, [foobarUint8Array, 'overlapped', 'pipe']));
t.is(stdout, foobarString);
};
test('stdin can be ["overlapped", "pipe"]', testInputOverlapped, 0);
test('stdio[*] input can be ["overlapped", "pipe"]', testInputOverlapped, 3);
const testOutputOverlapped = async (t, fdNumber) => {
const {stdio} = await execa('noop-fd.js', [`${fdNumber}`, foobarString], getStdio(fdNumber, ['overlapped', 'pipe']));
t.is(stdio[fdNumber], foobarString);
};
test('stdout can be ["overlapped", "pipe"]', testOutputOverlapped, 1);
test('stderr can be ["overlapped", "pipe"]', testOutputOverlapped, 2);
test('stdio[*] output can be ["overlapped", "pipe"]', testOutputOverlapped, 3);
const testFd3Undefined = async (t, stdioOption, options) => {
const subprocess = execa('empty.js', {...getStdio(3, stdioOption), ...options});
t.is(subprocess.stdio.length, 4);
t.is(subprocess.stdio[3], null);
const {stdio} = await subprocess;
t.is(stdio.length, 4);
t.is(stdio[3], undefined);
};
test('stdio[*] undefined means "ignore"', testFd3Undefined, undefined, {});
test('stdio[*] null means "ignore"', testFd3Undefined, null, {});
test('stdio[*] [undefined] means "ignore"', testFd3Undefined, [undefined], {});
test('stdio[*] [null] means "ignore"', testFd3Undefined, [null], {});
test('stdio[*] undefined means "ignore", "lines: true"', testFd3Undefined, undefined, {lines: true});
test('stdio[*] null means "ignore", "lines: true"', testFd3Undefined, null, {lines: true});
test('stdio[*] [undefined] means "ignore", "lines: true"', testFd3Undefined, [undefined], {lines: true});
test('stdio[*] [null] means "ignore", "lines: true"', testFd3Undefined, [null], {lines: true});
test('stdio[*] undefined means "ignore", "encoding: hex"', testFd3Undefined, undefined, {encoding: 'hex'});
test('stdio[*] null means "ignore", "encoding: hex"', testFd3Undefined, null, {encoding: 'hex'});
test('stdio[*] [undefined] means "ignore", "encoding: hex"', testFd3Undefined, [undefined], {encoding: 'hex'});
test('stdio[*] [null] means "ignore", "encoding: hex"', testFd3Undefined, [null], {encoding: 'hex'});
================================================
FILE: test/stdio/handle-options.js
================================================
import test from 'ava';
import {execa} from '../../index.js';
import {getStdio} from '../helpers/stdio.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
setFixtureDirectory();
const testNoPipeOption = async (t, stdioOption, fdNumber) => {
const subprocess = execa('empty.js', getStdio(fdNumber, stdioOption));
t.is(subprocess.stdio[fdNumber], null);
await subprocess;
};
test('stdin can be "ignore"', testNoPipeOption, 'ignore', 0);
test('stdin can be ["ignore"]', testNoPipeOption, ['ignore'], 0);
test('stdin can be ["ignore", "ignore"]', testNoPipeOption, ['ignore', 'ignore'], 0);
test('stdin can be "ipc"', testNoPipeOption, 'ipc', 0);
test('stdin can be ["ipc"]', testNoPipeOption, ['ipc'], 0);
test('stdin can be "inherit"', testNoPipeOption, 'inherit', 0);
test('stdin can be ["inherit"]', testNoPipeOption, ['inherit'], 0);
test('stdin can be 0', testNoPipeOption, 0, 0);
test('stdin can be [0]', testNoPipeOption, [0], 0);
test('stdout can be "ignore"', testNoPipeOption, 'ignore', 1);
test('stdout can be ["ignore"]', testNoPipeOption, ['ignore'], 1);
test('stdout can be ["ignore", "ignore"]', testNoPipeOption, ['ignore', 'ignore'], 1);
test('stdout can be "ipc"', testNoPipeOption, 'ipc', 1);
test('stdout can be ["ipc"]', testNoPipeOption, ['ipc'], 1);
test('stdout can be "inherit"', testNoPipeOption, 'inherit', 1);
test('stdout can be ["inherit"]', testNoPipeOption, ['inherit'], 1);
test('stdout can be 1', testNoPipeOption, 1, 1);
test('stdout can be [1]', testNoPipeOption, [1], 1);
test('stderr can be "ignore"', testNoPipeOption, 'ignore', 2);
test('stderr can be ["ignore"]', testNoPipeOption, ['ignore'], 2);
test('stderr can be ["ignore", "ignore"]', testNoPipeOption, ['ignore', 'ignore'], 2);
test('stderr can be "ipc"', testNoPipeOption, 'ipc', 2);
test('stderr can be ["ipc"]', testNoPipeOption, ['ipc'], 2);
test('stderr can be "inherit"', testNoPipeOption, 'inherit', 2);
test('stderr can be ["inherit"]', testNoPipeOption, ['inherit'], 2);
test('stderr can be 2', testNoPipeOption, 2, 2);
test('stderr can be [2]', testNoPipeOption, [2], 2);
test('stdio[*] can be "ignore"', testNoPipeOption, 'ignore', 3);
test('stdio[*] can be ["ignore"]', testNoPipeOption, ['ignore'], 3);
test('stdio[*] can be ["ignore", "ignore"]', testNoPipeOption, ['ignore', 'ignore'], 3);
test('stdio[*] can be "ipc"', testNoPipeOption, 'ipc', 3);
test('stdio[*] can be ["ipc"]', testNoPipeOption, ['ipc'], 3);
test('stdio[*] can be "inherit"', testNoPipeOption, 'inherit', 3);
test('stdio[*] can be ["inherit"]', testNoPipeOption, ['inherit'], 3);
test('stdio[*] can be 3', testNoPipeOption, 3, 3);
test('stdio[*] can be [3]', testNoPipeOption, [3], 3);
================================================
FILE: test/stdio/iterable.js
================================================
import {once} from 'node:events';
import {setImmediate} from 'node:timers/promises';
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {getStdio} from '../helpers/stdio.js';
import {
foobarString,
foobarObject,
foobarObjectString,
foobarArray,
} from '../helpers/input.js';
import {
noopGenerator,
serializeGenerator,
infiniteGenerator,
throwingGenerator,
} from '../helpers/generator.js';
const stringGenerator = function * () {
yield * foobarArray;
};
const textEncoder = new TextEncoder();
const binaryFoo = textEncoder.encode('foo');
const binaryBar = textEncoder.encode('bar');
const binaryArray = [binaryFoo, binaryBar];
const binaryGenerator = function * () {
yield * binaryArray;
};
const mixedArray = [foobarArray[0], binaryArray[1]];
const mixedGenerator = function * () {
yield * mixedArray;
};
const asyncGenerator = async function * () {
await setImmediate();
yield * foobarArray;
};
setFixtureDirectory();
const testIterable = async (t, stdioOption, fdNumber, execaMethod) => {
const {stdout} = await execaMethod('stdin-fd.js', [`${fdNumber}`], getStdio(fdNumber, stdioOption));
t.is(stdout, 'foobar');
};
test.serial('stdin option can be an array of strings', testIterable, [foobarArray], 0, execa);
test.serial('stdin option can be an array of strings - sync', testIterable, [foobarArray], 0, execaSync);
test.serial('stdio[*] option can be an array of strings', testIterable, [foobarArray], 3, execa);
test.serial('stdin option can be an array of Uint8Arrays', testIterable, [binaryArray], 0, execa);
test.serial('stdin option can be an array of Uint8Arrays - sync', testIterable, [binaryArray], 0, execaSync);
test.serial('stdio[*] option can be an array of Uint8Arrays', testIterable, [binaryArray], 3, execa);
test.serial('stdin option can be an iterable of strings', testIterable, stringGenerator(), 0, execa);
test.serial('stdin option can be an iterable of strings - sync', testIterable, stringGenerator(), 0, execaSync);
test.serial('stdio[*] option can be an iterable of strings', testIterable, stringGenerator(), 3, execa);
test.serial('stdin option can be an iterable of Uint8Arrays', testIterable, binaryGenerator(), 0, execa);
test.serial('stdin option can be an iterable of Uint8Arrays - sync', testIterable, binaryGenerator(), 0, execaSync);
test.serial('stdio[*] option can be an iterable of Uint8Arrays', testIterable, binaryGenerator(), 3, execa);
test.serial('stdin option can be an iterable of strings + Uint8Arrays', testIterable, mixedGenerator(), 0, execa);
test.serial('stdin option can be an iterable of strings + Uint8Arrays - sync', testIterable, mixedGenerator(), 0, execaSync);
test.serial('stdio[*] option can be an iterable of strings + Uint8Arrays', testIterable, mixedGenerator(), 3, execa);
test.serial('stdin option can be an async iterable', testIterable, asyncGenerator(), 0, execa);
test.serial('stdio[*] option can be an async iterable', testIterable, asyncGenerator(), 3, execa);
const foobarObjectGenerator = function * () {
yield foobarObject;
};
const foobarAsyncObjectGenerator = async function * () {
yield foobarObject;
};
const testObjectIterable = async (t, stdioOption, fdNumber, execaMethod) => {
const {stdout} = await execaMethod('stdin-fd.js', [`${fdNumber}`], getStdio(fdNumber, [stdioOption, serializeGenerator(true)]));
t.is(stdout, foobarObjectString);
};
test('stdin option can be an array of objects', testObjectIterable, [foobarObject], 0, execa);
test('stdio[*] option can be an array of objects', testObjectIterable, [foobarObject], 3, execa);
test('stdin option can be an iterable of objects', testObjectIterable, foobarObjectGenerator(), 0, execa);
test('stdio[*] option can be an iterable of objects', testObjectIterable, foobarObjectGenerator(), 3, execa);
test('stdin option can be an async iterable of objects', testObjectIterable, foobarAsyncObjectGenerator(), 0, execa);
test('stdio[*] option can be an async iterable of objects', testObjectIterable, foobarAsyncObjectGenerator(), 3, execa);
test('stdin option can be an array of objects - sync', testObjectIterable, [foobarObject], 0, execaSync);
test('stdin option can be an iterable of objects - sync', testObjectIterable, foobarObjectGenerator(), 0, execaSync);
const testIterableNoGeneratorsSync = (t, stdioOption, fdNumber) => {
t.throws(() => {
execaSync('empty.js', getStdio(fdNumber, stdioOption));
}, {message: /must be used to serialize/});
};
test('stdin option cannot be an array of objects without generators - sync', testIterableNoGeneratorsSync, [[foobarObject]], 0);
test('stdin option cannot be an iterable of objects without generators - sync', testIterableNoGeneratorsSync, foobarObjectGenerator(), 0);
const testIterableNoSerializeSync = (t, stdioOption, fdNumber) => {
t.throws(() => {
execaSync('empty.js', getStdio(fdNumber, [stdioOption, noopGenerator()]));
}, {message: /The `stdin` option's transform must use "objectMode: true" to receive as input: object/});
};
test('stdin option cannot be an array of objects without serializing - sync', testIterableNoSerializeSync, [foobarObject], 0);
test('stdin option cannot be an iterable of objects without serializing - sync', testIterableNoSerializeSync, foobarObjectGenerator(), 0);
const testIterableSync = (t, stdioOption, fdNumber) => {
t.throws(() => {
execaSync('empty.js', getStdio(fdNumber, stdioOption));
}, {message: /an iterable with synchronous methods/});
};
test('stdio[*] option cannot be an array of strings - sync', testIterableSync, [foobarArray], 3);
test('stdio[*] option cannot be an array of Uint8Arrays - sync', testIterableSync, [binaryArray], 3);
test('stdio[*] option cannot be an array of objects - sync', testIterableSync, [[foobarObject]], 3);
test('stdio[*] option cannot be an iterable of strings - sync', testIterableSync, stringGenerator(), 3);
test('stdio[*] option cannot be an iterable of Uint8Arrays - sync', testIterableSync, binaryGenerator(), 3);
test('stdio[*] option cannot be an iterable of objects - sync', testIterableSync, foobarObjectGenerator(), 3);
test('stdio[*] option cannot be multiple iterables - sync', testIterableSync, [stringGenerator(), stringGenerator()], 3);
const testAsyncIterableSync = (t, stdioOption, fdNumber) => {
t.throws(() => {
execaSync('empty.js', getStdio(fdNumber, stdioOption));
}, {message: /an async iterable with synchronous method/});
};
test('stdin option cannot be an async iterable - sync', testAsyncIterableSync, asyncGenerator(), 0);
test('stdio[*] option cannot be an async iterable - sync', testAsyncIterableSync, asyncGenerator(), 3);
test('stdin option cannot be an async iterable of objects - sync', testAsyncIterableSync, foobarAsyncObjectGenerator(), 0);
test('stdio[*] option cannot be an async iterable of objects - sync', testAsyncIterableSync, foobarAsyncObjectGenerator(), 3);
const testIterableError = async (t, fdNumber) => {
const cause = new Error(foobarString);
const error = await t.throwsAsync(execa('stdin-fd.js', [`${fdNumber}`], getStdio(fdNumber, throwingGenerator(cause)().transform())));
t.is(error.cause, cause);
};
test('stdin option handles errors in iterables', testIterableError, 0);
test('stdio[*] option handles errors in iterables', testIterableError, 3);
const testIterableErrorSync = (t, fdNumber) => {
const cause = new Error(foobarString);
const error = t.throws(() => {
execaSync('stdin-fd.js', [`${fdNumber}`], getStdio(fdNumber, throwingGenerator(cause)().transform()));
});
t.is(error, cause);
};
test('stdin option handles errors in iterables - sync', testIterableErrorSync, 0);
test('stdio[*] option handles errors in iterables - sync', testIterableErrorSync, 3);
const testNoIterableOutput = (t, stdioOption, fdNumber, execaMethod) => {
t.throws(() => {
execaMethod('empty.js', getStdio(fdNumber, stdioOption));
}, {message: /cannot be an iterable/});
};
test('stdout option cannot be an array of strings', testNoIterableOutput, [foobarArray], 1, execa);
test('stderr option cannot be an array of strings', testNoIterableOutput, [foobarArray], 2, execa);
test('stdout option cannot be an array of strings - sync', testNoIterableOutput, [foobarArray], 1, execaSync);
test('stderr option cannot be an array of strings - sync', testNoIterableOutput, [foobarArray], 2, execaSync);
test('stdout option cannot be an iterable', testNoIterableOutput, stringGenerator(), 1, execa);
test('stderr option cannot be an iterable', testNoIterableOutput, stringGenerator(), 2, execa);
test('stdout option cannot be an iterable - sync', testNoIterableOutput, stringGenerator(), 1, execaSync);
test('stderr option cannot be an iterable - sync', testNoIterableOutput, stringGenerator(), 2, execaSync);
test('stdin option can be an infinite iterable', async t => {
const iterable = infiniteGenerator().transform();
const subprocess = execa('stdin.js', getStdio(0, iterable));
await once(subprocess.stdout, 'data');
subprocess.kill();
const {stdout} = await t.throwsAsync(subprocess);
t.true(stdout.startsWith('foo'));
t.deepEqual(await iterable.next(), {value: undefined, done: true});
});
const testMultipleIterable = async (t, stdioOption, fdNumber, execaMethod) => {
const {stdout} = await execaMethod('stdin-fd.js', [`${fdNumber}`], getStdio(fdNumber, stdioOption));
const expectedOutputs = [
`${foobarArray[0]}${foobarArray[1]}${foobarArray[0]}${foobarArray[1]}`,
`${foobarArray[0]}${foobarArray[0]}${foobarArray[1]}${foobarArray[1]}`,
];
t.true(expectedOutputs.includes(stdout));
};
test('stdin option can be multiple iterables', testMultipleIterable, [stringGenerator(), stringGenerator()], 0, execa);
test('stdio[*] option can be multiple iterables', testMultipleIterable, [stringGenerator(), stringGenerator()], 3, execa);
test('stdin option can be multiple iterables - sync', testMultipleIterable, [stringGenerator(), stringGenerator()], 0, execaSync);
test('stdin option can be multiple mixed iterables', testMultipleIterable, [stringGenerator(), binaryGenerator()], 0, execa);
test('stdio[*] option can be multiple mixed iterables', testMultipleIterable, [stringGenerator(), binaryGenerator()], 3, execa);
test('stdin option can be multiple mixed iterables - sync', testMultipleIterable, [stringGenerator(), binaryGenerator()], 0, execaSync);
test('stdin option can be sync/async mixed iterables', testMultipleIterable, [stringGenerator(), asyncGenerator()], 0, execa);
test('stdio[*] option can be sync/async mixed iterables', testMultipleIterable, [stringGenerator(), asyncGenerator()], 3, execa);
test('stdin option iterable is canceled on subprocess error', async t => {
const iterable = infiniteGenerator().transform();
await t.throwsAsync(execa('stdin.js', {stdin: iterable, timeout: 1}), {message: /timed out/});
// eslint-disable-next-line no-empty
for await (const _ of iterable) {}
});
================================================
FILE: test/stdio/lines-main.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {fullStdio} from '../helpers/stdio.js';
import {getOutputsGenerator} from '../helpers/generator.js';
import {foobarString} from '../helpers/input.js';
import {
simpleFull,
simpleChunks,
simpleFullEndChunks,
simpleLines,
simpleFullEndLines,
noNewlinesChunks,
getSimpleChunkSubprocessAsync,
} from '../helpers/lines.js';
setFixtureDirectory();
// eslint-disable-next-line max-params
const testStreamLines = async (t, fdNumber, input, expectedOutput, lines, stripFinalNewline, execaMethod) => {
const {stdio} = await execaMethod('noop-fd.js', [`${fdNumber}`, input], {...fullStdio, lines, stripFinalNewline});
t.deepEqual(stdio[fdNumber], expectedOutput);
};
test('"lines: true" splits lines, stdout', testStreamLines, 1, simpleFull, simpleLines, true, false, execa);
test('"lines: true" splits lines, stdout, fd-specific', testStreamLines, 1, simpleFull, simpleLines, {stdout: true}, false, execa);
test('"lines: true" splits lines, stderr', testStreamLines, 2, simpleFull, simpleLines, true, false, execa);
test('"lines: true" splits lines, stderr, fd-specific', testStreamLines, 2, simpleFull, simpleLines, {stderr: true}, false, execa);
test('"lines: true" splits lines, stdio[*]', testStreamLines, 3, simpleFull, simpleLines, true, false, execa);
test('"lines: true" splits lines, stdio[*], fd-specific', testStreamLines, 3, simpleFull, simpleLines, {fd3: true}, false, execa);
test('"lines: true" splits lines, stdout, stripFinalNewline', testStreamLines, 1, simpleFull, noNewlinesChunks, true, true, execa);
test('"lines: true" splits lines, stdout, stripFinalNewline, fd-specific', testStreamLines, 1, simpleFull, noNewlinesChunks, true, {stdout: true}, execa);
test('"lines: true" splits lines, stderr, stripFinalNewline', testStreamLines, 2, simpleFull, noNewlinesChunks, true, true, execa);
test('"lines: true" splits lines, stderr, stripFinalNewline, fd-specific', testStreamLines, 2, simpleFull, noNewlinesChunks, true, {stderr: true}, execa);
test('"lines: true" splits lines, stdio[*], stripFinalNewline', testStreamLines, 3, simpleFull, noNewlinesChunks, true, true, execa);
test('"lines: true" splits lines, stdio[*], stripFinalNewline, fd-specific', testStreamLines, 3, simpleFull, noNewlinesChunks, true, {fd3: true}, execa);
test('"lines: true" splits lines, stdout, sync', testStreamLines, 1, simpleFull, simpleLines, true, false, execaSync);
test('"lines: true" splits lines, stdout, fd-specific, sync', testStreamLines, 1, simpleFull, simpleLines, {stdout: true}, false, execaSync);
test('"lines: true" splits lines, stderr, sync', testStreamLines, 2, simpleFull, simpleLines, true, false, execaSync);
test('"lines: true" splits lines, stderr, fd-specific, sync', testStreamLines, 2, simpleFull, simpleLines, {stderr: true}, false, execaSync);
test('"lines: true" splits lines, stdio[*], sync', testStreamLines, 3, simpleFull, simpleLines, true, false, execaSync);
test('"lines: true" splits lines, stdio[*], fd-specific, sync', testStreamLines, 3, simpleFull, simpleLines, {fd3: true}, false, execaSync);
test('"lines: true" splits lines, stdout, stripFinalNewline, sync', testStreamLines, 1, simpleFull, noNewlinesChunks, true, true, execaSync);
test('"lines: true" splits lines, stdout, stripFinalNewline, fd-specific, sync', testStreamLines, 1, simpleFull, noNewlinesChunks, true, {stdout: true}, execaSync);
test('"lines: true" splits lines, stderr, stripFinalNewline, sync', testStreamLines, 2, simpleFull, noNewlinesChunks, true, true, execaSync);
test('"lines: true" splits lines, stderr, stripFinalNewline, fd-specific, sync', testStreamLines, 2, simpleFull, noNewlinesChunks, true, {stderr: true}, execaSync);
test('"lines: true" splits lines, stdio[*], stripFinalNewline, sync', testStreamLines, 3, simpleFull, noNewlinesChunks, true, true, execaSync);
test('"lines: true" splits lines, stdio[*], stripFinalNewline, fd-specific, sync', testStreamLines, 3, simpleFull, noNewlinesChunks, true, {fd3: true}, execaSync);
const bigArray = Array.from({length: 1e5}).fill('.\n');
const bigString = bigArray.join('');
const bigStringNoNewlines = '.'.repeat(1e6);
const bigStringNoNewlinesEnd = `${bigStringNoNewlines}\n`;
// eslint-disable-next-line max-params
const testStreamLinesGenerator = async (t, input, expectedLines, objectMode, binary, stripFinalNewline, execaMethod) => {
const {stdout} = await execaMethod('noop.js', {
stdout: getOutputsGenerator(input)(objectMode, binary),
lines: true,
stripFinalNewline,
});
t.deepEqual(stdout, expectedLines);
};
test('"lines: true" works with strings generators', testStreamLinesGenerator, simpleChunks, simpleFullEndLines, false, false, false, execa);
test('"lines: true" works with strings generators, binary', testStreamLinesGenerator, simpleChunks, simpleLines, false, true, false, execa);
test('"lines: true" works with big strings generators', testStreamLinesGenerator, [bigString], bigArray, false, false, false, execa);
test('"lines: true" works with big strings generators without newlines', testStreamLinesGenerator, [bigStringNoNewlines], [bigStringNoNewlinesEnd], false, false, false, execa);
test('"lines: true" is a noop with strings generators, objectMode', testStreamLinesGenerator, simpleFullEndChunks, simpleFullEndChunks, true, false, false, execa);
test('"lines: true" is a noop with strings generators, stripFinalNewline, objectMode', testStreamLinesGenerator, simpleFullEndChunks, simpleFullEndChunks, true, false, true, execa);
test('"lines: true" is a noop with strings generators, stripFinalNewline, fd-specific, objectMode', testStreamLinesGenerator, simpleFullEndChunks, simpleFullEndChunks, true, false, {stdout: true}, execa);
test('"lines: true" is a noop with strings generators, binary, objectMode', testStreamLinesGenerator, simpleChunks, simpleChunks, true, true, false, execa);
test('"lines: true" is a noop big strings generators, objectMode', testStreamLinesGenerator, [bigString], [bigString], true, false, false, execa);
test('"lines: true" is a noop big strings generators without newlines, objectMode', testStreamLinesGenerator, [bigStringNoNewlines], [bigStringNoNewlines], true, false, false, execa);
test('"lines: true" works with strings generators, sync', testStreamLinesGenerator, simpleChunks, simpleFullEndLines, false, false, false, execaSync);
test('"lines: true" works with strings generators, binary, sync', testStreamLinesGenerator, simpleChunks, simpleLines, false, true, false, execaSync);
test('"lines: true" works with big strings generators, sync', testStreamLinesGenerator, [bigString], bigArray, false, false, false, execaSync);
test('"lines: true" works with big strings generators without newlines, sync', testStreamLinesGenerator, [bigStringNoNewlines], [bigStringNoNewlinesEnd], false, false, false, execaSync);
test('"lines: true" is a noop with strings generators, objectMode, sync', testStreamLinesGenerator, simpleFullEndChunks, simpleFullEndChunks, true, false, false, execaSync);
test('"lines: true" is a noop with strings generators, stripFinalNewline, objectMode, sync', testStreamLinesGenerator, simpleFullEndChunks, simpleFullEndChunks, true, false, true, execaSync);
test('"lines: true" is a noop with strings generators, stripFinalNewline, fd-specific, objectMode, sync', testStreamLinesGenerator, simpleFullEndChunks, simpleFullEndChunks, true, false, {stdout: true}, execaSync);
test('"lines: true" is a noop with strings generators, binary, objectMode, sync', testStreamLinesGenerator, simpleChunks, simpleChunks, true, true, false, execaSync);
test('"lines: true" is a noop big strings generators, objectMode, sync', testStreamLinesGenerator, [bigString], [bigString], true, false, false, execaSync);
test('"lines: true" is a noop big strings generators without newlines, objectMode, sync', testStreamLinesGenerator, [bigStringNoNewlines], [bigStringNoNewlines], true, false, false, execaSync);
test('"lines: true" stops on stream error', async t => {
const cause = new Error(foobarString);
const error = await t.throwsAsync(getSimpleChunkSubprocessAsync({
* stdout(line) {
if (line === noNewlinesChunks[2]) {
throw cause;
}
yield line;
},
}));
t.is(error.cause, cause);
t.deepEqual(error.stdout, noNewlinesChunks.slice(0, 2));
});
test('"lines: true" stops on stream error event', async t => {
const cause = new Error(foobarString);
const subprocess = getSimpleChunkSubprocessAsync();
subprocess.stdout.emit('error', cause);
const error = await t.throwsAsync(subprocess);
t.is(error.cause, cause);
t.deepEqual(error.stdout, []);
});
================================================
FILE: test/stdio/lines-max-buffer.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {simpleLines, noNewlinesChunks, getSimpleChunkSubprocessAsync} from '../helpers/lines.js';
import {assertErrorMessage} from '../helpers/max-buffer.js';
setFixtureDirectory();
const maxBuffer = simpleLines.length - 1;
const testBelowMaxBuffer = async (t, lines) => {
const {isMaxBuffer, stdout} = await getSimpleChunkSubprocessAsync({lines, maxBuffer: maxBuffer + 1});
t.false(isMaxBuffer);
t.deepEqual(stdout, noNewlinesChunks);
};
test('"lines: true" can be below "maxBuffer"', testBelowMaxBuffer, true);
test('"lines: true" can be below "maxBuffer", fd-specific', testBelowMaxBuffer, {stdout: true});
const testAboveMaxBuffer = async (t, lines) => {
const {isMaxBuffer, shortMessage, stdout} = await t.throwsAsync(getSimpleChunkSubprocessAsync({lines, maxBuffer}));
t.true(isMaxBuffer);
assertErrorMessage(t, shortMessage, {length: maxBuffer, unit: 'lines'});
t.deepEqual(stdout, noNewlinesChunks.slice(0, maxBuffer));
};
test('"lines: true" can be above "maxBuffer"', testAboveMaxBuffer, true);
test('"lines: true" can be above "maxBuffer", fd-specific', testAboveMaxBuffer, {stdout: true});
const testMaxBufferUnit = async (t, lines) => {
const {isMaxBuffer, shortMessage, stdout} = await t.throwsAsync(execa('noop-repeat.js', ['1', '...\n'], {lines, maxBuffer}));
t.true(isMaxBuffer);
assertErrorMessage(t, shortMessage, {length: maxBuffer, unit: 'lines'});
t.deepEqual(stdout, ['...', '...']);
};
test('"maxBuffer" is measured in lines with "lines: true"', testMaxBufferUnit, true);
test('"maxBuffer" is measured in lines with "lines: true", fd-specific', testMaxBufferUnit, {stdout: true});
const testMaxBufferUnitSync = (t, lines) => {
const {isMaxBuffer, shortMessage, stdout} = t.throws(() => {
execaSync('noop-repeat.js', ['1', '...\n'], {lines, maxBuffer});
}, {code: 'ENOBUFS'});
t.true(isMaxBuffer);
assertErrorMessage(t, shortMessage, {execaMethod: execaSync, length: maxBuffer});
t.deepEqual(stdout, ['..']);
};
test('"maxBuffer" is measured in bytes with "lines: true", sync', testMaxBufferUnitSync, true);
test('"maxBuffer" is measured in bytes with "lines: true", fd-specific, sync', testMaxBufferUnitSync, {stdout: true});
================================================
FILE: test/stdio/lines-mixed.js
================================================
import {Writable} from 'node:stream';
import test from 'ava';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {assertStreamOutput, assertStreamDataEvents, assertIterableChunks} from '../helpers/convert.js';
import {
simpleFull,
simpleLines,
noNewlinesChunks,
getSimpleChunkSubprocessAsync,
} from '../helpers/lines.js';
setFixtureDirectory();
const testAsyncIteration = async (t, expectedLines, stripFinalNewline) => {
const subprocess = getSimpleChunkSubprocessAsync({stripFinalNewline});
t.false(subprocess.stdout.readableObjectMode);
await assertStreamOutput(t, subprocess.stdout, simpleFull);
const {stdout} = await subprocess;
t.deepEqual(stdout, expectedLines);
};
test('"lines: true" works with stream async iteration', testAsyncIteration, simpleLines, false);
test('"lines: true" works with stream async iteration, stripFinalNewline', testAsyncIteration, noNewlinesChunks, true);
const testDataEvents = async (t, expectedLines, stripFinalNewline) => {
const subprocess = getSimpleChunkSubprocessAsync({stripFinalNewline});
await assertStreamDataEvents(t, subprocess.stdout, simpleFull);
const {stdout} = await subprocess;
t.deepEqual(stdout, expectedLines);
};
test('"lines: true" works with stream "data" events', testDataEvents, simpleLines, false);
test('"lines: true" works with stream "data" events, stripFinalNewline', testDataEvents, noNewlinesChunks, true);
const testWritableStream = async (t, expectedLines, stripFinalNewline) => {
let output = '';
const writable = new Writable({
write(line, encoding, done) {
output += line.toString();
done();
},
decodeStrings: false,
});
const {stdout} = await getSimpleChunkSubprocessAsync({stripFinalNewline, stdout: ['pipe', writable]});
t.deepEqual(output, simpleFull);
t.deepEqual(stdout, expectedLines);
};
test('"lines: true" works with writable streams targets', testWritableStream, simpleLines, false);
test('"lines: true" works with writable streams targets, stripFinalNewline', testWritableStream, noNewlinesChunks, true);
const testIterable = async (t, expectedLines, stripFinalNewline) => {
const subprocess = getSimpleChunkSubprocessAsync({stripFinalNewline});
await assertIterableChunks(t, subprocess, noNewlinesChunks);
const {stdout} = await subprocess;
t.deepEqual(stdout, expectedLines);
};
test('"lines: true" works with subprocess.iterable()', testIterable, simpleLines, false);
test('"lines: true" works with subprocess.iterable(), stripFinalNewline', testIterable, noNewlinesChunks, true);
================================================
FILE: test/stdio/lines-noop.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {getOutputsGenerator} from '../helpers/generator.js';
import {foobarObject} from '../helpers/input.js';
import {
simpleFull,
simpleFullUint8Array,
simpleFullHex,
simpleFullUtf16Uint8Array,
simpleLines,
noNewlinesChunks,
getSimpleChunkSubprocess,
} from '../helpers/lines.js';
setFixtureDirectory();
const testStreamLinesNoop = async (t, lines, execaMethod) => {
const {stdout} = await execaMethod('noop-fd.js', ['1', simpleFull], {lines});
t.is(stdout, simpleFull);
};
test('"lines: false" is a noop', testStreamLinesNoop, false, execa);
test('"lines: false" is a noop, fd-specific', testStreamLinesNoop, {stderr: true}, execa);
test('"lines: false" is a noop, fd-specific none', testStreamLinesNoop, {}, execa);
test('"lines: false" is a noop, sync', testStreamLinesNoop, false, execaSync);
test('"lines: false" is a noop, fd-specific, sync', testStreamLinesNoop, {stderr: true}, execaSync);
test('"lines: false" is a noop, fd-specific none, sync', testStreamLinesNoop, {}, execaSync);
const testLinesObjectMode = async (t, lines, execaMethod) => {
const {stdout} = await execaMethod('noop.js', {
stdout: getOutputsGenerator([foobarObject])(true),
lines,
});
t.deepEqual(stdout, [foobarObject]);
};
test('"lines: true" is a noop with objects generators, objectMode', testLinesObjectMode, true, execa);
test('"lines: true" is a noop with objects generators, fd-specific, objectMode', testLinesObjectMode, {stdout: true}, execa);
test('"lines: true" is a noop with objects generators, objectMode, sync', testLinesObjectMode, true, execaSync);
test('"lines: true" is a noop with objects generators, fd-specific, objectMode, sync', testLinesObjectMode, {stdout: true}, execaSync);
// eslint-disable-next-line max-params
const testEncoding = async (t, input, expectedOutput, encoding, lines, stripFinalNewline, execaMethod) => {
const {stdout} = await execaMethod('stdin.js', {
lines,
stripFinalNewline,
encoding,
input,
});
t.deepEqual(stdout, expectedOutput);
};
test('"lines: true" is a noop with "encoding: utf16"', testEncoding, simpleFullUtf16Uint8Array, simpleLines, 'utf16le', true, false, execa);
test('"lines: true" is a noop with "encoding: utf16", fd-specific', testEncoding, simpleFullUtf16Uint8Array, simpleLines, 'utf16le', {stdout: true}, false, execa);
test('"lines: true" is a noop with "encoding: utf16", stripFinalNewline', testEncoding, simpleFullUtf16Uint8Array, noNewlinesChunks, 'utf16le', true, true, execa);
test('"lines: true" is a noop with "encoding: utf16", stripFinalNewline, fd-specific', testEncoding, simpleFullUtf16Uint8Array, noNewlinesChunks, 'utf16le', true, {stdout: true}, execa);
test('"lines: true" is a noop with "encoding: buffer"', testEncoding, simpleFull, simpleFullUint8Array, 'buffer', true, false, execa);
test('"lines: true" is a noop with "encoding: buffer", fd-specific', testEncoding, simpleFull, simpleFullUint8Array, 'buffer', {stdout: true}, false, execa);
test('"lines: true" is a noop with "encoding: buffer", stripFinalNewline', testEncoding, simpleFull, simpleFullUint8Array, 'buffer', true, false, execa);
test('"lines: true" is a noop with "encoding: buffer", stripFinalNewline, fd-specific', testEncoding, simpleFull, simpleFullUint8Array, 'buffer', true, {stdout: false}, execa);
test('"lines: true" is a noop with "encoding: hex"', testEncoding, simpleFull, simpleFullHex, 'hex', true, false, execa);
test('"lines: true" is a noop with "encoding: hex", fd-specific', testEncoding, simpleFull, simpleFullHex, 'hex', {stdout: true}, false, execa);
test('"lines: true" is a noop with "encoding: hex", stripFinalNewline', testEncoding, simpleFull, simpleFullHex, 'hex', true, true, execa);
test('"lines: true" is a noop with "encoding: hex", stripFinalNewline, fd-specific', testEncoding, simpleFull, simpleFullHex, 'hex', true, {stdout: true}, execa);
test('"lines: true" is a noop with "encoding: utf16", sync', testEncoding, simpleFullUtf16Uint8Array, simpleLines, 'utf16le', true, false, execaSync);
test('"lines: true" is a noop with "encoding: utf16", fd-specific, sync', testEncoding, simpleFullUtf16Uint8Array, simpleLines, 'utf16le', {stdout: true}, false, execaSync);
test('"lines: true" is a noop with "encoding: utf16", stripFinalNewline, sync', testEncoding, simpleFullUtf16Uint8Array, noNewlinesChunks, 'utf16le', true, true, execaSync);
test('"lines: true" is a noop with "encoding: utf16", stripFinalNewline, fd-specific, sync', testEncoding, simpleFullUtf16Uint8Array, noNewlinesChunks, 'utf16le', true, {stdout: true}, execaSync);
test('"lines: true" is a noop with "encoding: buffer", sync', testEncoding, simpleFull, simpleFullUint8Array, 'buffer', true, false, execaSync);
test('"lines: true" is a noop with "encoding: buffer", fd-specific, sync', testEncoding, simpleFull, simpleFullUint8Array, 'buffer', {stdout: true}, false, execaSync);
test('"lines: true" is a noop with "encoding: buffer", stripFinalNewline, sync', testEncoding, simpleFull, simpleFullUint8Array, 'buffer', true, false, execaSync);
test('"lines: true" is a noop with "encoding: buffer", stripFinalNewline, fd-specific, sync', testEncoding, simpleFull, simpleFullUint8Array, 'buffer', true, {stdout: false}, execaSync);
test('"lines: true" is a noop with "encoding: hex", sync', testEncoding, simpleFull, simpleFullHex, 'hex', true, false, execaSync);
test('"lines: true" is a noop with "encoding: hex", fd-specific, sync', testEncoding, simpleFull, simpleFullHex, 'hex', {stdout: true}, false, execaSync);
test('"lines: true" is a noop with "encoding: hex", stripFinalNewline, sync', testEncoding, simpleFull, simpleFullHex, 'hex', true, true, execaSync);
test('"lines: true" is a noop with "encoding: hex", stripFinalNewline, fd-specific, sync', testEncoding, simpleFull, simpleFullHex, 'hex', true, {stdout: true}, execaSync);
const testLinesNoBuffer = async (t, lines, buffer, execaMethod) => {
const {stdout} = await getSimpleChunkSubprocess(execaMethod, {lines, buffer});
t.is(stdout, undefined);
};
test('"lines: true" is a noop with "buffer: false"', testLinesNoBuffer, true, false, execa);
test('"lines: true" is a noop with "buffer: false", fd-specific buffer', testLinesNoBuffer, true, {stdout: false}, execa);
test('"lines: true" is a noop with "buffer: false", fd-specific lines', testLinesNoBuffer, {stdout: true}, false, execa);
test('"lines: true" is a noop with "buffer: false", sync', testLinesNoBuffer, true, false, execaSync);
test('"lines: true" is a noop with "buffer: false", fd-specific buffer, sync', testLinesNoBuffer, true, {stdout: false}, execaSync);
test('"lines: true" is a noop with "buffer: false", fd-specific lines, sync', testLinesNoBuffer, {stdout: true}, false, execaSync);
================================================
FILE: test/stdio/native-fd.js
================================================
import {platform} from 'node:process';
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {getStdio, fullStdio} from '../helpers/stdio.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
import {nestedSubprocess} from '../helpers/nested.js';
setFixtureDirectory();
const isLinux = platform === 'linux';
const isWindows = platform === 'win32';
const testFd3InheritOutput = async (t, stdioOption, isSync) => {
const {stdio} = await nestedSubprocess('noop-fd.js', ['3', foobarString], {...getStdio(3, stdioOption), isSync}, fullStdio);
t.is(stdio[3], foobarString);
};
test('stdio[*] output can use "inherit"', testFd3InheritOutput, 'inherit', false);
test('stdio[*] output can use ["inherit"]', testFd3InheritOutput, ['inherit'], false);
test('stdio[*] output can use "inherit", sync', testFd3InheritOutput, 'inherit', true);
test('stdio[*] output can use ["inherit"], sync', testFd3InheritOutput, ['inherit'], true);
if (isLinux) {
const testOverflowStream = async (t, fdNumber, stdioOption, isSync) => {
const {stdout} = await nestedSubprocess('empty.js', {...getStdio(fdNumber, stdioOption), isSync}, fullStdio);
t.is(stdout, '');
};
test('stdin can use 4+', testOverflowStream, 0, 4, false);
test('stdin can use [4+]', testOverflowStream, 0, [4], false);
test('stdout can use 4+', testOverflowStream, 1, 4, false);
test('stdout can use [4+]', testOverflowStream, 1, [4], false);
test('stderr can use 4+', testOverflowStream, 2, 4, false);
test('stderr can use [4+]', testOverflowStream, 2, [4], false);
test('stdio[*] can use 4+', testOverflowStream, 3, 4, false);
test('stdio[*] can use [4+]', testOverflowStream, 3, [4], false);
test('stdin can use 4+, sync', testOverflowStream, 0, 4, true);
test('stdin can use [4+], sync', testOverflowStream, 0, [4], true);
test('stdout can use 4+, sync', testOverflowStream, 1, 4, true);
test('stdout can use [4+], sync', testOverflowStream, 1, [4], true);
test('stderr can use 4+, sync', testOverflowStream, 2, 4, true);
test('stderr can use [4+], sync', testOverflowStream, 2, [4], true);
test('stdio[*] can use 4+, sync', testOverflowStream, 3, 4, true);
test('stdio[*] can use [4+], sync', testOverflowStream, 3, [4], true);
}
const testOverflowStreamArray = (t, fdNumber, stdioOption) => {
t.throws(() => {
execa('empty.js', getStdio(fdNumber, stdioOption));
}, {message: /no such standard stream/});
};
test('stdin cannot use 4+ and another value', testOverflowStreamArray, 0, [4, 'pipe']);
test('stdout cannot use 4+ and another value', testOverflowStreamArray, 1, [4, 'pipe']);
test('stderr cannot use 4+ and another value', testOverflowStreamArray, 2, [4, 'pipe']);
test('stdio[*] cannot use 4+ and another value', testOverflowStreamArray, 3, [4, 'pipe']);
test('stdio[*] cannot use "inherit" and another value', testOverflowStreamArray, 3, ['inherit', 'pipe']);
const getInvalidFdCode = () => {
if (isLinux) {
return 'EINVAL';
}
return isWindows ? 'EBADF' : 'ENXIO';
};
const testOverflowStreamArraySync = (t, fdNumber) => {
t.throws(() => {
execaSync('noop-fd.js', [fdNumber, foobarString], getStdio(fdNumber, [4, 'pipe']));
}, {code: getInvalidFdCode()});
};
test('stdout cannot use 4+ and another value, sync', testOverflowStreamArraySync, 1);
test('stderr cannot use 4+ and another value, sync', testOverflowStreamArraySync, 2);
test('stdio[*] cannot use 4+ and another value, sync', testOverflowStreamArraySync, 3);
================================================
FILE: test/stdio/native-inherit-pipe.js
================================================
import {readFile, rm} from 'node:fs/promises';
import test from 'ava';
import tempfile from 'tempfile';
import {execa} from '../../index.js';
import {getStdio, fullStdio} from '../helpers/stdio.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
import {nestedSubprocess} from '../helpers/nested.js';
setFixtureDirectory();
const testInheritStdin = async (t, stdioOption, isSync) => {
const {stdout} = await execa('nested-multiple-stdin.js', [JSON.stringify(stdioOption), `${isSync}`], {input: foobarString});
t.is(stdout, `${foobarString}${foobarString}`);
};
test('stdin can be ["inherit", "pipe"]', testInheritStdin, ['inherit', 'pipe'], false);
test('stdin can be [0, "pipe"]', testInheritStdin, [0, 'pipe'], false);
test('stdin can be [process.stdin, "pipe"]', testInheritStdin, ['stdin', 'pipe'], false);
test.serial('stdin can be ["inherit", "pipe"], sync', testInheritStdin, ['inherit', 'pipe'], true);
test.serial('stdin can be [0, "pipe"], sync', testInheritStdin, [0, 'pipe'], true);
test.serial('stdin can be [process.stdin, "pipe"], sync', testInheritStdin, ['stdin', 'pipe'], true);
// eslint-disable-next-line max-params
const testInheritStdioOutput = async (t, fdNumber, outerFdNumber, stdioOption, isSync, encoding) => {
const {stdio} = await execa('nested-multiple-stdio-output.js', [JSON.stringify(stdioOption), `${fdNumber}`, `${outerFdNumber}`, `${isSync}`, encoding], fullStdio);
t.is(stdio[fdNumber], foobarString);
t.is(stdio[outerFdNumber], `nested ${foobarString}`);
};
test('stdout can be ["inherit", "pipe"]', testInheritStdioOutput, 1, 2, ['inherit', 'pipe'], false, 'utf8');
test('stdout can be [1, "pipe"]', testInheritStdioOutput, 1, 2, [1, 'pipe'], false, 'utf8');
test('stdout can be [process.stdout, "pipe"]', testInheritStdioOutput, 1, 2, ['stdout', 'pipe'], false, 'utf8');
test('stderr can be ["inherit", "pipe"]', testInheritStdioOutput, 2, 1, ['inherit', 'pipe'], false, 'utf8');
test('stderr can be [2, "pipe"]', testInheritStdioOutput, 2, 1, [2, 'pipe'], false, 'utf8');
test('stderr can be [process.stderr, "pipe"]', testInheritStdioOutput, 2, 1, ['stderr', 'pipe'], false, 'utf8');
test('stdout can be ["inherit", "pipe"], encoding "buffer"', testInheritStdioOutput, 1, 2, ['inherit', 'pipe'], false, 'buffer');
test('stdout can be [1, "pipe"], encoding "buffer"', testInheritStdioOutput, 1, 2, [1, 'pipe'], false, 'buffer');
test('stdout can be [process.stdout, "pipe"], encoding "buffer"', testInheritStdioOutput, 1, 2, ['stdout', 'pipe'], false, 'buffer');
test('stderr can be ["inherit", "pipe"], encoding "buffer"', testInheritStdioOutput, 2, 1, ['inherit', 'pipe'], false, 'buffer');
test('stderr can be [2, "pipe"], encoding "buffer"', testInheritStdioOutput, 2, 1, [2, 'pipe'], false, 'buffer');
test('stderr can be [process.stderr, "pipe"], encoding "buffer"', testInheritStdioOutput, 2, 1, ['stderr', 'pipe'], false, 'buffer');
test('stdout can be ["inherit", "pipe"], sync', testInheritStdioOutput, 1, 2, ['inherit', 'pipe'], true, 'utf8');
test('stdout can be [1, "pipe"], sync', testInheritStdioOutput, 1, 2, [1, 'pipe'], true, 'utf8');
test('stdout can be [process.stdout, "pipe"], sync', testInheritStdioOutput, 1, 2, ['stdout', 'pipe'], true, 'utf8');
test('stderr can be ["inherit", "pipe"], sync', testInheritStdioOutput, 2, 1, ['inherit', 'pipe'], true, 'utf8');
test('stderr can be [2, "pipe"], sync', testInheritStdioOutput, 2, 1, [2, 'pipe'], true, 'utf8');
test('stderr can be [process.stderr, "pipe"], sync', testInheritStdioOutput, 2, 1, ['stderr', 'pipe'], true, 'utf8');
test('stdio[*] output can be ["inherit", "pipe"], sync', testInheritStdioOutput, 3, 1, ['inherit', 'pipe'], true, 'utf8');
test('stdio[*] output can be [3, "pipe"], sync', testInheritStdioOutput, 3, 1, [3, 'pipe'], true, 'utf8');
test('stdout can be ["inherit", "pipe"], encoding "buffer", sync', testInheritStdioOutput, 1, 2, ['inherit', 'pipe'], true, 'buffer');
test('stdout can be [1, "pipe"], encoding "buffer", sync', testInheritStdioOutput, 1, 2, [1, 'pipe'], true, 'buffer');
test('stdout can be [process.stdout, "pipe"], encoding "buffer", sync', testInheritStdioOutput, 1, 2, ['stdout', 'pipe'], true, 'buffer');
test('stderr can be ["inherit", "pipe"], encoding "buffer", sync', testInheritStdioOutput, 2, 1, ['inherit', 'pipe'], true, 'buffer');
test('stderr can be [2, "pipe"], encoding "buffer", sync', testInheritStdioOutput, 2, 1, [2, 'pipe'], true, 'buffer');
test('stderr can be [process.stderr, "pipe"], encoding "buffer", sync', testInheritStdioOutput, 2, 1, ['stderr', 'pipe'], true, 'buffer');
test('stdio[*] output can be ["inherit", "pipe"], encoding "buffer", sync', testInheritStdioOutput, 3, 1, ['inherit', 'pipe'], true, 'buffer');
test('stdio[*] output can be [3, "pipe"], encoding "buffer", sync', testInheritStdioOutput, 3, 1, [3, 'pipe'], true, 'buffer');
const testInheritNoBuffer = async (t, stdioOption, isSync) => {
const filePath = tempfile();
await nestedSubprocess('nested-write.js', [filePath, foobarString], {stdin: stdioOption, buffer: false, isSync}, {input: foobarString});
t.is(await readFile(filePath, 'utf8'), `${foobarString} ${foobarString}`);
await rm(filePath);
};
test('stdin can be ["inherit", "pipe"], buffer: false', testInheritNoBuffer, ['inherit', 'pipe'], false);
test('stdin can be [0, "pipe"], buffer: false', testInheritNoBuffer, [0, 'pipe'], false);
test.serial('stdin can be ["inherit", "pipe"], buffer: false, sync', testInheritNoBuffer, ['inherit', 'pipe'], true);
test.serial('stdin can be [0, "pipe"], buffer: false, sync', testInheritNoBuffer, [0, 'pipe'], true);
test('stdin can use ["inherit", "pipe"] in a TTY', async t => {
const stdioOption = [['inherit', 'pipe'], 'inherit', 'pipe'];
const {stdout} = await execa('nested-sync-tty.js', [JSON.stringify({stdio: stdioOption}), 'false', 'stdin-fd.js', '0'], {input: foobarString});
t.is(stdout, foobarString);
});
const testNoTtyInput = async (t, fdNumber, optionName) => {
const stdioOption = ['pipe', 'inherit', 'pipe'];
stdioOption[fdNumber] = [[''], 'inherit', 'pipe'];
const {message} = await t.throwsAsync(execa('nested-sync-tty.js', [JSON.stringify({stdio: stdioOption}), 'true', 'stdin-fd.js', `${fdNumber}`], fullStdio));
t.true(message.includes(`The \`${optionName}: 'inherit'\` option is invalid: it cannot be a TTY`));
};
test('stdin cannot use ["inherit", "pipe"] in a TTY, sync', testNoTtyInput, 0, 'stdin');
test('stdio[*] input cannot use ["inherit", "pipe"] in a TTY, sync', testNoTtyInput, 3, 'stdio[3]');
const testTtyOutput = async (t, fdNumber, isSync) => {
const {stdio} = await execa('nested-sync-tty.js', [JSON.stringify(getStdio(fdNumber, ['inherit', 'pipe'])), `${isSync}`, 'noop-fd.js', `${fdNumber}`, foobarString], fullStdio);
t.is(stdio[fdNumber], foobarString);
};
test('stdout can use ["inherit", "pipe"] in a TTY', testTtyOutput, 1, false);
test('stderr can use ["inherit", "pipe"] in a TTY', testTtyOutput, 2, false);
test('stdout can use ["inherit", "pipe"] in a TTY, sync', testTtyOutput, 1, true);
test('stderr can use ["inherit", "pipe"] in a TTY, sync', testTtyOutput, 2, true);
test('stdio[*] output can use ["inherit", "pipe"] in a TTY, sync', testTtyOutput, 3, true);
================================================
FILE: test/stdio/native-redirect.js
================================================
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
setFixtureDirectory();
// eslint-disable-next-line max-params
const testRedirect = async (t, stdioOption, fdNumber, isInput, isSync) => {
const {fixtureName, ...options} = isInput
? {fixtureName: 'stdin-fd.js', input: foobarString}
: {fixtureName: 'noop-fd.js'};
const {stdio} = await execa('nested-stdio.js', [JSON.stringify(stdioOption), `${fdNumber}`, `${isSync}`, fixtureName, foobarString], options);
const resultFdNumber = isStderrDescriptor(stdioOption) ? 2 : 1;
t.is(stdio[resultFdNumber], foobarString);
};
const isStderrDescriptor = stdioOption => stdioOption === 2
|| stdioOption === 'stderr'
|| (Array.isArray(stdioOption) && isStderrDescriptor(stdioOption[0]));
test.serial('stdio[*] can be 0', testRedirect, 0, 3, true, false);
test.serial('stdio[*] can be [0]', testRedirect, [0], 3, true, false);
test.serial('stdio[*] can be [0, "pipe"]', testRedirect, [0, 'pipe'], 3, true, false);
test.serial('stdio[*] can be process.stdin', testRedirect, 'stdin', 3, true, false);
test.serial('stdio[*] can be [process.stdin]', testRedirect, ['stdin'], 3, true, false);
test.serial('stdio[*] can be [process.stdin, "pipe"]', testRedirect, ['stdin', 'pipe'], 3, true, false);
test('stdout can be 2', testRedirect, 2, 1, false, false);
test('stdout can be [2]', testRedirect, [2], 1, false, false);
test('stdout can be [2, "pipe"]', testRedirect, [2, 'pipe'], 1, false, false);
test('stdout can be process.stderr', testRedirect, 'stderr', 1, false, false);
test('stdout can be [process.stderr]', testRedirect, ['stderr'], 1, false, false);
test('stdout can be [process.stderr, "pipe"]', testRedirect, ['stderr', 'pipe'], 1, false, false);
test('stderr can be 1', testRedirect, 1, 2, false, false);
test('stderr can be [1]', testRedirect, [1], 2, false, false);
test('stderr can be [1, "pipe"]', testRedirect, [1, 'pipe'], 2, false, false);
test('stderr can be process.stdout', testRedirect, 'stdout', 2, false, false);
test('stderr can be [process.stdout]', testRedirect, ['stdout'], 2, false, false);
test('stderr can be [process.stdout, "pipe"]', testRedirect, ['stdout', 'pipe'], 2, false, false);
test('stdio[*] can be 1', testRedirect, 1, 3, false, false);
test('stdio[*] can be [1]', testRedirect, [1], 3, false, false);
test('stdio[*] can be [1, "pipe"]', testRedirect, [1, 'pipe'], 3, false, false);
test('stdio[*] can be 2', testRedirect, 2, 3, false, false);
test('stdio[*] can be [2]', testRedirect, [2], 3, false, false);
test('stdio[*] can be [2, "pipe"]', testRedirect, [2, 'pipe'], 3, false, false);
test('stdio[*] can be process.stdout', testRedirect, 'stdout', 3, false, false);
test('stdio[*] can be [process.stdout]', testRedirect, ['stdout'], 3, false, false);
test('stdio[*] can be [process.stdout, "pipe"]', testRedirect, ['stdout', 'pipe'], 3, false, false);
test('stdio[*] can be process.stderr', testRedirect, 'stderr', 3, false, false);
test('stdio[*] can be [process.stderr]', testRedirect, ['stderr'], 3, false, false);
test('stdio[*] can be [process.stderr, "pipe"]', testRedirect, ['stderr', 'pipe'], 3, false, false);
test('stdout can be 2, sync', testRedirect, 2, 1, false, true);
test('stdout can be [2], sync', testRedirect, [2], 1, false, true);
test('stdout can be [2, "pipe"], sync', testRedirect, [2, 'pipe'], 1, false, true);
test('stdout can be process.stderr, sync', testRedirect, 'stderr', 1, false, true);
test('stdout can be [process.stderr], sync', testRedirect, ['stderr'], 1, false, true);
test('stdout can be [process.stderr, "pipe"], sync', testRedirect, ['stderr', 'pipe'], 1, false, true);
test('stderr can be 1, sync', testRedirect, 1, 2, false, true);
test('stderr can be [1], sync', testRedirect, [1], 2, false, true);
test('stderr can be [1, "pipe"], sync', testRedirect, [1, 'pipe'], 2, false, true);
test('stderr can be process.stdout, sync', testRedirect, 'stdout', 2, false, true);
test('stderr can be [process.stdout], sync', testRedirect, ['stdout'], 2, false, true);
test('stderr can be [process.stdout, "pipe"], sync', testRedirect, ['stdout', 'pipe'], 2, false, true);
test('stdio[*] can be 1, sync', testRedirect, 1, 3, false, true);
test('stdio[*] can be [1], sync', testRedirect, [1], 3, false, true);
test('stdio[*] can be [1, "pipe"], sync', testRedirect, [1, 'pipe'], 3, false, true);
test('stdio[*] can be 2, sync', testRedirect, 2, 3, false, true);
test('stdio[*] can be [2], sync', testRedirect, [2], 3, false, true);
test('stdio[*] can be [2, "pipe"], sync', testRedirect, [2, 'pipe'], 3, false, true);
test('stdio[*] can be process.stdout, sync', testRedirect, 'stdout', 3, false, true);
test('stdio[*] can be [process.stdout], sync', testRedirect, ['stdout'], 3, false, true);
test('stdio[*] can be [process.stdout, "pipe"], sync', testRedirect, ['stdout', 'pipe'], 3, false, true);
test('stdio[*] can be process.stderr, sync', testRedirect, 'stderr', 3, false, true);
test('stdio[*] can be [process.stderr], sync', testRedirect, ['stderr'], 3, false, true);
test('stdio[*] can be [process.stderr, "pipe"], sync', testRedirect, ['stderr', 'pipe'], 3, false, true);
================================================
FILE: test/stdio/node-stream-custom.js
================================================
import {createReadStream, createWriteStream} from 'node:fs';
import {readFile, writeFile, rm} from 'node:fs/promises';
import {Writable, PassThrough} from 'node:stream';
import {text} from 'node:stream/consumers';
import {setImmediate} from 'node:timers/promises';
import {callbackify} from 'node:util';
import test from 'ava';
import tempfile from 'tempfile';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {getStdio} from '../helpers/stdio.js';
import {foobarString} from '../helpers/input.js';
import {noopReadable, noopWritable} from '../helpers/stream.js';
import {getEarlyErrorSubprocess, expectedEarlyError} from '../helpers/early-error.js';
setFixtureDirectory();
const testLazyFileReadable = async (t, fdNumber) => {
const filePath = tempfile();
await writeFile(filePath, 'foobar');
const stream = createReadStream(filePath);
const {stdout} = await execa('stdin-fd.js', [`${fdNumber}`], getStdio(fdNumber, [stream, 'pipe']));
t.is(stdout, 'foobar');
await rm(filePath);
};
test('stdin can be [Readable, "pipe"] without a file descriptor', testLazyFileReadable, 0);
test('stdio[*] can be [Readable, "pipe"] without a file descriptor', testLazyFileReadable, 3);
const testLazyFileReadableSync = (t, fdNumber) => {
t.throws(() => {
execaSync('stdin-fd.js', [`${fdNumber}`], getStdio(fdNumber, [noopReadable(), 'pipe']));
}, {message: /cannot both be an array and include a stream/});
};
test('stdin cannot be [Readable, "pipe"] without a file descriptor, sync', testLazyFileReadableSync, 0);
test('stdio[*] cannot be [Readable, "pipe"] without a file descriptor, sync', testLazyFileReadableSync, 3);
const testLazyFileWritable = async (t, fdNumber) => {
const filePath = tempfile();
const stream = createWriteStream(filePath);
await execa('noop-fd.js', [`${fdNumber}`, 'foobar'], getStdio(fdNumber, [stream, 'pipe']));
t.is(await readFile(filePath, 'utf8'), 'foobar');
await rm(filePath);
};
test('stdout can be [Writable, "pipe"] without a file descriptor', testLazyFileWritable, 1);
test('stderr can be [Writable, "pipe"] without a file descriptor', testLazyFileWritable, 2);
test('stdio[*] can be [Writable, "pipe"] without a file descriptor', testLazyFileWritable, 3);
const testLazyFileWritableSync = (t, fdNumber) => {
t.throws(() => {
execaSync('noop-fd.js', [`${fdNumber}`], getStdio(fdNumber, [noopWritable(), 'pipe']));
}, {message: /cannot both be an array and include a stream/});
};
test('stdout cannot be [Writable, "pipe"] without a file descriptor, sync', testLazyFileWritableSync, 1);
test('stderr cannot be [Writable, "pipe"] without a file descriptor, sync', testLazyFileWritableSync, 2);
test('stdio[*] cannot be [Writable, "pipe"] without a file descriptor, sync', testLazyFileWritableSync, 3);
test('Waits for custom streams destroy on subprocess errors', async t => {
let waitedForDestroy = false;
const stream = new Writable({
destroy: callbackify(async error => {
await setImmediate();
waitedForDestroy = true;
return error;
}),
});
const {timedOut} = await t.throwsAsync(execa('forever.js', {stdout: [stream, 'pipe'], timeout: 1}));
t.true(timedOut);
t.true(waitedForDestroy);
});
test('Handles custom streams destroy errors on subprocess success', async t => {
const cause = new Error('test');
const stream = new Writable({
destroy(destroyError, done) {
done(destroyError ?? cause);
},
});
const error = await t.throwsAsync(execa('empty.js', {stdout: [stream, 'pipe']}));
t.is(error.cause, cause);
});
const testStreamEarlyExit = async (t, stream, streamName) => {
const error = await t.throwsAsync(getEarlyErrorSubprocess({[streamName]: [stream, 'pipe']}));
t.like(error, expectedEarlyError);
t.true(stream.destroyed);
};
test('Input streams are canceled on early subprocess exit', testStreamEarlyExit, noopReadable(), 'stdin');
test('Output streams are canceled on early subprocess exit', testStreamEarlyExit, noopWritable(), 'stdout');
const testInputDuplexStream = async (t, fdNumber) => {
const stream = new PassThrough();
stream.end(foobarString);
const {stdout} = await execa('stdin-fd.js', [`${fdNumber}`], getStdio(fdNumber, [stream, new Uint8Array()]));
t.is(stdout, foobarString);
};
test('Can pass Duplex streams to stdin', testInputDuplexStream, 0);
test('Can pass Duplex streams to input stdio[*]', testInputDuplexStream, 3);
const testOutputDuplexStream = async (t, fdNumber) => {
const stream = new PassThrough();
const [output] = await Promise.all([
text(stream),
execa('noop-fd.js', [`${fdNumber}`], getStdio(fdNumber, [stream, 'pipe'])),
]);
t.is(output, foobarString);
};
test('Can pass Duplex streams to stdout', testOutputDuplexStream, 1);
test('Can pass Duplex streams to stderr', testOutputDuplexStream, 2);
test('Can pass Duplex streams to output stdio[*]', testOutputDuplexStream, 3);
const testInputStreamAbort = async (t, fdNumber) => {
const stream = new PassThrough();
stream.destroy();
const subprocess = execa('stdin-fd.js', [`${fdNumber}`], getStdio(fdNumber, [stream, new Uint8Array()]));
await subprocess;
t.true(subprocess.stdio[fdNumber].writableEnded);
};
test('subprocess.stdin is ended when an input stream aborts', testInputStreamAbort, 0);
test('subprocess.stdio[*] is ended when an input stream aborts', testInputStreamAbort, 3);
const testInputStreamError = async (t, fdNumber) => {
const stream = new PassThrough();
const cause = new Error(foobarString);
stream.destroy(cause);
const subprocess = execa('stdin-fd.js', [`${fdNumber}`], getStdio(fdNumber, [stream, new Uint8Array()]));
t.like(await t.throwsAsync(subprocess), {cause});
t.true(subprocess.stdio[fdNumber].writableEnded);
};
test('subprocess.stdin is ended when an input stream errors', testInputStreamError, 0);
test('subprocess.stdio[*] is ended when an input stream errors', testInputStreamError, 3);
const testOutputStreamError = async (t, fdNumber) => {
const stream = new PassThrough();
const cause = new Error(foobarString);
stream.destroy(cause);
const subprocess = execa('noop-fd.js', [`${fdNumber}`], getStdio(fdNumber, [stream, 'pipe']));
t.like(await t.throwsAsync(subprocess), {cause});
t.true(subprocess.stdio[fdNumber].readableAborted);
t.is(subprocess.stdio[fdNumber].errored, null);
};
test('subprocess.stdout is aborted when an output stream errors', testOutputStreamError, 1);
test('subprocess.stderr is aborted when an output stream errors', testOutputStreamError, 2);
test('subprocess.stdio[*] is aborted when an output stream errors', testOutputStreamError, 3);
================================================
FILE: test/stdio/node-stream-native.js
================================================
import {once} from 'node:events';
import {createReadStream, createWriteStream} from 'node:fs';
import {readFile, writeFile, rm} from 'node:fs/promises';
import test from 'ava';
import tempfile from 'tempfile';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {getStdio} from '../helpers/stdio.js';
import {foobarString} from '../helpers/input.js';
import {
noopReadable,
noopWritable,
noopDuplex,
simpleReadable,
} from '../helpers/stream.js';
setFixtureDirectory();
const testNoFileStreamSync = async (t, fdNumber, stream) => {
t.throws(() => {
execaSync('empty.js', getStdio(fdNumber, stream));
}, {code: 'ERR_INVALID_ARG_VALUE'});
};
test('stdin cannot be a Node.js Readable without a file descriptor - sync', testNoFileStreamSync, 0, noopReadable());
test('stdin cannot be a Node.js Duplex without a file descriptor - sync', testNoFileStreamSync, 0, noopDuplex());
test('stdout cannot be a Node.js Writable without a file descriptor - sync', testNoFileStreamSync, 1, noopWritable());
test('stdout cannot be a Node.js Duplex without a file descriptor - sync', testNoFileStreamSync, 1, noopDuplex());
test('stderr cannot be a Node.js Writable without a file descriptor - sync', testNoFileStreamSync, 2, noopWritable());
test('stderr cannot be a Node.js Duplex without a file descriptor - sync', testNoFileStreamSync, 2, noopDuplex());
test('stdio[*] cannot be a Node.js Readable without a file descriptor - sync', testNoFileStreamSync, 3, noopReadable());
test('stdio[*] cannot be a Node.js Writable without a file descriptor - sync', testNoFileStreamSync, 3, noopWritable());
test('stdio[*] cannot be a Node.js Duplex without a file descriptor - sync', testNoFileStreamSync, 3, noopDuplex());
test('input can be a Node.js Readable without a file descriptor', async t => {
const {stdout} = await execa('stdin.js', {input: simpleReadable()});
t.is(stdout, foobarString);
});
test('input cannot be a Node.js Readable without a file descriptor - sync', t => {
t.throws(() => {
execaSync('empty.js', {input: simpleReadable()});
}, {message: 'The `input` option cannot be a Node.js stream with synchronous methods.'});
});
const testNoFileStream = async (t, fdNumber, stream) => {
await t.throwsAsync(execa('empty.js', getStdio(fdNumber, stream)), {code: 'ERR_INVALID_ARG_VALUE'});
};
test('stdin cannot be a Node.js Readable without a file descriptor', testNoFileStream, 0, noopReadable());
test('stdin cannot be a Node.js Duplex without a file descriptor', testNoFileStream, 0, noopDuplex());
test('stdout cannot be a Node.js Writable without a file descriptor', testNoFileStream, 1, noopWritable());
test('stdout cannot be a Node.js Duplex without a file descriptor', testNoFileStream, 1, noopDuplex());
test('stderr cannot be a Node.js Writable without a file descriptor', testNoFileStream, 2, noopWritable());
test('stderr cannot be a Node.js Duplex without a file descriptor', testNoFileStream, 2, noopDuplex());
test('stdio[*] cannot be a Node.js Readable without a file descriptor', testNoFileStream, 3, noopReadable());
test('stdio[*] cannot be a Node.js Writable without a file descriptor', testNoFileStream, 3, noopWritable());
test('stdio[*] cannot be a Node.js Duplex without a file descriptor', testNoFileStream, 3, noopDuplex());
const createFileReadStream = async () => {
const filePath = tempfile();
await writeFile(filePath, 'foobar');
const stream = createReadStream(filePath);
await once(stream, 'open');
return {stream, filePath};
};
const createFileWriteStream = async () => {
const filePath = tempfile();
const stream = createWriteStream(filePath);
await once(stream, 'open');
return {stream, filePath};
};
const assertFileStreamError = async (t, subprocess, stream, filePath) => {
const cause = new Error('test');
stream.destroy(cause);
const error = await t.throwsAsync(subprocess);
t.is(error.cause, cause);
t.is(error.exitCode, 0);
t.is(error.signal, undefined);
await rm(filePath);
};
const testFileReadable = async (t, fdNumber, execaMethod) => {
const {stream, filePath} = await createFileReadStream();
const fdNumberString = fdNumber === 'input' ? '0' : `${fdNumber}`;
const {stdout} = await execaMethod('stdin-fd.js', [fdNumberString], getStdio(fdNumber, stream));
t.is(stdout, 'foobar');
await rm(filePath);
};
test('input can be a Node.js Readable with a file descriptor', testFileReadable, 'input', execa);
test('stdin can be a Node.js Readable with a file descriptor', testFileReadable, 0, execa);
test('stdio[*] can be a Node.js Readable with a file descriptor', testFileReadable, 3, execa);
test('stdin can be a Node.js Readable with a file descriptor - sync', testFileReadable, 0, execaSync);
test('stdio[*] can be a Node.js Readable with a file descriptor - sync', testFileReadable, 3, execaSync);
const testFileReadableError = async (t, fdNumber) => {
const {stream, filePath} = await createFileReadStream();
const fdNumberString = fdNumber === 'input' ? '0' : `${fdNumber}`;
const subprocess = execa('stdin-fd.js', [fdNumberString], getStdio(fdNumber, stream));
await assertFileStreamError(t, subprocess, stream, filePath);
};
test.serial('input handles errors from a Node.js Readable with a file descriptor', testFileReadableError, 'input');
test.serial('stdin handles errors from a Node.js Readable with a file descriptor', testFileReadableError, 0);
test.serial('stdio[*] handles errors from a Node.js Readable with a file descriptor', testFileReadableError, 3);
const testFileReadableOpen = async (t, fdNumber, useSingle, execaMethod) => {
const {stream, filePath} = await createFileReadStream();
t.deepEqual(stream.eventNames(), []);
const stdioOption = useSingle ? stream : [stream, 'pipe'];
await execaMethod('empty.js', getStdio(fdNumber, stdioOption));
t.is(stream.readable, useSingle && fdNumber !== 'input');
t.deepEqual(stream.eventNames(), []);
await rm(filePath);
};
test('input closes a Node.js Readable with a file descriptor', testFileReadableOpen, 'input', true, execa);
test('stdin leaves open a single Node.js Readable with a file descriptor', testFileReadableOpen, 0, true, execa);
test('stdin closes a combined Node.js Readable with a file descriptor', testFileReadableOpen, 0, false, execa);
test('stdio[*] leaves open a single Node.js Readable with a file descriptor', testFileReadableOpen, 3, true, execa);
test('stdin leaves open a single Node.js Readable with a file descriptor - sync', testFileReadableOpen, 0, true, execaSync);
test('stdio[*] leaves open a single Node.js Readable with a file descriptor - sync', testFileReadableOpen, 3, true, execaSync);
const testFileWritable = async (t, fdNumber, execaMethod) => {
const {stream, filePath} = await createFileWriteStream();
await execaMethod('noop-fd.js', [`${fdNumber}`, 'foobar'], getStdio(fdNumber, stream));
t.is(await readFile(filePath, 'utf8'), 'foobar');
await rm(filePath);
};
test('stdout can be a Node.js Writable with a file descriptor', testFileWritable, 1, execa);
test('stderr can be a Node.js Writable with a file descriptor', testFileWritable, 2, execa);
test('stdio[*] can be a Node.js Writable with a file descriptor', testFileWritable, 3, execa);
test('stdout can be a Node.js Writable with a file descriptor - sync', testFileWritable, 1, execaSync);
test('stderr can be a Node.js Writable with a file descriptor - sync', testFileWritable, 2, execaSync);
test('stdio[*] can be a Node.js Writable with a file descriptor - sync', testFileWritable, 3, execaSync);
const testFileWritableError = async (t, fdNumber) => {
const {stream, filePath} = await createFileWriteStream();
const subprocess = execa('noop-fd.js', [`${fdNumber}`, foobarString], getStdio(fdNumber, stream));
await assertFileStreamError(t, subprocess, stream, filePath);
};
test.serial('stdout handles errors from a Node.js Writable with a file descriptor', testFileWritableError, 1);
test.serial('stderr handles errors from a Node.js Writable with a file descriptor', testFileWritableError, 2);
test.serial('stdio[*] handles errors from a Node.js Writable with a file descriptor', testFileWritableError, 3);
const testFileWritableOpen = async (t, fdNumber, useSingle, execaMethod) => {
const {stream, filePath} = await createFileWriteStream();
t.deepEqual(stream.eventNames(), []);
const stdioOption = useSingle ? stream : [stream, 'pipe'];
await execaMethod('empty.js', getStdio(fdNumber, stdioOption));
t.is(stream.writable, useSingle);
t.deepEqual(stream.eventNames(), []);
await rm(filePath);
};
test('stdout leaves open a single Node.js Writable with a file descriptor', testFileWritableOpen, 1, true, execa);
test('stdout closes a combined Node.js Writable with a file descriptor', testFileWritableOpen, 1, false, execa);
test('stderr leaves open a single Node.js Writable with a file descriptor', testFileWritableOpen, 2, true, execa);
test('stderr closes a combined Node.js Writable with a file descriptor', testFileWritableOpen, 2, false, execa);
test('stdio[*] leaves open a single Node.js Writable with a file descriptor', testFileWritableOpen, 3, true, execa);
test('stdio[*] closes a combined Node.js Writable with a file descriptor', testFileWritableOpen, 3, false, execa);
test('stdout leaves open a single Node.js Writable with a file descriptor - sync', testFileWritableOpen, 1, true, execaSync);
test('stderr leaves open a single Node.js Writable with a file descriptor - sync', testFileWritableOpen, 2, true, execaSync);
test('stdio[*] leaves open a single Node.js Writable with a file descriptor - sync', testFileWritableOpen, 3, true, execaSync);
================================================
FILE: test/stdio/stdio-option.js
================================================
import {inspect} from 'node:util';
import test from 'ava';
import {normalizeStdioOption} from '../../lib/stdio/stdio-option.js';
const stdioMacro = (t, input, expected) => {
if (expected instanceof Error) {
t.throws(() => {
normalizeStdioOption(input);
}, {message: expected.message});
return;
}
t.deepEqual(normalizeStdioOption(input), expected);
};
stdioMacro.title = (_, input) => `execa() ${(inspect(input))}`;
test(stdioMacro, {stdio: 'inherit'}, ['inherit', 'inherit', 'inherit']);
test(stdioMacro, {stdio: 'pipe'}, ['pipe', 'pipe', 'pipe']);
test(stdioMacro, {stdio: 'ignore'}, ['ignore', 'ignore', 'ignore']);
test(stdioMacro, {}, ['pipe', 'pipe', 'pipe']);
test(stdioMacro, {stdio: []}, ['pipe', 'pipe', 'pipe']);
test(stdioMacro, {stdio: [0]}, [0, 'pipe', 'pipe']);
test(stdioMacro, {stdio: [0, 1]}, [0, 1, 'pipe']);
test(stdioMacro, {stdio: [0, 1, 2]}, [0, 1, 2]);
test(stdioMacro, {stdio: [0, 1, 2, 3]}, [0, 1, 2, 3]);
test(stdioMacro, {stdio: [undefined, 1, 2]}, ['pipe', 1, 2]);
test(stdioMacro, {stdio: [null, 1, 2]}, ['pipe', 1, 2]);
test(stdioMacro, {stdio: [0, undefined, 2]}, [0, 'pipe', 2]);
test(stdioMacro, {stdio: [0, null, 2]}, [0, 'pipe', 2]);
test(stdioMacro, {stdio: [0, 1, undefined]}, [0, 1, 'pipe']);
test(stdioMacro, {stdio: [0, 1, null]}, [0, 1, 'pipe']);
test(stdioMacro, {stdio: [0, 1, 2, undefined]}, [0, 1, 2, 'ignore']);
test(stdioMacro, {stdio: [0, 1, 2, null]}, [0, 1, 2, 'ignore']);
test(stdioMacro, {stdin: 'pipe'}, ['pipe', 'pipe', 'pipe']);
test(stdioMacro, {stdout: 'ignore'}, ['pipe', 'ignore', 'pipe']);
test(stdioMacro, {stderr: 'inherit'}, ['pipe', 'pipe', 'inherit']);
test(stdioMacro, {stdin: 'pipe', stdout: 'ignore', stderr: 'inherit'}, ['pipe', 'ignore', 'inherit']);
test(stdioMacro, {stdin: 'pipe', stdout: 'ignore'}, ['pipe', 'ignore', 'pipe']);
test(stdioMacro, {stdin: 'pipe', stderr: 'inherit'}, ['pipe', 'pipe', 'inherit']);
test(stdioMacro, {stdout: 'ignore', stderr: 'inherit'}, ['pipe', 'ignore', 'inherit']);
test(stdioMacro, {stdin: 0, stdout: 1, stderr: 2}, [0, 1, 2]);
test(stdioMacro, {stdin: 0, stdout: 1}, [0, 1, 'pipe']);
test(stdioMacro, {stdin: 0, stderr: 2}, [0, 'pipe', 2]);
test(stdioMacro, {stdout: 1, stderr: 2}, ['pipe', 1, 2]);
test(stdioMacro, {stdio: {foo: 'bar'}}, new TypeError('Expected `stdio` to be of type `string` or `Array`, got `object`'));
test(stdioMacro, {stdin: 'inherit', stdio: 'pipe'}, new Error('It\'s not possible to provide `stdio` in combination with one of `stdin`, `stdout`, `stderr`'));
test(stdioMacro, {stdin: 'inherit', stdio: ['pipe']}, new Error('It\'s not possible to provide `stdio` in combination with one of `stdin`, `stdout`, `stderr`'));
test(stdioMacro, {stdin: 'inherit', stdio: [undefined, 'pipe']}, new Error('It\'s not possible to provide `stdio` in combination with one of `stdin`, `stdout`, `stderr`'));
test(stdioMacro, {stdin: 0, stdio: 'pipe'}, new Error('It\'s not possible to provide `stdio` in combination with one of `stdin`, `stdout`, `stderr`'));
================================================
FILE: test/stdio/type-invalid.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {getStdio} from '../helpers/stdio.js';
import {noopGenerator} from '../helpers/generator.js';
import {generatorsMap} from '../helpers/map.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
setFixtureDirectory();
const testInvalidGenerator = (t, fdNumber, stdioOption, execaMethod) => {
t.throws(() => {
execaMethod('empty.js', getStdio(fdNumber, {...noopGenerator(), ...stdioOption}));
}, {message: 'final' in stdioOption ? /must be a generator/ : /must be a generator, a Duplex stream or a web TransformStream/});
};
test('Cannot use invalid "transform" with stdin', testInvalidGenerator, 0, {transform: true}, execa);
test('Cannot use invalid "transform" with stdout', testInvalidGenerator, 1, {transform: true}, execa);
test('Cannot use invalid "transform" with stderr', testInvalidGenerator, 2, {transform: true}, execa);
test('Cannot use invalid "transform" with stdio[*]', testInvalidGenerator, 3, {transform: true}, execa);
test('Cannot use invalid "final" with stdin', testInvalidGenerator, 0, {final: true}, execa);
test('Cannot use invalid "final" with stdout', testInvalidGenerator, 1, {final: true}, execa);
test('Cannot use invalid "final" with stderr', testInvalidGenerator, 2, {final: true}, execa);
test('Cannot use invalid "final" with stdio[*]', testInvalidGenerator, 3, {final: true}, execa);
test('Cannot use invalid "transform" with stdin, sync', testInvalidGenerator, 0, {transform: true}, execaSync);
test('Cannot use invalid "transform" with stdout, sync', testInvalidGenerator, 1, {transform: true}, execaSync);
test('Cannot use invalid "transform" with stderr, sync', testInvalidGenerator, 2, {transform: true}, execaSync);
test('Cannot use invalid "transform" with stdio[*], sync', testInvalidGenerator, 3, {transform: true}, execaSync);
test('Cannot use invalid "final" with stdin, sync', testInvalidGenerator, 0, {final: true}, execaSync);
test('Cannot use invalid "final" with stdout, sync', testInvalidGenerator, 1, {final: true}, execaSync);
test('Cannot use invalid "final" with stderr, sync', testInvalidGenerator, 2, {final: true}, execaSync);
test('Cannot use invalid "final" with stdio[*], sync', testInvalidGenerator, 3, {final: true}, execaSync);
// eslint-disable-next-line max-params
const testInvalidBinary = (t, fdNumber, optionName, type, execaMethod) => {
t.throws(() => {
execaMethod('empty.js', getStdio(fdNumber, {...generatorsMap[type].uppercase(), [optionName]: 'true'}));
}, {message: /a boolean/});
};
test('Cannot use invalid "binary" with stdin', testInvalidBinary, 0, 'binary', 'generator', execa);
test('Cannot use invalid "binary" with stdout', testInvalidBinary, 1, 'binary', 'generator', execa);
test('Cannot use invalid "binary" with stderr', testInvalidBinary, 2, 'binary', 'generator', execa);
test('Cannot use invalid "binary" with stdio[*]', testInvalidBinary, 3, 'binary', 'generator', execa);
test('Cannot use invalid "objectMode" with stdin, generators', testInvalidBinary, 0, 'objectMode', 'generator', execa);
test('Cannot use invalid "objectMode" with stdout, generators', testInvalidBinary, 1, 'objectMode', 'generator', execa);
test('Cannot use invalid "objectMode" with stderr, generators', testInvalidBinary, 2, 'objectMode', 'generator', execa);
test('Cannot use invalid "objectMode" with stdio[*], generators', testInvalidBinary, 3, 'objectMode', 'generator', execa);
test('Cannot use invalid "binary" with stdin, sync', testInvalidBinary, 0, 'binary', 'generator', execaSync);
test('Cannot use invalid "binary" with stdout, sync', testInvalidBinary, 1, 'binary', 'generator', execaSync);
test('Cannot use invalid "binary" with stderr, sync', testInvalidBinary, 2, 'binary', 'generator', execaSync);
test('Cannot use invalid "binary" with stdio[*], sync', testInvalidBinary, 3, 'binary', 'generator', execaSync);
test('Cannot use invalid "objectMode" with stdin, generators, sync', testInvalidBinary, 0, 'objectMode', 'generator', execaSync);
test('Cannot use invalid "objectMode" with stdout, generators, sync', testInvalidBinary, 1, 'objectMode', 'generator', execaSync);
test('Cannot use invalid "objectMode" with stderr, generators, sync', testInvalidBinary, 2, 'objectMode', 'generator', execaSync);
test('Cannot use invalid "objectMode" with stdio[*], generators, sync', testInvalidBinary, 3, 'objectMode', 'generator', execaSync);
test('Cannot use invalid "objectMode" with stdin, duplexes', testInvalidBinary, 0, 'objectMode', 'duplex', execa);
test('Cannot use invalid "objectMode" with stdout, duplexes', testInvalidBinary, 1, 'objectMode', 'duplex', execa);
test('Cannot use invalid "objectMode" with stderr, duplexes', testInvalidBinary, 2, 'objectMode', 'duplex', execa);
test('Cannot use invalid "objectMode" with stdio[*], duplexes', testInvalidBinary, 3, 'objectMode', 'duplex', execa);
test('Cannot use invalid "objectMode" with stdin, webTransforms', testInvalidBinary, 0, 'objectMode', 'webTransform', execa);
test('Cannot use invalid "objectMode" with stdout, webTransforms', testInvalidBinary, 1, 'objectMode', 'webTransform', execa);
test('Cannot use invalid "objectMode" with stderr, webTransforms', testInvalidBinary, 2, 'objectMode', 'webTransform', execa);
test('Cannot use invalid "objectMode" with stdio[*], webTransforms', testInvalidBinary, 3, 'objectMode', 'webTransform', execa);
================================================
FILE: test/stdio/type-undefined.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {getStdio} from '../helpers/stdio.js';
import {uppercaseGenerator} from '../helpers/generator.js';
import {uppercaseBufferDuplex} from '../helpers/duplex.js';
import {uppercaseBufferWebTransform} from '../helpers/web-transform.js';
import {generatorsMap} from '../helpers/map.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
setFixtureDirectory();
// eslint-disable-next-line max-params
const testUndefinedOption = (t, fdNumber, optionName, type, optionValue) => {
t.throws(() => {
execa('empty.js', getStdio(fdNumber, {...generatorsMap[type].uppercase(), [optionName]: optionValue}));
}, {message: /can only be defined when using a generator/});
};
test('Cannot use "binary" with duplexes and stdin', testUndefinedOption, 0, 'binary', 'duplex', true);
test('Cannot use "binary" with duplexes and stdout', testUndefinedOption, 1, 'binary', 'duplex', true);
test('Cannot use "binary" with duplexes and stderr', testUndefinedOption, 2, 'binary', 'duplex', true);
test('Cannot use "binary" with duplexes and stdio[*]', testUndefinedOption, 3, 'binary', 'duplex', true);
test('Cannot use "final" with duplexes and stdin', testUndefinedOption, 0, 'final', 'duplex', uppercaseBufferDuplex().transform);
test('Cannot use "final" with duplexes and stdout', testUndefinedOption, 1, 'final', 'duplex', uppercaseBufferDuplex().transform);
test('Cannot use "final" with duplexes and stderr', testUndefinedOption, 2, 'final', 'duplex', uppercaseBufferDuplex().transform);
test('Cannot use "final" with duplexes and stdio[*]', testUndefinedOption, 3, 'final', 'duplex', uppercaseBufferDuplex().transform);
test('Cannot use "binary" with webTransforms and stdin', testUndefinedOption, 0, 'binary', 'webTransform', true);
test('Cannot use "binary" with webTransforms and stdout', testUndefinedOption, 1, 'binary', 'webTransform', true);
test('Cannot use "binary" with webTransforms and stderr', testUndefinedOption, 2, 'binary', 'webTransform', true);
test('Cannot use "binary" with webTransforms and stdio[*]', testUndefinedOption, 3, 'binary', 'webTransform', true);
test('Cannot use "final" with webTransforms and stdin', testUndefinedOption, 0, 'final', 'webTransform', uppercaseBufferWebTransform().transform);
test('Cannot use "final" with webTransforms and stdout', testUndefinedOption, 1, 'final', 'webTransform', uppercaseBufferWebTransform().transform);
test('Cannot use "final" with webTransforms and stderr', testUndefinedOption, 2, 'final', 'webTransform', uppercaseBufferWebTransform().transform);
test('Cannot use "final" with webTransforms and stdio[*]', testUndefinedOption, 3, 'final', 'webTransform', uppercaseBufferWebTransform().transform);
const testUndefinedFinal = (t, fdNumber, type, useTransform) => {
t.throws(() => {
execa('empty.js', getStdio(fdNumber, {
transform: useTransform ? uppercaseGenerator().transform : undefined,
final: generatorsMap[type].uppercase().transform,
}));
}, {message: type === 'duplex' ? /must not be a Duplex/ : /must not be a web TransformStream/});
};
test('Cannot use "final" with duplexes and stdin, without transform', testUndefinedFinal, 0, 'duplex', false);
test('Cannot use "final" with duplexes and stdout, without transform', testUndefinedFinal, 1, 'duplex', false);
test('Cannot use "final" with duplexes and stderr, without transform', testUndefinedFinal, 2, 'duplex', false);
test('Cannot use "final" with duplexes and stdio[*], without transform', testUndefinedFinal, 3, 'duplex', false);
test('Cannot use "final" with duplexes and stdin, with transform', testUndefinedFinal, 0, 'duplex', true);
test('Cannot use "final" with duplexes and stdout, with transform', testUndefinedFinal, 1, 'duplex', true);
test('Cannot use "final" with duplexes and stderr, with transform', testUndefinedFinal, 2, 'duplex', true);
test('Cannot use "final" with duplexes and stdio[*], with transform', testUndefinedFinal, 3, 'duplex', true);
test('Cannot use "final" with webTransforms and stdin, without transform', testUndefinedFinal, 0, 'webTransform', false);
test('Cannot use "final" with webTransforms and stdout, without transform', testUndefinedFinal, 1, 'webTransform', false);
test('Cannot use "final" with webTransforms and stderr, without transform', testUndefinedFinal, 2, 'webTransform', false);
test('Cannot use "final" with webTransforms and stdio[*], without transform', testUndefinedFinal, 3, 'webTransform', false);
test('Cannot use "final" with webTransforms and stdin, with transform', testUndefinedFinal, 0, 'webTransform', true);
test('Cannot use "final" with webTransforms and stdout, with transform', testUndefinedFinal, 1, 'webTransform', true);
test('Cannot use "final" with webTransforms and stderr, with transform', testUndefinedFinal, 2, 'webTransform', true);
test('Cannot use "final" with webTransforms and stdio[*], with transform', testUndefinedFinal, 3, 'webTransform', true);
const testSyncMethodsDuplex = (t, fdNumber, type) => {
t.throws(() => {
execaSync('empty.js', getStdio(fdNumber, generatorsMap[type].uppercase()));
}, {message: type === 'duplex' ? /cannot be a Duplex stream/ : /cannot be a web TransformStream/});
};
test('Cannot use duplexes with sync methods and stdin', testSyncMethodsDuplex, 0, 'duplex');
test('Cannot use duplexes with sync methods and stdout', testSyncMethodsDuplex, 1, 'duplex');
test('Cannot use duplexes with sync methods and stderr', testSyncMethodsDuplex, 2, 'duplex');
test('Cannot use duplexes with sync methods and stdio[*]', testSyncMethodsDuplex, 3, 'duplex');
test('Cannot use webTransforms with sync methods and stdin', testSyncMethodsDuplex, 0, 'webTransform');
test('Cannot use webTransforms with sync methods and stdout', testSyncMethodsDuplex, 1, 'webTransform');
test('Cannot use webTransforms with sync methods and stderr', testSyncMethodsDuplex, 2, 'webTransform');
test('Cannot use webTransforms with sync methods and stdio[*]', testSyncMethodsDuplex, 3, 'webTransform');
================================================
FILE: test/stdio/typed-array.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {getStdio} from '../helpers/stdio.js';
import {foobarUint8Array, foobarBuffer, foobarString} from '../helpers/input.js';
setFixtureDirectory();
const testUint8Array = async (t, fdNumber, stdioOption, execaMethod) => {
const {stdout} = await execaMethod('stdin-fd.js', [`${fdNumber}`], getStdio(fdNumber, stdioOption));
t.is(stdout, foobarString);
};
test('stdin option can be a Uint8Array', testUint8Array, 0, foobarUint8Array, execa);
test('stdio[*] option can be a Uint8Array', testUint8Array, 3, foobarUint8Array, execa);
test('stdin option can be a Uint8Array - sync', testUint8Array, 0, foobarUint8Array, execaSync);
test('stdin option can be a Buffer', testUint8Array, 0, foobarBuffer, execa);
test('stdio[*] option can be a Buffer', testUint8Array, 3, foobarBuffer, execa);
test('stdin option can be a Buffer - sync', testUint8Array, 0, foobarBuffer, execaSync);
const testNoUint8ArrayOutput = (t, fdNumber, stdioOption, execaMethod) => {
t.throws(() => {
execaMethod('empty.js', getStdio(fdNumber, stdioOption));
}, {message: /cannot be a Uint8Array/});
};
test('stdout option cannot be a Uint8Array', testNoUint8ArrayOutput, 1, foobarUint8Array, execa);
test('stderr option cannot be a Uint8Array', testNoUint8ArrayOutput, 2, foobarUint8Array, execa);
test('stdout option cannot be a Uint8Array - sync', testNoUint8ArrayOutput, 1, foobarUint8Array, execaSync);
test('stderr option cannot be a Uint8Array - sync', testNoUint8ArrayOutput, 2, foobarUint8Array, execaSync);
test('stdout option cannot be a Buffer', testNoUint8ArrayOutput, 1, foobarBuffer, execa);
test('stderr option cannot be a Buffer', testNoUint8ArrayOutput, 2, foobarBuffer, execa);
test('stdout option cannot be a Buffer - sync', testNoUint8ArrayOutput, 1, foobarBuffer, execaSync);
test('stderr option cannot be a Buffer - sync', testNoUint8ArrayOutput, 2, foobarBuffer, execaSync);
================================================
FILE: test/stdio/web-stream.js
================================================
import {Readable} from 'node:stream';
import {setImmediate} from 'node:timers/promises';
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {getStdio} from '../helpers/stdio.js';
setFixtureDirectory();
const testReadableStream = async (t, fdNumber) => {
const readableStream = Readable.toWeb(Readable.from('foobar'));
const {stdout} = await execa('stdin-fd.js', [`${fdNumber}`], getStdio(fdNumber, readableStream));
t.is(stdout, 'foobar');
};
test('stdin can be a ReadableStream', testReadableStream, 0);
test('stdio[*] can be a ReadableStream', testReadableStream, 3);
const testWritableStream = async (t, fdNumber) => {
const result = [];
const writableStream = new WritableStream({
write(chunk) {
result.push(chunk);
},
});
await execa('noop-fd.js', [`${fdNumber}`, 'foobar'], getStdio(fdNumber, writableStream));
t.is(result.join(''), 'foobar');
};
test('stdout can be a WritableStream', testWritableStream, 1);
test('stderr can be a WritableStream', testWritableStream, 2);
test('stdio[*] can be a WritableStream', testWritableStream, 3);
const testWebStreamSync = (t, StreamClass, fdNumber, optionName) => {
t.throws(() => {
execaSync('empty.js', getStdio(fdNumber, new StreamClass()));
}, {message: `The \`${optionName}\` option cannot be a web stream with synchronous methods.`});
};
test('stdin cannot be a ReadableStream - sync', testWebStreamSync, ReadableStream, 0, 'stdin');
test('stdio[*] cannot be a ReadableStream - sync', testWebStreamSync, ReadableStream, 3, 'stdio[3]');
test('stdout cannot be a WritableStream - sync', testWebStreamSync, WritableStream, 1, 'stdout');
test('stderr cannot be a WritableStream - sync', testWebStreamSync, WritableStream, 2, 'stderr');
test('stdio[*] cannot be a WritableStream - sync', testWebStreamSync, WritableStream, 3, 'stdio[3]');
const testLongWritableStream = async (t, fdNumber) => {
let result = false;
const writableStream = new WritableStream({
async close() {
await setImmediate();
result = true;
},
});
await execa('empty.js', getStdio(fdNumber, writableStream));
t.true(result);
};
test('stdout waits for WritableStream completion', testLongWritableStream, 1);
test('stderr waits for WritableStream completion', testLongWritableStream, 2);
test('stdio[*] waits for WritableStream completion', testLongWritableStream, 3);
const testWritableStreamError = async (t, fdNumber) => {
const cause = new Error('foobar');
const writableStream = new WritableStream({
start(controller) {
controller.error(cause);
},
});
const error = await t.throwsAsync(execa('noop.js', getStdio(fdNumber, writableStream)));
t.is(error.cause, cause);
};
test('stdout option handles errors in WritableStream', testWritableStreamError, 1);
test('stderr option handles errors in WritableStream', testWritableStreamError, 2);
test('stdio[*] option handles errors in WritableStream', testWritableStreamError, 3);
const testReadableStreamError = async (t, fdNumber) => {
const cause = new Error('foobar');
const readableStream = new ReadableStream({
start(controller) {
controller.error(cause);
},
});
const error = await t.throwsAsync(execa('stdin-fd.js', [`${fdNumber}`], getStdio(fdNumber, readableStream)));
t.is(error.cause, cause);
};
test('stdin option handles errors in ReadableStream', testReadableStreamError, 0);
test('stdio[*] option handles errors in ReadableStream', testReadableStreamError, 3);
test('ReadableStream with stdin is canceled on subprocess exit', async t => {
let readableStream;
const promise = new Promise(resolve => {
readableStream = new ReadableStream({cancel: resolve});
});
await t.throwsAsync(execa('stdin.js', {stdin: readableStream, timeout: 1}), {message: /timed out/});
await promise;
});
================================================
FILE: test/stdio/web-transform.js
================================================
import {promisify} from 'node:util';
import {gunzip} from 'node:zlib';
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString, foobarUtf16Uint8Array, foobarUint8Array} from '../helpers/input.js';
setFixtureDirectory();
test('Can use CompressionStream()', async t => {
const {stdout} = await execa('noop-fd.js', ['1', foobarString], {stdout: new CompressionStream('gzip'), encoding: 'buffer'});
const decompressedStdout = await promisify(gunzip)(stdout);
t.is(decompressedStdout.toString(), foobarString);
});
test('Can use TextDecoderStream()', async t => {
const {stdout} = await execa('stdin.js', {
input: foobarUtf16Uint8Array,
stdout: new TextDecoderStream('utf-16le'),
encoding: 'buffer',
});
t.deepEqual(stdout, foobarUint8Array);
});
================================================
FILE: test/terminate/cancel.js
================================================
import {once, getEventListeners} from 'node:events';
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
setFixtureDirectory();
const testValidCancelSignal = (t, cancelSignal) => {
t.throws(() => {
execa('empty.js', {cancelSignal});
}, {message: /must be an AbortSignal/});
};
test('cancelSignal option cannot be AbortController', testValidCancelSignal, new AbortController());
test('cancelSignal option cannot be {}', testValidCancelSignal, {});
test('cancelSignal option cannot be null', testValidCancelSignal, null);
test('cancelSignal option cannot be a symbol', testValidCancelSignal, Symbol('test'));
test('result.isCanceled is false when abort isn\'t called (success)', async t => {
const {isCanceled, isGracefullyCanceled} = await execa('noop.js');
t.false(isCanceled);
t.false(isGracefullyCanceled);
});
test('result.isCanceled is false when abort isn\'t called (failure)', async t => {
const {isCanceled, isGracefullyCanceled} = await t.throwsAsync(execa('fail.js'));
t.false(isCanceled);
t.false(isGracefullyCanceled);
});
test('result.isCanceled is false when abort isn\'t called in sync mode (success)', t => {
const {isCanceled, isGracefullyCanceled} = execaSync('noop.js');
t.false(isCanceled);
t.false(isGracefullyCanceled);
});
test('result.isCanceled is false when abort isn\'t called in sync mode (failure)', t => {
const {isCanceled, isGracefullyCanceled} = t.throws(() => {
execaSync('fail.js');
});
t.false(isCanceled);
t.false(isGracefullyCanceled);
});
const testCancelSuccess = async (t, options) => {
const abortController = new AbortController();
const subprocess = execa('noop.js', {cancelSignal: abortController.signal, ...options});
abortController.abort();
const {isCanceled, isGracefullyCanceled} = await t.throwsAsync(subprocess);
t.true(isCanceled);
t.false(isGracefullyCanceled);
};
test('error.isCanceled is true when abort is used', testCancelSuccess, {});
test('gracefulCancel can be false with cancelSignal', testCancelSuccess, {gracefulCancel: false});
test('ipc can be false with cancelSignal', testCancelSuccess, {ipc: false});
test('serialization can be "json" with cancelSignal', testCancelSuccess, {ipc: true, serialization: 'json'});
test('error.isCanceled is false when kill method is used', async t => {
const abortController = new AbortController();
const subprocess = execa('noop.js', {cancelSignal: abortController.signal});
subprocess.kill();
const {isCanceled, isGracefullyCanceled} = await t.throwsAsync(subprocess);
t.false(isCanceled);
t.false(isGracefullyCanceled);
});
test('calling abort is considered a signal termination', async t => {
const abortController = new AbortController();
const subprocess = execa('forever.js', {cancelSignal: abortController.signal});
await once(subprocess, 'spawn');
abortController.abort();
const {isCanceled, isGracefullyCanceled, isTerminated, signal} = await t.throwsAsync(subprocess);
t.true(isCanceled);
t.false(isGracefullyCanceled);
t.true(isTerminated);
t.is(signal, 'SIGTERM');
});
test('cancelSignal can already be aborted', async t => {
const cancelSignal = AbortSignal.abort();
const {isCanceled, isGracefullyCanceled, isTerminated, signal} = await t.throwsAsync(execa('forever.js', {cancelSignal}));
t.true(isCanceled);
t.false(isGracefullyCanceled);
t.true(isTerminated);
t.is(signal, 'SIGTERM');
t.deepEqual(getEventListeners(cancelSignal, 'abort'), []);
});
test('calling abort does not emit the "error" event', async t => {
const abortController = new AbortController();
const subprocess = execa('forever.js', {cancelSignal: abortController.signal});
let error;
subprocess.once('error', errorArgument => {
error = errorArgument;
});
abortController.abort();
const {isCanceled, isGracefullyCanceled} = await t.throwsAsync(subprocess);
t.true(isCanceled);
t.false(isGracefullyCanceled);
t.is(error, undefined);
});
test('calling abort cleans up listeners on cancelSignal, called', async t => {
const abortController = new AbortController();
const subprocess = execa('forever.js', {cancelSignal: abortController.signal});
t.is(getEventListeners(abortController.signal, 'abort').length, 1);
abortController.abort();
const {isCanceled, isGracefullyCanceled} = await t.throwsAsync(subprocess);
t.true(isCanceled);
t.false(isGracefullyCanceled);
t.is(getEventListeners(abortController.signal, 'abort').length, 0);
});
test('calling abort cleans up listeners on cancelSignal, not called', async t => {
const abortController = new AbortController();
const subprocess = execa('noop.js', {cancelSignal: abortController.signal});
t.is(getEventListeners(abortController.signal, 'abort').length, 1);
await subprocess;
t.is(getEventListeners(abortController.signal, 'abort').length, 0);
});
test('calling abort cleans up listeners on cancelSignal, already aborted', async t => {
const cancelSignal = AbortSignal.abort();
const subprocess = execa('noop.js', {cancelSignal});
t.is(getEventListeners(cancelSignal, 'abort').length, 0);
const {isCanceled, isGracefullyCanceled} = await t.throwsAsync(subprocess);
t.true(isCanceled);
t.false(isGracefullyCanceled);
t.is(getEventListeners(cancelSignal, 'abort').length, 0);
});
test('calling abort throws an error with message "Command was canceled"', async t => {
const abortController = new AbortController();
const subprocess = execa('noop.js', {cancelSignal: abortController.signal});
abortController.abort();
await t.throwsAsync(subprocess, {message: /Command was canceled/});
});
test('calling abort with no argument keeps error properties', async t => {
const abortController = new AbortController();
const subprocess = execa('empty.js', {cancelSignal: abortController.signal});
abortController.abort();
const {cause, originalMessage, shortMessage, message} = await t.throwsAsync(subprocess);
t.is(cause.message, 'This operation was aborted');
t.is(cause.name, 'AbortError');
t.is(originalMessage, 'This operation was aborted');
t.is(shortMessage, 'Command was canceled: empty.js\nThis operation was aborted');
t.is(message, 'Command was canceled: empty.js\nThis operation was aborted');
});
test('calling abort with an error instance keeps error properties', async t => {
const abortController = new AbortController();
const subprocess = execa('empty.js', {cancelSignal: abortController.signal});
const error = new Error(foobarString);
error.code = foobarString;
abortController.abort(error);
const {cause, originalMessage, shortMessage, message, code} = await t.throwsAsync(subprocess);
t.is(cause, error);
t.is(originalMessage, foobarString);
t.is(shortMessage, `Command was canceled: empty.js\n${foobarString}`);
t.is(message, `Command was canceled: empty.js\n${foobarString}`);
t.is(code, foobarString);
});
test('calling abort with null keeps error properties', async t => {
const abortController = new AbortController();
const subprocess = execa('empty.js', {cancelSignal: abortController.signal});
abortController.abort(null);
const {cause, originalMessage, shortMessage, message} = await t.throwsAsync(subprocess);
t.is(cause, null);
t.is(originalMessage, 'null');
t.is(shortMessage, 'Command was canceled: empty.js\nnull');
t.is(message, 'Command was canceled: empty.js\nnull');
});
test('calling abort twice should show the same behaviour as calling it once', async t => {
const abortController = new AbortController();
const subprocess = execa('noop.js', {cancelSignal: abortController.signal});
abortController.abort();
abortController.abort();
const {isCanceled, isGracefullyCanceled} = await t.throwsAsync(subprocess);
t.true(isCanceled);
t.false(isGracefullyCanceled);
});
test('calling abort on a successfully completed subprocess does not make result.isCanceled true', async t => {
const abortController = new AbortController();
const subprocess = execa('noop.js', {cancelSignal: abortController.signal});
const {isCanceled, isGracefullyCanceled} = await subprocess;
abortController.abort();
t.false(isCanceled);
t.false(isGracefullyCanceled);
});
test('Throws when using the former "signal" option name', t => {
const abortController = new AbortController();
t.throws(() => {
execa('empty.js', {signal: abortController.signal});
}, {message: /renamed to "cancelSignal"/});
});
test('Cannot use cancelSignal, sync', t => {
const abortController = new AbortController();
t.throws(() => {
execaSync('empty.js', {cancelSignal: abortController.signal});
}, {message: /The "cancelSignal" option cannot be used/});
});
================================================
FILE: test/terminate/cleanup.js
================================================
import process from 'node:process';
import {setTimeout} from 'node:timers/promises';
import test from 'ava';
import isRunning from 'is-running';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {nestedSubprocess} from '../helpers/nested.js';
import {foobarString} from '../helpers/input.js';
setFixtureDirectory();
const isWindows = process.platform === 'win32';
// When subprocess exits before current process
const spawnAndExit = async (t, worker, cleanup, detached) => {
const {nestedResult: {stdout}} = await nestedSubprocess('noop-fd.js', ['1', foobarString], {worker, cleanup, detached});
t.is(stdout, foobarString);
};
test('spawnAndExit', spawnAndExit, false, false, false);
test('spawnAndExit cleanup', spawnAndExit, false, true, false);
test('spawnAndExit detached', spawnAndExit, false, false, true);
test('spawnAndExit cleanup detached', spawnAndExit, false, true, true);
test('spawnAndExit, worker', spawnAndExit, true, false, false);
test('spawnAndExit cleanup, worker', spawnAndExit, true, true, false);
test('spawnAndExit detached, worker', spawnAndExit, true, false, true);
test('spawnAndExit cleanup detached, worker', spawnAndExit, true, true, true);
// When current process exits before subprocess
const spawnAndKill = async (t, [signal, cleanup, detached, isKilled]) => {
const subprocess = execa('ipc-send-pid.js', [cleanup, detached], {stdio: 'ignore', ipc: true});
const pid = await subprocess.getOneMessage();
t.true(Number.isInteger(pid));
t.true(isRunning(pid));
process.kill(subprocess.pid, signal);
await t.throwsAsync(subprocess);
t.false(isRunning(subprocess.pid));
if (isKilled) {
await Promise.race([
setTimeout(1e4, undefined, {ref: false}),
pollForSubprocessExit(pid),
]);
t.is(isRunning(pid), false);
} else {
t.is(isRunning(pid), true);
process.kill(pid, 'SIGKILL');
}
};
const pollForSubprocessExit = async pid => {
while (isRunning(pid)) {
// eslint-disable-next-line no-await-in-loop
await setTimeout(100);
}
};
// Without `options.cleanup`:
// - on Windows subprocesses are killed if `options.detached: false`, but not
// if `options.detached: true`
// - on Linux subprocesses are never killed regardless of `options.detached`
// With `options.cleanup`, subprocesses are always killed
// - `options.cleanup` with SIGKILL is a noop, since it cannot be handled
test('spawnAndKill SIGTERM', spawnAndKill, ['SIGTERM', false, false, isWindows]);
test('spawnAndKill SIGKILL', spawnAndKill, ['SIGKILL', false, false, isWindows]);
test('spawnAndKill cleanup SIGTERM', spawnAndKill, ['SIGTERM', true, false, true]);
test('spawnAndKill cleanup SIGKILL', spawnAndKill, ['SIGKILL', true, false, isWindows]);
test('spawnAndKill detached SIGTERM', spawnAndKill, ['SIGTERM', false, true, false]);
test('spawnAndKill detached SIGKILL', spawnAndKill, ['SIGKILL', false, true, false]);
test('spawnAndKill cleanup detached SIGTERM', spawnAndKill, ['SIGTERM', true, true, false]);
test('spawnAndKill cleanup detached SIGKILL', spawnAndKill, ['SIGKILL', true, true, false]);
// See #128
test('removes exit handler on exit', async t => {
// @todo this relies on `signal-exit` internals
const exitListeners = globalThis[Symbol.for('signal-exit emitter')].listeners.exit;
const subprocess = execa('noop.js');
const listener = exitListeners.at(-1);
await subprocess;
t.false(exitListeners.includes(listener));
});
test('detach subprocess', async t => {
const {stdout} = await execa('detach.js');
const pid = Number(stdout);
t.true(Number.isInteger(pid));
t.true(isRunning(pid));
process.kill(pid, 'SIGKILL');
});
test('Cannot use "detached" option, sync', t => {
t.throws(() => {
execaSync('empty.js', {detached: true});
}, {message: /The "detached: true" option cannot be used/});
});
================================================
FILE: test/terminate/graceful.js
================================================
import {setTimeout} from 'node:timers/promises';
import test from 'ava';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
import {mockSendIoError} from '../helpers/ipc.js';
setFixtureDirectory();
test('cancelSignal cannot be undefined with gracefulCancel', t => {
t.throws(() => {
execa('empty.js', {gracefulCancel: true});
}, {message: /The `cancelSignal` option must be defined/});
});
test('ipc cannot be false with gracefulCancel', t => {
t.throws(() => {
execa('empty.js', {gracefulCancel: true, cancelSignal: AbortSignal.abort(), ipc: false});
}, {message: /The `ipc` option cannot be false/});
});
test('serialization cannot be "json" with gracefulCancel', t => {
t.throws(() => {
execa('empty.js', {gracefulCancel: true, cancelSignal: AbortSignal.abort(), serialization: 'json'});
}, {message: /The `serialization` option cannot be 'json'/});
});
test('Current process can send a message right away', async t => {
const controller = new AbortController();
const subprocess = execa('ipc-echo.js', {cancelSignal: controller.signal, gracefulCancel: true});
await subprocess.sendMessage(foobarString);
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, [foobarString]);
});
test('Current process can receive a message right away', async t => {
const controller = new AbortController();
const subprocess = execa('ipc-send.js', {cancelSignal: controller.signal, gracefulCancel: true});
t.is(await subprocess.getOneMessage(), foobarString);
const {ipcOutput} = await subprocess;
t.deepEqual(ipcOutput, [foobarString]);
});
test('Does not disconnect during I/O errors when sending the abort reason', async t => {
const controller = new AbortController();
const subprocess = execa('ipc-echo.js', {cancelSignal: controller.signal, gracefulCancel: true, forceKillAfterDelay: false});
const error = mockSendIoError(subprocess);
controller.abort(foobarString);
await setTimeout(0);
t.true(subprocess.connected);
subprocess.kill();
const {isCanceled, isGracefullyCanceled, signal, ipcOutput, cause} = await t.throwsAsync(subprocess);
t.false(isCanceled);
t.false(isGracefullyCanceled);
t.is(signal, 'SIGTERM');
t.deepEqual(ipcOutput, []);
t.is(cause, error);
});
class AbortError extends Error {
name = 'AbortError';
}
test('Abort reason is sent to the subprocess', async t => {
const controller = new AbortController();
const subprocess = execa('graceful-send.js', {cancelSignal: controller.signal, gracefulCancel: true, forceKillAfterDelay: false});
const error = new AbortError(foobarString);
controller.abort(error);
const {isCanceled, isGracefullyCanceled, isTerminated, exitCode, cause, ipcOutput} = await t.throwsAsync(subprocess);
t.true(isCanceled);
t.true(isGracefullyCanceled);
t.false(isTerminated);
t.is(exitCode, 0);
t.is(cause, error);
t.is(ipcOutput[0].message, error.message);
t.is(ipcOutput[0].stack, error.stack);
t.is(ipcOutput[0].name, 'Error');
});
test('Abort default reason is sent to the subprocess', async t => {
const controller = new AbortController();
const subprocess = execa('graceful-send.js', {cancelSignal: controller.signal, gracefulCancel: true, forceKillAfterDelay: false});
controller.abort();
const {isCanceled, isGracefullyCanceled, isTerminated, exitCode, cause, ipcOutput} = await t.throwsAsync(subprocess);
t.true(isCanceled);
t.true(isGracefullyCanceled);
t.false(isTerminated);
t.is(exitCode, 0);
const {reason} = controller.signal;
t.is(cause.stack, reason.stack);
t.is(ipcOutput[0].message, reason.message);
t.is(ipcOutput[0].stack, reason.stack);
});
test('Fail when sending non-serializable abort reason', async t => {
const controller = new AbortController();
const subprocess = execa('ipc-echo.js', {cancelSignal: controller.signal, gracefulCancel: true, forceKillAfterDelay: false});
controller.abort(() => {});
await setTimeout(0);
t.true(subprocess.connected);
await subprocess.sendMessage(foobarString);
const {isCanceled, isGracefullyCanceled, isTerminated, exitCode, cause, ipcOutput} = await t.throwsAsync(subprocess);
t.false(isCanceled);
t.false(isGracefullyCanceled);
t.false(isTerminated);
t.is(exitCode, 0);
t.deepEqual(ipcOutput, [foobarString]);
t.is(cause.message, '`cancelSignal`\'s `controller.abort()`\'s argument type is invalid: the message cannot be serialized: () => {}.');
t.is(cause.cause.message, '() => {} could not be cloned.');
});
test('timeout does not use graceful cancelSignal', async t => {
const controller = new AbortController();
const {timedOut, isCanceled, isGracefullyCanceled, isTerminated, signal, exitCode, shortMessage, ipcOutput} = await t.throwsAsync(execa('graceful-send.js', {cancelSignal: controller.signal, gracefulCancel: true, timeout: 1}));
t.true(timedOut);
t.false(isCanceled);
t.false(isGracefullyCanceled);
t.true(isTerminated);
t.is(signal, 'SIGTERM');
t.is(exitCode, undefined);
t.is(shortMessage, 'Command timed out after 1 milliseconds: graceful-send.js');
t.deepEqual(ipcOutput, []);
});
test('error on graceful cancelSignal on non-0 exit code', async t => {
const {isCanceled, isGracefullyCanceled, isTerminated, isForcefullyTerminated, exitCode, shortMessage} = await t.throwsAsync(execa('wait-fail.js', {cancelSignal: AbortSignal.abort(''), gracefulCancel: true, forceKillAfterDelay: false}));
t.true(isCanceled);
t.true(isGracefullyCanceled);
t.false(isTerminated);
t.false(isForcefullyTerminated);
t.is(exitCode, 2);
t.is(shortMessage, 'Command was gracefully canceled with exit code 2: wait-fail.js');
});
test('error on graceful cancelSignal on forceful termination', async t => {
const {isCanceled, isGracefullyCanceled, isTerminated, signal, isForcefullyTerminated, exitCode, shortMessage} = await t.throwsAsync(execa('forever.js', {cancelSignal: AbortSignal.abort(''), gracefulCancel: true, forceKillAfterDelay: 1}));
t.true(isCanceled);
t.true(isGracefullyCanceled);
t.true(isTerminated);
t.is(signal, 'SIGKILL');
t.true(isForcefullyTerminated);
t.is(exitCode, undefined);
t.is(shortMessage, 'Command was gracefully canceled and was forcefully terminated after 1 milliseconds: forever.js');
});
test('error on graceful cancelSignal on non-forceful termination', async t => {
const subprocess = execa('ipc-send-get.js', {cancelSignal: AbortSignal.abort(''), gracefulCancel: true, forceKillAfterDelay: 1e6});
t.is(await subprocess.getOneMessage(), foobarString);
subprocess.kill();
const {isCanceled, isGracefullyCanceled, isTerminated, signal, isForcefullyTerminated, exitCode, shortMessage} = await t.throwsAsync(subprocess);
t.true(isCanceled);
t.true(isGracefullyCanceled);
t.true(isTerminated);
t.is(signal, 'SIGTERM');
t.false(isForcefullyTerminated);
t.is(exitCode, undefined);
t.is(shortMessage, 'Command was gracefully canceled with SIGTERM (Termination): ipc-send-get.js');
});
test('`forceKillAfterDelay: false` with the "cancelSignal" option when graceful', async t => {
const subprocess = execa('forever.js', {cancelSignal: AbortSignal.abort(''), gracefulCancel: true, forceKillAfterDelay: false});
await setTimeout(6e3);
subprocess.kill('SIGKILL');
const {isCanceled, isGracefullyCanceled, isTerminated, signal, isForcefullyTerminated, exitCode, shortMessage} = await t.throwsAsync(subprocess);
t.true(isCanceled);
t.true(isGracefullyCanceled);
t.true(isTerminated);
t.is(signal, 'SIGKILL');
t.false(isForcefullyTerminated);
t.is(exitCode, undefined);
t.is(shortMessage, 'Command was gracefully canceled with SIGKILL (Forced termination): forever.js');
});
test('subprocess.getCancelSignal() is not defined', async t => {
const subprocess = execa('empty.js', {cancelSignal: AbortSignal.abort(''), gracefulCancel: true});
t.is(subprocess.getCancelSignal, undefined);
await t.throwsAsync(subprocess);
});
================================================
FILE: test/terminate/kill-error.js
================================================
import {once} from 'node:events';
import {setImmediate} from 'node:timers/promises';
import test from 'ava';
import isRunning from 'is-running';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
setFixtureDirectory();
test('.kill(error) propagates error', async t => {
const subprocess = execa('forever.js');
const originalMessage = 'test';
const cause = new Error(originalMessage);
t.true(subprocess.kill(cause));
const error = await t.throwsAsync(subprocess);
t.is(error.cause, cause);
t.true(cause.stack.includes(import.meta.url));
t.is(error.exitCode, undefined);
t.is(error.signal, 'SIGTERM');
t.true(error.isTerminated);
t.is(error.originalMessage, originalMessage);
t.true(error.message.includes(originalMessage));
t.true(error.message.includes('was killed with SIGTERM'));
});
test('.kill(error) uses killSignal', async t => {
const subprocess = execa('forever.js', {killSignal: 'SIGINT'});
const cause = new Error('test');
subprocess.kill(cause);
const error = await t.throwsAsync(subprocess);
t.is(error.cause, cause);
t.is(error.signal, 'SIGINT');
});
test('.kill(signal, error) uses signal', async t => {
const subprocess = execa('forever.js');
const cause = new Error('test');
subprocess.kill('SIGINT', cause);
const error = await t.throwsAsync(subprocess);
t.is(error.cause, cause);
t.is(error.signal, 'SIGINT');
});
test('.kill(error) is a noop if subprocess already exited', async t => {
const subprocess = execa('empty.js');
await subprocess;
t.false(isRunning(subprocess.pid));
t.false(subprocess.kill(new Error('test')));
});
test('.kill(error) terminates but does not change the error if the subprocess already errored but did not exit yet', async t => {
const subprocess = execa('forever.js');
const cause = new Error('first');
subprocess.stdout.destroy(cause);
await setImmediate();
const secondError = new Error('second');
t.true(subprocess.kill(secondError));
const error = await t.throwsAsync(subprocess);
t.is(error.cause, cause);
t.is(error.exitCode, undefined);
t.is(error.signal, 'SIGTERM');
t.true(error.isTerminated);
t.false(error.message.includes(secondError.message));
});
test('.kill(error) twice in a row', async t => {
const subprocess = execa('forever.js');
const cause = new Error('first');
subprocess.kill(cause);
const secondCause = new Error('second');
subprocess.kill(secondCause);
const error = await t.throwsAsync(subprocess);
t.is(error.cause, cause);
t.false(error.message.includes(secondCause.message));
});
test('.kill(error) does not emit the "error" event', async t => {
const subprocess = execa('forever.js');
const cause = new Error('test');
subprocess.kill(cause);
const error = await Promise.race([t.throwsAsync(subprocess), once(subprocess, 'error')]);
t.is(error.cause, cause);
});
================================================
FILE: test/terminate/kill-force.js
================================================
import process from 'node:process';
import {once, defaultMaxListeners} from 'node:events';
import {constants} from 'node:os';
import {setTimeout} from 'node:timers/promises';
import test from 'ava';
import isRunning from 'is-running';
import {execa} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {assertMaxListeners} from '../helpers/listeners.js';
import {foobarString} from '../helpers/input.js';
import {getEarlyErrorSubprocess} from '../helpers/early-error.js';
setFixtureDirectory();
const isWindows = process.platform === 'win32';
const spawnNoKillable = async (forceKillAfterDelay, options) => {
const subprocess = execa('no-killable.js', {
ipc: true,
forceKillAfterDelay,
...options,
});
await subprocess.getOneMessage();
return {subprocess};
};
const noKillableSimpleOptions = {killSignal: 'SIGWINCH', forceKillAfterDelay: 1};
const spawnNoKillableSimple = options => execa('forever.js', {...noKillableSimpleOptions, ...options});
test('kill("SIGKILL") should terminate cleanly', async t => {
const {subprocess} = await spawnNoKillable();
subprocess.kill('SIGKILL');
const {isTerminated, signal, isForcefullyTerminated, shortMessage} = await t.throwsAsync(subprocess);
t.true(isTerminated);
t.is(signal, 'SIGKILL');
t.false(isForcefullyTerminated);
t.is(shortMessage, 'Command was killed with SIGKILL (Forced termination): no-killable.js');
});
const testInvalidForceKill = async (t, forceKillAfterDelay) => {
t.throws(() => {
execa('empty.js', {forceKillAfterDelay});
}, {instanceOf: TypeError, message: /non-negative integer/});
};
test('`forceKillAfterDelay` should not be NaN', testInvalidForceKill, Number.NaN);
test('`forceKillAfterDelay` should not be negative', testInvalidForceKill, -1);
// `SIGTERM` cannot be caught on Windows, and it always aborts the subprocess (like `SIGKILL` on Unix).
// Therefore, this feature and those tests must be different on Windows.
if (isWindows) {
test('Can call `.kill()` with `forceKillAfterDelay` on Windows', async t => {
const {subprocess} = await spawnNoKillable();
subprocess.kill();
const {isTerminated, signal, isForcefullyTerminated, shortMessage} = await t.throwsAsync(subprocess);
t.true(isTerminated);
t.is(signal, 'SIGTERM');
t.false(isForcefullyTerminated);
t.is(shortMessage, 'Command was killed with SIGTERM (Termination): no-killable.js');
});
} else {
const testNoForceKill = async (t, forceKillAfterDelay, killArgument, options) => {
const {subprocess} = await spawnNoKillable(forceKillAfterDelay, options);
subprocess.kill(killArgument);
await setTimeout(6e3);
t.true(isRunning(subprocess.pid));
subprocess.kill('SIGKILL');
const {isTerminated, signal, isForcefullyTerminated} = await t.throwsAsync(subprocess);
t.true(isTerminated);
t.is(signal, 'SIGKILL');
t.false(isForcefullyTerminated);
};
test('`forceKillAfterDelay: false` should not kill after a timeout', testNoForceKill, false);
test('`forceKillAfterDelay` should not kill after a timeout with other signals', testNoForceKill, true, 'SIGINT');
test('`forceKillAfterDelay` should not kill after a timeout with wrong killSignal string', testNoForceKill, true, 'SIGTERM', {killSignal: 'SIGINT'});
test('`forceKillAfterDelay` should not kill after a timeout with wrong killSignal number', testNoForceKill, true, constants.signals.SIGTERM, {killSignal: constants.signals.SIGINT});
// eslint-disable-next-line max-params
const testForceKill = async (t, forceKillAfterDelay, killSignal, expectedDelay, expectedKillSignal, options) => {
const {subprocess} = await spawnNoKillable(forceKillAfterDelay, options);
subprocess.kill(killSignal);
const {isTerminated, signal, isForcefullyTerminated, shortMessage} = await t.throwsAsync(subprocess);
t.true(isTerminated);
t.is(signal, 'SIGKILL');
t.true(isForcefullyTerminated);
const messageSuffix = killSignal instanceof Error ? `\n${killSignal.message}` : '';
const signalDescription = expectedKillSignal === 'SIGINT' ? 'User interruption with CTRL-C' : 'Termination';
t.is(shortMessage, `Command was killed with ${expectedKillSignal} (${signalDescription}) and was forcefully terminated after ${expectedDelay} milliseconds: no-killable.js${messageSuffix}`);
};
test('`forceKillAfterDelay: number` should kill after a timeout', testForceKill, 50, undefined, 50, 'SIGTERM');
test('`forceKillAfterDelay: true` should kill after a timeout', testForceKill, true, undefined, 5e3, 'SIGTERM');
test('`forceKillAfterDelay: undefined` should kill after a timeout', testForceKill, undefined, undefined, 5e3, 'SIGTERM');
test('`forceKillAfterDelay` should kill after a timeout with SIGTERM', testForceKill, 50, 'SIGTERM', 50, 'SIGTERM');
test('`forceKillAfterDelay` should kill after a timeout with the killSignal string', testForceKill, 50, 'SIGINT', 50, 'SIGINT', {killSignal: 'SIGINT'});
test('`forceKillAfterDelay` should kill after a timeout with the killSignal string, mixed', testForceKill, 50, 'SIGINT', 50, 'SIGINT', {killSignal: constants.signals.SIGINT});
test('`forceKillAfterDelay` should kill after a timeout with the killSignal number', testForceKill, 50, constants.signals.SIGINT, 50, 'SIGINT', {killSignal: constants.signals.SIGINT});
test('`forceKillAfterDelay` should kill after a timeout with the killSignal number, mixed', testForceKill, 50, constants.signals.SIGINT, 50, 'SIGINT', {killSignal: 'SIGINT'});
test('`forceKillAfterDelay` should kill after a timeout with an error', testForceKill, 50, new Error('test'), 50, 'SIGTERM');
test('`forceKillAfterDelay` should kill after a timeout with an error and a killSignal', testForceKill, 50, new Error('test'), 50, 'SIGINT', {killSignal: 'SIGINT'});
test('`forceKillAfterDelay` works with the "cancelSignal" option', async t => {
const abortController = new AbortController();
const subprocess = spawnNoKillableSimple({cancelSignal: abortController.signal});
await once(subprocess, 'spawn');
abortController.abort('');
const {isTerminated, signal, isCanceled, isGracefullyCanceled, isForcefullyTerminated, shortMessage} = await t.throwsAsync(subprocess);
t.true(isTerminated);
t.is(signal, 'SIGKILL');
t.true(isCanceled);
t.false(isGracefullyCanceled);
t.true(isForcefullyTerminated);
t.is(shortMessage, 'Command was canceled and was forcefully terminated after 1 milliseconds: forever.js');
});
test('`forceKillAfterDelay` works with the "timeout" option', async t => {
const {isTerminated, signal, timedOut, isForcefullyTerminated, shortMessage} = await t.throwsAsync(spawnNoKillableSimple({timeout: 1}));
t.true(isTerminated);
t.is(signal, 'SIGKILL');
t.true(timedOut);
t.true(isForcefullyTerminated);
t.is(shortMessage, 'Command timed out after 1 milliseconds and was forcefully terminated after 1 milliseconds: forever.js');
});
test('`forceKillAfterDelay` works with the "maxBuffer" option', async t => {
const subprocess = execa('noop-forever.js', ['.'], {...noKillableSimpleOptions, maxBuffer: 1});
const [chunk] = await once(subprocess.stdout, 'data');
t.is(chunk.toString(), '.\n');
subprocess.kill();
const {isTerminated, signal, isForcefullyTerminated, shortMessage} = await t.throwsAsync(subprocess);
t.true(isTerminated);
t.is(signal, 'SIGKILL');
t.true(isForcefullyTerminated);
t.is(shortMessage, 'Command\'s stdout was larger than 1 characters and was forcefully terminated after 1 milliseconds: noop-forever.js .\nmaxBuffer exceeded');
});
test('`forceKillAfterDelay` works with the "error" event', async t => {
const subprocess = spawnNoKillableSimple();
await once(subprocess, 'spawn');
const error = new Error(foobarString);
error.code = 'ECODE';
subprocess.emit('error', error);
subprocess.kill();
const {isTerminated, signal, isForcefullyTerminated, shortMessage, originalMessage, cause} = await t.throwsAsync(subprocess);
t.true(isTerminated);
t.is(signal, 'SIGKILL');
t.true(isForcefullyTerminated);
t.is(cause, error);
t.is(originalMessage, error.message);
t.is(shortMessage, `Command failed with ${error.code} and was forcefully terminated after 1 milliseconds: forever.js\n${error.message}`);
});
test.serial('Can call `.kill()` with `forceKillAfterDelay` many times without triggering the maxListeners warning', async t => {
const checkMaxListeners = assertMaxListeners(t);
const subprocess = spawnNoKillableSimple();
for (let index = 0; index < defaultMaxListeners + 1; index += 1) {
subprocess.kill();
}
const {isTerminated, signal, isForcefullyTerminated} = await t.throwsAsync(subprocess);
t.true(isTerminated);
t.is(signal, 'SIGKILL');
t.true(isForcefullyTerminated);
checkMaxListeners();
});
test('Can call `.kill()` with `forceKillAfterDelay` multiple times', async t => {
const subprocess = spawnNoKillableSimple();
subprocess.kill();
subprocess.kill();
const {isTerminated, signal, isForcefullyTerminated} = await t.throwsAsync(subprocess);
t.true(isTerminated);
t.is(signal, 'SIGKILL');
t.true(isForcefullyTerminated);
});
}
test('result.isForcefullyTerminated is false on success', async t => {
const {isForcefullyTerminated} = await execa('empty.js');
t.false(isForcefullyTerminated);
});
test('error.isForcefullyTerminated is false on spawn errors', async t => {
const {isForcefullyTerminated} = await t.throwsAsync(getEarlyErrorSubprocess());
t.false(isForcefullyTerminated);
});
test('error.isForcefullyTerminated is false when already terminated', async t => {
const abortController = new AbortController();
const final = async function * () {
try {
await setTimeout(1e6, undefined, {signal: abortController.signal});
} catch {}
yield * [];
};
const subprocess = execa('forever.js', {stdout: {final}});
subprocess.kill();
await setTimeout(6e3);
abortController.abort();
const {isForcefullyTerminated, isTerminated, signal} = await t.throwsAsync(subprocess);
t.false(isForcefullyTerminated);
t.true(isTerminated);
t.is(signal, 'SIGTERM');
});
================================================
FILE: test/terminate/kill-signal.js
================================================
import {once} from 'node:events';
import {platform} from 'node:process';
import {constants} from 'node:os';
import {setImmediate} from 'node:timers/promises';
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {majorNodeVersion} from '../helpers/node-version.js';
setFixtureDirectory();
const isWindows = platform === 'win32';
const testKillSignal = async (t, killSignal) => {
const {isTerminated, signal} = await t.throwsAsync(execa('forever.js', {killSignal, timeout: 1}));
t.true(isTerminated);
t.is(signal, 'SIGINT');
};
test('Can use killSignal: "SIGINT"', testKillSignal, 'SIGINT');
test('Can use killSignal: 2', testKillSignal, constants.signals.SIGINT);
const testKillSignalSync = (t, killSignal) => {
const {isTerminated, signal} = t.throws(() => {
execaSync('forever.js', {killSignal, timeout: 1});
});
t.true(isTerminated);
t.is(signal, 'SIGINT');
};
test('Can use killSignal: "SIGINT", sync', testKillSignalSync, 'SIGINT');
test('Can use killSignal: 2, sync', testKillSignalSync, constants.signals.SIGINT);
test('Can call .kill("SIGTERM")', async t => {
const subprocess = execa('forever.js');
subprocess.kill('SIGTERM');
const {isTerminated, signal} = await t.throwsAsync(subprocess);
t.true(isTerminated);
t.is(signal, 'SIGTERM');
});
test('Can call .kill(15)', async t => {
const subprocess = execa('forever.js');
subprocess.kill(constants.signals.SIGTERM);
const {isTerminated, signal} = await t.throwsAsync(subprocess);
t.true(isTerminated);
t.is(signal, 'SIGTERM');
});
test('Can call .kill(0)', async t => {
const subprocess = execa('forever.js');
t.true(subprocess.kill(0));
subprocess.kill();
await t.throwsAsync(subprocess);
t.false(subprocess.kill(0));
});
test('Can call `.kill()` multiple times', async t => {
const subprocess = execa('forever.js');
subprocess.kill();
subprocess.kill();
const {exitCode, isTerminated, signal, code} = await t.throwsAsync(subprocess);
// On Windows, calling `subprocess.kill()` twice emits an `error` event on the subprocess.
// This does not happen when passing an `error` argument, nor when passing a non-terminating signal.
// There is no easy way to make this cross-platform, so we document the difference here.
if (isWindows && majorNodeVersion === 22) {
t.is(exitCode, undefined);
t.false(isTerminated);
t.is(signal, undefined);
t.is(code, 'EPERM');
} else {
t.is(exitCode, undefined);
t.true(isTerminated);
t.is(signal, 'SIGTERM');
t.is(code, undefined);
}
});
test('execa() returns a promise with kill()', async t => {
const subprocess = execa('noop.js', ['foo']);
t.is(typeof subprocess.kill, 'function');
await subprocess;
});
const testInvalidKillArgument = async (t, killArgument, secondKillArgument) => {
const subprocess = execa('empty.js');
const message = secondKillArgument instanceof Error || secondKillArgument === undefined
? /error instance or a signal name/
: /second argument is optional/;
t.throws(() => {
subprocess.kill(killArgument, secondKillArgument);
}, {message});
await subprocess;
};
test('Cannot call .kill(errorObject)', testInvalidKillArgument, {name: '', message: '', stack: ''});
test('Cannot call .kill(errorArray)', testInvalidKillArgument, [new Error('test')]);
test('Cannot call .kill(undefined, true)', testInvalidKillArgument, undefined, true);
test('Cannot call .kill("SIGTERM", true)', testInvalidKillArgument, 'SIGTERM', true);
test('Cannot call .kill(true, error)', testInvalidKillArgument, true, new Error('test'));
test('subprocess errors are handled before spawn', async t => {
const subprocess = execa('forever.js');
const cause = new Error('test');
subprocess.emit('error', cause);
subprocess.kill();
const error = await t.throwsAsync(subprocess);
t.is(error.cause, cause);
t.is(error.exitCode, undefined);
t.is(error.signal, undefined);
t.false(error.isTerminated);
});
test('subprocess errors are handled after spawn', async t => {
const subprocess = execa('forever.js');
await once(subprocess, 'spawn');
const cause = new Error('test');
subprocess.emit('error', cause);
subprocess.kill();
const error = await t.throwsAsync(subprocess);
t.is(error.cause, cause);
t.is(error.exitCode, undefined);
t.is(error.signal, 'SIGTERM');
t.true(error.isTerminated);
});
test('subprocess double errors are handled after spawn', async t => {
const abortController = new AbortController();
const subprocess = execa('forever.js', {cancelSignal: abortController.signal});
await once(subprocess, 'spawn');
const cause = new Error('test');
subprocess.emit('error', cause);
await setImmediate();
abortController.abort();
subprocess.emit('error', cause);
const error = await t.throwsAsync(subprocess);
t.is(error.cause, cause);
t.is(error.exitCode, undefined);
t.is(error.signal, 'SIGTERM');
t.true(error.isTerminated);
});
test('subprocess errors use killSignal', async t => {
const subprocess = execa('forever.js', {killSignal: 'SIGINT'});
await once(subprocess, 'spawn');
const cause = new Error('test');
subprocess.emit('error', cause);
subprocess.kill();
const error = await t.throwsAsync(subprocess);
t.is(error.cause, cause);
t.true(error.isTerminated);
t.is(error.signal, 'SIGINT');
});
================================================
FILE: test/terminate/signal.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
setFixtureDirectory();
const VALIDATION_MESSAGES = {
string: 'this signal name does not exist',
integer: 'this signal integer does not exist',
other: 'it must be a string or an integer',
rename: 'please rename it to',
zero: '0 cannot be used',
};
const validateMessage = (t, message, type) => {
t.true(message.includes(VALIDATION_MESSAGES[type]));
if (type !== 'rename' && type !== 'zero') {
t.true(message.includes('Available signal names: \'SIGABRT\', '));
t.true(message.includes('Available signal numbers: 1, '));
}
};
const testInvalidKillSignal = (t, killSignal, type, execaMethod) => {
const {message} = t.throws(() => {
execaMethod('empty.js', {killSignal});
});
t.true(message.includes('Invalid option `killSignal`'));
validateMessage(t, message, type);
};
test('Cannot use killSignal: "SIGOTHER"', testInvalidKillSignal, 'SIGOTHER', 'string', execa);
test('Cannot use killSignal: "Sigterm"', testInvalidKillSignal, 'Sigterm', 'rename', execa);
test('Cannot use killSignal: "sigterm"', testInvalidKillSignal, 'sigterm', 'rename', execa);
test('Cannot use killSignal: -1', testInvalidKillSignal, -1, 'integer', execa);
test('Cannot use killSignal: 200', testInvalidKillSignal, 200, 'integer', execa);
test('Cannot use killSignal: 1n', testInvalidKillSignal, 1n, 'other', execa);
test('Cannot use killSignal: 1.5', testInvalidKillSignal, 1.5, 'other', execa);
test('Cannot use killSignal: Infinity', testInvalidKillSignal, Number.POSITIVE_INFINITY, 'other', execa);
test('Cannot use killSignal: NaN', testInvalidKillSignal, Number.NaN, 'other', execa);
test('Cannot use killSignal: false', testInvalidKillSignal, false, 'other', execa);
test('Cannot use killSignal: null', testInvalidKillSignal, null, 'other', execa);
test('Cannot use killSignal: symbol', testInvalidKillSignal, Symbol('test'), 'other', execa);
test('Cannot use killSignal: {}', testInvalidKillSignal, {}, 'other', execa);
test('Cannot use killSignal: 0', testInvalidKillSignal, 0, 'zero', execa);
test('Cannot use killSignal: "SIGOTHER", sync', testInvalidKillSignal, 'SIGOTHER', 'string', execaSync);
test('Cannot use killSignal: "Sigterm", sync', testInvalidKillSignal, 'Sigterm', 'rename', execaSync);
test('Cannot use killSignal: "sigterm", sync', testInvalidKillSignal, 'sigterm', 'rename', execaSync);
test('Cannot use killSignal: -1, sync', testInvalidKillSignal, -1, 'integer', execaSync);
test('Cannot use killSignal: 200, sync', testInvalidKillSignal, 200, 'integer', execaSync);
test('Cannot use killSignal: 1.5, sync', testInvalidKillSignal, 1.5, 'other', execaSync);
test('Cannot use killSignal: Infinity, sync', testInvalidKillSignal, Number.POSITIVE_INFINITY, 'other', execaSync);
test('Cannot use killSignal: NaN, sync', testInvalidKillSignal, Number.NaN, 'other', execaSync);
test('Cannot use killSignal: null, sync', testInvalidKillSignal, null, 'other', execaSync);
test('Cannot use killSignal: symbol, sync', testInvalidKillSignal, Symbol('test'), 'other', execaSync);
test('Cannot use killSignal: {}, sync', testInvalidKillSignal, {}, 'other', execaSync);
test('Cannot use killSignal: 0, sync', testInvalidKillSignal, 0, 'zero', execaSync);
const testInvalidSignalArgument = async (t, signal, type) => {
const subprocess = execa('empty.js');
const {message} = t.throws(() => {
subprocess.kill(signal);
});
if (type === 'other') {
t.true(message.includes('must be an error instance or a signal name string/integer'));
} else {
t.true(message.includes('Invalid `subprocess.kill()`\'s argument'));
validateMessage(t, message, type);
}
await subprocess;
};
test('Cannot use subprocess.kill("SIGOTHER")', testInvalidSignalArgument, 'SIGOTHER', 'string');
test('Cannot use subprocess.kill("Sigterm")', testInvalidSignalArgument, 'Sigterm', 'rename');
test('Cannot use subprocess.kill("sigterm")', testInvalidSignalArgument, 'sigterm', 'rename');
test('Cannot use subprocess.kill(-1)', testInvalidSignalArgument, -1, 'integer');
test('Cannot use subprocess.kill(200)', testInvalidSignalArgument, 200, 'integer');
test('Cannot use subprocess.kill(1n)', testInvalidSignalArgument, 1n, 'other');
test('Cannot use subprocess.kill(1.5)', testInvalidSignalArgument, 1.5, 'other');
test('Cannot use subprocess.kill(Infinity)', testInvalidSignalArgument, Number.POSITIVE_INFINITY, 'other');
test('Cannot use subprocess.kill(NaN)', testInvalidSignalArgument, Number.NaN, 'other');
test('Cannot use subprocess.kill(false)', testInvalidSignalArgument, false, 'other');
test('Cannot use subprocess.kill(null)', testInvalidSignalArgument, null, 'other');
test('Cannot use subprocess.kill(symbol)', testInvalidSignalArgument, Symbol('test'), 'other');
test('Cannot use subprocess.kill({})', testInvalidSignalArgument, {}, 'other');
================================================
FILE: test/terminate/timeout.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory, FIXTURES_DIRECTORY} from '../helpers/fixtures-directory.js';
setFixtureDirectory();
test('timeout kills the subprocess if it times out', async t => {
const {isTerminated, signal, timedOut, originalMessage, shortMessage, message} = await t.throwsAsync(execa('forever.js', {timeout: 1}));
t.true(isTerminated);
t.is(signal, 'SIGTERM');
t.true(timedOut);
t.is(originalMessage, undefined);
t.is(shortMessage, 'Command timed out after 1 milliseconds: forever.js');
t.is(message, shortMessage);
});
test('timeout kills the subprocess if it times out, in sync mode', async t => {
const {isTerminated, signal, timedOut, originalMessage, shortMessage, message} = await t.throws(() => {
execaSync('node', ['forever.js'], {timeout: 1, cwd: FIXTURES_DIRECTORY});
});
t.true(isTerminated);
t.is(signal, 'SIGTERM');
t.true(timedOut);
t.is(originalMessage, 'spawnSync node ETIMEDOUT');
t.is(shortMessage, `Command timed out after 1 milliseconds: node forever.js\n${originalMessage}`);
t.is(message, shortMessage);
});
test('timeout does not kill the subprocess if it does not time out', async t => {
const {timedOut} = await execa('delay.js', ['500'], {timeout: 1e8});
t.false(timedOut);
});
test('timeout uses killSignal', async t => {
const {isTerminated, signal, timedOut} = await t.throwsAsync(execa('forever.js', {timeout: 1, killSignal: 'SIGINT'}));
t.true(isTerminated);
t.is(signal, 'SIGINT');
t.true(timedOut);
});
const INVALID_TIMEOUT_REGEXP = /`timeout` option to be a non-negative integer/;
const testTimeoutValidation = (t, timeout, execaMethod) => {
t.throws(() => {
execaMethod('empty.js', {timeout});
}, {message: INVALID_TIMEOUT_REGEXP});
};
test('timeout must not be negative', testTimeoutValidation, -1, execa);
test('timeout must be an integer', testTimeoutValidation, false, execa);
test('timeout must not be negative - sync', testTimeoutValidation, -1, execaSync);
test('timeout must be an integer - sync', testTimeoutValidation, false, execaSync);
test('timedOut is false if timeout is undefined', async t => {
const {timedOut} = await execa('noop.js');
t.false(timedOut);
});
test('timedOut is false if timeout is 0', async t => {
const {timedOut} = await execa('noop.js', {timeout: 0});
t.false(timedOut);
});
test('timedOut is false if timeout is undefined and exit code is 0 in sync mode', t => {
const {timedOut} = execaSync('noop.js');
t.false(timedOut);
});
test('timedOut is false if the timeout happened after a different error occurred', async t => {
const subprocess = execa('forever.js', {timeout: 1e3});
const cause = new Error('test');
subprocess.emit('error', cause);
const error = await t.throwsAsync(subprocess);
t.is(error.cause, cause);
t.false(error.timedOut);
});
================================================
FILE: test/transform/encoding-final.js
================================================
import {Buffer} from 'node:buffer';
import {exec} from 'node:child_process';
import {promisify} from 'node:util';
import test from 'ava';
import getStream from 'get-stream';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory, FIXTURES_DIRECTORY} from '../helpers/fixtures-directory.js';
import {fullStdio} from '../helpers/stdio.js';
import {foobarString, foobarUint8Array, foobarHex} from '../helpers/input.js';
const pExec = promisify(exec);
setFixtureDirectory();
const checkEncoding = async (t, encoding, fdNumber, execaMethod) => {
const {stdio} = await execaMethod('noop-fd.js', [`${fdNumber}`, STRING_TO_ENCODE], {...fullStdio, encoding});
compareValues(t, stdio[fdNumber], encoding);
if (execaMethod !== execaSync) {
const subprocess = execaMethod('noop-fd.js', [`${fdNumber}`, STRING_TO_ENCODE], {...fullStdio, encoding});
const result = await getStream(subprocess.stdio[fdNumber]);
compareValues(t, result, 'utf8');
await subprocess;
}
if (fdNumber === 3) {
return;
}
const {stdout, stderr} = await pExec(`node noop-fd.js ${fdNumber} ${STRING_TO_ENCODE}`, {encoding, cwd: FIXTURES_DIRECTORY});
compareValues(t, fdNumber === 1 ? stdout : stderr, encoding);
};
const compareValues = (t, value, encoding) => {
if (encoding === 'buffer') {
t.true(ArrayBuffer.isView(value));
t.true(BUFFER_TO_ENCODE.equals(value));
} else {
t.is(value, BUFFER_TO_ENCODE.toString(encoding));
}
};
// This string gives different outputs with each encoding type
const STRING_TO_ENCODE = '\u1000.';
const BUFFER_TO_ENCODE = Buffer.from(STRING_TO_ENCODE);
test('can pass encoding "buffer" to stdout', checkEncoding, 'buffer', 1, execa);
test('can pass encoding "utf8" to stdout', checkEncoding, 'utf8', 1, execa);
test('can pass encoding "utf16le" to stdout', checkEncoding, 'utf16le', 1, execa);
test('can pass encoding "latin1" to stdout', checkEncoding, 'latin1', 1, execa);
test('can pass encoding "ascii" to stdout', checkEncoding, 'ascii', 1, execa);
test('can pass encoding "hex" to stdout', checkEncoding, 'hex', 1, execa);
test('can pass encoding "base64" to stdout', checkEncoding, 'base64', 1, execa);
test('can pass encoding "base64url" to stdout', checkEncoding, 'base64url', 1, execa);
test('can pass encoding "buffer" to stderr', checkEncoding, 'buffer', 2, execa);
test('can pass encoding "utf8" to stderr', checkEncoding, 'utf8', 2, execa);
test('can pass encoding "utf16le" to stderr', checkEncoding, 'utf16le', 2, execa);
test('can pass encoding "latin1" to stderr', checkEncoding, 'latin1', 2, execa);
test('can pass encoding "ascii" to stderr', checkEncoding, 'ascii', 2, execa);
test('can pass encoding "hex" to stderr', checkEncoding, 'hex', 2, execa);
test('can pass encoding "base64" to stderr', checkEncoding, 'base64', 2, execa);
test('can pass encoding "base64url" to stderr', checkEncoding, 'base64url', 2, execa);
test('can pass encoding "buffer" to stdio[*]', checkEncoding, 'buffer', 3, execa);
test('can pass encoding "utf8" to stdio[*]', checkEncoding, 'utf8', 3, execa);
test('can pass encoding "utf16le" to stdio[*]', checkEncoding, 'utf16le', 3, execa);
test('can pass encoding "latin1" to stdio[*]', checkEncoding, 'latin1', 3, execa);
test('can pass encoding "ascii" to stdio[*]', checkEncoding, 'ascii', 3, execa);
test('can pass encoding "hex" to stdio[*]', checkEncoding, 'hex', 3, execa);
test('can pass encoding "base64" to stdio[*]', checkEncoding, 'base64', 3, execa);
test('can pass encoding "base64url" to stdio[*]', checkEncoding, 'base64url', 3, execa);
test('can pass encoding "buffer" to stdout - sync', checkEncoding, 'buffer', 1, execaSync);
test('can pass encoding "utf8" to stdout - sync', checkEncoding, 'utf8', 1, execaSync);
test('can pass encoding "utf16le" to stdout - sync', checkEncoding, 'utf16le', 1, execaSync);
test('can pass encoding "latin1" to stdout - sync', checkEncoding, 'latin1', 1, execaSync);
test('can pass encoding "ascii" to stdout - sync', checkEncoding, 'ascii', 1, execaSync);
test('can pass encoding "hex" to stdout - sync', checkEncoding, 'hex', 1, execaSync);
test('can pass encoding "base64" to stdout - sync', checkEncoding, 'base64', 1, execaSync);
test('can pass encoding "base64url" to stdout - sync', checkEncoding, 'base64url', 1, execaSync);
test('can pass encoding "buffer" to stderr - sync', checkEncoding, 'buffer', 2, execaSync);
test('can pass encoding "utf8" to stderr - sync', checkEncoding, 'utf8', 2, execaSync);
test('can pass encoding "utf16le" to stderr - sync', checkEncoding, 'utf16le', 2, execaSync);
test('can pass encoding "latin1" to stderr - sync', checkEncoding, 'latin1', 2, execaSync);
test('can pass encoding "ascii" to stderr - sync', checkEncoding, 'ascii', 2, execaSync);
test('can pass encoding "hex" to stderr - sync', checkEncoding, 'hex', 2, execaSync);
test('can pass encoding "base64" to stderr - sync', checkEncoding, 'base64', 2, execaSync);
test('can pass encoding "base64url" to stderr - sync', checkEncoding, 'base64url', 2, execaSync);
test('can pass encoding "buffer" to stdio[*] - sync', checkEncoding, 'buffer', 3, execaSync);
test('can pass encoding "utf8" to stdio[*] - sync', checkEncoding, 'utf8', 3, execaSync);
test('can pass encoding "utf16le" to stdio[*] - sync', checkEncoding, 'utf16le', 3, execaSync);
test('can pass encoding "latin1" to stdio[*] - sync', checkEncoding, 'latin1', 3, execaSync);
test('can pass encoding "ascii" to stdio[*] - sync', checkEncoding, 'ascii', 3, execaSync);
test('can pass encoding "hex" to stdio[*] - sync', checkEncoding, 'hex', 3, execaSync);
test('can pass encoding "base64" to stdio[*] - sync', checkEncoding, 'base64', 3, execaSync);
test('can pass encoding "base64url" to stdio[*] - sync', checkEncoding, 'base64url', 3, execaSync);
// eslint-disable-next-line max-params
const testEncodingInput = async (t, input, expectedStdout, encoding, execaMethod) => {
const {stdout} = await execaMethod('stdin.js', {input, encoding});
t.deepEqual(stdout, expectedStdout);
};
test('Can use string input', testEncodingInput, foobarString, foobarString, 'utf8', execa);
test('Can use Uint8Array input', testEncodingInput, foobarUint8Array, foobarString, 'utf8', execa);
test('Can use string input, encoding "buffer"', testEncodingInput, foobarString, foobarUint8Array, 'buffer', execa);
test('Can use Uint8Array input, encoding "buffer"', testEncodingInput, foobarUint8Array, foobarUint8Array, 'buffer', execa);
test('Can use string input, encoding "hex"', testEncodingInput, foobarString, foobarHex, 'hex', execa);
test('Can use Uint8Array input, encoding "hex"', testEncodingInput, foobarUint8Array, foobarHex, 'hex', execa);
test('Can use string input, sync', testEncodingInput, foobarString, foobarString, 'utf8', execaSync);
test('Can use Uint8Array input, sync', testEncodingInput, foobarUint8Array, foobarString, 'utf8', execaSync);
test('Can use string input, encoding "buffer", sync', testEncodingInput, foobarString, foobarUint8Array, 'buffer', execaSync);
test('Can use Uint8Array input, encoding "buffer", sync', testEncodingInput, foobarUint8Array, foobarUint8Array, 'buffer', execaSync);
test('Can use string input, encoding "hex", sync', testEncodingInput, foobarString, foobarHex, 'hex', execaSync);
test('Can use Uint8Array input, encoding "hex", sync', testEncodingInput, foobarUint8Array, foobarHex, 'hex', execaSync);
const testSubprocessEncoding = (t, encoding) => {
const subprocess = execa('empty.js', {...fullStdio, encoding});
t.is(subprocess.stdout.readableEncoding, null);
t.is(subprocess.stderr.readableEncoding, null);
t.is(subprocess.stdio[3].readableEncoding, null);
};
test('Does not modify subprocess.std* encoding, "utf8"', testSubprocessEncoding, 'utf8');
test('Does not modify subprocess.std* encoding, "utf16le"', testSubprocessEncoding, 'utf16le');
test('Does not modify subprocess.std* encoding, "buffer"', testSubprocessEncoding, 'buffer');
test('Does not modify subprocess.std* encoding, "hex"', testSubprocessEncoding, 'hex');
test('Does not modify subprocess.std* encoding, "base64"', testSubprocessEncoding, 'base64');
test('Does not modify subprocess.std* encoding, "base64url"', testSubprocessEncoding, 'base64url');
test('Does not modify subprocess.std* encoding, "latin1"', testSubprocessEncoding, 'latin1');
test('Does not modify subprocess.std* encoding, "ascii"', testSubprocessEncoding, 'ascii');
================================================
FILE: test/transform/encoding-ignored.js
================================================
import process from 'node:process';
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {outputObjectGenerator, addNoopGenerator} from '../helpers/generator.js';
import {foobarObject} from '../helpers/input.js';
setFixtureDirectory();
const testObjectMode = async (t, addNoopTransform, execaMethod) => {
const {stdout} = await execaMethod('noop.js', {
stdout: addNoopGenerator(outputObjectGenerator(), addNoopTransform, true),
encoding: 'base64',
});
t.deepEqual(stdout, [foobarObject]);
};
test('Other encodings work with transforms that return objects', testObjectMode, false, execa);
test('Other encodings work with transforms that return objects, noop transform', testObjectMode, true, execa);
test('Other encodings work with transforms that return objects, sync', testObjectMode, false, execaSync);
test('Other encodings work with transforms that return objects, noop transform, sync', testObjectMode, true, execaSync);
// eslint-disable-next-line max-params
const testIgnoredEncoding = async (t, stdoutOption, isUndefined, options, execaMethod) => {
const {stdout} = await execaMethod('empty.js', {stdout: stdoutOption, ...options});
t.is(stdout === undefined, isUndefined);
};
const base64Options = {encoding: 'base64'};
const linesOptions = {lines: true};
test('Is ignored with other encodings and "ignore"', testIgnoredEncoding, 'ignore', true, base64Options, execa);
test('Is ignored with other encodings and ["ignore"]', testIgnoredEncoding, ['ignore'], true, base64Options, execa);
test('Is ignored with other encodings and "ipc"', testIgnoredEncoding, 'ipc', true, base64Options, execa);
test('Is ignored with other encodings and ["ipc"]', testIgnoredEncoding, ['ipc'], true, base64Options, execa);
test('Is ignored with other encodings and "inherit"', testIgnoredEncoding, 'inherit', true, base64Options, execa);
test('Is ignored with other encodings and ["inherit"]', testIgnoredEncoding, ['inherit'], true, base64Options, execa);
test('Is ignored with other encodings and 1', testIgnoredEncoding, 1, true, base64Options, execa);
test('Is ignored with other encodings and [1]', testIgnoredEncoding, [1], true, base64Options, execa);
test('Is ignored with other encodings and process.stdout', testIgnoredEncoding, process.stdout, true, base64Options, execa);
test('Is ignored with other encodings and [process.stdout]', testIgnoredEncoding, [process.stdout], true, base64Options, execa);
test('Is not ignored with other encodings and "pipe"', testIgnoredEncoding, 'pipe', false, base64Options, execa);
test('Is not ignored with other encodings and ["pipe"]', testIgnoredEncoding, ['pipe'], false, base64Options, execa);
test('Is not ignored with other encodings and "overlapped"', testIgnoredEncoding, 'overlapped', false, base64Options, execa);
test('Is not ignored with other encodings and ["overlapped"]', testIgnoredEncoding, ['overlapped'], false, base64Options, execa);
test('Is not ignored with other encodings and ["inherit", "pipe"]', testIgnoredEncoding, ['inherit', 'pipe'], false, base64Options, execa);
test('Is not ignored with other encodings and undefined', testIgnoredEncoding, undefined, false, base64Options, execa);
test('Is not ignored with other encodings and null', testIgnoredEncoding, null, false, base64Options, execa);
test('Is ignored with "lines: true" and "ignore"', testIgnoredEncoding, 'ignore', true, linesOptions, execa);
test('Is ignored with "lines: true" and ["ignore"]', testIgnoredEncoding, ['ignore'], true, linesOptions, execa);
test('Is ignored with "lines: true" and "ipc"', testIgnoredEncoding, 'ipc', true, linesOptions, execa);
test('Is ignored with "lines: true" and ["ipc"]', testIgnoredEncoding, ['ipc'], true, linesOptions, execa);
test('Is ignored with "lines: true" and "inherit"', testIgnoredEncoding, 'inherit', true, linesOptions, execa);
test('Is ignored with "lines: true" and ["inherit"]', testIgnoredEncoding, ['inherit'], true, linesOptions, execa);
test('Is ignored with "lines: true" and 1', testIgnoredEncoding, 1, true, linesOptions, execa);
test('Is ignored with "lines: true" and [1]', testIgnoredEncoding, [1], true, linesOptions, execa);
test('Is ignored with "lines: true" and process.stdout', testIgnoredEncoding, process.stdout, true, linesOptions, execa);
test('Is ignored with "lines: true" and [process.stdout]', testIgnoredEncoding, [process.stdout], true, linesOptions, execa);
test('Is not ignored with "lines: true" and "pipe"', testIgnoredEncoding, 'pipe', false, linesOptions, execa);
test('Is not ignored with "lines: true" and ["pipe"]', testIgnoredEncoding, ['pipe'], false, linesOptions, execa);
test('Is not ignored with "lines: true" and "overlapped"', testIgnoredEncoding, 'overlapped', false, linesOptions, execa);
test('Is not ignored with "lines: true" and ["overlapped"]', testIgnoredEncoding, ['overlapped'], false, linesOptions, execa);
test('Is not ignored with "lines: true" and ["inherit", "pipe"]', testIgnoredEncoding, ['inherit', 'pipe'], false, linesOptions, execa);
test('Is not ignored with "lines: true" and undefined', testIgnoredEncoding, undefined, false, linesOptions, execa);
test('Is not ignored with "lines: true" and null', testIgnoredEncoding, null, false, linesOptions, execa);
test('Is ignored with "lines: true", other encodings and "ignore"', testIgnoredEncoding, 'ignore', true, {...base64Options, ...linesOptions}, execa);
test('Is not ignored with "lines: true", other encodings and "pipe"', testIgnoredEncoding, 'pipe', false, {...base64Options, ...linesOptions}, execa);
test('Is ignored with other encodings and "ignore", sync', testIgnoredEncoding, 'ignore', true, base64Options, execaSync);
test('Is ignored with other encodings and ["ignore"], sync', testIgnoredEncoding, ['ignore'], true, base64Options, execaSync);
test('Is ignored with other encodings and "inherit", sync', testIgnoredEncoding, 'inherit', true, base64Options, execaSync);
test('Is ignored with other encodings and ["inherit"], sync', testIgnoredEncoding, ['inherit'], true, base64Options, execaSync);
test('Is ignored with other encodings and 1, sync', testIgnoredEncoding, 1, true, base64Options, execaSync);
test('Is ignored with other encodings and [1], sync', testIgnoredEncoding, [1], true, base64Options, execaSync);
test('Is ignored with other encodings and process.stdout, sync', testIgnoredEncoding, process.stdout, true, base64Options, execaSync);
test('Is ignored with other encodings and [process.stdout], sync', testIgnoredEncoding, [process.stdout], true, base64Options, execaSync);
test('Is not ignored with other encodings and "pipe", sync', testIgnoredEncoding, 'pipe', false, base64Options, execaSync);
test('Is not ignored with other encodings and ["pipe"], sync', testIgnoredEncoding, ['pipe'], false, base64Options, execaSync);
test('Is not ignored with other encodings and undefined, sync', testIgnoredEncoding, undefined, false, base64Options, execaSync);
test('Is not ignored with other encodings and null, sync', testIgnoredEncoding, null, false, base64Options, execaSync);
test('Is ignored with "lines: true" and "ignore", sync', testIgnoredEncoding, 'ignore', true, linesOptions, execaSync);
test('Is ignored with "lines: true" and ["ignore"], sync', testIgnoredEncoding, ['ignore'], true, linesOptions, execaSync);
test('Is ignored with "lines: true" and "inherit", sync', testIgnoredEncoding, 'inherit', true, linesOptions, execaSync);
test('Is ignored with "lines: true" and ["inherit"], sync', testIgnoredEncoding, ['inherit'], true, linesOptions, execaSync);
test('Is ignored with "lines: true" and 1, sync', testIgnoredEncoding, 1, true, linesOptions, execaSync);
test('Is ignored with "lines: true" and [1], sync', testIgnoredEncoding, [1], true, linesOptions, execaSync);
test('Is ignored with "lines: true" and process.stdout, sync', testIgnoredEncoding, process.stdout, true, linesOptions, execaSync);
test('Is ignored with "lines: true" and [process.stdout], sync', testIgnoredEncoding, [process.stdout], true, linesOptions, execaSync);
test('Is not ignored with "lines: true" and "pipe", sync', testIgnoredEncoding, 'pipe', false, linesOptions, execaSync);
test('Is not ignored with "lines: true" and ["pipe"], sync', testIgnoredEncoding, ['pipe'], false, linesOptions, execaSync);
test('Is not ignored with "lines: true" and undefined, sync', testIgnoredEncoding, undefined, false, linesOptions, execaSync);
test('Is not ignored with "lines: true" and null, sync', testIgnoredEncoding, null, false, linesOptions, execaSync);
test('Is ignored with "lines: true", other encodings and "ignore", sync', testIgnoredEncoding, 'ignore', true, {...base64Options, ...linesOptions}, execaSync);
test('Is not ignored with "lines: true", other encodings and "pipe", sync', testIgnoredEncoding, 'pipe', false, {...base64Options, ...linesOptions}, execaSync);
================================================
FILE: test/transform/encoding-multibyte.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {noopGenerator, getOutputsGenerator, addNoopGenerator} from '../helpers/generator.js';
import {
multibyteChar,
multibyteString,
multibyteUint8Array,
breakingLength,
brokenSymbol,
} from '../helpers/encoding.js';
setFixtureDirectory();
const foobarArray = ['fo', 'ob', 'ar', '..'];
const testMultibyteCharacters = async (t, objectMode, addNoopTransform, execaMethod) => {
const {stdout} = await execaMethod('noop.js', {
stdout: addNoopGenerator(getOutputsGenerator(foobarArray)(objectMode, true), addNoopTransform, objectMode),
encoding: 'base64',
});
if (objectMode) {
t.deepEqual(stdout, foobarArray);
} else {
t.is(stdout, Buffer.from(foobarArray.join('')).toString('base64'));
}
};
test('Handle multibyte characters', testMultibyteCharacters, false, false, execa);
test('Handle multibyte characters, noop transform', testMultibyteCharacters, false, true, execa);
test('Handle multibyte characters, with objectMode', testMultibyteCharacters, true, false, execa);
test('Handle multibyte characters, with objectMode, noop transform', testMultibyteCharacters, true, true, execa);
test('Handle multibyte characters, sync', testMultibyteCharacters, false, false, execaSync);
test('Handle multibyte characters, noop transform, sync', testMultibyteCharacters, false, true, execaSync);
test('Handle multibyte characters, with objectMode, sync', testMultibyteCharacters, true, false, execaSync);
test('Handle multibyte characters, with objectMode, noop transform, sync', testMultibyteCharacters, true, true, execaSync);
const testMultibyte = async (t, objectMode, execaMethod) => {
const {stdout} = await execaMethod('stdin.js', {
stdin: [
[multibyteUint8Array.slice(0, breakingLength), multibyteUint8Array.slice(breakingLength)],
noopGenerator(objectMode, true),
],
});
t.is(stdout, multibyteString);
};
test('Generator handles multibyte characters with Uint8Array', testMultibyte, false, execa);
test('Generator handles multibyte characters with Uint8Array, objectMode', testMultibyte, true, execa);
test('Generator handles multibyte characters with Uint8Array, sync', testMultibyte, false, execaSync);
test('Generator handles multibyte characters with Uint8Array, objectMode, sync', testMultibyte, true, execaSync);
const testMultibytePartial = async (t, objectMode, execaMethod) => {
const {stdout} = await execaMethod('stdin.js', {
stdin: [
[multibyteUint8Array.slice(0, breakingLength)],
noopGenerator(objectMode, true),
],
});
t.is(stdout, `${multibyteChar}${brokenSymbol}`);
};
test('Generator handles partial multibyte characters with Uint8Array', testMultibytePartial, false, execa);
test('Generator handles partial multibyte characters with Uint8Array, objectMode', testMultibytePartial, true, execa);
test('Generator handles partial multibyte characters with Uint8Array, sync', testMultibytePartial, false, execaSync);
test('Generator handles partial multibyte characters with Uint8Array, objectMode, sync', testMultibytePartial, true, execaSync);
const testMultibytePartialOutput = async (t, execaMethod) => {
const {stdout} = await execaMethod('noop.js', {
stdout: getOutputsGenerator([
multibyteUint8Array.slice(0, breakingLength),
multibyteUint8Array.slice(breakingLength),
])(false, true),
});
t.is(stdout, multibyteString);
};
test('Generator handles output multibyte characters with Uint8Array', testMultibytePartialOutput, execa);
test('Generator handles output multibyte characters with Uint8Array, sync', testMultibytePartialOutput, execaSync);
================================================
FILE: test/transform/encoding-transform.js
================================================
import {Buffer} from 'node:buffer';
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {getStdio} from '../helpers/stdio.js';
import {
foobarString,
foobarUint8Array,
foobarBuffer,
foobarObject,
} from '../helpers/input.js';
import {noopGenerator, getOutputGenerator} from '../helpers/generator.js';
setFixtureDirectory();
const getTypeofGenerator = lines => (objectMode, binary) => ({
* transform(line) {
lines.push(Object.prototype.toString.call(line));
yield '';
},
objectMode,
binary,
});
const assertTypeofChunk = (t, lines, expectedType) => {
t.deepEqual(lines, [`[object ${expectedType}]`]);
};
// eslint-disable-next-line max-params
const testGeneratorFirstEncoding = async (t, input, encoding, expectedType, objectMode, binary) => {
const lines = [];
const subprocess = execa('stdin.js', {stdin: getTypeofGenerator(lines)(objectMode, binary), encoding});
subprocess.stdin.end(input);
await subprocess;
assertTypeofChunk(t, lines, expectedType);
};
test('First generator argument is string with default encoding, with string writes', testGeneratorFirstEncoding, foobarString, 'utf8', 'String', false, undefined);
test('First generator argument is string with default encoding, with Buffer writes', testGeneratorFirstEncoding, foobarBuffer, 'utf8', 'String', false, undefined);
test('First generator argument is string with default encoding, with Uint8Array writes', testGeneratorFirstEncoding, foobarUint8Array, 'utf8', 'String', false, undefined);
test('First generator argument is string with default encoding, with string writes, "binary: false"', testGeneratorFirstEncoding, foobarString, 'utf8', 'String', false, false);
test('First generator argument is Uint8Array with default encoding, with string writes, "binary: true"', testGeneratorFirstEncoding, foobarString, 'utf8', 'Uint8Array', false, true);
test('First generator argument is string with encoding "utf16le", with string writes', testGeneratorFirstEncoding, foobarString, 'utf16le', 'String', false, undefined);
test('First generator argument is string with encoding "utf16le", with Buffer writes', testGeneratorFirstEncoding, foobarBuffer, 'utf16le', 'String', false, undefined);
test('First generator argument is string with encoding "utf16le", with Uint8Array writes', testGeneratorFirstEncoding, foobarUint8Array, 'utf16le', 'String', false, undefined);
test('First generator argument is string with encoding "utf16le", with string writes, "binary: false"', testGeneratorFirstEncoding, foobarString, 'utf16le', 'String', false, false);
test('First generator argument is Uint8Array with encoding "utf16le", with string writes, "binary: true"', testGeneratorFirstEncoding, foobarString, 'utf16le', 'Uint8Array', false, true);
test('First generator argument is Uint8Array with encoding "buffer", with string writes', testGeneratorFirstEncoding, foobarString, 'buffer', 'Uint8Array', false, undefined);
test('First generator argument is Uint8Array with encoding "buffer", with Buffer writes', testGeneratorFirstEncoding, foobarBuffer, 'buffer', 'Uint8Array', false, undefined);
test('First generator argument is Uint8Array with encoding "buffer", with Uint8Array writes', testGeneratorFirstEncoding, foobarUint8Array, 'buffer', 'Uint8Array', false, undefined);
test('First generator argument is Uint8Array with encoding "buffer", with string writes, "binary: false"', testGeneratorFirstEncoding, foobarString, 'buffer', 'Uint8Array', false, false);
test('First generator argument is Uint8Array with encoding "buffer", with string writes, "binary: true"', testGeneratorFirstEncoding, foobarString, 'buffer', 'Uint8Array', false, true);
test('First generator argument is Uint8Array with encoding "hex", with string writes', testGeneratorFirstEncoding, foobarString, 'hex', 'Uint8Array', false, undefined);
test('First generator argument is Uint8Array with encoding "hex", with Buffer writes', testGeneratorFirstEncoding, foobarBuffer, 'hex', 'Uint8Array', false, undefined);
test('First generator argument is Uint8Array with encoding "hex", with Uint8Array writes', testGeneratorFirstEncoding, foobarUint8Array, 'hex', 'Uint8Array', false, undefined);
test('First generator argument is Uint8Array with encoding "hex", with string writes, "binary: false"', testGeneratorFirstEncoding, foobarString, 'hex', 'Uint8Array', false, false);
test('First generator argument is Uint8Array with encoding "hex", with string writes, "binary: true"', testGeneratorFirstEncoding, foobarString, 'hex', 'Uint8Array', false, true);
test('First generator argument can be string with objectMode', testGeneratorFirstEncoding, foobarString, 'utf8', 'String', true, undefined);
test('First generator argument can be string with objectMode, "binary: false"', testGeneratorFirstEncoding, foobarString, 'utf8', 'String', true, false);
test('First generator argument can be string with objectMode, "binary: true"', testGeneratorFirstEncoding, foobarString, 'utf8', 'String', true, true);
test('First generator argument can be objects with objectMode', testGeneratorFirstEncoding, foobarObject, 'utf8', 'Object', true, undefined);
test('First generator argument can be objects with objectMode, "binary: false"', testGeneratorFirstEncoding, foobarObject, 'utf8', 'Object', true, false);
test('First generator argument can be objects with objectMode, "binary: true"', testGeneratorFirstEncoding, foobarObject, 'utf8', 'Object', true, true);
// eslint-disable-next-line max-params
const testGeneratorFirstEncodingSync = (t, input, encoding, expectedType, objectMode, binary) => {
const lines = [];
execaSync('stdin.js', {stdin: [[input], getTypeofGenerator(lines)(objectMode, binary)], encoding});
assertTypeofChunk(t, lines, expectedType);
};
test('First generator argument is string with default encoding, with string writes, sync', testGeneratorFirstEncodingSync, foobarString, 'utf8', 'String', false, undefined);
test('First generator argument is string with default encoding, with Uint8Array writes, sync', testGeneratorFirstEncodingSync, foobarUint8Array, 'utf8', 'String', false, undefined);
test('First generator argument is string with default encoding, with string writes, "binary: false", sync', testGeneratorFirstEncodingSync, foobarString, 'utf8', 'String', false, false);
test('First generator argument is Uint8Array with default encoding, with string writes, "binary: true", sync', testGeneratorFirstEncodingSync, foobarString, 'utf8', 'Uint8Array', false, true);
test('First generator argument is string with encoding "utf16le", with string writes, sync', testGeneratorFirstEncodingSync, foobarString, 'utf16le', 'String', false, undefined);
test('First generator argument is string with encoding "utf16le", with Uint8Array writes, sync', testGeneratorFirstEncodingSync, foobarUint8Array, 'utf16le', 'String', false, undefined);
test('First generator argument is string with encoding "utf16le", with string writes, "binary: false", sync', testGeneratorFirstEncodingSync, foobarString, 'utf16le', 'String', false, false);
test('First generator argument is Uint8Array with encoding "utf16le", with string writes, "binary: true", sync', testGeneratorFirstEncodingSync, foobarString, 'utf16le', 'Uint8Array', false, true);
test('First generator argument is Uint8Array with encoding "buffer", with string writes, sync', testGeneratorFirstEncodingSync, foobarString, 'buffer', 'Uint8Array', false, undefined);
test('First generator argument is Uint8Array with encoding "buffer", with Uint8Array writes, sync', testGeneratorFirstEncodingSync, foobarUint8Array, 'buffer', 'Uint8Array', false, undefined);
test('First generator argument is Uint8Array with encoding "buffer", with string writes, "binary: false", sync', testGeneratorFirstEncodingSync, foobarString, 'buffer', 'Uint8Array', false, false);
test('First generator argument is Uint8Array with encoding "buffer", with string writes, "binary: true", sync', testGeneratorFirstEncodingSync, foobarString, 'buffer', 'Uint8Array', false, true);
test('First generator argument is Uint8Array with encoding "hex", with string writes, sync', testGeneratorFirstEncodingSync, foobarString, 'hex', 'Uint8Array', false, undefined);
test('First generator argument is Uint8Array with encoding "hex", with Uint8Array writes, sync', testGeneratorFirstEncodingSync, foobarUint8Array, 'hex', 'Uint8Array', false, undefined);
test('First generator argument is Uint8Array with encoding "hex", with string writes, "binary: false", sync', testGeneratorFirstEncodingSync, foobarString, 'hex', 'Uint8Array', false, false);
test('First generator argument is Uint8Array with encoding "hex", with string writes, "binary: true", sync', testGeneratorFirstEncodingSync, foobarString, 'hex', 'Uint8Array', false, true);
test('First generator argument can be string with objectMode, sync', testGeneratorFirstEncodingSync, foobarString, 'utf8', 'String', true, undefined);
test('First generator argument can be string with objectMode, "binary: false", sync', testGeneratorFirstEncodingSync, foobarString, 'utf8', 'String', true, false);
test('First generator argument can be string with objectMode, "binary: true", sync', testGeneratorFirstEncodingSync, foobarString, 'utf8', 'String', true, true);
test('First generator argument can be objects with objectMode, sync', testGeneratorFirstEncodingSync, foobarObject, 'utf8', 'Object', true, undefined);
test('First generator argument can be objects with objectMode, "binary: false", sync', testGeneratorFirstEncodingSync, foobarObject, 'utf8', 'Object', true, false);
test('First generator argument can be objects with objectMode, "binary: true", sync', testGeneratorFirstEncodingSync, foobarObject, 'utf8', 'Object', true, true);
const testEncodingIgnored = async (t, encoding) => {
const input = Buffer.from(foobarString).toString(encoding);
const subprocess = execa('stdin.js', {stdin: noopGenerator(true)});
subprocess.stdin.end(input, encoding);
const {stdout} = await subprocess;
t.is(stdout, input);
};
test('Write call encoding "utf8" is ignored with objectMode', testEncodingIgnored, 'utf8');
test('Write call encoding "utf16le" is ignored with objectMode', testEncodingIgnored, 'utf16le');
test('Write call encoding "hex" is ignored with objectMode', testEncodingIgnored, 'hex');
test('Write call encoding "base64" is ignored with objectMode', testEncodingIgnored, 'base64');
// eslint-disable-next-line max-params
const testGeneratorNextEncoding = async (t, input, encoding, firstObjectMode, secondObjectMode, expectedType, execaMethod) => {
const lines = [];
await execaMethod('noop.js', ['other'], {
stdout: [
getOutputGenerator(input)(firstObjectMode),
getTypeofGenerator(lines)(secondObjectMode),
],
encoding,
});
assertTypeofChunk(t, lines, expectedType);
};
test('Next generator argument is string with default encoding, with string writes', testGeneratorNextEncoding, foobarString, 'utf8', false, false, 'String', execa);
test('Next generator argument is string with default encoding, with string writes, objectMode first', testGeneratorNextEncoding, foobarString, 'utf8', true, false, 'String', execa);
test('Next generator argument is string with default encoding, with string writes, objectMode both', testGeneratorNextEncoding, foobarString, 'utf8', true, true, 'String', execa);
test('Next generator argument is string with default encoding, with Uint8Array writes', testGeneratorNextEncoding, foobarUint8Array, 'utf8', false, false, 'String', execa);
test('Next generator argument is Uint8Array with default encoding, with Uint8Array writes, objectMode first', testGeneratorNextEncoding, foobarUint8Array, 'utf8', true, false, 'Uint8Array', execa);
test('Next generator argument is string with default encoding, with Uint8Array writes, objectMode both', testGeneratorNextEncoding, foobarUint8Array, 'utf8', true, true, 'Uint8Array', execa);
test('Next generator argument is string with encoding "utf16le", with string writes', testGeneratorNextEncoding, foobarString, 'utf16le', false, false, 'String', execa);
test('Next generator argument is string with encoding "utf16le",, with string writes, objectMode first', testGeneratorNextEncoding, foobarString, 'utf16le', true, false, 'String', execa);
test('Next generator argument is string with encoding "utf16le",, with string writes, objectMode both', testGeneratorNextEncoding, foobarString, 'utf16le', true, true, 'String', execa);
test('Next generator argument is string with encoding "utf16le",, with Uint8Array writes', testGeneratorNextEncoding, foobarUint8Array, 'utf16le', false, false, 'String', execa);
test('Next generator argument is Uint8Array with encoding "utf16le",, with Uint8Array writes, objectMode first', testGeneratorNextEncoding, foobarUint8Array, 'utf16le', true, false, 'Uint8Array', execa);
test('Next generator argument is string with encoding "utf16le",, with Uint8Array writes, objectMode both', testGeneratorNextEncoding, foobarUint8Array, 'utf16le', true, true, 'Uint8Array', execa);
test('Next generator argument is Uint8Array with encoding "buffer", with string writes', testGeneratorNextEncoding, foobarString, 'buffer', false, false, 'Uint8Array', execa);
test('Next generator argument is string with encoding "buffer", with string writes, objectMode first', testGeneratorNextEncoding, foobarString, 'buffer', true, false, 'String', execa);
test('Next generator argument is string with encoding "buffer", with string writes, objectMode both', testGeneratorNextEncoding, foobarString, 'buffer', true, true, 'String', execa);
test('Next generator argument is Uint8Array with encoding "buffer", with Uint8Array writes', testGeneratorNextEncoding, foobarUint8Array, 'buffer', false, false, 'Uint8Array', execa);
test('Next generator argument is Uint8Array with encoding "buffer", with Uint8Array writes, objectMode first', testGeneratorNextEncoding, foobarUint8Array, 'buffer', true, false, 'Uint8Array', execa);
test('Next generator argument is Uint8Array with encoding "buffer", with Uint8Array writes, objectMode both', testGeneratorNextEncoding, foobarUint8Array, 'buffer', true, true, 'Uint8Array', execa);
test('Next generator argument is Uint8Array with encoding "hex", with string writes', testGeneratorNextEncoding, foobarString, 'hex', false, false, 'Uint8Array', execa);
test('Next generator argument is Uint8Array with encoding "hex", with Uint8Array writes', testGeneratorNextEncoding, foobarUint8Array, 'hex', false, false, 'Uint8Array', execa);
test('Next generator argument is object with default encoding, with object writes, objectMode first', testGeneratorNextEncoding, foobarObject, 'utf8', true, false, 'Object', execa);
test('Next generator argument is object with default encoding, with object writes, objectMode both', testGeneratorNextEncoding, foobarObject, 'utf8', true, true, 'Object', execa);
test('Next generator argument is string with default encoding, with string writes, sync', testGeneratorNextEncoding, foobarString, 'utf8', false, false, 'String', execaSync);
test('Next generator argument is string with default encoding, with string writes, objectMode first, sync', testGeneratorNextEncoding, foobarString, 'utf8', true, false, 'String', execaSync);
test('Next generator argument is string with default encoding, with string writes, objectMode both, sync', testGeneratorNextEncoding, foobarString, 'utf8', true, true, 'String', execaSync);
test('Next generator argument is string with default encoding, with Uint8Array writes, sync', testGeneratorNextEncoding, foobarUint8Array, 'utf8', false, false, 'String', execaSync);
test('Next generator argument is Uint8Array with default encoding, with Uint8Array writes, objectMode first, sync', testGeneratorNextEncoding, foobarUint8Array, 'utf8', true, false, 'Uint8Array', execaSync);
test('Next generator argument is string with default encoding, with Uint8Array writes, objectMode both, sync', testGeneratorNextEncoding, foobarUint8Array, 'utf8', true, true, 'Uint8Array', execaSync);
test('Next generator argument is string with encoding "utf16le", with string writes, sync', testGeneratorNextEncoding, foobarString, 'utf16le', false, false, 'String', execaSync);
test('Next generator argument is string with encoding "utf16le",, with string writes, objectMode first, sync', testGeneratorNextEncoding, foobarString, 'utf16le', true, false, 'String', execaSync);
test('Next generator argument is string with encoding "utf16le",, with string writes, objectMode both, sync', testGeneratorNextEncoding, foobarString, 'utf16le', true, true, 'String', execaSync);
test('Next generator argument is string with encoding "utf16le",, with Uint8Array writes, sync', testGeneratorNextEncoding, foobarUint8Array, 'utf16le', false, false, 'String', execaSync);
test('Next generator argument is Uint8Array with encoding "utf16le",, with Uint8Array writes, objectMode first, sync', testGeneratorNextEncoding, foobarUint8Array, 'utf16le', true, false, 'Uint8Array', execaSync);
test('Next generator argument is string with encoding "utf16le",, with Uint8Array writes, objectMode both, sync', testGeneratorNextEncoding, foobarUint8Array, 'utf16le', true, true, 'Uint8Array', execaSync);
test('Next generator argument is Uint8Array with encoding "buffer", with string writes, sync', testGeneratorNextEncoding, foobarString, 'buffer', false, false, 'Uint8Array', execaSync);
test('Next generator argument is string with encoding "buffer", with string writes, objectMode first, sync', testGeneratorNextEncoding, foobarString, 'buffer', true, false, 'String', execaSync);
test('Next generator argument is string with encoding "buffer", with string writes, objectMode both, sync', testGeneratorNextEncoding, foobarString, 'buffer', true, true, 'String', execaSync);
test('Next generator argument is Uint8Array with encoding "buffer", with Uint8Array writes, sync', testGeneratorNextEncoding, foobarUint8Array, 'buffer', false, false, 'Uint8Array', execaSync);
test('Next generator argument is Uint8Array with encoding "buffer", with Uint8Array writes, objectMode first, sync', testGeneratorNextEncoding, foobarUint8Array, 'buffer', true, false, 'Uint8Array', execaSync);
test('Next generator argument is Uint8Array with encoding "buffer", with Uint8Array writes, objectMode both, sync', testGeneratorNextEncoding, foobarUint8Array, 'buffer', true, true, 'Uint8Array', execaSync);
test('Next generator argument is Uint8Array with encoding "hex", with string writes, sync', testGeneratorNextEncoding, foobarString, 'hex', false, false, 'Uint8Array', execaSync);
test('Next generator argument is Uint8Array with encoding "hex", with Uint8Array writes, sync', testGeneratorNextEncoding, foobarUint8Array, 'hex', false, false, 'Uint8Array', execaSync);
test('Next generator argument is object with default encoding, with object writes, objectMode first, sync', testGeneratorNextEncoding, foobarObject, 'utf8', true, false, 'Object', execaSync);
test('Next generator argument is object with default encoding, with object writes, objectMode both, sync', testGeneratorNextEncoding, foobarObject, 'utf8', true, true, 'Object', execaSync);
const testFirstOutputGeneratorArgument = async (t, fdNumber, execaMethod) => {
const lines = [];
await execaMethod('noop-fd.js', [`${fdNumber}`], getStdio(fdNumber, getTypeofGenerator(lines)(true)));
assertTypeofChunk(t, lines, 'String');
};
test('The first generator with result.stdout does not receive an object argument even in objectMode', testFirstOutputGeneratorArgument, 1, execa);
test('The first generator with result.stderr does not receive an object argument even in objectMode', testFirstOutputGeneratorArgument, 2, execa);
test('The first generator with result.stdio[*] does not receive an object argument even in objectMode', testFirstOutputGeneratorArgument, 3, execa);
test('The first generator with result.stdout does not receive an object argument even in objectMode, sync', testFirstOutputGeneratorArgument, 1, execaSync);
test('The first generator with result.stderr does not receive an object argument even in objectMode, sync', testFirstOutputGeneratorArgument, 2, execaSync);
test('The first generator with result.stdio[*] does not receive an object argument even in objectMode, sync', testFirstOutputGeneratorArgument, 3, execaSync);
================================================
FILE: test/transform/generator-all.js
================================================
import {Buffer} from 'node:buffer';
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarObject} from '../helpers/input.js';
import {
outputObjectGenerator,
uppercaseGenerator,
uppercaseBufferGenerator,
} from '../helpers/generator.js';
setFixtureDirectory();
const textEncoder = new TextEncoder();
const getAllStdioOption = (stdioOption, encoding, objectMode) => {
if (stdioOption) {
return 'pipe';
}
if (objectMode) {
return outputObjectGenerator();
}
return encoding === 'utf8' ? uppercaseGenerator() : uppercaseBufferGenerator();
};
const getStdoutStderrOutput = ({output, stdioOption, encoding, objectMode, lines}) => {
if (objectMode && !stdioOption) {
return encoding === 'utf8' ? [foobarObject, foobarObject] : [foobarObject];
}
const stdioOutput = stdioOption ? output : output.toUpperCase();
if (encoding === 'hex') {
return Buffer.from(stdioOutput).toString('hex');
}
if (encoding === 'buffer') {
return textEncoder.encode(stdioOutput);
}
return lines ? stdioOutput.trim().split('\n').map(string => `${string}\n`) : stdioOutput;
};
const getAllOutput = ({stdoutOutput, stderrOutput, encoding, objectMode, lines}) => {
if (objectMode || (lines && encoding === 'utf8')) {
return [stdoutOutput, stderrOutput].flat();
}
return encoding === 'buffer'
? new Uint8Array([...stdoutOutput, ...stderrOutput])
: `${stdoutOutput}${stderrOutput}`;
};
// eslint-disable-next-line max-params
const testGeneratorAll = async (t, reject, encoding, objectMode, stdoutOption, stderrOption, lines, execaMethod) => {
const fixtureName = reject ? 'all.js' : 'all-fail.js';
const {stdout, stderr, all} = await execaMethod(fixtureName, {
all: true,
reject,
stdout: getAllStdioOption(stdoutOption, encoding, objectMode),
stderr: getAllStdioOption(stderrOption, encoding, objectMode),
encoding,
lines,
stripFinalNewline: false,
});
const stdoutOutput = getStdoutStderrOutput({
output: 'std\nout\n',
stdioOption: stdoutOption,
encoding,
objectMode,
lines,
});
t.deepEqual(stdout, stdoutOutput);
const stderrOutput = getStdoutStderrOutput({
output: 'std\nerr\n',
stdioOption: stderrOption,
encoding,
objectMode,
lines,
});
t.deepEqual(stderr, stderrOutput);
const allOutput = getAllOutput({
stdoutOutput,
stderrOutput,
encoding,
objectMode,
lines,
});
if (Array.isArray(all) && Array.isArray(allOutput)) {
t.deepEqual([...all].sort(), [...allOutput].sort());
} else {
t.deepEqual(all, allOutput);
}
};
test('Can use generators with result.all = transform + transform', testGeneratorAll, true, 'utf8', false, false, false, false, execa);
test('Can use generators with error.all = transform + transform', testGeneratorAll, false, 'utf8', false, false, false, false, execa);
test('Can use generators with result.all = transform + transform, encoding "buffer"', testGeneratorAll, true, 'buffer', false, false, false, false, execa);
test('Can use generators with error.all = transform + transform, encoding "buffer"', testGeneratorAll, false, 'buffer', false, false, false, false, execa);
test('Can use generators with result.all = transform + transform, encoding "hex"', testGeneratorAll, true, 'hex', false, false, false, false, execa);
test('Can use generators with error.all = transform + transform, encoding "hex"', testGeneratorAll, false, 'hex', false, false, false, false, execa);
test('Can use generators with result.all = transform + pipe', testGeneratorAll, true, 'utf8', false, false, true, false, execa);
test('Can use generators with error.all = transform + pipe', testGeneratorAll, false, 'utf8', false, false, true, false, execa);
test('Can use generators with result.all = transform + pipe, encoding "buffer"', testGeneratorAll, true, 'buffer', false, false, true, false, execa);
test('Can use generators with error.all = transform + pipe, encoding "buffer"', testGeneratorAll, false, 'buffer', false, false, true, false, execa);
test('Can use generators with result.all = transform + pipe, encoding "hex"', testGeneratorAll, true, 'hex', false, false, true, false, execa);
test('Can use generators with error.all = transform + pipe, encoding "hex"', testGeneratorAll, false, 'hex', false, false, true, false, execa);
test('Can use generators with result.all = pipe + transform', testGeneratorAll, true, 'utf8', false, true, false, false, execa);
test('Can use generators with error.all = pipe + transform', testGeneratorAll, false, 'utf8', false, true, false, false, execa);
test('Can use generators with result.all = pipe + transform, encoding "buffer"', testGeneratorAll, true, 'buffer', false, true, false, false, execa);
test('Can use generators with error.all = pipe + transform, encoding "buffer"', testGeneratorAll, false, 'buffer', false, true, false, false, execa);
test('Can use generators with result.all = pipe + transform, encoding "hex"', testGeneratorAll, true, 'hex', false, true, false, false, execa);
test('Can use generators with error.all = pipe + transform, encoding "hex"', testGeneratorAll, false, 'hex', false, true, false, false, execa);
test('Can use generators with result.all = transform + transform, objectMode', testGeneratorAll, true, 'utf8', true, false, false, false, execa);
test('Can use generators with error.all = transform + transform, objectMode', testGeneratorAll, false, 'utf8', true, false, false, false, execa);
test('Can use generators with result.all = transform + transform, objectMode, encoding "buffer"', testGeneratorAll, true, 'buffer', true, false, false, false, execa);
test('Can use generators with error.all = transform + transform, objectMode, encoding "buffer"', testGeneratorAll, false, 'buffer', true, false, false, false, execa);
test('Can use generators with result.all = transform + transform, objectMode, encoding "hex"', testGeneratorAll, true, 'hex', true, false, false, false, execa);
test('Can use generators with error.all = transform + transform, objectMode, encoding "hex"', testGeneratorAll, false, 'hex', true, false, false, false, execa);
test('Can use generators with result.all = transform + pipe, objectMode', testGeneratorAll, true, 'utf8', true, false, true, false, execa);
test('Can use generators with error.all = transform + pipe, objectMode', testGeneratorAll, false, 'utf8', true, false, true, false, execa);
test('Can use generators with result.all = transform + pipe, objectMode, encoding "buffer"', testGeneratorAll, true, 'buffer', true, false, true, false, execa);
test('Can use generators with error.all = transform + pipe, objectMode, encoding "buffer"', testGeneratorAll, false, 'buffer', true, false, true, false, execa);
test('Can use generators with result.all = transform + pipe, objectMode, encoding "hex"', testGeneratorAll, true, 'hex', true, false, true, false, execa);
test('Can use generators with error.all = transform + pipe, objectMode, encoding "hex"', testGeneratorAll, false, 'hex', true, false, true, false, execa);
test('Can use generators with result.all = pipe + transform, objectMode', testGeneratorAll, true, 'utf8', true, true, false, false, execa);
test('Can use generators with error.all = pipe + transform, objectMode', testGeneratorAll, false, 'utf8', true, true, false, false, execa);
test('Can use generators with result.all = pipe + transform, objectMode, encoding "buffer"', testGeneratorAll, true, 'buffer', true, true, false, false, execa);
test('Can use generators with error.all = pipe + transform, objectMode, encoding "buffer"', testGeneratorAll, false, 'buffer', true, true, false, false, execa);
test('Can use generators with result.all = pipe + transform, objectMode, encoding "hex"', testGeneratorAll, true, 'hex', true, true, false, false, execa);
test('Can use generators with error.all = pipe + transform, objectMode, encoding "hex"', testGeneratorAll, false, 'hex', true, true, false, false, execa);
test('Can use generators with result.all = transform + transform, sync', testGeneratorAll, true, 'utf8', false, false, false, false, execaSync);
test('Can use generators with error.all = transform + transform, sync', testGeneratorAll, false, 'utf8', false, false, false, false, execaSync);
test('Can use generators with result.all = transform + transform, encoding "buffer", sync', testGeneratorAll, true, 'buffer', false, false, false, false, execaSync);
test('Can use generators with error.all = transform + transform, encoding "buffer", sync', testGeneratorAll, false, 'buffer', false, false, false, false, execaSync);
test('Can use generators with result.all = transform + transform, encoding "hex", sync', testGeneratorAll, true, 'hex', false, false, false, false, execaSync);
test('Can use generators with error.all = transform + transform, encoding "hex", sync', testGeneratorAll, false, 'hex', false, false, false, false, execaSync);
test('Can use generators with result.all = transform + pipe, sync', testGeneratorAll, true, 'utf8', false, false, true, false, execaSync);
test('Can use generators with error.all = transform + pipe, sync', testGeneratorAll, false, 'utf8', false, false, true, false, execaSync);
test('Can use generators with result.all = transform + pipe, encoding "buffer", sync', testGeneratorAll, true, 'buffer', false, false, true, false, execaSync);
test('Can use generators with error.all = transform + pipe, encoding "buffer", sync', testGeneratorAll, false, 'buffer', false, false, true, false, execaSync);
test('Can use generators with result.all = transform + pipe, encoding "hex", sync', testGeneratorAll, true, 'hex', false, false, true, false, execaSync);
test('Can use generators with error.all = transform + pipe, encoding "hex", sync', testGeneratorAll, false, 'hex', false, false, true, false, execaSync);
test('Can use generators with result.all = pipe + transform, sync', testGeneratorAll, true, 'utf8', false, true, false, false, execaSync);
test('Can use generators with error.all = pipe + transform, sync', testGeneratorAll, false, 'utf8', false, true, false, false, execaSync);
test('Can use generators with result.all = pipe + transform, encoding "buffer", sync', testGeneratorAll, true, 'buffer', false, true, false, false, execaSync);
test('Can use generators with error.all = pipe + transform, encoding "buffer", sync', testGeneratorAll, false, 'buffer', false, true, false, false, execaSync);
test('Can use generators with result.all = pipe + transform, encoding "hex", sync', testGeneratorAll, true, 'hex', false, true, false, false, execaSync);
test('Can use generators with error.all = pipe + transform, encoding "hex", sync', testGeneratorAll, false, 'hex', false, true, false, false, execaSync);
test('Can use generators with result.all = transform + transform, objectMode, sync', testGeneratorAll, true, 'utf8', true, false, false, false, execaSync);
test('Can use generators with error.all = transform + transform, objectMode, sync', testGeneratorAll, false, 'utf8', true, false, false, false, execaSync);
test('Can use generators with result.all = transform + transform, objectMode, encoding "buffer", sync', testGeneratorAll, true, 'buffer', true, false, false, false, execaSync);
test('Can use generators with error.all = transform + transform, objectMode, encoding "buffer", sync', testGeneratorAll, false, 'buffer', true, false, false, false, execaSync);
test('Can use generators with result.all = transform + transform, objectMode, encoding "hex", sync', testGeneratorAll, true, 'hex', true, false, false, false, execaSync);
test('Can use generators with error.all = transform + transform, objectMode, encoding "hex", sync', testGeneratorAll, false, 'hex', true, false, false, false, execaSync);
test('Can use generators with result.all = transform + pipe, objectMode, sync', testGeneratorAll, true, 'utf8', true, false, true, false, execaSync);
test('Can use generators with error.all = transform + pipe, objectMode, sync', testGeneratorAll, false, 'utf8', true, false, true, false, execaSync);
test('Can use generators with result.all = transform + pipe, objectMode, encoding "buffer", sync', testGeneratorAll, true, 'buffer', true, false, true, false, execaSync);
test('Can use generators with error.all = transform + pipe, objectMode, encoding "buffer", sync', testGeneratorAll, false, 'buffer', true, false, true, false, execaSync);
test('Can use generators with result.all = transform + pipe, objectMode, encoding "hex", sync', testGeneratorAll, true, 'hex', true, false, true, false, execaSync);
test('Can use generators with error.all = transform + pipe, objectMode, encoding "hex", sync', testGeneratorAll, false, 'hex', true, false, true, false, execaSync);
test('Can use generators with result.all = pipe + transform, objectMode, sync', testGeneratorAll, true, 'utf8', true, true, false, false, execaSync);
test('Can use generators with error.all = pipe + transform, objectMode, sync', testGeneratorAll, false, 'utf8', true, true, false, false, execaSync);
test('Can use generators with result.all = pipe + transform, objectMode, encoding "buffer", sync', testGeneratorAll, true, 'buffer', true, true, false, false, execaSync);
test('Can use generators with error.all = pipe + transform, objectMode, encoding "buffer", sync', testGeneratorAll, false, 'buffer', true, true, false, false, execaSync);
test('Can use generators with result.all = pipe + transform, objectMode, encoding "hex", sync', testGeneratorAll, true, 'hex', true, true, false, false, execaSync);
test('Can use generators with error.all = pipe + transform, objectMode, encoding "hex", sync', testGeneratorAll, false, 'hex', true, true, false, false, execaSync);
test('Can use generators with result.all = transform + transform, lines', testGeneratorAll, true, 'utf8', false, false, false, true, execa);
test('Can use generators with error.all = transform + transform, lines', testGeneratorAll, false, 'utf8', false, false, false, true, execa);
test('Can use generators with result.all = transform + transform, encoding "buffer", lines', testGeneratorAll, true, 'buffer', false, false, false, true, execa);
test('Can use generators with error.all = transform + transform, encoding "buffer", lines', testGeneratorAll, false, 'buffer', false, false, false, true, execa);
test('Can use generators with result.all = transform + transform, encoding "hex", lines', testGeneratorAll, true, 'hex', false, false, false, true, execa);
test('Can use generators with error.all = transform + transform, encoding "hex", lines', testGeneratorAll, false, 'hex', false, false, false, true, execa);
test('Can use generators with result.all = transform + pipe, lines', testGeneratorAll, true, 'utf8', false, false, true, true, execa);
test('Can use generators with error.all = transform + pipe, lines', testGeneratorAll, false, 'utf8', false, false, true, true, execa);
test('Can use generators with result.all = transform + pipe, encoding "buffer", lines', testGeneratorAll, true, 'buffer', false, false, true, true, execa);
test('Can use generators with error.all = transform + pipe, encoding "buffer", lines', testGeneratorAll, false, 'buffer', false, false, true, true, execa);
test('Can use generators with result.all = transform + pipe, encoding "hex", lines', testGeneratorAll, true, 'hex', false, false, true, true, execa);
test('Can use generators with error.all = transform + pipe, encoding "hex", lines', testGeneratorAll, false, 'hex', false, false, true, true, execa);
test('Can use generators with result.all = pipe + transform, lines', testGeneratorAll, true, 'utf8', false, true, false, true, execa);
test('Can use generators with error.all = pipe + transform, lines', testGeneratorAll, false, 'utf8', false, true, false, true, execa);
test('Can use generators with result.all = pipe + transform, encoding "buffer", lines', testGeneratorAll, true, 'buffer', false, true, false, true, execa);
test('Can use generators with error.all = pipe + transform, encoding "buffer", lines', testGeneratorAll, false, 'buffer', false, true, false, true, execa);
test('Can use generators with result.all = pipe + transform, encoding "hex", lines', testGeneratorAll, true, 'hex', false, true, false, true, execa);
test('Can use generators with error.all = pipe + transform, encoding "hex", lines', testGeneratorAll, false, 'hex', false, true, false, true, execa);
test('Can use generators with result.all = transform + transform, objectMode, lines', testGeneratorAll, true, 'utf8', true, false, false, true, execa);
test('Can use generators with error.all = transform + transform, objectMode, lines', testGeneratorAll, false, 'utf8', true, false, false, true, execa);
test('Can use generators with result.all = transform + transform, objectMode, encoding "buffer", lines', testGeneratorAll, true, 'buffer', true, false, false, true, execa);
test('Can use generators with error.all = transform + transform, objectMode, encoding "buffer", lines', testGeneratorAll, false, 'buffer', true, false, false, true, execa);
test('Can use generators with result.all = transform + transform, objectMode, encoding "hex", lines', testGeneratorAll, true, 'hex', true, false, false, true, execa);
test('Can use generators with error.all = transform + transform, objectMode, encoding "hex", lines', testGeneratorAll, false, 'hex', true, false, false, true, execa);
test('Can use generators with result.all = transform + pipe, objectMode, lines', testGeneratorAll, true, 'utf8', true, false, true, true, execa);
test('Can use generators with error.all = transform + pipe, objectMode, lines', testGeneratorAll, false, 'utf8', true, false, true, true, execa);
test('Can use generators with result.all = transform + pipe, objectMode, encoding "buffer", lines', testGeneratorAll, true, 'buffer', true, false, true, true, execa);
test('Can use generators with error.all = transform + pipe, objectMode, encoding "buffer", lines', testGeneratorAll, false, 'buffer', true, false, true, true, execa);
test('Can use generators with result.all = transform + pipe, objectMode, encoding "hex", lines', testGeneratorAll, true, 'hex', true, false, true, true, execa);
test('Can use generators with error.all = transform + pipe, objectMode, encoding "hex", lines', testGeneratorAll, false, 'hex', true, false, true, true, execa);
test('Can use generators with result.all = pipe + transform, objectMode, lines', testGeneratorAll, true, 'utf8', true, true, false, true, execa);
test('Can use generators with error.all = pipe + transform, objectMode, lines', testGeneratorAll, false, 'utf8', true, true, false, true, execa);
test('Can use generators with result.all = pipe + transform, objectMode, encoding "buffer", lines', testGeneratorAll, true, 'buffer', true, true, false, true, execa);
test('Can use generators with error.all = pipe + transform, objectMode, encoding "buffer", lines', testGeneratorAll, false, 'buffer', true, true, false, true, execa);
test('Can use generators with result.all = pipe + transform, objectMode, encoding "hex", lines', testGeneratorAll, true, 'hex', true, true, false, true, execa);
test('Can use generators with error.all = pipe + transform, objectMode, encoding "hex", lines', testGeneratorAll, false, 'hex', true, true, false, true, execa);
test('Can use generators with result.all = transform + transform, sync, lines', testGeneratorAll, true, 'utf8', false, false, false, true, execaSync);
test('Can use generators with error.all = transform + transform, sync, lines', testGeneratorAll, false, 'utf8', false, false, false, true, execaSync);
test('Can use generators with result.all = transform + transform, encoding "buffer", sync, lines', testGeneratorAll, true, 'buffer', false, false, false, true, execaSync);
test('Can use generators with error.all = transform + transform, encoding "buffer", sync, lines', testGeneratorAll, false, 'buffer', false, false, false, true, execaSync);
test('Can use generators with result.all = transform + transform, encoding "hex", sync, lines', testGeneratorAll, true, 'hex', false, false, false, true, execaSync);
test('Can use generators with error.all = transform + transform, encoding "hex", sync, lines', testGeneratorAll, false, 'hex', false, false, false, true, execaSync);
test('Can use generators with result.all = transform + pipe, sync, lines', testGeneratorAll, true, 'utf8', false, false, true, true, execaSync);
test('Can use generators with error.all = transform + pipe, sync, lines', testGeneratorAll, false, 'utf8', false, false, true, true, execaSync);
test('Can use generators with result.all = transform + pipe, encoding "buffer", sync, lines', testGeneratorAll, true, 'buffer', false, false, true, true, execaSync);
test('Can use generators with error.all = transform + pipe, encoding "buffer", sync, lines', testGeneratorAll, false, 'buffer', false, false, true, true, execaSync);
test('Can use generators with result.all = transform + pipe, encoding "hex", sync, lines', testGeneratorAll, true, 'hex', false, false, true, true, execaSync);
test('Can use generators with error.all = transform + pipe, encoding "hex", sync, lines', testGeneratorAll, false, 'hex', false, false, true, true, execaSync);
test('Can use generators with result.all = pipe + transform, sync, lines', testGeneratorAll, true, 'utf8', false, true, false, true, execaSync);
test('Can use generators with error.all = pipe + transform, sync, lines', testGeneratorAll, false, 'utf8', false, true, false, true, execaSync);
test('Can use generators with result.all = pipe + transform, encoding "buffer", sync, lines', testGeneratorAll, true, 'buffer', false, true, false, true, execaSync);
test('Can use generators with error.all = pipe + transform, encoding "buffer", sync, lines', testGeneratorAll, false, 'buffer', false, true, false, true, execaSync);
test('Can use generators with result.all = pipe + transform, encoding "hex", sync, lines', testGeneratorAll, true, 'hex', false, true, false, true, execaSync);
test('Can use generators with error.all = pipe + transform, encoding "hex", sync, lines', testGeneratorAll, false, 'hex', false, true, false, true, execaSync);
test('Can use generators with result.all = transform + transform, objectMode, sync, lines', testGeneratorAll, true, 'utf8', true, false, false, true, execaSync);
test('Can use generators with error.all = transform + transform, objectMode, sync, lines', testGeneratorAll, false, 'utf8', true, false, false, true, execaSync);
test('Can use generators with result.all = transform + transform, objectMode, encoding "buffer", sync, lines', testGeneratorAll, true, 'buffer', true, false, false, true, execaSync);
test('Can use generators with error.all = transform + transform, objectMode, encoding "buffer", sync, lines', testGeneratorAll, false, 'buffer', true, false, false, true, execaSync);
test('Can use generators with result.all = transform + transform, objectMode, encoding "hex", sync, lines', testGeneratorAll, true, 'hex', true, false, false, true, execaSync);
test('Can use generators with error.all = transform + transform, objectMode, encoding "hex", sync, lines', testGeneratorAll, false, 'hex', true, false, false, true, execaSync);
test('Can use generators with result.all = transform + pipe, objectMode, sync, lines', testGeneratorAll, true, 'utf8', true, false, true, true, execaSync);
test('Can use generators with error.all = transform + pipe, objectMode, sync, lines', testGeneratorAll, false, 'utf8', true, false, true, true, execaSync);
test('Can use generators with result.all = transform + pipe, objectMode, encoding "buffer", sync, lines', testGeneratorAll, true, 'buffer', true, false, true, true, execaSync);
test('Can use generators with error.all = transform + pipe, objectMode, encoding "buffer", sync, lines', testGeneratorAll, false, 'buffer', true, false, true, true, execaSync);
test('Can use generators with result.all = transform + pipe, objectMode, encoding "hex", sync, lines', testGeneratorAll, true, 'hex', true, false, true, true, execaSync);
test('Can use generators with error.all = transform + pipe, objectMode, encoding "hex", sync, lines', testGeneratorAll, false, 'hex', true, false, true, true, execaSync);
test('Can use generators with result.all = pipe + transform, objectMode, sync, lines', testGeneratorAll, true, 'utf8', true, true, false, true, execaSync);
test('Can use generators with error.all = pipe + transform, objectMode, sync, lines', testGeneratorAll, false, 'utf8', true, true, false, true, execaSync);
test('Can use generators with result.all = pipe + transform, objectMode, encoding "buffer", sync, lines', testGeneratorAll, true, 'buffer', true, true, false, true, execaSync);
test('Can use generators with error.all = pipe + transform, objectMode, encoding "buffer", sync, lines', testGeneratorAll, false, 'buffer', true, true, false, true, execaSync);
test('Can use generators with result.all = pipe + transform, objectMode, encoding "hex", sync, lines', testGeneratorAll, true, 'hex', true, true, false, true, execaSync);
test('Can use generators with error.all = pipe + transform, objectMode, encoding "hex", sync, lines', testGeneratorAll, false, 'hex', true, true, false, true, execaSync);
================================================
FILE: test/transform/generator-error.js
================================================
import {once} from 'node:events';
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {foobarString} from '../helpers/input.js';
import {noopGenerator, infiniteGenerator, convertTransformToFinal} from '../helpers/generator.js';
import {generatorsMap} from '../helpers/map.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {getEarlyErrorSubprocess, expectedEarlyError} from '../helpers/early-error.js';
setFixtureDirectory();
const assertProcessError = async (t, type, execaMethod, getSubprocess) => {
const cause = new Error(foobarString);
const transform = generatorsMap[type].throwing(cause)();
const error = execaMethod === execa
? await t.throwsAsync(getSubprocess(transform))
: t.throws(() => {
getSubprocess(transform);
});
t.is(error.cause, cause);
};
const testThrowingGenerator = async (t, type, final, execaMethod) => {
await assertProcessError(t, type, execaMethod, transform => execaMethod('noop.js', {
stdout: convertTransformToFinal(transform, final),
}));
};
test('Generators "transform" errors make subprocess fail', testThrowingGenerator, 'generator', false, execa);
test('Generators "final" errors make subprocess fail', testThrowingGenerator, 'generator', true, execa);
test('Generators "transform" errors make subprocess fail, sync', testThrowingGenerator, 'generator', false, execaSync);
test('Generators "final" errors make subprocess fail, sync', testThrowingGenerator, 'generator', true, execaSync);
test('Duplexes "transform" errors make subprocess fail', testThrowingGenerator, 'duplex', false, execa);
test('WebTransform "transform" errors make subprocess fail', testThrowingGenerator, 'webTransform', false, execa);
const testSingleErrorOutput = async (t, type, execaMethod) => {
await assertProcessError(t, type, execaMethod, transform => execaMethod('noop.js', {
stdout: [
generatorsMap[type].noop(false),
transform,
generatorsMap[type].noop(false),
],
}));
};
test('Generators errors make subprocess fail even when other output generators do not throw', testSingleErrorOutput, 'generator', execa);
test('Generators errors make subprocess fail even when other output generators do not throw, sync', testSingleErrorOutput, 'generator', execaSync);
test('Duplexes errors make subprocess fail even when other output generators do not throw', testSingleErrorOutput, 'duplex', execa);
test('WebTransform errors make subprocess fail even when other output generators do not throw', testSingleErrorOutput, 'webTransform', execa);
const testSingleErrorInput = async (t, type, execaMethod) => {
await assertProcessError(t, type, execaMethod, transform => execaMethod('stdin.js', {
stdin: [
['foobar\n'],
generatorsMap[type].noop(false),
transform,
generatorsMap[type].noop(false),
],
}));
};
test('Generators errors make subprocess fail even when other input generators do not throw', testSingleErrorInput, 'generator', execa);
test('Generators errors make subprocess fail even when other input generators do not throw, sync', testSingleErrorInput, 'generator', execaSync);
test('Duplexes errors make subprocess fail even when other input generators do not throw', testSingleErrorInput, 'duplex', execa);
test('WebTransform errors make subprocess fail even when other input generators do not throw', testSingleErrorInput, 'webTransform', execa);
const testGeneratorCancel = async (t, error) => {
const subprocess = execa('noop.js', {stdout: infiniteGenerator()});
await once(subprocess.stdout, 'data');
subprocess.stdout.destroy(error);
await (error === undefined ? t.notThrowsAsync(subprocess) : t.throwsAsync(subprocess));
};
test('Running generators are canceled on subprocess abort', testGeneratorCancel, undefined);
test('Running generators are canceled on subprocess error', testGeneratorCancel, new Error('test'));
const testGeneratorDestroy = async (t, transform) => {
const subprocess = execa('forever.js', {stdout: transform});
const cause = new Error('test');
subprocess.stdout.destroy(cause);
subprocess.kill();
t.like(await t.throwsAsync(subprocess), {cause});
};
test('Generators are destroyed on subprocess error, sync', testGeneratorDestroy, noopGenerator(false));
test('Generators are destroyed on subprocess error, async', testGeneratorDestroy, infiniteGenerator());
test('Generators are destroyed on early subprocess exit', async t => {
const error = await t.throwsAsync(getEarlyErrorSubprocess({stdout: infiniteGenerator()}));
t.like(error, expectedEarlyError);
});
================================================
FILE: test/transform/generator-final.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {foobarString} from '../helpers/input.js';
import {getOutputAsyncGenerator, getOutputGenerator, convertTransformToFinal} from '../helpers/generator.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
setFixtureDirectory();
const testGeneratorFinal = async (t, fixtureName, execaMethod) => {
const {stdout} = await execaMethod(fixtureName, {stdout: convertTransformToFinal(getOutputGenerator(foobarString)(), true)});
t.is(stdout, foobarString);
};
test('Generators "final" can be used', testGeneratorFinal, 'noop.js', execa);
test('Generators "final" is used even on empty streams', testGeneratorFinal, 'empty.js', execa);
test('Generators "final" can be used, sync', testGeneratorFinal, 'noop.js', execaSync);
test('Generators "final" is used even on empty streams, sync', testGeneratorFinal, 'empty.js', execaSync);
const testFinalAlone = async (t, final, execaMethod) => {
const {stdout} = await execaMethod('noop-fd.js', ['1', '.'], {stdout: {final: final(foobarString)().transform}});
t.is(stdout, `.\n${foobarString}`);
};
test('Generators "final" can be used without "transform"', testFinalAlone, getOutputGenerator, execa);
test('Generators "final" can be used without "transform", sync', testFinalAlone, getOutputGenerator, execaSync);
test('Generators "final" can be used without "transform", async', testFinalAlone, getOutputAsyncGenerator, execa);
const testFinalNoOutput = async (t, final, execaMethod) => {
const {stdout} = await execaMethod('empty.js', {stdout: {final: final(foobarString)().transform}});
t.is(stdout, foobarString);
};
test('Generators "final" can be used without "transform" nor output', testFinalNoOutput, getOutputGenerator, execa);
test('Generators "final" can be used without "transform" nor output, sync', testFinalNoOutput, getOutputGenerator, execaSync);
test('Generators "final" can be used without "transform" nor output, async', testFinalNoOutput, getOutputAsyncGenerator, execa);
================================================
FILE: test/transform/generator-input.js
================================================
import {Buffer} from 'node:buffer';
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {getStdio} from '../helpers/stdio.js';
import {
foobarString,
foobarUppercase,
foobarHex,
foobarUint8Array,
foobarBuffer,
foobarObject,
foobarObjectString,
} from '../helpers/input.js';
import {generatorsMap} from '../helpers/map.js';
setFixtureDirectory();
const getInputObjectMode = (objectMode, addNoopTransform, type) => objectMode
? {
input: [foobarObject],
generators: generatorsMap[type].addNoop(generatorsMap[type].serialize(objectMode), addNoopTransform, objectMode),
output: foobarObjectString,
}
: {
input: foobarUint8Array,
generators: generatorsMap[type].addNoop(generatorsMap[type].uppercase(objectMode), addNoopTransform, objectMode),
output: foobarUppercase,
};
// eslint-disable-next-line max-params
const testGeneratorInput = async (t, fdNumber, objectMode, addNoopTransform, type, execaMethod) => {
const {input, generators, output} = getInputObjectMode(objectMode, addNoopTransform, type);
const {stdout} = await execaMethod('stdin-fd.js', [`${fdNumber}`], getStdio(fdNumber, [input, ...generators]));
t.is(stdout, output);
};
test('Can use generators with result.stdin', testGeneratorInput, 0, false, false, 'generator', execa);
test('Can use generators with result.stdio[*] as input', testGeneratorInput, 3, false, false, 'generator', execa);
test('Can use generators with result.stdin, objectMode', testGeneratorInput, 0, true, false, 'generator', execa);
test('Can use generators with result.stdio[*] as input, objectMode', testGeneratorInput, 3, true, false, 'generator', execa);
test('Can use generators with result.stdin, noop transform', testGeneratorInput, 0, false, true, 'generator', execa);
test('Can use generators with result.stdio[*] as input, noop transform', testGeneratorInput, 3, false, true, 'generator', execa);
test('Can use generators with result.stdin, objectMode, noop transform', testGeneratorInput, 0, true, true, 'generator', execa);
test('Can use generators with result.stdio[*] as input, objectMode, noop transform', testGeneratorInput, 3, true, true, 'generator', execa);
test('Can use generators with result.stdin, sync', testGeneratorInput, 0, false, false, 'generator', execaSync);
test('Can use generators with result.stdin, objectMode, sync', testGeneratorInput, 0, true, false, 'generator', execaSync);
test('Can use generators with result.stdin, noop transform, sync', testGeneratorInput, 0, false, true, 'generator', execaSync);
test('Can use generators with result.stdin, objectMode, noop transform, sync', testGeneratorInput, 0, true, true, 'generator', execaSync);
test('Can use duplexes with result.stdin', testGeneratorInput, 0, false, false, 'duplex', execa);
test('Can use duplexes with result.stdio[*] as input', testGeneratorInput, 3, false, false, 'duplex', execa);
test('Can use duplexes with result.stdin, objectMode', testGeneratorInput, 0, true, false, 'duplex', execa);
test('Can use duplexes with result.stdio[*] as input, objectMode', testGeneratorInput, 3, true, false, 'duplex', execa);
test('Can use duplexes with result.stdin, noop transform', testGeneratorInput, 0, false, true, 'duplex', execa);
test('Can use duplexes with result.stdio[*] as input, noop transform', testGeneratorInput, 3, false, true, 'duplex', execa);
test('Can use duplexes with result.stdin, objectMode, noop transform', testGeneratorInput, 0, true, true, 'duplex', execa);
test('Can use duplexes with result.stdio[*] as input, objectMode, noop transform', testGeneratorInput, 3, true, true, 'duplex', execa);
test('Can use webTransforms with result.stdin', testGeneratorInput, 0, false, false, 'webTransform', execa);
test('Can use webTransforms with result.stdio[*] as input', testGeneratorInput, 3, false, false, 'webTransform', execa);
test('Can use webTransforms with result.stdin, objectMode', testGeneratorInput, 0, true, false, 'webTransform', execa);
test('Can use webTransforms with result.stdio[*] as input, objectMode', testGeneratorInput, 3, true, false, 'webTransform', execa);
test('Can use webTransforms with result.stdin, noop transform', testGeneratorInput, 0, false, true, 'webTransform', execa);
test('Can use webTransforms with result.stdio[*] as input, noop transform', testGeneratorInput, 3, false, true, 'webTransform', execa);
test('Can use webTransforms with result.stdin, objectMode, noop transform', testGeneratorInput, 0, true, true, 'webTransform', execa);
test('Can use webTransforms with result.stdio[*] as input, objectMode, noop transform', testGeneratorInput, 3, true, true, 'webTransform', execa);
// eslint-disable-next-line max-params
const testGeneratorInputPipe = async (t, useShortcutProperty, objectMode, addNoopTransform, type, input) => {
const {generators, output} = getInputObjectMode(objectMode, addNoopTransform, type);
const subprocess = execa('stdin-fd.js', ['0'], getStdio(0, generators));
const stream = useShortcutProperty ? subprocess.stdin : subprocess.stdio[0];
stream.end(...input);
const {stdout} = await subprocess;
const expectedOutput = input[1] === 'utf16le' ? Buffer.from(output, input[1]).toString() : output;
t.is(stdout, expectedOutput);
};
test('Can use generators with subprocess.stdio[0] and default encoding', testGeneratorInputPipe, false, false, false, 'generator', [foobarString, 'utf8']);
test('Can use generators with subprocess.stdin and default encoding', testGeneratorInputPipe, true, false, false, 'generator', [foobarString, 'utf8']);
test('Can use generators with subprocess.stdio[0] and encoding "utf16le"', testGeneratorInputPipe, false, false, false, 'generator', [foobarString, 'utf16le']);
test('Can use generators with subprocess.stdin and encoding "utf16le"', testGeneratorInputPipe, true, false, false, 'generator', [foobarString, 'utf16le']);
test('Can use generators with subprocess.stdio[0] and encoding "buffer"', testGeneratorInputPipe, false, false, false, 'generator', [foobarBuffer, 'buffer']);
test('Can use generators with subprocess.stdin and encoding "buffer"', testGeneratorInputPipe, true, false, false, 'generator', [foobarBuffer, 'buffer']);
test('Can use generators with subprocess.stdio[0] and encoding "hex"', testGeneratorInputPipe, false, false, false, 'generator', [foobarHex, 'hex']);
test('Can use generators with subprocess.stdin and encoding "hex"', testGeneratorInputPipe, true, false, false, 'generator', [foobarHex, 'hex']);
test('Can use generators with subprocess.stdio[0], objectMode', testGeneratorInputPipe, false, true, false, 'generator', [foobarObject]);
test('Can use generators with subprocess.stdin, objectMode', testGeneratorInputPipe, true, true, false, 'generator', [foobarObject]);
test('Can use generators with subprocess.stdio[0] and default encoding, noop transform', testGeneratorInputPipe, false, false, true, 'generator', [foobarString, 'utf8']);
test('Can use generators with subprocess.stdin and default encoding, noop transform', testGeneratorInputPipe, true, false, true, 'generator', [foobarString, 'utf8']);
test('Can use generators with subprocess.stdio[0] and encoding "utf16le", noop transform', testGeneratorInputPipe, false, false, true, 'generator', [foobarString, 'utf16le']);
test('Can use generators with subprocess.stdin and encoding "utf16le", noop transform', testGeneratorInputPipe, true, false, true, 'generator', [foobarString, 'utf16le']);
test('Can use generators with subprocess.stdio[0] and encoding "buffer", noop transform', testGeneratorInputPipe, false, false, true, 'generator', [foobarBuffer, 'buffer']);
test('Can use generators with subprocess.stdin and encoding "buffer", noop transform', testGeneratorInputPipe, true, false, true, 'generator', [foobarBuffer, 'buffer']);
test('Can use generators with subprocess.stdio[0] and encoding "hex", noop transform', testGeneratorInputPipe, false, false, true, 'generator', [foobarHex, 'hex']);
test('Can use generators with subprocess.stdin and encoding "hex", noop transform', testGeneratorInputPipe, true, false, true, 'generator', [foobarHex, 'hex']);
test('Can use generators with subprocess.stdio[0], objectMode, noop transform', testGeneratorInputPipe, false, true, true, 'generator', [foobarObject]);
test('Can use generators with subprocess.stdin, objectMode, noop transform', testGeneratorInputPipe, true, true, true, 'generator', [foobarObject]);
test('Can use duplexes with subprocess.stdio[0] and default encoding', testGeneratorInputPipe, false, false, false, 'duplex', [foobarString, 'utf8']);
test('Can use duplexes with subprocess.stdin and default encoding', testGeneratorInputPipe, true, false, false, 'duplex', [foobarString, 'utf8']);
test('Can use duplexes with subprocess.stdio[0] and encoding "utf16le"', testGeneratorInputPipe, false, false, false, 'duplex', [foobarString, 'utf16le']);
test('Can use duplexes with subprocess.stdin and encoding "utf16le"', testGeneratorInputPipe, true, false, false, 'duplex', [foobarString, 'utf16le']);
test('Can use duplexes with subprocess.stdio[0] and encoding "buffer"', testGeneratorInputPipe, false, false, false, 'duplex', [foobarBuffer, 'buffer']);
test('Can use duplexes with subprocess.stdin and encoding "buffer"', testGeneratorInputPipe, true, false, false, 'duplex', [foobarBuffer, 'buffer']);
test('Can use duplexes with subprocess.stdio[0] and encoding "hex"', testGeneratorInputPipe, false, false, false, 'duplex', [foobarHex, 'hex']);
test('Can use duplexes with subprocess.stdin and encoding "hex"', testGeneratorInputPipe, true, false, false, 'duplex', [foobarHex, 'hex']);
test('Can use duplexes with subprocess.stdio[0], objectMode', testGeneratorInputPipe, false, true, false, 'duplex', [foobarObject]);
test('Can use duplexes with subprocess.stdin, objectMode', testGeneratorInputPipe, true, true, false, 'duplex', [foobarObject]);
test('Can use duplexes with subprocess.stdio[0] and default encoding, noop transform', testGeneratorInputPipe, false, false, true, 'duplex', [foobarString, 'utf8']);
test('Can use duplexes with subprocess.stdin and default encoding, noop transform', testGeneratorInputPipe, true, false, true, 'duplex', [foobarString, 'utf8']);
test('Can use duplexes with subprocess.stdio[0] and encoding "utf16le", noop transform', testGeneratorInputPipe, false, false, true, 'duplex', [foobarString, 'utf16le']);
test('Can use duplexes with subprocess.stdin and encoding "utf16le", noop transform', testGeneratorInputPipe, true, false, true, 'duplex', [foobarString, 'utf16le']);
test('Can use duplexes with subprocess.stdio[0] and encoding "buffer", noop transform', testGeneratorInputPipe, false, false, true, 'duplex', [foobarBuffer, 'buffer']);
test('Can use duplexes with subprocess.stdin and encoding "buffer", noop transform', testGeneratorInputPipe, true, false, true, 'duplex', [foobarBuffer, 'buffer']);
test('Can use duplexes with subprocess.stdio[0] and encoding "hex", noop transform', testGeneratorInputPipe, false, false, true, 'duplex', [foobarHex, 'hex']);
test('Can use duplexes with subprocess.stdin and encoding "hex", noop transform', testGeneratorInputPipe, true, false, true, 'duplex', [foobarHex, 'hex']);
test('Can use duplexes with subprocess.stdio[0], objectMode, noop transform', testGeneratorInputPipe, false, true, true, 'duplex', [foobarObject]);
test('Can use duplexes with subprocess.stdin, objectMode, noop transform', testGeneratorInputPipe, true, true, true, 'duplex', [foobarObject]);
test('Can use webTransforms with subprocess.stdio[0] and default encoding', testGeneratorInputPipe, false, false, false, 'webTransform', [foobarString, 'utf8']);
test('Can use webTransforms with subprocess.stdin and default encoding', testGeneratorInputPipe, true, false, false, 'webTransform', [foobarString, 'utf8']);
test('Can use webTransforms with subprocess.stdio[0] and encoding "utf16le"', testGeneratorInputPipe, false, false, false, 'webTransform', [foobarString, 'utf16le']);
test('Can use webTransforms with subprocess.stdin and encoding "utf16le"', testGeneratorInputPipe, true, false, false, 'webTransform', [foobarString, 'utf16le']);
test('Can use webTransforms with subprocess.stdio[0] and encoding "buffer"', testGeneratorInputPipe, false, false, false, 'webTransform', [foobarBuffer, 'buffer']);
test('Can use webTransforms with subprocess.stdin and encoding "buffer"', testGeneratorInputPipe, true, false, false, 'webTransform', [foobarBuffer, 'buffer']);
test('Can use webTransforms with subprocess.stdio[0] and encoding "hex"', testGeneratorInputPipe, false, false, false, 'webTransform', [foobarHex, 'hex']);
test('Can use webTransforms with subprocess.stdin and encoding "hex"', testGeneratorInputPipe, true, false, false, 'webTransform', [foobarHex, 'hex']);
test('Can use webTransforms with subprocess.stdio[0], objectMode', testGeneratorInputPipe, false, true, false, 'webTransform', [foobarObject]);
test('Can use webTransforms with subprocess.stdin, objectMode', testGeneratorInputPipe, true, true, false, 'webTransform', [foobarObject]);
test('Can use webTransforms with subprocess.stdio[0] and default encoding, noop transform', testGeneratorInputPipe, false, false, true, 'webTransform', [foobarString, 'utf8']);
test('Can use webTransforms with subprocess.stdin and default encoding, noop transform', testGeneratorInputPipe, true, false, true, 'webTransform', [foobarString, 'utf8']);
test('Can use webTransforms with subprocess.stdio[0] and encoding "utf16le", noop transform', testGeneratorInputPipe, false, false, true, 'webTransform', [foobarString, 'utf16le']);
test('Can use webTransforms with subprocess.stdin and encoding "utf16le", noop transform', testGeneratorInputPipe, true, false, true, 'webTransform', [foobarString, 'utf16le']);
test('Can use webTransforms with subprocess.stdio[0] and encoding "buffer", noop transform', testGeneratorInputPipe, false, false, true, 'webTransform', [foobarBuffer, 'buffer']);
test('Can use webTransforms with subprocess.stdin and encoding "buffer", noop transform', testGeneratorInputPipe, true, false, true, 'webTransform', [foobarBuffer, 'buffer']);
test('Can use webTransforms with subprocess.stdio[0] and encoding "hex", noop transform', testGeneratorInputPipe, false, false, true, 'webTransform', [foobarHex, 'hex']);
test('Can use webTransforms with subprocess.stdin and encoding "hex", noop transform', testGeneratorInputPipe, true, false, true, 'webTransform', [foobarHex, 'hex']);
test('Can use webTransforms with subprocess.stdio[0], objectMode, noop transform', testGeneratorInputPipe, false, true, true, 'webTransform', [foobarObject]);
test('Can use webTransforms with subprocess.stdin, objectMode, noop transform', testGeneratorInputPipe, true, true, true, 'webTransform', [foobarObject]);
const testGeneratorStdioInputPipe = async (t, objectMode, addNoopTransform, type) => {
const {input, generators, output} = getInputObjectMode(objectMode, addNoopTransform, type);
const subprocess = execa('stdin-fd.js', ['3'], getStdio(3, [[], ...generators]));
subprocess.stdio[3].write(Array.isArray(input) ? input[0] : input);
const {stdout} = await subprocess;
t.is(stdout, output);
};
test('Can use generators with subprocess.stdio[*] as input', testGeneratorStdioInputPipe, false, false, 'generator');
test('Can use generators with subprocess.stdio[*] as input, objectMode', testGeneratorStdioInputPipe, true, false, 'generator');
test('Can use generators with subprocess.stdio[*] as input, noop transform', testGeneratorStdioInputPipe, false, true, 'generator');
test('Can use generators with subprocess.stdio[*] as input, objectMode, noop transform', testGeneratorStdioInputPipe, true, true, 'generator');
test('Can use duplexes with subprocess.stdio[*] as input', testGeneratorStdioInputPipe, false, false, 'duplex');
test('Can use duplexes with subprocess.stdio[*] as input, objectMode', testGeneratorStdioInputPipe, true, false, 'duplex');
test('Can use duplexes with subprocess.stdio[*] as input, noop transform', testGeneratorStdioInputPipe, false, true, 'duplex');
test('Can use duplexes with subprocess.stdio[*] as input, objectMode, noop transform', testGeneratorStdioInputPipe, true, true, 'duplex');
test('Can use webTransforms with subprocess.stdio[*] as input', testGeneratorStdioInputPipe, false, false, 'webTransform');
test('Can use webTransforms with subprocess.stdio[*] as input, objectMode', testGeneratorStdioInputPipe, true, false, 'webTransform');
test('Can use webTransforms with subprocess.stdio[*] as input, noop transform', testGeneratorStdioInputPipe, false, true, 'webTransform');
test('Can use webTransforms with subprocess.stdio[*] as input, objectMode, noop transform', testGeneratorStdioInputPipe, true, true, 'webTransform');
================================================
FILE: test/transform/generator-main.js
================================================
import {Buffer} from 'node:buffer';
import {scheduler} from 'node:timers/promises';
import test from 'ava';
import {getStreamAsArray} from 'get-stream';
import {execa, execaSync} from '../../index.js';
import {foobarString} from '../helpers/input.js';
import {
noopGenerator,
outputObjectGenerator,
convertTransformToFinal,
prefix,
suffix,
} from '../helpers/generator.js';
import {generatorsMap} from '../helpers/map.js';
import {defaultHighWaterMark} from '../helpers/stream.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {maxBuffer, assertErrorMessage} from '../helpers/max-buffer.js';
setFixtureDirectory();
const repeatCount = defaultHighWaterMark * 3;
const writerGenerator = function * () {
for (let index = 0; index < repeatCount; index += 1) {
yield '\n';
}
};
const getLengthGenerator = function * (t, chunk) {
t.is(chunk.length, 1);
yield chunk;
};
// eslint-disable-next-line max-params
const testHighWaterMark = async (t, passThrough, binary, objectMode, execaMethod) => {
const {stdout} = await execaMethod('noop.js', {
stdout: [
...(objectMode ? [outputObjectGenerator()] : []),
writerGenerator,
...(passThrough ? [noopGenerator(false, binary)] : []),
{transform: getLengthGenerator.bind(undefined, t), preserveNewlines: true, objectMode: true},
],
});
t.is(stdout.length, repeatCount);
t.true(stdout.every(chunk => chunk === '\n'));
};
test('Synchronous yields are not buffered, no passThrough', testHighWaterMark, false, false, false, execa);
test('Synchronous yields are not buffered, line-wise passThrough', testHighWaterMark, true, false, false, execa);
test('Synchronous yields are not buffered, binary passThrough', testHighWaterMark, true, true, false, execa);
test('Synchronous yields are not buffered, objectMode as input but not output', testHighWaterMark, false, false, true, execa);
test('Synchronous yields are not buffered, no passThrough, sync', testHighWaterMark, false, false, false, execaSync);
test('Synchronous yields are not buffered, line-wise passThrough, sync', testHighWaterMark, true, false, false, execaSync);
test('Synchronous yields are not buffered, binary passThrough, sync', testHighWaterMark, true, true, false, execaSync);
test('Synchronous yields are not buffered, objectMode as input but not output, sync', testHighWaterMark, false, false, true, execaSync);
// eslint-disable-next-line max-params
const testNoYield = async (t, type, objectMode, final, output, execaMethod) => {
const {stdout} = await execaMethod('noop.js', {stdout: convertTransformToFinal(generatorsMap[type].noYield(objectMode), final)});
t.deepEqual(stdout, output);
};
test('Generator can filter "transform" by not calling yield', testNoYield, 'generator', false, false, '', execa);
test('Generator can filter "transform" by not calling yield, objectMode', testNoYield, 'generator', true, false, [], execa);
test('Generator can filter "final" by not calling yield', testNoYield, 'generator', false, true, '', execa);
test('Generator can filter "final" by not calling yield, objectMode', testNoYield, 'generator', true, true, [], execa);
test('Generator can filter "transform" by not calling yield, sync', testNoYield, 'generator', false, false, '', execaSync);
test('Generator can filter "transform" by not calling yield, objectMode, sync', testNoYield, 'generator', true, false, [], execaSync);
test('Generator can filter "final" by not calling yield, sync', testNoYield, 'generator', false, true, '', execaSync);
test('Generator can filter "final" by not calling yield, objectMode, sync', testNoYield, 'generator', true, true, [], execaSync);
test('Duplex can filter by not calling push', testNoYield, 'duplex', false, false, '', execa);
test('Duplex can filter by not calling push, objectMode', testNoYield, 'duplex', true, false, [], execa);
test('WebTransform can filter by not calling push', testNoYield, 'webTransform', false, false, '', execa);
test('WebTransform can filter by not calling push, objectMode', testNoYield, 'webTransform', true, false, [], execa);
const testMultipleYields = async (t, type, final, binary) => {
const {stdout} = await execa('noop-fd.js', ['1', foobarString], {stdout: convertTransformToFinal(generatorsMap[type].multipleYield(), final)});
const newline = binary ? '' : '\n';
t.is(stdout, `${prefix}${newline}${foobarString}${newline}${suffix}`);
};
test('Generator can yield "transform" multiple times at different moments', testMultipleYields, 'generator', false, false);
test('Generator can yield "final" multiple times at different moments', testMultipleYields, 'generator', true, false);
test('Duplex can push multiple times at different moments', testMultipleYields, 'duplex', false, true);
test('WebTransform can push multiple times at different moments', testMultipleYields, 'webTransform', false, true);
const partsPerChunk = 4;
const chunksPerCall = 10;
const callCount = 5;
const fullString = '\n'.repeat(defaultHighWaterMark / partsPerChunk);
const yieldFullStrings = function * () {
yield * Array.from({length: partsPerChunk * chunksPerCall}).fill(fullString);
};
const manyYieldGenerator = async function * () {
for (let index = 0; index < callCount; index += 1) {
yield * yieldFullStrings();
// eslint-disable-next-line no-await-in-loop
await scheduler.yield();
}
};
const testManyYields = async (t, final) => {
const subprocess = execa('noop.js', {stdout: convertTransformToFinal(manyYieldGenerator, final), stripFinalNewline: false});
const [chunks, {stdout}] = await Promise.all([getStreamAsArray(subprocess.stdout), subprocess]);
const expectedChunk = Buffer.from(fullString);
t.deepEqual(chunks, Array.from({length: callCount * partsPerChunk * chunksPerCall}).fill(expectedChunk));
t.is(chunks.join(''), stdout);
};
test('Generator "transform" yields are sent right away', testManyYields, false);
test('Generator "final" yields are sent right away', testManyYields, true);
const testMaxBuffer = async (t, type) => {
const bigString = '.'.repeat(maxBuffer);
const {stdout} = await execa('noop.js', {
maxBuffer,
stdout: generatorsMap[type].getOutput(bigString)(false, true),
});
t.is(stdout, bigString);
const {isMaxBuffer, shortMessage} = await t.throwsAsync(execa('noop.js', {
maxBuffer,
stdout: generatorsMap[type].getOutput(`${bigString}.`)(false, true),
}));
t.true(isMaxBuffer);
assertErrorMessage(t, shortMessage);
};
test('Generators take "maxBuffer" into account', testMaxBuffer, 'generator');
test('Duplexes take "maxBuffer" into account', testMaxBuffer, 'duplex');
test('WebTransforms take "maxBuffer" into account', testMaxBuffer, 'webTransform');
test('Generators does not take "maxBuffer" into account, sync', t => {
const bigString = '.'.repeat(maxBuffer);
const {isMaxBuffer, stdout} = execaSync('noop.js', {
maxBuffer,
stdout: generatorsMap.generator.getOutput(`${bigString}.`)(false, true),
});
t.false(isMaxBuffer);
t.is(stdout.length, maxBuffer + 1);
});
const testMaxBufferObject = async (t, type) => {
const bigArray = Array.from({length: maxBuffer}).fill('..');
const {stdout} = await execa('noop.js', {
maxBuffer,
stdout: generatorsMap[type].getOutputs(bigArray)(true, true),
});
t.is(stdout.length, maxBuffer);
const {isMaxBuffer, shortMessage} = await t.throwsAsync(execa('noop.js', {
maxBuffer,
stdout: generatorsMap[type].getOutputs([...bigArray, ''])(true, true),
}));
t.true(isMaxBuffer);
assertErrorMessage(t, shortMessage, {unit: 'objects'});
};
test('Generators take "maxBuffer" into account, objectMode', testMaxBufferObject, 'generator');
test('Duplexes take "maxBuffer" into account, objectMode', testMaxBufferObject, 'duplex');
test('WebTransforms take "maxBuffer" into account, objectMode', testMaxBufferObject, 'webTransform');
test('Generators does not take "maxBuffer" into account, objectMode, sync', t => {
const bigArray = Array.from({length: maxBuffer}).fill('..');
const {isMaxBuffer, stdout} = execaSync('noop.js', {
maxBuffer,
stdout: generatorsMap.generator.getOutputs([...bigArray, ''])(true, true),
});
t.false(isMaxBuffer);
t.is(stdout.length, maxBuffer + 1);
});
const testAsyncGenerators = async (t, type, final) => {
const {stdout} = await execa('noop.js', {
stdout: convertTransformToFinal(generatorsMap[type].timeout(1e2)(), final),
});
t.is(stdout, foobarString);
};
test('Generators "transform" is awaited on success', testAsyncGenerators, 'generator', false);
test('Generators "final" is awaited on success', testAsyncGenerators, 'generator', true);
test('Duplex is awaited on success', testAsyncGenerators, 'duplex', false);
test('WebTransform is awaited on success', testAsyncGenerators, 'webTransform', false);
================================================
FILE: test/transform/generator-mixed.js
================================================
import {readFile, writeFile, rm} from 'node:fs/promises';
import {PassThrough} from 'node:stream';
import test from 'ava';
import getStream from 'get-stream';
import tempfile from 'tempfile';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString, foobarUppercase, foobarUint8Array} from '../helpers/input.js';
import {uppercaseGenerator} from '../helpers/generator.js';
import {uppercaseBufferDuplex} from '../helpers/duplex.js';
import {uppercaseBufferWebTransform} from '../helpers/web-transform.js';
import {generatorsMap} from '../helpers/map.js';
setFixtureDirectory();
const testInputOption = async (t, type, execaMethod) => {
const {stdout} = await execaMethod('stdin-fd.js', ['0'], {stdin: generatorsMap[type].uppercase(), input: foobarUint8Array});
t.is(stdout, foobarUppercase);
};
test('Can use generators with input option', testInputOption, 'generator', execa);
test('Can use generators with input option, sync', testInputOption, 'generator', execaSync);
test('Can use duplexes with input option', testInputOption, 'duplex', execa);
test('Can use webTransforms with input option', testInputOption, 'webTransform', execa);
// eslint-disable-next-line max-params
const testInputFile = async (t, stdinOption, useInputFile, reversed, execaMethod) => {
const filePath = tempfile();
await writeFile(filePath, foobarString);
const options = useInputFile
? {inputFile: filePath, stdin: stdinOption}
: {stdin: [{file: filePath}, stdinOption]};
options.stdin = reversed ? options.stdin.reverse() : options.stdin;
const {stdout} = await execaMethod('stdin-fd.js', ['0'], options);
t.is(stdout, foobarUppercase);
await rm(filePath);
};
test('Can use generators with a file as input', testInputFile, uppercaseGenerator(), false, false, execa);
test('Can use generators with a file as input, reversed', testInputFile, uppercaseGenerator(), false, true, execa);
test('Can use generators with inputFile option', testInputFile, uppercaseGenerator(), true, false, execa);
test('Can use generators with a file as input, sync', testInputFile, uppercaseGenerator(), false, false, execaSync);
test('Can use generators with a file as input, reversed, sync', testInputFile, uppercaseGenerator(), false, true, execaSync);
test('Can use generators with inputFile option, sync', testInputFile, uppercaseGenerator(), true, false, execaSync);
test('Can use duplexes with a file as input', testInputFile, uppercaseBufferDuplex(), false, false, execa);
test('Can use duplexes with a file as input, reversed', testInputFile, uppercaseBufferDuplex(), false, true, execa);
test('Can use duplexes with inputFile option', testInputFile, uppercaseBufferDuplex(), true, false, execa);
test('Can use webTransforms with a file as input', testInputFile, uppercaseBufferWebTransform(), false, false, execa);
test('Can use webTransforms with a file as input, reversed', testInputFile, uppercaseBufferWebTransform(), false, true, execa);
test('Can use webTransforms with inputFile option', testInputFile, uppercaseBufferWebTransform(), true, false, execa);
const testOutputFile = async (t, reversed, type, execaMethod) => {
const filePath = tempfile();
const stdoutOption = [generatorsMap[type].uppercaseBuffer(false, true), {file: filePath}];
const reversedStdoutOption = reversed ? stdoutOption.reverse() : stdoutOption;
const {stdout} = await execaMethod('noop-fd.js', ['1'], {stdout: reversedStdoutOption});
t.is(stdout, foobarUppercase);
t.is(await readFile(filePath, 'utf8'), foobarUppercase);
await rm(filePath);
};
test('Can use generators with a file as output', testOutputFile, false, 'generator', execa);
test('Can use generators with a file as output, reversed', testOutputFile, true, 'generator', execa);
test('Can use generators with a file as output, sync', testOutputFile, false, 'generator', execaSync);
test('Can use generators with a file as output, reversed, sync', testOutputFile, true, 'generator', execaSync);
test('Can use duplexes with a file as output', testOutputFile, false, 'duplex', execa);
test('Can use duplexes with a file as output, reversed', testOutputFile, true, 'duplex', execa);
test('Can use webTransforms with a file as output', testOutputFile, false, 'webTransform', execa);
test('Can use webTransforms with a file as output, reversed', testOutputFile, true, 'webTransform', execa);
const testWritableDestination = async (t, type) => {
const passThrough = new PassThrough();
const [{stdout}, streamOutput] = await Promise.all([
execa('noop-fd.js', ['1', foobarString], {stdout: [generatorsMap[type].uppercaseBuffer(false, true), passThrough]}),
getStream(passThrough),
]);
t.is(stdout, foobarUppercase);
t.is(streamOutput, foobarUppercase);
};
test('Can use generators to a Writable stream', testWritableDestination, 'generator');
test('Can use duplexes to a Writable stream', testWritableDestination, 'duplex');
test('Can use webTransforms to a Writable stream', testWritableDestination, 'webTransform');
const testReadableSource = async (t, type) => {
const passThrough = new PassThrough();
const subprocess = execa('stdin-fd.js', ['0'], {stdin: [passThrough, generatorsMap[type].uppercase()]});
passThrough.end(foobarString);
const {stdout} = await subprocess;
t.is(stdout, foobarUppercase);
};
test('Can use generators from a Readable stream', testReadableSource, 'generator');
test('Can use duplexes from a Readable stream', testReadableSource, 'duplex');
test('Can use webTransforms from a Readable stream', testReadableSource, 'webTransform');
const testInherit = async (t, type) => {
const {stdout} = await execa('nested-inherit.js', [type]);
t.is(stdout, foobarUppercase);
};
test('Can use generators with "inherit"', testInherit, 'generator');
test('Can use duplexes with "inherit"', testInherit, 'duplex');
test('Can use webTransforms with "inherit"', testInherit, 'webTransform');
================================================
FILE: test/transform/generator-output.js
================================================
import test from 'ava';
import getStream, {getStreamAsArray} from 'get-stream';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {getStdio} from '../helpers/stdio.js';
import {foobarString, foobarUppercase, foobarObject} from '../helpers/input.js';
import {generatorsMap} from '../helpers/map.js';
setFixtureDirectory();
const getOutputObjectMode = (objectMode, addNoopTransform, type, binary) => objectMode
? {
generators: generatorsMap[type].addNoop(generatorsMap[type].outputObject(), addNoopTransform, objectMode, binary),
output: [foobarObject],
getStreamMethod: getStreamAsArray,
}
: {
generators: generatorsMap[type].addNoop(generatorsMap[type].uppercaseBuffer(objectMode, true), addNoopTransform, objectMode, binary),
output: foobarUppercase,
getStreamMethod: getStream,
};
// eslint-disable-next-line max-params
const testGeneratorOutput = async (t, fdNumber, reject, useShortcutProperty, objectMode, addNoopTransform, type, execaMethod) => {
const {generators, output} = getOutputObjectMode(objectMode, addNoopTransform, type);
const fixtureName = reject ? 'noop-fd.js' : 'noop-fail.js';
const {stdout, stderr, stdio} = await execaMethod(fixtureName, [`${fdNumber}`, foobarString], {...getStdio(fdNumber, generators), reject});
const result = useShortcutProperty ? [stdout, stderr][fdNumber - 1] : stdio[fdNumber];
t.deepEqual(result, output);
};
test('Can use generators with result.stdio[1]', testGeneratorOutput, 1, true, false, false, false, 'generator', execa);
test('Can use generators with result.stdout', testGeneratorOutput, 1, true, true, false, false, 'generator', execa);
test('Can use generators with result.stdio[2]', testGeneratorOutput, 2, true, false, false, false, 'generator', execa);
test('Can use generators with result.stderr', testGeneratorOutput, 2, true, true, false, false, 'generator', execa);
test('Can use generators with result.stdio[*] as output', testGeneratorOutput, 3, true, false, false, false, 'generator', execa);
test('Can use generators with error.stdio[1]', testGeneratorOutput, 1, false, false, false, false, 'generator', execa);
test('Can use generators with error.stdout', testGeneratorOutput, 1, false, true, false, false, 'generator', execa);
test('Can use generators with error.stdio[2]', testGeneratorOutput, 2, false, false, false, false, 'generator', execa);
test('Can use generators with error.stderr', testGeneratorOutput, 2, false, true, false, false, 'generator', execa);
test('Can use generators with error.stdio[*] as output', testGeneratorOutput, 3, false, false, false, false, 'generator', execa);
test('Can use generators with result.stdio[1], objectMode', testGeneratorOutput, 1, true, false, true, false, 'generator', execa);
test('Can use generators with result.stdout, objectMode', testGeneratorOutput, 1, true, true, true, false, 'generator', execa);
test('Can use generators with result.stdio[2], objectMode', testGeneratorOutput, 2, true, false, true, false, 'generator', execa);
test('Can use generators with result.stderr, objectMode', testGeneratorOutput, 2, true, true, true, false, 'generator', execa);
test('Can use generators with result.stdio[*] as output, objectMode', testGeneratorOutput, 3, true, false, true, false, 'generator', execa);
test('Can use generators with error.stdio[1], objectMode', testGeneratorOutput, 1, false, false, true, false, 'generator', execa);
test('Can use generators with error.stdout, objectMode', testGeneratorOutput, 1, false, true, true, false, 'generator', execa);
test('Can use generators with error.stdio[2], objectMode', testGeneratorOutput, 2, false, false, true, false, 'generator', execa);
test('Can use generators with error.stderr, objectMode', testGeneratorOutput, 2, false, true, true, false, 'generator', execa);
test('Can use generators with error.stdio[*] as output, objectMode', testGeneratorOutput, 3, false, false, true, false, 'generator', execa);
test('Can use generators with result.stdio[1], noop transform', testGeneratorOutput, 1, true, false, false, true, 'generator', execa);
test('Can use generators with result.stdout, noop transform', testGeneratorOutput, 1, true, true, false, true, 'generator', execa);
test('Can use generators with result.stdio[2], noop transform', testGeneratorOutput, 2, true, false, false, true, 'generator', execa);
test('Can use generators with result.stderr, noop transform', testGeneratorOutput, 2, true, true, false, true, 'generator', execa);
test('Can use generators with result.stdio[*] as output, noop transform', testGeneratorOutput, 3, true, false, false, true, 'generator', execa);
test('Can use generators with error.stdio[1], noop transform', testGeneratorOutput, 1, false, false, false, true, 'generator', execa);
test('Can use generators with error.stdout, noop transform', testGeneratorOutput, 1, false, true, false, true, 'generator', execa);
test('Can use generators with error.stdio[2], noop transform', testGeneratorOutput, 2, false, false, false, true, 'generator', execa);
test('Can use generators with error.stderr, noop transform', testGeneratorOutput, 2, false, true, false, true, 'generator', execa);
test('Can use generators with error.stdio[*] as output, noop transform', testGeneratorOutput, 3, false, false, false, true, 'generator', execa);
test('Can use generators with result.stdio[1], objectMode, noop transform', testGeneratorOutput, 1, true, false, true, true, 'generator', execa);
test('Can use generators with result.stdout, objectMode, noop transform', testGeneratorOutput, 1, true, true, true, true, 'generator', execa);
test('Can use generators with result.stdio[2], objectMode, noop transform', testGeneratorOutput, 2, true, false, true, true, 'generator', execa);
test('Can use generators with result.stderr, objectMode, noop transform', testGeneratorOutput, 2, true, true, true, true, 'generator', execa);
test('Can use generators with result.stdio[*] as output, objectMode, noop transform', testGeneratorOutput, 3, true, false, true, true, 'generator', execa);
test('Can use generators with error.stdio[1], objectMode, noop transform', testGeneratorOutput, 1, false, false, true, true, 'generator', execa);
test('Can use generators with error.stdout, objectMode, noop transform', testGeneratorOutput, 1, false, true, true, true, 'generator', execa);
test('Can use generators with error.stdio[2], objectMode, noop transform', testGeneratorOutput, 2, false, false, true, true, 'generator', execa);
test('Can use generators with error.stderr, objectMode, noop transform', testGeneratorOutput, 2, false, true, true, true, 'generator', execa);
test('Can use generators with error.stdio[*] as output, objectMode, noop transform', testGeneratorOutput, 3, false, false, true, true, 'generator', execa);
test('Can use generators with result.stdio[1], sync', testGeneratorOutput, 1, true, false, false, false, 'generator', execaSync);
test('Can use generators with result.stdout, sync', testGeneratorOutput, 1, true, true, false, false, 'generator', execaSync);
test('Can use generators with result.stdio[2], sync', testGeneratorOutput, 2, true, false, false, false, 'generator', execaSync);
test('Can use generators with result.stderr, sync', testGeneratorOutput, 2, true, true, false, false, 'generator', execaSync);
test('Can use generators with result.stdio[*] as output, sync', testGeneratorOutput, 3, true, false, false, false, 'generator', execaSync);
test('Can use generators with error.stdio[1], sync', testGeneratorOutput, 1, false, false, false, false, 'generator', execaSync);
test('Can use generators with error.stdout, sync', testGeneratorOutput, 1, false, true, false, false, 'generator', execaSync);
test('Can use generators with error.stdio[2], sync', testGeneratorOutput, 2, false, false, false, false, 'generator', execaSync);
test('Can use generators with error.stderr, sync', testGeneratorOutput, 2, false, true, false, false, 'generator', execaSync);
test('Can use generators with error.stdio[*] as output, sync', testGeneratorOutput, 3, false, false, false, false, 'generator', execaSync);
test('Can use generators with result.stdio[1], objectMode, sync', testGeneratorOutput, 1, true, false, true, false, 'generator', execaSync);
test('Can use generators with result.stdout, objectMode, sync', testGeneratorOutput, 1, true, true, true, false, 'generator', execaSync);
test('Can use generators with result.stdio[2], objectMode, sync', testGeneratorOutput, 2, true, false, true, false, 'generator', execaSync);
test('Can use generators with result.stderr, objectMode, sync', testGeneratorOutput, 2, true, true, true, false, 'generator', execaSync);
test('Can use generators with result.stdio[*] as output, objectMode, sync', testGeneratorOutput, 3, true, false, true, false, 'generator', execaSync);
test('Can use generators with error.stdio[1], objectMode, sync', testGeneratorOutput, 1, false, false, true, false, 'generator', execaSync);
test('Can use generators with error.stdout, objectMode, sync', testGeneratorOutput, 1, false, true, true, false, 'generator', execaSync);
test('Can use generators with error.stdio[2], objectMode, sync', testGeneratorOutput, 2, false, false, true, false, 'generator', execaSync);
test('Can use generators with error.stderr, objectMode, sync', testGeneratorOutput, 2, false, true, true, false, 'generator', execaSync);
test('Can use generators with error.stdio[*] as output, objectMode, sync', testGeneratorOutput, 3, false, false, true, false, 'generator', execaSync);
test('Can use generators with result.stdio[1], noop transform, sync', testGeneratorOutput, 1, true, false, false, true, 'generator', execaSync);
test('Can use generators with result.stdout, noop transform, sync', testGeneratorOutput, 1, true, true, false, true, 'generator', execaSync);
test('Can use generators with result.stdio[2], noop transform, sync', testGeneratorOutput, 2, true, false, false, true, 'generator', execaSync);
test('Can use generators with result.stderr, noop transform, sync', testGeneratorOutput, 2, true, true, false, true, 'generator', execaSync);
test('Can use generators with result.stdio[*] as output, noop transform, sync', testGeneratorOutput, 3, true, false, false, true, 'generator', execaSync);
test('Can use generators with error.stdio[1], noop transform, sync', testGeneratorOutput, 1, false, false, false, true, 'generator', execaSync);
test('Can use generators with error.stdout, noop transform, sync', testGeneratorOutput, 1, false, true, false, true, 'generator', execaSync);
test('Can use generators with error.stdio[2], noop transform, sync', testGeneratorOutput, 2, false, false, false, true, 'generator', execaSync);
test('Can use generators with error.stderr, noop transform, sync', testGeneratorOutput, 2, false, true, false, true, 'generator', execaSync);
test('Can use generators with error.stdio[*] as output, noop transform, sync', testGeneratorOutput, 3, false, false, false, true, 'generator', execaSync);
test('Can use generators with result.stdio[1], objectMode, noop transform, sync', testGeneratorOutput, 1, true, false, true, true, 'generator', execaSync);
test('Can use generators with result.stdout, objectMode, noop transform, sync', testGeneratorOutput, 1, true, true, true, true, 'generator', execaSync);
test('Can use generators with result.stdio[2], objectMode, noop transform, sync', testGeneratorOutput, 2, true, false, true, true, 'generator', execaSync);
test('Can use generators with result.stderr, objectMode, noop transform, sync', testGeneratorOutput, 2, true, true, true, true, 'generator', execaSync);
test('Can use generators with result.stdio[*] as output, objectMode, noop transform, sync', testGeneratorOutput, 3, true, false, true, true, 'generator', execaSync);
test('Can use generators with error.stdio[1], objectMode, noop transform, sync', testGeneratorOutput, 1, false, false, true, true, 'generator', execaSync);
test('Can use generators with error.stdout, objectMode, noop transform, sync', testGeneratorOutput, 1, false, true, true, true, 'generator', execaSync);
test('Can use generators with error.stdio[2], objectMode, noop transform, sync', testGeneratorOutput, 2, false, false, true, true, 'generator', execaSync);
test('Can use generators with error.stderr, objectMode, noop transform, sync', testGeneratorOutput, 2, false, true, true, true, 'generator', execaSync);
test('Can use generators with error.stdio[*] as output, objectMode, noop transform, sync', testGeneratorOutput, 3, false, false, true, true, 'generator', execaSync);
test('Can use duplexes with result.stdio[1]', testGeneratorOutput, 1, true, false, false, false, 'duplex', execa);
test('Can use duplexes with result.stdout', testGeneratorOutput, 1, true, true, false, false, 'duplex', execa);
test('Can use duplexes with result.stdio[2]', testGeneratorOutput, 2, true, false, false, false, 'duplex', execa);
test('Can use duplexes with result.stderr', testGeneratorOutput, 2, true, true, false, false, 'duplex', execa);
test('Can use duplexes with result.stdio[*] as output', testGeneratorOutput, 3, true, false, false, false, 'duplex', execa);
test('Can use duplexes with error.stdio[1]', testGeneratorOutput, 1, false, false, false, false, 'duplex', execa);
test('Can use duplexes with error.stdout', testGeneratorOutput, 1, false, true, false, false, 'duplex', execa);
test('Can use duplexes with error.stdio[2]', testGeneratorOutput, 2, false, false, false, false, 'duplex', execa);
test('Can use duplexes with error.stderr', testGeneratorOutput, 2, false, true, false, false, 'duplex', execa);
test('Can use duplexes with error.stdio[*] as output', testGeneratorOutput, 3, false, false, false, false, 'duplex', execa);
test('Can use duplexes with result.stdio[1], objectMode', testGeneratorOutput, 1, true, false, true, false, 'duplex', execa);
test('Can use duplexes with result.stdout, objectMode', testGeneratorOutput, 1, true, true, true, false, 'duplex', execa);
test('Can use duplexes with result.stdio[2], objectMode', testGeneratorOutput, 2, true, false, true, false, 'duplex', execa);
test('Can use duplexes with result.stderr, objectMode', testGeneratorOutput, 2, true, true, true, false, 'duplex', execa);
test('Can use duplexes with result.stdio[*] as output, objectMode', testGeneratorOutput, 3, true, false, true, false, 'duplex', execa);
test('Can use duplexes with error.stdio[1], objectMode', testGeneratorOutput, 1, false, false, true, false, 'duplex', execa);
test('Can use duplexes with error.stdout, objectMode', testGeneratorOutput, 1, false, true, true, false, 'duplex', execa);
test('Can use duplexes with error.stdio[2], objectMode', testGeneratorOutput, 2, false, false, true, false, 'duplex', execa);
test('Can use duplexes with error.stderr, objectMode', testGeneratorOutput, 2, false, true, true, false, 'duplex', execa);
test('Can use duplexes with error.stdio[*] as output, objectMode', testGeneratorOutput, 3, false, false, true, false, 'duplex', execa);
test('Can use duplexes with result.stdio[1], noop transform', testGeneratorOutput, 1, true, false, false, true, 'duplex', execa);
test('Can use duplexes with result.stdout, noop transform', testGeneratorOutput, 1, true, true, false, true, 'duplex', execa);
test('Can use duplexes with result.stdio[2], noop transform', testGeneratorOutput, 2, true, false, false, true, 'duplex', execa);
test('Can use duplexes with result.stderr, noop transform', testGeneratorOutput, 2, true, true, false, true, 'duplex', execa);
test('Can use duplexes with result.stdio[*] as output, noop transform', testGeneratorOutput, 3, true, false, false, true, 'duplex', execa);
test('Can use duplexes with error.stdio[1], noop transform', testGeneratorOutput, 1, false, false, false, true, 'duplex', execa);
test('Can use duplexes with error.stdout, noop transform', testGeneratorOutput, 1, false, true, false, true, 'duplex', execa);
test('Can use duplexes with error.stdio[2], noop transform', testGeneratorOutput, 2, false, false, false, true, 'duplex', execa);
test('Can use duplexes with error.stderr, noop transform', testGeneratorOutput, 2, false, true, false, true, 'duplex', execa);
test('Can use duplexes with error.stdio[*] as output, noop transform', testGeneratorOutput, 3, false, false, false, true, 'duplex', execa);
test('Can use duplexes with result.stdio[1], objectMode, noop transform', testGeneratorOutput, 1, true, false, true, true, 'duplex', execa);
test('Can use duplexes with result.stdout, objectMode, noop transform', testGeneratorOutput, 1, true, true, true, true, 'duplex', execa);
test('Can use duplexes with result.stdio[2], objectMode, noop transform', testGeneratorOutput, 2, true, false, true, true, 'duplex', execa);
test('Can use duplexes with result.stderr, objectMode, noop transform', testGeneratorOutput, 2, true, true, true, true, 'duplex', execa);
test('Can use duplexes with result.stdio[*] as output, objectMode, noop transform', testGeneratorOutput, 3, true, false, true, true, 'duplex', execa);
test('Can use duplexes with error.stdio[1], objectMode, noop transform', testGeneratorOutput, 1, false, false, true, true, 'duplex', execa);
test('Can use duplexes with error.stdout, objectMode, noop transform', testGeneratorOutput, 1, false, true, true, true, 'duplex', execa);
test('Can use duplexes with error.stdio[2], objectMode, noop transform', testGeneratorOutput, 2, false, false, true, true, 'duplex', execa);
test('Can use duplexes with error.stderr, objectMode, noop transform', testGeneratorOutput, 2, false, true, true, true, 'duplex', execa);
test('Can use duplexes with error.stdio[*] as output, objectMode, noop transform', testGeneratorOutput, 3, false, false, true, true, 'duplex', execa);
test('Can use webTransforms with result.stdio[1]', testGeneratorOutput, 1, true, false, false, false, 'webTransform', execa);
test('Can use webTransforms with result.stdout', testGeneratorOutput, 1, true, true, false, false, 'webTransform', execa);
test('Can use webTransforms with result.stdio[2]', testGeneratorOutput, 2, true, false, false, false, 'webTransform', execa);
test('Can use webTransforms with result.stderr', testGeneratorOutput, 2, true, true, false, false, 'webTransform', execa);
test('Can use webTransforms with result.stdio[*] as output', testGeneratorOutput, 3, true, false, false, false, 'webTransform', execa);
test('Can use webTransforms with error.stdio[1]', testGeneratorOutput, 1, false, false, false, false, 'webTransform', execa);
test('Can use webTransforms with error.stdout', testGeneratorOutput, 1, false, true, false, false, 'webTransform', execa);
test('Can use webTransforms with error.stdio[2]', testGeneratorOutput, 2, false, false, false, false, 'webTransform', execa);
test('Can use webTransforms with error.stderr', testGeneratorOutput, 2, false, true, false, false, 'webTransform', execa);
test('Can use webTransforms with error.stdio[*] as output', testGeneratorOutput, 3, false, false, false, false, 'webTransform', execa);
test('Can use webTransforms with result.stdio[1], objectMode', testGeneratorOutput, 1, true, false, true, false, 'webTransform', execa);
test('Can use webTransforms with result.stdout, objectMode', testGeneratorOutput, 1, true, true, true, false, 'webTransform', execa);
test('Can use webTransforms with result.stdio[2], objectMode', testGeneratorOutput, 2, true, false, true, false, 'webTransform', execa);
test('Can use webTransforms with result.stderr, objectMode', testGeneratorOutput, 2, true, true, true, false, 'webTransform', execa);
test('Can use webTransforms with result.stdio[*] as output, objectMode', testGeneratorOutput, 3, true, false, true, false, 'webTransform', execa);
test('Can use webTransforms with error.stdio[1], objectMode', testGeneratorOutput, 1, false, false, true, false, 'webTransform', execa);
test('Can use webTransforms with error.stdout, objectMode', testGeneratorOutput, 1, false, true, true, false, 'webTransform', execa);
test('Can use webTransforms with error.stdio[2], objectMode', testGeneratorOutput, 2, false, false, true, false, 'webTransform', execa);
test('Can use webTransforms with error.stderr, objectMode', testGeneratorOutput, 2, false, true, true, false, 'webTransform', execa);
test('Can use webTransforms with error.stdio[*] as output, objectMode', testGeneratorOutput, 3, false, false, true, false, 'webTransform', execa);
test('Can use webTransforms with result.stdio[1], noop transform', testGeneratorOutput, 1, true, false, false, true, 'webTransform', execa);
test('Can use webTransforms with result.stdout, noop transform', testGeneratorOutput, 1, true, true, false, true, 'webTransform', execa);
test('Can use webTransforms with result.stdio[2], noop transform', testGeneratorOutput, 2, true, false, false, true, 'webTransform', execa);
test('Can use webTransforms with result.stderr, noop transform', testGeneratorOutput, 2, true, true, false, true, 'webTransform', execa);
test('Can use webTransforms with result.stdio[*] as output, noop transform', testGeneratorOutput, 3, true, false, false, true, 'webTransform', execa);
test('Can use webTransforms with error.stdio[1], noop transform', testGeneratorOutput, 1, false, false, false, true, 'webTransform', execa);
test('Can use webTransforms with error.stdout, noop transform', testGeneratorOutput, 1, false, true, false, true, 'webTransform', execa);
test('Can use webTransforms with error.stdio[2], noop transform', testGeneratorOutput, 2, false, false, false, true, 'webTransform', execa);
test('Can use webTransforms with error.stderr, noop transform', testGeneratorOutput, 2, false, true, false, true, 'webTransform', execa);
test('Can use webTransforms with error.stdio[*] as output, noop transform', testGeneratorOutput, 3, false, false, false, true, 'webTransform', execa);
test('Can use webTransforms with result.stdio[1], objectMode, noop transform', testGeneratorOutput, 1, true, false, true, true, 'webTransform', execa);
test('Can use webTransforms with result.stdout, objectMode, noop transform', testGeneratorOutput, 1, true, true, true, true, 'webTransform', execa);
test('Can use webTransforms with result.stdio[2], objectMode, noop transform', testGeneratorOutput, 2, true, false, true, true, 'webTransform', execa);
test('Can use webTransforms with result.stderr, objectMode, noop transform', testGeneratorOutput, 2, true, true, true, true, 'webTransform', execa);
test('Can use webTransforms with result.stdio[*] as output, objectMode, noop transform', testGeneratorOutput, 3, true, false, true, true, 'webTransform', execa);
test('Can use webTransforms with error.stdio[1], objectMode, noop transform', testGeneratorOutput, 1, false, false, true, true, 'webTransform', execa);
test('Can use webTransforms with error.stdout, objectMode, noop transform', testGeneratorOutput, 1, false, true, true, true, 'webTransform', execa);
test('Can use webTransforms with error.stdio[2], objectMode, noop transform', testGeneratorOutput, 2, false, false, true, true, 'webTransform', execa);
test('Can use webTransforms with error.stderr, objectMode, noop transform', testGeneratorOutput, 2, false, true, true, true, 'webTransform', execa);
test('Can use webTransforms with error.stdio[*] as output, objectMode, noop transform', testGeneratorOutput, 3, false, false, true, true, 'webTransform', execa);
// eslint-disable-next-line max-params
const testGeneratorOutputPipe = async (t, fdNumber, useShortcutProperty, objectMode, addNoopTransform, type) => {
const {generators, output, getStreamMethod} = getOutputObjectMode(objectMode, addNoopTransform, type, true);
const subprocess = execa('noop-fd.js', [`${fdNumber}`, foobarString], getStdio(fdNumber, generators));
const stream = useShortcutProperty ? [subprocess.stdout, subprocess.stderr][fdNumber - 1] : subprocess.stdio[fdNumber];
const [result] = await Promise.all([getStreamMethod(stream), subprocess]);
t.deepEqual(result, output);
};
test('Can use generators with subprocess.stdio[1]', testGeneratorOutputPipe, 1, false, false, false, 'generator');
test('Can use generators with subprocess.stdout', testGeneratorOutputPipe, 1, true, false, false, 'generator');
test('Can use generators with subprocess.stdio[2]', testGeneratorOutputPipe, 2, false, false, false, 'generator');
test('Can use generators with subprocess.stderr', testGeneratorOutputPipe, 2, true, false, false, 'generator');
test('Can use generators with subprocess.stdio[*] as output', testGeneratorOutputPipe, 3, false, false, false, 'generator');
test('Can use generators with subprocess.stdio[1], objectMode', testGeneratorOutputPipe, 1, false, true, false, 'generator');
test('Can use generators with subprocess.stdout, objectMode', testGeneratorOutputPipe, 1, true, true, false, 'generator');
test('Can use generators with subprocess.stdio[2], objectMode', testGeneratorOutputPipe, 2, false, true, false, 'generator');
test('Can use generators with subprocess.stderr, objectMode', testGeneratorOutputPipe, 2, true, true, false, 'generator');
test('Can use generators with subprocess.stdio[*] as output, objectMode', testGeneratorOutputPipe, 3, false, true, false, 'generator');
test('Can use generators with subprocess.stdio[1], noop transform', testGeneratorOutputPipe, 1, false, false, true, 'generator');
test('Can use generators with subprocess.stdout, noop transform', testGeneratorOutputPipe, 1, true, false, true, 'generator');
test('Can use generators with subprocess.stdio[2], noop transform', testGeneratorOutputPipe, 2, false, false, true, 'generator');
test('Can use generators with subprocess.stderr, noop transform', testGeneratorOutputPipe, 2, true, false, true, 'generator');
test('Can use generators with subprocess.stdio[*] as output, noop transform', testGeneratorOutputPipe, 3, false, false, true, 'generator');
test('Can use generators with subprocess.stdio[1], objectMode, noop transform', testGeneratorOutputPipe, 1, false, true, true, 'generator');
test('Can use generators with subprocess.stdout, objectMode, noop transform', testGeneratorOutputPipe, 1, true, true, true, 'generator');
test('Can use generators with subprocess.stdio[2], objectMode, noop transform', testGeneratorOutputPipe, 2, false, true, true, 'generator');
test('Can use generators with subprocess.stderr, objectMode, noop transform', testGeneratorOutputPipe, 2, true, true, true, 'generator');
test('Can use generators with subprocess.stdio[*] as output, objectMode, noop transform', testGeneratorOutputPipe, 3, false, true, true, 'generator');
test('Can use duplexes with subprocess.stdio[1]', testGeneratorOutputPipe, 1, false, false, false, 'duplex');
test('Can use duplexes with subprocess.stdout', testGeneratorOutputPipe, 1, true, false, false, 'duplex');
test('Can use duplexes with subprocess.stdio[2]', testGeneratorOutputPipe, 2, false, false, false, 'duplex');
test('Can use duplexes with subprocess.stderr', testGeneratorOutputPipe, 2, true, false, false, 'duplex');
test('Can use duplexes with subprocess.stdio[*] as output', testGeneratorOutputPipe, 3, false, false, false, 'duplex');
test('Can use duplexes with subprocess.stdio[1], objectMode', testGeneratorOutputPipe, 1, false, true, false, 'duplex');
test('Can use duplexes with subprocess.stdout, objectMode', testGeneratorOutputPipe, 1, true, true, false, 'duplex');
test('Can use duplexes with subprocess.stdio[2], objectMode', testGeneratorOutputPipe, 2, false, true, false, 'duplex');
test('Can use duplexes with subprocess.stderr, objectMode', testGeneratorOutputPipe, 2, true, true, false, 'duplex');
test('Can use duplexes with subprocess.stdio[*] as output, objectMode', testGeneratorOutputPipe, 3, false, true, false, 'duplex');
test('Can use duplexes with subprocess.stdio[1], noop transform', testGeneratorOutputPipe, 1, false, false, true, 'duplex');
test('Can use duplexes with subprocess.stdout, noop transform', testGeneratorOutputPipe, 1, true, false, true, 'duplex');
test('Can use duplexes with subprocess.stdio[2], noop transform', testGeneratorOutputPipe, 2, false, false, true, 'duplex');
test('Can use duplexes with subprocess.stderr, noop transform', testGeneratorOutputPipe, 2, true, false, true, 'duplex');
test('Can use duplexes with subprocess.stdio[*] as output, noop transform', testGeneratorOutputPipe, 3, false, false, true, 'duplex');
test('Can use duplexes with subprocess.stdio[1], objectMode, noop transform', testGeneratorOutputPipe, 1, false, true, true, 'duplex');
test('Can use duplexes with subprocess.stdout, objectMode, noop transform', testGeneratorOutputPipe, 1, true, true, true, 'duplex');
test('Can use duplexes with subprocess.stdio[2], objectMode, noop transform', testGeneratorOutputPipe, 2, false, true, true, 'duplex');
test('Can use duplexes with subprocess.stderr, objectMode, noop transform', testGeneratorOutputPipe, 2, true, true, true, 'duplex');
test('Can use duplexes with subprocess.stdio[*] as output, objectMode, noop transform', testGeneratorOutputPipe, 3, false, true, true, 'duplex');
test('Can use webTransforms with subprocess.stdio[1]', testGeneratorOutputPipe, 1, false, false, false, 'webTransform');
test('Can use webTransforms with subprocess.stdout', testGeneratorOutputPipe, 1, true, false, false, 'webTransform');
test('Can use webTransforms with subprocess.stdio[2]', testGeneratorOutputPipe, 2, false, false, false, 'webTransform');
test('Can use webTransforms with subprocess.stderr', testGeneratorOutputPipe, 2, true, false, false, 'webTransform');
test('Can use webTransforms with subprocess.stdio[*] as output', testGeneratorOutputPipe, 3, false, false, false, 'webTransform');
test('Can use webTransforms with subprocess.stdio[1], objectMode', testGeneratorOutputPipe, 1, false, true, false, 'webTransform');
test('Can use webTransforms with subprocess.stdout, objectMode', testGeneratorOutputPipe, 1, true, true, false, 'webTransform');
test('Can use webTransforms with subprocess.stdio[2], objectMode', testGeneratorOutputPipe, 2, false, true, false, 'webTransform');
test('Can use webTransforms with subprocess.stderr, objectMode', testGeneratorOutputPipe, 2, true, true, false, 'webTransform');
test('Can use webTransforms with subprocess.stdio[*] as output, objectMode', testGeneratorOutputPipe, 3, false, true, false, 'webTransform');
test('Can use webTransforms with subprocess.stdio[1], noop transform', testGeneratorOutputPipe, 1, false, false, true, 'webTransform');
test('Can use webTransforms with subprocess.stdout, noop transform', testGeneratorOutputPipe, 1, true, false, true, 'webTransform');
test('Can use webTransforms with subprocess.stdio[2], noop transform', testGeneratorOutputPipe, 2, false, false, true, 'webTransform');
test('Can use webTransforms with subprocess.stderr, noop transform', testGeneratorOutputPipe, 2, true, false, true, 'webTransform');
test('Can use webTransforms with subprocess.stdio[*] as output, noop transform', testGeneratorOutputPipe, 3, false, false, true, 'webTransform');
test('Can use webTransforms with subprocess.stdio[1], objectMode, noop transform', testGeneratorOutputPipe, 1, false, true, true, 'webTransform');
test('Can use webTransforms with subprocess.stdout, objectMode, noop transform', testGeneratorOutputPipe, 1, true, true, true, 'webTransform');
test('Can use webTransforms with subprocess.stdio[2], objectMode, noop transform', testGeneratorOutputPipe, 2, false, true, true, 'webTransform');
test('Can use webTransforms with subprocess.stderr, objectMode, noop transform', testGeneratorOutputPipe, 2, true, true, true, 'webTransform');
test('Can use webTransforms with subprocess.stdio[*] as output, objectMode, noop transform', testGeneratorOutputPipe, 3, false, true, true, 'webTransform');
================================================
FILE: test/transform/generator-return.js
================================================
import {Buffer} from 'node:buffer';
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString, foobarUint8Array, foobarBuffer} from '../helpers/input.js';
import {getOutputGenerator, convertTransformToFinal} from '../helpers/generator.js';
setFixtureDirectory();
// eslint-disable-next-line max-params
const testGeneratorReturnType = async (t, input, encoding, reject, objectMode, final, execaMethod) => {
const fixtureName = reject ? 'noop-fd.js' : 'noop-fail.js';
const {stdout} = await execaMethod(fixtureName, ['1', foobarString], {
stdout: convertTransformToFinal(getOutputGenerator(input)(objectMode, true), final),
encoding,
reject,
});
const typeofChunk = Array.isArray(stdout) ? stdout[0] : stdout;
const output = Buffer.from(typeofChunk, encoding === 'buffer' || objectMode ? undefined : encoding).toString();
t.is(output, foobarString);
};
test('Generator can return string with default encoding', testGeneratorReturnType, foobarString, 'utf8', true, false, false, execa);
test('Generator can return Uint8Array with default encoding', testGeneratorReturnType, foobarUint8Array, 'utf8', true, false, false, execa);
test('Generator can return Buffer with default encoding', testGeneratorReturnType, foobarBuffer, 'utf8', true, false, false, execa);
test('Generator can return string with encoding "utf16le"', testGeneratorReturnType, foobarString, 'utf16le', true, false, false, execa);
test('Generator can return Uint8Array with encoding "utf16le"', testGeneratorReturnType, foobarUint8Array, 'utf16le', true, false, false, execa);
test('Generator can return Buffer with encoding "utf16le"', testGeneratorReturnType, foobarBuffer, 'utf16le', true, false, false, execa);
test('Generator can return string with encoding "buffer"', testGeneratorReturnType, foobarString, 'buffer', true, false, false, execa);
test('Generator can return Uint8Array with encoding "buffer"', testGeneratorReturnType, foobarUint8Array, 'buffer', true, false, false, execa);
test('Generator can return Buffer with encoding "buffer"', testGeneratorReturnType, foobarBuffer, 'buffer', true, false, false, execa);
test('Generator can return string with encoding "hex"', testGeneratorReturnType, foobarString, 'hex', true, false, false, execa);
test('Generator can return Uint8Array with encoding "hex"', testGeneratorReturnType, foobarUint8Array, 'hex', true, false, false, execa);
test('Generator can return Buffer with encoding "hex"', testGeneratorReturnType, foobarBuffer, 'hex', true, false, false, execa);
test('Generator can return string with default encoding, failure', testGeneratorReturnType, foobarString, 'utf8', false, false, false, execa);
test('Generator can return Uint8Array with default encoding, failure', testGeneratorReturnType, foobarUint8Array, 'utf8', false, false, false, execa);
test('Generator can return string with encoding "utf16le", failure', testGeneratorReturnType, foobarString, 'utf16le', false, false, false, execa);
test('Generator can return Uint8Array with encoding "utf16le", failure', testGeneratorReturnType, foobarUint8Array, 'utf16le', false, false, false, execa);
test('Generator can return string with encoding "buffer", failure', testGeneratorReturnType, foobarString, 'buffer', false, false, false, execa);
test('Generator can return Uint8Array with encoding "buffer", failure', testGeneratorReturnType, foobarUint8Array, 'buffer', false, false, false, execa);
test('Generator can return string with encoding "hex", failure', testGeneratorReturnType, foobarString, 'hex', false, false, false, execa);
test('Generator can return Uint8Array with encoding "hex", failure', testGeneratorReturnType, foobarUint8Array, 'hex', false, false, false, execa);
test('Generator can return string with default encoding, objectMode', testGeneratorReturnType, foobarString, 'utf8', true, true, false, execa);
test('Generator can return Uint8Array with default encoding, objectMode', testGeneratorReturnType, foobarUint8Array, 'utf8', true, true, false, execa);
test('Generator can return string with encoding "utf16le", objectMode', testGeneratorReturnType, foobarString, 'utf16le', true, true, false, execa);
test('Generator can return Uint8Array with encoding "utf16le", objectMode', testGeneratorReturnType, foobarUint8Array, 'utf16le', true, true, false, execa);
test('Generator can return string with encoding "buffer", objectMode', testGeneratorReturnType, foobarString, 'buffer', true, true, false, execa);
test('Generator can return Uint8Array with encoding "buffer", objectMode', testGeneratorReturnType, foobarUint8Array, 'buffer', true, true, false, execa);
test('Generator can return string with encoding "hex", objectMode', testGeneratorReturnType, foobarString, 'hex', true, true, false, execa);
test('Generator can return Uint8Array with encoding "hex", objectMode', testGeneratorReturnType, foobarUint8Array, 'hex', true, true, false, execa);
test('Generator can return string with default encoding, objectMode, failure', testGeneratorReturnType, foobarString, 'utf8', false, true, false, execa);
test('Generator can return Uint8Array with default encoding, objectMode, failure', testGeneratorReturnType, foobarUint8Array, 'utf8', false, true, false, execa);
test('Generator can return string with encoding "utf16le", objectMode, failure', testGeneratorReturnType, foobarString, 'utf16le', false, true, false, execa);
test('Generator can return Uint8Array with encoding "utf16le", objectMode, failure', testGeneratorReturnType, foobarUint8Array, 'utf16le', false, true, false, execa);
test('Generator can return string with encoding "buffer", objectMode, failure', testGeneratorReturnType, foobarString, 'buffer', false, true, false, execa);
test('Generator can return Uint8Array with encoding "buffer", objectMode, failure', testGeneratorReturnType, foobarUint8Array, 'buffer', false, true, false, execa);
test('Generator can return string with encoding "hex", objectMode, failure', testGeneratorReturnType, foobarString, 'hex', false, true, false, execa);
test('Generator can return Uint8Array with encoding "hex", objectMode, failure', testGeneratorReturnType, foobarUint8Array, 'hex', false, true, false, execa);
test('Generator can return final string with default encoding', testGeneratorReturnType, foobarString, 'utf8', true, false, true, execa);
test('Generator can return final Uint8Array with default encoding', testGeneratorReturnType, foobarUint8Array, 'utf8', true, false, true, execa);
test('Generator can return final string with encoding "utf16le"', testGeneratorReturnType, foobarString, 'utf16le', true, false, true, execa);
test('Generator can return final Uint8Array with encoding "utf16le"', testGeneratorReturnType, foobarUint8Array, 'utf16le', true, false, true, execa);
test('Generator can return final string with encoding "buffer"', testGeneratorReturnType, foobarString, 'buffer', true, false, true, execa);
test('Generator can return final Uint8Array with encoding "buffer"', testGeneratorReturnType, foobarUint8Array, 'buffer', true, false, true, execa);
test('Generator can return final string with encoding "hex"', testGeneratorReturnType, foobarString, 'hex', true, false, true, execa);
test('Generator can return final Uint8Array with encoding "hex"', testGeneratorReturnType, foobarUint8Array, 'hex', true, false, true, execa);
test('Generator can return final string with default encoding, failure', testGeneratorReturnType, foobarString, 'utf8', false, false, true, execa);
test('Generator can return final Uint8Array with default encoding, failure', testGeneratorReturnType, foobarUint8Array, 'utf8', false, false, true, execa);
test('Generator can return final string with encoding "utf16le", failure', testGeneratorReturnType, foobarString, 'utf16le', false, false, true, execa);
test('Generator can return final Uint8Array with encoding "utf16le", failure', testGeneratorReturnType, foobarUint8Array, 'utf16le', false, false, true, execa);
test('Generator can return final string with encoding "buffer", failure', testGeneratorReturnType, foobarString, 'buffer', false, false, true, execa);
test('Generator can return final Uint8Array with encoding "buffer", failure', testGeneratorReturnType, foobarUint8Array, 'buffer', false, false, true, execa);
test('Generator can return final string with encoding "hex", failure', testGeneratorReturnType, foobarString, 'hex', false, false, true, execa);
test('Generator can return final Uint8Array with encoding "hex", failure', testGeneratorReturnType, foobarUint8Array, 'hex', false, false, true, execa);
test('Generator can return final string with default encoding, objectMode', testGeneratorReturnType, foobarString, 'utf8', true, true, true, execa);
test('Generator can return final Uint8Array with default encoding, objectMode', testGeneratorReturnType, foobarUint8Array, 'utf8', true, true, true, execa);
test('Generator can return final string with encoding "utf16le", objectMode', testGeneratorReturnType, foobarString, 'utf16le', true, true, true, execa);
test('Generator can return final Uint8Array with encoding "utf16le", objectMode', testGeneratorReturnType, foobarUint8Array, 'utf16le', true, true, true, execa);
test('Generator can return final string with encoding "buffer", objectMode', testGeneratorReturnType, foobarString, 'buffer', true, true, true, execa);
test('Generator can return final Uint8Array with encoding "buffer", objectMode', testGeneratorReturnType, foobarUint8Array, 'buffer', true, true, true, execa);
test('Generator can return final string with encoding "hex", objectMode', testGeneratorReturnType, foobarString, 'hex', true, true, true, execa);
test('Generator can return final Uint8Array with encoding "hex", objectMode', testGeneratorReturnType, foobarUint8Array, 'hex', true, true, true, execa);
test('Generator can return final string with default encoding, objectMode, failure', testGeneratorReturnType, foobarString, 'utf8', false, true, true, execa);
test('Generator can return final Uint8Array with default encoding, objectMode, failure', testGeneratorReturnType, foobarUint8Array, 'utf8', false, true, true, execa);
test('Generator can return final string with encoding "utf16le", objectMode, failure', testGeneratorReturnType, foobarString, 'utf16le', false, true, true, execa);
test('Generator can return final Uint8Array with encoding "utf16le", objectMode, failure', testGeneratorReturnType, foobarUint8Array, 'utf16le', false, true, true, execa);
test('Generator can return final string with encoding "buffer", objectMode, failure', testGeneratorReturnType, foobarString, 'buffer', false, true, true, execa);
test('Generator can return final Uint8Array with encoding "buffer", objectMode, failure', testGeneratorReturnType, foobarUint8Array, 'buffer', false, true, true, execa);
test('Generator can return final string with encoding "hex", objectMode, failure', testGeneratorReturnType, foobarString, 'hex', false, true, true, execa);
test('Generator can return final Uint8Array with encoding "hex", objectMode, failure', testGeneratorReturnType, foobarUint8Array, 'hex', false, true, true, execa);
test('Generator can return string with default encoding, sync', testGeneratorReturnType, foobarString, 'utf8', true, false, false, execaSync);
test('Generator can return Uint8Array with default encoding, sync', testGeneratorReturnType, foobarUint8Array, 'utf8', true, false, false, execaSync);
test('Generator can return Buffer with default encoding, sync', testGeneratorReturnType, foobarBuffer, 'utf8', true, false, false, execaSync);
test('Generator can return string with encoding "utf16le", sync', testGeneratorReturnType, foobarString, 'utf16le', true, false, false, execaSync);
test('Generator can return Uint8Array with encoding "utf16le", sync', testGeneratorReturnType, foobarUint8Array, 'utf16le', true, false, false, execaSync);
test('Generator can return Buffer with encoding "utf16le", sync', testGeneratorReturnType, foobarBuffer, 'utf16le', true, false, false, execaSync);
test('Generator can return string with encoding "buffer", sync', testGeneratorReturnType, foobarString, 'buffer', true, false, false, execaSync);
test('Generator can return Uint8Array with encoding "buffer", sync', testGeneratorReturnType, foobarUint8Array, 'buffer', true, false, false, execaSync);
test('Generator can return Buffer with encoding "buffer", sync', testGeneratorReturnType, foobarBuffer, 'buffer', true, false, false, execaSync);
test('Generator can return string with encoding "hex", sync', testGeneratorReturnType, foobarString, 'hex', true, false, false, execaSync);
test('Generator can return Uint8Array with encoding "hex", sync', testGeneratorReturnType, foobarUint8Array, 'hex', true, false, false, execaSync);
test('Generator can return Buffer with encoding "hex", sync', testGeneratorReturnType, foobarBuffer, 'hex', true, false, false, execaSync);
test('Generator can return string with default encoding, failure, sync', testGeneratorReturnType, foobarString, 'utf8', false, false, false, execaSync);
test('Generator can return Uint8Array with default encoding, failure, sync', testGeneratorReturnType, foobarUint8Array, 'utf8', false, false, false, execaSync);
test('Generator can return string with encoding "utf16le", failure, sync', testGeneratorReturnType, foobarString, 'utf16le', false, false, false, execaSync);
test('Generator can return Uint8Array with encoding "utf16le", failure, sync', testGeneratorReturnType, foobarUint8Array, 'utf16le', false, false, false, execaSync);
test('Generator can return string with encoding "buffer", failure, sync', testGeneratorReturnType, foobarString, 'buffer', false, false, false, execaSync);
test('Generator can return Uint8Array with encoding "buffer", failure, sync', testGeneratorReturnType, foobarUint8Array, 'buffer', false, false, false, execaSync);
test('Generator can return string with encoding "hex", failure, sync', testGeneratorReturnType, foobarString, 'hex', false, false, false, execaSync);
test('Generator can return Uint8Array with encoding "hex", failure, sync', testGeneratorReturnType, foobarUint8Array, 'hex', false, false, false, execaSync);
test('Generator can return string with default encoding, objectMode, sync', testGeneratorReturnType, foobarString, 'utf8', true, true, false, execaSync);
test('Generator can return Uint8Array with default encoding, objectMode, sync', testGeneratorReturnType, foobarUint8Array, 'utf8', true, true, false, execaSync);
test('Generator can return string with encoding "utf16le", objectMode, sync', testGeneratorReturnType, foobarString, 'utf16le', true, true, false, execaSync);
test('Generator can return Uint8Array with encoding "utf16le", objectMode, sync', testGeneratorReturnType, foobarUint8Array, 'utf16le', true, true, false, execaSync);
test('Generator can return string with encoding "buffer", objectMode, sync', testGeneratorReturnType, foobarString, 'buffer', true, true, false, execaSync);
test('Generator can return Uint8Array with encoding "buffer", objectMode, sync', testGeneratorReturnType, foobarUint8Array, 'buffer', true, true, false, execaSync);
test('Generator can return string with encoding "hex", objectMode, sync', testGeneratorReturnType, foobarString, 'hex', true, true, false, execaSync);
test('Generator can return Uint8Array with encoding "hex", objectMode, sync', testGeneratorReturnType, foobarUint8Array, 'hex', true, true, false, execaSync);
test('Generator can return string with default encoding, objectMode, failure, sync', testGeneratorReturnType, foobarString, 'utf8', false, true, false, execaSync);
test('Generator can return Uint8Array with default encoding, objectMode, failure, sync', testGeneratorReturnType, foobarUint8Array, 'utf8', false, true, false, execaSync);
test('Generator can return string with encoding "utf16le", objectMode, failure, sync', testGeneratorReturnType, foobarString, 'utf16le', false, true, false, execaSync);
test('Generator can return Uint8Array with encoding "utf16le", objectMode, failure, sync', testGeneratorReturnType, foobarUint8Array, 'utf16le', false, true, false, execaSync);
test('Generator can return string with encoding "buffer", objectMode, failure, sync', testGeneratorReturnType, foobarString, 'buffer', false, true, false, execaSync);
test('Generator can return Uint8Array with encoding "buffer", objectMode, failure, sync', testGeneratorReturnType, foobarUint8Array, 'buffer', false, true, false, execaSync);
test('Generator can return string with encoding "hex", objectMode, failure, sync', testGeneratorReturnType, foobarString, 'hex', false, true, false, execaSync);
test('Generator can return Uint8Array with encoding "hex", objectMode, failure, sync', testGeneratorReturnType, foobarUint8Array, 'hex', false, true, false, execaSync);
test('Generator can return final string with default encoding, sync', testGeneratorReturnType, foobarString, 'utf8', true, false, true, execaSync);
test('Generator can return final Uint8Array with default encoding, sync', testGeneratorReturnType, foobarUint8Array, 'utf8', true, false, true, execaSync);
test('Generator can return final string with encoding "utf16le", sync', testGeneratorReturnType, foobarString, 'utf16le', true, false, true, execaSync);
test('Generator can return final Uint8Array with encoding "utf16le", sync', testGeneratorReturnType, foobarUint8Array, 'utf16le', true, false, true, execaSync);
test('Generator can return final string with encoding "buffer", sync', testGeneratorReturnType, foobarString, 'buffer', true, false, true, execaSync);
test('Generator can return final Uint8Array with encoding "buffer", sync', testGeneratorReturnType, foobarUint8Array, 'buffer', true, false, true, execaSync);
test('Generator can return final string with encoding "hex", sync', testGeneratorReturnType, foobarString, 'hex', true, false, true, execaSync);
test('Generator can return final Uint8Array with encoding "hex", sync', testGeneratorReturnType, foobarUint8Array, 'hex', true, false, true, execaSync);
test('Generator can return final string with default encoding, failure, sync', testGeneratorReturnType, foobarString, 'utf8', false, false, true, execaSync);
test('Generator can return final Uint8Array with default encoding, failure, sync', testGeneratorReturnType, foobarUint8Array, 'utf8', false, false, true, execaSync);
test('Generator can return final string with encoding "utf16le", failure, sync', testGeneratorReturnType, foobarString, 'utf16le', false, false, true, execaSync);
test('Generator can return final Uint8Array with encoding "utf16le", failure, sync', testGeneratorReturnType, foobarUint8Array, 'utf16le', false, false, true, execaSync);
test('Generator can return final string with encoding "buffer", failure, sync', testGeneratorReturnType, foobarString, 'buffer', false, false, true, execaSync);
test('Generator can return final Uint8Array with encoding "buffer", failure, sync', testGeneratorReturnType, foobarUint8Array, 'buffer', false, false, true, execaSync);
test('Generator can return final string with encoding "hex", failure, sync', testGeneratorReturnType, foobarString, 'hex', false, false, true, execaSync);
test('Generator can return final Uint8Array with encoding "hex", failure, sync', testGeneratorReturnType, foobarUint8Array, 'hex', false, false, true, execaSync);
test('Generator can return final string with default encoding, objectMode, sync', testGeneratorReturnType, foobarString, 'utf8', true, true, true, execaSync);
test('Generator can return final Uint8Array with default encoding, objectMode, sync', testGeneratorReturnType, foobarUint8Array, 'utf8', true, true, true, execaSync);
test('Generator can return final string with encoding "utf16le", objectMode, sync', testGeneratorReturnType, foobarString, 'utf16le', true, true, true, execaSync);
test('Generator can return final Uint8Array with encoding "utf16le", objectMode, sync', testGeneratorReturnType, foobarUint8Array, 'utf16le', true, true, true, execaSync);
test('Generator can return final string with encoding "buffer", objectMode, sync', testGeneratorReturnType, foobarString, 'buffer', true, true, true, execaSync);
test('Generator can return final Uint8Array with encoding "buffer", objectMode, sync', testGeneratorReturnType, foobarUint8Array, 'buffer', true, true, true, execaSync);
test('Generator can return final string with encoding "hex", objectMode, sync', testGeneratorReturnType, foobarString, 'hex', true, true, true, execaSync);
test('Generator can return final Uint8Array with encoding "hex", objectMode, sync', testGeneratorReturnType, foobarUint8Array, 'hex', true, true, true, execaSync);
test('Generator can return final string with default encoding, objectMode, failure, sync', testGeneratorReturnType, foobarString, 'utf8', false, true, true, execaSync);
test('Generator can return final Uint8Array with default encoding, objectMode, failure, sync', testGeneratorReturnType, foobarUint8Array, 'utf8', false, true, true, execaSync);
test('Generator can return final string with encoding "utf16le", objectMode, failure, sync', testGeneratorReturnType, foobarString, 'utf16le', false, true, true, execaSync);
test('Generator can return final Uint8Array with encoding "utf16le", objectMode, failure, sync', testGeneratorReturnType, foobarUint8Array, 'utf16le', false, true, true, execaSync);
test('Generator can return final string with encoding "buffer", objectMode, failure, sync', testGeneratorReturnType, foobarString, 'buffer', false, true, true, execaSync);
test('Generator can return final Uint8Array with encoding "buffer", objectMode, failure, sync', testGeneratorReturnType, foobarUint8Array, 'buffer', false, true, true, execaSync);
test('Generator can return final string with encoding "hex", objectMode, failure, sync', testGeneratorReturnType, foobarString, 'hex', false, true, true, execaSync);
test('Generator can return final Uint8Array with encoding "hex", objectMode, failure, sync', testGeneratorReturnType, foobarUint8Array, 'hex', false, true, true, execaSync);
================================================
FILE: test/transform/normalize-transform.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString, foobarUppercase, foobarUint8Array} from '../helpers/input.js';
import {casedSuffix} from '../helpers/generator.js';
import {generatorsMap} from '../helpers/map.js';
setFixtureDirectory();
const testAppendInput = async (t, reversed, type, execaMethod) => {
const stdin = [foobarUint8Array, generatorsMap[type].uppercase(), generatorsMap[type].append()];
const reversedStdin = reversed ? stdin.reverse() : stdin;
const {stdout} = await execaMethod('stdin-fd.js', ['0'], {stdin: reversedStdin});
const reversedSuffix = reversed ? casedSuffix.toUpperCase() : casedSuffix;
t.is(stdout, `${foobarUppercase}${reversedSuffix}`);
};
test('Can use multiple generators as input', testAppendInput, false, 'generator', execa);
test('Can use multiple generators as input, reversed', testAppendInput, true, 'generator', execa);
test('Can use multiple generators as input, sync', testAppendInput, false, 'generator', execaSync);
test('Can use multiple generators as input, reversed, sync', testAppendInput, true, 'generator', execaSync);
test('Can use multiple duplexes as input', testAppendInput, false, 'duplex', execa);
test('Can use multiple duplexes as input, reversed', testAppendInput, true, 'duplex', execa);
test('Can use multiple webTransforms as input', testAppendInput, false, 'webTransform', execa);
test('Can use multiple webTransforms as input, reversed', testAppendInput, true, 'webTransform', execa);
const testAppendOutput = async (t, reversed, type, execaMethod) => {
const stdoutOption = [generatorsMap[type].uppercase(), generatorsMap[type].append()];
const reversedStdoutOption = reversed ? stdoutOption.reverse() : stdoutOption;
const {stdout} = await execaMethod('noop-fd.js', ['1', foobarString], {stdout: reversedStdoutOption});
const reversedSuffix = reversed ? casedSuffix.toUpperCase() : casedSuffix;
t.is(stdout, `${foobarUppercase}${reversedSuffix}`);
};
test('Can use multiple generators as output', testAppendOutput, false, 'generator', execa);
test('Can use multiple generators as output, reversed', testAppendOutput, true, 'generator', execa);
test('Can use multiple generators as output, sync', testAppendOutput, false, 'generator', execaSync);
test('Can use multiple generators as output, reversed, sync', testAppendOutput, true, 'generator', execaSync);
test('Can use multiple duplexes as output', testAppendOutput, false, 'duplex', execa);
test('Can use multiple duplexes as output, reversed', testAppendOutput, true, 'duplex', execa);
test('Can use multiple webTransforms as output', testAppendOutput, false, 'webTransform', execa);
test('Can use multiple webTransforms as output, reversed', testAppendOutput, true, 'webTransform', execa);
const testGeneratorSyntax = async (t, type, usePlainObject, execaMethod) => {
const transform = generatorsMap[type].uppercase();
const {stdout} = await execaMethod('noop-fd.js', ['1', foobarString], {stdout: usePlainObject ? transform : transform.transform});
t.is(stdout, foobarUppercase);
};
test('Can pass generators with an options plain object', testGeneratorSyntax, 'generator', false, execa);
test('Can pass generators without an options plain object', testGeneratorSyntax, 'generator', true, execa);
test('Can pass generators with an options plain object, sync', testGeneratorSyntax, 'generator', false, execaSync);
test('Can pass generators without an options plain object, sync', testGeneratorSyntax, 'generator', true, execaSync);
test('Can pass webTransforms with an options plain object', testGeneratorSyntax, 'webTransform', true, execa);
test('Can pass webTransforms without an options plain object', testGeneratorSyntax, 'webTransform', false, execa);
================================================
FILE: test/transform/split-binary.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {getOutputsGenerator, resultGenerator} from '../helpers/generator.js';
import {
simpleFull,
simpleChunks,
simpleFullUint8Array,
simpleFullUtf16Inverted,
simpleFullUtf16Uint8Array,
simpleChunksUint8Array,
simpleFullEnd,
simpleFullEndUtf16Inverted,
simpleFullHex,
simpleLines,
noNewlinesChunks,
} from '../helpers/lines.js';
setFixtureDirectory();
// eslint-disable-next-line max-params
const testBinaryOption = async (t, binary, input, expectedLines, expectedOutput, objectMode, preserveNewlines, encoding, execaMethod) => {
const lines = [];
const {stdout} = await execaMethod('noop.js', {
stdout: [
getOutputsGenerator(input)(false, true),
resultGenerator(lines)(objectMode, binary, preserveNewlines),
],
stripFinalNewline: false,
encoding,
});
t.deepEqual(lines, expectedLines);
t.deepEqual(stdout, expectedOutput);
};
test('Does not split lines when "binary" is true', testBinaryOption, true, simpleChunks, simpleChunksUint8Array, simpleFull, false, true, 'utf8', execa);
test('Splits lines when "binary" is false', testBinaryOption, false, simpleChunks, simpleLines, simpleFull, false, true, 'utf8', execa);
test('Splits lines when "binary" is undefined', testBinaryOption, undefined, simpleChunks, simpleLines, simpleFull, false, true, 'utf8', execa);
test('Does not split lines when "binary" is true, encoding "utf16le"', testBinaryOption, true, simpleChunks, simpleChunksUint8Array, simpleFullUtf16Inverted, false, true, 'utf16le', execa);
test('Splits lines when "binary" is false, encoding "utf16le"', testBinaryOption, false, [simpleFullUtf16Uint8Array], simpleLines, simpleFullUtf16Inverted, false, true, 'utf16le', execa);
test('Splits lines when "binary" is undefined, encoding "utf16le"', testBinaryOption, undefined, [simpleFullUtf16Uint8Array], simpleLines, simpleFullUtf16Inverted, false, true, 'utf16le', execa);
test('Does not split lines when "binary" is true, encoding "buffer"', testBinaryOption, true, simpleChunks, simpleChunksUint8Array, simpleFullUint8Array, false, true, 'buffer', execa);
test('Does not split lines when "binary" is undefined, encoding "buffer"', testBinaryOption, undefined, simpleChunks, simpleChunksUint8Array, simpleFullUint8Array, false, true, 'buffer', execa);
test('Does not split lines when "binary" is false, encoding "buffer"', testBinaryOption, false, simpleChunks, simpleChunksUint8Array, simpleFullUint8Array, false, true, 'buffer', execa);
test('Does not split lines when "binary" is true, encoding "hex"', testBinaryOption, true, simpleChunks, simpleChunksUint8Array, simpleFullHex, false, true, 'hex', execa);
test('Does not split lines when "binary" is undefined, encoding "hex"', testBinaryOption, undefined, simpleChunks, simpleChunksUint8Array, simpleFullHex, false, true, 'hex', execa);
test('Does not split lines when "binary" is false, encoding "hex"', testBinaryOption, false, simpleChunks, simpleChunksUint8Array, simpleFullHex, false, true, 'hex', execa);
test('Does not split lines when "binary" is true, objectMode', testBinaryOption, true, simpleChunks, simpleChunksUint8Array, simpleChunksUint8Array, true, true, 'utf8', execa);
test('Splits lines when "binary" is false, objectMode', testBinaryOption, false, simpleChunks, simpleLines, simpleLines, true, true, 'utf8', execa);
test('Splits lines when "binary" is undefined, objectMode', testBinaryOption, undefined, simpleChunks, simpleLines, simpleLines, true, true, 'utf8', execa);
test('Does not split lines when "binary" is true, preserveNewlines', testBinaryOption, true, simpleChunks, simpleChunksUint8Array, simpleFull, false, false, 'utf8', execa);
test('Splits lines when "binary" is false, preserveNewlines', testBinaryOption, false, simpleChunks, noNewlinesChunks, simpleFullEnd, false, false, 'utf8', execa);
test('Splits lines when "binary" is undefined, preserveNewlines', testBinaryOption, undefined, simpleChunks, noNewlinesChunks, simpleFullEnd, false, false, 'utf8', execa);
test('Does not split lines when "binary" is true, preserveNewlines, encoding "utf16le"', testBinaryOption, true, simpleChunks, simpleChunksUint8Array, simpleFullUtf16Inverted, false, false, 'utf16le', execa);
test('Splits lines when "binary" is false, preserveNewlines, encoding "utf16le"', testBinaryOption, false, [simpleFullUtf16Uint8Array], noNewlinesChunks, simpleFullEndUtf16Inverted, false, false, 'utf16le', execa);
test('Splits lines when "binary" is undefined, preserveNewlines, encoding "utf16le"', testBinaryOption, undefined, [simpleFullUtf16Uint8Array], noNewlinesChunks, simpleFullEndUtf16Inverted, false, false, 'utf16le', execa);
test('Does not split lines when "binary" is true, encoding "buffer", preserveNewlines', testBinaryOption, true, simpleChunks, simpleChunksUint8Array, simpleFullUint8Array, false, false, 'buffer', execa);
test('Does not split lines when "binary" is undefined, encoding "buffer", preserveNewlines', testBinaryOption, undefined, simpleChunks, simpleChunksUint8Array, simpleFullUint8Array, false, false, 'buffer', execa);
test('Does not split lines when "binary" is false, encoding "buffer", preserveNewlines', testBinaryOption, false, simpleChunks, simpleChunksUint8Array, simpleFullUint8Array, false, false, 'buffer', execa);
test('Does not split lines when "binary" is true, objectMode, preserveNewlines', testBinaryOption, true, simpleChunks, simpleChunksUint8Array, simpleChunksUint8Array, true, false, 'utf8', execa);
test('Does not split lines when "binary" is true, encoding "hex", preserveNewlines', testBinaryOption, true, simpleChunks, simpleChunksUint8Array, simpleFullHex, false, false, 'hex', execa);
test('Does not split lines when "binary" is undefined, encoding "hex", preserveNewlines', testBinaryOption, undefined, simpleChunks, simpleChunksUint8Array, simpleFullHex, false, false, 'hex', execa);
test('Does not split lines when "binary" is false, encoding "hex", preserveNewlines', testBinaryOption, false, simpleChunks, simpleChunksUint8Array, simpleFullHex, false, false, 'hex', execa);
test('Splits lines when "binary" is false, objectMode, preserveNewlines', testBinaryOption, false, simpleChunks, noNewlinesChunks, noNewlinesChunks, true, false, 'utf8', execa);
test('Splits lines when "binary" is undefined, objectMode, preserveNewlines', testBinaryOption, undefined, simpleChunks, noNewlinesChunks, noNewlinesChunks, true, false, 'utf8', execa);
test('Does not split lines when "binary" is true, sync', testBinaryOption, true, simpleChunks, simpleChunksUint8Array, simpleFull, false, true, 'utf8', execaSync);
test('Splits lines when "binary" is false, sync', testBinaryOption, false, simpleChunks, simpleLines, simpleFull, false, true, 'utf8', execaSync);
test('Splits lines when "binary" is undefined, sync', testBinaryOption, undefined, simpleChunks, simpleLines, simpleFull, false, true, 'utf8', execaSync);
test('Does not split lines when "binary" is true, encoding "utf16le", sync', testBinaryOption, true, simpleChunks, simpleChunksUint8Array, simpleFullUtf16Inverted, false, true, 'utf16le', execaSync);
test('Splits lines when "binary" is false, encoding "utf16le", sync', testBinaryOption, false, [simpleFullUtf16Uint8Array], simpleLines, simpleFullUtf16Inverted, false, true, 'utf16le', execaSync);
test('Splits lines when "binary" is undefined, encoding "utf16le", sync', testBinaryOption, undefined, [simpleFullUtf16Uint8Array], simpleLines, simpleFullUtf16Inverted, false, true, 'utf16le', execaSync);
test('Does not split lines when "binary" is true, encoding "buffer", sync', testBinaryOption, true, simpleChunks, simpleChunksUint8Array, simpleFullUint8Array, false, true, 'buffer', execaSync);
test('Does not split lines when "binary" is undefined, encoding "buffer", sync', testBinaryOption, undefined, simpleChunks, simpleChunksUint8Array, simpleFullUint8Array, false, true, 'buffer', execaSync);
test('Does not split lines when "binary" is false, encoding "buffer", sync', testBinaryOption, false, simpleChunks, simpleChunksUint8Array, simpleFullUint8Array, false, true, 'buffer', execaSync);
test('Does not split lines when "binary" is true, encoding "hex", sync', testBinaryOption, true, simpleChunks, simpleChunksUint8Array, simpleFullHex, false, true, 'hex', execaSync);
test('Does not split lines when "binary" is undefined, encoding "hex", sync', testBinaryOption, undefined, simpleChunks, simpleChunksUint8Array, simpleFullHex, false, true, 'hex', execaSync);
test('Does not split lines when "binary" is false, encoding "hex", sync', testBinaryOption, false, simpleChunks, simpleChunksUint8Array, simpleFullHex, false, true, 'hex', execaSync);
test('Does not split lines when "binary" is true, objectMode, sync', testBinaryOption, true, simpleChunks, simpleChunksUint8Array, simpleChunksUint8Array, true, true, 'utf8', execaSync);
test('Splits lines when "binary" is false, objectMode, sync', testBinaryOption, false, simpleChunks, simpleLines, simpleLines, true, true, 'utf8', execaSync);
test('Splits lines when "binary" is undefined, objectMode, sync', testBinaryOption, undefined, simpleChunks, simpleLines, simpleLines, true, true, 'utf8', execaSync);
test('Does not split lines when "binary" is true, preserveNewlines, sync', testBinaryOption, true, simpleChunks, simpleChunksUint8Array, simpleFull, false, false, 'utf8', execaSync);
test('Splits lines when "binary" is false, preserveNewlines, sync', testBinaryOption, false, simpleChunks, noNewlinesChunks, simpleFullEnd, false, false, 'utf8', execaSync);
test('Splits lines when "binary" is undefined, preserveNewlines, sync', testBinaryOption, undefined, simpleChunks, noNewlinesChunks, simpleFullEnd, false, false, 'utf8', execaSync);
test('Does not split lines when "binary" is true, preserveNewlines, encoding "utf16le", sync', testBinaryOption, true, simpleChunks, simpleChunksUint8Array, simpleFullUtf16Inverted, false, false, 'utf16le', execaSync);
test('Splits lines when "binary" is false, preserveNewlines, encoding "utf16le", sync', testBinaryOption, false, [simpleFullUtf16Uint8Array], noNewlinesChunks, simpleFullEndUtf16Inverted, false, false, 'utf16le', execaSync);
test('Splits lines when "binary" is undefined, preserveNewlines, encoding "utf16le", sync', testBinaryOption, undefined, [simpleFullUtf16Uint8Array], noNewlinesChunks, simpleFullEndUtf16Inverted, false, false, 'utf16le', execaSync);
test('Does not split lines when "binary" is true, encoding "buffer", preserveNewlines, sync', testBinaryOption, true, simpleChunks, simpleChunksUint8Array, simpleFullUint8Array, false, false, 'buffer', execaSync);
test('Does not split lines when "binary" is undefined, encoding "buffer", preserveNewlines, sync', testBinaryOption, undefined, simpleChunks, simpleChunksUint8Array, simpleFullUint8Array, false, false, 'buffer', execaSync);
test('Does not split lines when "binary" is false, encoding "buffer", preserveNewlines, sync', testBinaryOption, false, simpleChunks, simpleChunksUint8Array, simpleFullUint8Array, false, false, 'buffer', execaSync);
test('Does not split lines when "binary" is true, objectMode, preserveNewlines, sync', testBinaryOption, true, simpleChunks, simpleChunksUint8Array, simpleChunksUint8Array, true, false, 'utf8', execaSync);
test('Does not split lines when "binary" is true, encoding "hex", preserveNewlines, sync', testBinaryOption, true, simpleChunks, simpleChunksUint8Array, simpleFullHex, false, false, 'hex', execaSync);
test('Does not split lines when "binary" is undefined, encoding "hex", preserveNewlines, sync', testBinaryOption, undefined, simpleChunks, simpleChunksUint8Array, simpleFullHex, false, false, 'hex', execaSync);
test('Does not split lines when "binary" is false, encoding "hex", preserveNewlines, sync', testBinaryOption, false, simpleChunks, simpleChunksUint8Array, simpleFullHex, false, false, 'hex', execaSync);
test('Splits lines when "binary" is false, objectMode, preserveNewlines, sync', testBinaryOption, false, simpleChunks, noNewlinesChunks, noNewlinesChunks, true, false, 'utf8', execaSync);
test('Splits lines when "binary" is undefined, objectMode, preserveNewlines, sync', testBinaryOption, undefined, simpleChunks, noNewlinesChunks, noNewlinesChunks, true, false, 'utf8', execaSync);
================================================
FILE: test/transform/split-lines.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {getStdio} from '../helpers/stdio.js';
import {getOutputsGenerator, resultGenerator} from '../helpers/generator.js';
import {
singleFull,
singleFullEnd,
simpleFull,
simpleChunks,
simpleFullEnd,
simpleFullEndChunks,
simpleLines,
simpleFullEndLines,
noNewlinesFull,
noNewlinesChunks,
} from '../helpers/lines.js';
setFixtureDirectory();
const windowsFull = 'aaa\r\nbbb\r\nccc';
const windowsFullEnd = `${windowsFull}\r\n`;
const windowsChunks = [windowsFull];
const windowsLines = ['aaa\r\n', 'bbb\r\n', 'ccc'];
const noNewlinesFullEnd = `${noNewlinesFull}\n`;
const noNewlinesLines = ['aaabbbccc'];
const singleChunks = [singleFull];
const noLines = [];
const emptyFull = '';
const emptyChunks = [emptyFull];
const manyEmptyChunks = [emptyFull, emptyFull, emptyFull];
const newlineFull = '\n';
const newlineChunks = [newlineFull];
const newlinesFull = '\n\n\n';
const newlinesChunks = [newlinesFull];
const newlinesLines = ['\n', '\n', '\n'];
const windowsNewlinesFull = '\r\n\r\n\r\n';
const windowsNewlinesChunks = [windowsNewlinesFull];
const windowsNewlinesLines = ['\r\n', '\r\n', '\r\n'];
const runOverChunks = ['aaa\nb', 'b', 'b\nccc'];
const bigFull = '.'.repeat(1e5);
const bigFullEnd = `${bigFull}\n`;
const bigChunks = [bigFull];
const manyChunks = Array.from({length: 1e3}).fill('.');
const manyFull = manyChunks.join('');
const manyFullEnd = `${manyFull}\n`;
const manyLines = [manyFull];
// eslint-disable-next-line max-params
const testLines = async (t, fdNumber, input, expectedLines, expectedOutput, objectMode, preserveNewlines, execaMethod) => {
const lines = [];
const {stdio} = await execaMethod('noop-fd.js', [`${fdNumber}`], {
...getStdio(fdNumber, [
getOutputsGenerator(input)(false, true),
resultGenerator(lines)(objectMode, false, preserveNewlines),
]),
stripFinalNewline: false,
});
t.deepEqual(lines, expectedLines);
t.deepEqual(stdio[fdNumber], expectedOutput);
};
test('Split stdout - n newlines, 1 chunk', testLines, 1, simpleChunks, simpleLines, simpleFull, false, true, execa);
test('Split stderr - n newlines, 1 chunk', testLines, 2, simpleChunks, simpleLines, simpleFull, false, true, execa);
test('Split stdio[*] - n newlines, 1 chunk', testLines, 3, simpleChunks, simpleLines, simpleFull, false, true, execa);
test('Split stdout - preserveNewlines, n chunks', testLines, 1, noNewlinesChunks, noNewlinesLines, noNewlinesFull, false, true, execa);
test('Split stdout - 0 newlines, 1 chunk', testLines, 1, singleChunks, singleChunks, singleFull, false, true, execa);
test('Split stdout - empty, 1 chunk', testLines, 1, emptyChunks, noLines, emptyFull, false, true, execa);
test('Split stdout - Windows newlines', testLines, 1, windowsChunks, windowsLines, windowsFull, false, true, execa);
test('Split stdout - chunk ends with newline', testLines, 1, simpleFullEndChunks, simpleFullEndLines, simpleFullEnd, false, true, execa);
test('Split stdout - single newline', testLines, 1, newlineChunks, newlineChunks, newlineFull, false, true, execa);
test('Split stdout - only newlines', testLines, 1, newlinesChunks, newlinesLines, newlinesFull, false, true, execa);
test('Split stdout - only Windows newlines', testLines, 1, windowsNewlinesChunks, windowsNewlinesLines, windowsNewlinesFull, false, true, execa);
test('Split stdout - line split over multiple chunks', testLines, 1, runOverChunks, simpleLines, simpleFull, false, true, execa);
test('Split stdout - 0 newlines, big line', testLines, 1, bigChunks, bigChunks, bigFull, false, true, execa);
test('Split stdout - 0 newlines, many chunks', testLines, 1, manyChunks, manyLines, manyFull, false, true, execa);
test('Split stdout - n newlines, 1 chunk, objectMode', testLines, 1, simpleChunks, simpleLines, simpleLines, true, true, execa);
test('Split stderr - n newlines, 1 chunk, objectMode', testLines, 2, simpleChunks, simpleLines, simpleLines, true, true, execa);
test('Split stdio[*] - n newlines, 1 chunk, objectMode', testLines, 3, simpleChunks, simpleLines, simpleLines, true, true, execa);
test('Split stdout - preserveNewlines, n chunks, objectMode', testLines, 1, noNewlinesChunks, noNewlinesLines, noNewlinesLines, true, true, execa);
test('Split stdout - empty, 1 chunk, objectMode', testLines, 1, emptyChunks, noLines, noLines, true, true, execa);
test('Split stdout - 0 newlines, 1 chunk, objectMode', testLines, 1, singleChunks, singleChunks, singleChunks, true, true, execa);
test('Split stdout - Windows newlines, objectMode', testLines, 1, windowsChunks, windowsLines, windowsLines, true, true, execa);
test('Split stdout - chunk ends with newline, objectMode', testLines, 1, simpleFullEndChunks, simpleFullEndLines, simpleFullEndLines, true, true, execa);
test('Split stdout - single newline, objectMode', testLines, 1, newlineChunks, newlineChunks, newlineChunks, true, true, execa);
test('Split stdout - only newlines, objectMode', testLines, 1, newlinesChunks, newlinesLines, newlinesLines, true, true, execa);
test('Split stdout - only Windows newlines, objectMode', testLines, 1, windowsNewlinesChunks, windowsNewlinesLines, windowsNewlinesLines, true, true, execa);
test('Split stdout - line split over multiple chunks, objectMode', testLines, 1, runOverChunks, simpleLines, simpleLines, true, true, execa);
test('Split stdout - 0 newlines, big line, objectMode', testLines, 1, bigChunks, bigChunks, bigChunks, true, true, execa);
test('Split stdout - 0 newlines, many chunks, objectMode', testLines, 1, manyChunks, manyLines, manyLines, true, true, execa);
test('Split stdout - n newlines, 1 chunk, preserveNewlines', testLines, 1, simpleChunks, noNewlinesChunks, simpleFullEnd, false, false, execa);
test('Split stderr - n newlines, 1 chunk, preserveNewlines', testLines, 2, simpleChunks, noNewlinesChunks, simpleFullEnd, false, false, execa);
test('Split stdio[*] - n newlines, 1 chunk, preserveNewlines', testLines, 3, simpleChunks, noNewlinesChunks, simpleFullEnd, false, false, execa);
test('Split stdout - preserveNewlines, n chunks, preserveNewlines', testLines, 1, noNewlinesChunks, noNewlinesLines, noNewlinesFullEnd, false, false, execa);
test('Split stdout - empty, 1 chunk, preserveNewlines', testLines, 1, emptyChunks, noLines, emptyFull, false, false, execa);
test('Split stdout - 0 newlines, 1 chunk, preserveNewlines', testLines, 1, singleChunks, singleChunks, singleFullEnd, false, false, execa);
test('Split stdout - Windows newlines, preserveNewlines', testLines, 1, windowsChunks, noNewlinesChunks, windowsFullEnd, false, false, execa);
test('Split stdout - chunk ends with newline, preserveNewlines', testLines, 1, simpleFullEndChunks, noNewlinesChunks, simpleFullEnd, false, false, execa);
test('Split stdout - single newline, preserveNewlines', testLines, 1, newlineChunks, emptyChunks, newlineFull, false, false, execa);
test('Split stdout - only newlines, preserveNewlines', testLines, 1, newlinesChunks, manyEmptyChunks, newlinesFull, false, false, execa);
test('Split stdout - only Windows newlines, preserveNewlines', testLines, 1, windowsNewlinesChunks, manyEmptyChunks, windowsNewlinesFull, false, false, execa);
test('Split stdout - line split over multiple chunks, preserveNewlines', testLines, 1, runOverChunks, noNewlinesChunks, simpleFullEnd, false, false, execa);
test('Split stdout - 0 newlines, big line, preserveNewlines', testLines, 1, bigChunks, bigChunks, bigFullEnd, false, false, execa);
test('Split stdout - 0 newlines, many chunks, preserveNewlines', testLines, 1, manyChunks, manyLines, manyFullEnd, false, false, execa);
test('Split stdout - n newlines, 1 chunk, objectMode, preserveNewlines', testLines, 1, simpleChunks, noNewlinesChunks, noNewlinesChunks, true, false, execa);
test('Split stderr - n newlines, 1 chunk, objectMode, preserveNewlines', testLines, 2, simpleChunks, noNewlinesChunks, noNewlinesChunks, true, false, execa);
test('Split stdio[*] - n newlines, 1 chunk, objectMode, preserveNewlines', testLines, 3, simpleChunks, noNewlinesChunks, noNewlinesChunks, true, false, execa);
test('Split stdout - preserveNewlines, n chunks, objectMode, preserveNewlines', testLines, 1, noNewlinesChunks, noNewlinesLines, noNewlinesLines, true, false, execa);
test('Split stdout - empty, 1 chunk, objectMode, preserveNewlines', testLines, 1, emptyChunks, noLines, noLines, true, false, execa);
test('Split stdout - 0 newlines, 1 chunk, objectMode, preserveNewlines', testLines, 1, singleChunks, singleChunks, singleChunks, true, false, execa);
test('Split stdout - Windows newlines, objectMode, preserveNewlines', testLines, 1, windowsChunks, noNewlinesChunks, noNewlinesChunks, true, false, execa);
test('Split stdout - chunk ends with newline, objectMode, preserveNewlines', testLines, 1, simpleFullEndChunks, noNewlinesChunks, noNewlinesChunks, true, false, execa);
test('Split stdout - single newline, objectMode, preserveNewlines', testLines, 1, newlineChunks, emptyChunks, emptyChunks, true, false, execa);
test('Split stdout - only newlines, objectMode, preserveNewlines', testLines, 1, newlinesChunks, manyEmptyChunks, manyEmptyChunks, true, false, execa);
test('Split stdout - only Windows newlines, objectMode, preserveNewlines', testLines, 1, windowsNewlinesChunks, manyEmptyChunks, manyEmptyChunks, true, false, execa);
test('Split stdout - line split over multiple chunks, objectMode, preserveNewlines', testLines, 1, runOverChunks, noNewlinesChunks, noNewlinesChunks, true, false, execa);
test('Split stdout - 0 newlines, big line, objectMode, preserveNewlines', testLines, 1, bigChunks, bigChunks, bigChunks, true, false, execa);
test('Split stdout - 0 newlines, many chunks, objectMode, preserveNewlines', testLines, 1, manyChunks, manyLines, manyLines, true, false, execa);
test('Split stdout - n newlines, 1 chunk, sync', testLines, 1, simpleChunks, simpleLines, simpleFull, false, true, execaSync);
test('Split stderr - n newlines, 1 chunk, sync', testLines, 2, simpleChunks, simpleLines, simpleFull, false, true, execaSync);
test('Split stdio[*] - n newlines, 1 chunk, sync', testLines, 3, simpleChunks, simpleLines, simpleFull, false, true, execaSync);
test('Split stdout - preserveNewlines, n chunks, sync', testLines, 1, noNewlinesChunks, noNewlinesLines, noNewlinesFull, false, true, execaSync);
test('Split stdout - 0 newlines, 1 chunk, sync', testLines, 1, singleChunks, singleChunks, singleFull, false, true, execaSync);
test('Split stdout - empty, 1 chunk, sync', testLines, 1, emptyChunks, noLines, emptyFull, false, true, execaSync);
test('Split stdout - Windows newlines, sync', testLines, 1, windowsChunks, windowsLines, windowsFull, false, true, execaSync);
test('Split stdout - chunk ends with newline, sync', testLines, 1, simpleFullEndChunks, simpleFullEndLines, simpleFullEnd, false, true, execaSync);
test('Split stdout - single newline, sync', testLines, 1, newlineChunks, newlineChunks, newlineFull, false, true, execaSync);
test('Split stdout - only newlines, sync', testLines, 1, newlinesChunks, newlinesLines, newlinesFull, false, true, execaSync);
test('Split stdout - only Windows newlines, sync', testLines, 1, windowsNewlinesChunks, windowsNewlinesLines, windowsNewlinesFull, false, true, execaSync);
test('Split stdout - line split over multiple chunks, sync', testLines, 1, runOverChunks, simpleLines, simpleFull, false, true, execaSync);
test('Split stdout - 0 newlines, big line, sync', testLines, 1, bigChunks, bigChunks, bigFull, false, true, execaSync);
test('Split stdout - 0 newlines, many chunks, sync', testLines, 1, manyChunks, manyLines, manyFull, false, true, execaSync);
test('Split stdout - n newlines, 1 chunk, objectMode, sync', testLines, 1, simpleChunks, simpleLines, simpleLines, true, true, execaSync);
test('Split stderr - n newlines, 1 chunk, objectMode, sync', testLines, 2, simpleChunks, simpleLines, simpleLines, true, true, execaSync);
test('Split stdio[*] - n newlines, 1 chunk, objectMode, sync', testLines, 3, simpleChunks, simpleLines, simpleLines, true, true, execaSync);
test('Split stdout - preserveNewlines, n chunks, objectMode, sync', testLines, 1, noNewlinesChunks, noNewlinesLines, noNewlinesLines, true, true, execaSync);
test('Split stdout - empty, 1 chunk, objectMode, sync', testLines, 1, emptyChunks, noLines, noLines, true, true, execaSync);
test('Split stdout - 0 newlines, 1 chunk, objectMode, sync', testLines, 1, singleChunks, singleChunks, singleChunks, true, true, execaSync);
test('Split stdout - Windows newlines, objectMode, sync', testLines, 1, windowsChunks, windowsLines, windowsLines, true, true, execaSync);
test('Split stdout - chunk ends with newline, objectMode, sync', testLines, 1, simpleFullEndChunks, simpleFullEndLines, simpleFullEndLines, true, true, execaSync);
test('Split stdout - single newline, objectMode, sync', testLines, 1, newlineChunks, newlineChunks, newlineChunks, true, true, execaSync);
test('Split stdout - only newlines, objectMode, sync', testLines, 1, newlinesChunks, newlinesLines, newlinesLines, true, true, execaSync);
test('Split stdout - only Windows newlines, objectMode, sync', testLines, 1, windowsNewlinesChunks, windowsNewlinesLines, windowsNewlinesLines, true, true, execaSync);
test('Split stdout - line split over multiple chunks, objectMode, sync', testLines, 1, runOverChunks, simpleLines, simpleLines, true, true, execaSync);
test('Split stdout - 0 newlines, big line, objectMode, sync', testLines, 1, bigChunks, bigChunks, bigChunks, true, true, execaSync);
test('Split stdout - 0 newlines, many chunks, objectMode, sync', testLines, 1, manyChunks, manyLines, manyLines, true, true, execaSync);
test('Split stdout - n newlines, 1 chunk, preserveNewlines, sync', testLines, 1, simpleChunks, noNewlinesChunks, simpleFullEnd, false, false, execaSync);
test('Split stderr - n newlines, 1 chunk, preserveNewlines, sync', testLines, 2, simpleChunks, noNewlinesChunks, simpleFullEnd, false, false, execaSync);
test('Split stdio[*] - n newlines, 1 chunk, preserveNewlines, sync', testLines, 3, simpleChunks, noNewlinesChunks, simpleFullEnd, false, false, execaSync);
test('Split stdout - preserveNewlines, n chunks, preserveNewlines, sync', testLines, 1, noNewlinesChunks, noNewlinesLines, noNewlinesFullEnd, false, false, execaSync);
test('Split stdout - empty, 1 chunk, preserveNewlines, sync', testLines, 1, emptyChunks, noLines, emptyFull, false, false, execaSync);
test('Split stdout - 0 newlines, 1 chunk, preserveNewlines, sync', testLines, 1, singleChunks, singleChunks, singleFullEnd, false, false, execaSync);
test('Split stdout - Windows newlines, preserveNewlines, sync', testLines, 1, windowsChunks, noNewlinesChunks, windowsFullEnd, false, false, execaSync);
test('Split stdout - chunk ends with newline, preserveNewlines, sync', testLines, 1, simpleFullEndChunks, noNewlinesChunks, simpleFullEnd, false, false, execaSync);
test('Split stdout - single newline, preserveNewlines, sync', testLines, 1, newlineChunks, emptyChunks, newlineFull, false, false, execaSync);
test('Split stdout - only newlines, preserveNewlines, sync', testLines, 1, newlinesChunks, manyEmptyChunks, newlinesFull, false, false, execaSync);
test('Split stdout - only Windows newlines, preserveNewlines, sync', testLines, 1, windowsNewlinesChunks, manyEmptyChunks, windowsNewlinesFull, false, false, execaSync);
test('Split stdout - line split over multiple chunks, preserveNewlines, sync', testLines, 1, runOverChunks, noNewlinesChunks, simpleFullEnd, false, false, execaSync);
test('Split stdout - 0 newlines, big line, preserveNewlines, sync', testLines, 1, bigChunks, bigChunks, bigFullEnd, false, false, execaSync);
test('Split stdout - 0 newlines, many chunks, preserveNewlines, sync', testLines, 1, manyChunks, manyLines, manyFullEnd, false, false, execaSync);
test('Split stdout - n newlines, 1 chunk, objectMode, preserveNewlines, sync', testLines, 1, simpleChunks, noNewlinesChunks, noNewlinesChunks, true, false, execaSync);
test('Split stderr - n newlines, 1 chunk, objectMode, preserveNewlines, sync', testLines, 2, simpleChunks, noNewlinesChunks, noNewlinesChunks, true, false, execaSync);
test('Split stdio[*] - n newlines, 1 chunk, objectMode, preserveNewlines, sync', testLines, 3, simpleChunks, noNewlinesChunks, noNewlinesChunks, true, false, execaSync);
test('Split stdout - preserveNewlines, n chunks, objectMode, preserveNewlines, sync', testLines, 1, noNewlinesChunks, noNewlinesLines, noNewlinesLines, true, false, execaSync);
test('Split stdout - empty, 1 chunk, objectMode, preserveNewlines, sync', testLines, 1, emptyChunks, noLines, noLines, true, false, execaSync);
test('Split stdout - 0 newlines, 1 chunk, objectMode, preserveNewlines, sync', testLines, 1, singleChunks, singleChunks, singleChunks, true, false, execaSync);
test('Split stdout - Windows newlines, objectMode, preserveNewlines, sync', testLines, 1, windowsChunks, noNewlinesChunks, noNewlinesChunks, true, false, execaSync);
test('Split stdout - chunk ends with newline, objectMode, preserveNewlines, sync', testLines, 1, simpleFullEndChunks, noNewlinesChunks, noNewlinesChunks, true, false, execaSync);
test('Split stdout - single newline, objectMode, preserveNewlines, sync', testLines, 1, newlineChunks, emptyChunks, emptyChunks, true, false, execaSync);
test('Split stdout - only newlines, objectMode, preserveNewlines, sync', testLines, 1, newlinesChunks, manyEmptyChunks, manyEmptyChunks, true, false, execaSync);
test('Split stdout - only Windows newlines, objectMode, preserveNewlines, sync', testLines, 1, windowsNewlinesChunks, manyEmptyChunks, manyEmptyChunks, true, false, execaSync);
test('Split stdout - line split over multiple chunks, objectMode, preserveNewlines, sync', testLines, 1, runOverChunks, noNewlinesChunks, noNewlinesChunks, true, false, execaSync);
test('Split stdout - 0 newlines, big line, objectMode, preserveNewlines, sync', testLines, 1, bigChunks, bigChunks, bigChunks, true, false, execaSync);
test('Split stdout - 0 newlines, many chunks, objectMode, preserveNewlines, sync', testLines, 1, manyChunks, manyLines, manyLines, true, false, execaSync);
================================================
FILE: test/transform/split-newline.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {getOutputsGenerator, noopGenerator, noopAsyncGenerator} from '../helpers/generator.js';
import {singleFull, singleFullEnd} from '../helpers/lines.js';
setFixtureDirectory();
const singleFullEndWindows = `${singleFull}\r\n`;
const mixedNewlines = '.\n.\r\n.\n.\r\n.\n';
const testStripNewline = async (t, input, expectedOutput, execaMethod) => {
const {stdout} = await execaMethod('noop.js', {
stdout: getOutputsGenerator([input])(),
stripFinalNewline: false,
});
t.is(stdout, expectedOutput);
};
test('Strips newline when user do not mistakenly yield one at the end', testStripNewline, singleFull, singleFullEnd, execa);
test('Strips newline when user mistakenly yielded one at the end', testStripNewline, singleFullEnd, singleFullEnd, execa);
test('Strips newline when user mistakenly yielded one at the end, Windows newline', testStripNewline, singleFullEndWindows, singleFullEndWindows, execa);
test('Strips newline when user do not mistakenly yield one at the end, sync', testStripNewline, singleFull, singleFullEnd, execaSync);
test('Strips newline when user mistakenly yielded one at the end, sync', testStripNewline, singleFullEnd, singleFullEnd, execaSync);
test('Strips newline when user mistakenly yielded one at the end, Windows newline, sync', testStripNewline, singleFullEndWindows, singleFullEndWindows, execaSync);
const testMixNewlines = async (t, generator, execaMethod) => {
const {stdout} = await execaMethod('noop-fd.js', ['1', mixedNewlines], {
stdout: generator(),
stripFinalNewline: false,
});
t.is(stdout, mixedNewlines);
};
test('Can mix Unix and Windows newlines', testMixNewlines, noopGenerator, execa);
test('Can mix Unix and Windows newlines, sync', testMixNewlines, noopGenerator, execaSync);
test('Can mix Unix and Windows newlines, async', testMixNewlines, noopAsyncGenerator, execa);
================================================
FILE: test/transform/split-transform.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {getOutputsGenerator, resultGenerator} from '../helpers/generator.js';
import {
foobarString,
foobarUint8Array,
foobarObject,
foobarObjectString,
} from '../helpers/input.js';
setFixtureDirectory();
const resultUint8ArrayGenerator = function * (lines, chunk) {
lines.push(chunk);
yield new TextEncoder().encode(chunk);
};
// eslint-disable-next-line max-params
const testStringToUint8Array = async (t, expectedOutput, objectMode, preserveNewlines, execaMethod) => {
const lines = [];
const {stdout} = await execaMethod('noop-fd.js', ['1', foobarString], {
stdout: {
transform: resultUint8ArrayGenerator.bind(undefined, lines),
objectMode,
preserveNewlines,
},
lines: true,
});
t.deepEqual(lines, [foobarString]);
t.deepEqual(stdout, expectedOutput);
};
test('Line splitting when converting from string to Uint8Array', testStringToUint8Array, [foobarString], false, true, execa);
test('Line splitting when converting from string to Uint8Array, objectMode', testStringToUint8Array, [foobarUint8Array], true, true, execa);
test('Line splitting when converting from string to Uint8Array, preserveNewlines', testStringToUint8Array, [foobarString], false, false, execa);
test('Line splitting when converting from string to Uint8Array, objectMode, preserveNewlines', testStringToUint8Array, [foobarUint8Array], true, false, execa);
test('Line splitting when converting from string to Uint8Array, sync', testStringToUint8Array, [foobarString], false, true, execaSync);
test('Line splitting when converting from string to Uint8Array, objectMode, sync', testStringToUint8Array, [foobarUint8Array], true, true, execaSync);
test('Line splitting when converting from string to Uint8Array, preserveNewlines, sync', testStringToUint8Array, [foobarString], false, false, execaSync);
test('Line splitting when converting from string to Uint8Array, objectMode, preserveNewlines, sync', testStringToUint8Array, [foobarUint8Array], true, false, execaSync);
const serializeResultGenerator = function * (lines, chunk) {
lines.push(chunk);
yield JSON.stringify(chunk);
};
const testUnsetObjectMode = async (t, expectedOutput, preserveNewlines, execaMethod) => {
const lines = [];
const {stdout} = await execaMethod('noop.js', {
stdout: [
getOutputsGenerator([foobarObject])(true),
{transform: serializeResultGenerator.bind(undefined, lines), preserveNewlines, objectMode: false},
],
stripFinalNewline: false,
});
t.deepEqual(lines, [foobarObject]);
t.is(stdout, expectedOutput);
};
test('Can switch from objectMode to non-objectMode', testUnsetObjectMode, `${foobarObjectString}\n`, false, execa);
test('Can switch from objectMode to non-objectMode, preserveNewlines', testUnsetObjectMode, foobarObjectString, true, execa);
test('Can switch from objectMode to non-objectMode, sync', testUnsetObjectMode, `${foobarObjectString}\n`, false, execaSync);
test('Can switch from objectMode to non-objectMode, preserveNewlines, sync', testUnsetObjectMode, foobarObjectString, true, execaSync);
// eslint-disable-next-line max-params
const testYieldArray = async (t, input, expectedLines, expectedOutput, execaMethod) => {
const lines = [];
const {stdout} = await execaMethod('noop.js', {
stdout: [
getOutputsGenerator(input)(),
resultGenerator(lines)(),
],
stripFinalNewline: false,
});
t.deepEqual(lines, expectedLines);
t.deepEqual(stdout, expectedOutput);
};
test('Can use "yield* array" to produce multiple lines', testYieldArray, [foobarString, foobarString], [foobarString, foobarString], `${foobarString}\n${foobarString}\n`, execa);
test('Can use "yield* array" to produce empty lines', testYieldArray, [foobarString, ''], [foobarString, ''], `${foobarString}\n\n`, execa);
test('Can use "yield* array" to produce multiple lines, sync', testYieldArray, [foobarString, foobarString], [foobarString, foobarString], `${foobarString}\n${foobarString}\n`, execaSync);
test('Can use "yield* array" to produce empty lines, sync', testYieldArray, [foobarString, ''], [foobarString, ''], `${foobarString}\n\n`, execaSync);
================================================
FILE: test/transform/validate.js
================================================
import test from 'ava';
import {execa, execaSync} from '../../index.js';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {getStdio} from '../helpers/stdio.js';
import {foobarUint8Array, foobarObject} from '../helpers/input.js';
import {serializeGenerator, getOutputGenerator, convertTransformToFinal} from '../helpers/generator.js';
setFixtureDirectory();
const getMessage = input => input === null || input === undefined
? 'not be called at all'
: 'a string or an Uint8Array';
const lastInputGenerator = input => objectMode => [foobarUint8Array, getOutputGenerator(input)(objectMode)];
const inputGenerator = input => objectMode => [...lastInputGenerator(input)(objectMode), serializeGenerator(true)];
// eslint-disable-next-line max-params
const testGeneratorReturn = async (t, fdNumber, generator, input, objectMode, isInput) => {
const fixtureName = isInput ? 'stdin-fd.js' : 'noop-fd.js';
const {message} = await t.throwsAsync(execa(fixtureName, [`${fdNumber}`], getStdio(fdNumber, generator(input)(objectMode))));
t.true(message.includes(getMessage(input)));
};
test('Generators with result.stdin cannot return an object if not in objectMode', testGeneratorReturn, 0, inputGenerator, foobarObject, false, true);
test('Generators with result.stdio[*] as input cannot return an object if not in objectMode', testGeneratorReturn, 3, inputGenerator, foobarObject, false, true);
test('The last generator with result.stdin cannot return an object even in objectMode', testGeneratorReturn, 0, lastInputGenerator, foobarObject, true, true);
test('The last generator with result.stdio[*] as input cannot return an object even in objectMode', testGeneratorReturn, 3, lastInputGenerator, foobarObject, true, true);
test('Generators with result.stdout cannot return an object if not in objectMode', testGeneratorReturn, 1, getOutputGenerator, foobarObject, false, false);
test('Generators with result.stderr cannot return an object if not in objectMode', testGeneratorReturn, 2, getOutputGenerator, foobarObject, false, false);
test('Generators with result.stdio[*] as output cannot return an object if not in objectMode', testGeneratorReturn, 3, getOutputGenerator, foobarObject, false, false);
test('Generators with result.stdin cannot return null if not in objectMode', testGeneratorReturn, 0, inputGenerator, null, false, true);
test('Generators with result.stdin cannot return null if in objectMode', testGeneratorReturn, 0, inputGenerator, null, true, true);
test('Generators with result.stdout cannot return null if not in objectMode', testGeneratorReturn, 1, getOutputGenerator, null, false, false);
test('Generators with result.stdout cannot return null if in objectMode', testGeneratorReturn, 1, getOutputGenerator, null, true, false);
test('Generators with result.stdin cannot return undefined if not in objectMode', testGeneratorReturn, 0, inputGenerator, undefined, false, true);
test('Generators with result.stdin cannot return undefined if in objectMode', testGeneratorReturn, 0, inputGenerator, undefined, true, true);
test('Generators with result.stdout cannot return undefined if not in objectMode', testGeneratorReturn, 1, getOutputGenerator, undefined, false, false);
test('Generators with result.stdout cannot return undefined if in objectMode', testGeneratorReturn, 1, getOutputGenerator, undefined, true, false);
// eslint-disable-next-line max-params
const testGeneratorReturnSync = (t, fdNumber, generator, input, objectMode, isInput) => {
const fixtureName = isInput ? 'stdin-fd.js' : 'noop-fd.js';
const {message} = t.throws(() => {
execaSync(fixtureName, [`${fdNumber}`], getStdio(fdNumber, generator(input)(objectMode)));
});
t.true(message.includes(getMessage(input)));
};
test('Generators with result.stdin cannot return an object if not in objectMode, sync', testGeneratorReturnSync, 0, inputGenerator, foobarObject, false, true);
test('The last generator with result.stdin cannot return an object even in objectMode, sync', testGeneratorReturnSync, 0, lastInputGenerator, foobarObject, true, true);
test('Generators with result.stdout cannot return an object if not in objectMode, sync', testGeneratorReturnSync, 1, getOutputGenerator, foobarObject, false, false);
test('Generators with result.stderr cannot return an object if not in objectMode, sync', testGeneratorReturnSync, 2, getOutputGenerator, foobarObject, false, false);
test('Generators with result.stdio[*] as output cannot return an object if not in objectMode, sync', testGeneratorReturnSync, 3, getOutputGenerator, foobarObject, false, false);
test('Generators with result.stdin cannot return null if not in objectMode, sync', testGeneratorReturnSync, 0, inputGenerator, null, false, true);
test('Generators with result.stdin cannot return null if in objectMode, sync', testGeneratorReturnSync, 0, inputGenerator, null, true, true);
test('Generators with result.stdout cannot return null if not in objectMode, sync', testGeneratorReturnSync, 1, getOutputGenerator, null, false, false);
test('Generators with result.stdout cannot return null if in objectMode, sync', testGeneratorReturnSync, 1, getOutputGenerator, null, true, false);
test('Generators with result.stdin cannot return undefined if not in objectMode, sync', testGeneratorReturnSync, 0, inputGenerator, undefined, false, true);
test('Generators with result.stdin cannot return undefined if in objectMode, sync', testGeneratorReturnSync, 0, inputGenerator, undefined, true, true);
test('Generators with result.stdout cannot return undefined if not in objectMode, sync', testGeneratorReturnSync, 1, getOutputGenerator, undefined, false, false);
test('Generators with result.stdout cannot return undefined if in objectMode, sync', testGeneratorReturnSync, 1, getOutputGenerator, undefined, true, false);
test('Generators "final" return value is validated', async t => {
await t.throwsAsync(
execa('noop.js', {stdout: convertTransformToFinal(getOutputGenerator(null)(true), true)}),
{message: /not be called at all/},
);
});
test('Generators "final" return value is validated, sync', t => {
t.throws(() => {
execaSync('noop.js', {stdout: convertTransformToFinal(getOutputGenerator(null)(true), true)});
}, {message: /not be called at all/});
});
================================================
FILE: test/verbose/complete.js
================================================
import {stripVTControlCharacters} from 'node:util';
import test from 'ava';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
import {nestedSubprocess} from '../helpers/nested.js';
import {
runErrorSubprocess,
runWarningSubprocess,
runEarlyErrorSubprocess,
getCompletionLine,
getCompletionLines,
testTimestamp,
getVerboseOption,
stdoutNoneOption,
stdoutShortOption,
stdoutFullOption,
stderrNoneOption,
stderrShortOption,
stderrFullOption,
fd3NoneOption,
fd3ShortOption,
fd3FullOption,
ipcNoneOption,
ipcShortOption,
ipcFullOption,
} from '../helpers/verbose.js';
setFixtureDirectory();
const testPrintCompletion = async (t, verbose, isSync) => {
const {stderr} = await nestedSubprocess('noop.js', [foobarString], {verbose, isSync});
t.is(getCompletionLine(stderr), `${testTimestamp} [0] √ (done in 0ms)`);
};
test('Prints completion, verbose "short"', testPrintCompletion, 'short', false);
test('Prints completion, verbose "full"', testPrintCompletion, 'full', false);
test('Prints completion, verbose "short", fd-specific stdout', testPrintCompletion, stdoutShortOption, false);
test('Prints completion, verbose "full", fd-specific stdout', testPrintCompletion, stdoutFullOption, false);
test('Prints completion, verbose "short", fd-specific stderr', testPrintCompletion, stderrShortOption, false);
test('Prints completion, verbose "full", fd-specific stderr', testPrintCompletion, stderrFullOption, false);
test('Prints completion, verbose "short", fd-specific fd3', testPrintCompletion, fd3ShortOption, false);
test('Prints completion, verbose "full", fd-specific fd3', testPrintCompletion, fd3FullOption, false);
test('Prints completion, verbose "short", fd-specific ipc', testPrintCompletion, ipcShortOption, false);
test('Prints completion, verbose "full", fd-specific ipc', testPrintCompletion, ipcFullOption, false);
test('Prints completion, verbose "short", sync', testPrintCompletion, 'short', true);
test('Prints completion, verbose "full", sync', testPrintCompletion, 'full', true);
test('Prints completion, verbose "short", fd-specific stdout, sync', testPrintCompletion, stdoutShortOption, true);
test('Prints completion, verbose "full", fd-specific stdout, sync', testPrintCompletion, stdoutFullOption, true);
test('Prints completion, verbose "short", fd-specific stderr, sync', testPrintCompletion, stderrShortOption, true);
test('Prints completion, verbose "full", fd-specific stderr, sync', testPrintCompletion, stderrFullOption, true);
test('Prints completion, verbose "short", fd-specific fd3, sync', testPrintCompletion, fd3ShortOption, true);
test('Prints completion, verbose "full", fd-specific fd3, sync', testPrintCompletion, fd3FullOption, true);
test('Prints completion, verbose "short", fd-specific ipc, sync', testPrintCompletion, ipcShortOption, true);
test('Prints completion, verbose "full", fd-specific ipc, sync', testPrintCompletion, ipcFullOption, true);
const testNoPrintCompletion = async (t, verbose, isSync) => {
const {stderr} = await nestedSubprocess('noop.js', [foobarString], {verbose, isSync});
t.is(stderr, '');
};
test('Does not print completion, verbose "none"', testNoPrintCompletion, 'none', false);
test('Does not print completion, verbose default"', testNoPrintCompletion, undefined, false);
test('Does not print completion, verbose "none", fd-specific stdout', testNoPrintCompletion, stdoutNoneOption, false);
test('Does not print completion, verbose "none", fd-specific stderr', testNoPrintCompletion, stderrNoneOption, false);
test('Does not print completion, verbose "none", fd-specific fd3', testNoPrintCompletion, fd3NoneOption, false);
test('Does not print completion, verbose "none", fd-specific ipc', testNoPrintCompletion, ipcNoneOption, false);
test('Does not print completion, verbose default", fd-specific', testNoPrintCompletion, {}, false);
test('Does not print completion, verbose "none", sync', testNoPrintCompletion, 'none', true);
test('Does not print completion, verbose default", sync', testNoPrintCompletion, undefined, true);
test('Does not print completion, verbose "none", fd-specific stdout, sync', testNoPrintCompletion, stdoutNoneOption, true);
test('Does not print completion, verbose "none", fd-specific stderr, sync', testNoPrintCompletion, stderrNoneOption, true);
test('Does not print completion, verbose "none", fd-specific fd3, sync', testNoPrintCompletion, fd3NoneOption, true);
test('Does not print completion, verbose "none", fd-specific ipc, sync', testNoPrintCompletion, ipcNoneOption, true);
test('Does not print completion, verbose default", fd-specific, sync', testNoPrintCompletion, {}, true);
const testPrintCompletionError = async (t, isSync) => {
const stderr = await runErrorSubprocess(t, 'short', isSync);
t.is(getCompletionLine(stderr), `${testTimestamp} [0] × (done in 0ms)`);
};
test('Prints completion after errors', testPrintCompletionError, false);
test('Prints completion after errors, sync', testPrintCompletionError, true);
const testPrintCompletionWarning = async (t, isSync) => {
const stderr = await runWarningSubprocess(t, isSync);
t.is(getCompletionLine(stderr), `${testTimestamp} [0] ‼ (done in 0ms)`);
};
test('Prints completion after errors, "reject" false', testPrintCompletionWarning, false);
test('Prints completion after errors, "reject" false, sync', testPrintCompletionWarning, true);
const testPrintCompletionEarly = async (t, isSync) => {
const stderr = await runEarlyErrorSubprocess(t, isSync);
t.is(getCompletionLine(stderr), undefined);
};
test('Prints completion after early validation errors', testPrintCompletionEarly, false);
test('Prints completion after early validation errors, sync', testPrintCompletionEarly, true);
test.serial('Prints duration', async t => {
const {stderr} = await nestedSubprocess('delay.js', ['1000'], {verbose: 'short'});
t.regex(stripVTControlCharacters(stderr).split('\n').at(-1), /\(done in [\d.]+s\)/);
});
const testPipeDuration = async (t, parentFixture, sourceVerbose, destinationVerbose) => {
const {stderr} = await nestedSubprocess('noop.js', [foobarString], {
parentFixture,
sourceOptions: getVerboseOption(sourceVerbose),
destinationFile: 'stdin.js',
destinationOptions: getVerboseOption(destinationVerbose),
});
const lines = getCompletionLines(stderr);
t.is(lines.includes(`${testTimestamp} [0] √ (done in 0ms)`), sourceVerbose || destinationVerbose);
t.is(lines.includes(`${testTimestamp} [1] √ (done in 0ms)`), sourceVerbose && destinationVerbose);
};
test('Prints both durations piped with .pipe("file")', testPipeDuration, 'nested-pipe-file.js', true, true);
test('Prints both durations piped with .pipe`command`', testPipeDuration, 'nested-pipe-script.js', true, true);
test('Prints both durations piped with .pipe(subprocess)', testPipeDuration, 'nested-pipe-subprocesses.js', true, true);
test('Prints first duration piped with .pipe("file")', testPipeDuration, 'nested-pipe-file.js', true, false);
test('Prints first duration piped with .pipe`command`', testPipeDuration, 'nested-pipe-script.js', true, false);
test('Prints first duration piped with .pipe(subprocess)', testPipeDuration, 'nested-pipe-subprocesses.js', true, false);
test('Prints second duration piped with .pipe("file")', testPipeDuration, 'nested-pipe-file.js', false, true);
test('Prints second duration piped with .pipe`command`', testPipeDuration, 'nested-pipe-script.js', false, true);
test('Prints second duration piped with .pipe(subprocess)', testPipeDuration, 'nested-pipe-subprocesses.js', false, true);
test('Prints neither durations piped with .pipe("file")', testPipeDuration, 'nested-pipe-file.js', false, false);
test('Prints neither durations piped with .pipe`command`', testPipeDuration, 'nested-pipe-script.js', false, false);
test('Prints neither durations piped with .pipe(subprocess)', testPipeDuration, 'nested-pipe-subprocesses.js', false, false);
================================================
FILE: test/verbose/custom-command.js
================================================
import test from 'ava';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {fullStdio} from '../helpers/stdio.js';
import {
QUOTE,
getNormalizedLine,
testTimestamp,
runVerboseSubprocess,
} from '../helpers/verbose.js';
setFixtureDirectory();
const testPrintCommandCustom = async (t, fdNumber, isSync) => {
const {stderr} = await runVerboseSubprocess({
optionsFixture: 'custom-print.js',
isSync,
type: 'command',
fdNumber,
});
t.is(getNormalizedLine(stderr), `${testTimestamp} [0] $ noop-verbose.js ${QUOTE}. .${QUOTE}`);
};
test('Prints command, verbose custom', testPrintCommandCustom, undefined, false);
test('Prints command, verbose custom, fd-specific stdout', testPrintCommandCustom, 'stdout', false);
test('Prints command, verbose custom, fd-specific stderr', testPrintCommandCustom, 'stderr', false);
test('Prints command, verbose custom, fd-specific fd3', testPrintCommandCustom, 'fd3', false);
test('Prints command, verbose custom, fd-specific ipc', testPrintCommandCustom, 'ipc', false);
test('Prints command, verbose custom, sync', testPrintCommandCustom, undefined, true);
test('Prints command, verbose custom, fd-specific stdout, sync', testPrintCommandCustom, 'stdout', true);
test('Prints command, verbose custom, fd-specific stderr, sync', testPrintCommandCustom, 'stderr', true);
test('Prints command, verbose custom, fd-specific fd3, sync', testPrintCommandCustom, 'fd3', true);
test('Prints command, verbose custom, fd-specific ipc, sync', testPrintCommandCustom, 'ipc', true);
const testPrintCommandOrder = async (t, fdNumber, secondFdNumber, hasOutput) => {
const {stderr} = await runVerboseSubprocess({
optionsFixture: 'custom-print-multiple.js',
type: 'command',
fdNumber,
secondFdNumber,
...fullStdio,
});
if (hasOutput) {
t.is(getNormalizedLine(stderr), `${testTimestamp} [0] $ noop-verbose.js ${QUOTE}. .${QUOTE}`);
} else {
t.is(stderr, '');
}
};
test('Prints command, verbose custom, fd-specific stdout+stderr', testPrintCommandOrder, 'stdout', 'stderr', true);
test('Prints command, verbose custom, fd-specific stderr+stdout', testPrintCommandOrder, 'stderr', 'stdout', false);
test('Prints command, verbose custom, fd-specific stdout+fd3', testPrintCommandOrder, 'stdout', 'fd3', true);
test('Prints command, verbose custom, fd-specific fd3+stdout', testPrintCommandOrder, 'fd3', 'stdout', false);
test('Prints command, verbose custom, fd-specific stdout+ipc', testPrintCommandOrder, 'stdout', 'ipc', true);
test('Prints command, verbose custom, fd-specific ipc+stdout', testPrintCommandOrder, 'ipc', 'stdout', false);
test('Prints command, verbose custom, fd-specific stderr+fd3', testPrintCommandOrder, 'stderr', 'fd3', true);
test('Prints command, verbose custom, fd-specific fd3+stderr', testPrintCommandOrder, 'fd3', 'stderr', false);
test('Prints command, verbose custom, fd-specific stderr+ipc', testPrintCommandOrder, 'stderr', 'ipc', true);
test('Prints command, verbose custom, fd-specific ipc+stderr', testPrintCommandOrder, 'ipc', 'stderr', false);
test('Prints command, verbose custom, fd-specific fd3+ipc', testPrintCommandOrder, 'fd3', 'ipc', true);
test('Prints command, verbose custom, fd-specific ipc+fd3', testPrintCommandOrder, 'ipc', 'fd3', false);
const testPrintCommandFunction = async (t, fdNumber, secondFdNumber) => {
const {stderr} = await runVerboseSubprocess({
optionsFixture: 'custom-print-function.js',
type: 'command',
fdNumber,
secondFdNumber,
...fullStdio,
});
t.is(getNormalizedLine(stderr), `${testTimestamp} [0] $ noop-verbose.js ${QUOTE}. .${QUOTE}`);
};
test('Prints command, verbose custom, fd-specific stdout+stderr, single function', testPrintCommandFunction, 'stdout', 'stderr');
test('Prints command, verbose custom, fd-specific stderr+stdout, single function', testPrintCommandFunction, 'stderr', 'stdout');
test('Prints command, verbose custom, fd-specific stdout+fd3, single function', testPrintCommandFunction, 'stdout', 'fd3');
test('Prints command, verbose custom, fd-specific fd3+stdout, single function', testPrintCommandFunction, 'fd3', 'stdout');
test('Prints command, verbose custom, fd-specific stdout+ipc, single function', testPrintCommandFunction, 'stdout', 'ipc');
test('Prints command, verbose custom, fd-specific ipc+stdout, single function', testPrintCommandFunction, 'ipc', 'stdout');
test('Prints command, verbose custom, fd-specific stderr+fd3, single function', testPrintCommandFunction, 'stderr', 'fd3');
test('Prints command, verbose custom, fd-specific fd3+stderr, single function', testPrintCommandFunction, 'fd3', 'stderr');
test('Prints command, verbose custom, fd-specific stderr+ipc, single function', testPrintCommandFunction, 'stderr', 'ipc');
test('Prints command, verbose custom, fd-specific ipc+stderr, single function', testPrintCommandFunction, 'ipc', 'stderr');
test('Prints command, verbose custom, fd-specific fd3+ipc, single function', testPrintCommandFunction, 'fd3', 'ipc');
test('Prints command, verbose custom, fd-specific ipc+fd3, single function', testPrintCommandFunction, 'ipc', 'fd3');
const testVerboseMessage = async (t, isSync) => {
const {stderr} = await runVerboseSubprocess({
isSync,
type: 'command',
eventProperty: 'message',
});
t.is(getNormalizedLine(stderr), `noop-verbose.js ${QUOTE}. .${QUOTE}`);
};
test('"verbose" function receives verboseObject.message', testVerboseMessage, false);
test('"verbose" function receives verboseObject.message, sync', testVerboseMessage, true);
================================================
FILE: test/verbose/custom-common.js
================================================
import test from 'ava';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
import {nestedSubprocess} from '../helpers/nested.js';
import {QUOTE, getCommandLine, testTimestamp} from '../helpers/verbose.js';
setFixtureDirectory();
const testCustomReturn = async (t, verboseOutput, expectedOutput) => {
const {stderr} = await nestedSubprocess(
'empty.js',
{optionsFixture: 'custom-return.js', optionsInput: {verboseOutput}},
{stripFinalNewline: false},
);
t.is(stderr, expectedOutput);
};
test('"verbose" returning a string prints it', testCustomReturn, `${foobarString}\n`, `${foobarString}\n`);
test('"verbose" returning a string without a newline adds it', testCustomReturn, foobarString, `${foobarString}\n`);
test('"verbose" returning a string with multiple newlines keeps them', testCustomReturn, `${foobarString}\n\n`, `${foobarString}\n\n`);
test('"verbose" returning an empty string prints an empty line', testCustomReturn, '', '\n');
test('"verbose" returning undefined ignores it', testCustomReturn, undefined, '');
test('"verbose" returning a number ignores it', testCustomReturn, 0, '');
test('"verbose" returning a bigint ignores it', testCustomReturn, 0n, '');
test('"verbose" returning a boolean ignores it', testCustomReturn, true, '');
test('"verbose" returning an object ignores it', testCustomReturn, {}, '');
test('"verbose" returning an array ignores it', testCustomReturn, [], '');
test('"verbose" receives verboseLine string as first argument', async t => {
const {stderr} = await nestedSubprocess('noop.js', [foobarString], {optionsFixture: 'custom-uppercase.js'});
t.is(getCommandLine(stderr), `${testTimestamp} [0] $ NOOP.js ${foobarString}`);
});
test('"verbose" can print as JSON', async t => {
const {stderr} = await nestedSubprocess('noop.js', ['. .'], {optionsFixture: 'custom-json.js', type: 'duration', reject: false});
const {type, message, escapedCommand, commandId, timestamp, piped, result, options} = JSON.parse(stderr);
t.is(type, 'duration');
t.true(message.includes('done in'));
t.is(escapedCommand, `noop.js ${QUOTE}. .${QUOTE}`);
t.is(commandId, '0');
t.true(Number.isInteger(new Date(timestamp).getTime()));
t.false(piped);
t.false(result.failed);
t.is(result.exitCode, 0);
t.is(result.stdout, '. .');
t.is(result.stderr, '');
t.false(options.reject);
});
================================================
FILE: test/verbose/custom-complete.js
================================================
import test from 'ava';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {fullStdio} from '../helpers/stdio.js';
import {getNormalizedLine, testTimestamp, runVerboseSubprocess} from '../helpers/verbose.js';
setFixtureDirectory();
const testPrintCompletionCustom = async (t, fdNumber, isSync) => {
const {stderr} = await runVerboseSubprocess({
optionsFixture: 'custom-print.js',
isSync,
type: 'duration',
fdNumber,
});
t.is(getNormalizedLine(stderr), `${testTimestamp} [0] × (done in 0ms)`);
};
test('Prints completion, verbose custom', testPrintCompletionCustom, undefined, false);
test('Prints completion, verbose custom, fd-specific stdout', testPrintCompletionCustom, 'stdout', false);
test('Prints completion, verbose custom, fd-specific stderr', testPrintCompletionCustom, 'stderr', false);
test('Prints completion, verbose custom, fd-specific fd3', testPrintCompletionCustom, 'fd3', false);
test('Prints completion, verbose custom, fd-specific ipc', testPrintCompletionCustom, 'ipc', false);
test('Prints completion, verbose custom, sync', testPrintCompletionCustom, undefined, true);
test('Prints completion, verbose custom, fd-specific stdout, sync', testPrintCompletionCustom, 'stdout', true);
test('Prints completion, verbose custom, fd-specific stderr, sync', testPrintCompletionCustom, 'stderr', true);
test('Prints completion, verbose custom, fd-specific fd3, sync', testPrintCompletionCustom, 'fd3', true);
test('Prints completion, verbose custom, fd-specific ipc, sync', testPrintCompletionCustom, 'ipc', true);
const testPrintCompletionOrder = async (t, fdNumber, secondFdNumber, hasOutput) => {
const {stderr} = await runVerboseSubprocess({
optionsFixture: 'custom-print-multiple.js',
type: 'duration',
fdNumber,
secondFdNumber,
...fullStdio,
});
if (hasOutput) {
t.is(getNormalizedLine(stderr), `${testTimestamp} [0] × (done in 0ms)`);
} else {
t.is(stderr, '');
}
};
test('Prints completion, verbose custom, fd-specific stdout+stderr', testPrintCompletionOrder, 'stdout', 'stderr', true);
test('Prints completion, verbose custom, fd-specific stderr+stdout', testPrintCompletionOrder, 'stderr', 'stdout', false);
test('Prints completion, verbose custom, fd-specific stdout+fd3', testPrintCompletionOrder, 'stdout', 'fd3', true);
test('Prints completion, verbose custom, fd-specific fd3+stdout', testPrintCompletionOrder, 'fd3', 'stdout', false);
test('Prints completion, verbose custom, fd-specific stdout+ipc', testPrintCompletionOrder, 'stdout', 'ipc', true);
test('Prints completion, verbose custom, fd-specific ipc+stdout', testPrintCompletionOrder, 'ipc', 'stdout', false);
test('Prints completion, verbose custom, fd-specific stderr+fd3', testPrintCompletionOrder, 'stderr', 'fd3', true);
test('Prints completion, verbose custom, fd-specific fd3+stderr', testPrintCompletionOrder, 'fd3', 'stderr', false);
test('Prints completion, verbose custom, fd-specific stderr+ipc', testPrintCompletionOrder, 'stderr', 'ipc', true);
test('Prints completion, verbose custom, fd-specific ipc+stderr', testPrintCompletionOrder, 'ipc', 'stderr', false);
test('Prints completion, verbose custom, fd-specific fd3+ipc', testPrintCompletionOrder, 'fd3', 'ipc', true);
test('Prints completion, verbose custom, fd-specific ipc+fd3', testPrintCompletionOrder, 'ipc', 'fd3', false);
const testPrintCompletionFunction = async (t, fdNumber, secondFdNumber) => {
const {stderr} = await runVerboseSubprocess({
optionsFixture: 'custom-print-function.js',
type: 'duration',
fdNumber,
secondFdNumber,
...fullStdio,
});
t.is(getNormalizedLine(stderr), `${testTimestamp} [0] × (done in 0ms)`);
};
test('Prints completion, verbose custom, fd-specific stdout+stderr, single function', testPrintCompletionFunction, 'stdout', 'stderr');
test('Prints completion, verbose custom, fd-specific stderr+stdout, single function', testPrintCompletionFunction, 'stderr', 'stdout');
test('Prints completion, verbose custom, fd-specific stdout+fd3, single function', testPrintCompletionFunction, 'stdout', 'fd3');
test('Prints completion, verbose custom, fd-specific fd3+stdout, single function', testPrintCompletionFunction, 'fd3', 'stdout');
test('Prints completion, verbose custom, fd-specific stdout+ipc, single function', testPrintCompletionFunction, 'stdout', 'ipc');
test('Prints completion, verbose custom, fd-specific ipc+stdout, single function', testPrintCompletionFunction, 'ipc', 'stdout');
test('Prints completion, verbose custom, fd-specific stderr+fd3, single function', testPrintCompletionFunction, 'stderr', 'fd3');
test('Prints completion, verbose custom, fd-specific fd3+stderr, single function', testPrintCompletionFunction, 'fd3', 'stderr');
test('Prints completion, verbose custom, fd-specific stderr+ipc, single function', testPrintCompletionFunction, 'stderr', 'ipc');
test('Prints completion, verbose custom, fd-specific ipc+stderr, single function', testPrintCompletionFunction, 'ipc', 'stderr');
test('Prints completion, verbose custom, fd-specific fd3+ipc, single function', testPrintCompletionFunction, 'fd3', 'ipc');
test('Prints completion, verbose custom, fd-specific ipc+fd3, single function', testPrintCompletionFunction, 'ipc', 'fd3');
const testVerboseMessage = async (t, isSync) => {
const {stderr} = await runVerboseSubprocess({
isSync,
type: 'duration',
eventProperty: 'message',
});
t.is(getNormalizedLine(stderr), '(done in 0ms)');
};
test('"verbose" function receives verboseObject.message', testVerboseMessage, false);
test('"verbose" function receives verboseObject.message, sync', testVerboseMessage, true);
================================================
FILE: test/verbose/custom-error.js
================================================
import test from 'ava';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {fullStdio} from '../helpers/stdio.js';
import {
QUOTE,
getNormalizedLine,
testTimestamp,
runVerboseSubprocess,
} from '../helpers/verbose.js';
setFixtureDirectory();
const testPrintErrorCustom = async (t, fdNumber, isSync) => {
const {stderr} = await runVerboseSubprocess({
optionsFixture: 'custom-print.js',
isSync,
type: 'error',
fdNumber,
});
t.is(getNormalizedLine(stderr), `${testTimestamp} [0] × Command failed with exit code 2: noop-verbose.js ${QUOTE}. .${QUOTE}`);
};
test('Prints error, verbose custom', testPrintErrorCustom, undefined, false);
test('Prints error, verbose custom, fd-specific stdout', testPrintErrorCustom, 'stdout', false);
test('Prints error, verbose custom, fd-specific stderr', testPrintErrorCustom, 'stderr', false);
test('Prints error, verbose custom, fd-specific fd3', testPrintErrorCustom, 'fd3', false);
test('Prints error, verbose custom, fd-specific ipc', testPrintErrorCustom, 'ipc', false);
test('Prints error, verbose custom, sync', testPrintErrorCustom, undefined, true);
test('Prints error, verbose custom, fd-specific stdout, sync', testPrintErrorCustom, 'stdout', true);
test('Prints error, verbose custom, fd-specific stderr, sync', testPrintErrorCustom, 'stderr', true);
test('Prints error, verbose custom, fd-specific fd3, sync', testPrintErrorCustom, 'fd3', true);
test('Prints error, verbose custom, fd-specific ipc, sync', testPrintErrorCustom, 'ipc', true);
const testPrintErrorOrder = async (t, fdNumber, secondFdNumber, hasOutput) => {
const {stderr} = await runVerboseSubprocess({
optionsFixture: 'custom-print-multiple.js',
type: 'error',
fdNumber,
secondFdNumber,
...fullStdio,
});
if (hasOutput) {
t.is(getNormalizedLine(stderr), `${testTimestamp} [0] × Command failed with exit code 2: noop-verbose.js ${QUOTE}. .${QUOTE}`);
} else {
t.is(stderr, '');
}
};
test('Prints error, verbose custom, fd-specific stdout+stderr', testPrintErrorOrder, 'stdout', 'stderr', true);
test('Prints error, verbose custom, fd-specific stderr+stdout', testPrintErrorOrder, 'stderr', 'stdout', false);
test('Prints error, verbose custom, fd-specific stdout+fd3', testPrintErrorOrder, 'stdout', 'fd3', true);
test('Prints error, verbose custom, fd-specific fd3+stdout', testPrintErrorOrder, 'fd3', 'stdout', false);
test('Prints error, verbose custom, fd-specific stdout+ipc', testPrintErrorOrder, 'stdout', 'ipc', true);
test('Prints error, verbose custom, fd-specific ipc+stdout', testPrintErrorOrder, 'ipc', 'stdout', false);
test('Prints error, verbose custom, fd-specific stderr+fd3', testPrintErrorOrder, 'stderr', 'fd3', true);
test('Prints error, verbose custom, fd-specific fd3+stderr', testPrintErrorOrder, 'fd3', 'stderr', false);
test('Prints error, verbose custom, fd-specific stderr+ipc', testPrintErrorOrder, 'stderr', 'ipc', true);
test('Prints error, verbose custom, fd-specific ipc+stderr', testPrintErrorOrder, 'ipc', 'stderr', false);
test('Prints error, verbose custom, fd-specific fd3+ipc', testPrintErrorOrder, 'fd3', 'ipc', true);
test('Prints error, verbose custom, fd-specific ipc+fd3', testPrintErrorOrder, 'ipc', 'fd3', false);
const testPrintErrorFunction = async (t, fdNumber, secondFdNumber) => {
const {stderr} = await runVerboseSubprocess({
optionsFixture: 'custom-print-function.js',
type: 'error',
fdNumber,
secondFdNumber,
...fullStdio,
});
t.is(getNormalizedLine(stderr), `${testTimestamp} [0] × Command failed with exit code 2: noop-verbose.js ${QUOTE}. .${QUOTE}`);
};
test('Prints error, verbose custom, fd-specific stdout+stderr, single function', testPrintErrorFunction, 'stdout', 'stderr');
test('Prints error, verbose custom, fd-specific stderr+stdout, single function', testPrintErrorFunction, 'stderr', 'stdout');
test('Prints error, verbose custom, fd-specific stdout+fd3, single function', testPrintErrorFunction, 'stdout', 'fd3');
test('Prints error, verbose custom, fd-specific fd3+stdout, single function', testPrintErrorFunction, 'fd3', 'stdout');
test('Prints error, verbose custom, fd-specific stdout+ipc, single function', testPrintErrorFunction, 'stdout', 'ipc');
test('Prints error, verbose custom, fd-specific ipc+stdout, single function', testPrintErrorFunction, 'ipc', 'stdout');
test('Prints error, verbose custom, fd-specific stderr+fd3, single function', testPrintErrorFunction, 'stderr', 'fd3');
test('Prints error, verbose custom, fd-specific fd3+stderr, single function', testPrintErrorFunction, 'fd3', 'stderr');
test('Prints error, verbose custom, fd-specific stderr+ipc, single function', testPrintErrorFunction, 'stderr', 'ipc');
test('Prints error, verbose custom, fd-specific ipc+stderr, single function', testPrintErrorFunction, 'ipc', 'stderr');
test('Prints error, verbose custom, fd-specific fd3+ipc, single function', testPrintErrorFunction, 'fd3', 'ipc');
test('Prints error, verbose custom, fd-specific ipc+fd3, single function', testPrintErrorFunction, 'ipc', 'fd3');
const testVerboseMessage = async (t, isSync) => {
const {stderr} = await runVerboseSubprocess({
isSync,
type: 'error',
eventProperty: 'message',
});
t.is(stderr, `Command failed with exit code 2: noop-verbose.js ${QUOTE}. .${QUOTE}`);
};
test('"verbose" function receives verboseObject.message', testVerboseMessage, false);
test('"verbose" function receives verboseObject.message, sync', testVerboseMessage, true);
================================================
FILE: test/verbose/custom-event.js
================================================
import test from 'ava';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {runVerboseSubprocess} from '../helpers/verbose.js';
setFixtureDirectory();
const testVerboseType = async (t, type, isSync) => {
const {stderr} = await runVerboseSubprocess({isSync, type, eventProperty: 'type'});
t.is(stderr, type);
};
test('"verbose" function receives verboseObject.type "command"', testVerboseType, 'command', false);
test('"verbose" function receives verboseObject.type "output"', testVerboseType, 'output', false);
test('"verbose" function receives verboseObject.type "ipc"', testVerboseType, 'ipc', false);
test('"verbose" function receives verboseObject.type "error"', testVerboseType, 'error', false);
test('"verbose" function receives verboseObject.type "duration"', testVerboseType, 'duration', false);
test('"verbose" function receives verboseObject.type "command", sync', testVerboseType, 'command', true);
test('"verbose" function receives verboseObject.type "output", sync', testVerboseType, 'output', true);
test('"verbose" function receives verboseObject.type "error", sync', testVerboseType, 'error', true);
test('"verbose" function receives verboseObject.type "duration", sync', testVerboseType, 'duration', true);
const testVerboseTimestamp = async (t, type, isSync) => {
const {stderr} = await runVerboseSubprocess({isSync, type, eventProperty: 'timestamp'});
t.true(Number.isInteger(new Date(stderr).getTime()));
};
test('"verbose" function receives verboseObject.timestamp, "command"', testVerboseTimestamp, 'command', false);
test('"verbose" function receives verboseObject.timestamp, "output"', testVerboseTimestamp, 'output', false);
test('"verbose" function receives verboseObject.timestamp, "ipc"', testVerboseTimestamp, 'ipc', false);
test('"verbose" function receives verboseObject.timestamp, "error"', testVerboseTimestamp, 'error', false);
test('"verbose" function receives verboseObject.timestamp, "duration"', testVerboseTimestamp, 'duration', false);
test('"verbose" function receives verboseObject.timestamp, "command", sync', testVerboseTimestamp, 'command', true);
test('"verbose" function receives verboseObject.timestamp, "output", sync', testVerboseTimestamp, 'output', true);
test('"verbose" function receives verboseObject.timestamp, "error", sync', testVerboseTimestamp, 'error', true);
test('"verbose" function receives verboseObject.timestamp, "duration", sync', testVerboseTimestamp, 'duration', true);
const testVerbosePiped = async (t, type, isSync, expectedOutputs) => {
const {stderr} = await runVerboseSubprocess({
isSync,
type,
parentFixture: 'nested-pipe-verbose.js',
destinationFile: 'noop-verbose.js',
destinationArguments: ['. . .'],
eventProperty: 'piped',
});
t.true(expectedOutputs.map(expectedOutput => expectedOutput.join('\n')).includes(stderr));
};
test('"verbose" function receives verboseObject.piped, "command"', testVerbosePiped, 'command', false, [[false, true]]);
test('"verbose" function receives verboseObject.piped, "output"', testVerbosePiped, 'output', false, [[true]]);
test('"verbose" function receives verboseObject.piped, "ipc"', testVerbosePiped, 'ipc', false, [[false, true], [true, false]]);
test('"verbose" function receives verboseObject.piped, "error"', testVerbosePiped, 'error', false, [[false, true], [true, false]]);
test('"verbose" function receives verboseObject.piped, "duration"', testVerbosePiped, 'duration', false, [[false, true], [true, false]]);
test('"verbose" function receives verboseObject.piped, "command", sync', testVerbosePiped, 'command', true, [[false, true]]);
test('"verbose" function receives verboseObject.piped, "output", sync', testVerbosePiped, 'output', true, [[true]]);
test('"verbose" function receives verboseObject.piped, "error", sync', testVerbosePiped, 'error', true, [[false, true], [true, false]]);
test('"verbose" function receives verboseObject.piped, "duration", sync', testVerbosePiped, 'duration', true, [[false, true], [true, false]]);
================================================
FILE: test/verbose/custom-id.js
================================================
import test from 'ava';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {QUOTE, runVerboseSubprocess} from '../helpers/verbose.js';
setFixtureDirectory();
const testVerboseCommandId = async (t, type, isSync) => {
const {stderr} = await runVerboseSubprocess({isSync, type, eventProperty: 'commandId'});
t.is(stderr, '0');
};
test('"verbose" function receives verboseObject.commandId, "command"', testVerboseCommandId, 'command', false);
test('"verbose" function receives verboseObject.commandId, "output"', testVerboseCommandId, 'output', false);
test('"verbose" function receives verboseObject.commandId, "ipc"', testVerboseCommandId, 'ipc', false);
test('"verbose" function receives verboseObject.commandId, "error"', testVerboseCommandId, 'error', false);
test('"verbose" function receives verboseObject.commandId, "duration"', testVerboseCommandId, 'duration', false);
test('"verbose" function receives verboseObject.commandId, "command", sync', testVerboseCommandId, 'command', true);
test('"verbose" function receives verboseObject.commandId, "output", sync', testVerboseCommandId, 'output', true);
test('"verbose" function receives verboseObject.commandId, "error", sync', testVerboseCommandId, 'error', true);
test('"verbose" function receives verboseObject.commandId, "duration", sync', testVerboseCommandId, 'duration', true);
const testVerboseEscapedCommand = async (t, type, isSync) => {
const {stderr} = await runVerboseSubprocess({isSync, type, eventProperty: 'escapedCommand'});
t.is(stderr, `noop-verbose.js ${QUOTE}. .${QUOTE}`);
};
test('"verbose" function receives verboseObject.escapedCommand, "command"', testVerboseEscapedCommand, 'command', false);
test('"verbose" function receives verboseObject.escapedCommand, "output"', testVerboseEscapedCommand, 'output', false);
test('"verbose" function receives verboseObject.escapedCommand, "ipc"', testVerboseEscapedCommand, 'ipc', false);
test('"verbose" function receives verboseObject.escapedCommand, "error"', testVerboseEscapedCommand, 'error', false);
test('"verbose" function receives verboseObject.escapedCommand, "duration"', testVerboseEscapedCommand, 'duration', false);
test('"verbose" function receives verboseObject.escapedCommand, "command", sync', testVerboseEscapedCommand, 'command', true);
test('"verbose" function receives verboseObject.escapedCommand, "output", sync', testVerboseEscapedCommand, 'output', true);
test('"verbose" function receives verboseObject.escapedCommand, "error", sync', testVerboseEscapedCommand, 'error', true);
test('"verbose" function receives verboseObject.escapedCommand, "duration", sync', testVerboseEscapedCommand, 'duration', true);
================================================
FILE: test/verbose/custom-ipc.js
================================================
import {inspect} from 'node:util';
import test from 'ava';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {fullStdio} from '../helpers/stdio.js';
import {
getNormalizedLine,
getNormalizedLines,
testTimestamp,
runVerboseSubprocess,
} from '../helpers/verbose.js';
import {nestedSubprocess} from '../helpers/nested.js';
import {foobarObject} from '../helpers/input.js';
setFixtureDirectory();
const testPrintIpcCustom = async (t, fdNumber, hasOutput) => {
const {stderr} = await runVerboseSubprocess({
optionsFixture: 'custom-print.js',
type: 'ipc',
fdNumber,
...fullStdio,
});
if (hasOutput) {
t.is(getNormalizedLine(stderr), `${testTimestamp} [0] * . .`);
} else {
t.is(stderr, '');
}
};
test('Prints IPC, verbose custom', testPrintIpcCustom, undefined, true);
test('Prints IPC, verbose custom, fd-specific stdout', testPrintIpcCustom, 'stdout', false);
test('Prints IPC, verbose custom, fd-specific stderr', testPrintIpcCustom, 'stderr', false);
test('Prints IPC, verbose custom, fd-specific fd3', testPrintIpcCustom, 'fd3', false);
test('Prints IPC, verbose custom, fd-specific ipc', testPrintIpcCustom, 'ipc', true);
const testPrintIpcOrder = async (t, fdNumber, secondFdNumber, hasOutput) => {
const {stderr} = await runVerboseSubprocess({
optionsFixture: 'custom-print-multiple.js',
type: 'ipc',
fdNumber,
secondFdNumber,
...fullStdio,
});
if (hasOutput) {
t.is(getNormalizedLine(stderr), `${testTimestamp} [0] * . .`);
} else {
t.is(stderr, '');
}
};
test('Prints IPC, verbose custom, fd-specific stdout+stderr', testPrintIpcOrder, 'stdout', 'stderr', false);
test('Prints IPC, verbose custom, fd-specific stderr+stdout', testPrintIpcOrder, 'stderr', 'stdout', false);
test('Prints IPC, verbose custom, fd-specific stdout+fd3', testPrintIpcOrder, 'stdout', 'fd3', false);
test('Prints IPC, verbose custom, fd-specific fd3+stdout', testPrintIpcOrder, 'fd3', 'stdout', false);
test('Prints IPC, verbose custom, fd-specific stdout+ipc', testPrintIpcOrder, 'stdout', 'ipc', false);
test('Prints IPC, verbose custom, fd-specific ipc+stdout', testPrintIpcOrder, 'ipc', 'stdout', true);
test('Prints IPC, verbose custom, fd-specific stderr+fd3', testPrintIpcOrder, 'stderr', 'fd3', false);
test('Prints IPC, verbose custom, fd-specific fd3+stderr', testPrintIpcOrder, 'fd3', 'stderr', false);
test('Prints IPC, verbose custom, fd-specific stderr+ipc', testPrintIpcOrder, 'stderr', 'ipc', false);
test('Prints IPC, verbose custom, fd-specific ipc+stderr', testPrintIpcOrder, 'ipc', 'stderr', true);
test('Prints IPC, verbose custom, fd-specific fd3+ipc', testPrintIpcOrder, 'fd3', 'ipc', false);
test('Prints IPC, verbose custom, fd-specific ipc+fd3', testPrintIpcOrder, 'ipc', 'fd3', true);
const testPrintIpcFunction = async (t, fdNumber, secondFdNumber, hasOutput) => {
const {stderr} = await runVerboseSubprocess({
optionsFixture: 'custom-print-function.js',
type: 'ipc',
fdNumber,
secondFdNumber,
...fullStdio,
});
if (hasOutput) {
t.is(getNormalizedLine(stderr), `${testTimestamp} [0] * . .`);
} else {
t.is(stderr, '');
}
};
test('Prints IPC, verbose custom, fd-specific stdout+stderr, single function', testPrintIpcFunction, 'stdout', 'stderr', false);
test('Prints IPC, verbose custom, fd-specific stderr+stdout, single function', testPrintIpcFunction, 'stderr', 'stdout', false);
test('Prints IPC, verbose custom, fd-specific stdout+fd3, single function', testPrintIpcFunction, 'stdout', 'fd3', false);
test('Prints IPC, verbose custom, fd-specific fd3+stdout, single function', testPrintIpcFunction, 'fd3', 'stdout', false);
test('Prints IPC, verbose custom, fd-specific stdout+ipc, single function', testPrintIpcFunction, 'stdout', 'ipc', false);
test('Prints IPC, verbose custom, fd-specific ipc+stdout, single function', testPrintIpcFunction, 'ipc', 'stdout', true);
test('Prints IPC, verbose custom, fd-specific stderr+fd3, single function', testPrintIpcFunction, 'stderr', 'fd3', false);
test('Prints IPC, verbose custom, fd-specific fd3+stderr, single function', testPrintIpcFunction, 'fd3', 'stderr', false);
test('Prints IPC, verbose custom, fd-specific stderr+ipc, single function', testPrintIpcFunction, 'stderr', 'ipc', false);
test('Prints IPC, verbose custom, fd-specific ipc+stderr, single function', testPrintIpcFunction, 'ipc', 'stderr', true);
test('Prints IPC, verbose custom, fd-specific fd3+ipc, single function', testPrintIpcFunction, 'fd3', 'ipc', false);
test('Prints IPC, verbose custom, fd-specific ipc+fd3, single function', testPrintIpcFunction, 'ipc', 'fd3', true);
test('"verbose" function receives verboseObject.message', async t => {
const {stderr} = await runVerboseSubprocess({
type: 'ipc',
eventProperty: 'message',
});
t.is(stderr, '. .');
});
test('"verbose" function receives verboseObject.message line-wise', async t => {
const {stderr} = await runVerboseSubprocess({
optionsFixture: 'custom-print.js',
type: 'ipc',
output: '.\n.',
});
t.deepEqual(getNormalizedLines(stderr), [`${testTimestamp} [0] * .`, `${testTimestamp} [0] * .`]);
});
test('"verbose" function receives verboseObject.message serialized', async t => {
const {stderr} = await nestedSubprocess('ipc-echo.js', {
ipcInput: foobarObject,
optionsFixture: 'custom-print.js',
optionsInput: {type: 'ipc'},
});
t.is(getNormalizedLine(stderr), `${testTimestamp} [0] * ${inspect(foobarObject)}`);
});
================================================
FILE: test/verbose/custom-options.js
================================================
import test from 'ava';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {runVerboseSubprocess} from '../helpers/verbose.js';
setFixtureDirectory();
const testVerboseOptionsExplicit = async (t, type, isSync) => {
const maxBuffer = 1000;
const {stderr} = await runVerboseSubprocess({
isSync,
type,
optionsFixture: 'custom-option.js',
optionName: 'maxBuffer',
maxBuffer,
});
t.is(stderr, `${maxBuffer}`);
};
test('"verbose" function receives verboseObject.options explicitly set, "command"', testVerboseOptionsExplicit, 'command', false);
test('"verbose" function receives verboseObject.options explicitly set, "output"', testVerboseOptionsExplicit, 'output', false);
test('"verbose" function receives verboseObject.options explicitly set, "ipc"', testVerboseOptionsExplicit, 'ipc', false);
test('"verbose" function receives verboseObject.options explicitly set, "error"', testVerboseOptionsExplicit, 'error', false);
test('"verbose" function receives verboseObject.options explicitly set, "duration"', testVerboseOptionsExplicit, 'duration', false);
test('"verbose" function receives verboseObject.options explicitly set, "command", sync', testVerboseOptionsExplicit, 'command', true);
test('"verbose" function receives verboseObject.options explicitly set, "output", sync', testVerboseOptionsExplicit, 'output', true);
test('"verbose" function receives verboseObject.options explicitly set, "error", sync', testVerboseOptionsExplicit, 'error', true);
test('"verbose" function receives verboseObject.options explicitly set, "duration", sync', testVerboseOptionsExplicit, 'duration', true);
const testVerboseOptionsDefault = async (t, type, isSync) => {
const {stderr} = await runVerboseSubprocess({
isSync,
type,
optionsFixture: 'custom-option.js',
optionName: 'maxBuffer',
});
t.is(stderr, 'undefined');
};
test('"verbose" function receives verboseObject.options before default values and normalization, "command"', testVerboseOptionsDefault, 'command', false);
test('"verbose" function receives verboseObject.options before default values and normalization, "output"', testVerboseOptionsDefault, 'output', false);
test('"verbose" function receives verboseObject.options before default values and normalization, "ipc"', testVerboseOptionsDefault, 'ipc', false);
test('"verbose" function receives verboseObject.options before default values and normalization, "error"', testVerboseOptionsDefault, 'error', false);
test('"verbose" function receives verboseObject.options before default values and normalization, "duration"', testVerboseOptionsDefault, 'duration', false);
test('"verbose" function receives verboseObject.options before default values and normalization, "command", sync', testVerboseOptionsDefault, 'command', true);
test('"verbose" function receives verboseObject.options before default values and normalization, "output", sync', testVerboseOptionsDefault, 'output', true);
test('"verbose" function receives verboseObject.options before default values and normalization, "error", sync', testVerboseOptionsDefault, 'error', true);
test('"verbose" function receives verboseObject.options before default values and normalization, "duration", sync', testVerboseOptionsDefault, 'duration', true);
================================================
FILE: test/verbose/custom-output.js
================================================
import {inspect} from 'node:util';
import test from 'ava';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {fullStdio} from '../helpers/stdio.js';
import {
getNormalizedLine,
getNormalizedLines,
testTimestamp,
runVerboseSubprocess,
} from '../helpers/verbose.js';
import {nestedSubprocess} from '../helpers/nested.js';
import {foobarObject} from '../helpers/input.js';
setFixtureDirectory();
const testPrintOutputCustom = async (t, fdNumber, isSync, hasOutput) => {
const {stderr} = await runVerboseSubprocess({
optionsFixture: 'custom-print.js',
isSync,
type: 'output',
fdNumber,
});
if (hasOutput) {
t.is(getNormalizedLine(stderr), `${testTimestamp} [0] . .`);
} else {
t.is(stderr, '');
}
};
test('Prints stdout, verbose custom', testPrintOutputCustom, undefined, false, true);
test('Prints stdout, verbose custom, fd-specific stdout', testPrintOutputCustom, 'stdout', false, true);
test('Prints stdout, verbose custom, fd-specific stderr', testPrintOutputCustom, 'stderr', false, false);
test('Prints stdout, verbose custom, fd-specific fd3', testPrintOutputCustom, 'fd3', false, false);
test('Prints stdout, verbose custom, fd-specific ipc', testPrintOutputCustom, 'ipc', false, false);
test('Prints stdout, verbose custom, sync', testPrintOutputCustom, undefined, true, true);
test('Prints stdout, verbose custom, fd-specific stdout, sync', testPrintOutputCustom, 'stdout', true, true);
test('Prints stdout, verbose custom, fd-specific stderr, sync', testPrintOutputCustom, 'stderr', true, false);
test('Prints stdout, verbose custom, fd-specific fd3, sync', testPrintOutputCustom, 'fd3', true, false);
test('Prints stdout, verbose custom, fd-specific ipc, sync', testPrintOutputCustom, 'ipc', true, false);
const testPrintOutputOrder = async (t, fdNumber, secondFdNumber, hasOutput) => {
const {stderr} = await runVerboseSubprocess({
optionsFixture: 'custom-print-multiple.js',
type: 'output',
fdNumber,
secondFdNumber,
...fullStdio,
});
if (hasOutput) {
t.is(getNormalizedLine(stderr), `${testTimestamp} [0] . .`);
} else {
t.is(stderr, '');
}
};
test('Prints stdout, verbose custom, fd-specific stdout+stderr', testPrintOutputOrder, 'stdout', 'stderr', true);
test('Prints stdout, verbose custom, fd-specific stderr+stdout', testPrintOutputOrder, 'stderr', 'stdout', false);
test('Prints stdout, verbose custom, fd-specific stdout+fd3', testPrintOutputOrder, 'stdout', 'fd3', true);
test('Prints stdout, verbose custom, fd-specific fd3+stdout', testPrintOutputOrder, 'fd3', 'stdout', false);
test('Prints stdout, verbose custom, fd-specific stdout+ipc', testPrintOutputOrder, 'stdout', 'ipc', true);
test('Prints stdout, verbose custom, fd-specific ipc+stdout', testPrintOutputOrder, 'ipc', 'stdout', false);
test('Prints stdout, verbose custom, fd-specific stderr+fd3', testPrintOutputOrder, 'stderr', 'fd3', false);
test('Prints stdout, verbose custom, fd-specific fd3+stderr', testPrintOutputOrder, 'fd3', 'stderr', false);
test('Prints stdout, verbose custom, fd-specific stderr+ipc', testPrintOutputOrder, 'stderr', 'ipc', false);
test('Prints stdout, verbose custom, fd-specific ipc+stderr', testPrintOutputOrder, 'ipc', 'stderr', false);
test('Prints stdout, verbose custom, fd-specific fd3+ipc', testPrintOutputOrder, 'fd3', 'ipc', false);
test('Prints stdout, verbose custom, fd-specific ipc+fd3', testPrintOutputOrder, 'ipc', 'fd3', false);
const testPrintOutputFunction = async (t, fdNumber, secondFdNumber, hasOutput) => {
const {stderr} = await runVerboseSubprocess({
optionsFixture: 'custom-print-function.js',
type: 'output',
fdNumber,
secondFdNumber,
...fullStdio,
});
if (hasOutput) {
t.is(getNormalizedLine(stderr), `${testTimestamp} [0] . .`);
} else {
t.is(stderr, '');
}
};
test('Prints stdout, verbose custom, fd-specific stdout+stderr, single function', testPrintOutputFunction, 'stdout', 'stderr', true);
test('Prints stdout, verbose custom, fd-specific stderr+stdout, single function', testPrintOutputFunction, 'stderr', 'stdout', false);
test('Prints stdout, verbose custom, fd-specific stdout+fd3, single function', testPrintOutputFunction, 'stdout', 'fd3', true);
test('Prints stdout, verbose custom, fd-specific fd3+stdout, single function', testPrintOutputFunction, 'fd3', 'stdout', false);
test('Prints stdout, verbose custom, fd-specific stdout+ipc, single function', testPrintOutputFunction, 'stdout', 'ipc', true);
test('Prints stdout, verbose custom, fd-specific ipc+stdout, single function', testPrintOutputFunction, 'ipc', 'stdout', false);
test('Prints stdout, verbose custom, fd-specific stderr+fd3, single function', testPrintOutputFunction, 'stderr', 'fd3', false);
test('Prints stdout, verbose custom, fd-specific fd3+stderr, single function', testPrintOutputFunction, 'fd3', 'stderr', false);
test('Prints stdout, verbose custom, fd-specific stderr+ipc, single function', testPrintOutputFunction, 'stderr', 'ipc', false);
test('Prints stdout, verbose custom, fd-specific ipc+stderr, single function', testPrintOutputFunction, 'ipc', 'stderr', false);
test('Prints stdout, verbose custom, fd-specific fd3+ipc, single function', testPrintOutputFunction, 'fd3', 'ipc', false);
test('Prints stdout, verbose custom, fd-specific ipc+fd3, single function', testPrintOutputFunction, 'ipc', 'fd3', false);
const testVerboseMessage = async (t, isSync) => {
const {stderr} = await runVerboseSubprocess({
isSync,
type: 'output',
eventProperty: 'message',
});
t.is(stderr, '. .');
};
test('"verbose" function receives verboseObject.message', testVerboseMessage, false);
test('"verbose" function receives verboseObject.message, sync', testVerboseMessage, true);
const testPrintOutputMultiline = async (t, isSync) => {
const {stderr} = await runVerboseSubprocess({
optionsFixture: 'custom-print.js',
isSync,
type: 'output',
output: '.\n.',
});
t.deepEqual(getNormalizedLines(stderr), [`${testTimestamp} [0] .`, `${testTimestamp} [0] .`]);
};
test('"verbose" function receives verboseObject.message line-wise', testPrintOutputMultiline, false);
test('"verbose" function receives verboseObject.message line-wise, sync', testPrintOutputMultiline, true);
test('"verbose" function receives verboseObject.message serialized', async t => {
const {stderr} = await nestedSubprocess('noop.js', {optionsFixture: 'custom-object-stdout.js'});
t.is(getNormalizedLine(stderr), `${testTimestamp} [0] ${inspect(foobarObject)}`);
});
================================================
FILE: test/verbose/custom-reject.js
================================================
import test from 'ava';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {runVerboseSubprocess} from '../helpers/verbose.js';
import {earlyErrorOptions, earlyErrorOptionsSync} from '../helpers/early-error.js';
setFixtureDirectory();
// eslint-disable-next-line max-params
const testVerboseReject = async (t, type, options, isSync, expectedOutput) => {
const {stderr} = await runVerboseSubprocess({
isSync,
type,
optionsFixture: 'custom-option.js',
optionName: 'reject',
...options,
});
t.is(stderr, expectedOutput.map(String).join('\n'));
};
test('"verbose" function receives verboseObject.options.reject, "command"', testVerboseReject, 'command', {}, false, [undefined]);
test('"verbose" function receives verboseObject.options.reject, "output"', testVerboseReject, 'output', {}, false, [undefined]);
test('"verbose" function receives verboseObject.options.reject, "ipc"', testVerboseReject, 'ipc', {}, false, [undefined]);
test('"verbose" function receives verboseObject.options.reject, "error"', testVerboseReject, 'error', {}, false, [undefined]);
test('"verbose" function receives verboseObject.options.reject, "duration"', testVerboseReject, 'duration', {}, false, [undefined]);
test('"verbose" function receives verboseObject.options.reject, "command", spawn error', testVerboseReject, 'command', earlyErrorOptions, false, [undefined]);
test('"verbose" function receives verboseObject.options.reject, "output", spawn error', testVerboseReject, 'output', earlyErrorOptions, false, []);
test('"verbose" function receives verboseObject.options.reject, "ipc", spawn error', testVerboseReject, 'ipc', earlyErrorOptions, false, []);
test('"verbose" function receives verboseObject.options.reject, "error", spawn error', testVerboseReject, 'error', earlyErrorOptions, false, [undefined, undefined]);
test('"verbose" function receives verboseObject.options.reject, "duration", spawn error', testVerboseReject, 'duration', earlyErrorOptions, false, [undefined]);
test('"verbose" function receives verboseObject.options.reject, "command", sync', testVerboseReject, 'command', {}, true, [undefined]);
test('"verbose" function receives verboseObject.options.reject, "output", sync', testVerboseReject, 'output', {}, true, [undefined]);
test('"verbose" function receives verboseObject.options.reject, "error", sync', testVerboseReject, 'error', {}, true, [undefined]);
test('"verbose" function receives verboseObject.options.reject, "duration", sync', testVerboseReject, 'duration', {}, true, [undefined]);
test('"verbose" function receives verboseObject.options.reject, "command", spawn error, sync', testVerboseReject, 'command', earlyErrorOptionsSync, true, [undefined]);
test('"verbose" function receives verboseObject.options.reject, "output", spawn error, sync', testVerboseReject, 'output', earlyErrorOptionsSync, true, []);
test('"verbose" function receives verboseObject.options.reject, "error", spawn error, sync', testVerboseReject, 'error', earlyErrorOptionsSync, true, [undefined, undefined]);
test('"verbose" function receives verboseObject.options.reject, "duration", spawn error, sync', testVerboseReject, 'duration', earlyErrorOptionsSync, true, [undefined]);
================================================
FILE: test/verbose/custom-result.js
================================================
import test from 'ava';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {runVerboseSubprocess} from '../helpers/verbose.js';
import {
earlyErrorOptions,
earlyErrorOptionsSync,
expectedEarlyError,
expectedEarlyErrorSync,
} from '../helpers/early-error.js';
setFixtureDirectory();
const testVerboseResultEnd = async (t, type, isSync) => {
const {stderr: parentStderr} = await runVerboseSubprocess({
isSync,
type,
optionsFixture: 'custom-result.js',
});
const {failed, exitCode, stdout, stderr, ipcOutput, durationMs} = JSON.parse(parentStderr);
t.true(failed);
t.is(exitCode, 2);
t.is(stdout, '. .');
t.is(stderr, '');
t.is(typeof durationMs, 'number');
t.deepEqual(ipcOutput, isSync ? [] : ['. .']);
};
test('"verbose" function receives verboseObject.result, "error"', testVerboseResultEnd, 'error', false);
test('"verbose" function receives verboseObject.result, "duration"', testVerboseResultEnd, 'duration', false);
test('"verbose" function receives verboseObject.result, "error", sync', testVerboseResultEnd, 'error', true);
test('"verbose" function receives verboseObject.result, "duration", sync', testVerboseResultEnd, 'duration', true);
// eslint-disable-next-line max-params
const testVerboseResultEndSpawn = async (t, type, options, expectedOutput, isSync) => {
const {stderr: parentStderr} = await runVerboseSubprocess({
isSync,
type,
optionsFixture: 'custom-result.js',
...options,
});
const lastLine = parentStderr.split('\n').at(-1);
const result = JSON.parse(lastLine);
t.like(result, expectedOutput);
t.true(result.failed);
t.is(result.exitCode, undefined);
t.is(result.stdout, undefined);
t.is(result.stderr, undefined);
t.is(typeof result.durationMs, 'number');
t.deepEqual(result.ipcOutput, []);
};
test('"verbose" function receives verboseObject.result, "error", spawn error', testVerboseResultEndSpawn, 'error', earlyErrorOptions, expectedEarlyError, false);
test('"verbose" function receives verboseObject.result, "duration", spawn error', testVerboseResultEndSpawn, 'duration', earlyErrorOptions, expectedEarlyError, false);
test('"verbose" function receives verboseObject.result, "error", spawn error, sync', testVerboseResultEndSpawn, 'error', earlyErrorOptionsSync, expectedEarlyErrorSync, true);
test('"verbose" function receives verboseObject.result, "duration", spawn error, sync', testVerboseResultEndSpawn, 'duration', earlyErrorOptionsSync, expectedEarlyErrorSync, true);
const testVerboseResultStart = async (t, type, options, isSync) => {
const {stderr: parentStderr} = await runVerboseSubprocess({
isSync,
type,
optionsFixture: 'custom-result.js',
...options,
});
t.is(parentStderr, '');
};
test('"verbose" function does not receive verboseObject.result, "command"', testVerboseResultStart, 'command', {}, false);
test('"verbose" function does not receive verboseObject.result, "output"', testVerboseResultStart, 'output', {}, false);
test('"verbose" function does not receive verboseObject.result, "ipc"', testVerboseResultStart, 'ipc', {}, false);
test('"verbose" function does not receive verboseObject.result, "command", spawn error', testVerboseResultStart, 'command', earlyErrorOptions, false);
test('"verbose" function does not receive verboseObject.result, "output", spawn error', testVerboseResultStart, 'output', earlyErrorOptions, false);
test('"verbose" function does not receive verboseObject.result, "ipc", spawn error', testVerboseResultStart, 'ipc', earlyErrorOptions, false);
test('"verbose" function does not receive verboseObject.result, "command", sync', testVerboseResultStart, 'command', {}, true);
test('"verbose" function does not receive verboseObject.result, "output", sync', testVerboseResultStart, 'output', {}, true);
test('"verbose" function does not receive verboseObject.result, "command", spawn error, sync', testVerboseResultStart, 'command', earlyErrorOptionsSync, true);
test('"verbose" function does not receive verboseObject.result, "output", spawn error, sync', testVerboseResultStart, 'output', earlyErrorOptionsSync, true);
================================================
FILE: test/verbose/custom-start.js
================================================
import test from 'ava';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {fullStdio} from '../helpers/stdio.js';
import {
QUOTE,
getNormalizedLine,
testTimestamp,
runVerboseSubprocess,
} from '../helpers/verbose.js';
setFixtureDirectory();
const testPrintCommandCustom = async (t, fdNumber, worker, isSync) => {
const {stderr} = await runVerboseSubprocess({
optionsFixture: 'custom-print.js',
worker,
isSync,
type: 'command',
fdNumber,
});
t.is(getNormalizedLine(stderr), `${testTimestamp} [0] $ noop-verbose.js ${QUOTE}. .${QUOTE}`);
};
test('Prints command, verbose custom', testPrintCommandCustom, undefined, false, false);
test('Prints command, verbose custom, fd-specific stdout', testPrintCommandCustom, 'stdout', false, false);
test('Prints command, verbose custom, fd-specific stderr', testPrintCommandCustom, 'stderr', false, false);
test('Prints command, verbose custom, fd-specific fd3', testPrintCommandCustom, 'fd3', false, false);
test('Prints command, verbose custom, fd-specific ipc', testPrintCommandCustom, 'ipc', false, false);
test('Prints command, verbose custom, sync', testPrintCommandCustom, undefined, false, true);
test('Prints command, verbose custom, fd-specific stdout, sync', testPrintCommandCustom, 'stdout', false, true);
test('Prints command, verbose custom, fd-specific stderr, sync', testPrintCommandCustom, 'stderr', false, true);
test('Prints command, verbose custom, fd-specific fd3, sync', testPrintCommandCustom, 'fd3', false, true);
test('Prints command, verbose custom, fd-specific ipc, sync', testPrintCommandCustom, 'ipc', false, true);
test('Prints command, verbose custom, worker', testPrintCommandCustom, undefined, true, false);
test('Prints command, verbose custom, fd-specific stdout, worker', testPrintCommandCustom, 'stdout', true, false);
test('Prints command, verbose custom, fd-specific stderr, worker', testPrintCommandCustom, 'stderr', true, false);
test('Prints command, verbose custom, fd-specific fd3, worker', testPrintCommandCustom, 'fd3', true, false);
test('Prints command, verbose custom, fd-specific ipc, worker', testPrintCommandCustom, 'ipc', true, false);
test('Prints command, verbose custom, worker, sync', testPrintCommandCustom, undefined, true, true);
test('Prints command, verbose custom, fd-specific stdout, worker, sync', testPrintCommandCustom, 'stdout', true, true);
test('Prints command, verbose custom, fd-specific stderr, worker, sync', testPrintCommandCustom, 'stderr', true, true);
test('Prints command, verbose custom, fd-specific fd3, worker, sync', testPrintCommandCustom, 'fd3', true, true);
test('Prints command, verbose custom, fd-specific ipc, worker, sync', testPrintCommandCustom, 'ipc', true, true);
const testPrintCommandOrder = async (t, fdNumber, secondFdNumber, hasOutput) => {
const {stderr} = await runVerboseSubprocess({
optionsFixture: 'custom-print-multiple.js',
type: 'command',
fdNumber,
secondFdNumber,
...fullStdio,
});
if (hasOutput) {
t.is(getNormalizedLine(stderr), `${testTimestamp} [0] $ noop-verbose.js ${QUOTE}. .${QUOTE}`);
} else {
t.is(stderr, '');
}
};
test('Prints command, verbose custom, fd-specific stdout+stderr', testPrintCommandOrder, 'stdout', 'stderr', true);
test('Prints command, verbose custom, fd-specific stderr+stdout', testPrintCommandOrder, 'stderr', 'stdout', false);
test('Prints command, verbose custom, fd-specific stdout+fd3', testPrintCommandOrder, 'stdout', 'fd3', true);
test('Prints command, verbose custom, fd-specific fd3+stdout', testPrintCommandOrder, 'fd3', 'stdout', false);
test('Prints command, verbose custom, fd-specific stdout+ipc', testPrintCommandOrder, 'stdout', 'ipc', true);
test('Prints command, verbose custom, fd-specific ipc+stdout', testPrintCommandOrder, 'ipc', 'stdout', false);
test('Prints command, verbose custom, fd-specific stderr+fd3', testPrintCommandOrder, 'stderr', 'fd3', true);
test('Prints command, verbose custom, fd-specific fd3+stderr', testPrintCommandOrder, 'fd3', 'stderr', false);
test('Prints command, verbose custom, fd-specific stderr+ipc', testPrintCommandOrder, 'stderr', 'ipc', true);
test('Prints command, verbose custom, fd-specific ipc+stderr', testPrintCommandOrder, 'ipc', 'stderr', false);
test('Prints command, verbose custom, fd-specific fd3+ipc', testPrintCommandOrder, 'fd3', 'ipc', true);
test('Prints command, verbose custom, fd-specific ipc+fd3', testPrintCommandOrder, 'ipc', 'fd3', false);
const testVerboseMessage = async (t, isSync) => {
const {stderr} = await runVerboseSubprocess({
isSync,
type: 'command',
eventProperty: 'message',
});
t.is(stderr, `noop-verbose.js ${QUOTE}. .${QUOTE}`);
};
test('"verbose" function receives verboseObject.message', testVerboseMessage, false);
test('"verbose" function receives verboseObject.message, sync', testVerboseMessage, true);
================================================
FILE: test/verbose/custom-throw.js
================================================
import test from 'ava';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
import {runVerboseSubprocess} from '../helpers/verbose.js';
import {earlyErrorOptions, earlyErrorOptionsSync} from '../helpers/early-error.js';
setFixtureDirectory();
const testCommandThrowPropagate = async (t, type, options, isSync) => {
const {nestedResult} = await runVerboseSubprocess({
isSync,
type,
optionsFixture: 'custom-throw.js',
errorMessage: foobarString,
...options,
});
t.true(nestedResult instanceof Error);
t.is(nestedResult.message, foobarString);
};
test('Propagate verbose exception in "verbose" function, "command"', testCommandThrowPropagate, 'command', {}, false);
test('Propagate verbose exception in "verbose" function, "error"', testCommandThrowPropagate, 'error', {}, false);
test('Propagate verbose exception in "verbose" function, "duration"', testCommandThrowPropagate, 'duration', {}, false);
test('Propagate verbose exception in "verbose" function, "command", spawn error', testCommandThrowPropagate, 'command', earlyErrorOptions, false);
test('Propagate verbose exception in "verbose" function, "error", spawn error', testCommandThrowPropagate, 'error', earlyErrorOptions, false);
test('Propagate verbose exception in "verbose" function, "duration", spawn error', testCommandThrowPropagate, 'duration', earlyErrorOptions, false);
test('Propagate verbose exception in "verbose" function, "command", sync', testCommandThrowPropagate, 'command', {}, true);
test('Propagate verbose exception in "verbose" function, "error", sync', testCommandThrowPropagate, 'error', {}, true);
test('Propagate verbose exception in "verbose" function, "duration", sync', testCommandThrowPropagate, 'duration', {}, true);
test('Propagate verbose exception in "verbose" function, "command", spawn error, sync', testCommandThrowPropagate, 'command', earlyErrorOptionsSync, true);
test('Propagate verbose exception in "verbose" function, "error", spawn error, sync', testCommandThrowPropagate, 'error', earlyErrorOptionsSync, true);
test('Propagate verbose exception in "verbose" function, "duration", spawn error, sync', testCommandThrowPropagate, 'duration', earlyErrorOptionsSync, true);
const testCommandThrowHandle = async (t, type, isSync) => {
const {nestedResult} = await runVerboseSubprocess({
isSync,
type,
optionsFixture: 'custom-throw.js',
errorMessage: foobarString,
});
t.true(nestedResult instanceof Error);
t.true(nestedResult.stack.startsWith(isSync ? 'ExecaSyncError' : 'ExecaError'));
t.true(nestedResult.cause instanceof Error);
t.is(nestedResult.cause.message, foobarString);
};
test('Handle exceptions in "verbose" function, "output"', testCommandThrowHandle, 'output', false);
test('Handle exceptions in "verbose" function, "ipc"', testCommandThrowHandle, 'ipc', false);
test('Handle exceptions in "verbose" function, "output", sync', testCommandThrowHandle, 'output', true);
const testCommandThrowWrap = async (t, type, options, isSync) => {
const {nestedResult} = await runVerboseSubprocess({
isSync,
type,
optionsFixture: 'custom-throw.js',
errorMessage: foobarString,
...options,
});
t.true(nestedResult instanceof Error);
t.true(nestedResult.stack.startsWith(isSync ? 'ExecaSyncError' : 'ExecaError'));
t.true(nestedResult.cause instanceof Error);
t.not(nestedResult.cause.message, foobarString);
};
test('Propagate wrapped exception in "verbose" function, "output", spawn error', testCommandThrowWrap, 'output', earlyErrorOptions, false);
test('Propagate wrapped exception in "verbose" function, "ipc", spawn error', testCommandThrowWrap, 'ipc', earlyErrorOptions, false);
test('Propagate wrapped exception in "verbose" function, "output", spawn error, sync', testCommandThrowWrap, 'output', earlyErrorOptionsSync, true);
================================================
FILE: test/verbose/error.js
================================================
import test from 'ava';
import {red} from 'yoctocolors';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
import {nestedSubprocess} from '../helpers/nested.js';
import {
QUOTE,
runErrorSubprocess,
runEarlyErrorSubprocess,
getErrorLine,
getErrorLines,
testTimestamp,
getVerboseOption,
stdoutNoneOption,
stdoutShortOption,
stdoutFullOption,
stderrNoneOption,
stderrShortOption,
stderrFullOption,
fd3NoneOption,
fd3ShortOption,
fd3FullOption,
ipcNoneOption,
ipcShortOption,
ipcFullOption,
} from '../helpers/verbose.js';
setFixtureDirectory();
const testPrintError = async (t, verbose, isSync) => {
const stderr = await runErrorSubprocess(t, verbose, isSync);
t.is(getErrorLine(stderr), `${testTimestamp} [0] × Command failed with exit code 2: noop-fail.js 1 ${foobarString}`);
};
test('Prints error, verbose "short"', testPrintError, 'short', false);
test('Prints error, verbose "full"', testPrintError, 'full', false);
test('Prints error, verbose "short", fd-specific stdout', testPrintError, stdoutShortOption, false);
test('Prints error, verbose "full", fd-specific stdout', testPrintError, stdoutFullOption, false);
test('Prints error, verbose "short", fd-specific stderr', testPrintError, stderrShortOption, false);
test('Prints error, verbose "full", fd-specific stderr', testPrintError, stderrFullOption, false);
test('Prints error, verbose "short", fd-specific fd3', testPrintError, fd3ShortOption, false);
test('Prints error, verbose "full", fd-specific fd3', testPrintError, fd3FullOption, false);
test('Prints error, verbose "short", fd-specific ipc', testPrintError, ipcShortOption, false);
test('Prints error, verbose "full", fd-specific ipc', testPrintError, ipcFullOption, false);
test('Prints error, verbose "short", sync', testPrintError, 'short', true);
test('Prints error, verbose "full", sync', testPrintError, 'full', true);
test('Prints error, verbose "short", fd-specific stdout, sync', testPrintError, stdoutShortOption, true);
test('Prints error, verbose "full", fd-specific stdout, sync', testPrintError, stdoutFullOption, true);
test('Prints error, verbose "short", fd-specific stderr, sync', testPrintError, stderrShortOption, true);
test('Prints error, verbose "full", fd-specific stderr, sync', testPrintError, stderrFullOption, true);
test('Prints error, verbose "short", fd-specific fd3, sync', testPrintError, fd3ShortOption, true);
test('Prints error, verbose "full", fd-specific fd3, sync', testPrintError, fd3FullOption, true);
test('Prints error, verbose "short", fd-specific ipc, sync', testPrintError, ipcShortOption, true);
test('Prints error, verbose "full", fd-specific ipc, sync', testPrintError, ipcFullOption, true);
const testNoPrintError = async (t, verbose, isSync) => {
const stderr = await runErrorSubprocess(t, verbose, isSync, false);
t.is(getErrorLine(stderr), undefined);
};
test('Does not print error, verbose "none"', testNoPrintError, 'none', false);
test('Does not print error, verbose default', testNoPrintError, undefined, false);
test('Does not print error, verbose "none", fd-specific stdout', testNoPrintError, stdoutNoneOption, false);
test('Does not print error, verbose "none", fd-specific stderr', testNoPrintError, stderrNoneOption, false);
test('Does not print error, verbose "none", fd-specific fd3', testNoPrintError, fd3NoneOption, false);
test('Does not print error, verbose "none", fd-specific ipc', testNoPrintError, ipcNoneOption, false);
test('Does not print error, verbose default, fd-specific', testNoPrintError, {}, false);
test('Does not print error, verbose "none", sync', testNoPrintError, 'none', true);
test('Does not print error, verbose default, sync', testNoPrintError, undefined, true);
test('Does not print error, verbose "none", fd-specific stdout, sync', testNoPrintError, stdoutNoneOption, true);
test('Does not print error, verbose "none", fd-specific stderr, sync', testNoPrintError, stderrNoneOption, true);
test('Does not print error, verbose "none", fd-specific fd3, sync', testNoPrintError, fd3NoneOption, true);
test('Does not print error, verbose "none", fd-specific ipc, sync', testNoPrintError, ipcNoneOption, true);
test('Does not print error, verbose default, fd-specific, sync', testNoPrintError, {}, true);
const testPrintNoError = async (t, isSync) => {
const {stderr} = await nestedSubprocess('noop.js', [foobarString], {verbose: 'short', isSync});
t.is(getErrorLine(stderr), undefined);
};
test('Does not print error if none', testPrintNoError, false);
test('Does not print error if none, sync', testPrintNoError, true);
const testPrintErrorEarly = async (t, isSync) => {
const stderr = await runEarlyErrorSubprocess(t, isSync);
t.is(getErrorLine(stderr), undefined);
};
test('Prints early validation error', testPrintErrorEarly, false);
test('Prints early validation error, sync', testPrintErrorEarly, true);
test('Does not repeat stdout|stderr with error', async t => {
const stderr = await runErrorSubprocess(t, 'short');
t.deepEqual(getErrorLines(stderr), [`${testTimestamp} [0] × Command failed with exit code 2: noop-fail.js 1 ${foobarString}`]);
});
test('Prints error differently if "reject" is false', async t => {
const {stderr} = await nestedSubprocess('noop-fail.js', ['1', foobarString], {verbose: 'short', reject: false});
t.deepEqual(getErrorLines(stderr), [`${testTimestamp} [0] ‼ Command failed with exit code 2: noop-fail.js 1 ${foobarString}`]);
});
const testPipeError = async (t, parentFixture, sourceVerbose, destinationVerbose) => {
const {stderr} = await t.throwsAsync(nestedSubprocess('noop-fail.js', ['1'], {
parentFixture,
sourceOptions: getVerboseOption(sourceVerbose),
destinationFile: 'stdin-fail.js',
destinationOptions: getVerboseOption(destinationVerbose),
}));
const lines = getErrorLines(stderr);
t.is(lines.includes(`${testTimestamp} [0] × Command failed with exit code 2: noop-fail.js 1`), sourceVerbose);
t.is(lines.includes(`${testTimestamp} [${sourceVerbose ? 1 : 0}] × Command failed with exit code 2: stdin-fail.js`), destinationVerbose);
};
test('Prints both errors piped with .pipe("file")', testPipeError, 'nested-pipe-file.js', true, true);
test('Prints both errors piped with .pipe`command`', testPipeError, 'nested-pipe-script.js', true, true);
test('Prints both errors piped with .pipe(subprocess)', testPipeError, 'nested-pipe-subprocesses.js', true, true);
test('Prints first error piped with .pipe("file")', testPipeError, 'nested-pipe-file.js', true, false);
test('Prints first error piped with .pipe`command`', testPipeError, 'nested-pipe-script.js', true, false);
test('Prints first error piped with .pipe(subprocess)', testPipeError, 'nested-pipe-subprocesses.js', true, false);
test('Prints second error piped with .pipe("file")', testPipeError, 'nested-pipe-file.js', false, true);
test('Prints second error piped with .pipe`command`', testPipeError, 'nested-pipe-script.js', false, true);
test('Prints second error piped with .pipe(subprocess)', testPipeError, 'nested-pipe-subprocesses.js', false, true);
test('Prints neither errors piped with .pipe("file")', testPipeError, 'nested-pipe-file.js', false, false);
test('Prints neither errors piped with .pipe`command`', testPipeError, 'nested-pipe-script.js', false, false);
test('Prints neither errors piped with .pipe(subprocess)', testPipeError, 'nested-pipe-subprocesses.js', false, false);
test('Quotes spaces from error', async t => {
const {stderr} = await t.throwsAsync(nestedSubprocess('noop-forever.js', ['foo bar'], {parentFixture: 'nested-fail.js', verbose: 'short'}));
t.deepEqual(getErrorLines(stderr), [
`${testTimestamp} [0] × Command was killed with SIGTERM (Termination): noop-forever.js ${QUOTE}foo bar${QUOTE}`,
`${testTimestamp} [0] × foo bar`,
]);
});
test('Quotes special punctuation from error', async t => {
const {stderr} = await t.throwsAsync(nestedSubprocess('noop-forever.js', ['%'], {parentFixture: 'nested-fail.js', verbose: 'short'}));
t.deepEqual(getErrorLines(stderr), [
`${testTimestamp} [0] × Command was killed with SIGTERM (Termination): noop-forever.js ${QUOTE}%${QUOTE}`,
`${testTimestamp} [0] × %`,
]);
});
test('Does not escape internal characters from error', async t => {
const {stderr} = await t.throwsAsync(nestedSubprocess('noop-forever.js', ['ã'], {parentFixture: 'nested-fail.js', verbose: 'short'}));
t.deepEqual(getErrorLines(stderr), [
`${testTimestamp} [0] × Command was killed with SIGTERM (Termination): noop-forever.js ${QUOTE}ã${QUOTE}`,
`${testTimestamp} [0] × ã`,
]);
});
test('Escapes and strips color sequences from error', async t => {
const {stderr} = await t.throwsAsync(nestedSubprocess('noop-forever.js', [red(foobarString)], {parentFixture: 'nested-fail.js', verbose: 'short'}, {env: {FORCE_COLOR: '1'}}));
t.deepEqual(getErrorLines(stderr), [
`${testTimestamp} [0] × Command was killed with SIGTERM (Termination): noop-forever.js ${QUOTE}\\u001b[31m${foobarString}\\u001b[39m${QUOTE}`,
`${testTimestamp} [0] × ${foobarString}`,
]);
});
test('Escapes control characters from error', async t => {
const {stderr} = await t.throwsAsync(nestedSubprocess('noop-forever.js', ['\u0001'], {parentFixture: 'nested-fail.js', verbose: 'short'}));
t.deepEqual(getErrorLines(stderr), [
`${testTimestamp} [0] × Command was killed with SIGTERM (Termination): noop-forever.js ${QUOTE}\\u0001${QUOTE}`,
`${testTimestamp} [0] × \\u0001`,
]);
});
================================================
FILE: test/verbose/info.js
================================================
import test from 'ava';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {execa, execaSync} from '../../index.js';
import {foobarString} from '../helpers/input.js';
import {nestedSubprocess} from '../helpers/nested.js';
import {
QUOTE,
getCommandLine,
getOutputLine,
getNormalizedLines,
testTimestamp,
} from '../helpers/verbose.js';
import {earlyErrorOptions, earlyErrorOptionsSync} from '../helpers/early-error.js';
setFixtureDirectory();
const testVerboseGeneral = async (t, execaMethod) => {
const {all} = await execaMethod('verbose-script.js', {env: {NODE_DEBUG: 'execa'}, all: true});
t.deepEqual(getNormalizedLines(all), [
`${testTimestamp} [0] $ node -e ${QUOTE}console.error(1)${QUOTE}`,
'1',
`${testTimestamp} [0] √ (done in 0ms)`,
`${testTimestamp} [1] $ node -e ${QUOTE}process.exit(2)${QUOTE}`,
`${testTimestamp} [1] ‼ Command failed with exit code 2: node -e ${QUOTE}process.exit(2)${QUOTE}`,
`${testTimestamp} [1] ‼ (done in 0ms)`,
]);
};
test('Prints command, NODE_DEBUG=execa + "inherit"', testVerboseGeneral, execa);
test('Prints command, NODE_DEBUG=execa + "inherit", sync', testVerboseGeneral, execaSync);
test('NODE_DEBUG=execa changes verbose default value to "full"', async t => {
const {stderr} = await nestedSubprocess('noop.js', [foobarString], {}, {env: {NODE_DEBUG: 'execa'}});
t.is(getCommandLine(stderr), `${testTimestamp} [0] $ noop.js ${foobarString}`);
t.is(getOutputLine(stderr), `${testTimestamp} [0] ${foobarString}`);
});
const testDebugEnvPriority = async (t, isSync) => {
const {stderr} = await nestedSubprocess('noop.js', [foobarString], {verbose: 'short', isSync}, {env: {NODE_DEBUG: 'execa'}});
t.is(getCommandLine(stderr), `${testTimestamp} [0] $ noop.js ${foobarString}`);
t.is(getOutputLine(stderr), undefined);
};
test('NODE_DEBUG=execa has lower priority', testDebugEnvPriority, false);
test('NODE_DEBUG=execa has lower priority, sync', testDebugEnvPriority, true);
const invalidFalseMessage = 'renamed to "verbose: \'none\'"';
const invalidTrueMessage = 'renamed to "verbose: \'short\'"';
const invalidUnknownMessage = 'Allowed values are: \'none\', \'short\', \'full\'';
const testInvalidVerbose = (t, verbose, expectedMessage, execaMethod) => {
const {message} = t.throws(() => {
execaMethod('empty.js', {verbose});
});
t.true(message.includes(expectedMessage));
};
test('Does not allow "verbose: false"', testInvalidVerbose, false, invalidFalseMessage, execa);
test('Does not allow "verbose: false", sync', testInvalidVerbose, false, invalidFalseMessage, execaSync);
test('Does not allow "verbose: true"', testInvalidVerbose, true, invalidTrueMessage, execa);
test('Does not allow "verbose: true", sync', testInvalidVerbose, true, invalidTrueMessage, execaSync);
test('Does not allow "verbose: \'unknown\'"', testInvalidVerbose, 'unknown', invalidUnknownMessage, execa);
test('Does not allow "verbose: \'unknown\'", sync', testInvalidVerbose, 'unknown', invalidUnknownMessage, execaSync);
const testValidationError = async (t, isSync) => {
const {stderr, nestedResult} = await nestedSubprocess('empty.js', {verbose: 'full', isSync, timeout: []});
t.deepEqual(getNormalizedLines(stderr), [`${testTimestamp} [0] $ empty.js`]);
t.true(nestedResult instanceof Error);
};
test('Prints validation errors', testValidationError, false);
test('Prints validation errors, sync', testValidationError, true);
test('Prints early spawn errors', async t => {
const {stderr} = await nestedSubprocess('empty.js', {...earlyErrorOptions, verbose: 'full'});
t.deepEqual(getNormalizedLines(stderr), [
`${testTimestamp} [0] $ empty.js`,
`${testTimestamp} [0] × Command failed with ERR_INVALID_ARG_TYPE: empty.js`,
`${testTimestamp} [0] × The "options.detached" property must be of type boolean. Received type string ('true')`,
`${testTimestamp} [0] × (done in 0ms)`,
]);
});
test('Prints early spawn errors, sync', async t => {
const {stderr} = await nestedSubprocess('empty.js', {...earlyErrorOptionsSync, verbose: 'full', isSync: true});
t.deepEqual(getNormalizedLines(stderr), [
`${testTimestamp} [0] $ empty.js`,
`${testTimestamp} [0] × Command failed with ERR_OUT_OF_RANGE: empty.js`,
`${testTimestamp} [0] × The value of "options.maxBuffer" is out of range. It must be a positive number. Received false`,
`${testTimestamp} [0] × (done in 0ms)`,
]);
});
================================================
FILE: test/verbose/ipc.js
================================================
import {on} from 'node:events';
import {inspect} from 'node:util';
import test from 'ava';
import {red} from 'yoctocolors';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString, foobarObject} from '../helpers/input.js';
import {nestedSubprocess, nestedInstance} from '../helpers/nested.js';
import {
getIpcLine,
getIpcLines,
testTimestamp,
ipcNoneOption,
ipcShortOption,
ipcFullOption,
} from '../helpers/verbose.js';
setFixtureDirectory();
const testPrintIpc = async (t, verbose) => {
const {stderr} = await nestedSubprocess('ipc-send.js', {ipc: true, verbose});
t.is(getIpcLine(stderr), `${testTimestamp} [0] * ${foobarString}`);
};
test('Prints IPC, verbose "full"', testPrintIpc, 'full');
test('Prints IPC, verbose "full", fd-specific', testPrintIpc, ipcFullOption);
const testNoPrintIpc = async (t, verbose) => {
const {stderr} = await nestedSubprocess('ipc-send.js', {ipc: true, verbose});
t.is(getIpcLine(stderr), undefined);
};
test('Does not print IPC, verbose default', testNoPrintIpc, undefined);
test('Does not print IPC, verbose "none"', testNoPrintIpc, 'none');
test('Does not print IPC, verbose "short"', testNoPrintIpc, 'short');
test('Does not print IPC, verbose default, fd-specific', testNoPrintIpc, {});
test('Does not print IPC, verbose "none", fd-specific', testNoPrintIpc, ipcNoneOption);
test('Does not print IPC, verbose "short", fd-specific', testNoPrintIpc, ipcShortOption);
const testNoIpc = async (t, ipc) => {
const {nestedResult, stderr} = await nestedSubprocess('ipc-send.js', {ipc, verbose: 'full'});
t.true(nestedResult instanceof Error);
t.true(nestedResult.message.includes('sendMessage() can only be used'));
t.is(getIpcLine(stderr), undefined);
};
test('Does not print IPC, ipc: false', testNoIpc, false);
test('Does not print IPC, ipc: default', testNoIpc, undefined);
test('Prints objects from IPC', async t => {
const {stderr} = await nestedSubprocess('ipc-send-json.js', [JSON.stringify(foobarObject)], {ipc: true, verbose: 'full'});
t.is(getIpcLine(stderr), `${testTimestamp} [0] * ${inspect(foobarObject)}`);
});
test('Prints multiline arrays from IPC', async t => {
const bigArray = Array.from({length: 100}, (_, index) => index);
const {stderr} = await nestedSubprocess('ipc-send-json.js', [JSON.stringify(bigArray)], {ipc: true, verbose: 'full'});
const ipcLines = getIpcLines(stderr);
t.is(ipcLines[0], `${testTimestamp} [0] * [`);
t.is(ipcLines.at(-2), `${testTimestamp} [0] * 96, 97, 98, 99`);
t.is(ipcLines.at(-1), `${testTimestamp} [0] * ]`);
});
test('Does not quote spaces from IPC', async t => {
const {stderr} = await nestedSubprocess('ipc-send.js', ['foo bar'], {ipc: true, verbose: 'full'});
t.is(getIpcLine(stderr), `${testTimestamp} [0] * foo bar`);
});
test('Does not quote newlines from IPC', async t => {
const {stderr} = await nestedSubprocess('ipc-send.js', ['foo\nbar'], {ipc: true, verbose: 'full'});
t.deepEqual(getIpcLines(stderr), [
`${testTimestamp} [0] * foo`,
`${testTimestamp} [0] * bar`,
]);
});
test('Does not quote special punctuation from IPC', async t => {
const {stderr} = await nestedSubprocess('ipc-send.js', ['%'], {ipc: true, verbose: 'full'});
t.is(getIpcLine(stderr), `${testTimestamp} [0] * %`);
});
test('Does not escape internal characters from IPC', async t => {
const {stderr} = await nestedSubprocess('ipc-send.js', ['ã'], {ipc: true, verbose: 'full'});
t.is(getIpcLine(stderr), `${testTimestamp} [0] * ã`);
});
test('Strips color sequences from IPC', async t => {
const {stderr} = await nestedSubprocess('ipc-send.js', [red(foobarString)], {ipc: true, verbose: 'full'}, {env: {FORCE_COLOR: '1'}});
t.is(getIpcLine(stderr), `${testTimestamp} [0] * ${foobarString}`);
});
test('Escapes control characters from IPC', async t => {
const {stderr} = await nestedSubprocess('ipc-send.js', ['\u0001'], {ipc: true, verbose: 'full'});
t.is(getIpcLine(stderr), `${testTimestamp} [0] * \\u0001`);
});
test('Prints IPC progressively', async t => {
const subprocess = nestedInstance('ipc-send-forever.js', {ipc: true, verbose: 'full'});
for await (const chunk of on(subprocess.stderr, 'data')) {
const ipcLine = getIpcLine(chunk.toString());
if (ipcLine !== undefined) {
t.is(ipcLine, `${testTimestamp} [0] * ${foobarString}`);
break;
}
}
subprocess.kill();
await t.throwsAsync(subprocess);
});
================================================
FILE: test/verbose/log.js
================================================
import {stripVTControlCharacters} from 'node:util';
import test from 'ava';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
import {nestedSubprocess} from '../helpers/nested.js';
import {getNormalizedLines, getCommandLine, getCompletionLine} from '../helpers/verbose.js';
import {PARALLEL_COUNT} from '../helpers/parallel.js';
setFixtureDirectory();
const testNoStdout = async (t, verbose, isSync) => {
const {stdout} = await nestedSubprocess('noop.js', [foobarString], {verbose, stdio: 'inherit', isSync});
t.is(stdout, foobarString);
};
test('Logs on stderr not stdout, verbose "none"', testNoStdout, 'none', false);
test('Logs on stderr not stdout, verbose "short"', testNoStdout, 'short', false);
test('Logs on stderr not stdout, verbose "full"', testNoStdout, 'full', false);
test('Logs on stderr not stdout, verbose "none", sync', testNoStdout, 'none', true);
test('Logs on stderr not stdout, verbose "short", sync', testNoStdout, 'short', true);
test('Logs on stderr not stdout, verbose "full", sync', testNoStdout, 'full', true);
const testColor = async (t, expectedResult, forceColor) => {
const {stderr} = await nestedSubprocess('noop.js', [foobarString], {verbose: 'short'}, {env: {FORCE_COLOR: forceColor}});
t.is(stderr !== stripVTControlCharacters(stderr), expectedResult);
};
test('Prints with colors if supported', testColor, true, '1');
test('Prints without colors if not supported', testColor, false, '0');
test.serial('Prints lines in order when interleaved with subprocess stderr', async t => {
const results = await Promise.all(Array.from({length: PARALLEL_COUNT}, () =>
nestedSubprocess('noop-fd.js', ['2', `${foobarString}\n`], {verbose: 'full', stderr: 'inherit'}, {all: true}),
));
for (const {all} of results) {
t.deepEqual(
getNormalizedLines(all),
[getCommandLine(all), foobarString, getCompletionLine(all)],
);
}
});
================================================
FILE: test/verbose/output-buffer.js
================================================
import test from 'ava';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString, foobarUppercase} from '../helpers/input.js';
import {nestedSubprocess} from '../helpers/nested.js';
import {
getOutputLine,
testTimestamp,
stdoutNoneOption,
stdoutFullOption,
stderrFullOption,
} from '../helpers/verbose.js';
setFixtureDirectory();
const testPrintOutputNoBuffer = async (t, verbose, buffer, isSync) => {
const {stderr} = await nestedSubprocess('noop.js', [foobarString], {verbose, buffer, isSync});
t.is(getOutputLine(stderr), `${testTimestamp} [0] ${foobarString}`);
};
test('Prints stdout, buffer: false', testPrintOutputNoBuffer, 'full', false, false);
test('Prints stdout, buffer: false, fd-specific buffer', testPrintOutputNoBuffer, 'full', {stdout: false}, false);
test('Prints stdout, buffer: false, fd-specific verbose', testPrintOutputNoBuffer, stdoutFullOption, false, false);
test('Prints stdout, buffer: false, sync', testPrintOutputNoBuffer, 'full', false, true);
test('Prints stdout, buffer: false, fd-specific buffer, sync', testPrintOutputNoBuffer, 'full', {stdout: false}, true);
test('Prints stdout, buffer: false, fd-specific verbose, sync', testPrintOutputNoBuffer, stdoutFullOption, false, true);
const testPrintOutputNoBufferFalse = async (t, verbose, buffer, isSync) => {
const {stderr} = await nestedSubprocess('noop.js', [foobarString], {verbose, buffer, isSync});
t.is(getOutputLine(stderr), undefined);
};
test('Does not print stdout, buffer: false, fd-specific none', testPrintOutputNoBufferFalse, stdoutNoneOption, false, false);
test('Does not print stdout, buffer: false, different fd', testPrintOutputNoBufferFalse, stderrFullOption, false, false);
test('Does not print stdout, buffer: false, different fd, fd-specific buffer', testPrintOutputNoBufferFalse, stderrFullOption, {stdout: false}, false);
test('Does not print stdout, buffer: false, fd-specific none, sync', testPrintOutputNoBufferFalse, stdoutNoneOption, false, true);
test('Does not print stdout, buffer: false, different fd, sync', testPrintOutputNoBufferFalse, stderrFullOption, false, true);
test('Does not print stdout, buffer: false, different fd, fd-specific buffer, sync', testPrintOutputNoBufferFalse, stderrFullOption, {stdout: false}, true);
const testPrintOutputNoBufferTransform = async (t, buffer, isSync) => {
const {stderr} = await nestedSubprocess('noop.js', [foobarString], {
optionsFixture: 'generator-uppercase.js',
verbose: 'full',
buffer,
isSync,
});
t.is(getOutputLine(stderr), `${testTimestamp} [0] ${foobarUppercase}`);
};
test('Prints stdout, buffer: false, transform', testPrintOutputNoBufferTransform, false, false);
test('Prints stdout, buffer: false, transform, fd-specific buffer', testPrintOutputNoBufferTransform, {stdout: false}, false);
test('Prints stdout, buffer: false, transform, sync', testPrintOutputNoBufferTransform, false, true);
test('Prints stdout, buffer: false, transform, fd-specific buffer, sync', testPrintOutputNoBufferTransform, {stdout: false}, true);
================================================
FILE: test/verbose/output-enable.js
================================================
import test from 'ava';
import {red} from 'yoctocolors';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString, foobarUtf16Uint8Array} from '../helpers/input.js';
import {fullStdio} from '../helpers/stdio.js';
import {nestedSubprocess} from '../helpers/nested.js';
import {
runErrorSubprocess,
getOutputLine,
getOutputLines,
testTimestamp,
stdoutNoneOption,
stdoutShortOption,
stdoutFullOption,
stderrNoneOption,
stderrShortOption,
stderrFullOption,
fd3NoneOption,
fd3ShortOption,
fd3FullOption,
} from '../helpers/verbose.js';
setFixtureDirectory();
const testPrintOutput = async (t, verbose, fdNumber, isSync) => {
const {stderr} = await nestedSubprocess('noop-fd.js', [`${fdNumber}`, foobarString], {verbose, isSync});
t.is(getOutputLine(stderr), `${testTimestamp} [0] ${foobarString}`);
};
test('Prints stdout, verbose "full"', testPrintOutput, 'full', 1, false);
test('Prints stderr, verbose "full"', testPrintOutput, 'full', 2, false);
test('Prints stdout, verbose "full", fd-specific', testPrintOutput, stdoutFullOption, 1, false);
test('Prints stderr, verbose "full", fd-specific', testPrintOutput, stderrFullOption, 2, false);
test('Prints stdout, verbose "full", sync', testPrintOutput, 'full', 1, true);
test('Prints stderr, verbose "full", sync', testPrintOutput, 'full', 2, true);
test('Prints stdout, verbose "full", fd-specific, sync', testPrintOutput, stdoutFullOption, 1, true);
test('Prints stderr, verbose "full", fd-specific, sync', testPrintOutput, stderrFullOption, 2, true);
const testNoPrintOutput = async (t, verbose, fdNumber, isSync) => {
const {stderr} = await nestedSubprocess('noop-fd.js', [`${fdNumber}`, foobarString], {verbose, ...fullStdio, isSync});
t.is(getOutputLine(stderr), undefined);
};
test('Does not print stdout, verbose default', testNoPrintOutput, undefined, 1, false);
test('Does not print stdout, verbose "none"', testNoPrintOutput, 'none', 1, false);
test('Does not print stdout, verbose "short"', testNoPrintOutput, 'short', 1, false);
test('Does not print stderr, verbose default', testNoPrintOutput, undefined, 2, false);
test('Does not print stderr, verbose "none"', testNoPrintOutput, 'none', 2, false);
test('Does not print stderr, verbose "short"', testNoPrintOutput, 'short', 2, false);
test('Does not print stdio[*], verbose default', testNoPrintOutput, undefined, 3, false);
test('Does not print stdio[*], verbose "none"', testNoPrintOutput, 'none', 3, false);
test('Does not print stdio[*], verbose "short"', testNoPrintOutput, 'short', 3, false);
test('Does not print stdio[*], verbose "full"', testNoPrintOutput, 'full', 3, false);
test('Does not print stdout, verbose default, fd-specific', testNoPrintOutput, {}, 1, false);
test('Does not print stdout, verbose "none", fd-specific', testNoPrintOutput, stdoutNoneOption, 1, false);
test('Does not print stdout, verbose "short", fd-specific', testNoPrintOutput, stdoutShortOption, 1, false);
test('Does not print stderr, verbose default, fd-specific', testNoPrintOutput, {}, 2, false);
test('Does not print stderr, verbose "none", fd-specific', testNoPrintOutput, stderrNoneOption, 2, false);
test('Does not print stderr, verbose "short", fd-specific', testNoPrintOutput, stderrShortOption, 2, false);
test('Does not print stdio[*], verbose default, fd-specific', testNoPrintOutput, {}, 3, false);
test('Does not print stdio[*], verbose "none", fd-specific', testNoPrintOutput, fd3NoneOption, 3, false);
test('Does not print stdio[*], verbose "short", fd-specific', testNoPrintOutput, fd3ShortOption, 3, false);
test('Does not print stdio[*], verbose "full", fd-specific', testNoPrintOutput, fd3FullOption, 3, false);
test('Does not print stdout, verbose default, sync', testNoPrintOutput, undefined, 1, true);
test('Does not print stdout, verbose "none", sync', testNoPrintOutput, 'none', 1, true);
test('Does not print stdout, verbose "short", sync', testNoPrintOutput, 'short', 1, true);
test('Does not print stderr, verbose default, sync', testNoPrintOutput, undefined, 2, true);
test('Does not print stderr, verbose "none", sync', testNoPrintOutput, 'none', 2, true);
test('Does not print stderr, verbose "short", sync', testNoPrintOutput, 'short', 2, true);
test('Does not print stdio[*], verbose default, sync', testNoPrintOutput, undefined, 3, true);
test('Does not print stdio[*], verbose "none", sync', testNoPrintOutput, 'none', 3, true);
test('Does not print stdio[*], verbose "short", sync', testNoPrintOutput, 'short', 3, true);
test('Does not print stdio[*], verbose "full", sync', testNoPrintOutput, 'full', 3, true);
test('Does not print stdout, verbose default, fd-specific, sync', testNoPrintOutput, {}, 1, true);
test('Does not print stdout, verbose "none", fd-specific, sync', testNoPrintOutput, stdoutNoneOption, 1, true);
test('Does not print stdout, verbose "short", fd-specific, sync', testNoPrintOutput, stdoutShortOption, 1, true);
test('Does not print stderr, verbose default, fd-specific, sync', testNoPrintOutput, {}, 2, true);
test('Does not print stderr, verbose "none", fd-specific, sync', testNoPrintOutput, stderrNoneOption, 2, true);
test('Does not print stderr, verbose "short", fd-specific, sync', testNoPrintOutput, stderrShortOption, 2, true);
test('Does not print stdio[*], verbose default, fd-specific, sync', testNoPrintOutput, {}, 3, true);
test('Does not print stdio[*], verbose "none", fd-specific, sync', testNoPrintOutput, fd3NoneOption, 3, true);
test('Does not print stdio[*], verbose "short", fd-specific, sync', testNoPrintOutput, fd3ShortOption, 3, true);
test('Does not print stdio[*], verbose "full", fd-specific, sync', testNoPrintOutput, fd3FullOption, 3, true);
const testPrintError = async (t, isSync) => {
const stderr = await runErrorSubprocess(t, 'full', isSync);
t.is(getOutputLine(stderr), `${testTimestamp} [0] ${foobarString}`);
};
test('Prints stdout after errors', testPrintError, false);
test('Prints stdout after errors, sync', testPrintError, true);
test('Does not quote spaces from stdout', async t => {
const {stderr} = await nestedSubprocess('noop.js', ['foo bar'], {verbose: 'full'});
t.is(getOutputLine(stderr), `${testTimestamp} [0] foo bar`);
});
test('Does not quote special punctuation from stdout', async t => {
const {stderr} = await nestedSubprocess('noop.js', ['%'], {verbose: 'full'});
t.is(getOutputLine(stderr), `${testTimestamp} [0] %`);
});
test('Does not escape internal characters from stdout', async t => {
const {stderr} = await nestedSubprocess('noop.js', ['ã'], {verbose: 'full'});
t.is(getOutputLine(stderr), `${testTimestamp} [0] ã`);
});
test('Strips color sequences from stdout', async t => {
const {stderr} = await nestedSubprocess('noop.js', [red(foobarString)], {verbose: 'full'}, {env: {FORCE_COLOR: '1'}});
t.is(getOutputLine(stderr), `${testTimestamp} [0] ${foobarString}`);
});
test('Escapes control characters from stdout', async t => {
const {stderr} = await nestedSubprocess('noop.js', ['\u0001'], {verbose: 'full'});
t.is(getOutputLine(stderr), `${testTimestamp} [0] \\u0001`);
});
const testStdioSame = async (t, fdNumber) => {
const {nestedResult: {stdio}} = await nestedSubprocess('noop-fd.js', [`${fdNumber}`, foobarString], {verbose: 'full'});
t.is(stdio[fdNumber], foobarString);
};
test('Does not change subprocess.stdout', testStdioSame, 1);
test('Does not change subprocess.stderr', testStdioSame, 2);
const testSingleNewline = async (t, isSync) => {
const {stderr} = await nestedSubprocess('noop-fd.js', ['1', '\n'], {verbose: 'full', isSync});
t.deepEqual(getOutputLines(stderr), [`${testTimestamp} [0] `]);
};
test('Prints stdout, single newline', testSingleNewline, false);
test('Prints stdout, single newline, sync', testSingleNewline, true);
const testUtf16 = async (t, isSync) => {
const {stderr} = await nestedSubprocess('stdin.js', {
verbose: 'full',
input: foobarUtf16Uint8Array,
encoding: 'utf16le',
isSync,
});
t.is(getOutputLine(stderr), `${testTimestamp} [0] ${foobarString}`);
};
test('Can use encoding UTF16, verbose "full"', testUtf16, false);
test('Can use encoding UTF16, verbose "full", sync', testUtf16, true);
================================================
FILE: test/verbose/output-mixed.js
================================================
import {inspect} from 'node:util';
import test from 'ava';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString, foobarObject} from '../helpers/input.js';
import {simpleFull, noNewlinesChunks} from '../helpers/lines.js';
import {nestedSubprocess} from '../helpers/nested.js';
import {getOutputLine, getOutputLines, testTimestamp} from '../helpers/verbose.js';
setFixtureDirectory();
const testLines = async (t, lines, stripFinalNewline, isSync) => {
const {stderr} = await nestedSubprocess('noop-fd.js', ['1', simpleFull], {
verbose: 'full',
lines,
stripFinalNewline,
isSync,
});
t.deepEqual(getOutputLines(stderr), noNewlinesChunks.map(line => `${testTimestamp} [0] ${line}`));
};
test('Prints stdout, "lines: true"', testLines, true, false, false);
test('Prints stdout, "lines: true", fd-specific', testLines, {stdout: true}, false, false);
test('Prints stdout, "lines: true", stripFinalNewline', testLines, true, true, false);
test('Prints stdout, "lines: true", sync', testLines, true, false, true);
test('Prints stdout, "lines: true", fd-specific, sync', testLines, {stdout: true}, false, true);
test('Prints stdout, "lines: true", stripFinalNewline, sync', testLines, true, true, true);
const testOnlyTransforms = async (t, isSync) => {
const {stderr} = await nestedSubprocess('noop.js', [foobarString], {
optionsFixture: 'generator-uppercase.js',
verbose: 'full',
isSync,
});
t.is(getOutputLine(stderr), `${testTimestamp} [0] ${foobarString.toUpperCase()}`);
};
test('Prints stdout with only transforms', testOnlyTransforms, false);
test('Prints stdout with only transforms, sync', testOnlyTransforms, true);
test('Prints stdout with only duplexes', async t => {
const {stderr} = await nestedSubprocess('noop.js', [foobarString], {
optionsFixture: 'generator-duplex.js',
verbose: 'full',
});
t.is(getOutputLine(stderr), `${testTimestamp} [0] ${foobarString.toUpperCase()}`);
});
const testObjectMode = async (t, isSync) => {
const {stderr} = await nestedSubprocess('noop.js', {
optionsFixture: 'generator-object.js',
verbose: 'full',
isSync,
});
t.is(getOutputLine(stderr), `${testTimestamp} [0] ${inspect(foobarObject)}`);
};
test('Prints stdout with object transforms', testObjectMode, false);
test('Prints stdout with object transforms, sync', testObjectMode, true);
const testBigArray = async (t, isSync) => {
const {stderr} = await nestedSubprocess('noop.js', {
optionsFixture: 'generator-big-array.js',
verbose: 'full',
isSync,
});
const lines = getOutputLines(stderr);
t.is(lines[0], `${testTimestamp} [0] [`);
t.true(lines[1].startsWith(`${testTimestamp} [0] 0, 1,`));
t.is(lines.at(-1), `${testTimestamp} [0] ]`);
};
test('Prints stdout with big object transforms', testBigArray, false);
test('Prints stdout with big object transforms, sync', testBigArray, true);
const testObjectModeString = async (t, isSync) => {
const {stderr} = await nestedSubprocess('noop.js', {
optionsFixture: 'generator-string-object.js',
verbose: 'full',
isSync,
});
t.deepEqual(getOutputLines(stderr), noNewlinesChunks.map(line => `${testTimestamp} [0] ${line}`));
};
test('Prints stdout with string transforms in objectMode', testObjectModeString, false);
test('Prints stdout with string transforms in objectMode, sync', testObjectModeString, true);
================================================
FILE: test/verbose/output-noop.js
================================================
import {rm, readFile} from 'node:fs/promises';
import test from 'ava';
import tempfile from 'tempfile';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
import {nestedSubprocess} from '../helpers/nested.js';
import {getOutputLine, testTimestamp} from '../helpers/verbose.js';
setFixtureDirectory();
const testNoOutputOptions = async (t, isSync, options) => {
const {stderr} = await nestedSubprocess('noop.js', [foobarString], {verbose: 'full', isSync, ...options});
t.is(getOutputLine(stderr), undefined);
};
test('Does not print stdout, encoding "buffer"', testNoOutputOptions, false, {encoding: 'buffer'});
test('Does not print stdout, encoding "hex"', testNoOutputOptions, false, {encoding: 'hex'});
test('Does not print stdout, encoding "base64"', testNoOutputOptions, false, {encoding: 'base64'});
test('Does not print stdout, stdout "ignore"', testNoOutputOptions, false, {stdout: 'ignore'});
test('Does not print stdout, stdout "inherit"', testNoOutputOptions, false, {stdout: 'inherit'});
test('Does not print stdout, stdout 1', testNoOutputOptions, false, {stdout: 1});
test('Does not print stdout, encoding "buffer", sync', testNoOutputOptions, true, {encoding: 'buffer'});
test('Does not print stdout, encoding "hex", sync', testNoOutputOptions, true, {encoding: 'hex'});
test('Does not print stdout, encoding "base64", sync', testNoOutputOptions, true, {encoding: 'base64'});
test('Does not print stdout, stdout "ignore", sync', testNoOutputOptions, true, {stdout: 'ignore'});
test('Does not print stdout, stdout "inherit", sync', testNoOutputOptions, true, {stdout: 'inherit'});
test('Does not print stdout, stdout 1, sync', testNoOutputOptions, true, {stdout: 1});
const testNoOutputDynamic = async (t, isSync, optionsFixture) => {
const {stderr} = await nestedSubprocess('noop.js', [foobarString], {verbose: 'full', isSync, optionsFixture});
t.is(getOutputLine(stderr), undefined);
};
test('Does not print stdout, stdout Writable', testNoOutputDynamic, false, 'writable.js');
test('Does not print stdout, stdout WritableStream', testNoOutputDynamic, false, 'writable-web.js');
test('Does not print stdout, stdout Writable, sync', testNoOutputDynamic, true, 'writable.js');
test('Does not print stdout, stdout WritableStream, sync', testNoOutputDynamic, true, 'writable-web.js');
const testNoOutputStream = async (t, parentFixture) => {
const {stderr} = await nestedSubprocess('noop.js', [foobarString], {verbose: 'full', parentFixture});
t.is(getOutputLine(stderr), undefined);
};
test('Does not print stdout, .pipe(stream)', testNoOutputStream, 'nested-pipe-stream.js');
test('Does not print stdout, .pipe(subprocess)', testNoOutputStream, 'nested-pipe-subprocess.js');
const testStdoutFile = async (t, isSync, optionsFixture) => {
const file = tempfile();
const {stderr} = await nestedSubprocess('noop.js', [foobarString], {
verbose: 'full',
stdout: {file},
isSync,
optionsFixture,
});
t.is(getOutputLine(stderr), undefined);
const contents = await readFile(file, 'utf8');
t.is(contents.trim(), foobarString);
await rm(file);
};
test('Does not print stdout, stdout { file }', testStdoutFile, false);
test('Does not print stdout, stdout fileUrl', testStdoutFile, false, 'file-url.js');
test('Does not print stdout, stdout { file }, sync', testStdoutFile, true);
test('Does not print stdout, stdout fileUrl, sync', testStdoutFile, true, 'file-url.js');
const testPrintOutputOptions = async (t, options, isSync) => {
const {stderr} = await nestedSubprocess('noop.js', [foobarString], {verbose: 'full', isSync, ...options});
t.is(getOutputLine(stderr), `${testTimestamp} [0] ${foobarString}`);
};
test('Prints stdout, stdout "pipe"', testPrintOutputOptions, {stdout: 'pipe'}, false);
test('Prints stdout, stdout "overlapped"', testPrintOutputOptions, {stdout: 'overlapped'}, false);
test('Prints stdout, stdout null', testPrintOutputOptions, {stdout: null}, false);
test('Prints stdout, stdout ["pipe"]', testPrintOutputOptions, {stdout: ['pipe']}, false);
test('Prints stdout, stdout "pipe", sync', testPrintOutputOptions, {stdout: 'pipe'}, true);
test('Prints stdout, stdout null, sync', testPrintOutputOptions, {stdout: null}, true);
test('Prints stdout, stdout ["pipe"], sync', testPrintOutputOptions, {stdout: ['pipe']}, true);
================================================
FILE: test/verbose/output-pipe.js
================================================
import test from 'ava';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
import {nestedSubprocess} from '../helpers/nested.js';
import {
getOutputLine,
getOutputLines,
testTimestamp,
getVerboseOption,
} from '../helpers/verbose.js';
setFixtureDirectory();
const testPipeOutput = async (t, parentFixture, sourceVerbose, destinationVerbose) => {
const {stderr} = await nestedSubprocess('noop.js', [foobarString], {
parentFixture,
sourceOptions: getVerboseOption(sourceVerbose, 'full'),
destinationFile: 'stdin.js',
destinationOptions: getVerboseOption(destinationVerbose, 'full'),
});
const lines = getOutputLines(stderr);
const id = sourceVerbose && destinationVerbose ? 1 : 0;
t.deepEqual(lines, destinationVerbose
? [`${testTimestamp} [${id}] ${foobarString}`]
: []);
};
test('Prints stdout if both verbose with .pipe("file")', testPipeOutput, 'nested-pipe-file.js', true, true);
test('Prints stdout if both verbose with .pipe`command`', testPipeOutput, 'nested-pipe-script.js', true, true);
test('Prints stdout if both verbose with .pipe(subprocess)', testPipeOutput, 'nested-pipe-subprocesses.js', true, true);
test('Prints stdout if only second verbose with .pipe("file")', testPipeOutput, 'nested-pipe-file.js', false, true);
test('Prints stdout if only second verbose with .pipe`command`', testPipeOutput, 'nested-pipe-script.js', false, true);
test('Prints stdout if only second verbose with .pipe(subprocess)', testPipeOutput, 'nested-pipe-subprocesses.js', false, true);
test('Does not print stdout if only first verbose with .pipe("file")', testPipeOutput, 'nested-pipe-file.js', true, false);
test('Does not print stdout if only first verbose with .pipe`command`', testPipeOutput, 'nested-pipe-script.js', true, false);
test('Does not print stdout if only first verbose with .pipe(subprocess)', testPipeOutput, 'nested-pipe-subprocesses.js', true, false);
test('Does not print stdout if neither verbose with .pipe("file")', testPipeOutput, 'nested-pipe-file.js', false, false);
test('Does not print stdout if neither verbose with .pipe`command`', testPipeOutput, 'nested-pipe-script.js', false, false);
test('Does not print stdout if neither verbose with .pipe(subprocess)', testPipeOutput, 'nested-pipe-subprocesses.js', false, false);
const testPrintOutputFixture = async (t, parentFixture) => {
const {stderr} = await nestedSubprocess('noop.js', [foobarString], {parentFixture, verbose: 'full', unpipe: true});
t.is(getOutputLine(stderr), `${testTimestamp} [0] ${foobarString}`);
};
test('Prints stdout, .pipe(stream) + .unpipe()', testPrintOutputFixture, 'nested-pipe-stream.js');
test('Prints stdout, .pipe(subprocess) + .unpipe()', testPrintOutputFixture, 'nested-pipe-subprocess.js');
================================================
FILE: test/verbose/output-progressive.js
================================================
import {on} from 'node:events';
import test from 'ava';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
import {nestedSubprocess, nestedInstance} from '../helpers/nested.js';
import {getOutputLine, getOutputLines, testTimestamp} from '../helpers/verbose.js';
setFixtureDirectory();
test('Prints stdout one line at a time', async t => {
const subprocess = nestedInstance('noop-progressive.js', [foobarString], {verbose: 'full'});
for await (const chunk of on(subprocess.stderr, 'data')) {
const outputLine = getOutputLine(chunk.toString().trim());
if (outputLine !== undefined) {
t.is(outputLine, `${testTimestamp} [0] ${foobarString}`);
break;
}
}
await subprocess;
});
test.serial('Prints stdout progressively, interleaved', async t => {
const subprocess = nestedInstance('noop-repeat.js', ['1', `${foobarString}\n`], {parentFixture: 'nested-double.js', verbose: 'full'});
let firstSubprocessPrinted = false;
let secondSubprocessPrinted = false;
for await (const chunk of on(subprocess.stderr, 'data')) {
const outputLine = getOutputLine(chunk.toString().trim());
if (outputLine === undefined) {
continue;
}
if (outputLine.includes(foobarString)) {
t.is(outputLine, `${testTimestamp} [0] ${foobarString}`);
firstSubprocessPrinted ||= true;
} else {
t.is(outputLine, `${testTimestamp} [1] ${foobarString.toUpperCase()}`);
secondSubprocessPrinted ||= true;
}
if (firstSubprocessPrinted && secondSubprocessPrinted) {
break;
}
}
subprocess.kill();
await t.throwsAsync(subprocess);
});
const testInterleaved = async (t, expectedLines, isSync) => {
const {stderr} = await nestedSubprocess('noop-132.js', {verbose: 'full', isSync});
t.deepEqual(getOutputLines(stderr), expectedLines.map(line => `${testTimestamp} [0] ${line}`));
};
test('Prints stdout + stderr interleaved', testInterleaved, [1, 2, 3], false);
test('Prints stdout + stderr not interleaved, sync', testInterleaved, [1, 3, 2], true);
================================================
FILE: test/verbose/start.js
================================================
import test from 'ava';
import {red} from 'yoctocolors';
import {setFixtureDirectory} from '../helpers/fixtures-directory.js';
import {foobarString} from '../helpers/input.js';
import {nestedSubprocess} from '../helpers/nested.js';
import {
QUOTE,
runErrorSubprocess,
runEarlyErrorSubprocess,
getCommandLine,
getCommandLines,
testTimestamp,
getVerboseOption,
stdoutNoneOption,
stdoutShortOption,
stdoutFullOption,
stderrNoneOption,
stderrShortOption,
stderrFullOption,
fd3NoneOption,
fd3ShortOption,
fd3FullOption,
ipcNoneOption,
ipcShortOption,
ipcFullOption,
} from '../helpers/verbose.js';
setFixtureDirectory();
const testPrintCommand = async (t, verbose, worker, isSync) => {
const {stderr} = await nestedSubprocess('noop.js', [foobarString], {verbose, worker, isSync});
t.is(getCommandLine(stderr), `${testTimestamp} [0] $ noop.js ${foobarString}`);
};
test('Prints command, verbose "short"', testPrintCommand, 'short', false, false);
test('Prints command, verbose "full"', testPrintCommand, 'full', false, false);
test('Prints command, verbose "short", fd-specific stdout', testPrintCommand, stdoutShortOption, false, false);
test('Prints command, verbose "full", fd-specific stdout', testPrintCommand, stdoutFullOption, false, false);
test('Prints command, verbose "short", fd-specific stderr', testPrintCommand, stderrShortOption, false, false);
test('Prints command, verbose "full", fd-specific stderr', testPrintCommand, stderrFullOption, false, false);
test('Prints command, verbose "short", fd-specific fd3', testPrintCommand, fd3ShortOption, false, false);
test('Prints command, verbose "full", fd-specific fd3', testPrintCommand, fd3FullOption, false, false);
test('Prints command, verbose "short", fd-specific ipc', testPrintCommand, ipcShortOption, false, false);
test('Prints command, verbose "full", fd-specific ipc', testPrintCommand, ipcFullOption, false, false);
test('Prints command, verbose "short", sync', testPrintCommand, 'short', false, true);
test('Prints command, verbose "full", sync', testPrintCommand, 'full', false, true);
test('Prints command, verbose "short", fd-specific stdout, sync', testPrintCommand, stdoutShortOption, false, true);
test('Prints command, verbose "full", fd-specific stdout, sync', testPrintCommand, stdoutFullOption, false, true);
test('Prints command, verbose "short", fd-specific stderr, sync', testPrintCommand, stderrShortOption, false, true);
test('Prints command, verbose "full", fd-specific stderr, sync', testPrintCommand, stderrFullOption, false, true);
test('Prints command, verbose "short", fd-specific fd3, sync', testPrintCommand, fd3ShortOption, false, true);
test('Prints command, verbose "full", fd-specific fd3, sync', testPrintCommand, fd3FullOption, false, true);
test('Prints command, verbose "short", fd-specific ipc, sync', testPrintCommand, ipcShortOption, false, true);
test('Prints command, verbose "full", fd-specific ipc, sync', testPrintCommand, ipcFullOption, false, true);
test('Prints command, verbose "short", worker', testPrintCommand, 'short', true, false);
test('Prints command, verbose "full", worker', testPrintCommand, 'full', true, false);
test('Prints command, verbose "short", fd-specific stdout, worker', testPrintCommand, stdoutShortOption, true, false);
test('Prints command, verbose "full", fd-specific stdout, worker', testPrintCommand, stdoutFullOption, true, false);
test('Prints command, verbose "short", fd-specific stderr, worker', testPrintCommand, stderrShortOption, true, false);
test('Prints command, verbose "full", fd-specific stderr, worker', testPrintCommand, stderrFullOption, true, false);
test('Prints command, verbose "short", fd-specific fd3, worker', testPrintCommand, fd3ShortOption, true, false);
test('Prints command, verbose "full", fd-specific fd3, worker', testPrintCommand, fd3FullOption, true, false);
test('Prints command, verbose "short", fd-specific ipc, worker', testPrintCommand, ipcShortOption, true, false);
test('Prints command, verbose "full", fd-specific ipc, worker', testPrintCommand, ipcFullOption, true, false);
test('Prints command, verbose "short", worker, sync', testPrintCommand, 'short', true, true);
test('Prints command, verbose "full", worker, sync', testPrintCommand, 'full', true, true);
test('Prints command, verbose "short", fd-specific stdout, worker, sync', testPrintCommand, stdoutShortOption, true, true);
test('Prints command, verbose "full", fd-specific stdout, worker, sync', testPrintCommand, stdoutFullOption, true, true);
test('Prints command, verbose "short", fd-specific stderr, worker, sync', testPrintCommand, stderrShortOption, true, true);
test('Prints command, verbose "full", fd-specific stderr, worker, sync', testPrintCommand, stderrFullOption, true, true);
test('Prints command, verbose "short", fd-specific fd3, worker, sync', testPrintCommand, fd3ShortOption, true, true);
test('Prints command, verbose "full", fd-specific fd3, worker, sync', testPrintCommand, fd3FullOption, true, true);
test('Prints command, verbose "short", fd-specific ipc, worker, sync', testPrintCommand, ipcShortOption, true, true);
test('Prints command, verbose "full", fd-specific ipc, worker, sync', testPrintCommand, ipcFullOption, true, true);
const testNoPrintCommand = async (t, verbose, isSync) => {
const {stderr} = await nestedSubprocess('noop.js', [foobarString], {verbose, isSync});
t.is(stderr, '');
};
test('Does not print command, verbose "none"', testNoPrintCommand, 'none', false);
test('Does not print command, verbose default', testNoPrintCommand, undefined, false);
test('Does not print command, verbose "none", fd-specific stdout', testNoPrintCommand, stdoutNoneOption, false);
test('Does not print command, verbose "none", fd-specific stderr', testNoPrintCommand, stderrNoneOption, false);
test('Does not print command, verbose "none", fd-specific fd3', testNoPrintCommand, fd3NoneOption, false);
test('Does not print command, verbose "none", fd-specific ipc', testNoPrintCommand, ipcNoneOption, false);
test('Does not print command, verbose default, fd-specific', testNoPrintCommand, {}, false);
test('Does not print command, verbose "none", sync', testNoPrintCommand, 'none', true);
test('Does not print command, verbose default, sync', testNoPrintCommand, undefined, true);
test('Does not print command, verbose "none", fd-specific stdout, sync', testNoPrintCommand, stdoutNoneOption, true);
test('Does not print command, verbose "none", fd-specific stderr, sync', testNoPrintCommand, stderrNoneOption, true);
test('Does not print command, verbose "none", fd-specific fd3, sync', testNoPrintCommand, fd3NoneOption, true);
test('Does not print command, verbose "none", fd-specific ipc, sync', testNoPrintCommand, ipcNoneOption, true);
test('Does not print command, verbose default, fd-specific, sync', testNoPrintCommand, {}, true);
const testPrintCommandError = async (t, isSync) => {
const stderr = await runErrorSubprocess(t, 'short', isSync);
t.is(getCommandLine(stderr), `${testTimestamp} [0] $ noop-fail.js 1 ${foobarString}`);
};
test('Prints command after errors', testPrintCommandError, false);
test('Prints command after errors, sync', testPrintCommandError, true);
const testPrintCommandEarly = async (t, isSync) => {
const stderr = await runEarlyErrorSubprocess(t, isSync);
t.is(getCommandLine(stderr), `${testTimestamp} [0] $ noop.js ${foobarString}`);
};
test('Prints command before early validation errors', testPrintCommandEarly, false);
test('Prints command before early validation errors, sync', testPrintCommandEarly, true);
const testPipeCommand = async (t, parentFixture, sourceVerbose, destinationVerbose) => {
const {stderr} = await nestedSubprocess('noop.js', [foobarString], {
parentFixture,
sourceOptions: getVerboseOption(sourceVerbose),
destinationFile: 'stdin.js',
destinationOptions: getVerboseOption(destinationVerbose),
});
const pipeSymbol = parentFixture === 'nested-pipe-subprocesses.js' ? '$' : '|';
const lines = getCommandLines(stderr);
t.is(lines.includes(`${testTimestamp} [0] $ noop.js ${foobarString}`), sourceVerbose);
t.is(lines.includes(`${testTimestamp} [${sourceVerbose ? 1 : 0}] ${pipeSymbol} stdin.js`), destinationVerbose);
};
test('Prints both commands piped with .pipe("file")', testPipeCommand, 'nested-pipe-file.js', true, true);
test('Prints both commands piped with .pipe`command`', testPipeCommand, 'nested-pipe-script.js', true, true);
test('Prints both commands piped with .pipe(subprocess)', testPipeCommand, 'nested-pipe-subprocesses.js', true, true);
test('Prints first command piped with .pipe("file")', testPipeCommand, 'nested-pipe-file.js', true, false);
test('Prints first command piped with .pipe`command`', testPipeCommand, 'nested-pipe-script.js', true, false);
test('Prints first command piped with .pipe(subprocess)', testPipeCommand, 'nested-pipe-subprocesses.js', true, false);
test('Prints second command piped with .pipe("file")', testPipeCommand, 'nested-pipe-file.js', false, true);
test('Prints second command piped with .pipe`command`', testPipeCommand, 'nested-pipe-script.js', false, true);
test('Prints second command piped with .pipe(subprocess)', testPipeCommand, 'nested-pipe-subprocesses.js', false, true);
test('Prints neither commands piped with .pipe("file")', testPipeCommand, 'nested-pipe-file.js', false, false);
test('Prints neither commands piped with .pipe`command`', testPipeCommand, 'nested-pipe-script.js', false, false);
test('Prints neither commands piped with .pipe(subprocess)', testPipeCommand, 'nested-pipe-subprocesses.js', false, false);
test('Quotes spaces from command', async t => {
const {stderr} = await nestedSubprocess('noop.js', ['foo bar'], {verbose: 'short'});
t.is(getCommandLine(stderr), `${testTimestamp} [0] $ noop.js ${QUOTE}foo bar${QUOTE}`);
});
test('Quotes special punctuation from command', async t => {
const {stderr} = await nestedSubprocess('noop.js', ['%'], {verbose: 'short'});
t.is(getCommandLine(stderr), `${testTimestamp} [0] $ noop.js ${QUOTE}%${QUOTE}`);
});
test('Does not escape internal characters from command', async t => {
const {stderr} = await nestedSubprocess('noop.js', ['ã'], {verbose: 'short'});
t.is(getCommandLine(stderr), `${testTimestamp} [0] $ noop.js ${QUOTE}ã${QUOTE}`);
});
test('Escapes color sequences from command', async t => {
const {stderr} = await nestedSubprocess('noop.js', [red(foobarString)], {verbose: 'short'}, {env: {FORCE_COLOR: '1'}});
t.true(getCommandLine(stderr).includes(`${QUOTE}\\u001b[31m${foobarString}\\u001b[39m${QUOTE}`));
});
test('Escapes control characters from command', async t => {
const {stderr} = await nestedSubprocess('noop.js', ['\u0001'], {verbose: 'short'});
t.is(getCommandLine(stderr), `${testTimestamp} [0] $ noop.js ${QUOTE}\\u0001${QUOTE}`);
});
================================================
FILE: test-d/arguments/encoding-option.test-d.ts
================================================
import {expectError} from 'tsd';
import {execa, execaSync} from '../../index.js';
await execa('unicorns', {encoding: 'utf8'});
execaSync('unicorns', {encoding: 'utf8'});
/* eslint-disable unicorn/text-encoding-identifier-case */
expectError(await execa('unicorns', {encoding: 'utf-8'}));
expectError(execaSync('unicorns', {encoding: 'utf-8'}));
expectError(await execa('unicorns', {encoding: 'UTF8'}));
expectError(execaSync('unicorns', {encoding: 'UTF8'}));
/* eslint-enable unicorn/text-encoding-identifier-case */
await execa('unicorns', {encoding: 'utf16le'});
execaSync('unicorns', {encoding: 'utf16le'});
expectError(await execa('unicorns', {encoding: 'utf-16le'}));
expectError(execaSync('unicorns', {encoding: 'utf-16le'}));
expectError(await execa('unicorns', {encoding: 'ucs2'}));
expectError(execaSync('unicorns', {encoding: 'ucs2'}));
expectError(await execa('unicorns', {encoding: 'ucs-2'}));
expectError(execaSync('unicorns', {encoding: 'ucs-2'}));
await execa('unicorns', {encoding: 'buffer'});
execaSync('unicorns', {encoding: 'buffer'});
expectError(await execa('unicorns', {encoding: null}));
expectError(execaSync('unicorns', {encoding: null}));
await execa('unicorns', {encoding: 'hex'});
execaSync('unicorns', {encoding: 'hex'});
await execa('unicorns', {encoding: 'base64'});
execaSync('unicorns', {encoding: 'base64'});
await execa('unicorns', {encoding: 'base64url'});
execaSync('unicorns', {encoding: 'base64url'});
await execa('unicorns', {encoding: 'latin1'});
execaSync('unicorns', {encoding: 'latin1'});
expectError(await execa('unicorns', {encoding: 'binary'}));
expectError(execaSync('unicorns', {encoding: 'binary'}));
await execa('unicorns', {encoding: 'ascii'});
execaSync('unicorns', {encoding: 'ascii'});
expectError(await execa('unicorns', {encoding: 'utf8' as string}));
expectError(execaSync('unicorns', {encoding: 'utf8' as string}));
expectError(await execa('unicorns', {encoding: 'unknownEncoding'}));
expectError(execaSync('unicorns', {encoding: 'unknownEncoding'}));
================================================
FILE: test-d/arguments/env.test-d.ts
================================================
import process, {type env} from 'node:process';
import {expectType, expectAssignable} from 'tsd';
import {execa, type Options, type Result} from '../../index.js';
type NodeEnv = 'production' | 'development' | 'test';
// Libraries like Next.js or Remix do the following type augmentation.
// The following type tests ensure this works with Execa.
// See https://github.com/sindresorhus/execa/pull/1141 and https://github.com/sindresorhus/execa/issues/1132
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace NodeJS {
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface ProcessEnv {
readonly NODE_ENV: NodeEnv;
}
}
}
// The global types are impacted
expectType(process.env.NODE_ENV);
expectType('' as (typeof env)['NODE_ENV']);
expectType('' as NodeJS.ProcessEnv['NODE_ENV']);
expectType('' as globalThis.NodeJS.ProcessEnv['NODE_ENV']);
// But Execa's types are not impacted
expectType('' as Exclude['NODE_ENV']);
expectAssignable(await execa({env: {test: 'example'}})`unicorns`);
expectAssignable(await execa({env: {test: 'example'} as const})`unicorns`);
expectAssignable(await execa({env: {test: undefined}})`unicorns`);
expectAssignable(await execa({env: {test: undefined} as const})`unicorns`);
================================================
FILE: test-d/arguments/options.test-d.ts
================================================
import * as process from 'node:process';
import {expectError, expectAssignable, expectNotAssignable} from 'tsd';
import {
execa,
execaSync,
type Options,
type SyncOptions,
} from '../../index.js';
const fileUrl = new URL('file:///test');
expectAssignable({preferLocal: false});
expectAssignable({cleanup: false});
expectNotAssignable({other: false});
expectAssignable({preferLocal: false});
expectNotAssignable({cleanup: false});
expectNotAssignable({other: false});
await execa('unicorns', {preferLocal: false});
execaSync('unicorns', {preferLocal: false});
await execa('unicorns', {preferLocal: false as boolean});
execaSync('unicorns', {preferLocal: false as boolean});
expectError(await execa('unicorns', {preferLocal: 'false'}));
expectError(execaSync('unicorns', {preferLocal: 'false'}));
await execa('unicorns', {localDir: '.'});
execaSync('unicorns', {localDir: '.'});
await execa('unicorns', {localDir: '.' as string});
execaSync('unicorns', {localDir: '.' as string});
await execa('unicorns', {localDir: fileUrl});
execaSync('unicorns', {localDir: fileUrl});
expectError(await execa('unicorns', {localDir: false}));
expectError(execaSync('unicorns', {localDir: false}));
await execa('unicorns', {node: true});
execaSync('unicorns', {node: true});
await execa('unicorns', {node: true as boolean});
execaSync('unicorns', {node: true as boolean});
expectError(await execa('unicorns', {node: 'true'}));
expectError(execaSync('unicorns', {node: 'true'}));
await execa('unicorns', {nodePath: './node'});
execaSync('unicorns', {nodePath: './node'});
await execa('unicorns', {nodePath: './node' as string});
execaSync('unicorns', {nodePath: './node' as string});
await execa('unicorns', {nodePath: fileUrl});
execaSync('unicorns', {nodePath: fileUrl});
expectError(await execa('unicorns', {nodePath: false}));
expectError(execaSync('unicorns', {nodePath: false}));
await execa('unicorns', {nodeOptions: ['--async-stack-traces'] as const});
execaSync('unicorns', {nodeOptions: ['--async-stack-traces'] as const});
await execa('unicorns', {nodeOptions: ['--async-stack-traces'] as string[]});
execaSync('unicorns', {nodeOptions: ['--async-stack-traces'] as string[]});
expectError(await execa('unicorns', {nodeOptions: [false] as const}));
expectError(execaSync('unicorns', {nodeOptions: [false] as const}));
await execa('unicorns', {input: ''});
execaSync('unicorns', {input: ''});
await execa('unicorns', {input: '' as string});
execaSync('unicorns', {input: '' as string});
await execa('unicorns', {input: new Uint8Array()});
execaSync('unicorns', {input: new Uint8Array()});
await execa('unicorns', {input: process.stdin});
execaSync('unicorns', {input: process.stdin});
expectError(await execa('unicorns', {input: false}));
expectError(execaSync('unicorns', {input: false}));
await execa('unicorns', {inputFile: ''});
execaSync('unicorns', {inputFile: ''});
await execa('unicorns', {inputFile: '' as string});
execaSync('unicorns', {inputFile: '' as string});
await execa('unicorns', {inputFile: fileUrl});
execaSync('unicorns', {inputFile: fileUrl});
expectError(await execa('unicorns', {inputFile: false}));
expectError(execaSync('unicorns', {inputFile: false}));
await execa('unicorns', {lines: false});
execaSync('unicorns', {lines: false});
await execa('unicorns', {lines: false as boolean});
execaSync('unicorns', {lines: false as boolean});
expectError(await execa('unicorns', {lines: 'false'}));
expectError(execaSync('unicorns', {lines: 'false'}));
await execa('unicorns', {reject: false});
execaSync('unicorns', {reject: false});
await execa('unicorns', {reject: false as boolean});
execaSync('unicorns', {reject: false as boolean});
expectError(await execa('unicorns', {reject: 'false'}));
expectError(execaSync('unicorns', {reject: 'false'}));
await execa('unicorns', {stripFinalNewline: false});
execaSync('unicorns', {stripFinalNewline: false});
await execa('unicorns', {stripFinalNewline: false as boolean});
execaSync('unicorns', {stripFinalNewline: false as boolean});
expectError(await execa('unicorns', {stripFinalNewline: 'false'}));
expectError(execaSync('unicorns', {stripFinalNewline: 'false'}));
await execa('unicorns', {extendEnv: false});
execaSync('unicorns', {extendEnv: false});
await execa('unicorns', {extendEnv: false as boolean});
execaSync('unicorns', {extendEnv: false as boolean});
expectError(await execa('unicorns', {extendEnv: 'false'}));
expectError(execaSync('unicorns', {extendEnv: 'false'}));
await execa('unicorns', {cwd: '.'});
execaSync('unicorns', {cwd: '.'});
await execa('unicorns', {cwd: '.' as string});
execaSync('unicorns', {cwd: '.' as string});
await execa('unicorns', {cwd: fileUrl});
execaSync('unicorns', {cwd: fileUrl});
expectError(await execa('unicorns', {cwd: false}));
expectError(execaSync('unicorns', {cwd: false}));
/* eslint-disable @typescript-eslint/naming-convention */
await execa('unicorns', {env: {PATH: ''}});
execaSync('unicorns', {env: {PATH: ''}});
const env: Record = {PATH: ''};
await execa('unicorns', {env});
execaSync('unicorns', {env});
/* eslint-enable @typescript-eslint/naming-convention */
expectError(await execa('unicorns', {env: false}));
expectError(execaSync('unicorns', {env: false}));
await execa('unicorns', {argv0: ''});
execaSync('unicorns', {argv0: ''});
await execa('unicorns', {argv0: '' as string});
execaSync('unicorns', {argv0: '' as string});
expectError(await execa('unicorns', {argv0: false}));
expectError(execaSync('unicorns', {argv0: false}));
await execa('unicorns', {uid: 0});
execaSync('unicorns', {uid: 0});
await execa('unicorns', {uid: 0 as number});
execaSync('unicorns', {uid: 0 as number});
expectError(await execa('unicorns', {uid: '0'}));
expectError(execaSync('unicorns', {uid: '0'}));
await execa('unicorns', {gid: 0});
execaSync('unicorns', {gid: 0});
await execa('unicorns', {gid: 0 as number});
execaSync('unicorns', {gid: 0 as number});
expectError(await execa('unicorns', {gid: '0'}));
expectError(execaSync('unicorns', {gid: '0'}));
await execa('unicorns', {shell: true});
execaSync('unicorns', {shell: true});
await execa('unicorns', {shell: true as boolean});
execaSync('unicorns', {shell: true as boolean});
await execa('unicorns', {shell: '/bin/sh'});
execaSync('unicorns', {shell: '/bin/sh'});
await execa('unicorns', {shell: '/bin/sh' as string});
execaSync('unicorns', {shell: '/bin/sh' as string});
await execa('unicorns', {shell: fileUrl});
execaSync('unicorns', {shell: fileUrl});
expectError(await execa('unicorns', {shell: {}}));
expectError(execaSync('unicorns', {shell: {}}));
await execa('unicorns', {timeout: 1000});
execaSync('unicorns', {timeout: 1000});
await execa('unicorns', {timeout: 1000 as number});
execaSync('unicorns', {timeout: 1000 as number});
expectError(await execa('unicorns', {timeout: '1000'}));
expectError(execaSync('unicorns', {timeout: '1000'}));
await execa('unicorns', {maxBuffer: 1000});
execaSync('unicorns', {maxBuffer: 1000});
await execa('unicorns', {maxBuffer: 1000 as number});
execaSync('unicorns', {maxBuffer: 1000 as number});
expectError(await execa('unicorns', {maxBuffer: '1000'}));
expectError(execaSync('unicorns', {maxBuffer: '1000'}));
await execa('unicorns', {killSignal: 'SIGTERM'});
execaSync('unicorns', {killSignal: 'SIGTERM'});
expectError(await execa('unicorns', {killSignal: 'SIGTERM' as string}));
expectError(execaSync('unicorns', {killSignal: 'SIGTERM' as string}));
await execa('unicorns', {killSignal: 9});
execaSync('unicorns', {killSignal: 9});
await execa('unicorns', {killSignal: 9 as number});
execaSync('unicorns', {killSignal: 9 as number});
expectError(await execa('unicorns', {killSignal: false}));
expectError(execaSync('unicorns', {killSignal: false}));
expectError(await execa('unicorns', {killSignal: 'Sigterm'}));
expectError(execaSync('unicorns', {killSignal: 'Sigterm'}));
expectError(await execa('unicorns', {killSignal: 'sigterm'}));
expectError(execaSync('unicorns', {killSignal: 'sigterm'}));
expectError(await execa('unicorns', {killSignal: 'SIGOTHER'}));
expectError(execaSync('unicorns', {killSignal: 'SIGOTHER'}));
expectError(await execa('unicorns', {killSignal: 'SIGEMT'}));
expectError(execaSync('unicorns', {killSignal: 'SIGEMT'}));
expectError(await execa('unicorns', {killSignal: 'SIGCLD'}));
expectError(execaSync('unicorns', {killSignal: 'SIGCLD'}));
expectError(await execa('unicorns', {killSignal: 'SIGRT1'}));
expectError(execaSync('unicorns', {killSignal: 'SIGRT1'}));
await execa('unicorns', {forceKillAfterDelay: false});
expectError(execaSync('unicorns', {forceKillAfterDelay: false}));
await execa('unicorns', {forceKillAfterDelay: true});
expectError(execaSync('unicorns', {forceKillAfterDelay: true}));
await execa('unicorns', {forceKillAfterDelay: false as boolean});
expectError(execaSync('unicorns', {forceKillAfterDelay: false as boolean}));
await execa('unicorns', {forceKillAfterDelay: 42});
expectError(execaSync('unicorns', {forceKillAfterDelay: 42}));
await execa('unicorns', {forceKillAfterDelay: 42 as number});
expectError(execaSync('unicorns', {forceKillAfterDelay: 42 as number}));
expectError(await execa('unicorns', {forceKillAfterDelay: 'true'}));
expectError(execaSync('unicorns', {forceKillAfterDelay: 'true'}));
await execa('unicorns', {windowsVerbatimArguments: true});
execaSync('unicorns', {windowsVerbatimArguments: true});
await execa('unicorns', {windowsVerbatimArguments: true as boolean});
execaSync('unicorns', {windowsVerbatimArguments: true as boolean});
expectError(await execa('unicorns', {windowsVerbatimArguments: 'true'}));
expectError(execaSync('unicorns', {windowsVerbatimArguments: 'true'}));
await execa('unicorns', {windowsHide: false});
execaSync('unicorns', {windowsHide: false});
await execa('unicorns', {windowsHide: false as boolean});
execaSync('unicorns', {windowsHide: false as boolean});
expectError(await execa('unicorns', {windowsHide: 'false'}));
expectError(execaSync('unicorns', {windowsHide: 'false'}));
await execa('unicorns', {cleanup: false});
expectError(execaSync('unicorns', {cleanup: false}));
await execa('unicorns', {cleanup: false as boolean});
expectError(execaSync('unicorns', {cleanup: false as boolean}));
expectError(await execa('unicorns', {cleanup: 'false'}));
expectError(execaSync('unicorns', {cleanup: 'false'}));
await execa('unicorns', {buffer: false});
execaSync('unicorns', {buffer: false});
await execa('unicorns', {buffer: false as boolean});
execaSync('unicorns', {buffer: false as boolean});
expectError(await execa('unicorns', {buffer: 'false'}));
expectError(execaSync('unicorns', {buffer: 'false'}));
await execa('unicorns', {all: true});
execaSync('unicorns', {all: true});
await execa('unicorns', {all: true as boolean});
execaSync('unicorns', {all: true as boolean});
expectError(await execa('unicorns', {all: 'true'}));
expectError(execaSync('unicorns', {all: 'true'}));
await execa('unicorns', {ipc: true});
expectError(execaSync('unicorns', {ipc: true}));
await execa('unicorns', {ipc: true as boolean});
expectError(execaSync('unicorns', {ipc: true as boolean}));
expectError(await execa('unicorns', {ipc: 'true'}));
expectError(execaSync('unicorns', {ipc: 'true'}));
await execa('unicorns', {serialization: 'json'});
expectError(execaSync('unicorns', {serialization: 'json'}));
await execa('unicorns', {serialization: 'advanced'});
expectError(execaSync('unicorns', {serialization: 'advanced'}));
expectError(await execa('unicorns', {serialization: 'advanced' as string}));
expectError(execaSync('unicorns', {serialization: 'advanced' as string}));
expectError(await execa('unicorns', {serialization: 'other'}));
expectError(execaSync('unicorns', {serialization: 'other'}));
await execa('unicorns', {ipcInput: ''});
expectError(execaSync('unicorns', {ipcInput: ''}));
await execa('unicorns', {ipcInput: '' as string});
expectError(execaSync('unicorns', {ipcInput: '' as string}));
await execa('unicorns', {ipcInput: {}});
expectError(execaSync('unicorns', {ipcInput: {}}));
await execa('unicorns', {ipcInput: undefined});
execaSync('unicorns', {ipcInput: undefined});
expectError(await execa('unicorns', {ipcInput: 0n}));
expectError(execaSync('unicorns', {ipcInput: 0n}));
await execa('unicorns', {detached: true});
expectError(execaSync('unicorns', {detached: true}));
await execa('unicorns', {detached: true as boolean});
expectError(execaSync('unicorns', {detached: true as boolean}));
expectError(await execa('unicorns', {detached: 'true'}));
expectError(execaSync('unicorns', {detached: 'true'}));
await execa('unicorns', {cancelSignal: AbortSignal.abort()});
expectError(execaSync('unicorns', {cancelSignal: AbortSignal.abort()}));
expectError(await execa('unicorns', {cancelSignal: false}));
expectError(execaSync('unicorns', {cancelSignal: false}));
await execa('unicorns', {gracefulCancel: true, cancelSignal: AbortSignal.abort()});
expectError(execaSync('unicorns', {gracefulCancel: true, cancelSignal: AbortSignal.abort()}));
await execa('unicorns', {gracefulCancel: true as boolean, cancelSignal: AbortSignal.abort()});
expectError(execaSync('unicorns', {gracefulCancel: true as boolean, cancelSignal: AbortSignal.abort()}));
expectError(await execa('unicorns', {gracefulCancel: 'true', cancelSignal: AbortSignal.abort()}));
expectError(execaSync('unicorns', {gracefulCancel: 'true', cancelSignal: AbortSignal.abort()}));
================================================
FILE: test-d/arguments/specific.test-d.ts
================================================
import {expectType, expectError} from 'tsd';
import {execa, execaSync} from '../../index.js';
await execa('unicorns', {maxBuffer: {}});
expectError(await execa('unicorns', {maxBuffer: []}));
await execa('unicorns', {maxBuffer: {stdout: 0}});
await execa('unicorns', {maxBuffer: {stderr: 0}});
await execa('unicorns', {maxBuffer: {stdout: 0, stderr: 0} as const});
await execa('unicorns', {maxBuffer: {all: 0}});
await execa('unicorns', {maxBuffer: {fd1: 0}});
await execa('unicorns', {maxBuffer: {fd2: 0}});
await execa('unicorns', {maxBuffer: {fd3: 0}});
await execa('unicorns', {maxBuffer: {ipc: 0}});
expectError(await execa('unicorns', {maxBuffer: {stdout: '0'}}));
execaSync('unicorns', {maxBuffer: {}});
expectError(execaSync('unicorns', {maxBuffer: []}));
execaSync('unicorns', {maxBuffer: {stdout: 0}});
execaSync('unicorns', {maxBuffer: {stderr: 0}});
execaSync('unicorns', {maxBuffer: {stdout: 0, stderr: 0} as const});
execaSync('unicorns', {maxBuffer: {all: 0}});
execaSync('unicorns', {maxBuffer: {fd1: 0}});
execaSync('unicorns', {maxBuffer: {fd2: 0}});
execaSync('unicorns', {maxBuffer: {fd3: 0}});
execaSync('unicorns', {maxBuffer: {ipc: 0}});
expectError(execaSync('unicorns', {maxBuffer: {stdout: '0'}}));
await execa('unicorns', {verbose: {}});
expectError(await execa('unicorns', {verbose: []}));
await execa('unicorns', {verbose: {stdout: 'none'}});
await execa('unicorns', {verbose: {stderr: 'none'}});
await execa('unicorns', {verbose: {stdout: 'none', stderr: 'none'} as const});
await execa('unicorns', {verbose: {all: 'none'}});
await execa('unicorns', {verbose: {fd1: 'none'}});
await execa('unicorns', {verbose: {fd2: 'none'}});
await execa('unicorns', {verbose: {fd3: 'none'}});
await execa('unicorns', {verbose: {ipc: 'none'}});
expectError(await execa('unicorns', {verbose: {stdout: 'other'}}));
execaSync('unicorns', {verbose: {}});
expectError(execaSync('unicorns', {verbose: []}));
execaSync('unicorns', {verbose: {stdout: 'none'}});
execaSync('unicorns', {verbose: {stderr: 'none'}});
execaSync('unicorns', {verbose: {stdout: 'none', stderr: 'none'} as const});
execaSync('unicorns', {verbose: {all: 'none'}});
execaSync('unicorns', {verbose: {fd1: 'none'}});
execaSync('unicorns', {verbose: {fd2: 'none'}});
execaSync('unicorns', {verbose: {fd3: 'none'}});
execaSync('unicorns', {verbose: {ipc: 'none'}});
expectError(execaSync('unicorns', {verbose: {stdout: 'other'}}));
await execa('unicorns', {stripFinalNewline: {}});
expectError(await execa('unicorns', {stripFinalNewline: []}));
await execa('unicorns', {stripFinalNewline: {stdout: true}});
await execa('unicorns', {stripFinalNewline: {stderr: true}});
await execa('unicorns', {stripFinalNewline: {stdout: true, stderr: true} as const});
await execa('unicorns', {stripFinalNewline: {all: true}});
await execa('unicorns', {stripFinalNewline: {fd1: true}});
await execa('unicorns', {stripFinalNewline: {fd2: true}});
await execa('unicorns', {stripFinalNewline: {fd3: true}});
await execa('unicorns', {stripFinalNewline: {ipc: true}});
expectError(await execa('unicorns', {stripFinalNewline: {stdout: 'true'}}));
execaSync('unicorns', {stripFinalNewline: {}});
expectError(execaSync('unicorns', {stripFinalNewline: []}));
execaSync('unicorns', {stripFinalNewline: {stdout: true}});
execaSync('unicorns', {stripFinalNewline: {stderr: true}});
execaSync('unicorns', {stripFinalNewline: {stdout: true, stderr: true} as const});
execaSync('unicorns', {stripFinalNewline: {all: true}});
execaSync('unicorns', {stripFinalNewline: {fd1: true}});
execaSync('unicorns', {stripFinalNewline: {fd2: true}});
execaSync('unicorns', {stripFinalNewline: {fd3: true}});
execaSync('unicorns', {stripFinalNewline: {ipc: true}});
expectError(execaSync('unicorns', {stripFinalNewline: {stdout: 'true'}}));
await execa('unicorns', {lines: {}});
expectError(await execa('unicorns', {lines: []}));
await execa('unicorns', {lines: {stdout: true}});
await execa('unicorns', {lines: {stderr: true}});
await execa('unicorns', {lines: {stdout: true, stderr: true} as const});
await execa('unicorns', {lines: {all: true}});
await execa('unicorns', {lines: {fd1: true}});
await execa('unicorns', {lines: {fd2: true}});
await execa('unicorns', {lines: {fd3: true}});
await execa('unicorns', {lines: {ipc: true}});
expectError(await execa('unicorns', {lines: {stdout: 'true'}}));
execaSync('unicorns', {lines: {}});
expectError(execaSync('unicorns', {lines: []}));
execaSync('unicorns', {lines: {stdout: true}});
execaSync('unicorns', {lines: {stderr: true}});
execaSync('unicorns', {lines: {stdout: true, stderr: true} as const});
execaSync('unicorns', {lines: {all: true}});
execaSync('unicorns', {lines: {fd1: true}});
execaSync('unicorns', {lines: {fd2: true}});
execaSync('unicorns', {lines: {fd3: true}});
execaSync('unicorns', {lines: {ipc: true}});
expectError(execaSync('unicorns', {lines: {stdout: 'true'}}));
await execa('unicorns', {buffer: {}});
expectError(await execa('unicorns', {buffer: []}));
await execa('unicorns', {buffer: {stdout: true}});
await execa('unicorns', {buffer: {stderr: true}});
await execa('unicorns', {buffer: {stdout: true, stderr: true} as const});
await execa('unicorns', {buffer: {all: true}});
await execa('unicorns', {buffer: {fd1: true}});
await execa('unicorns', {buffer: {fd2: true}});
await execa('unicorns', {buffer: {fd3: true}});
await execa('unicorns', {buffer: {ipc: true}});
expectError(await execa('unicorns', {buffer: {stdout: 'true'}}));
execaSync('unicorns', {buffer: {}});
expectError(execaSync('unicorns', {buffer: []}));
execaSync('unicorns', {buffer: {stdout: true}});
execaSync('unicorns', {buffer: {stderr: true}});
execaSync('unicorns', {buffer: {stdout: true, stderr: true} as const});
execaSync('unicorns', {buffer: {all: true}});
execaSync('unicorns', {buffer: {fd1: true}});
execaSync('unicorns', {buffer: {fd2: true}});
execaSync('unicorns', {buffer: {fd3: true}});
execaSync('unicorns', {buffer: {ipc: true}});
expectError(execaSync('unicorns', {buffer: {stdout: 'true'}}));
expectError(await execa('unicorns', {preferLocal: {}}));
expectError(await execa('unicorns', {preferLocal: []}));
expectError(await execa('unicorns', {preferLocal: {stdout: 0}}));
expectError(await execa('unicorns', {preferLocal: {stderr: 0}}));
expectError(await execa('unicorns', {preferLocal: {stdout: 0, stderr: 0} as const}));
expectError(await execa('unicorns', {preferLocal: {all: 0}}));
expectError(await execa('unicorns', {preferLocal: {fd1: 0}}));
expectError(await execa('unicorns', {preferLocal: {fd2: 0}}));
expectError(await execa('unicorns', {preferLocal: {fd3: 0}}));
expectError(await execa('unicorns', {preferLocal: {ipc: 0}}));
expectError(await execa('unicorns', {preferLocal: {stdout: '0'}}));
expectError(execaSync('unicorns', {preferLocal: {}}));
expectError(execaSync('unicorns', {preferLocal: []}));
expectError(execaSync('unicorns', {preferLocal: {stdout: 0}}));
expectError(execaSync('unicorns', {preferLocal: {stderr: 0}}));
expectError(execaSync('unicorns', {preferLocal: {stdout: 0, stderr: 0} as const}));
expectError(execaSync('unicorns', {preferLocal: {all: 0}}));
expectError(execaSync('unicorns', {preferLocal: {fd1: 0}}));
expectError(execaSync('unicorns', {preferLocal: {fd2: 0}}));
expectError(execaSync('unicorns', {preferLocal: {fd3: 0}}));
expectError(execaSync('unicorns', {preferLocal: {ipc: 0}}));
expectError(execaSync('unicorns', {preferLocal: {stdout: '0'}}));
expectType(execaSync('unicorns', {lines: {stdout: true, fd1: false}}).stdout);
expectType(execaSync('unicorns', {lines: {stdout: true, all: false}}).stdout);
expectType(execaSync('unicorns', {lines: {fd1: true, all: false}}).stdout);
expectType(execaSync('unicorns', {lines: {stderr: true, fd2: false}}).stderr);
expectType(execaSync('unicorns', {lines: {stderr: true, all: false}}).stderr);
expectType(execaSync('unicorns', {lines: {fd2: true, all: false}}).stderr);
expectType(execaSync('unicorns', {lines: {fd1: false, stdout: true}}).stdout);
expectType(execaSync('unicorns', {lines: {all: false, stdout: true}}).stdout);
expectType(execaSync('unicorns', {lines: {all: false, fd1: true}}).stdout);
expectType(execaSync('unicorns', {lines: {fd2: false, stderr: true}}).stderr);
expectType(execaSync('unicorns', {lines: {all: false, stderr: true}}).stderr);
expectType(execaSync('unicorns', {lines: {all: false, fd2: true}}).stderr);
================================================
FILE: test-d/convert/duplex.test-d.ts
================================================
import type {Duplex} from 'node:stream';
import {expectType, expectError} from 'tsd';
import {execa} from '../../index.js';
const subprocess = execa('unicorns');
expectType(subprocess.duplex());
subprocess.duplex({from: 'stdout'});
subprocess.duplex({from: 'stderr'});
subprocess.duplex({from: 'all'});
subprocess.duplex({from: 'fd3'});
subprocess.duplex({to: 'fd3'});
subprocess.duplex({from: 'stdout', to: 'stdin'});
subprocess.duplex({from: 'stdout', to: 'fd3'});
expectError(subprocess.duplex({from: 'stdout' as string}));
expectError(subprocess.duplex({to: 'fd3' as string}));
expectError(subprocess.duplex({from: 'stdin'}));
expectError(subprocess.duplex({from: 'stderr', to: 'stdout'}));
expectError(subprocess.duplex({from: 'fd'}));
expectError(subprocess.duplex({from: 'fdNotANumber'}));
expectError(subprocess.duplex({to: 'fd'}));
expectError(subprocess.duplex({to: 'fdNotANumber'}));
subprocess.duplex({binary: false});
expectError(subprocess.duplex({binary: 'false'}));
subprocess.duplex({preserveNewlines: false});
expectError(subprocess.duplex({preserveNewlines: 'false'}));
expectError(subprocess.duplex('stdout'));
expectError(subprocess.duplex({other: 'stdout'}));
================================================
FILE: test-d/convert/iterable.test-d.ts
================================================
import {expectType, expectError} from 'tsd';
import {execa} from '../../index.js';
const subprocess = execa('unicorns');
const bufferSubprocess = execa('unicorns', {encoding: 'buffer', all: true});
const hexSubprocess = execa('unicorns', {encoding: 'hex', all: true});
const asyncIteration = async () => {
for await (const line of subprocess) {
expectType(line);
}
for await (const line of subprocess.iterable()) {
expectType(line);
}
for await (const line of subprocess.iterable({binary: false})) {
expectType(line);
}
for await (const line of subprocess.iterable({binary: true})) {
expectType(line);
}
for await (const line of subprocess.iterable({} as {binary: boolean})) {
expectType(line);
}
for await (const line of bufferSubprocess) {
expectType(line);
}
for await (const line of bufferSubprocess.iterable()) {
expectType(line);
}
for await (const line of bufferSubprocess.iterable({binary: false})) {
expectType(line);
}
for await (const line of bufferSubprocess.iterable({binary: true})) {
expectType(line);
}
for await (const line of bufferSubprocess.iterable({} as {binary: boolean})) {
expectType(line);
}
};
await asyncIteration();
expectType>(subprocess.iterable());
expectType>(subprocess.iterable({binary: false}));
expectType>(subprocess.iterable({binary: true}));
expectType>(subprocess.iterable({} as {binary: boolean}));
expectType>(bufferSubprocess.iterable());
expectType>(bufferSubprocess.iterable({binary: false}));
expectType>(bufferSubprocess.iterable({binary: true}));
expectType>(bufferSubprocess.iterable({} as {binary: boolean}));
expectType>(hexSubprocess.iterable());
expectType>(hexSubprocess.iterable({binary: false}));
expectType>(hexSubprocess.iterable({binary: true}));
expectType>(hexSubprocess.iterable({} as {binary: boolean}));
subprocess.iterable({});
subprocess.iterable({from: 'stdout'});
subprocess.iterable({from: 'stderr'});
subprocess.iterable({from: 'all'});
subprocess.iterable({from: 'fd3'});
expectError(subprocess.iterable({from: 'fd3' as string}));
expectError(subprocess.iterable({from: 'stdin'}));
expectError(subprocess.iterable({from: 'fd'}));
expectError(subprocess.iterable({from: 'fdNotANumber'}));
expectError(subprocess.iterable({to: 'stdin'}));
subprocess.iterable({binary: false});
expectError(subprocess.iterable({binary: 'false'}));
subprocess.iterable({preserveNewlines: false});
expectError(subprocess.iterable({preserveNewlines: 'false'}));
expectError(subprocess.iterable('stdout'));
expectError(subprocess.iterable({other: 'stdout'}));
================================================
FILE: test-d/convert/readable.test-d.ts
================================================
import type {Readable} from 'node:stream';
import {expectType, expectError} from 'tsd';
import {execa} from '../../index.js';
const subprocess = execa('unicorns');
expectType(subprocess.readable());
subprocess.readable({from: 'stdout'});
subprocess.readable({from: 'stderr'});
subprocess.readable({from: 'all'});
subprocess.readable({from: 'fd3'});
expectError(subprocess.readable({from: 'fd3' as string}));
expectError(subprocess.readable({from: 'stdin'}));
expectError(subprocess.readable({from: 'fd'}));
expectError(subprocess.readable({from: 'fdNotANumber'}));
expectError(subprocess.readable({to: 'stdin'}));
subprocess.readable({binary: false});
expectError(subprocess.readable({binary: 'false'}));
subprocess.readable({preserveNewlines: false});
expectError(subprocess.readable({preserveNewlines: 'false'}));
expectError(subprocess.readable('stdout'));
expectError(subprocess.readable({other: 'stdout'}));
================================================
FILE: test-d/convert/writable.test-d.ts
================================================
import type {Writable} from 'node:stream';
import {expectType, expectError} from 'tsd';
import {execa} from '../../index.js';
const subprocess = execa('unicorns');
expectType(subprocess.writable());
subprocess.writable({to: 'stdin'});
subprocess.writable({to: 'fd3'});
expectError(subprocess.writable({to: 'fd3' as string}));
expectError(subprocess.writable({to: 'stdout'}));
expectError(subprocess.writable({to: 'fd'}));
expectError(subprocess.writable({to: 'fdNotANumber'}));
expectError(subprocess.writable({from: 'stdout'}));
expectError(subprocess.writable({binary: false}));
expectError(subprocess.writable({preserveNewlines: false}));
expectError(subprocess.writable('stdin'));
expectError(subprocess.writable({other: 'stdin'}));
================================================
FILE: test-d/ipc/get-each.test-d.ts
================================================
import {expectType, expectError} from 'tsd';
import {
getEachMessage,
execa,
type Message,
type Options,
} from '../../index.js';
const subprocess = execa('test', {ipc: true});
for await (const message of subprocess.getEachMessage()) {
expectType>(message);
}
for await (const message of execa('test', {ipc: true, serialization: 'json'}).getEachMessage()) {
expectType>(message);
}
for await (const message of getEachMessage()) {
expectType(message);
}
expectError(subprocess.getEachMessage(''));
expectError(getEachMessage(''));
execa('test', {ipcInput: ''}).getEachMessage();
execa('test', {ipcInput: '' as Message}).getEachMessage();
execa('test', {gracefulCancel: true, cancelSignal: AbortSignal.abort()}).getEachMessage();
execa('test', {} as Options).getEachMessage?.();
execa('test', {ipc: true as boolean}).getEachMessage?.();
execa('test', {ipcInput: '' as '' | undefined}).getEachMessage?.();
execa('test', {gracefulCancel: true as boolean | undefined, cancelSignal: AbortSignal.abort()}).getEachMessage?.();
expectType(execa('test').getEachMessage);
expectType(execa('test', {}).getEachMessage);
expectType(execa('test', {ipc: false}).getEachMessage);
expectType(execa('test', {ipcInput: undefined}).getEachMessage);
expectType(execa('test', {gracefulCancel: undefined}).getEachMessage);
expectType(execa('test', {gracefulCancel: false}).getEachMessage);
expectType(execa('test', {ipc: false, ipcInput: ''}).getEachMessage);
expectType(execa('test', {ipc: false, gracefulCancel: true, cancelSignal: AbortSignal.abort()}).getEachMessage);
subprocess.getEachMessage({reference: true} as const);
getEachMessage({reference: true} as const);
subprocess.getEachMessage({reference: true as boolean});
getEachMessage({reference: true as boolean});
expectError(subprocess.getEachMessage({reference: 'true'} as const));
expectError(getEachMessage({reference: 'true'} as const));
================================================
FILE: test-d/ipc/get-one.test-d.ts
================================================
import {expectType, expectError} from 'tsd';
import {
getOneMessage,
execa,
type Message,
type Options,
} from '../../index.js';
const subprocess = execa('test', {ipc: true});
expectType>>(subprocess.getOneMessage());
const jsonSubprocess = execa('test', {ipc: true, serialization: 'json'});
expectType>>(jsonSubprocess.getOneMessage());
expectType>(getOneMessage());
expectError(await subprocess.getOneMessage(''));
expectError(await getOneMessage(''));
expectError(await subprocess.getOneMessage({}, ''));
expectError(await getOneMessage({}, ''));
await execa('test', {ipcInput: ''}).getOneMessage();
await execa('test', {ipcInput: '' as Message}).getOneMessage();
await execa('test', {gracefulCancel: true, cancelSignal: AbortSignal.abort()}).getOneMessage();
await execa('test', {} as Options).getOneMessage?.();
await execa('test', {ipc: true as boolean}).getOneMessage?.();
await execa('test', {ipcInput: '' as '' | undefined}).getOneMessage?.();
await execa('test', {gracefulCancel: true as boolean | undefined, cancelSignal: AbortSignal.abort()}).getOneMessage?.();
expectType(execa('test').getOneMessage);
expectType(execa('test', {}).getOneMessage);
expectType(execa('test', {ipc: false}).getOneMessage);
expectType(execa('test', {ipcInput: undefined}).getOneMessage);
expectType(execa('test', {gracefulCancel: undefined}).getOneMessage);
expectType(execa('test', {gracefulCancel: false}).getOneMessage);
expectType(execa('test', {ipc: false, ipcInput: ''}).getOneMessage);
expectType(execa('test', {ipc: false, gracefulCancel: true, cancelSignal: AbortSignal.abort()}).getOneMessage);
await subprocess.getOneMessage({filter: undefined} as const);
await subprocess.getOneMessage({filter: (message: Message<'advanced'>) => true} as const);
await jsonSubprocess.getOneMessage({filter: (message: Message<'json'>) => true} as const);
await jsonSubprocess.getOneMessage({filter: (message: Message<'advanced'>) => true} as const);
await subprocess.getOneMessage({filter: (message: Message<'advanced'> | bigint) => true} as const);
await subprocess.getOneMessage({filter: () => true} as const);
expectError(await subprocess.getOneMessage({filter: (message: Message<'advanced'>) => ''} as const));
// eslint-disable-next-line @typescript-eslint/no-empty-function
expectError(await subprocess.getOneMessage({filter(message: Message<'advanced'>) {}} as const));
expectError(await subprocess.getOneMessage({filter: (message: Message<'json'>) => true} as const));
expectError(await subprocess.getOneMessage({filter: (message: '') => true} as const));
expectError(await subprocess.getOneMessage({filter: true} as const));
await getOneMessage({filter: undefined} as const);
await getOneMessage({filter: (message: Message) => true} as const);
await getOneMessage({filter: (message: Message<'advanced'>) => true} as const);
await getOneMessage({filter: (message: Message | bigint) => true} as const);
await getOneMessage({filter: () => true} as const);
expectError(await getOneMessage({filter: (message: Message) => ''} as const));
// eslint-disable-next-line @typescript-eslint/no-empty-function
expectError(await getOneMessage({filter(message: Message) {}} as const));
expectError(await getOneMessage({filter: (message: Message<'json'>) => true} as const));
expectError(await getOneMessage({filter: (message: '') => true} as const));
expectError(await getOneMessage({filter: true} as const));
expectError(await subprocess.getOneMessage({unknownOption: true} as const));
expectError(await getOneMessage({unknownOption: true} as const));
await subprocess.getOneMessage({reference: true} as const);
await getOneMessage({reference: true} as const);
await subprocess.getOneMessage({reference: true as boolean});
await getOneMessage({reference: true as boolean});
expectError(await subprocess.getOneMessage({reference: 'true'} as const));
expectError(await getOneMessage({reference: 'true'} as const));
================================================
FILE: test-d/ipc/graceful.ts
================================================
import {expectType, expectError} from 'tsd';
import {getCancelSignal, execa} from '../../index.js';
expectType>(getCancelSignal());
expectError(await getCancelSignal(''));
expectError(execa('test').getCancelSignal);
================================================
FILE: test-d/ipc/message.test-d.ts
================================================
import {File} from 'node:buffer';
import {expectAssignable, expectNotAssignable} from 'tsd';
import {sendMessage, type Message} from '../../index.js';
await sendMessage('');
expectAssignable('');
expectAssignable>('');
expectAssignable>('');
await sendMessage(0);
expectAssignable(0);
expectAssignable>(0);
expectAssignable>(0);
await sendMessage(true);
expectAssignable(true);
expectAssignable>(true);
expectAssignable>(true);
await sendMessage([] as const);
expectAssignable([] as const);
expectAssignable>([] as const);
expectAssignable>([] as const);
await sendMessage([true] as const);
expectAssignable([true] as const);
expectAssignable>([true] as const);
expectAssignable>([true] as const);
await sendMessage([undefined] as const);
expectAssignable([undefined] as const);
expectAssignable>([undefined] as const);
expectNotAssignable>([undefined] as const);
await sendMessage([0n] as const);
expectAssignable([0n] as const);
expectAssignable>([0n] as const);
expectNotAssignable>([0n] as const);
await sendMessage({} as const);
expectAssignable({} as const);
expectAssignable>({} as const);
expectAssignable>({} as const);
await sendMessage({test: true} as const);
expectAssignable({test: true} as const);
expectAssignable>({test: true} as const);
expectAssignable>({test: true} as const);
await sendMessage({test: undefined} as const);
expectAssignable({test: undefined} as const);
expectAssignable>({test: undefined} as const);
expectNotAssignable>({test: undefined} as const);
await sendMessage({test: 0n} as const);
expectAssignable({test: 0n} as const);
expectAssignable>({test: 0n} as const);
expectNotAssignable>({test: 0n} as const);
await sendMessage(null);
expectAssignable(null);
expectAssignable>(null);
expectAssignable>(null);
await sendMessage(Number.NaN);
expectAssignable(Number.NaN);
expectAssignable>(Number.NaN);
expectAssignable>(Number.NaN);
await sendMessage(Number.POSITIVE_INFINITY);
expectAssignable(Number.POSITIVE_INFINITY);
expectAssignable>(Number.POSITIVE_INFINITY);
expectAssignable>(Number.POSITIVE_INFINITY);
await sendMessage(new Map());
expectAssignable(new Map());
expectAssignable>(new Map());
expectNotAssignable>(new Map());
await sendMessage(new Set());
expectAssignable(new Set());
expectAssignable>(new Set());
expectNotAssignable>(new Set());
await sendMessage(new Date());
expectAssignable(new Date());
expectAssignable>(new Date());
expectNotAssignable>(new Date());
await sendMessage(/regexp/);
expectAssignable(/regexp/);
expectAssignable>(/regexp/);
expectNotAssignable>(/regexp/);
await sendMessage(new Blob());
expectAssignable(new Blob());
expectAssignable>(new Blob());
expectNotAssignable>(new Blob());
await sendMessage(new File([], ''));
expectAssignable(new File([], ''));
expectAssignable>(new File([], ''));
expectNotAssignable>(new File([], ''));
await sendMessage(new DataView(new ArrayBuffer(0)));
expectAssignable(new DataView(new ArrayBuffer(0)));
expectAssignable>(new DataView(new ArrayBuffer(0)));
expectNotAssignable>(new DataView(new ArrayBuffer(0)));
await sendMessage(new ArrayBuffer(0));
expectAssignable(new ArrayBuffer(0));
expectAssignable>(new ArrayBuffer(0));
expectNotAssignable>(new ArrayBuffer(0));
await sendMessage(new SharedArrayBuffer(0));
expectAssignable(new SharedArrayBuffer(0));
expectAssignable>(new SharedArrayBuffer(0));
expectNotAssignable>(new SharedArrayBuffer(0));
await sendMessage(new Uint8Array());
expectAssignable(new Uint8Array());
expectAssignable>(new Uint8Array());
expectNotAssignable>(new Uint8Array());
await sendMessage(AbortSignal.abort());
expectAssignable(AbortSignal.abort());
expectAssignable>(AbortSignal.abort());
expectNotAssignable>(AbortSignal.abort());
await sendMessage(new Error('test'));
expectAssignable(new Error('test'));
expectAssignable>(new Error('test'));
expectNotAssignable>(new Error('test'));
================================================
FILE: test-d/ipc/send.test-d.ts
================================================
import {expectType, expectError} from 'tsd';
import {
sendMessage,
execa,
type Message,
type Options,
} from '../../index.js';
const subprocess = execa('test', {ipc: true});
expectType(await subprocess.sendMessage(''));
expectType>(sendMessage(''));
expectError(await subprocess.sendMessage());
expectError(await sendMessage());
expectError(await subprocess.sendMessage(undefined));
expectError(await sendMessage(undefined));
expectError(await subprocess.sendMessage(0n));
expectError(await sendMessage(0n));
expectError(await subprocess.sendMessage(Symbol('test')));
expectError(await sendMessage(Symbol('test')));
await execa('test', {ipcInput: ''}).sendMessage('');
await execa('test', {ipcInput: '' as Message}).sendMessage('');
await execa('test', {gracefulCancel: true, cancelSignal: AbortSignal.abort()}).sendMessage('');
await execa('test', {} as Options).sendMessage?.('');
await execa('test', {ipc: true as boolean}).sendMessage?.('');
await execa('test', {ipcInput: '' as '' | undefined}).sendMessage?.('');
await execa('test', {gracefulCancel: true as boolean | undefined, cancelSignal: AbortSignal.abort()}).sendMessage?.('');
expectType(execa('test').sendMessage);
expectType(execa('test', {}).sendMessage);
expectType(execa('test', {ipc: false}).sendMessage);
expectType(execa('test', {ipcInput: undefined}).sendMessage);
expectType(execa('test', {gracefulCancel: undefined}).sendMessage);
expectType(execa('test', {gracefulCancel: false}).sendMessage);
expectType(execa('test', {ipc: false, ipcInput: ''}).sendMessage);
expectType(execa('test', {ipc: false, gracefulCancel: true, cancelSignal: AbortSignal.abort()}).sendMessage);
await subprocess.sendMessage('', {} as const);
await sendMessage('', {} as const);
await subprocess.sendMessage('', {strict: true} as const);
await sendMessage('', {strict: true} as const);
await subprocess.sendMessage('', {strict: true as boolean});
await sendMessage('', {strict: true as boolean});
expectError(await subprocess.sendMessage('', true));
expectError(await sendMessage('', true));
expectError(await subprocess.sendMessage('', {strict: 'true'}));
expectError(await sendMessage('', {strict: 'true'}));
expectError(await subprocess.sendMessage('', {unknown: true}));
expectError(await sendMessage('', {unknown: true}));
expectError(await subprocess.sendMessage('', {strict: true}, {}));
expectError(await sendMessage('', {strict: true}, {}));
================================================
FILE: test-d/methods/command.test-d.ts
================================================
import {expectType, expectError, expectAssignable} from 'tsd';
import {
execa,
execaSync,
$,
execaNode,
execaCommand,
execaCommandSync,
parseCommandString,
type Result,
type ResultPromise,
type SyncResult,
} from '../../index.js';
const fileUrl = new URL('file:///test');
const stringArray = ['foo', 'bar'] as const;
expectError(parseCommandString());
expectError(parseCommandString(true));
expectError(parseCommandString(['unicorns', 'arg']));
expectType(parseCommandString(''));
expectType(parseCommandString('unicorns foo bar'));
expectType>(await execa`${parseCommandString('unicorns foo bar')}`);
expectType>(execaSync`${parseCommandString('unicorns foo bar')}`);
expectType>(await $`${parseCommandString('unicorns foo bar')}`);
expectType>($.sync`${parseCommandString('unicorns foo bar')}`);
expectType>(await execaNode`${parseCommandString('foo bar')}`);
expectType>(await execa`unicorns ${parseCommandString('foo bar')}`);
expectType>(await execa('unicorns', parseCommandString('foo bar')));
expectType>(await execa('unicorns', ['foo', ...parseCommandString('bar')]));
expectError(execaCommand());
expectError(execaCommand(true));
expectError(execaCommand(['unicorns', 'arg']));
expectAssignable(execaCommand('unicorns'));
expectError(execaCommand(fileUrl));
expectError(execaCommand('unicorns', []));
expectError(execaCommand('unicorns', ['foo']));
expectError(execaCommand('unicorns', 'foo'));
expectError(execaCommand('unicorns', [true]));
expectAssignable(execaCommand('unicorns', {}));
expectError(execaCommand('unicorns', [], {}));
expectError(execaCommand('unicorns', [], []));
expectError(execaCommand('unicorns', {other: true}));
expectAssignable(execaCommand`unicorns`);
expectType>(await execaCommand('unicorns'));
expectType>(await execaCommand`unicorns`);
expectAssignable(execaCommand({}));
expectAssignable(execaCommand({})('unicorns'));
expectAssignable(execaCommand({})`unicorns`);
expectAssignable<{stdout: string}>(await execaCommand('unicorns'));
expectAssignable<{stdout: Uint8Array}>(await execaCommand('unicorns', {encoding: 'buffer'}));
expectAssignable<{stdout: string}>(await execaCommand({})('unicorns'));
expectAssignable<{stdout: Uint8Array}>(await execaCommand({encoding: 'buffer'})('unicorns'));
expectAssignable<{stdout: Uint8Array}>(await execaCommand({})({encoding: 'buffer'})('unicorns'));
expectAssignable<{stdout: Uint8Array}>(await execaCommand({encoding: 'buffer'})({})('unicorns'));
expectAssignable<{stdout: string}>(await execaCommand({})`unicorns`);
expectAssignable<{stdout: Uint8Array}>(await execaCommand({encoding: 'buffer'})`unicorns`);
expectAssignable<{stdout: Uint8Array}>(await execaCommand({})({encoding: 'buffer'})`unicorns`);
expectAssignable<{stdout: Uint8Array}>(await execaCommand({encoding: 'buffer'})({})`unicorns`);
expectType>(await execaCommand`${'unicorns'}`);
expectType>(await execaCommand`unicorns ${'foo'}`);
expectError(await execaCommand`unicorns ${'foo'} ${'bar'}`);
expectError(await execaCommand`unicorns ${1}`);
expectError(await execaCommand`unicorns ${stringArray}`);
expectError(await execaCommand`unicorns ${[1, 2]}`);
expectType>(await execaCommand`unicorns ${false.toString()}`);
expectError(await execaCommand`unicorns ${false}`);
expectError(await execaCommand`unicorns ${await execaCommand`echo foo`}`);
expectError(await execaCommand`unicorns ${await execaCommand({reject: false})`echo foo`}`);
expectError(await execaCommand`unicorns ${execaCommand`echo foo`}`);
expectError(await execaCommand`unicorns ${[await execaCommand`echo foo`, 'bar']}`);
expectError(await execaCommand`unicorns ${[execaCommand`echo foo`, 'bar']}`);
expectError(execaCommandSync());
expectError(execaCommandSync(true));
expectError(execaCommandSync(['unicorns', 'arg']));
expectType>(execaCommandSync('unicorns'));
expectError(execaCommandSync(fileUrl));
expectError(execaCommandSync('unicorns', []));
expectError(execaCommandSync('unicorns', ['foo']));
expectType>(execaCommandSync('unicorns', {}));
expectError(execaCommandSync('unicorns', [], {}));
expectError(execaCommandSync('unicorns', 'foo'));
expectError(execaCommandSync('unicorns', [true]));
expectError(execaCommandSync('unicorns', [], []));
expectError(execaCommandSync('unicorns', {other: true}));
expectType>(execaCommandSync`unicorns`);
expectAssignable(execaCommandSync({}));
expectType>(execaCommandSync({})('unicorns'));
expectType>(execaCommandSync({})`unicorns`);
expectType>(execaCommandSync('unicorns'));
expectType>(execaCommandSync`unicorns`);
expectAssignable<{stdout: string}>(execaCommandSync('unicorns'));
expectAssignable<{stdout: Uint8Array}>(execaCommandSync('unicorns', {encoding: 'buffer'}));
expectAssignable<{stdout: string}>(execaCommandSync({})('unicorns'));
expectAssignable<{stdout: Uint8Array}>(execaCommandSync({encoding: 'buffer'})('unicorns'));
expectAssignable<{stdout: Uint8Array}>(execaCommandSync({})({encoding: 'buffer'})('unicorns'));
expectAssignable<{stdout: Uint8Array}>(execaCommandSync({encoding: 'buffer'})({})('unicorns'));
expectAssignable<{stdout: string}>(execaCommandSync({})`unicorns`);
expectAssignable<{stdout: Uint8Array}>(execaCommandSync({encoding: 'buffer'})`unicorns`);
expectAssignable<{stdout: Uint8Array}>(execaCommandSync({})({encoding: 'buffer'})`unicorns`);
expectAssignable<{stdout: Uint8Array}>(execaCommandSync({encoding: 'buffer'})({})`unicorns`);
expectType>(execaCommandSync`${'unicorns'}`);
expectType>(execaCommandSync`unicorns ${'foo'}`);
expectError(execaCommandSync`unicorns ${'foo'} ${'bar'}`);
expectError(execaCommandSync`unicorns ${1}`);
expectError(execaCommandSync`unicorns ${stringArray}`);
expectError(execaCommandSync`unicorns ${[1, 2]}`);
expectError(execaCommandSync`unicorns ${execaCommandSync`echo foo`}`);
expectError(execaCommandSync`unicorns ${[execaCommandSync`echo foo`, 'bar']}`);
expectType>(execaCommandSync`unicorns ${false.toString()}`);
expectError(execaCommandSync`unicorns ${false}`);
================================================
FILE: test-d/methods/list.test-d.ts
================================================
import {expectAssignable} from 'tsd';
import {
type ExecaMethod,
type ExecaSyncMethod,
type ExecaNodeMethod,
type ExecaScriptMethod,
type ExecaScriptSyncMethod,
execa,
execaSync,
execaNode,
$,
} from '../../index.js';
const options = {preferLocal: true} as const;
const secondOptions = {node: true} as const;
expectAssignable(execa);
expectAssignable(execa({}));
expectAssignable(execa({})({}));
expectAssignable(execa(options));
expectAssignable(execa(options)(secondOptions));
expectAssignable(execa(options)({}));
expectAssignable(execa({})(options));
expectAssignable(execaSync);
expectAssignable(execaSync({}));
expectAssignable(execaSync({})({}));
expectAssignable(execaSync(options));
expectAssignable(execaSync(options)(secondOptions));
expectAssignable(execaSync(options)({}));
expectAssignable(execaSync({})(options));
expectAssignable(execaNode);
expectAssignable(execaNode({}));
expectAssignable(execaNode({})({}));
expectAssignable(execaNode(options));
expectAssignable(execaNode(options)(secondOptions));
expectAssignable(execaNode(options)({}));
expectAssignable(execaNode({})(options));
expectAssignable($);
expectAssignable($({}));
expectAssignable($({})({}));
expectAssignable