Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions .github/workflows/test-all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,60 @@ jobs:
fail-on-error: true
secrets: inherit

test_react_common_cxx:
runs-on: 8-core-ubuntu
needs: [check_code_changes, lint]
if: needs.check_code_changes.outputs.any_code_change == '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
# The tests use C++20 features newer than the image's clang 14, such as
# std::source_location and parenthesised aggregate initialisation.
- name: Install dependencies
shell: bash
run: |
sudo apt update
sudo apt install -y git cmake openssl libssl-dev curl gnupg
codename=$(. /etc/os-release && echo "$VERSION_CODENAME")
curl -fsSL https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc > /dev/null
echo "deb http://apt.llvm.org/$codename/ llvm-toolchain-$codename-18 main" | sudo tee /etc/apt/sources.list.d/llvm-18.list
sudo apt update
sudo apt install -y clang-18
- 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-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }}
- name: Run ReactCommon C++ tests
shell: bash
run: ./gradlew :private:react-native-fantom:testReactCommon
env:
CC: clang-18
CXX: clang++-18
- name: Upload test results
if: ${{ always() }}
uses: ./.github/actions/upload-artifact
with:
name: test-react-common-cxx-results
compression-level: 1
if-no-files-found: ignore
path: private/react-native-fantom/build/reports

build_android:
runs-on: 8-core-ubuntu
needs: [set_release_type, check_code_changes]
Expand Down
1 change: 1 addition & 0 deletions packages/react-native/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ fmt="12.1.0"
folly="2024.11.18.00"
glog="0.3.5"
gflags="2.2.0"
googletest="1.18.0"
nlohmannjson="3.12.0"

[libraries]
Expand Down
58 changes: 58 additions & 0 deletions private/react-native-fantom/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ fun getSDKPath(): String {

val FOLLY_VERSION = libs.versions.folly.get()
val GFLAGS_VERSION = libs.versions.gflags.get()
val GOOGLETEST_VERSION = libs.versions.googletest.get()
val NLOHMANNJSON_VERSION = libs.versions.nlohmannjson.get()

val buildDir = project.layout.buildDirectory.get().asFile
Expand Down Expand Up @@ -104,6 +105,27 @@ val prepareGflags by
outputDir.set(File(thirdParty, "gflags"))
}

val downloadGoogleTestDest = File(downloadsDir, "googletest-${GOOGLETEST_VERSION}.tar.gz")
val downloadGoogleTest by
tasks.registering(Download::class) {
dependsOn(createNativeDepsDirectories)
src("https://github.com/google/googletest/archive/v${GOOGLETEST_VERSION}.tar.gz")
onlyIfModified(true)
overwrite(false)
retries(5)
quiet(true)
dest(downloadGoogleTestDest)
}

val prepareGoogleTest by
tasks.registering(Copy::class) {
dependsOn(listOf(downloadGoogleTest))
from(tarTree(downloadGoogleTestDest))
eachFile { path = path.substringAfter("/") }
includeEmptyDirs = false
into("$thirdParty/googletest")
}

