diff --git a/packages/react-native/ReactCommon/react/renderer/debug/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/debug/CMakeLists.txt index 165a79e5c53f2a..a1fccffe2bf8d6 100644 --- a/packages/react-native/ReactCommon/react/renderer/debug/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/debug/CMakeLists.txt @@ -15,3 +15,10 @@ target_include_directories(react_renderer_debug PUBLIC ${REACT_COMMON_DIR}) target_link_libraries(react_renderer_debug folly_runtime react_debug) target_compile_reactnative_options(react_renderer_debug PRIVATE) target_compile_options(react_renderer_debug PRIVATE -Wpedantic) + +# Enable debug string convertible for Debug builds or when explicitly requested +# This allows getRenderedOutput() to include props in tests +# Set RN_ENABLE_DEBUG_STRING_CONVERTIBLE=ON to enable for Release builds +if(${CMAKE_BUILD_TYPE} MATCHES Debug OR RN_ENABLE_DEBUG_STRING_CONVERTIBLE) + target_compile_definitions(react_renderer_debug PUBLIC RN_ENABLE_DEBUG_STRING_CONVERTIBLE) +endif() diff --git a/packages/react-native/ReactCommon/react/renderer/debug/flags.h b/packages/react-native/ReactCommon/react/renderer/debug/flags.h index f7e2002f920287..4fd6f16eaaa0d7 100644 --- a/packages/react-native/ReactCommon/react/renderer/debug/flags.h +++ b/packages/react-native/ReactCommon/react/renderer/debug/flags.h @@ -29,8 +29,9 @@ // #define RN_SHADOW_TREE_INTROSPECTION 1 // This enables certain object-to-string debug conversions to be compiled. -// Enable if `REACT_NATIVE_DEBUG` is enabled. -#ifdef REACT_NATIVE_DEBUG +// Enable if either `REACT_NATIVE_DEBUG` or `RN_ENABLE_DEBUG_STRING_CONVERTIBLE` +// is defined +#if defined(REACT_NATIVE_DEBUG) || defined(RN_ENABLE_DEBUG_STRING_CONVERTIBLE) #define RN_DEBUG_STRING_CONVERTIBLE 1 #else #define RN_DEBUG_STRING_CONVERTIBLE 0 diff --git a/private/react-native-fantom/build.gradle.kts b/private/react-native-fantom/build.gradle.kts index d40e66f97c2939..3bf026783050ee 100644 --- a/private/react-native-fantom/build.gradle.kts +++ b/private/react-native-fantom/build.gradle.kts @@ -188,6 +188,7 @@ val configureFantomTester by "-DREACT_COMMON_DIR=$reactNativeDir/ReactCommon", "-DREACT_CXX_PLATFORM_DIR=$reactNativeDir/ReactCxxPlatform", "-DREACT_THIRD_PARTY_NDK_DIR=$reactAndroidBuildDir/third-party-ndk", + "-DRN_ENABLE_DEBUG_STRING_CONVERTIBLE=ON", ) commandLine(cmdArgs) standardOutputFile.set(project.file("$buildDir/reports/configure-fantom_tester.log")) diff --git a/private/react-native-fantom/runner/executables/hermesc.js b/private/react-native-fantom/runner/executables/hermesc.js index 9d43d643be7322..d53ee02c8a8a07 100644 --- a/private/react-native-fantom/runner/executables/hermesc.js +++ b/private/react-native-fantom/runner/executables/hermesc.js @@ -15,6 +15,7 @@ import {NATIVE_BUILD_OUTPUT_PATH} from '../paths'; import { getBuckModesForPlatform, getBuckOptionsForHermes, + getConfigForAnimationBackend, getDebugInfoFromCommandResult, getHermesCompilerTarget, runBuck2Sync, @@ -23,23 +24,23 @@ import { import fs from 'fs'; import path from 'path'; -type TesterOptions = $ReadOnly<{ - isOptimizedMode: boolean, +type HermescOptions = $ReadOnly<{ + enableCoverage: boolean, + enableRelease: boolean, hermesVariant: HermesVariant, }>; -function getHermesCompilerPath({ - isOptimizedMode, +function getHermescPath({ + enableRelease, hermesVariant, -}: TesterOptions): string { +}: HermescOptions): string { return path.join( NATIVE_BUILD_OUTPUT_PATH, - `hermesc-${(hermesVariant as string).toLowerCase()}-${isOptimizedMode ? 'opt' : 'dev'}`, + `hermesc-${(hermesVariant as string).toLowerCase()}-${enableRelease ? 'opt' : 'dev'}`, ); } - -export function build(options: TesterOptions): void { - const destPath = getHermesCompilerPath(options); +export function build(options: HermescOptions): void { + const destPath = getHermescPath(options); if (fs.existsSync(destPath)) { return; } @@ -49,8 +50,12 @@ export function build(options: TesterOptions): void { try { const result = runBuck2Sync([ 'build', - ...getBuckModesForPlatform(options.isOptimizedMode), + ...getBuckModesForPlatform({ + enableRelease: options.enableRelease, + enableCoverage: options.enableCoverage, + }), ...getBuckOptionsForHermes(options.hermesVariant), + ...getConfigForAnimationBackend(), getHermesCompilerTarget(options.hermesVariant), '--out', tmpPath, @@ -75,11 +80,11 @@ export function build(options: TesterOptions): void { export function run( args: $ReadOnlyArray, - options: TesterOptions, + options: HermescOptions, ): SyncCommandResult { if (!isCI) { build(options); } - return runCommandSync(getHermesCompilerPath(options), args); + return runCommandSync(getHermescPath(options), args); } diff --git a/private/react-native-fantom/runner/executables/tester.js b/private/react-native-fantom/runner/executables/tester.js index 2e173a41de9d48..ce5a32794fccc9 100644 --- a/private/react-native-fantom/runner/executables/tester.js +++ b/private/react-native-fantom/runner/executables/tester.js @@ -28,17 +28,18 @@ const FANTOM_TESTER_BUCK_TARGET = 'fbsource//xplat/js/react-native-github/private/react-native-fantom/tester:tester'; type TesterOptions = $ReadOnly<{ - isOptimizedMode: boolean, + enableCoverage: boolean, + enableRelease: boolean, hermesVariant: HermesVariant, }>; function getFantomTesterPath({ - isOptimizedMode, hermesVariant, + ...options }: TesterOptions): string { return path.join( NATIVE_BUILD_OUTPUT_PATH, - `fantom-tester-${(hermesVariant as string).toLowerCase()}-${isOptimizedMode ? 'opt' : 'dev'}`, + `fantom-tester-${(hermesVariant as string).toLowerCase()}-${options.enableRelease ? 'opt' : 'dev'}`, ); } @@ -51,15 +52,23 @@ export function build(options: TesterOptions): void { const tmpPath = destPath + '-' + Date.now(); try { - const result = runBuck2Sync([ - 'build', - ...getBuckModesForPlatform(options.isOptimizedMode), - ...getBuckOptionsForHermes(options.hermesVariant), - ...getConfigForAnimationBackend(), - FANTOM_TESTER_BUCK_TARGET, - '--out', - tmpPath, - ]); + const result = runBuck2Sync( + [ + 'build', + ...getBuckModesForPlatform({ + enableRelease: options.enableRelease, + enableCoverage: options.enableCoverage, + }), + ...getBuckOptionsForHermes(options.hermesVariant), + ...getConfigForAnimationBackend(), + FANTOM_TESTER_BUCK_TARGET, + '--out', + tmpPath, + ], + { + withFDB: false, + }, + ); if (result.status !== 0) { throw new Error(getDebugInfoFromCommandResult(result)); @@ -94,7 +103,10 @@ export function run( return runBuck2( [ 'run', - ...getBuckModesForPlatform(options.isOptimizedMode), + ...getBuckModesForPlatform({ + enableRelease: options.enableRelease, + enableCoverage: options.enableCoverage, + }), ...getBuckOptionsForHermes(options.hermesVariant), ...getConfigForAnimationBackend(), FANTOM_TESTER_BUCK_TARGET, diff --git a/private/react-native-fantom/runner/global-setup/build.js b/private/react-native-fantom/runner/global-setup/build.js index 929984ab0eb435..647cef09ad20aa 100644 --- a/private/react-native-fantom/runner/global-setup/build.js +++ b/private/react-native-fantom/runner/global-setup/build.js @@ -37,7 +37,7 @@ async function tryOrLog( } } -export default async function build(): Promise { +export default async function build(enableCoverage: boolean): Promise { try { fs.rmSync(NATIVE_BUILD_OUTPUT_PATH, {recursive: true}); } catch {} @@ -45,10 +45,10 @@ export default async function build(): Promise { fs.mkdirSync(NATIVE_BUILD_OUTPUT_PATH, {recursive: true}); if (isCI) { - for (const isOptimizedMode of [false, true]) { + for (const enableRelease of [false, true]) { for (const hermesVariant of HermesVariant.members()) { - buildFantomTester({isOptimizedMode, hermesVariant}); - buildHermesCompiler({isOptimizedMode, hermesVariant}); + buildFantomTester({enableRelease, hermesVariant, enableCoverage}); + buildHermesCompiler({enableRelease, hermesVariant, enableCoverage}); } } diff --git a/private/react-native-fantom/runner/global-setup/globalSetup.js b/private/react-native-fantom/runner/global-setup/globalSetup.js index d357d839ad5370..6cd629388c8380 100644 --- a/private/react-native-fantom/runner/global-setup/globalSetup.js +++ b/private/react-native-fantom/runner/global-setup/globalSetup.js @@ -19,7 +19,10 @@ import {Server} from 'net'; import path from 'path'; export default async function globalSetup( - globalConfig: {...}, + globalConfig: { + collectCoverage: boolean, + ... + }, projectConfig: {...}, ): Promise { process.env.__FANTOM_RUN_ID__ ??= `run-${Date.now()}`; @@ -29,7 +32,7 @@ export default async function globalSetup( await startMetroServer(); if (!isOSS) { - await build(); + await build(globalConfig.collectCoverage); } } diff --git a/private/react-native-fantom/runner/paths.js b/private/react-native-fantom/runner/paths.js index 43bacc1d7c5a01..f78b726f7a4364 100644 --- a/private/react-native-fantom/runner/paths.js +++ b/private/react-native-fantom/runner/paths.js @@ -30,6 +30,8 @@ export const JS_HEAP_SNAPSHOTS_OUTPUT_PATH: string = path.join( 'js-heap-snapshots', ); +export const COVERAGE_OUTPUT_PATH: string = path.join(OUTPUT_PATH, 'coverage'); + export function getTestBuildOutputPath(): string { const fantomRunID = process.env.__FANTOM_RUN_ID__; if (fantomRunID == null) { @@ -41,6 +43,16 @@ export function getTestBuildOutputPath(): string { return path.join(JS_BUILD_OUTPUT_PATH, fantomRunID); } +export function getCoverageFilePath(): string { + const fantomRunID = process.env.__FANTOM_RUN_ID__; + if (fantomRunID == null) { + throw new Error( + 'Expected Fantom run ID to be set by global setup, but it was not (process.env.__FANTOM_RUN_ID__ is null)', + ); + } + return `${COVERAGE_OUTPUT_PATH}/${fantomRunID}.profraw`; +} + export function buildJSTracesOutputPath({ testPath, testConfig, diff --git a/private/react-native-fantom/runner/runner.js b/private/react-native-fantom/runner/runner.js index 1291f6843317c5..6a3fbca73a9459 100644 --- a/private/react-native-fantom/runner/runner.js +++ b/private/react-native-fantom/runner/runner.js @@ -32,6 +32,7 @@ import {run as runFantomTester} from './executables/tester'; import formatFantomConfig from './formatFantomConfig'; import getFantomTestConfigs from './getFantomTestConfigs'; import { + COVERAGE_OUTPUT_PATH, JS_HEAP_SNAPSHOTS_OUTPUT_PATH, JS_TRACES_OUTPUT_PATH, buildJSHeapSnapshotsOutputPathTemplate, @@ -66,6 +67,8 @@ if (EnvironmentOptions.profileJS) { fs.mkdirSync(JS_TRACES_OUTPUT_PATH, {recursive: true}); } +fs.mkdirSync(COVERAGE_OUTPUT_PATH, {recursive: true}); + function buildError( failureDetail: FailureDetail, sourceMapPath: string, @@ -163,18 +166,20 @@ async function processRNTesterCommandResult( function generateBytecodeBundle({ sourcePath, bytecodePath, - isOptimizedMode, + enableRelease, hermesVariant, + enableCoverage, }: { - sourcePath: string, bytecodePath: string, - isOptimizedMode: boolean, + enableCoverage: boolean, + enableRelease: boolean, hermesVariant: HermesVariant, + sourcePath: string, }): void { const hermesCompilerCommandResult = runHermesCompiler( [ '-emit-binary', - isOptimizedMode ? '-O' : null, + enableRelease ? '-O' : null, '-max-diagnostic-width', '80', '-out', @@ -182,8 +187,9 @@ function generateBytecodeBundle({ sourcePath, ].filter(Boolean), { - isOptimizedMode, + enableRelease, hermesVariant, + enableCoverage, }, ); @@ -335,6 +341,12 @@ module.exports = async function runTest( path.basename(testJSBundlePath, '.js') + '.map', ); + const collectCoverage = shouldCollectCoverage( + testPath, + testContents, + globalConfig, + ); + const bundleOptions = { testPath, entry: entrypointPath, @@ -344,11 +356,7 @@ module.exports = async function runTest( sourceMap: true, sourceMapUrl: sourceMapPath, customTransformOptions: { - collectCoverage: shouldCollectCoverage( - testPath, - testContents, - globalConfig, - ), + collectCoverage, }, }; @@ -361,7 +369,8 @@ module.exports = async function runTest( generateBytecodeBundle({ sourcePath: testJSBundlePath, bytecodePath: testBytecodeBundlePath, - isOptimizedMode: testConfig.isJsOptimized, + enableCoverage: collectCoverage, + enableRelease: testConfig.isJsOptimized, hermesVariant: testConfig.hermesVariant, }); } @@ -388,8 +397,9 @@ module.exports = async function runTest( rnTesterCommandArgs, ) : runFantomTester(rnTesterCommandArgs, { - isOptimizedMode: testConfig.isNativeOptimized, + enableRelease: testConfig.isNativeOptimized, hermesVariant: testConfig.hermesVariant, + enableCoverage: collectCoverage, }); const [processedResult, benchmarkResult] = diff --git a/private/react-native-fantom/runner/utils.js b/private/react-native-fantom/runner/utils.js index 495b2e5ecacc9a..ea2b4bd4c7d446 100644 --- a/private/react-native-fantom/runner/utils.js +++ b/private/react-native-fantom/runner/utils.js @@ -9,6 +9,7 @@ */ import * as EnvironmentOptions from './EnvironmentOptions'; +import {getCoverageFilePath} from './paths'; import {spawn, spawnSync} from 'child_process'; import fs from 'fs'; import os from 'os'; @@ -51,10 +52,14 @@ export function getHermesCompilerTarget(variant: HermesVariant): string { } } -export function getBuckModesForPlatform( - enableRelease: boolean = false, -): $ReadOnlyArray { - let mode = enableRelease ? 'opt' : 'dev'; +export function getBuckModesForPlatform({ + enableCoverage, + enableRelease, +}: { + enableCoverage: boolean, + enableRelease: boolean, +}): $ReadOnlyArray { + let mode = enableCoverage ? 'code-coverage' : enableRelease ? 'opt' : 'dev'; if (enableRelease) { if (EnvironmentOptions.enableASAN || EnvironmentOptions.enableTSAN) { @@ -151,6 +156,7 @@ export function runCommand( encoding: 'utf8', env: { ...process.env, + LLVM_PROFILE_FILE: getCoverageFilePath(), PATH: `/usr/local/bin:/usr/bin:${process.env.PATH ?? ''}`, }, }, @@ -191,6 +197,7 @@ export function runCommandSync( encoding: 'utf8', env: { ...process.env, + LLVM_PROFILE_FILE: getCoverageFilePath(), PATH: `/usr/local/bin:/usr/bin:${process.env.PATH ?? ''}`, }, });