PromucFlow_constructor/app/client/cypress/cypress-split.ts
Saroj ba9ebf1d6e
ci: Migrate pending workflows to dime defenders (#26968)
## Description
- Updated the logic to use DimeDefenders spec split
- Updated ci-test-hosted, ci-test-limited and ci-test-custom-script
- Changes to handle when there is no spec to run in CI

#### Type of change
- DimeDefender script
- Workflows
## Testing
- Workflow run
2023-09-06 14:40:13 +05:30

173 lines
4.9 KiB
TypeScript

/* eslint-disable no-console */
import globby from "globby";
import minimatch from "minimatch";
import { exec } from "child_process";
const fs = require("fs/promises");
type GetEnvOptions = {
required?: boolean;
};
// used to roughly determine how many tests are in a file
const testPattern = /(^|\s)(it)\(/g;
// This function will get all the spec paths using the pattern
async function getSpecFilePaths(
specPattern: any,
ignoreTestFiles: any,
): Promise<string[]> {
const files = globby.sync(specPattern, {
ignore: ignoreTestFiles,
});
// ignore the files that doesn't match
const ignorePatterns = [...(ignoreTestFiles || [])];
// a function which returns true if the file does NOT match
const doesNotMatchAllIgnoredPatterns = (file: string) => {
// using {dot: true} here so that folders with a '.' in them are matched
const MINIMATCH_OPTIONS = { dot: true, matchBase: true };
return ignorePatterns.every((pattern) => {
return !minimatch(file, pattern, MINIMATCH_OPTIONS);
});
};
const filtered = files.filter(doesNotMatchAllIgnoredPatterns);
return filtered;
}
// This function will determine the test counts in each file to sort it further
async function getTestCount(filePath: string): Promise<number> {
const content = await fs.readFile(filePath, "utf8");
return content.match(testPattern)?.length || 0;
}
// Sorting the spec files as per the test count in it
async function sortSpecFilesByTestCount(
specPathsOriginal: string[],
): Promise<string[]> {
const specPaths = [...specPathsOriginal];
const testPerSpec: Record<string, number> = {};
for (const specPath of specPaths) {
testPerSpec[specPath] = await getTestCount(specPath);
}
return (
Object.entries(testPerSpec)
// Sort by the number of tests per spec file. And this will create a consistent file list/ordering so that file division proper.
.sort((a, b) => b[1] - a[1])
.map((x) => x[0])
);
}
// This function will split the specs between the runners by calculating the modulus between spec index and the totalRunners
function splitSpecs(
specs: string[],
totalRunnersCount: number,
currentRunner: number,
): string[] {
let specs_to_run = specs.filter((_, index) => {
return index % totalRunnersCount === currentRunner;
});
return specs_to_run;
}
// This function will finally get the specs as a comma separated string to pass the specs to the command
async function getSpecsToRun(
totalRunnersCount = 0,
currentRunner = 0,
specPattern: string | string[] = "cypress/e2e/**/**/*.{js,ts}",
ignorePattern: string | string[],
): Promise<string[]> {
try {
const specFilePaths = await sortSpecFilesByTestCount(
await getSpecFilePaths(specPattern, ignorePattern),
);
if (!specFilePaths.length) {
throw Error("No spec files found.");
}
const specsToRun = splitSpecs(
specFilePaths,
totalRunnersCount,
currentRunner,
);
return specsToRun;
} catch (err) {
console.error(err);
process.exit(1);
}
}
// This function will help to get and convert the env variables
function getEnvNumber(
varName: string,
{ required = false }: GetEnvOptions = {},
): number {
if (required && process.env[varName] === undefined) {
throw Error(`${varName} is not set.`);
}
const value = Number(process.env[varName]);
if (isNaN(value)) {
throw Error(`${varName} is not a number.`);
}
return value;
}
// This function will helps to check and get env variables
function getEnvValue(
varName: string,
{ required = false }: GetEnvOptions = {},
) {
if (required && process.env[varName] === undefined) {
throw Error(`${varName} is not set.`);
}
const value = process.env[varName] === undefined ? "" : process.env[varName];
return value;
}
// This is to fetch the env variables from CI
function getArgs() {
return {
totalRunners: getEnvValue("TOTAL_RUNNERS", { required: false }),
thisRunner: getEnvValue("THIS_RUNNER", { required: false }),
cypressSpecs: getEnvValue("CYPRESS_SPECS", { required: false }),
};
}
export async function cypressSplit(on: any, config: any) {
try {
let currentRunner = 0;
let allRunners = 1;
let specPattern = await config.specPattern;
const ignorePattern = await config.excludeSpecPattern;
const { cypressSpecs, thisRunner, totalRunners } = getArgs();
if (cypressSpecs != "")
specPattern = cypressSpecs?.split(",").filter((val) => val !== "");
if (totalRunners != "") {
currentRunner = Number(thisRunner);
allRunners = Number(totalRunners);
}
const specs = await getSpecsToRun(
allRunners,
currentRunner,
specPattern,
ignorePattern,
);
if (specs.length > 0) {
config.specPattern = specs.length == 1 ? specs[0] : specs;
} else {
config.specPattern = "cypress/scripts/no_spec.ts";
}
return config;
} catch (err) {
console.log(err);
}
}