@knapsack-pro/vitest
Version:
Knapsack Pro Vitest splits Vitest tests across CI nodes and makes sure that tests will run in optimal time on each CI node.
106 lines (105 loc) • 4.51 kB
JavaScript
import { KnapsackProCore, KnapsackProLogger, } from '@knapsack-pro/core';
import { startVitest, parseCLI, createVitest, } from 'vitest/node';
import { v4 as uuidv4 } from 'uuid';
import { minimatch } from 'minimatch';
import { glob } from 'glob';
import { join, dirname } from 'path';
import fs from 'fs';
import { fileURLToPath } from 'url';
import * as Urls from './urls.js';
if (process.env.KNAPSACK_PRO_TEST_SUITE_TOKEN_VITEST) {
process.env.KNAPSACK_PRO_TEST_SUITE_TOKEN =
process.env.KNAPSACK_PRO_TEST_SUITE_TOKEN_VITEST;
}
const knapsackProLogger = new KnapsackProLogger();
const projectPath = process.cwd();
async function main() {
const filePath = fileURLToPath(import.meta.url);
const dirName = dirname(filePath);
const pkg = JSON.parse(fs.readFileSync(join(dirName, '..', 'package.json'), 'utf8'));
knapsackProLogger.debug(`Running ${pkg.name}@${pkg.version}`);
const resolvedConfig = await getResolvedConfig();
const knapsackPro = new KnapsackProCore(pkg.name, pkg.version, makeGetAllTestFiles(resolvedConfig));
const onSuccess = async (testFiles) => {
const vitest = await startVitest('test', undefined, {
include: testFiles.map((testFile) => testFile.path),
watch: false,
...generateCoverageConfig(resolvedConfig),
});
if (!vitest) {
throw new Error('[@knapsack-pro/vitest] Vitest failed to start');
}
await vitest.close();
return getTestResults(vitest);
};
const onError = () => { };
knapsackPro.runQueueMode(onSuccess, onError);
}
main();
function getTestResults(vitest) {
const recordedTestFiles = vitest.state.getFiles().map((file) => {
const testFile = vitest.state.getReportedEntity(file);
if (testFile?.type !== 'module') {
throw new Error('[@knapsack-pro/vitest] Vitest reported non-module file');
}
const path = process.platform === 'win32'
? file.filepath.replace(`${projectPath}\\`, '').replace(/\\/g, '/')
: file.filepath.replace(`${projectPath}/`, '');
const diagnostic = testFile.diagnostic();
return {
path,
time_execution: diagnostic.duration / 1000,
};
});
const isTestSuiteGreen = process.exitCode !== 1;
return {
recordedTestFiles,
isTestSuiteGreen,
};
}
async function getResolvedConfig() {
const cliArguments = parseCLI(`vitest ${process.argv.slice(2)}`);
knapsackProLogger.debug(`Vitest CLI options:\n${KnapsackProLogger.objectInspect(cliArguments)}`);
if (cliArguments.filter.length > 0) {
throw new Error('[@knapsack-pro/vitest] Passing list of files to test from command line is not supported');
}
const resolvedConfig = (await createVitest('test', { ...cliArguments.options, watch: false })).config;
return resolvedConfig;
}
function makeGetAllTestFiles(resolvedConfig) {
return () => {
const testFileIncludePattern = process.env.KNAPSACK_PRO_TEST_FILE_PATTERN ||
resolvedConfig.include ||
'**/*.{test,spec}.?(c|m)[jt]s?(x)';
const testFileExcludePattern = process.env
.KNAPSACK_PRO_TEST_FILE_EXCLUDE_PATTERN
? [process.env.KNAPSACK_PRO_TEST_FILE_EXCLUDE_PATTERN]
: resolvedConfig.exclude || [];
const testFiles = glob
.sync(testFileIncludePattern)
.filter((testFilePath) => testFileExcludePattern.every((excludePattern) => !minimatch(testFilePath, excludePattern, {
matchBase: true,
})))
.filter((testFilePath) => !testFilePath.match(/node_modules/))
.map((testFilePath) => ({ path: testFilePath }));
if (testFiles.length === 0) {
const errorMessage = `[@knapsack-pro/vitest] Test files cannot be found.\nPlease set KNAPSACK_PRO_TEST_FILE_PATTERN matching your test directory structure.\nLearn more: ${Urls.NO_TESTS_FOUND}`;
knapsackProLogger.error(errorMessage);
throw new Error(errorMessage);
}
return testFiles;
};
}
function generateCoverageConfig(resolvedConfig) {
const coverageDirectory = process.env.KNAPSACK_PRO_COVERAGE_DIRECTORY ||
resolvedConfig.coverage.reportsDirectory;
if (resolvedConfig.coverage.enabled) {
return {
coverage: {
reportsDirectory: join(coverageDirectory, uuidv4()),
},
};
}
return {};
}