Skip to content

Commit

Permalink
Split test files
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky committed Sep 1, 2024
1 parent eac7c65 commit cfac7dd
Show file tree
Hide file tree
Showing 12 changed files with 1,483 additions and 1,338 deletions.
32 changes: 32 additions & 0 deletions test/context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import test from 'ava';
import {red} from 'yoctocolors';
import nanoSpawn from '../source/index.js';
import {testString} from './helpers/arguments.js';
import {assertDurationMs} from './helpers/assert.js';
import {nodePrint, nodePrintStdout} from './helpers/commands.js';

test('result.command does not quote normal arguments', async t => {
const {command} = await nanoSpawn('node', ['--version']);
t.is(command, 'node --version');
});

const testCommandEscaping = async (t, input, expectedCommand) => {
const {command, stdout} = await nanoSpawn(...nodePrint(`"${input}"`));
t.is(command, `node -p '"${expectedCommand}"'`);
t.is(stdout, input);
};

test('result.command quotes spaces', testCommandEscaping, '. .', '. .');
test('result.command quotes single quotes', testCommandEscaping, '\'', '\'\\\'\'');
test('result.command quotes unusual characters', testCommandEscaping, ',', ',');
test('result.command strips ANSI sequences', testCommandEscaping, red(testString), testString);

test('result.durationMs is set', async t => {
const {durationMs} = await nanoSpawn(...nodePrintStdout);
assertDurationMs(t, durationMs);
});

test('error.durationMs is set', async t => {
const {durationMs} = await t.throwsAsync(nanoSpawn('node', ['--unknown']));
assertDurationMs(t, durationMs);
});
11 changes: 11 additions & 0 deletions test/helpers/arguments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const testString = 'test';
export const secondTestString = 'secondTest';
export const thirdTestString = 'thirdTest';
export const fourthTestString = 'fourthTest';
export const testUpperCase = testString.toUpperCase();
export const testDoubleUpperCase = `${testUpperCase}${testUpperCase}`;

export const multibyteString = '.\u{1F984}.';
const multibyteUint8Array = new TextEncoder().encode(multibyteString);
export const multibyteFirstHalf = multibyteUint8Array.slice(0, 3);
export const multibyteSecondHalf = multibyteUint8Array.slice(3);
100 changes: 100 additions & 0 deletions test/helpers/assert.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import {nonExistentCommand, nodeHangingCommand, nodeEvalCommandStart} from './commands.js';

export const assertDurationMs = (t, durationMs) => {
t.true(Number.isFinite(durationMs));
t.true(durationMs > 0);
};

export const assertNonExistent = (t, {exitCode, signalName, command, message, stderr, cause, durationMs}, commandStart = nonExistentCommand, expectedCommand = commandStart) => {
t.is(exitCode, undefined);
t.is(signalName, undefined);
t.is(command, expectedCommand);
t.is(message, `Command failed: ${expectedCommand}`);
t.is(stderr, '');
t.true(cause.message.includes(commandStart));
t.is(cause.code, 'ENOENT');
t.is(cause.syscall, `spawn ${commandStart}`);
t.is(cause.path, commandStart);
assertDurationMs(t, durationMs);
};

export const assertWindowsNonExistent = (t, {exitCode, signalName, command, message, stderr, cause, durationMs}, expectedCommand = nonExistentCommand) => {
t.is(exitCode, 1);
t.is(signalName, undefined);
t.is(command, expectedCommand);
t.is(message, `Command failed with exit code 1: ${expectedCommand}`);
t.true(stderr.includes('not recognized as an internal or external command'));
t.is(cause, undefined);
assertDurationMs(t, durationMs);
};

export const assertUnixNonExistentShell = (t, {exitCode, signalName, command, message, stderr, cause, durationMs}, expectedCommand = nonExistentCommand) => {
t.is(exitCode, 127);
t.is(signalName, undefined);
t.is(command, expectedCommand);
t.is(message, `Command failed with exit code 127: ${expectedCommand}`);
t.true(stderr.includes('not found'));
t.is(cause, undefined);
assertDurationMs(t, durationMs);
};

export const assertUnixNotFound = (t, {exitCode, signalName, command, message, stderr, cause, durationMs}, expectedCommand = nonExistentCommand) => {
t.is(exitCode, 127);
t.is(signalName, undefined);
t.is(command, expectedCommand);
t.is(message, `Command failed with exit code 127: ${expectedCommand}`);
t.true(stderr.includes('No such file or directory'));
t.is(cause, undefined);
assertDurationMs(t, durationMs);
};

export const assertFail = (t, {exitCode, signalName, command, message, cause, durationMs}, commandStart = nodeEvalCommandStart) => {
t.is(exitCode, 2);
t.is(signalName, undefined);
t.true(command.startsWith(commandStart));
t.true(message.startsWith(`Command failed with exit code 2: ${commandStart}`));
t.is(cause, undefined);
assertDurationMs(t, durationMs);
};

export const assertSigterm = (t, {exitCode, signalName, command, message, stderr, cause, durationMs}, expectedCommand = nodeHangingCommand) => {
t.is(exitCode, undefined);
t.is(signalName, 'SIGTERM');
t.is(command, expectedCommand);
t.is(message, `Command was terminated with SIGTERM: ${expectedCommand}`);
t.is(stderr, '');
t.is(cause, undefined);
assertDurationMs(t, durationMs);
};

