From 3105a8e99f21d0e824c05760b2f40f36fc7bc57a Mon Sep 17 00:00:00 2001 From: "abha.jaswal" Date: Tue, 24 Feb 2026 11:01:55 +0530 Subject: [PATCH 1/4] [Gtest] Add unit tests for halbackends Signed-off-by: Yongjoo Ahn --- CMakeLists.txt | 6 +- README.md | 148 +++++++++- packaging/hal-backend-ml-accelerator.spec | 36 ++- test/CMakeLists.txt | 67 +++++ test/hal_backend_ml_dummy_passthrough_test.cc | 214 ++++++++++++++ test/hal_backend_ml_snpe_test.cc | 262 +++++++++++++++++ test/hal_backend_ml_test_util.cc | 229 +++++++++++++++ test/hal_backend_ml_test_util.h | 169 +++++++++++ test/hal_backend_ml_test_wrapper.h | 24 ++ test/hal_backend_ml_vivante_test.cc | 272 ++++++++++++++++++ test/main.cpp | 107 +++++++ test/res/sample_snpe_test_config.json | 18 ++ test/res/sample_vivante_test_config.json | 17 ++ 13 files changed, 1557 insertions(+), 12 deletions(-) create mode 100644 test/CMakeLists.txt create mode 100644 test/hal_backend_ml_dummy_passthrough_test.cc create mode 100644 test/hal_backend_ml_snpe_test.cc create mode 100644 test/hal_backend_ml_test_util.cc create mode 100644 test/hal_backend_ml_test_util.h create mode 100644 test/hal_backend_ml_test_wrapper.h create mode 100644 test/hal_backend_ml_vivante_test.cc create mode 100644 test/main.cpp create mode 100644 test/res/sample_snpe_test_config.json create mode 100644 test/res/sample_vivante_test_config.json diff --git a/CMakeLists.txt b/CMakeLists.txt index f8ea764..a4476d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 2.6) +CMAKE_MINIMUM_REQUIRED(VERSION 3.10) PROJECT(hal-backend-ml CXX) INCLUDE(GNUInstallDirs) @@ -23,7 +23,7 @@ SET(VERSION_FLAGS "-DVERSION='\"${VERSION}\"' -DVERSION_MAJOR=${VERSION_MAJOR} - INCLUDE(FindPkgConfig) # Common Options -SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -O2 -fomit-frame-pointer -std=gnu++0x") +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -O2 -fomit-frame-pointer -std=c++14") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -ffunction-sections") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${VERSION_FLAGS}") @@ -98,3 +98,5 @@ ADD_LIBRARY(${SNPE_LIBRARY_NAME} SHARED ${SNPE_SRCS} ${UTIL_SRCS}) TARGET_LINK_LIBRARIES(${SNPE_LIBRARY_NAME} ${snpe_build_dep_pkgs_LDFLAGS}) INSTALL(TARGETS ${SNPE_LIBRARY_NAME} DESTINATION ${HAL_LIBDIR} COMPONENT RuntimeLibraries) ENDIF() + +ADD_SUBDIRECTORY(test) diff --git a/README.md b/README.md index 7b8a017..330c19f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # HAL ML Accelerator Backends -This document provides an overview of the available Machine Learning (ML) accelerator backends within this project. It details their purpose, configuration, and specific custom properties that can be used to tailor their behavior. +This document provides an overview of the available Machine Learning (ML) accelerator backends within this project. It details their purpose, configuration, specific custom properties, and testing setup. ## 1. Vivante Backend (`ml-vivante`) @@ -14,7 +14,7 @@ There are two options: 1. model.nb + model.json 2. model.nb + model.so -- **Model Files (`prop->model_files`):** +- **Model Files (`prop->model_files`):** - `model_files[0]`: Path to the Vivante model file (e.g., `my_model.nb`). This file contains the neural network graph and weights. - (Optional) `model_files[1]`: Path to the Vivante shared object file (e.g., `vnn_my_model.so`). This library provides functions like `vnn_CreateNeuralNetwork` and `vnn_ReleaseNeuralNetwork`. @@ -22,11 +22,12 @@ Note that if the shared object file is not provided, the backend attempts to loa ### Custom Properties (`prop->custom_properties`) -- **`json`**: +- **`json`**: - **Description:** Path to the JSON file for the given model file. - **Key:** `json` - **Value:** Path to the JSON file. - **Example:** `json:/path/to/my_model.json` + ```json // yolo-v8m.json { @@ -78,31 +79,32 @@ Note that if the shared object file is not provided, the backend attempts to loa ### Configuration -- **Model Files (`prop->model_files`):** +- **Model Files (`prop->model_files`):** - `model_files[0]`: Path to the SNPE model file (e.g., `my_model.dlc`). ### Custom Properties (`prop->custom_properties`) Custom properties for the SNPE backend are provided as a single string, with individual `key:value` pairs separated by commas. + **Format:** `key1:value1,key2:value2,key3:value3a;value3b` -- **`Runtime`**: +- **`Runtime`**: - **Description:** Specifies the preferred SNPE runtime target for model execution. - **Key:** `Runtime` - - **Value:** + - **Value:** - `CPU`: Use CPU runtime. - `GPU`: Use GPU runtime. - `DSP`: Use DSP runtime. - `NPU` or `AIP`: Use NPU/AIP runtime (specifically maps to `SNPE_RUNTIME_AIP_FIXED8_TF`). - **Example:** `Runtime:DSP` -- **`OutputTensor`**: +- **`OutputTensor`**: - **Description:** Specifies the names of the output tensors the application wishes to retrieve. If not provided, the backend uses all default output tensors defined in the model. - **Key:** `OutputTensor` - **Value:** A semicolon-separated list of output tensor names. Tensor names themselves can include colons. - **Example:** `OutputTensor:detection_scores:0;detection_classes:0;raw_outputs/box_encodings` -- **`OutputType`**: +- **`OutputType`**: - **Description:** Specifies the desired data types for the output tensors. The order of types in the list should correspond to the order of output tensors (either the default order or the order specified by the `OutputTensor` property). - **Key:** `OutputType` - **Value:** A semicolon-separated list of data types. @@ -110,7 +112,7 @@ Custom properties for the SNPE backend are provided as a single string, with ind - `TF8`: Output tensor data type will be 8-bit quantized (typically `uint8_t`). - **Example:** `OutputType:FLOAT32;TF8` (assuming two output tensors, the first as float32, the second as TF8) -- **`InputType`**: +- **`InputType`**: - **Description:** Specifies the data types for the input tensors. The order of types in the list should correspond to the order of input tensors as defined in the model. - **Key:** `InputType` - **Value:** A semicolon-separated list of data types. @@ -121,3 +123,131 @@ Custom properties for the SNPE backend are provided as a single string, with ind ### Example `custom_properties` String for SNPE: `"Runtime:DSP,OutputTensor:my_output_tensor1;my_output_tensor2,OutputType:FLOAT32;FLOAT32,InputType:TF8"` + +### Input Data Files for Testing + +For testing purposes, you can specify raw input data files to load instead of using zero-filled buffers. This is configured in the JSON config file under the `configParameters` object: + +- **`input_file`**: + - **Description:** Array of paths to raw binary input data files. One file per input tensor. + - **Format:** Array of file paths + - **Example:** `["sample_raw_0", "sample_raw_1"]` (for multi-input models) + - **Example:** `["bus_416x416_float.raw"]` (for single-input models) + +**Example JSON configuration with input files:** +```json +{ + + "metadata": { + "configParameters": [ + { + "fwname": "tizen-hal", + "fw_opened": 0, + "num_models": 1, + "model_files": ["inception-v3-99.nb"], + "input_file": ["pizza_3x299x299_uint8.raw"], + "input_configured": 0, + "output_configured": 0, + "custom_properties": "backend:vivante,json:inception-v3-99.json" + } + + ] + + } + + } + +``` + +The raw binary files should contain tensor data in the model's expected format (e.g., float32, uint8) with the exact size matching the input tensor dimensions. If input files are not provided, the test will use zero-filled buffers. + +## 3. Testing with GTest + +The project includes a comprehensive testing framework using Google Test (GTest) to validate backend functionality. + +### Test Structure + +The test setup consists of the following components: + +- **`test/main.cpp`**: Main test entry point that initializes GTest and manages the global properties structure. +- **`test/hal_backend_ml_vivante_test.cpp`**: Test cases for the Vivante backend. +- **`test/hal_backend_ml_snpe_test.cpp`**: Test cases for the SNPE backend. +- **`test/hal_backend_ml_dummy_passthrough_test.cpp`**: Test cases for the dummy passthrough backend. +- **`test/hal_backend_ml_test_util.cpp`**: Utility functions used by tests. +- **`test/hal_backend_ml_test_util.h`**: Header file for test utilities. +- **`test/hal_backend_ml_test_wrapper.h`**: Wrapper functions for backend APIs. + +### Building Tests + +Tests are built when the corresponding backend is enabled and BUILD_TESTS option is set to ON: + +**For Vivante tests:** +```bash +cmake -DENABLE_VIVANTE=ON -DBUILD_TESTS=ON . +make hal-backend-ml-vivante-test +``` + +**For SNPE tests:** +```bash +cmake -DENABLE_SNPE=ON -DBUILD_TESTS=ON . +make hal-backend-ml-snpe-test +``` + +**For Dummy Passthrough tests:** +```bash +cmake -DENABLE_DUMMY=ON -DBUILD_TESTS=ON . +make hal-backend-ml-dummy-passthrough-test +``` + +The test executables are linked against: +- `libgtest.so` - Google Test framework +- `libgtest_main.so` - Provides main() function for tests +- Backend-specific library (`libhal-backend-ml-vivante.so`, `libhal-backend-ml-snpe.so`, or `libhal-backend-ml-dummy-passthrough.so`) +- `pthread` - Threading support + +### Running Tests + +The test executables require a JSON configuration file path as a command-line argument: + +```bash +# Run Vivante backend tests +/hal/bin/ml-accelerator/hal-backend-ml-vivante-test /path/to/model_config.json + +# Run SNPE backend tests +/hal/bin/ml-accelerator/hal-backend-ml-snpe-test /path/to/model_config.json + +# Run Dummy Passthrough backend tests +/hal/bin/ml-accelerator/hal-backend-ml-dummy-passthrough-test /path/to/model_config.json +``` + +### Test Configuration + +The JSON configuration file should contain: +- Model file paths +- Input tensor specifications +- Output tensor specifications +- Quantization parameters +- Any backend-specific custom properties + +### Building Dependencies + +To build with GTest support, ensure the following dependencies are installed: + +**On Tizen systems:** +```bash +BuildRequires: gtest-devel +``` + +The build system automatically includes GTest support when building test executables via the CMake configuration in [`test/CMakeLists.txt`](./test/CMakeLists.txt). + +### Test Installations + +When building with tests enabled, test binaries are installed to: +``` +/hal/bin/ml-accelerator/ +``` + +For example: +- Vivante test: `/hal/bin/ml-accelerator/hal-backend-ml-vivante-test` +- SNPE test: `/hal/bin/ml-accelerator/hal-backend-ml-snpe-test` +- Dummy Passthrough test: `/hal/bin/ml-accelerator/hal-backend-ml-dummy-passthrough-test` diff --git a/packaging/hal-backend-ml-accelerator.spec b/packaging/hal-backend-ml-accelerator.spec index 17f0c28..afa15b1 100644 --- a/packaging/hal-backend-ml-accelerator.spec +++ b/packaging/hal-backend-ml-accelerator.spec @@ -9,8 +9,13 @@ Group: Machine Learning/ML Framework License: Apache-2.0 Source0: %{name}-%{version}.tar.gz +%define _module_name hal-backend-ml-accelerator +%define _module_name_snpe hal-backend-ml-snpe +%define _module_name_vivante hal-backend-ml-vivante +%define _module_name_dummypassthrough hal-backend-ml-dummy-passthrough BuildRequires: cmake BuildRequires: pkgconfig(hal-rootstrap) +BuildRequires: gtest-devel # For DA %if 0%{?_with_da_profile} @@ -27,6 +32,8 @@ BuildRequires: pkgconfig(hal-rootstrap) %endif # For DA +%define build_tests 1 +%define _testdir %{_hal_bindir}/ml-accelerator/ %description ML HAL backend drivers for various targets @@ -57,6 +64,28 @@ Summary: hal-backend-ml-accelerator for snpe %define enable_snpe -DENABLE_SNPE=ON %endif +%if 0%{?build_tests} + +%package halbackendtest +Summary: Test Binary for Hal backend +Requires: %{name} = %{version}-%{release} +%description halbackendtest +Test Binary for hal-backend +%define enable_tests -DBUILD_TESTS=ON + +%files halbackendtest +%manifest packaging/hal-backend-ml-accelerator.manifest +%if 0%{?dummy_support} +%{_testdir}%{_module_name_dummypassthrough}-test +%endif +%if 0%{?vivante_support} +%{_testdir}%{_module_name_vivante}-test +%endif +%if 0%{?snpe_support} +%{_testdir}%{_module_name_snpe}-test +%endif + +%endif %prep %setup -q @@ -65,16 +94,20 @@ Summary: hal-backend-ml-accelerator for snpe %cmake \ -DCMAKE_HAL_LIBDIR_PREFIX=%{_hal_libdir} \ -DCMAKE_HAL_LICENSEDIR_PREFIX=%{_hal_licensedir} \ + %if 0%{?build_tests} + -DTEST_DIR=%{_testdir} \ + %endif %{?enable_dummy} \ %{?enable_vivante} \ %{?enable_snpe} \ + %{?enable_tests} \ . - make %{?_smp_mflags} %install %make_install + %post /sbin/ldconfig @@ -96,6 +129,7 @@ make %{?_smp_mflags} %endif %if 0%{?snpe_support} + %files snpe %manifest packaging/hal-backend-ml-accelerator.manifest %license LICENSE diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..fb80b42 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,67 @@ +# Check if we're in TEST mode +IF(NOT DEFINED TEST_DIR) + SET(TEST_DIR "/hal/bin/ml-accelerator/") +ENDIF() + +# Test installation directory +SET(TEST_INSTALL_DIR "${TEST_DIR}") + +option(BUILD_TESTS "Build unit tests" OFF) +INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/src) +INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/gtest) + +SET(PROJECT_NAME_FULL "hal-backend-ml-accelerator") +SET(PROJECT_NAME_SNPE "hal-backend-ml-snpe") +SET(PROJECT_NAME_VIVANTE "hal-backend-ml-vivante") +SET(PROJECT_NAME_DUMMY "hal-backend-ml-dummy-passthrough") +SET(UTIL_SRCS + ${PROJECT_SOURCE_DIR}/src/hal-backend-ml-util.cc +) +# Common test sources +SET(COMMON_TEST_SRCS + main.cpp + hal_backend_ml_test_util.cc +) + +IF(BUILD_TESTS) +# Vivante tests +IF(ENABLE_VIVANTE) + +ADD_EXECUTABLE(${VIVANTE_LIBRARY_NAME}-test + ${COMMON_TEST_SRCS} + hal_backend_ml_vivante_test.cc +) +TARGET_LINK_LIBRARIES(${PROJECT_NAME_VIVANTE}-test libgtest.so libgtest_main.so -pthread) +TARGET_LINK_LIBRARIES(${VIVANTE_LIBRARY_NAME}-test + ${VIVANTE_LIBRARY_NAME} + ${TEST_PKGS_LDFLAGS} + -lpthread +) +INSTALL(TARGETS ${PROJECT_NAME_VIVANTE}-test RUNTIME DESTINATION ${TEST_INSTALL_DIR}) + +ENDIF() + + +# SNPE tests +IF(ENABLE_SNPE) + +ADD_EXECUTABLE(${SNPE_LIBRARY_NAME}-test + ${COMMON_TEST_SRCS} + ${CMAKE_CURRENT_SOURCE_DIR}/hal_backend_ml_snpe_test.cc +) +TARGET_LINK_LIBRARIES(${PROJECT_NAME_SNPE}-test libgtest.so libgtest_main.so -pthread) +TARGET_LINK_LIBRARIES(${PROJECT_NAME_SNPE}-test ${SNPE_LIBRARY_NAME} ${TEST_PKGS_LDFLAGS} -lpthread) +INSTALL(TARGETS ${PROJECT_NAME_SNPE}-test RUNTIME DESTINATION ${TEST_INSTALL_DIR}) + +ENDIF() + +# Dummy Passthrough tests +ADD_EXECUTABLE(${PROJECT_NAME_DUMMY}-test + ${COMMON_TEST_SRCS} + ${CMAKE_CURRENT_SOURCE_DIR}/hal_backend_ml_dummy_passthrough_test.cc +) +TARGET_LINK_LIBRARIES(${PROJECT_NAME_DUMMY}-test libgtest.so libgtest_main.so -pthread) +TARGET_LINK_LIBRARIES(${PROJECT_NAME_DUMMY}-test ${DUMMY_PASSTHROUGH_LIBRARY_NAME} ${TEST_PKGS_LDFLAGS} -lpthread) +INSTALL(TARGETS ${PROJECT_NAME_DUMMY}-test RUNTIME DESTINATION ${TEST_INSTALL_DIR}) + +ENDIF() diff --git a/test/hal_backend_ml_dummy_passthrough_test.cc b/test/hal_backend_ml_dummy_passthrough_test.cc new file mode 100644 index 0000000..2040b00 --- /dev/null +++ b/test/hal_backend_ml_dummy_passthrough_test.cc @@ -0,0 +1,214 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +#define TESTING 1 +#include +#include +#include +#include +#include +#include "hal-backend-ml-util.h" +#include "hal_backend_ml_test_util.h" +#include "hal-backend-ml-util.cc" +#include "hal_backend_ml_test_wrapper.h" +#include "hal-backend-ml-dummy-passthrough.cc" + +// =================================================================== +// Basic Lifecycle Tests +// =================================================================== + +TEST_F(MLBackendTest, DummyPassthrough_InitAndExit) { + void* hal_data = nullptr; + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_dummy_passthrough_init(&hal_data)); + EXPECT_NE(hal_data, nullptr); + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_dummy_passthrough_deinit(hal_data)); +} + +TEST_F(MLBackendTest, DummyPassthrough_configure_instance) { + void* hal_data = nullptr; + TestGstTensorFilterProperties* test_config = get_test_config(); + ASSERT_NE(test_config, nullptr) << "Test configuration not initialized"; + + // Initialize the backend + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_dummy_passthrough_init(&hal_data)); + EXPECT_NE(hal_data, nullptr); + + // Configure the instance with properties + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_dummy_passthrough_configure_instance(hal_data, &test_config->base)); + + // Deinitialize the backend + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_dummy_passthrough_deinit(hal_data)); +} + +TEST_F(MLBackendTest, DummyPassthrough_inference) { + void* hal_data = nullptr; + GstTensorMemory input[NNS_TENSOR_MEMORY_MAX] = {0}; + GstTensorMemory output[NNS_TENSOR_MEMORY_MAX] = {0}; + GstTensorsInfo in_info = {0}; + GstTensorsInfo out_info = {0}; + TestGstTensorFilterProperties* test_config = get_test_config(); + ASSERT_NE(test_config, nullptr) << "Test configuration not initialized"; + + // Initialize the backend + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_dummy_passthrough_init(&hal_data)); + ASSERT_NE(hal_data, nullptr); + + // Configure the instance with properties + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_dummy_passthrough_configure_instance(hal_data, &test_config->base)); + + // Get input/output tensor info + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_dummy_passthrough_get_model_info(hal_data, GET_IN_OUT_INFO, &in_info, &out_info)); + + // Allocate and load input/output buffers + allocate_and_load_test_buffers(input, output, &in_info, &out_info, test_config); + + // Perform inference + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_dummy_passthrough_invoke(hal_data, input, output)); + + // Free input and output buffers + free_test_buffers(input, output, &in_info, &out_info); + + // Free tensor info memory + gst_tensors_info_free(&in_info); + gst_tensors_info_free(&out_info); + + // Deinitialize the backend + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_dummy_passthrough_deinit(hal_data)); +} + +// =================================================================== +// Framework Info Tests +// =================================================================== + +TEST_F(MLBackendTest, DummyPassthrough_get_framework_info) { + void* hal_data = nullptr; + GstTensorFilterFrameworkInfo fw_info = {0}; + + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_dummy_passthrough_init(&hal_data)); + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_dummy_passthrough_get_framework_info(hal_data, &fw_info)); + EXPECT_STREQ("dummy-passthrough", fw_info.name); + EXPECT_FALSE(fw_info.allow_in_place); + EXPECT_FALSE(fw_info.allocate_in_invoke); + EXPECT_FALSE(fw_info.run_without_model); + EXPECT_FALSE(fw_info.verify_model_path); + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_dummy_passthrough_deinit(hal_data)); +} + +// =================================================================== +// Model Info Tests +// =================================================================== + +TEST_F(MLBackendTest, DummyPassthrough_get_model_info) { + void* hal_data = nullptr; + GstTensorsInfo in_info = {0}; + GstTensorsInfo out_info = {0}; + TestGstTensorFilterProperties* test_config = get_test_config(); + ASSERT_NE(test_config, nullptr) << "Test configuration not initialized"; + + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_dummy_passthrough_init(&hal_data)); + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_dummy_passthrough_configure_instance(hal_data, &test_config->base)); + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_dummy_passthrough_get_model_info(hal_data, GET_IN_OUT_INFO, &in_info, &out_info)); + EXPECT_GT(in_info.num_tensors, 0); + EXPECT_GT(out_info.num_tensors, 0); + + // Free tensor info memory + gst_tensors_info_free(&in_info); + gst_tensors_info_free(&out_info); + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_dummy_passthrough_deinit(hal_data)); +} + +// =================================================================== +// Event Handler Tests +// =================================================================== + +TEST_F(MLBackendTest, DummyPassthrough_event_handler_not_supported) { + void* hal_data = nullptr; + + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_dummy_passthrough_init(&hal_data)); + + EXPECT_EQ(HAL_ML_ERROR_NOT_SUPPORTED, ml_dummy_passthrough_event_handler(hal_data, 0, nullptr)); + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_dummy_passthrough_deinit(hal_data)); +} + +// =================================================================== +// Error Handling Tests - NULL Parameters +// =================================================================== + +TEST(DummyPassthroughTest, DeinitWithNullParameter) { + EXPECT_EQ(HAL_ML_ERROR_INVALID_PARAMETER, ml_dummy_passthrough_deinit(nullptr)); +} + +TEST(DummyPassthroughTest, ConfigureInstanceWithNullBackend) { + GstTensorFilterProperties test_prop = {0}; + EXPECT_EQ(HAL_ML_ERROR_INVALID_PARAMETER, ml_dummy_passthrough_configure_instance(nullptr, &test_prop)); +} + +TEST(DummyPassthroughTest, ConfigureInstanceWithNullProperties) { + void* hal_data = nullptr; + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_dummy_passthrough_init(&hal_data)); + + EXPECT_EQ(HAL_ML_ERROR_INVALID_PARAMETER, ml_dummy_passthrough_configure_instance(hal_data, nullptr)); + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_dummy_passthrough_deinit(hal_data)); +} + + +TEST(DummyPassthroughTest, InvokeWithNullBackend) { + GstTensorMemory input[NNS_TENSOR_MEMORY_MAX] = {0}; + GstTensorMemory output[NNS_TENSOR_MEMORY_MAX] = {0}; + + EXPECT_EQ(HAL_ML_ERROR_INVALID_PARAMETER, ml_dummy_passthrough_invoke(nullptr, input, output)); +} + + +TEST(DummyPassthroughTest, GetModelInfoWithNullBackend) { + GstTensorsInfo in_info = {0}; + GstTensorsInfo out_info = {0}; + + EXPECT_EQ(HAL_ML_ERROR_INVALID_PARAMETER, + ml_dummy_passthrough_get_model_info(nullptr, GET_IN_OUT_INFO, &in_info, &out_info)); +} + +TEST(DummyPassthroughTest, GetFrameworkInfoWithNullBackend) { + GstTensorFilterFrameworkInfo fw_info = {0}; + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_dummy_passthrough_get_framework_info(nullptr, &fw_info)); + EXPECT_STREQ("dummy-passthrough", fw_info.name); +} + +// =================================================================== +// Multiple Inference Tests +// =================================================================== + +TEST_F(MLBackendTest, DummyPassthrough_multiple_inferences) { + void* hal_data = nullptr; + GstTensorMemory input[NNS_TENSOR_MEMORY_MAX] = {0}; + GstTensorMemory output[NNS_TENSOR_MEMORY_MAX] = {0}; + GstTensorsInfo in_info = {0}; + GstTensorsInfo out_info = {0}; + TestGstTensorFilterProperties* test_config = get_test_config(); + ASSERT_NE(test_config, nullptr) << "Test configuration not initialized"; + + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_dummy_passthrough_init(&hal_data)); + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_dummy_passthrough_configure_instance(hal_data, &test_config->base)); + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_dummy_passthrough_get_model_info(hal_data, GET_IN_OUT_INFO, &in_info, &out_info)); + + // Perform multiple inferences + for (int i = 0; i < 3; i++) { + allocate_and_load_test_buffers(input, output, &in_info, &out_info, test_config); + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_dummy_passthrough_invoke(hal_data, input, output)); + free_test_buffers(input, output, &in_info, &out_info); + } + + // Free tensor info memory + gst_tensors_info_free(&in_info); + gst_tensors_info_free(&out_info); + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_dummy_passthrough_deinit(hal_data)); +} diff --git a/test/hal_backend_ml_snpe_test.cc b/test/hal_backend_ml_snpe_test.cc new file mode 100644 index 0000000..9ad3a22 --- /dev/null +++ b/test/hal_backend_ml_snpe_test.cc @@ -0,0 +1,262 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +#define TESTING 1 +#include +#include +#include +#include +#include +#include "hal-backend-ml-util.h" +#include "hal_backend_ml_test_util.h" +#include "hal-backend-ml-util.cc" +#include "hal_backend_ml_test_wrapper.h" +#include "hal-backend-ml-snpe.cc" + +// =================================================================== +// Basic Lifecycle Tests +// =================================================================== + +TEST_F(MLBackendTest, Snpe_InitAndExit) { + void* hal_data = nullptr; + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_snpe_init(&hal_data)); + EXPECT_NE(hal_data, nullptr); + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_snpe_deinit(hal_data)); +} + +TEST_F(MLBackendTest, Snpe_configure_instance) { + void* hal_data = nullptr; + TestGstTensorFilterProperties* test_config = get_test_config(); + ASSERT_NE(test_config, nullptr) << "Test configuration not initialized"; + + // Initialize the backend + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_snpe_init(&hal_data)); + EXPECT_NE(hal_data, nullptr); + + // Configure the instance with properties + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_snpe_configure_instance(hal_data, &test_config->base)); + + // Deinitialize the backend + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_snpe_deinit(hal_data)); +} + +TEST_F(MLBackendTest, Snpe_inference) { + void* hal_data = nullptr; + GstTensorMemory input[NNS_TENSOR_MEMORY_MAX] = {0}; + GstTensorMemory output[NNS_TENSOR_MEMORY_MAX] = {0}; + GstTensorsInfo in_info = {0}; + GstTensorsInfo out_info = {0}; + TestGstTensorFilterProperties* test_config = get_test_config(); + ASSERT_NE(test_config, nullptr) << "Test configuration not initialized"; + + // Initialize the backend + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_snpe_init(&hal_data)); + ASSERT_NE(hal_data, nullptr); + + // Configure the instance with properties + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_snpe_configure_instance(hal_data, &test_config->base)); + + // Get input/output tensor info + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_snpe_get_model_info(hal_data, GET_IN_OUT_INFO, &in_info, &out_info)); + + // Allocate and load input/output buffers + allocate_and_load_test_buffers(input, output, &in_info, &out_info, test_config); + + // Perform inference + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_snpe_invoke(hal_data, input, output)); + + // Free input and output buffers + free_test_buffers(input, output, &in_info, &out_info); + + // Free tensor info memory + gst_tensors_info_free(&in_info); + gst_tensors_info_free(&out_info); + + // Deinitialize the backend + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_snpe_deinit(hal_data)); +} + +// =================================================================== +// Framework Info Tests +// =================================================================== + +TEST_F(MLBackendTest, Snpe_get_framework_info) { + void* hal_data = nullptr; + GstTensorFilterFrameworkInfo fw_info = {0}; + + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_snpe_init(&hal_data)); + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_snpe_get_framework_info(hal_data, &fw_info)); + EXPECT_STREQ("snpe", fw_info.name); + EXPECT_FALSE(fw_info.allow_in_place); + EXPECT_FALSE(fw_info.allocate_in_invoke); + EXPECT_FALSE(fw_info.run_without_model); + EXPECT_FALSE(fw_info.verify_model_path); + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_snpe_deinit(hal_data)); +} + +// =================================================================== +// Model Info Tests +// =================================================================== + +TEST_F(MLBackendTest, Snpe_get_model_info) { + void* hal_data = nullptr; + GstTensorsInfo in_info = {0}; + GstTensorsInfo out_info = {0}; + TestGstTensorFilterProperties* test_config = get_test_config(); + ASSERT_NE(test_config, nullptr) << "Test configuration not initialized"; + + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_snpe_init(&hal_data)); + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_snpe_configure_instance(hal_data, &test_config->base)); + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_snpe_get_model_info(hal_data, GET_IN_OUT_INFO, &in_info, &out_info)); + EXPECT_GT(in_info.num_tensors, 0); + EXPECT_GT(out_info.num_tensors, 0); + + // Free tensor info memory + gst_tensors_info_free(&in_info); + gst_tensors_info_free(&out_info); + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_snpe_deinit(hal_data)); +} + +// =================================================================== +// Event Handler Tests +// =================================================================== + +TEST_F(MLBackendTest, Snpe_event_handler_not_supported) { + void* hal_data = nullptr; + + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_snpe_init(&hal_data)); + + EXPECT_EQ(HAL_ML_ERROR_NOT_SUPPORTED, ml_snpe_event_handler(hal_data, 0, nullptr)); + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_snpe_deinit(hal_data)); +} + +// =================================================================== +// Error Handling Tests - NULL Parameters +// =================================================================== + +TEST(SnpeTest, DeinitWithNullParameter) { + EXPECT_EQ(HAL_ML_ERROR_INVALID_PARAMETER, ml_snpe_deinit(nullptr)); +} + +TEST(SnpeTest, ConfigureInstanceWithNullBackend) { + GstTensorFilterProperties test_prop = {0}; + EXPECT_EQ(HAL_ML_ERROR_INVALID_PARAMETER, ml_snpe_configure_instance(nullptr, &test_prop)); +} + +TEST(SnpeTest, ConfigureInstanceWithNullProperties) { + void* hal_data = nullptr; + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_snpe_init(&hal_data)); + + EXPECT_EQ(HAL_ML_ERROR_INVALID_PARAMETER, ml_snpe_configure_instance(hal_data, nullptr)); + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_snpe_deinit(hal_data)); +} + +TEST(SnpeTest, InvokeWithNullBackend) { + GstTensorMemory input[NNS_TENSOR_MEMORY_MAX] = {0}; + GstTensorMemory output[NNS_TENSOR_MEMORY_MAX] = {0}; + + EXPECT_EQ(HAL_ML_ERROR_INVALID_PARAMETER, ml_snpe_invoke(nullptr, input, output)); +} + +TEST(SnpeTest, GetModelInfoWithNullBackend) { + GstTensorsInfo in_info = {0}; + GstTensorsInfo out_info = {0}; + + EXPECT_EQ(HAL_ML_ERROR_INVALID_PARAMETER, + ml_snpe_get_model_info(nullptr, GET_IN_OUT_INFO, &in_info, &out_info)); +} + +TEST(SnpeTest, GetFrameworkInfoWithNullBackend) { + GstTensorFilterFrameworkInfo fw_info = {0}; + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_snpe_get_framework_info(nullptr, &fw_info)); + EXPECT_STREQ("snpe", fw_info.name); +} + +// =================================================================== +// Static Helper Function Tests +// =================================================================== + +TEST(SnpeTest, TrimString_Normal) { + std::string result = _trim_string(" hello world "); + EXPECT_EQ("hello world", result); +} + +TEST(SnpeTest, TrimString_LeadingWhitespace) { + std::string result = _trim_string(" \t\n test"); + EXPECT_EQ("test", result); +} + +TEST(SnpeTest, TrimString_TrailingWhitespace) { + std::string result = _trim_string("test \t\n "); + EXPECT_EQ("test", result); +} + +TEST(SnpeTest, TrimString_EmptyString) { + std::string result = _trim_string(""); + EXPECT_EQ("", result); +} + +TEST(SnpeTest, TrimString_AllWhitespace) { + std::string result = _trim_string(" \t\n\r "); + EXPECT_EQ("", result); +} + +TEST(SnpeTest, TrimString_NoWhitespace) { + std::string result = _trim_string("helloworld"); + EXPECT_EQ("helloworld", result); +} + +TEST(SnpeTest, TrimString_MixedWhitespace) { + std::string result = _trim_string(" \t multiple \n spaces \r "); + EXPECT_EQ("multiple \n spaces", result); +} + +TEST(SnpeTest, SetEnvironmentVariableAdsp) { + // This test verifies the environment variable setup + // The function sets ADSP_LIBRARY_PATH based on config file or default + set_environment_var_adsp(); + + const char* adsp_path = g_getenv("ADSP_LIBRARY_PATH"); + ASSERT_NE(adsp_path, nullptr) << "ADSP_LIBRARY_PATH environment variable not set"; + ASSERT_STRNE("", adsp_path) << "ADSP_LIBRARY_PATH is empty"; +} + + +// =================================================================== +// Multiple Inference Tests +// =================================================================== + +TEST_F(MLBackendTest, Snpe_multiple_inferences) { + void* hal_data = nullptr; + GstTensorMemory input[NNS_TENSOR_MEMORY_MAX] = {0}; + GstTensorMemory output[NNS_TENSOR_MEMORY_MAX] = {0}; + GstTensorsInfo in_info = {0}; + GstTensorsInfo out_info = {0}; + TestGstTensorFilterProperties* test_config = get_test_config(); + ASSERT_NE(test_config, nullptr) << "Test configuration not initialized"; + + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_snpe_init(&hal_data)); + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_snpe_configure_instance(hal_data, &test_config->base)); + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_snpe_get_model_info(hal_data, GET_IN_OUT_INFO, &in_info, &out_info)); + + // Perform multiple inferences + for (int i = 0; i < 3; i++) { + allocate_and_load_test_buffers(input, output, &in_info, &out_info, test_config); + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_snpe_invoke(hal_data, input, output)); + free_test_buffers(input, output, &in_info, &out_info); + } + + // Free tensor info memory + gst_tensors_info_free(&in_info); + gst_tensors_info_free(&out_info); + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_snpe_deinit(hal_data)); +} diff --git a/test/hal_backend_ml_test_util.cc b/test/hal_backend_ml_test_util.cc new file mode 100644 index 0000000..1e05985 --- /dev/null +++ b/test/hal_backend_ml_test_util.cc @@ -0,0 +1,229 @@ +#include "hal_backend_ml_test_util.h" +#include +#include +#include +#include "hal-backend-ml-util.h" +#include + +/* Global test configuration - accessed through getter/setter functions */ +static TestGstTensorFilterProperties* g_test_config = nullptr; + +TestGstTensorFilterProperties* +get_test_config() +{ + return g_test_config; +} + +void +set_test_config(TestGstTensorFilterProperties *config) +{ + g_test_config = config; +} + +int +parse_json_file (char * json_path, TestGstTensorFilterProperties *prop) +{ + JsonParser *parser = NULL; + GError *error = NULL; + JsonNode *rootNode = NULL; + JsonObject *rootObject = NULL; + JsonObject *metadata = NULL; + JsonArray *configParametersArray = NULL; + int ret = HAL_ML_ERROR_NONE; + + /* Input validation */ + if (!json_path) { + g_warning ("JSON file path is NULL"); + return HAL_ML_ERROR_INVALID_PARAMETER; + } + + if (!prop) { + g_warning ("TestGstTensorFilterProperties pointer is NULL"); + return HAL_ML_ERROR_INVALID_PARAMETER; + } + + /* Free existing allocations */ + if (prop->base.model_files) { + for (int i = 0; i < prop->base.num_models; ++i) { + g_free ((char *) prop->base.model_files[i]); + } + delete[] prop->base.model_files; + prop->base.model_files = NULL; + } + + if (prop->input_data_files) { + for (guint i = 0; i < prop->num_input_files; ++i) { + g_free ((char *) prop->input_data_files[i]); + } + delete[] prop->input_data_files; + prop->input_data_files = NULL; + } + + g_free ((char *) prop->base.fwname); + prop->base.fwname = NULL; + + g_free ((char *) prop->base.custom_properties); + prop->base.custom_properties = NULL; + + /* Declare variables before gotos to avoid crossing initialization */ + guint elements; + guint elem; + JsonNode *configNode; + JsonObject *configObject; + guint num_models; + guint i; + + /* Initialize parser */ + parser = json_parser_new (); + if (!parser) { + g_warning ("Failed to create JSON parser"); + return HAL_ML_ERROR_RUNTIME_ERROR; + } + + /* Load JSON file */ + if (!json_parser_load_from_file (parser, json_path, &error)) { + g_warning ("Failed to load JSON file: %s", error->message); + ret = HAL_ML_ERROR_IO_ERROR; + goto cleanup; + } + + /* Get root node */ + rootNode = json_parser_get_root (parser); + if (!rootNode || JSON_NODE_TYPE (rootNode) != JSON_NODE_OBJECT) { + g_warning ("JSON root is not an object"); + ret = HAL_ML_ERROR_INVALID_PARAMETER; + goto cleanup; + } + + /* Get root object */ + rootObject = json_node_get_object (rootNode); + if (!rootObject) { + g_warning ("Failed to get JSON object from root node"); + ret = HAL_ML_ERROR_INVALID_PARAMETER; + goto cleanup; + } + + /* Get metadata object */ + metadata = json_object_get_object_member (rootObject, "metadata"); + if (!metadata) { + g_warning ("'metadata' object not found in JSON"); + ret = HAL_ML_ERROR_INVALID_PARAMETER; + goto cleanup; + } + + /* Get configParameters array */ + configParametersArray = json_object_get_array_member (metadata, "configParameters"); + if (!configParametersArray) { + g_warning ("'configParameters' array not found in 'metadata'"); + ret = HAL_ML_ERROR_INVALID_PARAMETER; + goto cleanup; + } + + /* Process each config parameter element */ + elements = json_array_get_length (configParametersArray); + for (elem = 0; elem < elements; ++elem) { + configNode = json_array_get_element (configParametersArray, elem); + configObject = NULL; + + if (!configNode || JSON_NODE_TYPE (configNode) != JSON_NODE_OBJECT) { + g_warning ("Invalid element at index %u in 'configParameters'", elem); + continue; + } + + configObject = json_node_get_object (configNode); + if (!configObject) { + g_warning ("Failed to get object from element at index %u", elem); + continue; + } + + /* Parse 'fwname' */ + if (json_object_has_member (configObject, "fwname")) { + const gchar *fwname = json_object_get_string_member (configObject, "fwname"); + if (fwname) { + g_free ((char *) prop->base.fwname); + prop->base.fwname = g_strdup (fwname); + g_info ("fwname: %s", prop->base.fwname); + } else { + g_warning ("'fwname' is not a string"); + } + } + + /* Parse 'fw_opened' */ + prop->base.fw_opened = json_object_get_int_member_with_default (configObject, "fw_opened", 0); + + /* Parse 'num_models' (will be updated based on model_files array) */ + prop->base.num_models = json_object_get_int_member_with_default (configObject, "num_models", 0); + g_info ("num_models: %d", prop->base.num_models); + + /* Parse 'model_files' array */ + JsonArray *model_files_array = json_object_get_array_member (configObject, "model_files"); + if (model_files_array) { + num_models = json_array_get_length (model_files_array); + + if (num_models > 0) { + prop->base.num_models = num_models; + prop->base.model_files = new const char*[num_models]; + + for (i = 0; i < num_models; ++i) { + const gchar *file_name = json_array_get_string_element (model_files_array, i); + if (file_name) { + prop->base.model_files[i] = g_strdup (file_name); + g_info ("Model file %u: %s", i, prop->base.model_files[i]); + } else { + g_warning ("Failed to get model file name at index %u", i); + prop->base.model_files[i] = NULL; + } + } + } + } else { + g_warning ("'model_files' array not found in configParameters"); + } + + /* Parse 'input_configured' */ + prop->base.input_configured = json_object_get_int_member_with_default (configObject, "input_configured", 0); + + /* Parse 'output_configured' */ + prop->base.output_configured = json_object_get_int_member_with_default (configObject, "output_configured", 0); + + /* Parse 'custom_properties' */ + if (json_object_has_member (configObject, "custom_properties")) { + const gchar *custom_properties = json_object_get_string_member (configObject, "custom_properties"); + if (custom_properties) { + g_free ((char *) prop->base.custom_properties); + prop->base.custom_properties = g_strdup (custom_properties); + g_info ("Custom properties: %s", prop->base.custom_properties); + } + } + + /* Parse 'input_file' array */ + JsonArray *input_file_array = json_object_get_array_member (configObject, "input_file"); + if (input_file_array) { + guint num_input_files = json_array_get_length (input_file_array); + + if (num_input_files > 0) { + prop->input_data_files = new const char*[num_input_files]; + prop->num_input_files = num_input_files; + + for (i = 0; i < num_input_files; ++i) { + const gchar *file_path = json_array_get_string_element (input_file_array, i); + if (file_path) { + prop->input_data_files[i] = g_strdup (file_path); + g_info ("Input data file %u: %s", i, prop->input_data_files[i]); + } else { + g_warning ("Failed to get input file path at index %u", i); + prop->input_data_files[i] = NULL; + } + } + } + } else { + prop->num_input_files = 0; + prop->input_data_files = nullptr; + } + } + +cleanup: + g_clear_error (&error); + g_clear_object (&parser); + + return ret; +} diff --git a/test/hal_backend_ml_test_util.h b/test/hal_backend_ml_test_util.h new file mode 100644 index 0000000..5b249ed --- /dev/null +++ b/test/hal_backend_ml_test_util.h @@ -0,0 +1,169 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +#ifndef __HAL_BACKEND_ML_TEST_UTIL_H__ +#define __HAL_BACKEND_ML_TEST_UTIL_H__ + +#include "nnstreamer_plugin_api_filter.h" +#include +#include +#include "hal-backend-ml-util.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Test-specific extension of GstTensorFilterProperties + * + * This struct extends the original GstTensorFilterProperties with test-specific + * fields needed for unit testing. It uses composition to include all original + * fields and adds test-specific extensions. + */ +typedef struct _TestGstTensorFilterProperties +{ + GstTensorFilterProperties base; /**< Original GstTensorFilterProperties struct */ + + /* Test-specific extensions */ + const char **input_data_files; /**< Array of input data file paths for testing */ + guint num_input_files; /**< Number of input data files */ +} TestGstTensorFilterProperties; + +/** + * @brief Parse JSON configuration file for test setup + * + * Parses a JSON configuration file and populates the TestGstTensorFilterProperties + * structure with the configuration parameters. + * + * @param json_path Path to the JSON configuration file + * @param prop Pointer to TestGstTensorFilterProperties structure to populate + * @return HAL_ML_ERROR_NONE on success, error code on failure + */ +int parse_json_file(char * json_path, TestGstTensorFilterProperties *prop); + +/** + * @brief Get the test configuration + * + * Returns a pointer to the global test configuration structure. + * This is used by test cases to access the parsed configuration. + * + * @return Pointer to TestGstTensorFilterProperties structure + */ +TestGstTensorFilterProperties* get_test_config(); + +/** + * @brief Set the test configuration + * + * Sets the global test configuration structure pointer. + * This is called by main() after parsing the JSON configuration. + * + * @param config Pointer to TestGstTensorFilterProperties structure to set as global config + */ +void set_test_config(TestGstTensorFilterProperties *config); + +#ifdef __cplusplus +} + +/** + * @brief Base test fixture for ML backend tests + * + * This fixture provides a common base class for ML backend tests. + * Tests access the global test configuration through get_test_config(). + */ +class MLBackendTest : public ::testing::Test { +protected: + // No setup/teardown needed - tests use get_test_config() for configuration +}; + +#endif // __cplusplus + +/** + * @brief Allocate and load test buffers for inference + * + * Allocates input and output buffers, loads data from files if available, + * or fills with zeros if no input files are provided. + * + * @param input Input buffer array to allocate and load + * @param output Output buffer array to allocate + * @param in_info Input tensor info + * @param out_info Output tensor info + * @param prop Test properties containing input data file paths + */ +static inline void +allocate_and_load_test_buffers (GstTensorMemory *input, GstTensorMemory *output, + GstTensorsInfo *in_info, GstTensorsInfo *out_info, + TestGstTensorFilterProperties *prop) +{ + /* Allocate and load input buffers */ + for (guint i = 0; i < in_info->num_tensors; i++) { + GstTensorInfo *info = gst_tensors_info_get_nth_info (in_info, i); + input[i].size = gst_tensor_info_get_size (info); + input[i].data = g_malloc (input[i].size); + ASSERT_NE (input[i].data, nullptr); + + /* Load raw data from file if provided */ + if (prop->input_data_files && i < prop->num_input_files) { + const gchar *input_file = prop->input_data_files[i]; + FILE *fp = fopen (input_file, "rb"); + if (fp) { + size_t bytes_read = fread (input[i].data, 1, input[i].size, fp); + fclose (fp); + + ASSERT_EQ (bytes_read, input[i].size) + << "Read " << bytes_read << " bytes, expected " << input[i].size + << " bytes from file: " << input_file; + } else { + /* File open failed, use zero-filled buffer */ + memset (input[i].data, 0, input[i].size); + } + } else { + /* No input file provided, use zero-filled buffer */ + memset (input[i].data, 0, input[i].size); + } + } + + /* Allocate output buffers */ + for (guint i = 0; i < out_info->num_tensors; i++) { + GstTensorInfo *info = gst_tensors_info_get_nth_info (out_info, i); + output[i].size = gst_tensor_info_get_size (info); + output[i].data = g_malloc0 (output[i].size); + ASSERT_NE (output[i].data, nullptr); + } +} + +/** + * @brief Free allocated test buffers + * + * Frees all input and output buffers that were allocated for testing. + * Safely handles null pointers. + * + * @param input Input buffer array to free + * @param output Output buffer array to free + * @param in_info Input tensor info (for num_tensors) + * @param out_info Output tensor info (for num_tensors) + */ +static inline void +free_test_buffers (GstTensorMemory *input, GstTensorMemory *output, + GstTensorsInfo *in_info, GstTensorsInfo *out_info) +{ + /* Free input buffers */ + if (input) { + for (guint i = 0; i < in_info->num_tensors; i++) { + if (input[i].data) { + g_free (input[i].data); + input[i].data = nullptr; + } + } + } + + /* Free output buffers */ + if (output) { + for (guint i = 0; i < out_info->num_tensors; i++) { + if (output[i].data) { + g_free (output[i].data); + output[i].data = nullptr; + } + } + } +} + +#endif /* __HAL_BACKEND_ML_TEST_UTIL_H__ */ diff --git a/test/hal_backend_ml_test_wrapper.h b/test/hal_backend_ml_test_wrapper.h new file mode 100644 index 0000000..bee6835 --- /dev/null +++ b/test/hal_backend_ml_test_wrapper.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +/** + * @file hal_backend_ml_test_wrapper.h + * @brief Wrapper header for testing static functions in hal-backend-ml-*.cc files + * + * This header provides a way to test static functions by undefining 'static' + * when TESTING is defined. This allows the test to compile the source file + * directly and access the functions. + */ + +#ifdef TESTING + +// Undefine static to make functions accessible during testing +#ifdef static +#undef static +#define static +#endif + +// Note: The source files (hal-backend-ml-*.cc) will be included +// by the test files, and with TESTING defined, static functions will +// become regular functions that can be called from tests. + +#endif // TESTING diff --git a/test/hal_backend_ml_vivante_test.cc b/test/hal_backend_ml_vivante_test.cc new file mode 100644 index 0000000..e58c3e0 --- /dev/null +++ b/test/hal_backend_ml_vivante_test.cc @@ -0,0 +1,272 @@ +#define TESTING 1 +#include +#include +#include +#include +#include +#include "hal-backend-ml-util.h" +#include "hal_backend_ml_test_util.h" +#include "hal-backend-ml-util.cc" +#include "hal_backend_ml_test_wrapper.h" +#include "hal-backend-ml-vivante.cc" + +// =================================================================== +// Basic Lifecycle Tests +// =================================================================== + +TEST_F(MLBackendTest, Vivante_InitAndExit) { + void* hal_data = nullptr; + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_vivante_init(&hal_data)); + EXPECT_NE(hal_data, nullptr); + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_vivante_deinit(hal_data)); +} + +TEST_F(MLBackendTest, Vivante_configure_instance) { + void* hal_data = nullptr; + TestGstTensorFilterProperties* test_config = get_test_config(); + ASSERT_NE(test_config, nullptr) << "Test configuration not initialized"; + + // Initialize the backend + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_vivante_init(&hal_data)); + EXPECT_NE(hal_data, nullptr); + + // Configure the instance with properties + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_vivante_configure_instance(hal_data, &test_config->base)); + + // Deinitialize the backend + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_vivante_deinit(hal_data)); +} + +TEST_F(MLBackendTest, Vivante_inference) { + void* hal_data = nullptr; + GstTensorMemory input[NNS_TENSOR_MEMORY_MAX] = {0}; + GstTensorMemory output[NNS_TENSOR_MEMORY_MAX] = {0}; + GstTensorsInfo in_info = {0}; + GstTensorsInfo out_info = {0}; + TestGstTensorFilterProperties* test_config = get_test_config(); + ASSERT_NE(test_config, nullptr) << "Test configuration not initialized"; + + // Initialize the backend + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_vivante_init(&hal_data)); + ASSERT_NE(hal_data, nullptr); + + // Configure the instance with properties + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_vivante_configure_instance(hal_data, &test_config->base)); + + // Get input/output tensor info + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_vivante_get_model_info(hal_data, GET_IN_OUT_INFO, &in_info, &out_info)); + + // Allocate and load input/output buffers + allocate_and_load_test_buffers(input, output, &in_info, &out_info, test_config); + + // Perform inference + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_vivante_invoke(hal_data, input, output)); + + // Free input and output buffers + free_test_buffers(input, output, &in_info, &out_info); + + // Free tensor info memory + gst_tensors_info_free(&in_info); + gst_tensors_info_free(&out_info); + + // Deinitialize the backend + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_vivante_deinit(hal_data)); +} + +// =================================================================== +// Framework Info Tests +// =================================================================== + +TEST_F(MLBackendTest, Vivante_get_framework_info) { + void* hal_data = nullptr; + GstTensorFilterFrameworkInfo fw_info = {0}; + + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_vivante_init(&hal_data)); + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_vivante_get_framework_info(hal_data, &fw_info)); + EXPECT_STREQ("vivante", fw_info.name); + EXPECT_FALSE(fw_info.allow_in_place); + EXPECT_FALSE(fw_info.allocate_in_invoke); + EXPECT_FALSE(fw_info.run_without_model); + EXPECT_FALSE(fw_info.verify_model_path); + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_vivante_deinit(hal_data)); +} + +// =================================================================== +// Model Info Tests +// =================================================================== + +TEST_F(MLBackendTest, Vivante_get_model_info) { + void* hal_data = nullptr; + GstTensorsInfo in_info = {0}; + GstTensorsInfo out_info = {0}; + TestGstTensorFilterProperties* test_config = get_test_config(); + ASSERT_NE(test_config, nullptr) << "Test configuration not initialized"; + + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_vivante_init(&hal_data)); + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_vivante_configure_instance(hal_data, &test_config->base)); + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_vivante_get_model_info(hal_data, GET_IN_OUT_INFO, &in_info, &out_info)); + EXPECT_GT(in_info.num_tensors, 0); + EXPECT_GT(out_info.num_tensors, 0); + + // Free tensor info memory + gst_tensors_info_free(&in_info); + gst_tensors_info_free(&out_info); + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_vivante_deinit(hal_data)); +} + +// =================================================================== +// Event Handler Tests +// =================================================================== + +TEST_F(MLBackendTest, Vivante_event_handler_not_supported) { + void* hal_data = nullptr; + + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_vivante_init(&hal_data)); + + EXPECT_EQ(HAL_ML_ERROR_NOT_SUPPORTED, ml_vivante_event_handler(hal_data, 0, nullptr)); + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_vivante_deinit(hal_data)); +} + +// =================================================================== +// Error Handling Tests - NULL Parameters +// =================================================================== + +TEST(VivanteTest, DeinitWithNullParameter) { + EXPECT_EQ(HAL_ML_ERROR_INVALID_PARAMETER, ml_vivante_deinit(nullptr)); +} + +TEST(VivanteTest, ConfigureInstanceWithNullBackend) { + GstTensorFilterProperties test_prop = {0}; + EXPECT_EQ(HAL_ML_ERROR_INVALID_PARAMETER, ml_vivante_configure_instance(nullptr, &test_prop)); +} + +TEST(VivanteTest, ConfigureInstanceWithNullProperties) { + void* hal_data = nullptr; + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_vivante_init(&hal_data)); + + EXPECT_EQ(HAL_ML_ERROR_INVALID_PARAMETER, ml_vivante_configure_instance(hal_data, nullptr)); + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_vivante_deinit(hal_data)); +} + +TEST(VivanteTest, InvokeWithNullBackend) { + GstTensorMemory input[NNS_TENSOR_MEMORY_MAX] = {0}; + GstTensorMemory output[NNS_TENSOR_MEMORY_MAX] = {0}; + + EXPECT_EQ(HAL_ML_ERROR_INVALID_PARAMETER, ml_vivante_invoke(nullptr, input, output)); +} + +TEST(VivanteTest, GetModelInfoWithNullBackend) { + GstTensorsInfo in_info = {0}; + GstTensorsInfo out_info = {0}; + + EXPECT_EQ(HAL_ML_ERROR_INVALID_PARAMETER, + ml_vivante_get_model_info(nullptr, GET_IN_OUT_INFO, &in_info, &out_info)); +} + +TEST(VivanteTest, GetFrameworkInfoWithNullBackend) { + GstTensorFilterFrameworkInfo fw_info = {0}; + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_vivante_get_framework_info(nullptr, &fw_info)); + EXPECT_STREQ("vivante", fw_info.name); +} + +// =================================================================== +// Static Helper Function Tests - Type Conversions +// =================================================================== + +TEST(VivanteTest, VivanteVsiTypeFromString) { + EXPECT_EQ(VSI_NN_TYPE_INT8, vivante_vsi_type_from_string("VSI_NN_TYPE_INT8")); + EXPECT_EQ(VSI_NN_TYPE_UINT8, vivante_vsi_type_from_string("VSI_NN_TYPE_UINT8")); + EXPECT_EQ(VSI_NN_TYPE_INT16, vivante_vsi_type_from_string("VSI_NN_TYPE_INT16")); + EXPECT_EQ(VSI_NN_TYPE_UINT16, vivante_vsi_type_from_string("VSI_NN_TYPE_UINT16")); + EXPECT_EQ(VSI_NN_TYPE_INT32, vivante_vsi_type_from_string("VSI_NN_TYPE_INT32")); + EXPECT_EQ(VSI_NN_TYPE_UINT32, vivante_vsi_type_from_string("VSI_NN_TYPE_UINT32")); + EXPECT_EQ(VSI_NN_TYPE_INT64, vivante_vsi_type_from_string("VSI_NN_TYPE_INT64")); + EXPECT_EQ(VSI_NN_TYPE_UINT64, vivante_vsi_type_from_string("VSI_NN_TYPE_UINT64")); + EXPECT_EQ(VSI_NN_TYPE_FLOAT16, vivante_vsi_type_from_string("VSI_NN_TYPE_FLOAT16")); + EXPECT_EQ(VSI_NN_TYPE_FLOAT32, vivante_vsi_type_from_string("VSI_NN_TYPE_FLOAT32")); + EXPECT_EQ(VSI_NN_TYPE_FLOAT64, vivante_vsi_type_from_string("VSI_NN_TYPE_FLOAT64")); + EXPECT_EQ(VSI_NN_TYPE_BFLOAT16, vivante_vsi_type_from_string("VSI_NN_TYPE_BFLOAT16")); + EXPECT_EQ(VSI_NN_TYPE_BOOL8, vivante_vsi_type_from_string("VSI_NN_TYPE_BOOL8")); + + // Case insensitive + EXPECT_EQ(VSI_NN_TYPE_FLOAT32, vivante_vsi_type_from_string("vsi_nn_type_float32")); + EXPECT_EQ(VSI_NN_TYPE_FLOAT32, vivante_vsi_type_from_string("VSI_NN_TYPE_FLOAT32")); + + // NULL or unknown + EXPECT_EQ(VSI_NN_TYPE_NONE, vivante_vsi_type_from_string(nullptr)); + EXPECT_EQ(VSI_NN_TYPE_NONE, vivante_vsi_type_from_string("UNKNOWN_TYPE")); +} + +TEST(VivanteTest, VivanteQntTypeFromString) { + EXPECT_EQ(VSI_NN_QNT_TYPE_NONE, vivante_qnt_type_from_string("VSI_NN_QNT_TYPE_NONE")); + EXPECT_EQ(VSI_NN_QNT_TYPE_DFP, vivante_qnt_type_from_string("VSI_NN_QNT_TYPE_DFP")); + EXPECT_EQ(VSI_NN_QNT_TYPE_AFFINE_ASYMMETRIC, vivante_qnt_type_from_string("VSI_NN_QNT_TYPE_AFFINE_ASYMMETRIC")); + EXPECT_EQ(VSI_NN_QNT_TYPE_AFFINE_PERCHANNEL_SYMMETRIC, vivante_qnt_type_from_string("VSI_NN_QNT_TYPE_AFFINE_PERCHANNEL_SYMMETRIC")); + EXPECT_EQ(VSI_NN_QNT_TYPE_AFFINE_SYMMETRIC, vivante_qnt_type_from_string("VSI_NN_QNT_TYPE_AFFINE_SYMMETRIC")); + + // Case insensitive + EXPECT_EQ(VSI_NN_QNT_TYPE_DFP, vivante_qnt_type_from_string("vsi_nn_qnt_type_dfp")); + + // NULL or unknown + EXPECT_EQ(VSI_NN_QNT_TYPE_NONE, vivante_qnt_type_from_string(nullptr)); + EXPECT_EQ(VSI_NN_QNT_TYPE_NONE, vivante_qnt_type_from_string("UNKNOWN_QNT_TYPE")); +} + +TEST(VivanteTest, ConvertToTensorType) { + EXPECT_EQ(_NNS_INT8, convert_to_tensor_type(VSI_NN_TYPE_INT8)); + EXPECT_EQ(_NNS_INT8, convert_to_tensor_type(VSI_NN_TYPE_BOOL8)); + EXPECT_EQ(_NNS_UINT8, convert_to_tensor_type(VSI_NN_TYPE_UINT8)); + EXPECT_EQ(_NNS_INT16, convert_to_tensor_type(VSI_NN_TYPE_INT16)); + EXPECT_EQ(_NNS_UINT16, convert_to_tensor_type(VSI_NN_TYPE_UINT16)); + EXPECT_EQ(_NNS_INT32, convert_to_tensor_type(VSI_NN_TYPE_INT32)); + EXPECT_EQ(_NNS_UINT32, convert_to_tensor_type(VSI_NN_TYPE_UINT32)); + EXPECT_EQ(_NNS_INT64, convert_to_tensor_type(VSI_NN_TYPE_INT64)); + EXPECT_EQ(_NNS_UINT64, convert_to_tensor_type(VSI_NN_TYPE_UINT64)); + EXPECT_EQ(_NNS_FLOAT16, convert_to_tensor_type(VSI_NN_TYPE_FLOAT16)); + EXPECT_EQ(_NNS_FLOAT16, convert_to_tensor_type(VSI_NN_TYPE_BFLOAT16)); + EXPECT_EQ(_NNS_FLOAT32, convert_to_tensor_type(VSI_NN_TYPE_FLOAT32)); + EXPECT_EQ(_NNS_FLOAT64, convert_to_tensor_type(VSI_NN_TYPE_FLOAT64)); + + // Unknown type + EXPECT_EQ(_NNS_END, convert_to_tensor_type(VSI_NN_TYPE_NONE)); +} + +// =================================================================== +// Multiple Inference Tests +// =================================================================== + +TEST_F(MLBackendTest, Vivante_multiple_inferences) { + void* hal_data = nullptr; + GstTensorMemory input[NNS_TENSOR_MEMORY_MAX] = {0}; + GstTensorMemory output[NNS_TENSOR_MEMORY_MAX] = {0}; + GstTensorsInfo in_info = {0}; + GstTensorsInfo out_info = {0}; + TestGstTensorFilterProperties* test_config = get_test_config(); + ASSERT_NE(test_config, nullptr) << "Test configuration not initialized"; + + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_vivante_init(&hal_data)); + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_vivante_configure_instance(hal_data, &test_config->base)); + ASSERT_EQ(HAL_ML_ERROR_NONE, ml_vivante_get_model_info(hal_data, GET_IN_OUT_INFO, &in_info, &out_info)); + + // Perform multiple inferences + for (int i = 0; i < 3; i++) { + allocate_and_load_test_buffers(input, output, &in_info, &out_info, test_config); + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_vivante_invoke(hal_data, input, output)); + free_test_buffers(input, output, &in_info, &out_info); + } + + // Free tensor info memory + gst_tensors_info_free(&in_info); + gst_tensors_info_free(&out_info); + + EXPECT_EQ(HAL_ML_ERROR_NONE, ml_vivante_deinit(hal_data)); +} diff --git a/test/main.cpp b/test/main.cpp new file mode 100644 index 0000000..cb938a3 --- /dev/null +++ b/test/main.cpp @@ -0,0 +1,107 @@ +#include "gtest/gtest.h" +#include +#include "hal_backend_ml_test_util.h" +#include "hal-backend-ml-util.h" +#include +#include + +/** + * @brief Display usage information for the test executable + * + * Shows how to run the test executable with the required configuration file. + * + * @param program_name Name of the test executable + */ +static void +show_usage (const char *program_name) +{ + std::cerr << "\n"; + std::cerr << "=====================================================================\n"; + std::cerr << " HAL Backend ML Accelerator Test Runner\n"; + std::cerr << "=====================================================================\n\n"; + std::cerr << "Usage: " << program_name << " \n\n"; + std::cerr << "Required Arguments:\n"; + std::cerr << " config.json Path to JSON configuration file for test setup\n\n"; + std::cerr << "JSON Configuration File Format:\n"; + std::cerr << " The JSON file must contain the following structure:\n"; + std::cerr << " {\n"; + std::cerr << " \"metadata\": {\n"; + std::cerr << " \"configParameters\": [\n"; + std::cerr << " {\n"; + std::cerr << " \"fwname\": \"snpe|vivante|dummy-passthrough\",\n"; + std::cerr << " \"model_files\": [\"/path/to/model.dlc\"],\n"; + std::cerr << " \"input_file\": [\"/path/to/input1.bin\", ...],\n"; + std::cerr << " \"custom_properties\": \"key=value;...\"\n"; + std::cerr << " }\n"; + std::cerr << " ]\n"; + std::cerr << " }\n"; + std::cerr << " }\n\n"; + std::cerr << "Example:\n"; + std::cerr << " " << program_name << " /path/to/test_config.json\n\n"; + std::cerr << "=====================================================================\n\n"; +} + +int +main (int argc, char **argv) +{ + int ret = 0; + TestGstTensorFilterProperties* prop = nullptr; + + /* Check for --help flag */ + if (argc >= 2 && (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0)) { + show_usage(argv[0]); + return 0; + } + + /* Validate command line arguments */ + if (argc < 2) { + std::cerr << "Error: Missing JSON configuration file path\n"; + show_usage(argv[0]); + return 1; + } + + char *json_path; + json_path = argv[1]; + + /* Allocate and initialize test configuration */ + prop = new TestGstTensorFilterProperties(); + memset(prop, 0, sizeof(TestGstTensorFilterProperties)); + + /* Parse JSON configuration file */ + ret = parse_json_file(json_path, prop); + if (ret != HAL_ML_ERROR_NONE) { + std::cerr << "Error: Failed to parse JSON file (error code: " << ret << ")" << std::endl; + g_free ((char *) prop->base.fwname); + g_free ((char *) prop->base.custom_properties); + delete prop; + return 1; + } + + /* Set the global test configuration */ + set_test_config(prop); + + /* Run all tests */ + testing::InitGoogleTest (&argc, argv); + ret = RUN_ALL_TESTS (); + + /* Cleanup allocated memory */ + if (prop->base.model_files) { + for (int i = 0; i < prop->base.num_models; ++i) { + g_free ((char *) prop->base.model_files[i]); + } + delete[] prop->base.model_files; + } + + if (prop->input_data_files) { + for (guint i = 0; i < prop->num_input_files; ++i) { + g_free ((char *) prop->input_data_files[i]); + } + delete[] prop->input_data_files; + } + + g_free ((char *) prop->base.custom_properties); + g_free ((char *) prop->base.fwname); + delete prop; + + return ret; +} diff --git a/test/res/sample_snpe_test_config.json b/test/res/sample_snpe_test_config.json new file mode 100644 index 0000000..b2b50fc --- /dev/null +++ b/test/res/sample_snpe_test_config.json @@ -0,0 +1,18 @@ +{ + + "metadata": { + "configParameters": [ + { + "fwname": "tizen-hal", + "fw_opened": 0, + "num_models": 1, + "model_files": ["yolov8m_416_quantized.dlc"], + "input_file": ["bus_416x416_float.raw"], + "input_configured": 0, + "output_configured": 0, + "custom_properties": "backend:snpe,Runtime:DSP,InputType:TF8,OutputType:FLOAT32;FLOAT32,OutputTensor:/model.22/Sigmoid_output_0;/model.22/Mul_output_0" + } + + ] + } + } diff --git a/test/res/sample_vivante_test_config.json b/test/res/sample_vivante_test_config.json new file mode 100644 index 0000000..061508d --- /dev/null +++ b/test/res/sample_vivante_test_config.json @@ -0,0 +1,17 @@ +{ + "metadata": { + "configParameters": [ + { + "fwname": "tizen-hal", + "fw_opened": 0, + "num_models": 1, + "model_files": ["inception-v3.nb"], + "input_file": ["pizza_3x299x299_uint8.raw"], + "input_configured": 0, + "output_configured": 0, + "custom_properties": "backend:vivante,json:inception-v3.json" + } + ] + } + + } From edf5b7ffaff54122afd3771a10000b4c3f672265 Mon Sep 17 00:00:00 2001 From: Yongjoo Ahn Date: Tue, 24 Feb 2026 20:39:50 +0900 Subject: [PATCH 2/4] [build] Wrap the dummy test target - Add proper `IF(ENABLE_DUMMY)` in test/CMakeLists.txt Signed-off-by: Yongjoo Ahn --- CMakeLists.txt | 5 ++++- test/CMakeLists.txt | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a4476d8..df9e033 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,7 @@ INCLUDE(GNUInstallDirs) option(ENABLE_DUMMY "Enable dummy-passthrough backend" OFF) option(ENABLE_VIVANTE "Enable vivante backend" OFF) option(ENABLE_SNPE "Enable snpe backend" OFF) +option(BUILD_TESTS "Build unit tests" OFF) SET(HAL_LIBDIR ${CMAKE_HAL_LIBDIR_PREFIX}) SET(HAL_LICENSEDIR ${CMAKE_HAL_LICENSEDIR_PREFIX}) @@ -99,4 +100,6 @@ TARGET_LINK_LIBRARIES(${SNPE_LIBRARY_NAME} ${snpe_build_dep_pkgs_LDFLAGS}) INSTALL(TARGETS ${SNPE_LIBRARY_NAME} DESTINATION ${HAL_LIBDIR} COMPONENT RuntimeLibraries) ENDIF() -ADD_SUBDIRECTORY(test) +IF(BUILD_TESTS) + ADD_SUBDIRECTORY(test) +ENDIF() diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index fb80b42..25e63f8 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -6,7 +6,6 @@ ENDIF() # Test installation directory SET(TEST_INSTALL_DIR "${TEST_DIR}") -option(BUILD_TESTS "Build unit tests" OFF) INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/src) INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/gtest) @@ -56,6 +55,7 @@ INSTALL(TARGETS ${PROJECT_NAME_SNPE}-test RUNTIME DESTINATION ${TEST_INSTALL_DIR ENDIF() # Dummy Passthrough tests +IF(ENABLE_DUMMY) ADD_EXECUTABLE(${PROJECT_NAME_DUMMY}-test ${COMMON_TEST_SRCS} ${CMAKE_CURRENT_SOURCE_DIR}/hal_backend_ml_dummy_passthrough_test.cc @@ -63,5 +63,6 @@ ADD_EXECUTABLE(${PROJECT_NAME_DUMMY}-test TARGET_LINK_LIBRARIES(${PROJECT_NAME_DUMMY}-test libgtest.so libgtest_main.so -pthread) TARGET_LINK_LIBRARIES(${PROJECT_NAME_DUMMY}-test ${DUMMY_PASSTHROUGH_LIBRARY_NAME} ${TEST_PKGS_LDFLAGS} -lpthread) INSTALL(TARGETS ${PROJECT_NAME_DUMMY}-test RUNTIME DESTINATION ${TEST_INSTALL_DIR}) +ENDIF() ENDIF() From 898f75e85ef311568c9fadd96104ef9a240e606b Mon Sep 17 00:00:00 2001 From: Yongjoo Ahn Date: Tue, 24 Feb 2026 20:46:14 +0900 Subject: [PATCH 3/4] [build] Set `build_tests` as 0 in tizen spec - Disable `build_tests` by default. - HAL backends should not use gtest but use only hal-rootstrap - `gbs build ... --define "build_test 1"` would enable build test - Update README.md Signed-off-by: Yongjoo Ahn --- README.md | 4 +++- packaging/hal-backend-ml-accelerator.spec | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 330c19f..d87135b 100644 --- a/README.md +++ b/README.md @@ -234,8 +234,10 @@ The JSON configuration file should contain: To build with GTest support, ensure the following dependencies are installed: **On Tizen systems:** + +You should set `build_test` as `1` in gbs to enable test: ```bash -BuildRequires: gtest-devel +gbs build {your_gbs_options} --define "build_test 1" ``` The build system automatically includes GTest support when building test executables via the CMake configuration in [`test/CMakeLists.txt`](./test/CMakeLists.txt). diff --git a/packaging/hal-backend-ml-accelerator.spec b/packaging/hal-backend-ml-accelerator.spec index afa15b1..fd99225 100644 --- a/packaging/hal-backend-ml-accelerator.spec +++ b/packaging/hal-backend-ml-accelerator.spec @@ -15,7 +15,6 @@ Source0: %{name}-%{version}.tar.gz %define _module_name_dummypassthrough hal-backend-ml-dummy-passthrough BuildRequires: cmake BuildRequires: pkgconfig(hal-rootstrap) -BuildRequires: gtest-devel # For DA %if 0%{?_with_da_profile} @@ -32,8 +31,11 @@ BuildRequires: gtest-devel %endif # For DA +%if 0%{?build_test} %define build_tests 1 %define _testdir %{_hal_bindir}/ml-accelerator/ +BuildRequires: gtest-devel +%endif %description ML HAL backend drivers for various targets From 8e84097dc9ff1f116ad17a768c387f38e9dc0f19 Mon Sep 17 00:00:00 2001 From: Yongjoo Ahn Date: Wed, 25 Feb 2026 13:50:12 +0900 Subject: [PATCH 4/4] [fix] Clean cmake script and spec file Signed-off-by: Yongjoo Ahn --- README.md | 4 +-- packaging/hal-backend-ml-accelerator.spec | 15 +++----- test/CMakeLists.txt | 42 ++++++++--------------- test/res/sample_snpe_test_config.json | 32 ++++++++--------- test/res/sample_vivante_test_config.json | 31 ++++++++--------- 5 files changed, 51 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index d87135b..cedbab4 100644 --- a/README.md +++ b/README.md @@ -235,9 +235,9 @@ To build with GTest support, ensure the following dependencies are installed: **On Tizen systems:** -You should set `build_test` as `1` in gbs to enable test: +You should set `build_tests` as `1` in gbs to enable test: ```bash -gbs build {your_gbs_options} --define "build_test 1" +gbs build {your_gbs_options} --define "build_tests 1" ``` The build system automatically includes GTest support when building test executables via the CMake configuration in [`test/CMakeLists.txt`](./test/CMakeLists.txt). diff --git a/packaging/hal-backend-ml-accelerator.spec b/packaging/hal-backend-ml-accelerator.spec index fd99225..547c044 100644 --- a/packaging/hal-backend-ml-accelerator.spec +++ b/packaging/hal-backend-ml-accelerator.spec @@ -31,9 +31,7 @@ BuildRequires: pkgconfig(hal-rootstrap) %endif # For DA -%if 0%{?build_test} -%define build_tests 1 -%define _testdir %{_hal_bindir}/ml-accelerator/ +%if 0%{?build_tests} BuildRequires: gtest-devel %endif @@ -67,13 +65,14 @@ Summary: hal-backend-ml-accelerator for snpe %endif %if 0%{?build_tests} +%define _testdir %{_hal_bindir}/ml-accelerator/ +%define enable_tests -DBUILD_TESTS=ON -DTEST_DIR=%{_testdir} %package halbackendtest Summary: Test Binary for Hal backend Requires: %{name} = %{version}-%{release} %description halbackendtest Test Binary for hal-backend -%define enable_tests -DBUILD_TESTS=ON %files halbackendtest %manifest packaging/hal-backend-ml-accelerator.manifest @@ -86,7 +85,8 @@ Test Binary for hal-backend %if 0%{?snpe_support} %{_testdir}%{_module_name_snpe}-test %endif - +%else +%define enable_tests -DBUILD_TESTS=OFF %endif %prep @@ -96,9 +96,6 @@ Test Binary for hal-backend %cmake \ -DCMAKE_HAL_LIBDIR_PREFIX=%{_hal_libdir} \ -DCMAKE_HAL_LICENSEDIR_PREFIX=%{_hal_licensedir} \ - %if 0%{?build_tests} - -DTEST_DIR=%{_testdir} \ - %endif %{?enable_dummy} \ %{?enable_vivante} \ %{?enable_snpe} \ @@ -109,7 +106,6 @@ make %{?_smp_mflags} %install %make_install - %post /sbin/ldconfig @@ -131,7 +127,6 @@ make %{?_smp_mflags} %endif %if 0%{?snpe_support} - %files snpe %manifest packaging/hal-backend-ml-accelerator.manifest %license LICENSE diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 25e63f8..005146f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,6 +1,6 @@ # Check if we're in TEST mode IF(NOT DEFINED TEST_DIR) - SET(TEST_DIR "/hal/bin/ml-accelerator/") + SET(TEST_DIR "/hal/bin/ml-accelerator/") ENDIF() # Test installation directory @@ -13,56 +13,42 @@ SET(PROJECT_NAME_FULL "hal-backend-ml-accelerator") SET(PROJECT_NAME_SNPE "hal-backend-ml-snpe") SET(PROJECT_NAME_VIVANTE "hal-backend-ml-vivante") SET(PROJECT_NAME_DUMMY "hal-backend-ml-dummy-passthrough") -SET(UTIL_SRCS - ${PROJECT_SOURCE_DIR}/src/hal-backend-ml-util.cc -) + # Common test sources SET(COMMON_TEST_SRCS - main.cpp - hal_backend_ml_test_util.cc + main.cpp + hal_backend_ml_test_util.cc ) -IF(BUILD_TESTS) # Vivante tests IF(ENABLE_VIVANTE) - -ADD_EXECUTABLE(${VIVANTE_LIBRARY_NAME}-test - ${COMMON_TEST_SRCS} - hal_backend_ml_vivante_test.cc +ADD_EXECUTABLE(${VIVANTE_LIBRARY_NAME}-test + ${COMMON_TEST_SRCS} + ${CMAKE_CURRENT_SOURCE_DIR}/hal_backend_ml_vivante_test.cc ) TARGET_LINK_LIBRARIES(${PROJECT_NAME_VIVANTE}-test libgtest.so libgtest_main.so -pthread) -TARGET_LINK_LIBRARIES(${VIVANTE_LIBRARY_NAME}-test - ${VIVANTE_LIBRARY_NAME} - ${TEST_PKGS_LDFLAGS} - -lpthread -) +TARGET_LINK_LIBRARIES(${VIVANTE_LIBRARY_NAME}-test ${VIVANTE_LIBRARY_NAME} ${TEST_PKGS_LDFLAGS} -lpthread) INSTALL(TARGETS ${PROJECT_NAME_VIVANTE}-test RUNTIME DESTINATION ${TEST_INSTALL_DIR}) - ENDIF() - # SNPE tests IF(ENABLE_SNPE) - -ADD_EXECUTABLE(${SNPE_LIBRARY_NAME}-test - ${COMMON_TEST_SRCS} - ${CMAKE_CURRENT_SOURCE_DIR}/hal_backend_ml_snpe_test.cc +ADD_EXECUTABLE(${SNPE_LIBRARY_NAME}-test + ${COMMON_TEST_SRCS} + ${CMAKE_CURRENT_SOURCE_DIR}/hal_backend_ml_snpe_test.cc ) TARGET_LINK_LIBRARIES(${PROJECT_NAME_SNPE}-test libgtest.so libgtest_main.so -pthread) TARGET_LINK_LIBRARIES(${PROJECT_NAME_SNPE}-test ${SNPE_LIBRARY_NAME} ${TEST_PKGS_LDFLAGS} -lpthread) INSTALL(TARGETS ${PROJECT_NAME_SNPE}-test RUNTIME DESTINATION ${TEST_INSTALL_DIR}) - ENDIF() # Dummy Passthrough tests IF(ENABLE_DUMMY) -ADD_EXECUTABLE(${PROJECT_NAME_DUMMY}-test - ${COMMON_TEST_SRCS} - ${CMAKE_CURRENT_SOURCE_DIR}/hal_backend_ml_dummy_passthrough_test.cc +ADD_EXECUTABLE(${PROJECT_NAME_DUMMY}-test + ${COMMON_TEST_SRCS} + ${CMAKE_CURRENT_SOURCE_DIR}/hal_backend_ml_dummy_passthrough_test.cc ) TARGET_LINK_LIBRARIES(${PROJECT_NAME_DUMMY}-test libgtest.so libgtest_main.so -pthread) TARGET_LINK_LIBRARIES(${PROJECT_NAME_DUMMY}-test ${DUMMY_PASSTHROUGH_LIBRARY_NAME} ${TEST_PKGS_LDFLAGS} -lpthread) INSTALL(TARGETS ${PROJECT_NAME_DUMMY}-test RUNTIME DESTINATION ${TEST_INSTALL_DIR}) ENDIF() - -ENDIF() diff --git a/test/res/sample_snpe_test_config.json b/test/res/sample_snpe_test_config.json index b2b50fc..2d2662f 100644 --- a/test/res/sample_snpe_test_config.json +++ b/test/res/sample_snpe_test_config.json @@ -1,18 +1,16 @@ { - - "metadata": { - "configParameters": [ - { - "fwname": "tizen-hal", - "fw_opened": 0, - "num_models": 1, - "model_files": ["yolov8m_416_quantized.dlc"], - "input_file": ["bus_416x416_float.raw"], - "input_configured": 0, - "output_configured": 0, - "custom_properties": "backend:snpe,Runtime:DSP,InputType:TF8,OutputType:FLOAT32;FLOAT32,OutputTensor:/model.22/Sigmoid_output_0;/model.22/Mul_output_0" - } - - ] - } - } + "metadata": { + "configParameters": [ + { + "fwname": "tizen-hal", + "fw_opened": 0, + "num_models": 1, + "model_files": ["yolov8m_416_quantized.dlc"], + "input_file": ["bus_416x416_float.raw"], + "input_configured": 0, + "output_configured": 0, + "custom_properties": "backend:snpe,Runtime:DSP,InputType:TF8,OutputType:FLOAT32;FLOAT32,OutputTensor:/model.22/Sigmoid_output_0;/model.22/Mul_output_0" + } + ] + } +} diff --git a/test/res/sample_vivante_test_config.json b/test/res/sample_vivante_test_config.json index 061508d..dbe4123 100644 --- a/test/res/sample_vivante_test_config.json +++ b/test/res/sample_vivante_test_config.json @@ -1,17 +1,16 @@ { - "metadata": { - "configParameters": [ - { - "fwname": "tizen-hal", - "fw_opened": 0, - "num_models": 1, - "model_files": ["inception-v3.nb"], - "input_file": ["pizza_3x299x299_uint8.raw"], - "input_configured": 0, - "output_configured": 0, - "custom_properties": "backend:vivante,json:inception-v3.json" - } - ] - } - - } + "metadata": { + "configParameters": [ + { + "fwname": "tizen-hal", + "fw_opened": 0, + "num_models": 1, + "model_files": ["inception-v3.nb"], + "input_file": ["pizza_3x299x299_uint8.raw"], + "input_configured": 0, + "output_configured": 0, + "custom_properties": "backend:vivante,json:inception-v3.json" + } + ] + } +}