From d6c14cdf362b8b952813dcab5b35270f28dde883 Mon Sep 17 00:00:00 2001 From: lum1n0us Date: Sat, 28 Feb 2026 12:31:43 +0800 Subject: [PATCH] Add CMocka unit tests for wasm_runtime functions and improve error handling --- core/iwasm/interpreter/wasm_runtime.c | 4 +- core/iwasm/interpreter/wasm_runtime.h | 9 + tests/unit/CMakeLists.txt | 16 + tests/unit/wasm-runtime/CMakeLists.txt | 46 +++ .../wasm-runtime/mocks/wasm_loader_mock.c | 39 ++ .../wasm-runtime/mocks/wasm_loader_mock.h | 32 ++ tests/unit/wasm-runtime/test_runner.c | 30 ++ tests/unit/wasm-runtime/wasm_runtime_test.c | 345 ++++++++++++++++++ 8 files changed, 519 insertions(+), 2 deletions(-) create mode 100644 tests/unit/wasm-runtime/CMakeLists.txt create mode 100644 tests/unit/wasm-runtime/mocks/wasm_loader_mock.c create mode 100644 tests/unit/wasm-runtime/mocks/wasm_loader_mock.h create mode 100644 tests/unit/wasm-runtime/test_runner.c create mode 100644 tests/unit/wasm-runtime/wasm_runtime_test.c diff --git a/core/iwasm/interpreter/wasm_runtime.c b/core/iwasm/interpreter/wasm_runtime.c index cac3730bf..c30205c3a 100644 --- a/core/iwasm/interpreter/wasm_runtime.c +++ b/core/iwasm/interpreter/wasm_runtime.c @@ -31,7 +31,7 @@ #include "../aot/aot_runtime.h" #endif -static void +WASM_RUNTIME_API_INTER void set_error_buf(char *error_buf, uint32 error_buf_size, const char *string) { if (error_buf != NULL) { @@ -40,7 +40,7 @@ set_error_buf(char *error_buf, uint32 error_buf_size, const char *string) } } -static void +WASM_RUNTIME_API_INTER void set_error_buf_v(char *error_buf, uint32 error_buf_size, const char *format, ...) { va_list args; diff --git a/core/iwasm/interpreter/wasm_runtime.h b/core/iwasm/interpreter/wasm_runtime.h index 0ba2049af..bb2cb5f8b 100644 --- a/core/iwasm/interpreter/wasm_runtime.h +++ b/core/iwasm/interpreter/wasm_runtime.h @@ -19,6 +19,15 @@ extern "C" { #define EXCEPTION_BUF_LEN 128 +/* Test visibility macro for internal functions */ +#ifndef WASM_RUNTIME_API_INTER +#ifdef WAMR_BUILD_TEST +#define WASM_RUNTIME_API_INTER +#else +#define WASM_RUNTIME_API_INTER static +#endif +#endif + typedef struct WASMModuleInstance WASMModuleInstance; typedef struct WASMFunctionInstance WASMFunctionInstance; typedef struct WASMMemoryInstance WASMMemoryInstance; diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 9c1a4a5d5..b4ab6f82b 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -50,6 +50,21 @@ endif() set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) FetchContent_MakeAvailable(googletest) +# Fetch CMocka for C unit tests +if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.24") + FetchContent_Declare( + cmocka + URL https://git.cryptomilk.org/projects/cmocka.git/snapshot/cmocka-2.0.1.tar.gz + DOWNLOAD_EXTRACT_TIMESTAMP ON + ) +else() + FetchContent_Declare( + cmocka + URL https://git.cryptomilk.org/projects/cmocka.git/snapshot/cmocka-2.0.1.tar.gz + ) +endif() +FetchContent_MakeAvailable(cmocka) + include(GoogleTest) enable_testing() @@ -65,6 +80,7 @@ add_subdirectory(gc) add_subdirectory(tid-allocator) add_subdirectory(unsupported-features) add_subdirectory(smart-tests) +add_subdirectory(wasm-runtime) if (NOT WAMR_BUILD_TARGET STREQUAL "X86_32") add_subdirectory(aot-stack-frame) diff --git a/tests/unit/wasm-runtime/CMakeLists.txt b/tests/unit/wasm-runtime/CMakeLists.txt new file mode 100644 index 000000000..86de6abdf --- /dev/null +++ b/tests/unit/wasm-runtime/CMakeLists.txt @@ -0,0 +1,46 @@ +# Copyright (C) 2019 Intel Corporation. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +cmake_minimum_required(VERSION 3.14) + +project(test-wasm-runtime) + +# Enable test build flag +add_definitions(-DWAMR_BUILD_TEST=1) + +# Test-specific feature configuration +set(WAMR_BUILD_AOT 0) +set(WAMR_BUILD_FAST_INTERP 0) +set(WAMR_BUILD_INTERP 1) +set(WAMR_BUILD_JIT 0) +set(WAMR_BUILD_LIBC_WASI 0) +set(WAMR_BUILD_APP_FRAMEWORK 0) + +# Test both multi-module enabled and disabled +if(NOT DEFINED WAMR_BUILD_MULTI_MODULE) + set(WAMR_BUILD_MULTI_MODULE 0) +endif() + +include(../unit_common.cmake) + +# Test source files +set(TEST_SOURCES + test_runner.c + mocks/wasm_loader_mock.c + ${WAMR_RUNTIME_LIB_SOURCE} +) + +# Create test executable +add_executable(wasm-runtime-test ${TEST_SOURCES}) + +target_include_directories(wasm-runtime-test PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/mocks +) + +# Link dependencies +target_link_libraries(wasm-runtime-test cmocka::cmocka m) +target_link_options(wasm-runtime-test PRIVATE -Wl,--wrap=wasm_loader_load) + +# Add to ctest +add_test(NAME wasm-runtime-test COMMAND wasm-runtime-test) +set_tests_properties(wasm-runtime-test PROPERTIES TIMEOUT 30) \ No newline at end of file diff --git a/tests/unit/wasm-runtime/mocks/wasm_loader_mock.c b/tests/unit/wasm-runtime/mocks/wasm_loader_mock.c new file mode 100644 index 000000000..065129ea9 --- /dev/null +++ b/tests/unit/wasm-runtime/mocks/wasm_loader_mock.c @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include +#include "wasm_loader_mock.h" +#include "wasm_export.h" +#include "bh_platform.h" + +WASMModule * +__wrap_wasm_loader_load(uint8 *buf, uint32 size, +#if WASM_ENABLE_MULTI_MODULE != 0 + bool main_module, +#endif + const LoadArgs *args, char *error_buf, uint32 error_buf_size) +{ + /* Check expected parameters */ + check_expected_ptr(buf); + check_expected_uint(size); +#if WASM_ENABLE_MULTI_MODULE != 0 + check_expected_uint(main_module); +#endif + check_expected_ptr(args); + + /* Mock error buffer writing if provided */ + bool populate_error = mock_type(bool); + if (populate_error && error_buf) { + const char *error_msg = mock_ptr_type(const char *); + if (error_msg && error_buf_size > 0) { + strncpy(error_buf, error_msg, error_buf_size); + if (error_buf_size > 0) { + error_buf[error_buf_size - 1] = '\0'; + } + } + } + + /* Return mocked module pointer */ + return mock_ptr_type(WASMModule *); +} diff --git a/tests/unit/wasm-runtime/mocks/wasm_loader_mock.h b/tests/unit/wasm-runtime/mocks/wasm_loader_mock.h new file mode 100644 index 000000000..69abe52c5 --- /dev/null +++ b/tests/unit/wasm-runtime/mocks/wasm_loader_mock.h @@ -0,0 +1,32 @@ +#ifndef WASM_LOADER_MOCK_H +#define WASM_LOADER_MOCK_H + +#include +#include +#include "wasm_loader.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Mock function declarations - only compiled when WAMR_BUILD_TEST is defined */ +#ifdef WAMR_BUILD_TEST + +WASMModule * +wasm_loader_load(uint8 *buf, uint32 size, +#if WASM_ENABLE_MULTI_MODULE != 0 + bool main_module, +#endif + const LoadArgs *args, char *error_buf, uint32 error_buf_size); + +WASMModule * +wasm_loader_load_from_sections(WASMSection *section_list, char *error_buf, + uint32 error_buf_size); + +#endif /* WAMR_BUILD_TEST */ + +#ifdef __cplusplus +} +#endif + +#endif /* WASM_LOADER_MOCK_H */ \ No newline at end of file diff --git a/tests/unit/wasm-runtime/test_runner.c b/tests/unit/wasm-runtime/test_runner.c new file mode 100644 index 000000000..8369119e2 --- /dev/null +++ b/tests/unit/wasm-runtime/test_runner.c @@ -0,0 +1,30 @@ +#include +#include +#include +#include +#include + +/* Include test implementations */ +#include "wasm_runtime_test.c" + +int +main(void) +{ + int result = 0; + + /* Run all test groups */ + // result |= cmocka_run_group_tests(set_error_buf_tests, NULL, NULL); + // result |= cmocka_run_group_tests(set_error_buf_v_tests, NULL, NULL); + + /* Run tests with multi-module enabled */ + result |= cmocka_run_group_tests(wasm_load_tests_multi_module_enabled, NULL, + NULL); + + /* Run tests with multi-module disabled (if compiled) */ +#if WASM_ENABLE_MULTI_MODULE == 0 + result |= cmocka_run_group_tests(wasm_load_tests_multi_module_disabled, + NULL, NULL); +#endif + + return result; +} \ No newline at end of file diff --git a/tests/unit/wasm-runtime/wasm_runtime_test.c b/tests/unit/wasm-runtime/wasm_runtime_test.c new file mode 100644 index 000000000..cbb0c9287 --- /dev/null +++ b/tests/unit/wasm-runtime/wasm_runtime_test.c @@ -0,0 +1,345 @@ +#include +#include +#include +#include +#include +#include + +/* Include the actual implementation with test visibility */ +#if WAMR_BUILD_TEST != 1 + #error "WAMR_BUILD_TEST must be defined as 1 to include test implementations" +#endif + +#include "wasm_runtime.h" +#include "wasm_loader_mock.h" +#include "wasm_export.h" +#include "wasm_runtime.h" +#include "bh_platform.h" + +WASM_RUNTIME_API_INTER void +set_error_buf(char *error_buf, uint32 error_buf_size, const char *string); + +WASM_RUNTIME_API_INTER void +set_error_buf_v(char *error_buf, uint32 error_buf_size, const char *format, ...); + +/* ==================== set_error_buf() Tests ==================== */ + +static void +test_set_error_buf_null_buffer(void **state) +{ + (void)state; + char dummy_buffer[128]; + + /* Should not crash or write anything */ + set_error_buf(NULL, sizeof(dummy_buffer), "test error"); + set_error_buf(NULL, 0, "test error"); + set_error_buf(NULL, 128, NULL); +} + +static void +test_set_error_buf_valid_buffer(void **state) +{ + (void)state; + char error_buf[256]; + const char *test_error = "test error message"; + + memset(error_buf, 0xAA, sizeof(error_buf)); /* Fill with pattern */ + set_error_buf(error_buf, sizeof(error_buf), test_error); + + /* Check prefix is added */ + assert_string_not_equal(error_buf, test_error); + assert_true(strstr(error_buf, "WASM module instantiate failed:") != NULL); + assert_true(strstr(error_buf, test_error) != NULL); +} + +static void +test_set_error_buf_buffer_overflow(void **state) +{ + (void)state; + char error_buf[10]; /* Small buffer */ + const char *long_error = + "This is a very long error message that should be truncated"; + + memset(error_buf, 0xAA, sizeof(error_buf)); + set_error_buf(error_buf, sizeof(error_buf), long_error); + + /* Should be null-terminated and not overflow */ + assert_true(strlen(error_buf) < sizeof(error_buf)); + assert_true(error_buf[sizeof(error_buf) - 1] == '\0' + || error_buf[sizeof(error_buf) - 1] == (char)0xAA); +} + +static void +test_set_error_buf_exact_size(void **state) +{ + (void)state; + /* Test with buffer exactly sized for message */ + const char *error_msg = "err"; + char error_buf[256]; + int needed_size = + snprintf(NULL, 0, "WASM module instantiate failed: %s", error_msg) + 1; + + char exact_buf[needed_size]; + set_error_buf(exact_buf, sizeof(exact_buf), error_msg); + + /* Should be null-terminated */ + assert_true(exact_buf[sizeof(exact_buf) - 1] == '\0'); +} + +static void +test_wasm_load_null_buffer(void **state) +{ + (void)state; + LoadArgs args = { 0 }; + char error_buf[128]; + WASMModule *result; + + /* The actual behavior depends on wasm_loader_load implementation. + For unit test, we expect it to call wasm_loader_load with NULL buffer. + We'll set expectation for NULL buffer. */ + expect_uint_value(__wrap_wasm_loader_load, buf, (uintptr_t)NULL); + expect_uint_value(__wrap_wasm_loader_load, size, 0); +#if WASM_ENABLE_MULTI_MODULE != 0 + expect_uint_value(__wrap_wasm_loader_load, main_module, false); +#endif + expect_uint_value(__wrap_wasm_loader_load, args, (uintptr_t)&args); + will_return(__wrap_wasm_loader_load, false); /* Don't populate error buffer */ + will_return(__wrap_wasm_loader_load, NULL); + + result = wasm_load(NULL, 0, +#if WASM_ENABLE_MULTI_MODULE != 0 + false, +#endif + &args, error_buf, sizeof(error_buf)); + assert_null(result); +} + +static void +test_set_error_buf_empty_string(void **state) +{ + (void)state; + char error_buf[128]; + + set_error_buf(error_buf, sizeof(error_buf), ""); + assert_true(strstr(error_buf, "WASM module instantiate failed:") != NULL); +} + +static void +test_set_error_buf_null_string(void **state) +{ + (void)state; + char error_buf[128]; + + /* NULL string should be handled (may crash or handle gracefully) */ + set_error_buf(error_buf, sizeof(error_buf), NULL); + /* If it doesn't crash, we consider it passed */ +} + +const struct CMUnitTest set_error_buf_tests[] = { + cmocka_unit_test(test_set_error_buf_null_buffer), + cmocka_unit_test(test_set_error_buf_valid_buffer), + cmocka_unit_test(test_set_error_buf_buffer_overflow), + cmocka_unit_test(test_set_error_buf_exact_size), + cmocka_unit_test(test_set_error_buf_empty_string), + cmocka_unit_test(test_set_error_buf_null_string), +}; + +/* ==================== set_error_buf_v() Tests ==================== */ + +static void +test_set_error_buf_v_basic_formatting(void **state) +{ + (void)state; + char error_buf[256]; + + set_error_buf_v(error_buf, sizeof(error_buf), "Error %d: %s", 42, + "test"); + + assert_true(strstr(error_buf, "WASM module instantiate failed:") != NULL); + assert_true(strstr(error_buf, "Error 42: test") != NULL); +} + +static void +test_set_error_buf_v_multiple_args(void **state) +{ + (void)state; + char error_buf[256]; + + set_error_buf_v(error_buf, sizeof(error_buf), "%s %d %u %f", "test", + -1, 100, 3.14); + /* Just verify it doesn't crash with multiple args */ + assert_true(strlen(error_buf) > 0); +} + +static void +test_set_error_buf_v_null_format(void **state) +{ + (void)state; + char error_buf[128]; + + /* NULL format - should handle gracefully or crash */ + set_error_buf_v(error_buf, sizeof(error_buf), NULL); + /* If no crash, test passes */ +} + +static void +test_set_error_buf_v_internal_buffer_overflow(void **state) +{ + (void)state; + char error_buf[256]; + /* Create string longer than internal 128-byte buffer */ + char long_str[200]; + memset(long_str, 'A', sizeof(long_str) - 1); + long_str[sizeof(long_str) - 1] = '\0'; + + set_error_buf_v(error_buf, sizeof(error_buf), "%s", long_str); + /* Should truncate internally but not crash */ + assert_true(strlen(error_buf) < sizeof(error_buf)); +} + +const struct CMUnitTest set_error_buf_v_tests[] = { + cmocka_unit_test(test_set_error_buf_v_basic_formatting), + cmocka_unit_test(test_set_error_buf_v_multiple_args), + cmocka_unit_test(test_set_error_buf_v_null_format), + cmocka_unit_test(test_set_error_buf_v_internal_buffer_overflow), +}; + +/* ==================== wasm_load() Tests ==================== */ + +static void +test_wasm_load_success(void **state) +{ + (void)state; + uint8_t buffer[100] = { 0 }; + LoadArgs args = { 0 }; + char error_buf[128]; + WASMModule *mock_module = (WASMModule *)0xDEADBEEF; + + /* Setup expectations for wasm_loader_load */ + expect_uint_value(__wrap_wasm_loader_load, buf, (uintptr_t)buffer); + expect_uint_value(__wrap_wasm_loader_load, size, 100); +#if WASM_ENABLE_MULTI_MODULE != 0 + expect_uint_value(__wrap_wasm_loader_load, main_module, true); +#endif + expect_uint_value(__wrap_wasm_loader_load, args, (uintptr_t)&args); + /* Don't populate error buffer */ + will_return(__wrap_wasm_loader_load, false); + will_return(__wrap_wasm_loader_load, mock_module); + + WASMModule *result = wasm_load(buffer, 100, +#if WASM_ENABLE_MULTI_MODULE != 0 + true, +#endif + &args, error_buf, sizeof(error_buf)); + + assert_ptr_equal(result, mock_module); +} + +static void +test_wasm_load_error_buffer_populated(void **state) +{ + (void)state; + uint8_t buffer[50] = { 0 }; + LoadArgs args = { 0 }; + char error_buf[128] = { 0 }; + const char *error_msg = "Loader error"; + WASMModule *mock_module = NULL; /* Simulate failure */ + + /* Expect wasm_loader_load to be called and return NULL */ + expect_uint_value(__wrap_wasm_loader_load, buf, (uintptr_t)buffer); + expect_uint_value(__wrap_wasm_loader_load, size, 50); +#if WASM_ENABLE_MULTI_MODULE != 0 + expect_uint_value(__wrap_wasm_loader_load, main_module, true); +#endif + expect_uint_value(__wrap_wasm_loader_load, args, (uintptr_t)&args); + /* Tell mock to populate error buffer */ + will_return(__wrap_wasm_loader_load, true); /* populate error_buf */ + will_return(__wrap_wasm_loader_load, error_msg); + will_return(__wrap_wasm_loader_load, mock_module); + + WASMModule *result = wasm_load(buffer, 50, +#if WASM_ENABLE_MULTI_MODULE != 0 + true, +#endif + &args, error_buf, sizeof(error_buf)); + + assert_null(result); + /* Check that error buffer was populated (by mock) */ + assert_string_equal(error_buf, error_msg); +} + +static void +test_wasm_load_null_error_buffer(void **state) +{ + (void)state; + uint8_t buffer[50] = { 0 }; + LoadArgs args = { 0 }; + WASMModule *mock_module = (WASMModule *)0xDEADBEEF; + + /* Should work even with NULL error buffer */ + expect_uint_value(__wrap_wasm_loader_load, buf, (uintptr_t)buffer); + expect_uint_value(__wrap_wasm_loader_load, size, 50); +#if WASM_ENABLE_MULTI_MODULE != 0 + expect_uint_value(__wrap_wasm_loader_load, main_module, true); +#endif + expect_uint_value(__wrap_wasm_loader_load, args, (uintptr_t)&args); + will_return(__wrap_wasm_loader_load, + false); /* Don't populate error buffer (it's NULL) */ + will_return(__wrap_wasm_loader_load, mock_module); + + WASMModule *result = wasm_load(buffer, 50, +#if WASM_ENABLE_MULTI_MODULE != 0 + true, +#endif + &args, NULL, 0); + + assert_ptr_equal(result, mock_module); +} + +static void +test_wasm_load_zero_error_buffer_size(void **state) +{ + (void)state; + uint8_t buffer[50] = { 0 }; + LoadArgs args = { 0 }; + char error_buf[128]; + WASMModule *mock_module = (WASMModule *)0xDEADBEEF; + + /* Should work even with zero error buffer size */ + expect_uint_value(__wrap_wasm_loader_load, buf, (uintptr_t)buffer); + expect_uint_value(__wrap_wasm_loader_load, size, 50); +#if WASM_ENABLE_MULTI_MODULE != 0 + expect_uint_value(__wrap_wasm_loader_load, main_module, true); +#endif + expect_uint_value(__wrap_wasm_loader_load, args, (uintptr_t)&args); + will_return(__wrap_wasm_loader_load, + false); /* Don't populate error buffer (size is 0) */ + will_return(__wrap_wasm_loader_load, mock_module); + + WASMModule *result = wasm_load(buffer, 50, +#if WASM_ENABLE_MULTI_MODULE != 0 + true, +#endif + &args, error_buf, 0); + + assert_ptr_equal(result, mock_module); +} + +/* Test group for multi-module enabled case */ +const struct CMUnitTest wasm_load_tests_multi_module_enabled[] = { + cmocka_unit_test(test_wasm_load_success), + cmocka_unit_test(test_wasm_load_null_buffer), + cmocka_unit_test(test_wasm_load_error_buffer_populated), + cmocka_unit_test(test_wasm_load_null_error_buffer), + cmocka_unit_test(test_wasm_load_zero_error_buffer_size), +}; + +/* Test group for multi-module disabled case (identical tests, + compiled conditionally) */ +const struct CMUnitTest wasm_load_tests_multi_module_disabled[] = { + cmocka_unit_test(test_wasm_load_success), + cmocka_unit_test(test_wasm_load_null_buffer), + cmocka_unit_test(test_wasm_load_error_buffer_populated), + cmocka_unit_test(test_wasm_load_null_error_buffer), + cmocka_unit_test(test_wasm_load_zero_error_buffer_size), +}; \ No newline at end of file