mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Refactor Fantom runner to decouple compilation from execution to speed up test execution (#52758)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/52758 Changelog: [internal] This is a change to how Fantom tests run in Meta infra via Buck. After this, the biggest opportunity will be optimizing how we generate bundles with Metro. Reviewed By: christophpurrer Differential Revision: D78672863 fbshipit-source-id: 1152907f3ba60e7d2e48bcc588f3c07aef7bb393
This commit is contained in:
1 parent
f047c9b42b
commit
3ecd48fa91
6 files changed
+259
-81
No files matched your search
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
import type {HermesVariant, SyncCommandResult} from '../utils';
|
||||
|
||||
import {isCI} from '../EnvironmentOptions';
|
||||
import {
|
||||
getBuckModesForPlatform,
|
||||
getBuckOptionsForHermes,
|
||||
getDebugInfoFromCommandResult,
|
||||
getHermesCompilerTarget,
|
||||
runBuck2Sync,
|
||||
runCommandSync,
|
||||
} from '../utils';
|
||||
import {getNativeBuildOutputPath} from './utils';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
type TesterOptions = $ReadOnly<{
|
||||
isOptimizedMode: boolean,
|
||||
hermesVariant: HermesVariant,
|
||||
}>;
|
||||
|
||||
function getHermesCompilerPath({
|
||||
isOptimizedMode,
|
||||
hermesVariant,
|
||||
}: TesterOptions): string {
|
||||
return path.join(
|
||||
getNativeBuildOutputPath(),
|
||||
`hermesc-${(hermesVariant as string).toLowerCase()}-${isOptimizedMode ? 'opt' : 'dev'}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function build(options: TesterOptions): void {
|
||||
const destPath = getHermesCompilerPath(options);
|
||||
if (fs.existsSync(destPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tmpPath = destPath + '-' + Date.now();
|
||||
|
||||
try {
|
||||
const result = runBuck2Sync([
|
||||
'build',
|
||||
...getBuckModesForPlatform(options.isOptimizedMode),
|
||||
...getBuckOptionsForHermes(options.hermesVariant),
|
||||
getHermesCompilerTarget(options.hermesVariant),
|
||||
'--out',
|
||||
tmpPath,
|
||||
]);
|
||||
|
||||
if (result.status !== 0) {
|
||||
throw new Error(getDebugInfoFromCommandResult(result));
|
||||
}
|
||||
|
||||
if (fs.existsSync(destPath)) {
|
||||
// Another test might have compiled the binary after our initial check.
|
||||
return;
|
||||
}
|
||||
|
||||
fs.renameSync(tmpPath, destPath);
|
||||
} finally {
|
||||
try {
|
||||
fs.unlinkSync(tmpPath);
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
export function run(
|
||||
args: $ReadOnlyArray<string>,
|
||||
options: TesterOptions,
|
||||
): SyncCommandResult {
|
||||
if (!isCI) {
|
||||
build(options);
|
||||
}
|
||||
|
||||
return runCommandSync(getHermesCompilerPath(options), args);
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
import type {AsyncCommandResult, HermesVariant} from '../utils';
|
||||
|
||||
import {debugCpp, isCI} from '../EnvironmentOptions';
|
||||
import {
|
||||
getBuckModesForPlatform,
|
||||
getBuckOptionsForHermes,
|
||||
getDebugInfoFromCommandResult,
|
||||
runBuck2,
|
||||
runBuck2Sync,
|
||||
runCommand,
|
||||
} from '../utils';
|
||||
import {getNativeBuildOutputPath} from './utils';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const FANTOM_TESTER_BUCK_TARGET =
|
||||
'fbsource//xplat/js/react-native-github/private/react-native-fantom/tester:tester';
|
||||
|
||||
type TesterOptions = $ReadOnly<{
|
||||
isOptimizedMode: boolean,
|
||||
hermesVariant: HermesVariant,
|
||||
}>;
|
||||
|
||||
function getFantomTesterPath({
|
||||
isOptimizedMode,
|
||||
hermesVariant,
|
||||
}: TesterOptions): string {
|
||||
return path.join(
|
||||
getNativeBuildOutputPath(),
|
||||
`fantom-tester-${(hermesVariant as string).toLowerCase()}-${isOptimizedMode ? 'opt' : 'dev'}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function build(options: TesterOptions): void {
|
||||
const destPath = getFantomTesterPath(options);
|
||||
if (fs.existsSync(destPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tmpPath = destPath + '-' + Date.now();
|
||||
|
||||
try {
|
||||
const result = runBuck2Sync([
|
||||
'build',
|
||||
...getBuckModesForPlatform(options.isOptimizedMode),
|
||||
...getBuckOptionsForHermes(options.hermesVariant),
|
||||
FANTOM_TESTER_BUCK_TARGET,
|
||||
'--out',
|
||||
tmpPath,
|
||||
]);
|
||||
|
||||
if (result.status !== 0) {
|
||||
throw new Error(getDebugInfoFromCommandResult(result));
|
||||
}
|
||||
|
||||
if (fs.existsSync(destPath)) {
|
||||
// Another test might have compiled the binary after our initial check.
|
||||
return;
|
||||
}
|
||||
|
||||
fs.renameSync(tmpPath, destPath);
|
||||
} finally {
|
||||
try {
|
||||
fs.unlinkSync(tmpPath);
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
export function run(
|
||||
args: $ReadOnlyArray<string>,
|
||||
options: TesterOptions,
|
||||
): AsyncCommandResult {
|
||||
if (isCI && debugCpp) {
|
||||
throw new Error('Cannot run Fantom with C++ debugging on CI');
|
||||
}
|
||||
|
||||
if (!isCI && !debugCpp) {
|
||||
build(options);
|
||||
}
|
||||
|
||||
if (debugCpp) {
|
||||
return runBuck2(
|
||||
[
|
||||
'run',
|
||||
...getBuckModesForPlatform(options.isOptimizedMode),
|
||||
...getBuckOptionsForHermes(options.hermesVariant),
|
||||
FANTOM_TESTER_BUCK_TARGET,
|
||||
'--',
|
||||
...args,
|
||||
],
|
||||
{
|
||||
withFDB: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return runCommand(getFantomTesterPath(options), args);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
|
||||
const BUILD_OUTPUT_ROOT = path.resolve(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'build',
|
||||
'native',
|
||||
);
|
||||
|
||||
export function getNativeBuildOutputPath(): string {
|
||||
return BUILD_OUTPUT_ROOT;
|
||||
}
|
||||
+17
-47
@@ -9,11 +9,10 @@
|
||||
*/
|
||||
|
||||
import {isCI} from '../EnvironmentOptions';
|
||||
import {
|
||||
getBuckModesForPlatform,
|
||||
getDebugInfoFromCommandResult,
|
||||
runBuck2Sync,
|
||||
} from '../utils';
|
||||
import {build as buildHermesCompiler} from '../executables/hermesc';
|
||||
import {build as buildFantomTester} from '../executables/tester';
|
||||
import {getNativeBuildOutputPath} from '../executables/utils';
|
||||
import {HermesVariant} from '../utils';
|
||||
// $FlowExpectedError[untyped-import]
|
||||
import fs from 'fs';
|
||||
import Metro from 'metro';
|
||||
@@ -39,23 +38,20 @@ async function tryOrLog(
|
||||
}
|
||||
|
||||
export default async function build(): Promise<void> {
|
||||
try {
|
||||
fs.rmSync(getNativeBuildOutputPath(), {recursive: true});
|
||||
} catch {}
|
||||
|
||||
fs.mkdirSync(getNativeBuildOutputPath(), {recursive: true});
|
||||
|
||||
if (isCI) {
|
||||
await tryOrLog(
|
||||
() => warmUpHermesCompiler(false),
|
||||
'Error warming up Hermes compiler (dev)',
|
||||
);
|
||||
await tryOrLog(
|
||||
() => warmUpHermesCompiler(true),
|
||||
'Error warming up Hermes compiler (opt)',
|
||||
);
|
||||
await tryOrLog(
|
||||
() => warmUpRNTesterCLI(false),
|
||||
'Error warming up RN Tester CLI (dev)',
|
||||
);
|
||||
await tryOrLog(
|
||||
() => warmUpRNTesterCLI(true),
|
||||
'Error warming up RN Tester CLI (opt)',
|
||||
);
|
||||
for (const isOptimizedMode of [false, true]) {
|
||||
for (const hermesVariant of HermesVariant.members()) {
|
||||
buildFantomTester({isOptimizedMode, hermesVariant});
|
||||
buildHermesCompiler({isOptimizedMode, hermesVariant});
|
||||
}
|
||||
}
|
||||
|
||||
await tryOrLog(() => warmUpMetro(false), 'Error warming up Metro (dev)');
|
||||
await tryOrLog(() => warmUpMetro(true), 'Error warming up Metro (opt)');
|
||||
}
|
||||
@@ -91,29 +87,3 @@ async function warmUpMetro(isOptimizedMode: boolean): Promise<void> {
|
||||
fs.unlinkSync(bundlePath);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function warmUpHermesCompiler(isOptimizedMode: boolean): void {
|
||||
const buildHermesCompilerCommandResult = runBuck2Sync([
|
||||
'build',
|
||||
...getBuckModesForPlatform(isOptimizedMode),
|
||||
'//xplat/hermes/tools/hermesc:hermesc',
|
||||
]);
|
||||
|
||||
if (buildHermesCompilerCommandResult.status !== 0) {
|
||||
throw new Error(
|
||||
getDebugInfoFromCommandResult(buildHermesCompilerCommandResult),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function warmUpRNTesterCLI(isOptimizedMode: boolean): void {
|
||||
const buildRNTesterCommandResult = runBuck2Sync([
|
||||
'build',
|
||||
...getBuckModesForPlatform(isOptimizedMode),
|
||||
'//xplat/js/react-native-github/private/react-native-fantom/tester:tester',
|
||||
]);
|
||||
|
||||
if (buildRNTesterCommandResult.status !== 0) {
|
||||
throw new Error(getDebugInfoFromCommandResult(buildRNTesterCommandResult));
|
||||
}
|
||||
}
|
||||
+11
-24
@@ -22,6 +22,8 @@ import type {
|
||||
|
||||
import entrypointTemplate from './entrypoint-template';
|
||||
import * as EnvironmentOptions from './EnvironmentOptions';
|
||||
import {run as runHermesCompiler} from './executables/hermesc';
|
||||
import {run as runFantomTester} from './executables/tester';
|
||||
import formatFantomConfig from './formatFantomConfig';
|
||||
import getFantomTestConfigs from './getFantomTestConfigs';
|
||||
import {
|
||||
@@ -30,13 +32,8 @@ import {
|
||||
} from './snapshotUtils';
|
||||
import {
|
||||
HermesVariant as HermesVariantEnum,
|
||||
getBuckModesForPlatform,
|
||||
getBuckOptionsForHermes,
|
||||
getDebugInfoFromCommandResult,
|
||||
getHermesCompilerTarget,
|
||||
printConsoleLog,
|
||||
runBuck2,
|
||||
runBuck2Sync,
|
||||
runCommand,
|
||||
symbolicateStackTrace,
|
||||
} from './utils';
|
||||
@@ -163,13 +160,8 @@ function generateBytecodeBundle({
|
||||
isOptimizedMode: boolean,
|
||||
hermesVariant: HermesVariant,
|
||||
}): void {
|
||||
const hermesCompilerCommandResult = runBuck2Sync(
|
||||
const hermesCompilerCommandResult = runHermesCompiler(
|
||||
[
|
||||
'run',
|
||||
...getBuckModesForPlatform(isOptimizedMode),
|
||||
...getBuckOptionsForHermes(hermesVariant),
|
||||
getHermesCompilerTarget(hermesVariant),
|
||||
'--',
|
||||
'-emit-binary',
|
||||
isOptimizedMode ? '-O' : null,
|
||||
'-max-diagnostic-width',
|
||||
@@ -178,6 +170,10 @@ function generateBytecodeBundle({
|
||||
bytecodePath,
|
||||
sourcePath,
|
||||
].filter(Boolean),
|
||||
{
|
||||
isOptimizedMode,
|
||||
hermesVariant,
|
||||
},
|
||||
);
|
||||
|
||||
if (hermesCompilerCommandResult.status !== 0) {
|
||||
@@ -342,19 +338,10 @@ module.exports = async function runTest(
|
||||
path.join(__dirname, '..', 'build', 'tester', 'fantom_tester'),
|
||||
rnTesterCommandArgs,
|
||||
)
|
||||
: runBuck2(
|
||||
[
|
||||
'run',
|
||||
...getBuckModesForPlatform(testConfig.isNativeOptimized),
|
||||
...getBuckOptionsForHermes(testConfig.hermesVariant),
|
||||
'//xplat/js/react-native-github/private/react-native-fantom/tester:tester',
|
||||
'--',
|
||||
...rnTesterCommandArgs,
|
||||
],
|
||||
{
|
||||
withFDB: EnvironmentOptions.debugCpp,
|
||||
},
|
||||
);
|
||||
: runFantomTester(rnTesterCommandArgs, {
|
||||
isOptimizedMode: testConfig.isNativeOptimized,
|
||||
hermesVariant: testConfig.hermesVariant,
|
||||
});
|
||||
|
||||
const processedResult = await processRNTesterCommandResult(
|
||||
rnTesterCommandResult,
|
||||
|
||||
+15
-10
@@ -96,7 +96,7 @@ export type SyncCommandResult = {
|
||||
stderr: string,
|
||||
};
|
||||
|
||||
function maybeLogCommand(command: string, args: Array<string>): void {
|
||||
function maybeLogCommand(command: string, args: $ReadOnlyArray<string>): void {
|
||||
if (EnvironmentOptions.logCommands) {
|
||||
console.log(`RUNNING \`${command} ${args.join(' ')}\``);
|
||||
}
|
||||
@@ -104,17 +104,22 @@ function maybeLogCommand(command: string, args: Array<string>): void {
|
||||
|
||||
export function runCommand(
|
||||
command: string,
|
||||
args: Array<string>,
|
||||
args: $ReadOnlyArray<string>,
|
||||
): AsyncCommandResult {
|
||||
maybeLogCommand(command, args);
|
||||
|
||||
const childProcess = spawn(command, args, {
|
||||
encoding: 'utf8',
|
||||
env: {
|
||||
...process.env,
|
||||
PATH: `/usr/local/bin:${process.env.PATH ?? ''}`,
|
||||
const childProcess = spawn(
|
||||
command,
|
||||
// spawn is typed with Array instead of with $ReadOnlyArray
|
||||
[...args],
|
||||
{
|
||||
encoding: 'utf8',
|
||||
env: {
|
||||
...process.env,
|
||||
PATH: `/usr/local/bin:${process.env.PATH ?? ''}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
const result: AsyncCommandResult = {
|
||||
childProcess,
|
||||
@@ -143,11 +148,11 @@ export function runCommand(
|
||||
|
||||
export function runCommandSync(
|
||||
command: string,
|
||||
args: Array<string>,
|
||||
args: $ReadOnlyArray<string>,
|
||||
): SyncCommandResult {
|
||||
maybeLogCommand(command, args);
|
||||
|
||||
const result = spawnSync(command, args, {
|
||||
const result = spawnSync(command, [...args], {
|
||||
encoding: 'utf8',
|
||||
env: {
|
||||
...process.env,
|
||||
|
||||
Reference in new issue
Block a user