@stryker-mutator/tap-runner
Version:
A plugin to use the TAP (test anything protocol) test runner in Stryker, the JavaScript mutation testing framework
97 lines (82 loc) • 2.79 kB
text/typescript
import os from 'os';
import { ChildProcessWithoutNullStreams } from 'child_process';
import { glob } from 'glob';
import * as tap from 'tap-parser';
import { TapParser } from './tap-parser-factory.js';
export function findTestyLookingFiles(
globPatterns: string[],
): Promise<string[]> {
return glob(globPatterns, { ignore: ['**/node_modules/**'], posix: true });
}
export interface TapResult {
result: tap.FinalResults;
failedTests: tap.TapError[];
}
function parseTap(
tapProcess: ChildProcessWithoutNullStreams,
forceBail: boolean,
) {
return new Promise<TapResult>((resolve) => {
const failedTests: tap.TapError[] = [];
const config = { bail: forceBail };
const parser = TapParser.Parser(config, (result) => {
resolve({ result, failedTests });
});
parser.on('bailout', () => {
// Bailout within a test file is not supported on windows, because when the process is killed the exit handler which saves the file with the result does not run.
if (os.platform() !== 'win32') {
tapProcess.kill();
}
});
parser.on('fail', (reason: tap.TapError) => {
failedTests.push(reason);
});
tapProcess.stdout.pipe(parser);
});
}
export async function captureTapResult(
tapProcess: ChildProcessWithoutNullStreams,
disableBail: boolean,
): Promise<TapResult> {
const exitAsPromised = new Promise<number>((resolve) =>
tapProcess.on('exit', resolve),
);
const stderrOutput: Buffer[] = [];
tapProcess.stderr.on('data', (chunk: Buffer) => {
stderrOutput.push(chunk);
});
const [exitCodeResult, tapResult] = await Promise.all([
exitAsPromised,
parseTap(tapProcess, disableBail),
]);
if (exitCodeResult !== 0 && !tapResult.failedTests.length) {
// The tap process errored, but we don't have any failed tests. This is probably a syntax error in the test file.
throw new Error(
`Tap process exited with code ${exitCodeResult}. To reproduce it yourself, use the following command:
cd "${process.cwd()}"
${tapProcess.spawnargs.map((arg, index) => (index === 0 ? arg : `"${arg}"`)).join(' ')}
Stderr output:
${stderrOutput.join('')}`,
);
}
return tapResult;
}
export function buildArguments(
args: string[],
hookFile: string,
testFile: string,
): string[] {
const hookFilePlaceholder = '{{hookFile}}';
const testFilePlaceholder = '{{testFile}}';
if (!args.some((arg) => arg.includes(hookFilePlaceholder))) {
args = ['-r', hookFilePlaceholder, ...args];
}
if (!args.some((arg) => arg.includes(testFilePlaceholder))) {
args = [...args, testFilePlaceholder];
}
return args.map((arg) =>
arg
.replaceAll(testFilePlaceholder, testFile)
.replaceAll(hookFilePlaceholder, hookFile),
);
}