Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
252748f
wip
chmjkb Jan 16, 2026
3bc9aff
feat: basic on-device gtest run
chmjkb Jan 19, 2026
f05bcac
feat: extend cmake to use 3rd party android libs
chmjkb Jan 20, 2026
0a6a8ae
feat(wip): add a run_tests script to run tests on emulator
chmjkb Jan 20, 2026
147894b
feat: add some tests along with 3p stubs
chmjkb Jan 20, 2026
a596818
feat: add more tests
chmjkb Jan 21, 2026
bbe3291
feat: add llm tests
chmjkb Jan 21, 2026
c885a99
chore: remove logs, add jsi stubs
chmjkb Jan 21, 2026
34cdb66
chore: void cast to avoid [[nodiscard]] warnings
chmjkb Jan 22, 2026
64e2d17
feat: add tti tests
chmjkb Jan 22, 2026
43513ac
chore: avoid redownloading when model file exists
chmjkb Jan 22, 2026
bee34e1
chore: add .pte files to gitignore
chmjkb Jan 22, 2026
3ae1a3d
chore: separate integration and unit tests, add some more tests
chmjkb Jan 23, 2026
88059a7
chore: use typed tests for basemodel tests
chmjkb Jan 23, 2026
1d416c3
feat: improve tests
chmjkb Jan 24, 2026
e397e23
feat: add some more tests
chmjkb Jan 24, 2026
c565649
chore: remove pointless tests
chmjkb Jan 24, 2026
da04969
feat: add vertical ocr tests
chmjkb Jan 24, 2026
6ea237d
chore: rename variable
chmjkb Jan 24, 2026
53c7106
fix: expect throw rnexecutorch error instead of std
chmjkb Jan 24, 2026
4540c13
chore: update readme and the run tests script
chmjkb Jan 26, 2026
916b3b3
chore: add missing assets
chmjkb Jan 26, 2026
8863fa2
fix: use proper image name in vertical ocr test
chmjkb Jan 26, 2026
17189b6
chore: review suggestions
chmjkb Jan 27, 2026
8fc251d
Apply suggestion from @chmjkb
chmjkb Jan 27, 2026
22baead
chore: review changes
chmjkb Jan 27, 2026
c740a1f
chore: dynamically find required static libs
chmjkb Jan 28, 2026
5a844d1
chore: update cspell wordlist
chmjkb Jan 28, 2026
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
3 changes: 3 additions & 0 deletions .cspell-wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,6 @@ Português
codegen
cstdint
ocurred
libfbjni
libc
gradlew
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,4 @@ apps/*/android/
# custom
*.tgz
Makefile
*.pte
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,48 @@ LLM::LLM(const std::string &modelSource, const std::string &tokenizerSource,
}

// TODO: add a way to manipulate the generation config with params
#ifdef TEST_BUILD
std::string LLM::generate(std::string input,
std::shared_ptr<jsi::Function> callback) {
if (!runner || !runner->is_loaded()) {
throw RnExecutorchError(RnExecutorchErrorCode::ModuleNotLoaded,
"Runner is not loaded");
}

std::string output;

// Create a native callback that accumulates tokens and optionally invokes JS
auto nativeCallback = [this, callback, &output](const std::string &token) {
output += token;
if (callback && callInvoker) {
callInvoker->invokeAsync([callback, token](jsi::Runtime &runtime) {
callback->call(runtime, jsi::String::createFromUtf8(runtime, token));
});
}
};

auto config = llm::GenerationConfig{.echo = false, .warming = false};
auto error = runner->generate(input, config, nativeCallback, {});
if (error != executorch::runtime::Error::Ok) {
throw RnExecutorchError(error, "Failed to generate text");
}

return output;
}
#else
void LLM::generate(std::string input, std::shared_ptr<jsi::Function> callback) {
if (!runner || !runner->is_loaded()) {
throw RnExecutorchError(RnExecutorchErrorCode::ModuleNotLoaded,
"Runner is not loaded");
}

// Create a native callback that will invoke the JS callback on the JS thread
// Create a native callback that only invokes JS (no accumulation)
auto nativeCallback = [this, callback](const std::string &token) {
callInvoker->invokeAsync([callback, token](jsi::Runtime &runtime) {
callback->call(runtime, jsi::String::createFromUtf8(runtime, token));
});
if (callback && callInvoker) {
callInvoker->invokeAsync([callback, token](jsi::Runtime &runtime) {
callback->call(runtime, jsi::String::createFromUtf8(runtime, token));
});
}
};

auto config = llm::GenerationConfig{.echo = false, .warming = false};
Expand All @@ -47,6 +78,7 @@ void LLM::generate(std::string input, std::shared_ptr<jsi::Function> callback) {
throw RnExecutorchError(error, "Failed to generate text");
}
}
#endif

void LLM::interrupt() {
if (!runner || !runner->is_loaded()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ class LLM : public BaseModel {
const std::string &tokenizerSource,
std::shared_ptr<react::CallInvoker> callInvoker);

#ifdef TEST_BUILD
std::string generate(std::string input,
std::shared_ptr<jsi::Function> callback);
#else
void generate(std::string input, std::shared_ptr<jsi::Function> callback);
#endif
void interrupt();
void unload() noexcept;
size_t getGeneratedTokenCount() const noexcept;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,19 @@ cv::Mat softmax(const cv::Mat &inputs) {
cv::Mat maxVal;
cv::reduce(inputs, maxVal, 1, cv::REDUCE_MAX, CV_32F);
cv::Mat expInputs;
cv::exp(inputs - cv::repeat(maxVal, 1, inputs.cols), expInputs);
cv::Mat repeated = inputs - cv::repeat(maxVal, 1, inputs.cols);
repeated.convertTo(repeated, CV_32F);
#ifdef TEST_BUILD
// Manually compute exp to avoid SIMD issues in test environment
expInputs = cv::Mat(repeated.size(), CV_32F);
for (int i = 0; i < repeated.rows; i++) {
for (int j = 0; j < repeated.cols; j++) {
expInputs.at<float>(i, j) = std::exp(repeated.at<float>(i, j));
}
}
#else
cv::exp(repeated, expInputs);
#endif
cv::Mat sumExp;
cv::reduce(expInputs, sumExp, 1, cv::REDUCE_SUM, CV_32F);
cv::Mat softmaxOutput = expInputs / cv::repeat(sumExp, 1, inputs.cols);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
namespace rnexecutorch::models::style_transfer {
using namespace facebook;
using executorch::extension::TensorPtr;
using executorch::runtime::Error;

StyleTransfer::StyleTransfer(const std::string &modelSource,
std::shared_ptr<react::CallInvoker> callInvoker)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,257 @@
cmake_minimum_required(VERSION 3.10)
if(NOT ANDROID_ABI)
message(FATAL_ERROR "Tests can be only built for Android simulator")
endif()

cmake_minimum_required(VERSION 3.13)
project(RNExecutorchTests)

# C++ standard
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)

# googletest subdirectory
# Using an absolute path from the top-level source directory
add_subdirectory(${CMAKE_SOURCE_DIR}/../../../../../third-party/googletest ${PROJECT_BINARY_DIR}/googletest)
# tests/ <- CMAKE_SOURCE_DIR (this file's location)
# rnexecutorch/ <- RNEXECUTORCH_DIR (parent of tests)
# common/ <- COMMON_DIR
# react-native-executorch/ <- PACKAGE_ROOT
# <monorepo-root>/ <- MONOREPO_ROOT
# <monorepo-root>/third-party/ <- THIRD_PARTY_DIR
set(RNEXECUTORCH_DIR "${CMAKE_SOURCE_DIR}/..")
set(COMMON_DIR "${RNEXECUTORCH_DIR}/..")
set(PACKAGE_ROOT "${COMMON_DIR}/..")
set(MONOREPO_ROOT "${PACKAGE_ROOT}/../..")
set(THIRD_PARTY_DIR "${MONOREPO_ROOT}/third-party")
set(REACT_NATIVE_DIR "${MONOREPO_ROOT}/node_modules/react-native")
set(ANDROID_THIRD_PARTY "${PACKAGE_ROOT}/third-party/android/libs/")

# Add Gtest as a subdirectory
add_subdirectory(${THIRD_PARTY_DIR}/googletest ${PROJECT_BINARY_DIR}/googletest)

# ExecuTorch Prebuilt binaries
add_library(executorch_prebuilt SHARED IMPORTED)
set_target_properties(executorch_prebuilt PROPERTIES
IMPORTED_LOCATION "${ANDROID_THIRD_PARTY}/executorch/${ANDROID_ABI}/libexecutorch.so"
)

# pthreadpool and cpuinfo (needed for OpenMP/OpenCV)
if(ANDROID_ABI STREQUAL "arm64-v8a")
add_library(pthreadpool SHARED IMPORTED)
set_target_properties(pthreadpool PROPERTIES
IMPORTED_LOCATION "${ANDROID_THIRD_PARTY}/pthreadpool/${ANDROID_ABI}/libpthreadpool.so"
)

add_library(cpuinfo SHARED IMPORTED)
set_target_properties(cpuinfo PROPERTIES
IMPORTED_LOCATION "${ANDROID_THIRD_PARTY}/cpuinfo/${ANDROID_ABI}/libcpuinfo.so"
)

set(EXECUTORCH_LIBS pthreadpool cpuinfo)
else()
set(EXECUTORCH_LIBS "")
endif()

# OpenCV (Interface Library)
set(OPENCV_LIBS_DIR "${ANDROID_THIRD_PARTY}/opencv/${ANDROID_ABI}")
set(OPENCV_THIRD_PARTY_DIR "${ANDROID_THIRD_PARTY}/opencv-third-party/${ANDROID_ABI}")

# Directories to include
include_directories(${CMAKE_SOURCE_DIR}/../data_processing)
include_directories(${CMAKE_SOURCE_DIR}/..)
if(ANDROID_ABI STREQUAL "arm64-v8a")
set(OPENCV_THIRD_PARTY_LIBS
"${OPENCV_THIRD_PARTY_DIR}/libkleidicv_hal.a"
"${OPENCV_THIRD_PARTY_DIR}/libkleidicv_thread.a"
"${OPENCV_THIRD_PARTY_DIR}/libkleidicv.a"
)
elseif(ANDROID_ABI STREQUAL "x86_64")
set(OPENCV_THIRD_PARTY_LIBS "")
endif()

# Source files
set(SOURCE_FILES ${CMAKE_SOURCE_DIR}/../data_processing/Numerical.cpp
${CMAKE_SOURCE_DIR}/../data_processing/FileUtils.h)

# Executables for the tests
add_executable(NumericalTests NumericalTest.cpp ${SOURCE_FILES})
add_executable(FileUtilsTests FileUtilsTest.cpp ${SOURCE_FILES})
add_executable(LogTests LogTest.cpp)
add_library(opencv_deps INTERFACE)
target_link_libraries(opencv_deps INTERFACE
${OPENCV_LIBS_DIR}/libopencv_core.a
${OPENCV_LIBS_DIR}/libopencv_features2d.a
${OPENCV_LIBS_DIR}/libopencv_highgui.a
${OPENCV_LIBS_DIR}/libopencv_imgproc.a
${OPENCV_LIBS_DIR}/libopencv_photo.a
${OPENCV_LIBS_DIR}/libopencv_video.a
${OPENCV_THIRD_PARTY_LIBS}
${EXECUTORCH_LIBS}
z
dl
m
log
)
target_link_options(opencv_deps INTERFACE -fopenmp -static-openmp)

# Libraries linking
target_link_libraries(NumericalTests gtest gtest_main)
target_link_libraries(FileUtilsTests gtest gtest_main)
target_link_libraries(LogTests gtest gtest_main)
# Tokenizers (Interface Library)
set(TOKENIZERS_LIBS_DIR "${ANDROID_THIRD_PARTY}/tokenizers-cpp/${ANDROID_ABI}")
add_library(tokenizers_deps INTERFACE)
target_link_libraries(tokenizers_deps INTERFACE
${TOKENIZERS_LIBS_DIR}/libtokenizers_cpp.a
${TOKENIZERS_LIBS_DIR}/libtokenizers_c.a
${TOKENIZERS_LIBS_DIR}/libsentencepiece.a
)

# Source Definitions
set(CORE_SOURCES
${RNEXECUTORCH_DIR}/models/BaseModel.cpp
${RNEXECUTORCH_DIR}/data_processing/Numerical.cpp
${CMAKE_SOURCE_DIR}/integration/stubs/jsi_stubs.cpp
)

set(IMAGE_UTILS_SOURCES
${RNEXECUTORCH_DIR}/data_processing/ImageProcessing.cpp
${RNEXECUTORCH_DIR}/data_processing/base64.cpp
${COMMON_DIR}/ada/ada.cpp
)

set(TOKENIZER_SOURCES ${RNEXECUTORCH_DIR}/TokenizerModule.cpp)
set(DSP_SOURCES ${RNEXECUTORCH_DIR}/data_processing/dsp.cpp)

# Core Library
add_library(rntests_core STATIC ${CORE_SOURCES})

target_include_directories(rntests_core PUBLIC
${RNEXECUTORCH_DIR}/data_processing
${RNEXECUTORCH_DIR}
${COMMON_DIR}
${PACKAGE_ROOT}/third-party/include
${REACT_NATIVE_DIR}/ReactCommon
${REACT_NATIVE_DIR}/ReactCommon/jsi
${REACT_NATIVE_DIR}/ReactCommon/callinvoker
${COMMON_DIR}/ada
)

target_link_libraries(rntests_core PUBLIC
executorch_prebuilt
gtest
log
)

# Testing functionalities
enable_testing()
add_test(NAME NumericalTests COMMAND NumericalTests)
add_test(NAME FileUtilsTests COMMAND FileUtilsTests)
add_test(NAME LogTests COMMAND LogTests)
function(add_rn_test TEST_TARGET TEST_FILENAME)
cmake_parse_arguments(ARG "" "" "SOURCES;LIBS" ${ARGN})
# Create executable using the explicit filename provided
add_executable(${TEST_TARGET} ${TEST_FILENAME} ${ARG_SOURCES})

target_compile_definitions(${TEST_TARGET} PRIVATE TEST_BUILD)
target_link_libraries(${TEST_TARGET} PRIVATE rntests_core gtest_main ${ARG_LIBS})
target_link_options(${TEST_TARGET} PRIVATE "LINKER:-z,max-page-size=16384")

add_test(NAME ${TEST_TARGET} COMMAND ${TEST_TARGET})
endfunction()

add_rn_test(NumericalTests unit/NumericalTest.cpp)
add_rn_test(LogTests unit/LogTest.cpp)
add_rn_test(BaseModelTests integration/BaseModelTest.cpp)

add_rn_test(ClassificationTests integration/ClassificationTest.cpp
SOURCES
${RNEXECUTORCH_DIR}/models/classification/Classification.cpp
${IMAGE_UTILS_SOURCES}
LIBS opencv_deps
)

add_rn_test(ObjectDetectionTests integration/ObjectDetectionTest.cpp
SOURCES
${RNEXECUTORCH_DIR}/models/object_detection/ObjectDetection.cpp
${RNEXECUTORCH_DIR}/models/object_detection/Utils.cpp
${IMAGE_UTILS_SOURCES}
LIBS opencv_deps
)

add_rn_test(ImageEmbeddingsTests integration/ImageEmbeddingsTest.cpp
SOURCES
${RNEXECUTORCH_DIR}/models/embeddings/image/ImageEmbeddings.cpp
${RNEXECUTORCH_DIR}/models/embeddings/BaseEmbeddings.cpp
${IMAGE_UTILS_SOURCES}
LIBS opencv_deps
)

add_rn_test(TextEmbeddingsTests integration/TextEmbeddingsTest.cpp
SOURCES
${RNEXECUTORCH_DIR}/models/embeddings/text/TextEmbeddings.cpp
${RNEXECUTORCH_DIR}/models/embeddings/BaseEmbeddings.cpp
${TOKENIZER_SOURCES}
LIBS tokenizers_deps
)

add_rn_test(StyleTransferTests integration/StyleTransferTest.cpp
SOURCES
${RNEXECUTORCH_DIR}/models/style_transfer/StyleTransfer.cpp
${IMAGE_UTILS_SOURCES}
LIBS opencv_deps
)

add_rn_test(VADTests integration/VoiceActivityDetectionTest.cpp
SOURCES
${RNEXECUTORCH_DIR}/models/voice_activity_detection/VoiceActivityDetection.cpp
${RNEXECUTORCH_DIR}/models/voice_activity_detection/Utils.cpp
${DSP_SOURCES}
)

add_rn_test(TokenizerModuleTests integration/TokenizerModuleTest.cpp
SOURCES ${TOKENIZER_SOURCES}
LIBS tokenizers_deps
)

add_rn_test(SpeechToTextTests integration/SpeechToTextTest.cpp
SOURCES
${RNEXECUTORCH_DIR}/models/speech_to_text/SpeechToText.cpp
${RNEXECUTORCH_DIR}/models/speech_to_text/asr/ASR.cpp
${RNEXECUTORCH_DIR}/models/speech_to_text/stream/HypothesisBuffer.cpp
${RNEXECUTORCH_DIR}/models/speech_to_text/stream/OnlineASRProcessor.cpp
${RNEXECUTORCH_DIR}/data_processing/gzip.cpp
${TOKENIZER_SOURCES}
${DSP_SOURCES}
LIBS tokenizers_deps z
)

add_rn_test(LLMTests integration/LLMTest.cpp
SOURCES
${RNEXECUTORCH_DIR}/models/llm/LLM.cpp
${COMMON_DIR}/runner/runner.cpp
${COMMON_DIR}/runner/text_prefiller.cpp
${COMMON_DIR}/runner/text_decoder_runner.cpp
${COMMON_DIR}/runner/sampler.cpp
${COMMON_DIR}/runner/arange_util.cpp
LIBS tokenizers_deps
)

add_rn_test(TextToImageTests integration/TextToImageTest.cpp
SOURCES
${RNEXECUTORCH_DIR}/models/text_to_image/TextToImage.cpp
${RNEXECUTORCH_DIR}/models/text_to_image/Encoder.cpp
${RNEXECUTORCH_DIR}/models/text_to_image/UNet.cpp
${RNEXECUTORCH_DIR}/models/text_to_image/Decoder.cpp
${RNEXECUTORCH_DIR}/models/text_to_image/Scheduler.cpp
${RNEXECUTORCH_DIR}/models/embeddings/text/TextEmbeddings.cpp
${RNEXECUTORCH_DIR}/models/embeddings/BaseEmbeddings.cpp
${TOKENIZER_SOURCES}
LIBS tokenizers_deps
)

add_rn_test(OCRTests integration/OCRTest.cpp
SOURCES
${RNEXECUTORCH_DIR}/models/ocr/OCR.cpp
${RNEXECUTORCH_DIR}/models/ocr/CTCLabelConverter.cpp
${RNEXECUTORCH_DIR}/models/ocr/Detector.cpp
${RNEXECUTORCH_DIR}/models/ocr/RecognitionHandler.cpp
${RNEXECUTORCH_DIR}/models/ocr/Recognizer.cpp
${RNEXECUTORCH_DIR}/models/ocr/utils/DetectorUtils.cpp
${RNEXECUTORCH_DIR}/models/ocr/utils/RecognitionHandlerUtils.cpp
${RNEXECUTORCH_DIR}/models/ocr/utils/RecognizerUtils.cpp
${IMAGE_UTILS_SOURCES}
LIBS opencv_deps
)

add_rn_test(VerticalOCRTests integration/VerticalOCRTest.cpp
SOURCES
${RNEXECUTORCH_DIR}/models/vertical_ocr/VerticalOCR.cpp
${RNEXECUTORCH_DIR}/models/vertical_ocr/VerticalDetector.cpp
${RNEXECUTORCH_DIR}/models/ocr/Detector.cpp
${RNEXECUTORCH_DIR}/models/ocr/CTCLabelConverter.cpp
${RNEXECUTORCH_DIR}/models/ocr/Recognizer.cpp
${RNEXECUTORCH_DIR}/models/ocr/utils/DetectorUtils.cpp
${RNEXECUTORCH_DIR}/models/ocr/utils/RecognitionHandlerUtils.cpp
${RNEXECUTORCH_DIR}/models/ocr/utils/RecognizerUtils.cpp
${IMAGE_UTILS_SOURCES}
LIBS opencv_deps
)
Loading
Loading