mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
bb4e940b6d
Summary: This change allow our CI to run E2E tests using a specific tag in the commit message ## Changelog: [Internal] - Allow to run e2e test using a specific tag in the last commit message Pull Request resolved: https://github.com/facebook/react-native/pull/41308 Test Plan: CircleCI is green Tested interacting with the PR/branch Reviewed By: NickGerleman Differential Revision: D50975588 Pulled By: cipolleschi fbshipit-source-id: 6318800d7e86e1cab394af2b320e280304189dd2
248 lines
6.2 KiB
JavaScript
248 lines
6.2 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @format
|
|
*/
|
|
|
|
/**
|
|
* This script is meant to be used only in CircleCI to compute which tests we should execute
|
|
* based on the files modified by the commit.
|
|
*/
|
|
|
|
const {execSync} = require('child_process');
|
|
const fs = require('fs');
|
|
const yargs = require('yargs');
|
|
|
|
/**
|
|
* Check whether the filename is a JS/TS file and not in the script folder
|
|
*/
|
|
function isJSChange(name) {
|
|
const isJS =
|
|
name.endsWith('.js') ||
|
|
name.endsWith('.jsx') ||
|
|
name.endsWith('.ts') ||
|
|
name.endsWith('.tsx');
|
|
const notAScript = name.indexOf('scripts/') < 0;
|
|
return isJS && notAScript;
|
|
}
|
|
|
|
function isIOS(name) {
|
|
return (
|
|
name.indexOf('React/') > -1 ||
|
|
name.endsWith('.rb') ||
|
|
name.endsWith('.podspec')
|
|
);
|
|
}
|
|
|
|
/**
|
|
* This maps some high level pipelines to some conditions.
|
|
* If those conditions are true, we are going to run the associated pipeline in CI.
|
|
* So, for example, is the commit the CI is analyzing only contains changes in the React/ folder or changes to ruby files
|
|
* the RUN_IOS flag will be turned on and we will run only the CI suite for iOS tests.
|
|
*
|
|
* This mapping is not final and we can update it with more pipelines or we can update the filter condition in the future.
|
|
*/
|
|
const mapping = [
|
|
{
|
|
name: 'RUN_IOS',
|
|
filterFN: name => isIOS(name),
|
|
},
|
|
{
|
|
name: 'RUN_ANDROID',
|
|
filterFN: name => name.indexOf('ReactAndroid/') > -1,
|
|
},
|
|
{
|
|
name: 'RUN_JS',
|
|
filterFN: name => isJSChange(name),
|
|
},
|
|
{
|
|
name: 'SKIP',
|
|
filterFN: name => name.endsWith('.md'),
|
|
},
|
|
];
|
|
|
|
// ===== //
|
|
// Yargs //
|
|
// ===== //
|
|
|
|
const createConfigsOption = {
|
|
input_path: {
|
|
alias: 'i',
|
|
describe: 'The path to the folder where the Config parts are located',
|
|
default: '.circleci/configurations',
|
|
},
|
|
output_path: {
|
|
alias: 'o',
|
|
describe: 'The path where the `generated_config.yml` will be created',
|
|
default: '.circleci',
|
|
},
|
|
config_file: {
|
|
alias: 'c',
|
|
describe: 'The configuration file with the parameter to set',
|
|
default: '/tmp/circleci/pipeline_config.json',
|
|
},
|
|
};
|
|
|
|
const filterJobsOptions = {
|
|
output_path: {
|
|
alias: 'o',
|
|
ddescribe: 'The output path for the configurations',
|
|
default: '/tmp/circleci',
|
|
},
|
|
};
|
|
|
|
yargs
|
|
.command(
|
|
'create-configs',
|
|
'Creates the circleci config from its files',
|
|
createConfigsOption,
|
|
argv => createConfigs(argv.input_path, argv.output_path, argv.config_file),
|
|
)
|
|
.command(
|
|
'filter-jobs',
|
|
'Filters the jobs based on the list of chaged files in the PR',
|
|
filterJobsOptions,
|
|
argv => filterJobs(argv.output_path),
|
|
)
|
|
.demandCommand()
|
|
.strict()
|
|
.help().argv;
|
|
|
|
// ============== //
|
|
// GITHUB UTILS //
|
|
// ============== //
|
|
|
|
function _getFilesFromGit() {
|
|
try {
|
|
execSync('git fetch');
|
|
const commonCommit = String(
|
|
execSync('git merge-base HEAD origin/HEAD '),
|
|
).trim();
|
|
return String(execSync(`git diff --name-only HEAD ${commonCommit}`))
|
|
.trim()
|
|
.split('\n');
|
|
} catch (error) {
|
|
console.error(error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function _shouldRunE2E() {
|
|
try {
|
|
const gitCommand = 'git log -1 --pretty=format:"%B" | head -n 1';
|
|
const commitMessage = String(execSync(gitCommand)).trim();
|
|
console.log(`commitMessage: ${commitMessage}`);
|
|
return commitMessage.includes('#run-e2e-tests');
|
|
} catch (error) {
|
|
console.error(error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function _computeAndSavePipelineParameters(
|
|
pipelineType,
|
|
outputPath,
|
|
shouldRunE2E,
|
|
) {
|
|
fs.mkdirSync(outputPath, {recursive: true});
|
|
const filePath = `${outputPath}/pipeline_config.json`;
|
|
|
|
if (pipelineType === 'SKIP') {
|
|
fs.writeFileSync(filePath, JSON.stringify({}, null, 2));
|
|
return;
|
|
}
|
|
|
|
console.log(`Should run e2e? ${shouldRunE2E}`);
|
|
if (pipelineType === 'ALL') {
|
|
fs.writeFileSync(
|
|
filePath,
|
|
JSON.stringify({run_all: true, run_e2e: shouldRunE2E}, null, 2),
|
|
);
|
|
return;
|
|
}
|
|
|
|
const params = {
|
|
run_all: false,
|
|
run_ios: pipelineType === 'RUN_IOS',
|
|
run_android: pipelineType === 'RUN_ANDROID',
|
|
run_js: pipelineType === 'RUN_JS',
|
|
run_e2e: shouldRunE2E,
|
|
};
|
|
|
|
const stringifiedParams = JSON.stringify(params, null, 2);
|
|
fs.writeFileSync(filePath, stringifiedParams);
|
|
console.info(`Generated params:\n${stringifiedParams}`);
|
|
}
|
|
|
|
// ============== //
|
|
// COMMANDS //
|
|
// ============== //
|
|
|
|
function createConfigs(inputPath, outputPath, configFile) {
|
|
// Order is important!
|
|
const baseConfigFiles = [
|
|
'top_level.yml',
|
|
'executors.yml',
|
|
'commands.yml',
|
|
'jobs.yml',
|
|
'workflows.yml',
|
|
];
|
|
|
|
const baseFolder = 'test_workflows';
|
|
const testConfigs = {
|
|
run_ios: ['testIOS.yml'],
|
|
run_android: ['testAndroid.yml'],
|
|
run_e2e: ['testE2E.yml'],
|
|
run_all: ['testJS.yml', 'testAll.yml'],
|
|
run_js: ['testJS.yml'],
|
|
};
|
|
|
|
if (!fs.existsSync(configFile)) {
|
|
throw new Error(`Config file: ${configFile} does not exists`);
|
|
}
|
|
|
|
const jsonParams = JSON.parse(fs.readFileSync(configFile));
|
|
let configurationPartsFiles = baseConfigFiles;
|
|
|
|
for (const [key, shouldTest] of Object.entries(jsonParams)) {
|
|
if (shouldTest) {
|
|
const mappedFiles = testConfigs[key].map(f => `${baseFolder}/${f}`);
|
|
configurationPartsFiles = configurationPartsFiles.concat(mappedFiles);
|
|
}
|
|
}
|
|
console.log(configurationPartsFiles);
|
|
let configParts = [];
|
|
configurationPartsFiles.forEach(yamlPart => {
|
|
const fileContent = fs.readFileSync(`${inputPath}/${yamlPart}`);
|
|
configParts.push(fileContent);
|
|
});
|
|
console.info(`writing to: ${outputPath}/generated_config.yml`);
|
|
fs.writeFileSync(
|
|
`${outputPath}/generated_config.yml`,
|
|
configParts.join('\n'),
|
|
);
|
|
}
|
|
|
|
function filterJobs(outputPath) {
|
|
const fileList = _getFilesFromGit();
|
|
|
|
if (fileList.length === 0) {
|
|
_computeAndSavePipelineParameters('SKIP', outputPath);
|
|
return;
|
|
}
|
|
|
|
const shouldRunE2E = _shouldRunE2E();
|
|
|
|
for (const filter of mapping) {
|
|
const found = fileList.every(filter.filterFN);
|
|
if (found) {
|
|
_computeAndSavePipelineParameters(filter.name, outputPath, shouldRunE2E);
|
|
return;
|
|
}
|
|
}
|
|
_computeAndSavePipelineParameters('ALL', outputPath, shouldRunE2E);
|
|
}
|