val downloadNlohmannJsonDest = File(downloadsDir, "nlohmann_json-${NLOHMANNJSON_VERSION}.tar.gz")
val downloadNlohmannJson by
tasks.registering(Download::class) {
Expand Down Expand Up @@ -164,6 +186,7 @@ val prepareNative3pDependencies by
tasks.registering {
dependsOn(
prepareGflags,
prepareGoogleTest,
prepareNlohmannJson,
prepareFolly,
":packages:react-native:ReactAndroid:prepareBoost",
Expand Down Expand Up @@ -229,3 +252,38 @@ val buildFantomTester by
standardOutputFile.set(project.file("$buildDir/reports/build-fantom_tester.log"))
errorOutputFile.set(project.file("$buildDir/reports/build-fantom_tester.error.log"))
}

val buildReactCommonTests by
tasks.registering(CustomExecTask::class) {
dependsOn(configureFantomTester)
workingDir(testerDir)
inputs.files(testerBuildOutputFileTree)
outputs.upToDateWhen { false }
commandLine(
cmakeBinaryPath,
"--build",
testerBuildDir.toString(),
"--target",
"react_common_tests",
"-j",
ndkBuildJobs,
)
standardOutputFile.set(project.file("$buildDir/reports/build-react_common_tests.log"))
errorOutputFile.set(project.file("$buildDir/reports/build-react_common_tests.error.log"))
}

val testReactCommon by
tasks.registering(Exec::class) {
dependsOn(buildReactCommonTests)
outputs.upToDateWhen { false }
commandLine(
"$cmakePath/bin/ctest",
"--test-dir",
"$testerBuildDir/react-common-tests",
"--output-on-failure",
"--output-junit",
"$reportsDir/react-common-tests.xml",
"-j",
ndkBuildJobs,
)
}
2 changes: 2 additions & 0 deletions private/react-native-fantom/tester/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -258,3 +258,5 @@ target_compile_options(fantom_tester
-fexceptions
-frtti
-std=c++20)

add_subdirectory(react-common-tests)
142 changes: 142 additions & 0 deletions private/react-native-fantom/tester/react-common-tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# 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 C++ unit tests in ReactCommon/**/tests against the libraries the
# Fantom tester already compiles, one GoogleTest executable per tests
# directory. Run them with ctest from this directory's build directory.

cmake_minimum_required(VERSION 3.13)

enable_testing()
include(GoogleTest)

set(INSTALL_GTEST OFF CACHE BOOL "" FORCE)
add_subdirectory(${FANTOM_THIRD_PARTY_DIR}/googletest googletest EXCLUDE_FROM_ALL)

add_react_common_subdir(react/networking)
add_react_common_subdir(react/renderer/animations)
add_react_common_subdir(react/renderer/element)

# rrc_textinput compiles the Android platform sources, which need the Android
# text layout; the tests only use the cross-platform base.
file(GLOB react_common_tests_textinput_SRC CONFIGURE_DEPENDS
${REACT_COMMON_DIR}/react/renderer/components/textinput/*.cpp)
add_library(react_common_tests_textinput OBJECT ${react_common_tests_textinput_SRC})
target_link_libraries(react_common_tests_textinput
PUBLIC
react_renderer_attributedstring
react_renderer_core
rrc_text
rrc_view)
target_compile_reactnative_options(react_common_tests_textinput PRIVATE)

# The folly executors some tests use, and what they pull in, which
# folly_runtime leaves out.
set(folly_DIR ${FANTOM_THIRD_PARTY_DIR}/folly/folly)
add_library(react_common_tests_folly STATIC
${folly_DIR}/ExceptionString.cpp
${folly_DIR}/Executor.cpp
${folly_DIR}/SingletonThreadLocal.cpp
${folly_DIR}/concurrency/ProcessLocalUniqueId.cpp
${folly_DIR}/detail/MemoryIdler.cpp
${folly_DIR}/detail/StaticSingletonManager.cpp
${folly_DIR}/detail/ThreadLocalDetail.cpp
${folly_DIR}/detail/thread_local_globals.cpp
${folly_DIR}/executors/ManualExecutor.cpp
${folly_DIR}/executors/QueuedImmediateExecutor.cpp
${folly_DIR}/io/async/Request.cpp
${folly_DIR}/memory/MallctlHelper.cpp
${folly_DIR}/memory/ReentrantAllocator.cpp
${folly_DIR}/portability/SysMembarrier.cpp
${folly_DIR}/synchronization/AsymmetricThreadFence.cpp
${folly_DIR}/synchronization/Hazptr.cpp
${folly_DIR}/synchronization/HazptrDomain.cpp
${folly_DIR}/system/Pid.cpp)
target_compile_options(react_common_tests_folly PRIVATE -fexceptions -frtti)
target_link_libraries(react_common_tests_folly PUBLIC folly_runtime)

# Test headers are included by the paths Buck exports them under.
set(react_common_tests_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/include)
configure_file(
${REACT_COMMON_DIR}/callinvoker/ReactCommon/tests/TestCallInvoker.h
${react_common_tests_INCLUDE_DIR}/ReactCommon/TestCallInvoker.h
COPYONLY)

get_target_property(react_common_tests_LIBS fantom_tester LINK_LIBRARIES)

# Pre-existing failures in OSS, on macOS and Linux alike, excluded by gtest
# name per test target.
set(jsinspector_modern_tests_EXCLUDED
# Debugger.setBreakpointByUrl resolves no locations for a columnless
# breakpoint.
JsiIntegrationHermesTest/*.ResolveColumnlessBreakpointAfterEval
JsiIntegrationHermesTest/*.ResolveColumnlessBreakpointAfterReload
# The second session gets no Debugger.scriptParsed once the first disables
# Debugger.
JsiIntegrationHermesTest/*.TwoConnectionsDebuggerLifecycle)

file(GLOB_RECURSE react_common_tests_SRC CONFIGURE_DEPENDS ${REACT_COMMON_DIR}/*.cpp)
list(FILTER react_common_tests_SRC INCLUDE REGEX "/tests/")
list(FILTER react_common_tests_SRC EXCLUDE REGEX "/benchmarks/")

set(react_common_tests_DIRS)
foreach(source ${react_common_tests_SRC})
string(REGEX REPLACE "^(.*/tests)/.*$" "\\1" test_dir ${source})
list(APPEND react_common_tests_DIRS ${test_dir})
endforeach()
list(REMOVE_DUPLICATES react_common_tests_DIRS)
list(SORT react_common_tests_DIRS)

set(react_common_tests_TARGETS)
foreach(test_dir ${react_common_tests_DIRS})
file(RELATIVE_PATH test_name ${REACT_COMMON_DIR} ${test_dir})
string(MAKE_C_IDENTIFIER ${test_name} test_target)

set(test_dir_SRC ${react_common_tests_SRC})
list(FILTER test_dir_SRC INCLUDE REGEX "^${test_dir}/")

add_executable(${test_target} EXCLUDE_FROM_ALL ${test_dir_SRC})
# Header-only ReactCommon libraries export -Werror -Wpedantic, and source
# options are the only ones placed after them on the command line.
set_source_files_properties(${test_dir_SRC} PROPERTIES COMPILE_OPTIONS -Wno-error)
target_include_directories(${test_target}
PRIVATE
${react_common_tests_INCLUDE_DIR}
${test_dir}
${test_dir}/..)
target_link_libraries(${test_target}
PRIVATE
${react_common_tests_LIBS}
react_common_tests_folly
react_common_tests_textinput
react_networking
react_renderer_animations
react_renderer_element
GTest::gmock_main)
target_compile_options(${test_target}
PRIVATE
-fexceptions
-frtti
-std=c++20)

set(test_FILTER "*")
if(DEFINED ${test_target}_EXCLUDED)
list(JOIN ${test_target}_EXCLUDED ":" test_EXCLUDED)
set(test_FILTER "*-${test_EXCLUDED}")
endif()

# One process per test case: some suites leave global state behind, such as
# registered HostTargets in jsinspector-modern, that later suites trip over.
gtest_discover_tests(${test_target}
TEST_PREFIX ${test_name}/
TEST_FILTER ${test_FILTER}
NO_PRETTY_TYPES
NO_PRETTY_VALUES
DISCOVERY_MODE PRE_TEST)
list(APPEND react_common_tests_TARGETS ${test_target})
endforeach()

add_custom_target(react_common_tests)
add_dependencies(react_common_tests ${react_common_tests_TARGETS})
Loading