From df36c96ccc0944c281b510c06e522f94e75c70be Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Fri, 31 Jul 2026 12:46:02 -0700 Subject: [PATCH] [RN] Run ReactCommon C++ unit tests on GitHub CI ## Summary ReactCommon's C++ unit tests (`/tests/*.cpp`) ship with the repository but were not built or run by CI, so the renderer, layout, and runtime C++ core was only exercised indirectly (integration tests and platform builds). This adds a small CMake harness under `private/react-native-tests` that compiles a representative subset of those gtest suites on Linux and runs them with CTest, reusing the desktop C++ toolchain and Gradle-staged third-party dependencies already used by the Fantom tester. A new `test_cxx` GitHub Actions job builds and runs the harness on a standard Linux runner. Suites covered initially: `react/renderer/graphics`, `react/utils`, `react/renderer/css`; more can be added incrementally (see `private/react-native-tests/README.md`). The CI job is advisory (`continue-on-error`) so a harness issue cannot block merges, and should be promoted to a required check once consistently green. ## Changelog [Internal] ## Test Plan Runs in the new advisory `test_cxx` CI job on this PR (Linux): builds the CMake harness and runs the gtest suites via CTest. --- .github/actions/run-cxx-tests/action.yml | 74 +++ .github/workflows/test-all.yml | 25 + private/react-native-tests/README.md | 79 ++++ private/react-native-tests/build.gradle.kts | 139 ++++++ private/react-native-tests/build.sh | 11 + .../react-native-tests/codegen/CMakeLists.txt | 43 ++ private/react-native-tests/cxx/CMakeLists.txt | 442 ++++++++++++++++++ .../cxx/cmake/modules/Deps.cmake | 44 ++ private/react-native-tests/package.json | 10 + private/react-native-tests/test.sh | 11 + settings.gradle.kts | 1 + 11 files changed, 879 insertions(+) create mode 100644 .github/actions/run-cxx-tests/action.yml create mode 100644 private/react-native-tests/README.md create mode 100644 private/react-native-tests/build.gradle.kts create mode 100755 private/react-native-tests/build.sh create mode 100644 private/react-native-tests/codegen/CMakeLists.txt create mode 100644 private/react-native-tests/cxx/CMakeLists.txt create mode 100644 private/react-native-tests/cxx/cmake/modules/Deps.cmake create mode 100644 private/react-native-tests/package.json create mode 100755 private/react-native-tests/test.sh diff --git a/.github/actions/run-cxx-tests/action.yml b/.github/actions/run-cxx-tests/action.yml new file mode 100644 index 000000000000..29b548526f63 --- /dev/null +++ b/.github/actions/run-cxx-tests/action.yml @@ -0,0 +1,74 @@ +name: Run C++ Tests +description: Builds and runs the ReactCommon C++ (gtest) unit-test harness on Linux +inputs: + gradle-cache-encryption-key: + description: 'The encryption key needed to store the Gradle Configuration cache' +runs: + using: composite + steps: + - name: Install dependencies + shell: bash + run: | + sudo apt update + sudo apt install -y git cmake openssl libssl-dev clang + - name: Setup git safe folders + shell: bash + run: git config --global --add safe.directory '*' + - name: Setup node.js + uses: ./.github/actions/setup-node + - name: Install node dependencies + uses: ./.github/actions/yarn-install + - name: Setup gradle + uses: ./.github/actions/setup-gradle + with: + cache-read-only: 'false' + cache-encryption-key: ${{ inputs.gradle-cache-encryption-key }} + - name: Restore C++ tests ccache + uses: actions/cache/restore@v5 + with: + path: /github/home/.cache/ccache + key: + v1-ccache-cxx-tests-${{ github.job }}-${{ github.ref }}-${{ hashFiles( + 'packages/react-native/ReactCommon/**/*.cpp', + 'packages/react-native/ReactCommon/**/*.h', + 'packages/react-native/ReactCommon/**/CMakeLists.txt' + ) }} + restore-keys: | + v1-ccache-cxx-tests-${{ github.job }}-${{ github.ref }}- + v1-ccache-cxx-tests-${{ github.job }}- + v1-ccache-cxx-tests- + - name: Show ccache stats (before) + shell: bash + run: ccache -s -v + - name: Build and run C++ tests + shell: bash + run: yarn workspace @react-native/tests-cxx test + env: + CC: clang + CXX: clang++ + # libhermesvm.so is a shared library; gtest_discover_tests runs the + # binaries at build time and CTest runs them again, so it must be + # loadable in both phases. + LD_LIBRARY_PATH: ${{ github.workspace }}/packages/react-native/ReactAndroid/hermes-engine/build/hermes/lib + - name: Save C++ tests ccache + if: ${{ github.ref == 'refs/heads/main' || contains(github.ref, '-stable') }} + uses: actions/cache/save@v5 + with: + path: /github/home/.cache/ccache + key: + v1-ccache-cxx-tests-${{ github.job }}-${{ github.ref }}-${{ hashFiles( + 'packages/react-native/ReactCommon/**/*.cpp', + 'packages/react-native/ReactCommon/**/*.h', + 'packages/react-native/ReactCommon/**/CMakeLists.txt' + ) }} + - name: Show ccache stats (after) + shell: bash + run: ccache -s -v + - name: Upload test results + if: ${{ always() }} + uses: actions/upload-artifact@v6 + with: + name: run-cxx-tests-results + compression-level: 1 + path: | + private/react-native-tests/build/reports diff --git a/.github/workflows/test-all.yml b/.github/workflows/test-all.yml index 3b1a45e6bd39..003a364927c2 100644 --- a/.github/workflows/test-all.yml +++ b/.github/workflows/test-all.yml @@ -300,6 +300,31 @@ jobs: fail-on-error: true secrets: inherit + test_cxx: + runs-on: 8-core-ubuntu + needs: [check_code_changes, lint] + if: needs.check_code_changes.outputs.any_code_change == 'true' + # Advisory during rollout: a harness issue must not block merges. Once this + # job is consistently green, drop continue-on-error and add it to branch + # protection so it becomes a required merge gate. + continue-on-error: true + container: + image: reactnativecommunity/react-native-android:latest + env: + # Set the encoding to resolve a known character encoding issue with decompressing tar.gz files in containers + # via Gradle: https://github.com/gradle/gradle/issues/23391#issuecomment-1878979127 + LC_ALL: C.UTF8 + TERM: 'dumb' + GRADLE_OPTS: '-Dorg.gradle.daemon=false' + REACT_NATIVE_DOWNLOADS_DIR: /opt/react-native-downloads + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Run C++ Tests + uses: ./.github/actions/run-cxx-tests + with: + gradle-cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} + build_android: runs-on: 8-core-ubuntu needs: [set_release_type, check_code_changes] diff --git a/private/react-native-tests/README.md b/private/react-native-tests/README.md new file mode 100644 index 000000000000..40146b97dc47 --- /dev/null +++ b/private/react-native-tests/README.md @@ -0,0 +1,79 @@ +# @react-native/tests-cxx + +A Linux + CMake harness that builds and runs a **representative subset** of +ReactCommon's C++ (gtest) unit tests on GitHub CI. + +## Why this exists + +ReactCommon's C++ test sources (`/tests/*.cpp`) are part of the +repository, but the build rules that compile and run them are Meta-internal and +are not part of the open-source tree. As a result these unit tests did not run +on GitHub CI at all — the renderer, layout, and runtime C++ core was only +covered by higher-level integration tests (Fantom) and by platform builds. + +This harness closes that gap cheaply: C++ unit tests build and run on standard +(free) Linux runners, so GitHub CI can gate merges on them. + +The goal is a **representative**, high-signal subset — not full coverage. Suites +are added deliberately, weighted by how often they catch regressions and by +build cost. + +## How it works + +The harness reuses the exact desktop C++ toolchain already proven by the Fantom +tester (`private/react-native-fantom`): + +- **Gradle** (`build.gradle.kts`) stages the third-party dependencies (folly, + boost, glog, double-conversion, fast_float, fmt, gflags) by depending on the + Fantom tester's `prepareNative3pDependencies`, then invokes CMake. +- **CMake** (`cxx/CMakeLists.txt`) compiles each in-scope ReactCommon subsystem + into its library and links its `tests/*.cpp` into a per-subsystem gtest binary, + registered with CTest. GoogleTest itself is fetched via `FetchContent`. + +## Coverage + +30 suites (~900 gtest cases) run today, spanning the C++ core: + +- **Primitives / parsing / serialization:** graphics, css, utils, mapbuffer, + timing, featureflags +- **Fabric renderer:** renderer/core, mounting, components/{view, text, + scrollview, image, root}, attributedstring, textlayoutmanager, element, + componentregistry (via deps), imagemanager, renderer/debug +- **Scheduling / runtime:** runtimescheduler, scheduler, uimanager, + uimanager/consistency, performance/timeline, animated, animations +- **Debugger / infra:** debug/redbox, reactperflogger/fusebox, jserrorhandler, + telemetry, cxxreact + +Suites for `renderer/core`, `runtimescheduler`, `scheduler` and `animated` +create a JS runtime and link the Hermes VM. + +Not yet included: `jsinspector-modern` and `jsinspector-modern/tracing` (their +tests depend on a specific GoogleTest version / `std::source_location` support +that this harness's GoogleTest doesn't match), and `react/bridging` (its test +helper header uses a Buck header-namespace that doesn't map to CMake). + +## Running locally (Meta-internal) + +Requires the Android SDK's CMake and the same environment used to build the +Fantom tester. + +```sh +yarn workspace @react-native/tests-cxx test # build + run (ctest) +yarn workspace @react-native/tests-cxx build # build only +``` + +## Adding a subsystem + +1. Add the subsystem and any missing dependencies to the + `add_react_common_subdir(...)` list in `cxx/CMakeLists.txt`. +2. Add a `react_native_add_cxx_test_suite(...)` call listing the subsystem's + full library closure (subsystem lib + its ReactCommon deps + third-party). + ReactCommon libraries are CMake `OBJECT` libraries, so the closure must be + listed explicitly rather than relying on transitive linking. +3. Confirm the new suite builds and passes in the `test_cxx` CI job. + +## Rollout + +The `test_cxx` GitHub Actions job starts **advisory** (`continue-on-error`) so a +harness issue cannot block merges. Once it is consistently green it should be +made a **required** check via branch protection. diff --git a/private/react-native-tests/build.gradle.kts b/private/react-native-tests/build.gradle.kts new file mode 100644 index 000000000000..e60212df0857 --- /dev/null +++ b/private/react-native-tests/build.gradle.kts @@ -0,0 +1,139 @@ +/* + * 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. + */ + +import com.facebook.react.tasks.internal.* +import com.facebook.react.tasks.internal.utils.* + +plugins { id("com.facebook.react") } + +// CMake shipped with the Android SDK (same version the Fantom tester uses). If +// missing it is downloaded automatically by the Android SDK manager. +val cmakeVersion = System.getenv("CMAKE_VERSION") ?: "3.30.5" +val cmakePath = "${getSDKPath()}/cmake/$cmakeVersion" +val cmakeBinaryPath = "${cmakePath}/bin/cmake" +val ctestBinaryPath = "${cmakePath}/bin/ctest" +val buildJobs = Runtime.getRuntime().availableProcessors().toString() + +fun getSDKPath(): String { + val androidSdkRoot = System.getenv("ANDROID_SDK_ROOT") + val androidHome = System.getenv("ANDROID_HOME") + return when { + !androidSdkRoot.isNullOrBlank() -> androidSdkRoot + !androidHome.isNullOrBlank() -> androidHome + else -> throw IllegalStateException("Neither ANDROID_SDK_ROOT nor ANDROID_HOME is set.") + } +} + +val buildDir = project.layout.buildDirectory.get().asFile +val reportsDir = File("$buildDir/reports") +val reactNativeRootDir = projectDir.parentFile.parentFile +val reactNativeDir = File("$reactNativeRootDir/packages/react-native") +val reactAndroidDir = File("$reactNativeDir/ReactAndroid") +val reactAndroidBuildDir = File("$reactAndroidDir/build") + +// The C++ test harness reuses the third-party dependencies staged by the Fantom +// tester build: folly and gflags land in /build/third-party; glog, +// double-conversion, fast_float, fmt and boost land in +// ReactAndroid/build/third-party-ndk. Depending on Fantom's +// prepareNative3pDependencies guarantees all of them are prepared. +val fantomDir = File("$reactNativeRootDir/private/react-native-fantom") +val stagedThirdPartyDir = File("$fantomDir/build/third-party") +val testerThirdPartySrcDir = File("$fantomDir/tester/third-party") + +val cxxDir = File("$projectDir/cxx") +val cxxBuildDir = File("$buildDir/cxx") +val cxxBuildOutputFileTree = + fileTree(cxxBuildDir.toString()) + .include("**/*.cmake", "**/*.marks", "**/compiler_depends.ts", "**/Makefile", "**/link.txt") + +val createReportsDir by tasks.registering { reportsDir.mkdirs() } + +// Generated codegen sources (FBReactNativeSpec) that Fabric component tests +// depend on. `generateCodegenArtifactsFromSchema` produces them under +// ReactAndroid/build/generated; stage them next to codegen/CMakeLists.txt. +val codegenSrcDir = File("$reactAndroidBuildDir/generated/source/codegen/jni") +val codegenOutDir = File("$buildDir/codegen") +val prepareRNCodegen by + tasks.registering(Copy::class) { + dependsOn(":packages:react-native:ReactAndroid:generateCodegenArtifactsFromSchema") + from(codegenSrcDir) + from("codegen") + include("react/**/*.h", "react/**/*.cpp", "CMakeLists.txt") + includeEmptyDirs = false + duplicatesStrategy = DuplicatesStrategy.INCLUDE + into(codegenOutDir) + } + +// Hermes VM + prefab headers, for suites whose tests create a JS runtime +// (hermes::makeHermesRuntime). Mirrors the Fantom tester's Hermes setup. +val enableHermesBuild by tasks.registering { + project(":packages:react-native:ReactAndroid:hermes-engine") { + tasks.configureEach { enabled = true } + } +} +val prepareHermesDependencies by tasks.registering { + dependsOn( + enableHermesBuild, + ":packages:react-native:ReactAndroid:hermes-engine:buildHermesLibWithDebugger", + ":packages:react-native:ReactAndroid:hermes-engine:prepareHeadersForPrefabWithDebugger", + ) +} + +val configureCxxTests by + tasks.registering(CustomExecTask::class) { + dependsOn( + createReportsDir, + prepareRNCodegen, + prepareHermesDependencies, + ":private:react-native-fantom:prepareNative3pDependencies", + ) + workingDir(cxxDir) + inputs.dir(cxxDir) + outputs.files(cxxBuildOutputFileTree) + commandLine( + cmakeBinaryPath, + "--log-level=ERROR", + "-S", + ".", + "-B", + cxxBuildDir.toString(), + "-DCMAKE_BUILD_TYPE=Debug", + "-DREACT_ANDROID_DIR=$reactAndroidDir", + "-DREACT_COMMON_DIR=$reactNativeDir/ReactCommon", + "-DREACT_THIRD_PARTY_NDK_DIR=$reactAndroidBuildDir/third-party-ndk", + "-DRN_STAGED_THIRD_PARTY_DIR=$stagedThirdPartyDir", + "-DRN_TESTER_THIRD_PARTY_SRC_DIR=$testerThirdPartySrcDir", + "-DRN_CODEGEN_DIR=$codegenOutDir", + "-DRN_ENABLE_DEBUG_STRING_CONVERTIBLE=ON", + ) + standardOutputFile.set(project.file("$buildDir/reports/configure-cxx-tests.log")) + errorOutputFile.set(project.file("$buildDir/reports/configure-cxx-tests.error.log")) + } + +val buildCxxTests by + tasks.registering(CustomExecTask::class) { + dependsOn(configureCxxTests) + workingDir(cxxDir) + inputs.files(cxxBuildOutputFileTree) + commandLine(cmakeBinaryPath, "--build", cxxBuildDir.toString(), "-j", buildJobs) + standardOutputFile.set(project.file("$buildDir/reports/build-cxx-tests.log")) + errorOutputFile.set(project.file("$buildDir/reports/build-cxx-tests.error.log")) + } + +val runCxxTests by + tasks.registering(CustomExecTask::class) { + dependsOn(buildCxxTests) + workingDir(cxxBuildDir) + commandLine( + ctestBinaryPath, + "--output-on-failure", + "--output-junit", + "$reportsDir/cxx-tests-results.xml", + ) + standardOutputFile.set(project.file("$buildDir/reports/run-cxx-tests.log")) + errorOutputFile.set(project.file("$buildDir/reports/run-cxx-tests.error.log")) + } diff --git a/private/react-native-tests/build.sh b/private/react-native-tests/build.sh new file mode 100755 index 000000000000..86916da52437 --- /dev/null +++ b/private/react-native-tests/build.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# 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. + +set -e + +pushd ../.. +./gradlew :private:react-native-tests:buildCxxTests +popd diff --git a/private/react-native-tests/codegen/CMakeLists.txt b/private/react-native-tests/codegen/CMakeLists.txt new file mode 100644 index 000000000000..2bfc4a2097d1 --- /dev/null +++ b/private/react-native-tests/codegen/CMakeLists.txt @@ -0,0 +1,43 @@ +# 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. + +# Builds the generated codegen sources (FBReactNativeSpec) that Fabric component +# tests depend on. Populated by the Gradle `prepareRNCodegen` task, which runs +# `generateCodegenArtifactsFromSchema` and stages the generated headers/sources +# next to this file. Mirrors private/react-native-fantom/tester/codegen. + +cmake_minimum_required(VERSION 3.13) +set(CMAKE_VERBOSE_MAKEFILE on) + +file(GLOB react_codegen_SRCS CONFIGURE_DEPENDS react/renderer/components/FBReactNativeSpec/*.cpp) + +add_library( + react_codegen_rncore + OBJECT + ${react_codegen_SRCS} +) + +target_include_directories(react_codegen_rncore + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/. + ${CMAKE_CURRENT_SOURCE_DIR}/react/renderer/components/FBReactNativeSpec + ${CMAKE_CURRENT_SOURCE_DIR}/react/renderer/components) + +target_link_libraries( + react_codegen_rncore + jsi + react_nativemodule_core + rrc_view +) + +target_compile_options( + react_codegen_rncore + PRIVATE + -DLOG_TAG=\"ReactNative\" + -fexceptions + -frtti + -std=c++20 + -Wall +) diff --git a/private/react-native-tests/cxx/CMakeLists.txt b/private/react-native-tests/cxx/CMakeLists.txt new file mode 100644 index 000000000000..dc34c4d2b0a0 --- /dev/null +++ b/private/react-native-tests/cxx/CMakeLists.txt @@ -0,0 +1,442 @@ +# 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. + +# C++ unit-test harness for React Native's ReactCommon. +# +# ReactCommon's C++ test sources (`/tests/*.cpp`) ship to the OSS +# repo, but internally they build and run via Buck (`rn_xplat_cxx_test`), which +# is not exported. This harness builds a representative subset of those gtest +# suites on plain Linux with CMake so GitHub CI can run them as a merge gate. It +# reuses the desktop C++ toolchain already proven by the Fantom tester: the same +# ReactCommon libraries, the same Gradle-staged third-party dependencies, and +# the same generated codegen sources. + +cmake_minimum_required(VERSION 3.13) + +set(CMAKE_VERBOSE_MAKEFILE on) +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if(UNIX AND NOT APPLE) + set(CMAKE_POSITION_INDEPENDENT_CODE ON) + set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} -latomic") + set(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_LINK_FLAGS} -latomic") +endif() + +project(react_native_cxx_tests CXX C) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/") +include(Deps) + +# ReactCommon subsystem CMakeLists call shared helpers (react_native_android_selector, +# target_compile_reactnative_options). Include the modules that define them once, +# up front, so the functions exist for every add_subdirectory() below regardless +# of the order subsystems are added. +include(${REACT_COMMON_DIR}/cmake-utils/internal/react-native-platform-selector.cmake) +include(${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake) + +# Host-platform (cxx) header roots. In the Buck/Android build these sit on the +# global include path, so a subsystem's `platform/cxx` headers (e.g. +# HostPlatformViewProps.h) resolve even from other subsystems that include them +# transitively without linking the owner (e.g. componentregistry -> ViewProps.h). +# Expose them globally here to match that. +foreach(rn_platform_root + react/renderer/graphics + react/renderer/imagemanager + react/renderer/textlayoutmanager + react/renderer/components/view + react/renderer/components/text + react/renderer/components/scrollview + react/renderer/components/modal) + include_directories(${REACT_COMMON_DIR}/${rn_platform_root}/platform/cxx) +endforeach() + +# --------------------------------------------------------------------------- +# GoogleTest — the one dependency the runtime build does not already provide. +# Fetched at configure time (CI has network; folly et al. are downloaded too). +# --------------------------------------------------------------------------- +include(FetchContent) +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/refs/tags/v1.15.2.tar.gz) +FetchContent_MakeAvailable(googletest) + +enable_testing() +include(GoogleTest) + +# --------------------------------------------------------------------------- +# Third-party dependencies (staged by Gradle; see ../build.gradle.kts). +# --------------------------------------------------------------------------- +add_tester_third_party_subdir(boost) +add_react_third_party_ndk_subdir(glog) +add_react_third_party_ndk_subdir(double-conversion) +add_react_third_party_ndk_subdir(fast_float) +add_react_third_party_ndk_subdir(fmt) +add_staged_third_party_subdir(folly) +add_staged_third_party_subdir(gflags) + +# Shim target several ReactCommon subsystems expect to link for glog setup; the +# real initialization is platform-specific and not needed for unit tests. +add_library(glog_init INTERFACE) + +# Hermes VM + prefab headers (built by the Gradle prepareHermesDependencies task). +# Suites whose tests create a JS runtime via hermes::makeHermesRuntime link this. +# The shared libhermesvm.so must be on LD_LIBRARY_PATH at test-discovery/run time +# (set in the run-cxx-tests action). +include_directories(${REACT_ANDROID_DIR}/hermes-engine/build/prefab-headers) +find_library(LIB_HERMES hermesvm + HINTS ${REACT_ANDROID_DIR}/hermes-engine/build/hermes/lib REQUIRED) +add_library(hermes-engine::hermesvm INTERFACE IMPORTED) +set_target_properties(hermes-engine::hermesvm PROPERTIES + INTERFACE_LINK_LIBRARIES ${LIB_HERMES}) + +# Generated codegen sources (FBReactNativeSpec), staged by the Gradle +# prepareRNCodegen task. Defines react_codegen_rncore, which Fabric component +# libraries (e.g. rrc_modal) link against. +add_subdirectory(${RN_CODEGEN_DIR} codegen) + +# --------------------------------------------------------------------------- +# ReactCommon libraries under test and their transitive dependencies. +# +# Excludes Hermes/JS-runtime subsystems: the in-scope suites construct shadow +# nodes / props directly and never instantiate a JS engine. (react/renderer/core's +# own tests DO need Hermes, so that suite is intentionally not built here.) +# --------------------------------------------------------------------------- +add_react_common_subdir(jsi) +add_react_common_subdir(logger) +add_react_common_subdir(oscompat) +add_react_common_subdir(callinvoker) +add_react_common_subdir(runtimeexecutor) +add_react_common_subdir(reactperflogger) +add_react_common_subdir(react/debug) +add_react_common_subdir(react/utils) +add_react_common_subdir(react/timing) +add_react_common_subdir(react/featureflags) +add_react_common_subdir(react/bridging) +add_react_common_subdir(react/nativemodule/core) +add_react_common_subdir(jserrorhandler) +add_react_common_subdir(jsinspector-modern) +add_react_common_subdir(jsinspector-modern/cdp) +add_react_common_subdir(jsinspector-modern/network) +add_react_common_subdir(jsinspector-modern/tracing) +add_react_common_subdir(react/performance/timeline) +add_react_common_subdir(react/renderer/debug) +add_react_common_subdir(react/renderer/consistency) +add_react_common_subdir(react/renderer/graphics) +add_react_common_subdir(react/renderer/css) +add_react_common_subdir(react/renderer/mapbuffer) +add_react_common_subdir(react/renderer/runtimescheduler) +add_react_common_subdir(react/renderer/core) +add_react_common_subdir(react/renderer/attributedstring) +add_react_common_subdir(react/renderer/textlayoutmanager) +add_react_common_subdir(react/renderer/componentregistry) +add_react_common_subdir(react/renderer/componentregistry/native) +add_react_common_subdir(react/renderer/element) +add_react_common_subdir(react/renderer/components/view) +add_react_common_subdir(react/renderer/components/root) +add_react_common_subdir(react/renderer/components/scrollview) +add_react_common_subdir(react/renderer/components/text) +add_react_common_subdir(react/renderer/components/modal) +add_react_common_subdir(react/renderer/telemetry) +add_react_common_subdir(react/renderer/mounting) +add_react_common_subdir(cxxreact) +add_react_common_subdir(react/renderer/bridging) +add_react_common_subdir(react/renderer/dom) +add_react_common_subdir(react/renderer/leakchecker) +add_react_common_subdir(react/renderer/imagemanager) +add_react_common_subdir(react/renderer/uimanager) +add_react_common_subdir(react/renderer/uimanager/consistency) +add_react_common_subdir(react/renderer/components/image) +add_react_common_subdir(react/renderer/components/legacyviewmanagerinterop) +add_react_common_subdir(react/renderer/animations) +add_react_common_subdir(react/performance/cdpmetrics) +add_react_common_subdir(react/renderer/observers/events) +add_react_common_subdir(react/renderer/viewtransition) +add_react_common_subdir(react/renderer/animationbackend) +add_react_common_subdir(react/renderer/scheduler) +add_react_common_subdir(react/renderer/animated) +add_react_common_subdir(yoga) +# Several subsystems link the target name `yoga`; the subdir defines `yogacore`. +add_library(yoga ALIAS yogacore) + +# --------------------------------------------------------------------------- +# Test suites. Each entry compiles a subsystem's `tests/*.cpp` into its own +# gtest binary and registers it with CTest. +# +# ReactCommon libraries are OBJECT libraries, whose objects are not always +# pulled transitively, so callers must pass the full closure of libraries +# explicitly (subsystem lib + its ReactCommon deps + third-party), mirroring how +# the Fantom tester links. +# --------------------------------------------------------------------------- +function(react_native_add_cxx_test_suite suite_name subsystem_path) + file(GLOB suite_SRC CONFIGURE_DEPENDS + ${REACT_COMMON_DIR}/${subsystem_path}/tests/*.cpp) + add_executable(${suite_name} ${suite_SRC}) + target_include_directories(${suite_name} PRIVATE ${REACT_COMMON_DIR}) + target_link_libraries(${suite_name} + PRIVATE + ${ARGN} + gtest + gmock + gtest_main + # Linked last so its warning overrides come after the subsystem libraries' + # interface flags (-Wall -Werror -Wpedantic) and win the ordering. + rn_test_overrides) + target_compile_options(${suite_name} PRIVATE -fexceptions -frtti) + gtest_discover_tests(${suite_name}) +endfunction() + +# Warning overrides for test code. Test sources are held to -Werror in the +# internal Buck build, but for OSS CI we only care about pass/fail. An INTERFACE +# library (linked last by the helper) so these come after the subsystem +# libraries' interface -Werror/-Wpedantic and win the flag ordering. +add_library(rn_test_overrides INTERFACE) +target_compile_options(rn_test_overrides INTERFACE -Wno-error -Wno-embedded-directive) + +# Third-party libraries every suite links. +set(RN_TEST_THIRD_PARTY + folly_runtime + glog + glog_init + double-conversion + fmt + boost) + +# --- Small, self-contained suites (minimal closures) --- +react_native_add_cxx_test_suite( + react_renderer_graphics_tests + react/renderer/graphics + react_renderer_graphics + react_renderer_debug + react_utils + react_debug + jsi + ${RN_TEST_THIRD_PARTY}) + +react_native_add_cxx_test_suite( + react_utils_tests + react/utils + react_utils + react_debug + jsi + ${RN_TEST_THIRD_PARTY}) + +react_native_add_cxx_test_suite( + react_renderer_css_tests + react/renderer/css + react_renderer_css + react_utils + react_debug + jsi + fast_float + ${RN_TEST_THIRD_PARTY}) + +react_native_add_cxx_test_suite( + react_renderer_mapbuffer_tests + react/renderer/mapbuffer + react_renderer_mapbuffer + react_debug + ${RN_TEST_THIRD_PARTY}) + +# --- Fabric renderer suites (full renderer + component + codegen closure) --- +set(RN_RENDERER_LIBS + jsi + logger + oscompat + callinvoker + runtimeexecutor + reactperflogger + react_debug + react_utils + react_timing + react_featureflags + react_bridging + react_nativemodule_core + jserrorhandler + jsinspector + jsinspector_cdp + jsinspector_network + jsinspector_tracing + react_performance_timeline + react_renderer_debug + react_renderer_consistency + react_renderer_graphics + react_renderer_css + react_renderer_mapbuffer + react_renderer_runtimescheduler + react_renderer_core + react_renderer_attributedstring + react_renderer_textlayoutmanager + react_renderer_componentregistry + rrc_native + react_renderer_element + rrc_view + rrc_root + rrc_scrollview + rrc_text + rrc_modal + react_renderer_telemetry + react_renderer_mounting + react_cxxreact + react_renderer_bridging + react_renderer_dom + react_renderer_leakchecker + react_renderer_imagemanager + react_renderer_uimanager + react_renderer_uimanager_consistency + rrc_image + rrc_legacyviewmanagerinterop + react_renderer_animations + react_codegen_rncore + yogacore + fast_float + ${RN_TEST_THIRD_PARTY}) + +react_native_add_cxx_test_suite( + react_renderer_components_view_tests + react/renderer/components/view + ${RN_RENDERER_LIBS}) + +react_native_add_cxx_test_suite( + react_renderer_mounting_tests + react/renderer/mounting + ${RN_RENDERER_LIBS}) + +# More suites whose libraries are already built above and whose tests are +# Hermes-free. (Suites whose tests instantiate a JS engine — core, uimanager, +# runtimescheduler, scheduler, jsinspector-modern integration — are deferred.) +react_native_add_cxx_test_suite( + react_renderer_attributedstring_tests + react/renderer/attributedstring + ${RN_RENDERER_LIBS}) + +react_native_add_cxx_test_suite( + react_renderer_telemetry_tests + react/renderer/telemetry + ${RN_RENDERER_LIBS}) + +react_native_add_cxx_test_suite( + react_performance_timeline_tests + react/performance/timeline + ${RN_RENDERER_LIBS}) + +react_native_add_cxx_test_suite( + react_renderer_textlayoutmanager_tests + react/renderer/textlayoutmanager + ${RN_RENDERER_LIBS}) + +react_native_add_cxx_test_suite( + react_renderer_components_text_tests + react/renderer/components/text + ${RN_RENDERER_LIBS}) + +react_native_add_cxx_test_suite( + react_renderer_components_scrollview_tests + react/renderer/components/scrollview + ${RN_RENDERER_LIBS}) + +react_native_add_cxx_test_suite( + react_renderer_components_image_tests + react/renderer/components/image + ${RN_RENDERER_LIBS}) + +react_native_add_cxx_test_suite( + react_renderer_components_root_tests + react/renderer/components/root + ${RN_RENDERER_LIBS}) + +react_native_add_cxx_test_suite( + react_renderer_uimanager_tests + react/renderer/uimanager + ${RN_RENDERER_LIBS}) + +react_native_add_cxx_test_suite( + react_renderer_uimanager_consistency_tests + react/renderer/uimanager/consistency + ${RN_RENDERER_LIBS}) + +react_native_add_cxx_test_suite( + react_renderer_element_tests + react/renderer/element + ${RN_RENDERER_LIBS}) + +react_native_add_cxx_test_suite( + react_renderer_debug_tests + react/renderer/debug + ${RN_RENDERER_LIBS}) + +react_native_add_cxx_test_suite( + react_renderer_imagemanager_tests + react/renderer/imagemanager + ${RN_RENDERER_LIBS}) + +react_native_add_cxx_test_suite( + react_renderer_animations_tests + react/renderer/animations + ${RN_RENDERER_LIBS}) + +react_native_add_cxx_test_suite( + react_featureflags_tests + react/featureflags + ${RN_RENDERER_LIBS}) + +react_native_add_cxx_test_suite( + react_timing_tests + react/timing + ${RN_RENDERER_LIBS}) + +react_native_add_cxx_test_suite( + jserrorhandler_tests + jserrorhandler + ${RN_RENDERER_LIBS}) + +react_native_add_cxx_test_suite( + react_debug_redbox_tests + react/debug/redbox + ${RN_RENDERER_LIBS}) + +react_native_add_cxx_test_suite( + reactperflogger_fusebox_tests + reactperflogger/fusebox + ${RN_RENDERER_LIBS}) + +# --- Suites whose tests create a JS runtime (Hermes) --- +set(RN_HERMES_LIBS ${RN_RENDERER_LIBS} hermes-engine::hermesvm) + +react_native_add_cxx_test_suite( + react_renderer_core_tests + react/renderer/core + ${RN_HERMES_LIBS}) + +react_native_add_cxx_test_suite( + react_renderer_runtimescheduler_tests + react/renderer/runtimescheduler + ${RN_HERMES_LIBS}) + +# Scheduler + animation closure (adds cdpmetrics/viewtransition/animationbackend/ +# observers-events on top of the Hermes closure). +set(RN_SCHEDULER_LIBS + ${RN_HERMES_LIBS} + react_renderer_scheduler + react_renderer_animationbackend + react_performance_cdpmetrics + react_renderer_viewtransition + react_renderer_observers_events + react_renderer_animated) + +react_native_add_cxx_test_suite( + react_renderer_scheduler_tests + react/renderer/scheduler + ${RN_SCHEDULER_LIBS}) + +react_native_add_cxx_test_suite( + react_renderer_animated_tests + react/renderer/animated + ${RN_SCHEDULER_LIBS}) + +react_native_add_cxx_test_suite( + react_cxxreact_tests + cxxreact + ${RN_RENDERER_LIBS}) diff --git a/private/react-native-tests/cxx/cmake/modules/Deps.cmake b/private/react-native-tests/cxx/cmake/modules/Deps.cmake new file mode 100644 index 000000000000..9b9d269477e6 --- /dev/null +++ b/private/react-native-tests/cxx/cmake/modules/Deps.cmake @@ -0,0 +1,44 @@ +# 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. + +# Helpers for pulling ReactCommon subsystems and their (Gradle-staged) +# third-party dependencies into the C++ test harness. These mirror the helpers +# used by the Fantom tester (private/react-native-fantom/tester) so the harness +# reuses the exact same, already-proven, desktop C++ build of ReactCommon. + +file(TO_CMAKE_PATH "${REACT_COMMON_DIR}" REACT_COMMON_DIR) +file(TO_CMAKE_PATH "${REACT_THIRD_PARTY_NDK_DIR}" REACT_THIRD_PARTY_NDK_DIR) +file(TO_CMAKE_PATH "${RN_STAGED_THIRD_PARTY_DIR}" RN_STAGED_THIRD_PARTY_DIR) +file(TO_CMAKE_PATH "${RN_TESTER_THIRD_PARTY_SRC_DIR}" RN_TESTER_THIRD_PARTY_SRC_DIR) + +# A ReactCommon subsystem (e.g. react/renderer/graphics) -> builds its library. +function(add_react_common_subdir relative_path) + add_subdirectory( + ${REACT_COMMON_DIR}/${relative_path} + ReactCommon/${relative_path}) +endfunction() + +# Third-party deps prepared into ReactAndroid/build/third-party-ndk by Gradle +# (glog, double-conversion, fast_float, fmt). +function(add_react_third_party_ndk_subdir relative_path) + add_subdirectory( + ${REACT_THIRD_PARTY_NDK_DIR}/${relative_path} + ${relative_path}) +endfunction() + +# Third-party deps staged (untarred + desktop CMakeLists copied in) into the +# Fantom tester's build/third-party by Gradle (folly, gflags). +function(add_staged_third_party_subdir relative_path) + add_subdirectory( + ${RN_STAGED_THIRD_PARTY_DIR}/${relative_path} + ${relative_path}) +endfunction() + +# Third-party CMake wrappers that live in the Fantom tester source tree (boost). +function(add_tester_third_party_subdir relative_path) + add_subdirectory( + ${RN_TESTER_THIRD_PARTY_SRC_DIR}/${relative_path} + ${relative_path}) +endfunction() diff --git a/private/react-native-tests/package.json b/private/react-native-tests/package.json new file mode 100644 index 000000000000..3cfd60023084 --- /dev/null +++ b/private/react-native-tests/package.json @@ -0,0 +1,10 @@ +{ + "name": "@react-native/tests-cxx", + "private": true, + "version": "0.0.0", + "description": "Linux CMake harness that runs a representative subset of ReactCommon C++ (gtest) unit tests on GitHub CI", + "scripts": { + "build": "./build.sh", + "test": "./test.sh" + } +} diff --git a/private/react-native-tests/test.sh b/private/react-native-tests/test.sh new file mode 100755 index 000000000000..224d9ece641a --- /dev/null +++ b/private/react-native-tests/test.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# 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. + +set -e + +pushd ../.. +./gradlew :private:react-native-tests:runCxxTests +popd diff --git a/settings.gradle.kts b/settings.gradle.kts index 702a042a1088..a813b44bf465 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -21,6 +21,7 @@ include( ":packages:rn-tester:android:app", ":packages:rn-tester:android:app:benchmark", ":private:react-native-fantom", + ":private:react-native-tests", ) includeBuild("packages/gradle-plugin/")