mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
f6197cd846
Summary: Testing releases takes a lot of time because we have to build locally several configurations. However, the artifacts that we build locally are also built in CI. The goal of this PR is to implement a mechanism to download those artifacts from the CI instead of build locally, so that testing the release locally can take much less time. As an example, the full test cycle can take more than 2 hours given that we need to repackage and rebuilt the app from the template. My plan is to add a table with the time saved once the PR is done ### TODO: - [x] Download Hermes tarball for RNTester iOS - [x] Download Hermes APK for RNTester Android - [x] Download JSC APK for RNTester Android - [x] Download Packaged version of React Native to create a new app - [x] Use the downloaded React Native to initialize an app from the template - [x] Download Maven Local prebuilt in CI and use it for Template Android app ### Time Savings | Setup | Before [s] | After [s] | Notes | | --- | --- | --- | --- | | iOS RNTester Hermes | 339.68 | 194.86 | Time saved by downloading Hermes rather then building it | | iOS RNTester JSC | 129.80 | 123.35 | Not significant, expected as this workflow did not change | Android RNTester Hermes | 1188.82 | 5.28 | Huge improvement: we download the APK rather then build | | Android RNTester JSC | 103.10 | 6.28 | Huge improvement: we download the APK rather then build | | Creating the RNTestProject | 2074.82 | 191.16 | We download Maven, the packaged version of RN and Hermes instead of building from scratch | ## Changelog: [Internal] - Speed up Release testing by downloading the CircleCI artifacts Pull Request resolved: https://github.com/facebook/react-native/pull/37971 Test Plan: - Tested the script locally Reviewed By: cortinico, dmytrorykun Differential Revision: D46859120 Pulled By: cipolleschi fbshipit-source-id: 8878ebaccf6edb801f8e9884e2bf3946380aa748
306 lines
9.0 KiB
JavaScript
306 lines
9.0 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
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
/*
|
|
* This script is a re-interpretation of the old test-manual.e2e.sh script.
|
|
* the idea is to provide a better DX for the manual testing.
|
|
* It's using Javascript over Bash for consistency with the rest of the recent scripts
|
|
* and to make it more accessible for other devs to play around with.
|
|
*/
|
|
|
|
const {exec, pushd, popd, pwd, cd} = require('shelljs');
|
|
const updateTemplatePackage = require('./update-template-package');
|
|
const yargs = require('yargs');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
|
|
const {
|
|
checkPackagerRunning,
|
|
maybeLaunchAndroidEmulator,
|
|
launchPackagerInSeparateWindow,
|
|
setupCircleCIArtifacts,
|
|
prepareArtifacts,
|
|
} = require('./testing-utils');
|
|
|
|
const argv = yargs
|
|
.option('t', {
|
|
alias: 'target',
|
|
default: 'RNTester',
|
|
choices: ['RNTester', 'RNTestProject'],
|
|
})
|
|
.option('p', {
|
|
alias: 'platform',
|
|
default: 'iOS',
|
|
choices: ['iOS', 'Android'],
|
|
})
|
|
.option('h', {
|
|
alias: 'hermes',
|
|
type: 'boolean',
|
|
default: true,
|
|
})
|
|
.option('c', {
|
|
alias: 'circleciToken',
|
|
type: 'string',
|
|
}).argv;
|
|
|
|
// === RNTester === //
|
|
|
|
/**
|
|
* Start the test for RNTester on iOS.
|
|
*
|
|
* Parameters:
|
|
* - @circleCIArtifacts manager object to manage all the download of CircleCIArtifacts. If null, it will fallback not to use them.
|
|
* - @onReleaseBranch whether we are on a release branch or not
|
|
*/
|
|
async function testRNTesterIOS(circleCIArtifacts, onReleaseBranch) {
|
|
console.info(
|
|
`We're going to test the ${
|
|
argv.hermes ? 'Hermes' : 'JSC'
|
|
} version of RNTester iOS with the new Architecture enabled`,
|
|
);
|
|
|
|
// remember that for this to be successful
|
|
// you should have run bundle install once
|
|
// in your local setup
|
|
if (argv.hermes && circleCIArtifacts != null) {
|
|
const hermesURL = await circleCIArtifacts.artifactURLHermesDebug();
|
|
const hermesPath = path.join(
|
|
circleCIArtifacts.baseTmpPath(),
|
|
'hermes-ios-debug.tar.gz',
|
|
);
|
|
// download hermes source code from manifold
|
|
circleCIArtifacts.downloadArtifact(hermesURL, hermesPath);
|
|
console.info(`Downloaded Hermes in ${hermesPath}`);
|
|
exec(
|
|
`HERMES_ENGINE_TARBALL_PATH=${hermesPath} RCT_NEW_ARCH_ENABLED=1 bundle exec pod install --ansi`,
|
|
);
|
|
} else {
|
|
exec(
|
|
`USE_HERMES=${
|
|
argv.hermes ? 1 : 0
|
|
} CI=${onReleaseBranch} RCT_NEW_ARCH_ENABLED=1 bundle exec pod install --ansi`,
|
|
);
|
|
}
|
|
|
|
// if everything succeeded so far, we can launch Metro and the app
|
|
// start the Metro server in a separate window
|
|
launchPackagerInSeparateWindow(pwd());
|
|
|
|
// launch the app on iOS simulator
|
|
exec('npx react-native run-ios --scheme RNTester --simulator "iPhone 14"');
|
|
}
|
|
|
|
/**
|
|
* Start the test for RNTester on Android.
|
|
*
|
|
* Parameters:
|
|
* - @circleCIArtifacts manager object to manage all the download of CircleCIArtifacts. If null, it will fallback not to use them.
|
|
*/
|
|
async function testRNTesterAndroid(circleCIArtifacts) {
|
|
maybeLaunchAndroidEmulator();
|
|
|
|
console.info(
|
|
`We're going to test the ${
|
|
argv.hermes ? 'Hermes' : 'JSC'
|
|
} version of RNTester Android with the new Architecture enabled`,
|
|
);
|
|
|
|
// Start the Metro server so it will be ready if the app can be built and installed successfully.
|
|
launchPackagerInSeparateWindow(pwd());
|
|
|
|
// Wait for the Android Emulator to be properly loaded and bootstrapped
|
|
exec(
|
|
"adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done; input keyevent 82'",
|
|
);
|
|
|
|
if (circleCIArtifacts != null) {
|
|
const downloadPath = path.join(
|
|
circleCIArtifacts.baseTmpPath(),
|
|
'rntester.apk',
|
|
);
|
|
|
|
const emulatorArch = exec('adb shell getprop ro.product.cpu.abi').trim();
|
|
const rntesterAPKURL = argv.hermes
|
|
? await circleCIArtifacts.artifactURLForHermesRNTesterAPK(emulatorArch)
|
|
: await circleCIArtifacts.artifactURLForJSCRNTesterAPK(emulatorArch);
|
|
|
|
console.info('Start Downloading APK');
|
|
circleCIArtifacts.downloadArtifact(rntesterAPKURL, downloadPath);
|
|
|
|
exec(`adb install ${downloadPath}`);
|
|
} else {
|
|
exec(
|
|
`../../gradlew :packages:rn-tester:android:app:${
|
|
argv.hermes ? 'installHermesDebug' : 'installJscDebug'
|
|
} --quiet`,
|
|
);
|
|
}
|
|
|
|
// launch the app
|
|
// TODO: we should find a way to make it work like for iOS, via npx react-native run-android
|
|
// currently, that fails with an error.
|
|
exec(
|
|
'adb shell am start -n com.facebook.react.uiapp/com.facebook.react.uiapp.RNTesterActivity',
|
|
);
|
|
|
|
// just to make sure that the Android up won't have troubles finding the Metro server
|
|
exec('adb reverse tcp:8081 tcp:8081');
|
|
}
|
|
|
|
/**
|
|
* Function that start testing on RNTester.
|
|
*
|
|
* Parameters:
|
|
* - @circleCIArtifacts manager object to manage all the download of CircleCIArtifacts. If null, it will fallback not to use them.
|
|
* - @onReleaseBranch whether we are on a release branch or not
|
|
*/
|
|
async function testRNTester(circleCIArtifacts, onReleaseBranch) {
|
|
// FIXME: make sure that the commands retains colors
|
|
// (--ansi) doesn't always work
|
|
// see also https://github.com/shelljs/shelljs/issues/86
|
|
pushd('packages/rn-tester');
|
|
|
|
if (argv.platform === 'iOS') {
|
|
await testRNTesterIOS(circleCIArtifacts, onReleaseBranch);
|
|
} else {
|
|
await testRNTesterAndroid(circleCIArtifacts);
|
|
}
|
|
popd();
|
|
}
|
|
|
|
// === RNTestProject === //
|
|
|
|
async function testRNTestProject(circleCIArtifacts) {
|
|
console.info("We're going to test a fresh new RN project");
|
|
|
|
// create the local npm package to feed the CLI
|
|
|
|
// base setup required (specular to publish-npm.js)
|
|
const baseVersion = require('../packages/react-native/package.json').version;
|
|
|
|
// in local testing, 1000.0.0 mean we are on main, every other case means we are
|
|
// working on a release version
|
|
const buildType = baseVersion !== '1000.0.0' ? 'release' : 'dry-run';
|
|
|
|
// we need to add the unique timestamp to avoid npm/yarn to use some local caches
|
|
const dateIdentifier = new Date()
|
|
.toISOString()
|
|
.slice(0, -8)
|
|
.replace(/[-:]/g, '')
|
|
.replace(/[T]/g, '-');
|
|
|
|
const releaseVersion = `${baseVersion}-${dateIdentifier}`;
|
|
|
|
// Prepare some variables for later use
|
|
const repoRoot = pwd();
|
|
const reactNativePackagePath = `${repoRoot}/packages/react-native`;
|
|
const localNodeTGZPath = `${reactNativePackagePath}/react-native-${releaseVersion}.tgz`;
|
|
|
|
const mavenLocalPath =
|
|
circleCIArtifacts != null
|
|
? path.join(circleCIArtifacts.baseTmpPath(), 'maven-local.zip')
|
|
: '/private/tmp/maven-local';
|
|
const hermesPath = await prepareArtifacts(
|
|
circleCIArtifacts,
|
|
mavenLocalPath,
|
|
localNodeTGZPath,
|
|
releaseVersion,
|
|
buildType,
|
|
reactNativePackagePath,
|
|
);
|
|
|
|
updateTemplatePackage({
|
|
'react-native': `file:${localNodeTGZPath}`,
|
|
});
|
|
|
|
// create locally the node module
|
|
exec('npm pack --pack-destination ', {cwd: reactNativePackagePath});
|
|
|
|
// node pack does not creates a version of React Native with the right name on main.
|
|
// Let's add some defensive programming checks:
|
|
if (!fs.existsSync(localNodeTGZPath)) {
|
|
const tarfile = fs
|
|
.readdirSync(reactNativePackagePath)
|
|
.find(name => name.startsWith('react-native-') && name.endsWith('.tgz'));
|
|
if (!tarfile) {
|
|
throw new Error("Couldn't find a zipped version of react-native");
|
|
}
|
|
exec(
|
|
`cp ${path.join(reactNativePackagePath, tarfile)} ${localNodeTGZPath}`,
|
|
);
|
|
}
|
|
|
|
pushd('/tmp/');
|
|
// need to avoid the pod install step - we'll do it later
|
|
exec(
|
|
`node ${reactNativePackagePath}/cli.js init RNTestProject --template ${localNodeTGZPath} --skip-install`,
|
|
);
|
|
|
|
cd('RNTestProject');
|
|
exec('yarn install');
|
|
|
|
// need to do this here so that Android will be properly setup either way
|
|
exec(
|
|
`echo "REACT_NATIVE_MAVEN_LOCAL_REPO=${mavenLocalPath}" >> android/gradle.properties`,
|
|
);
|
|
|
|
// doing the pod install here so that it's easier to play around RNTestProject
|
|
cd('ios');
|
|
exec('bundle install');
|
|
exec(
|
|
`HERMES_ENGINE_TARBALL_PATH=${hermesPath} USE_HERMES=${
|
|
argv.hermes ? 1 : 0
|
|
} bundle exec pod install --ansi`,
|
|
);
|
|
|
|
cd('..');
|
|
|
|
if (argv.platform === 'iOS') {
|
|
exec('yarn ios');
|
|
} else {
|
|
// android
|
|
exec('yarn android');
|
|
}
|
|
popd();
|
|
}
|
|
|
|
async function main() {
|
|
/*
|
|
* see the test-local-e2e.js script for clean up process
|
|
*/
|
|
|
|
// command order: we ask the user to select if they want to test RN tester
|
|
// or RNTestProject
|
|
|
|
// if they select RN tester, we ask if iOS or Android, and then we run the tests
|
|
// if they select RNTestProject, we run the RNTestProject test
|
|
|
|
checkPackagerRunning();
|
|
|
|
const branchName = exec('git rev-parse --abbrev-ref HEAD', {
|
|
silent: true,
|
|
}).stdout.trim();
|
|
const onReleaseBranch = branchName.endsWith('-stable');
|
|
|
|
let circleCIArtifacts = await setupCircleCIArtifacts(
|
|
argv.circleciToken,
|
|
branchName,
|
|
);
|
|
|
|
if (argv.target === 'RNTester') {
|
|
await testRNTester(circleCIArtifacts, onReleaseBranch);
|
|
} else {
|
|
await testRNTestProject(circleCIArtifacts);
|
|
}
|
|
}
|
|
|
|
main();
|