1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
|
import process from 'node:process';
import childProcess from 'node:child_process';
import {promisify} from 'node:util';
import test from 'ava';
import {execa, execaSync} from '../index.js';
import {FIXTURES_DIR, setFixtureDir} from './helpers/fixtures-dir.js';
const pExec = promisify(childProcess.exec);
setFixtureDir();
const TIMEOUT_REGEXP = /timed out after/;
const getExitRegExp = exitMessage => new RegExp(`failed with exit code ${exitMessage}`);
test('stdout/stderr/all available on errors', async t => {
const {stdout, stderr, all} = await t.throwsAsync(execa('exit.js', ['2'], {all: true}), {message: getExitRegExp('2')});
t.is(typeof stdout, 'string');
t.is(typeof stderr, 'string');
t.is(typeof all, 'string');
});
const WRONG_COMMAND = process.platform === 'win32'
? '\'wrong\' is not recognized as an internal or external command,\r\noperable program or batch file.'
: '';
test('stdout/stderr/all on process errors', async t => {
const {stdout, stderr, all} = await t.throwsAsync(execa('wrong command', {all: true}));
t.is(stdout, '');
t.is(stderr, WRONG_COMMAND);
t.is(all, WRONG_COMMAND);
});
test('stdout/stderr/all on process errors, in sync mode', t => {
const {stdout, stderr, all} = t.throws(() => {
execaSync('wrong command');
});
t.is(stdout, '');
t.is(stderr, WRONG_COMMAND);
t.is(all, undefined);
});
test('exitCode is 0 on success', async t => {
const {exitCode} = await execa('noop.js', ['foo']);
t.is(exitCode, 0);
});
const testExitCode = async (t, number) => {
const {exitCode} = await t.throwsAsync(execa('exit.js', [`${number}`]), {message: getExitRegExp(number)});
t.is(exitCode, number);
};
test('exitCode is 2', testExitCode, 2);
test('exitCode is 3', testExitCode, 3);
test('exitCode is 4', testExitCode, 4);
test('error.message contains the command', async t => {
await t.throwsAsync(execa('exit.js', ['2', 'foo', 'bar']), {message: /exit.js 2 foo bar/});
});
test('error.message contains stdout/stderr if available', async t => {
const {message} = await t.throwsAsync(execa('echo-fail.js'));
t.true(message.includes('stderr'));
t.true(message.includes('stdout'));
});
test('error.message does not contain stdout/stderr if not available', async t => {
const {message} = await t.throwsAsync(execa('echo-fail.js', {stdio: 'ignore'}));
t.false(message.includes('stderr'));
t.false(message.includes('stdout'));
});
test('error.shortMessage does not contain stdout/stderr', async t => {
const {shortMessage} = await t.throwsAsync(execa('echo-fail.js'));
t.false(shortMessage.includes('stderr'));
t.false(shortMessage.includes('stdout'));
});
test('Original error.message is kept', async t => {
const {originalMessage} = await t.throwsAsync(execa('noop.js', {cwd: 1}));
t.true(originalMessage.startsWith('The "options.cwd" property must be of type string or an instance of Buffer or URL. Received type number'));
});
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('exit.js', ['2']));
t.true(failed);
});
test('error.killed is true if process was killed directly', async t => {
const subprocess = execa('noop.js');
subprocess.kill();
const {killed} = await t.throwsAsync(subprocess, {message: /was killed with SIGTERM/});
t.true(killed);
});
test('error.killed is false if process was killed indirectly', async t => {
const subprocess = execa('noop.js');
process.kill(subprocess.pid, 'SIGINT');
// `process.kill()` is emulated by Node.js on Windows
const message = process.platform === 'win32' ? /failed with exit code 1/ : /was killed with SIGINT/;
const {killed} = await t.throwsAsync(subprocess, {message});
t.false(killed);
});
test('result.killed is false if not killed', async t => {
const {killed} = await execa('noop.js');
t.false(killed);
});
test('result.killed is false if not killed, in sync mode', t => {
const {killed} = execaSync('noop.js');
t.false(killed);
});
test('result.killed is false on process error', async t => {
const {killed} = await t.throwsAsync(execa('wrong command'));
t.false(killed);
});
test('result.killed is false on process error, in sync mode', t => {
const {killed} = t.throws(() => {
execaSync('wrong command');
});
t.false(killed);
});
if (process.platform === 'darwin') {
test('sanity check: child_process.exec also has killed.false if killed indirectly', async t => {
const promise = pExec('noop.js');
process.kill(promise.child.pid, 'SIGINT');
const error = await t.throwsAsync(promise);
t.truthy(error);
t.false(error.killed);
});
}
if (process.platform !== 'win32') {
test('error.signal is SIGINT', async t => {
const subprocess = execa('noop.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('noop.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('noop.js');
process.kill(subprocess.pid, 'SIGTERM');
const {signal} = await t.throwsAsync(subprocess, {message: /was killed with SIGTERM/});
t.is(signal, 'SIGTERM');
});
test('custom error.signal', async t => {
const {signal} = await t.throwsAsync(execa('noop.js', {killSignal: 'SIGHUP', timeout: 1, message: TIMEOUT_REGEXP}));
t.is(signal, 'SIGHUP');
});
test('exitCode is undefined on signal termination', async t => {
const subprocess = execa('noop.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 process failed, but was not killed', async t => {
const {signal} = await t.throwsAsync(execa('exit.js', [2]), {message: getExitRegExp('2')});
t.is(signal, undefined);
});
test('result.signalDescription is undefined for successful execution', async t => {
const {signalDescription} = await execa('noop.js');
t.is(signalDescription, undefined);
});
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', {cwd: 1}));
t.is(code, 'ERR_INVALID_ARG_TYPE');
});
test('error.cwd is defined on failure if applicable', async t => {
const {cwd} = await t.throwsAsync(execa('noop-throw.js', [], {cwd: FIXTURES_DIR}));
t.is(cwd, FIXTURES_DIR);
});
test('error.cwd is undefined on failure if not passed as options', async t => {
const expectedCwd = process.cwd();
const {cwd} = await t.throwsAsync(execa('noop-throw.js'));
t.is(cwd, expectedCwd);
});
|