mirror of
https://github.com/gitlabhq/gitlabhq.git
synced 2025-08-01 16:46:16 +00:00
176 lines
5.2 KiB
JavaScript
Executable File
176 lines
5.2 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Unlike rspec tests, we don't have a crystalball mapping file to calculate predicted tests, instead we need to find them using the dependency graph generated at runtime.
|
|
*
|
|
* So, we use helper functions in this module to list the frontend predictive tests (Jest) that are ran in tier-1, based on these criteria
|
|
* 1. Changed files (changed_files.txt)
|
|
* 2. Vue version predictive tests -- this doesn't affect test discovery, only test execution, so, we'lll get the list using Vue2
|
|
* 2b. Vue 2 (includes all tests)
|
|
* 2a. Vue 3 (some vue3 tests are quarantined)
|
|
* 3. Backend changes (`js_matching_files.txt`)
|
|
* 3a. Fixtures
|
|
* 3b. Views
|
|
* 4. Jest integration tests (uses --config `jest.config.integration.js`)
|
|
*
|
|
* CI rule match these file patterns
|
|
* .frontend-predictive-patterns:
|
|
* '{,ee/,jh/}{app/assets/javascripts,spec/frontend}/**'
|
|
*
|
|
* Command usage:
|
|
* Run
|
|
* ```sh
|
|
* # set env variables and then run the following
|
|
* ./scripts/frontend/find_jest_predictive_tests.js
|
|
* ```
|
|
*/
|
|
const { spawnSync } = require('node:child_process');
|
|
const { readFileSync, writeFileSync, mkdirSync } = require('node:fs');
|
|
const { relative, dirname } = require('node:path');
|
|
|
|
const requiredEnv = [
|
|
'JEST_MATCHING_TEST_FILES_PATH',
|
|
'RSPEC_MATCHING_JS_FILES_PATH',
|
|
'RSPEC_CHANGED_FILES_PATH',
|
|
];
|
|
|
|
function hasRequiredEnvironmentVariables() {
|
|
const missing = requiredEnv.filter((envVar) => !process.env[envVar]);
|
|
|
|
if (missing.length > 0) {
|
|
console.warn(`Warning: Missing required environment variables: ${missing.join(', ')}`);
|
|
console.warn('Some functionality may not work as expected.');
|
|
process.exitCode = 1;
|
|
}
|
|
}
|
|
|
|
function getChangedFiles() {
|
|
const files = [];
|
|
const { RSPEC_MATCHING_JS_FILES_PATH, RSPEC_CHANGED_FILES_PATH } = process.env;
|
|
|
|
for (const [name, filePath] of Object.entries({
|
|
RSPEC_CHANGED_FILES_PATH,
|
|
RSPEC_MATCHING_JS_FILES_PATH,
|
|
})) {
|
|
try {
|
|
const contents = readFileSync(filePath, { encoding: 'UTF-8' });
|
|
files.push(...contents.split(/\s+/).filter(Boolean));
|
|
} catch (error) {
|
|
console.warn(
|
|
`Failed to read from path ${filePath} given by environment variable ${name}`,
|
|
error,
|
|
);
|
|
}
|
|
}
|
|
|
|
return Array.from(new Set(files));
|
|
}
|
|
|
|
function findJestTests(config, changedFiles) {
|
|
const args = ['--ci', '--config', config, '--listTests'];
|
|
|
|
if (changedFiles) {
|
|
if (!changedFiles.length) return [];
|
|
|
|
args.push('--findRelatedTests');
|
|
args.push(...changedFiles);
|
|
}
|
|
try {
|
|
const childProcess = spawnSync('node_modules/.bin/jest', args, {
|
|
encoding: 'utf8',
|
|
stdio: 'pipe',
|
|
env: process.env,
|
|
});
|
|
|
|
if (childProcess.error) {
|
|
throw new Error(`Failed to spawn Jest: ${childProcess.error.message}`);
|
|
}
|
|
|
|
if (childProcess.status !== 0) {
|
|
const errorOutput = childProcess.stderr || 'No error output captured';
|
|
throw new Error(`Jest exited with code ${childProcess.status}: ${errorOutput}`);
|
|
}
|
|
|
|
if (!childProcess.stdout) {
|
|
console.warn('No output from Jest.');
|
|
return [];
|
|
}
|
|
return childProcess.stdout
|
|
.split('\n')
|
|
.map((line) => line.trim())
|
|
.filter((line) => line.endsWith('.js'))
|
|
.map((test) => relative(process.cwd(), test));
|
|
} catch (error) {
|
|
throw new Error(`Failed to run Jest with config ${config}: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
function collectTests(changedFiles) {
|
|
if (changedFiles && !changedFiles.length) {
|
|
console.log('No changed files found - no tests to run');
|
|
return [];
|
|
}
|
|
console.log(`Analyzing ${changedFiles.length} changed files...`);
|
|
|
|
const configs = [
|
|
{ name: 'unit', configPath: 'jest.config.js' },
|
|
{ name: 'integration', configPath: 'jest.config.integration.js' },
|
|
];
|
|
|
|
const allTests = [];
|
|
|
|
for (const { name, configPath } of configs) {
|
|
try {
|
|
console.log(`Checking ${name} tests...`);
|
|
const tests = findJestTests(configPath, changedFiles);
|
|
allTests.push(...tests);
|
|
} catch (error) {
|
|
console.error(`Error with ${name} tests config:\n`, error);
|
|
}
|
|
}
|
|
|
|
return Array.from(new Set(allTests)).sort();
|
|
}
|
|
|
|
function saveMatchingTestFiles(testFiles) {
|
|
const { JEST_MATCHING_TEST_FILES_PATH } = process.env;
|
|
if (JEST_MATCHING_TEST_FILES_PATH) {
|
|
mkdirSync(dirname(JEST_MATCHING_TEST_FILES_PATH), { recursive: true });
|
|
writeFileSync(JEST_MATCHING_TEST_FILES_PATH, testFiles.join(' '));
|
|
console.log(`Saved to: ${JEST_MATCHING_TEST_FILES_PATH}`);
|
|
}
|
|
}
|
|
|
|
function logAndSaveMatchingTestFiles(changedFiles) {
|
|
const matchingTestFiles = collectTests(changedFiles);
|
|
console.log(`\nFound ${matchingTestFiles.length} predictive test files`);
|
|
|
|
console.log('\n=== TEST FILES ===');
|
|
console.log(matchingTestFiles.join('\n'));
|
|
|
|
saveMatchingTestFiles(matchingTestFiles);
|
|
}
|
|
|
|
function listPredictiveTests() {
|
|
try {
|
|
hasRequiredEnvironmentVariables();
|
|
|
|
logAndSaveMatchingTestFiles(getChangedFiles());
|
|
} catch (error) {
|
|
console.error('Error finding predictive jest tests:', error);
|
|
process.exitCode = 1;
|
|
}
|
|
}
|
|
|
|
if (require.main === module) {
|
|
listPredictiveTests();
|
|
} else {
|
|
module.exports = {
|
|
hasRequiredEnvironmentVariables,
|
|
getChangedFiles,
|
|
findJestTests,
|
|
collectTests,
|
|
logAndSaveMatchingTestFiles,
|
|
};
|
|
}
|