Files
explore/deploy/tools/affected-tests/index.js
T

209 lines
6.5 KiB
JavaScript

/* eslint-disable no-console */
const { execSync } = require('child_process');
const dependencyTree = require('dependency-tree');
const fs = require('fs');
const path = require('path');
const ROOT_DIR = path.resolve(__dirname, '../../../');
const TARGET_FILE = path.resolve(ROOT_DIR, './playwright/affected-tests.txt');
const NON_EXISTENT_DEPS = [];
const DIRECTORIES_WITH_TESTS = [
path.resolve(ROOT_DIR, './ui'),
];
const VISITED = {};
function getAllPwFilesInDirectory(directory) {
const files = fs.readdirSync(directory, { recursive: true });
return files
.filter((file) => file.endsWith('.pw.tsx'))
.map((file) => path.join(directory, file));
}
function getFileDeps(filename, changedNpmModules) {
return dependencyTree.toList({
filename,
directory: ROOT_DIR,
filter: (path) => {
if (path.indexOf('node_modules') === -1) {
return true;
}
if (changedNpmModules.some((module) => path.startsWith(module))) {
return true;
}
return false;
},
tsConfig: path.resolve(ROOT_DIR, './tsconfig.json'),
nonExistent: NON_EXISTENT_DEPS,
visited: VISITED,
});
}
async function getChangedFiles() {
const command = process.env.CI ?
`git diff --name-only origin/${ process.env.GITHUB_BASE_REF } ${ process.env.GITHUB_SHA } -- ${ ROOT_DIR }` :
`git diff --name-only main $(git branch --show-current) -- ${ ROOT_DIR }`;
console.log('Executing command: ', command);
const files = execSync(command)
.toString()
.trim()
.split('\n')
.filter(Boolean);
return files.map((file) => path.join(ROOT_DIR, file));
}
function checkChangesInChakraTheme(changedFiles) {
const themeDir = path.resolve(ROOT_DIR, './theme');
return changedFiles.some((file) => file.startsWith(themeDir));
}
function checkChangesInSvgSprite(changedFiles) {
const iconDir = path.resolve(ROOT_DIR, './icons');
const areIconsChanged = changedFiles.some((file) => file.startsWith(iconDir));
if (!areIconsChanged) {
return false;
}
const svgNamesFile = path.resolve(ROOT_DIR, './public/icons/name.d.ts');
const areSvgNamesChanged = changedFiles.some((file) => file === svgNamesFile);
if (!areSvgNamesChanged) {
// If only the icons have changed and not the names in the SVG file, we will need to run all tests.
// This is because we cannot correctly identify the test files that depend on these changes.
return true;
}
// If the icon names have changed, then there should be changes in the components that use them.
// Otherwise, typescript would complain about that.
return false;
}
function createTargetFile(content) {
fs.writeFileSync(TARGET_FILE, content);
}
function getPackageJsonUpdatedProps(packageJsonFile) {
const command = process.env.CI ?
`git diff --unified=0 origin/${ process.env.GITHUB_BASE_REF } ${ process.env.GITHUB_SHA } -- ${ packageJsonFile }` :
`git diff --unified=0 main $(git branch --show-current) -- ${ packageJsonFile }`;
console.log('Executing command: ', command);
const changedLines = execSync(command)
.toString()
.trim()
.split('\n')
.filter(Boolean)
.filter((line) => line.startsWith('+ ') || line.startsWith('- '));
const changedProps = [ ...new Set(
changedLines
.map((line) => line.replaceAll(' ', '').replaceAll('+', '').replaceAll('-', ''))
.map((line) => line.split(':')[0].replaceAll('"', '')),
) ];
return changedProps;
}
function getUpdatedNpmModules(changedFiles) {
const packageJsonFile = path.resolve(ROOT_DIR, './package.json');
if (!changedFiles.includes(packageJsonFile)) {
return [];
}
try {
const packageJsonContent = JSON.parse(fs.readFileSync(packageJsonFile, 'utf-8'));
const usedNpmModules = [
...Object.keys(packageJsonContent.dependencies || {}),
...Object.keys(packageJsonContent.devDependencies || {}),
];
const updatedProps = getPackageJsonUpdatedProps(packageJsonFile);
return updatedProps.filter((prop) => usedNpmModules.includes(prop));
} catch (error) {}
}
async function run() {
// NOTES:
// - The absence of TARGET_FILE implies that all tests should be run.
// - The empty TARGET_FILE implies that no tests should be run.
const start = Date.now();
fs.unlink(TARGET_FILE, () => {});
const changedFiles = await getChangedFiles();
if (!changedFiles.length) {
createTargetFile('');
console.log('No changed files found. Exiting...');
return;
}
console.log('Changed files in the branch: ', changedFiles);
if (checkChangesInChakraTheme(changedFiles)) {
console.log('Changes in Chakra theme detected. It is advisable to run all test suites. Exiting...');
return;
}
if (checkChangesInSvgSprite(changedFiles)) {
console.log('There are some changes in the SVG sprite that cannot be linked to a specific component. It is advisable to run all test suites. Exiting...');
return;
}
let changedNpmModules = getUpdatedNpmModules(changedFiles);
if (!changedNpmModules) {
console.log('Some error occurred while detecting changed NPM modules. It is advisable to run all test suites. Exiting...');
return;
}
console.log('Changed NPM modules in the branch: ', changedNpmModules);
changedNpmModules = [
...changedNpmModules,
...changedNpmModules.map((module) => `@types/${ module }`), // there are some deps that are resolved to .d.ts files
].map((module) => path.resolve(ROOT_DIR, `./node_modules/${ module }`));
const allTestFiles = DIRECTORIES_WITH_TESTS.reduce((acc, dir) => {
return acc.concat(getAllPwFilesInDirectory(dir));
}, []);
const isDepChanged = (dep) => changedFiles.includes(dep) || changedNpmModules.some((module) => dep.startsWith(module));
const testFilesToRun = allTestFiles
.map((file) => ({ file, deps: getFileDeps(file, changedNpmModules) }))
.filter(({ deps }) => deps.some(isDepChanged));
const testFileNamesToRun = testFilesToRun.map(({ file }) => path.relative(ROOT_DIR, file));
if (!testFileNamesToRun.length) {
createTargetFile('');
console.log('No tests to run. Exiting...');
return;
}
createTargetFile(testFileNamesToRun.join('\n'));
const end = Date.now();
const testFilesToRunWithFilteredDeps = testFilesToRun.map(({ file, deps }) => ({
file,
deps: deps.filter(isDepChanged),
}));
console.log('Total time: ', ((end - start) / 1_000).toLocaleString());
console.log('Total test to run: ', testFileNamesToRun.length);
console.log('Tests to run with changed deps: ', testFilesToRunWithFilteredDeps);
console.log('Non existent deps: ', NON_EXISTENT_DEPS);
}
run();