From 4bced9099a873bb9ff6316023785995d758559ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Mon, 2 Dec 2024 06:06:26 -0800 Subject: [PATCH] Implement warm up step to remove costly builds from individual test running time (#48015) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/48015 Changelog: [internal] Right now, when we run individual Fantom tests, we compile Hermes and the RN Tester CLI as part of the test, which causes the first test to run to be very slow and the remaining tests in the same run to be very fast. This is misleading because it makes it look like the test itself is slow, when it's actually paying a price for everyone. Fortunately, Jest has an option to do a global setup before any tests in the project run (and it doesn't run if none of the tests in the project run, in multi-project setups), so we can use it to do the necessary warmup so it doesn't end up being attributed to individual tests. Reviewed By: javache Differential Revision: D66595406 fbshipit-source-id: 496aa2b248da661f7504c8445fed1edad0301803 --- jest/integration/config/jest.config.js | 1 + jest/integration/runner/warmup/index.js | 13 +++ jest/integration/runner/warmup/warmup.js | 113 +++++++++++++++++++ jest/integration/runtime/WarmUpEntryPoint.js | 17 +++ 4 files changed, 144 insertions(+) create mode 100644 jest/integration/runner/warmup/index.js create mode 100644 jest/integration/runner/warmup/warmup.js create mode 100644 jest/integration/runtime/WarmUpEntryPoint.js diff --git a/jest/integration/config/jest.config.js b/jest/integration/config/jest.config.js index b00053063fe..cd047bfaae4 100644 --- a/jest/integration/config/jest.config.js +++ b/jest/integration/config/jest.config.js @@ -25,4 +25,5 @@ module.exports = { transformIgnorePatterns: ['.*'], testRunner: './jest/integration/runner/index.js', watchPathIgnorePatterns: ['/jest/integration/build/'], + globalSetup: './jest/integration/runner/warmup/index.js', }; diff --git a/jest/integration/runner/warmup/index.js b/jest/integration/runner/warmup/index.js new file mode 100644 index 00000000000..1e3f3ef54ba --- /dev/null +++ b/jest/integration/runner/warmup/index.js @@ -0,0 +1,13 @@ +/** + * 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 + * @oncall react_native + */ + +require('../../../../scripts/build/babel-register').registerForMonorepo(); + +module.exports = require('./warmup'); diff --git a/jest/integration/runner/warmup/warmup.js b/jest/integration/runner/warmup/warmup.js new file mode 100644 index 00000000000..9836209b84a --- /dev/null +++ b/jest/integration/runner/warmup/warmup.js @@ -0,0 +1,113 @@ +/** + * 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. + * + * @flow strict-local + * @format + * @oncall react_native + */ + +import {getBuckModeForPlatform, runBuck2} from '../utils'; +// $FlowExpectedError[untyped-import] +import fs from 'fs'; +import Metro from 'metro'; +import os from 'os'; +import path from 'path'; + +export default async function warmUp( + globalConfig: {...}, + projectConfig: {...}, +): Promise { + try { + warmUpHermesCompiler(); + warmUpRNTesterCLI(); + await warmUpMetro(); + } catch (e) { + // Sandcastle fails to parse the test output if we log stuff to stdout/stderr. + if ((process.env.SANDCASTLE ?? '') !== '') { + console.error( + 'Global warmup failed. Tests will continue to run but will likely fail. Details:\n', + e, + ); + } + } +} + +async function warmUpMetro(): Promise { + const metroConfig = await Metro.loadConfig({ + config: path.resolve(__dirname, '..', '..', 'config', 'metro.config.js'), + }); + + const entrypointPath = path.resolve( + __dirname, + '..', + '..', + 'runtime', + 'WarmUpEntryPoint.js', + ); + + const bundlePath = path.join( + os.tmpdir(), + `fantom-warmup-bundle-${Date.now()}.js`, + ); + + await Metro.runBuild(metroConfig, { + entry: entrypointPath, + out: bundlePath, + platform: 'android', + minify: false, + dev: true, + }); + + try { + fs.unlinkSync(bundlePath); + } catch {} +} + +function warmUpHermesCompiler(): void { + const buildHermesCompilerCommandResult = runBuck2([ + 'build', + getBuckModeForPlatform(), + '//xplat/hermes/tools/hermesc:hermesc', + ]); + + if (buildHermesCompilerCommandResult.status !== 0) { + throw new Error( + [ + 'Failed to build Hermes compiler. Full output:', + buildHermesCompilerCommandResult.originalCommand, + 'stdout:', + buildHermesCompilerCommandResult.stdout, + 'stderr:', + buildHermesCompilerCommandResult.stderr, + 'error:', + buildHermesCompilerCommandResult.error, + ].join('\n'), + ); + } +} + +function warmUpRNTesterCLI(): void { + const buildRNTesterCommandResult = runBuck2([ + 'build', + getBuckModeForPlatform(), + '//xplat/ReactNative/react-native-cxx/samples/tester:tester', + ]); + + if (buildRNTesterCommandResult.status !== 0) { + throw new Error( + [ + 'Failed to build RN tester binary. Full output:', + buildRNTesterCommandResult.originalCommand, + 'stdout:', + buildRNTesterCommandResult.stdout, + 'stderr:', + buildRNTesterCommandResult.stderr, + 'error:', + buildRNTesterCommandResult.error, + ].join('\n'), + ); + } +} diff --git a/jest/integration/runtime/WarmUpEntryPoint.js b/jest/integration/runtime/WarmUpEntryPoint.js new file mode 100644 index 00000000000..faf4f6c45ee --- /dev/null +++ b/jest/integration/runtime/WarmUpEntryPoint.js @@ -0,0 +1,17 @@ +/** + * 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 + * @oncall react_native + */ + +/** + * This is just an entrypoint to warm up the Metro cache before the tests run. + */ + +import 'react-native/Libraries/Core/InitializeCore.js'; +import 'react-native/src/private/__tests__/ReactNativeTester'; +import './setup';