@stryker-mutator/vitest-runner
Version:
A plugin to use the vitest test runner and framework in Stryker, the JavaScript mutation testing framework
129 lines (115 loc) • 3.42 kB
text/typescript
import path from 'path';
import {
BaseTestResult,
TestResult,
TestStatus,
} from '@stryker-mutator/api/test-runner';
import { type RunMode, type RunnerTestSuite, type TaskState } from 'vitest';
import { RunnerTestCase } from 'vitest/node';
import { MutantCoverage } from '@stryker-mutator/api/core';
import { collectTestName, toRawTestId } from './test-helpers.js';
function convertTaskStateToTestStatus(
taskState: TaskState | undefined,
testMode: RunMode,
): TestStatus {
if (testMode === 'skip') {
return TestStatus.Skipped;
}
switch (taskState) {
case 'pass':
return TestStatus.Success;
case 'fail':
return TestStatus.Failed;
case 'skip':
case 'todo':
return TestStatus.Skipped;
default: // taskState is undefined | "run" | "only". This should not happen
}
return TestStatus.Failed;
}
export function convertTestToTestResult(test: RunnerTestCase): TestResult {
const status = convertTaskStateToTestStatus(test.result?.state, test.mode);
const baseTestResult: BaseTestResult = {
id: normalizeTestId(toRawTestId(test)),
name: collectTestName(test),
timeSpentMs: test.result?.duration ?? 0,
fileName: test.file?.filepath && path.resolve(test.file.filepath),
};
if (status === TestStatus.Failed) {
return {
...baseTestResult,
status,
failureMessage:
test.result?.errors?.[0]?.message ?? 'StrykerJS: Unknown test failure',
};
} else if (status === TestStatus.Skipped) {
const suiteError = findSuiteError(test.suite);
if (suiteError) {
return {
...baseTestResult,
status: TestStatus.Failed,
failureMessage: suiteError,
};
}
}
return {
...baseTestResult,
status,
};
}
function findSuiteError(
suite: RunnerTestSuite | undefined,
): string | undefined {
if (!suite) {
return undefined;
}
if (suite.result?.state === 'fail') {
return (
suite.result?.errors?.[0]?.message ?? 'StrykerJS: Suite execution failed'
);
}
return findSuiteError(suite.suite);
}
export function fromTestId(id: string): { file: string; test: string } {
const [file, ...name] = id.split('#');
return { file, test: name.join('#') };
}
export function normalizeTestId(id: string): string {
const { file, test } = fromTestId(id);
return `${path.relative(process.cwd(), file).replace(/\\/g, '/')}#${test}`;
}
export function normalizeCoverage(rawCoverage: MutantCoverage): MutantCoverage {
return {
perTest: Object.fromEntries(
Object.entries(rawCoverage.perTest).map(
([rawTestId, coverageData]) =>
[normalizeTestId(rawTestId), coverageData] as const,
),
),
static: rawCoverage.static,
};
}
export function collectTestsFromSuite(
suite: RunnerTestSuite,
): RunnerTestCase[] {
return suite.tasks.flatMap((task) => {
if (task.type === 'suite') {
return collectTestsFromSuite(task);
} else if (task.type === 'test') {
return task;
} else {
return [];
}
});
}
export function isErrorCodeError(
error: unknown,
): error is Error & { code: string } {
return (
error instanceof Error && 'code' in error && typeof error.code === 'string'
);
}
/** @see https://github.com/vitest-dev/vitest/blob/main/packages/vitest/src/node/errors.ts */
export const VITEST_ERROR_CODES = Object.freeze({
FILES_NOT_FOUND: 'VITEST_FILES_NOT_FOUND',
});