export const assertEarlyError = (t, {exitCode, signalName, command, message, stderr, cause, durationMs}, commandStart = nodeEvalCommandStart) => {
t.is(exitCode, undefined);
t.is(signalName, undefined);
t.true(command.startsWith(commandStart));
t.true(message.startsWith(`Command failed: ${commandStart}`));
t.is(stderr, '');
t.true(cause.message.includes('options.detached'));
t.false(cause.message.includes('Command'));
assertDurationMs(t, durationMs);
};

export const assertAbortError = (t, {exitCode, signalName, command, stderr, message, cause, durationMs}, expectedCause, expectedCommand = nodeHangingCommand) => {
t.is(exitCode, undefined);
t.is(signalName, undefined);
t.is(command, expectedCommand);
t.is(message, `Command failed: ${expectedCommand}`);
t.is(stderr, '');
t.is(cause.message, 'The operation was aborted');
t.is(cause.cause, expectedCause);
assertDurationMs(t, durationMs);
};

export const assertErrorEvent = (t, {exitCode, signalName, command, message, stderr, cause, durationMs}, expectedCause, commandStart = nodeEvalCommandStart) => {
t.is(exitCode, undefined);
t.is(signalName, undefined);
t.true(command.startsWith(commandStart));
t.true(message.startsWith(`Command failed: ${commandStart}`));
t.is(stderr, '');
t.is(cause, expectedCause);
assertDurationMs(t, durationMs);
};
58 changes: 58 additions & 0 deletions test/helpers/commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {testString, secondTestString} from './arguments.js';

export const nodeHanging = ['node'];
export const [nodeHangingCommand] = nodeHanging;
export const nodePrint = bodyString => ['node', ['-p', bodyString]];
export const nodeEval = bodyString => ['node', ['-e', bodyString]];
export const nodeEvalCommandStart = 'node -e';
export const nodePrintStdout = nodeEval(`console.log("${testString}")`);
export const nodePrintStderr = nodeEval(`console.error("${testString}")`);
export const nodePrintBoth = nodeEval(`console.log("${testString}");
setTimeout(() => {
console.error("${secondTestString}");
}, 0);`);
export const nodePrintBothFail = nodeEval(`console.log("${testString}");
setTimeout(() => {
console.error("${secondTestString}");
process.exit(2);
}, 0);`);
export const nodePrintFail = nodeEval(`console.log("${testString}");
process.exit(2);`);
export const nodePrintSleep = nodeEval(`setTimeout(() => {
console.log("${testString}");
}, 1e2);`);
export const nodePrintSleepFail = nodeEval(`setTimeout(() => {
console.log("${testString}");
process.exit(2);
}, 1e2);`);
export const nodePrintArgv0 = nodePrint('process.argv0');
export const nodePrintNoNewline = output => nodeEval(`process.stdout.write("${output.replaceAll('\n', '\\n').replaceAll('\r', '\\r')}")`);
export const nodePassThrough = nodeEval('process.stdin.pipe(process.stdout)');
export const nodePassThroughPrint = nodeEval(`process.stdin.pipe(process.stdout);
console.log("${testString}");`);
export const nodePassThroughPrintFail = nodeEval(`process.stdin.once("data", (chunk) => {
console.log(chunk.toString());
process.exit(2);
});
console.log("${testString}");`);
export const nodeToUpperCase = nodeEval(`process.stdin.on("data", chunk => {
console.log(chunk.toString().trim().toUpperCase());
});`);
export const nodeToUpperCaseStderr = nodeEval(`process.stdin.on("data", chunk => {
console.error(chunk.toString().trim().toUpperCase());
});`);
export const nodeToUpperCaseFail = nodeEval(`process.stdin.on("data", chunk => {
console.log(chunk.toString().trim().toUpperCase());
process.exit(2);
});`);
export const nodeDouble = nodeEval(`process.stdin.on("data", chunk => {
console.log(chunk.toString().trim() + chunk.toString().trim());
});`);
export const nodeDoubleFail = nodeEval(`process.stdin.on("data", chunk => {
console.log(chunk.toString().trim() + chunk.toString().trim());
process.exit(2);
});`);
export const localBinary = ['ava', ['--version']];
export const localBinaryCommand = localBinary.flat().join(' ');
export const [localBinaryCommandStart] = localBinary;
export const nonExistentCommand = 'non-existent-command';
37 changes: 37 additions & 0 deletions test/helpers/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import path from 'node:path';
import process from 'node:process';
import {setTimeout} from 'node:timers/promises';
import {fileURLToPath} from 'node:url';
import {multibyteFirstHalf, multibyteSecondHalf} from './arguments.js';

export const isLinux = process.platform === 'linux';
export const isWindows = process.platform === 'win32';

export const FIXTURES_URL = new URL('../fixtures/', import.meta.url);
export const fixturesPath = fileURLToPath(FIXTURES_URL);

export const nodeDirectory = path.dirname(process.execPath);

export const earlyErrorOptions = {detached: 'true'};

// TODO: replace with Array.fromAsync() after dropping support for Node <22.0.0
export const arrayFromAsync = async asyncIterable => {
const chunks = [];
for await (const chunk of asyncIterable) {
chunks.push(chunk);
}

return chunks;
};

export const destroySubprocessStream = async ({nodeChildProcess}, error, streamName) => {
const subprocess = await nodeChildProcess;
subprocess[streamName].destroy(error);
};

export const writeMultibyte = async promise => {
const {stdin} = await promise.nodeChildProcess;
stdin.write(multibyteFirstHalf);
await setTimeout(1e2);
stdin.end(multibyteSecondHalf);
};
Loading

0 comments on commit cfac7dd

Please sign in to comment.