From e5855dd5131ee2a211aebeb67048e57787731866 Mon Sep 17 00:00:00 2001 From: Jorge Morte Date: Fri, 17 Apr 2026 11:08:52 +0200 Subject: [PATCH] platform: purge expired SSR routes periodically --- lib/platform/linux/platform.c | 1 + test/CMakeLists.txt | 70 +++++++++++++++++++++++-------- test/platform_ssr_unit_tests.cpp | 71 ++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 17 deletions(-) create mode 100644 test/platform_ssr_unit_tests.cpp diff --git a/lib/platform/linux/platform.c b/lib/platform/linux/platform.c index 018b11f..d0754da 100644 --- a/lib/platform/linux/platform.c +++ b/lib/platform/linux/platform.c @@ -115,6 +115,7 @@ static void * dispatch_indication(void * unused) // Force a garbage collect (to be sure it's called even if no frag are received) reassembly_garbage_collect(); + ssr_purge_expired(); // Check if we wake up but nothing in queue if (m_queue_empty) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 9e4fd83..b36b9ad 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -7,6 +7,7 @@ set(CMAKE_BUILD_TYPE RelWithDebInfo) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) add_compile_options(-Wall -Werror -Wextra) +option(WPC_ENABLE_COVERAGE "Enable coverage instrumentation for unit-test builds" OFF) set(WPC_LIB_DIR "${CMAKE_CURRENT_LIST_DIR}/../lib/") set(WPC_INTERNAL_INCLUDE_DIRS @@ -15,6 +16,19 @@ set(WPC_INTERNAL_INCLUDE_DIRS ${WPC_LIB_DIR}/wpc/include ) +function(enable_coverage_if_requested target_name) + if(NOT WPC_ENABLE_COVERAGE) + return() + endif() + + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + target_compile_options(${target_name} PRIVATE -O0 -g --coverage) + target_link_options(${target_name} PRIVATE --coverage) + else() + message(WARNING "WPC_ENABLE_COVERAGE is only supported with GCC or Clang") + endif() +endfunction() + include(FetchContent) FetchContent_Declare( wpc-lib @@ -28,6 +42,9 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(wpc-lib googletest) +enable_coverage_if_requested(wpc) +enable_coverage_if_requested(wpc_platform) + enable_testing() add_executable(${CMAKE_PROJECT_NAME} @@ -75,41 +92,60 @@ target_include_directories(meshAPI_ssr_unit PRIVATE target_compile_options(meshAPI_ssr_unit PRIVATE -ffunction-sections -fdata-sections) target_link_options(meshAPI_ssr_unit PRIVATE -Wl,--gc-sections) +enable_coverage_if_requested(meshAPI_ssr_unit) gtest_discover_tests(meshAPI_ssr_unit) -add_executable(meshAPI_wpc_internal_unit - ${WPC_LIB_DIR}/wpc/wpc_internal.c - ${CMAKE_CURRENT_LIST_DIR}/wpc_internal_ssr_unit_tests.cpp +add_executable(meshAPI_wpc_unit + ${WPC_LIB_DIR}/wpc/wpc.c + ${WPC_LIB_DIR}/wpc/wpc_ssr.c + ${CMAKE_CURRENT_LIST_DIR}/wpc_ssr_unit_tests.cpp ) -target_link_libraries(meshAPI_wpc_internal_unit +target_link_libraries(meshAPI_wpc_unit GTest::gtest_main ) -target_include_directories(meshAPI_wpc_internal_unit PRIVATE +target_include_directories(meshAPI_wpc_unit PRIVATE ${WPC_INTERNAL_INCLUDE_DIRS} ) -target_compile_options(meshAPI_wpc_internal_unit PRIVATE -ffunction-sections -fdata-sections) -target_link_options(meshAPI_wpc_internal_unit PRIVATE -Wl,--gc-sections) +target_compile_options(meshAPI_wpc_unit PRIVATE -ffunction-sections -fdata-sections) +target_link_options(meshAPI_wpc_unit PRIVATE -Wl,--gc-sections) -gtest_discover_tests(meshAPI_wpc_internal_unit) +enable_coverage_if_requested(meshAPI_wpc_unit) +gtest_discover_tests(meshAPI_wpc_unit) -add_executable(meshAPI_wpc_unit - ${WPC_LIB_DIR}/wpc/wpc.c - ${WPC_LIB_DIR}/wpc/wpc_ssr.c - ${CMAKE_CURRENT_LIST_DIR}/wpc_ssr_unit_tests.cpp +add_executable(meshAPI_platform_unit + ${CMAKE_CURRENT_LIST_DIR}/platform_ssr_unit_tests.cpp ) -target_link_libraries(meshAPI_wpc_unit +target_link_libraries(meshAPI_platform_unit GTest::gtest_main + wpc_platform ) -target_include_directories(meshAPI_wpc_unit PRIVATE +target_include_directories(meshAPI_platform_unit PRIVATE ${WPC_INTERNAL_INCLUDE_DIRS} ) -target_compile_options(meshAPI_wpc_unit PRIVATE -ffunction-sections -fdata-sections) -target_link_options(meshAPI_wpc_unit PRIVATE -Wl,--gc-sections) +enable_coverage_if_requested(meshAPI_platform_unit) +gtest_discover_tests(meshAPI_platform_unit) -gtest_discover_tests(meshAPI_wpc_unit) +add_executable(meshAPI_wpc_internal_unit + ${WPC_LIB_DIR}/wpc/wpc_internal.c + ${CMAKE_CURRENT_LIST_DIR}/wpc_internal_ssr_unit_tests.cpp +) + +target_link_libraries(meshAPI_wpc_internal_unit + GTest::gtest_main +) + +target_include_directories(meshAPI_wpc_internal_unit PRIVATE + ${WPC_INTERNAL_INCLUDE_DIRS} +) + +target_compile_options(meshAPI_wpc_internal_unit PRIVATE -ffunction-sections -fdata-sections) +target_link_options(meshAPI_wpc_internal_unit PRIVATE -Wl,--gc-sections) + +enable_coverage_if_requested(meshAPI_wpc_internal_unit) +gtest_discover_tests(meshAPI_wpc_internal_unit) diff --git a/test/platform_ssr_unit_tests.cpp b/test/platform_ssr_unit_tests.cpp new file mode 100644 index 0000000..966d483 --- /dev/null +++ b/test/platform_ssr_unit_tests.cpp @@ -0,0 +1,71 @@ +#include + +#include +#include +#include + +#define _Static_assert static_assert +extern "C" +{ +#include "platform.h" +} + +namespace +{ +std::atomic g_gc_calls{0}; +std::atomic g_ssr_purge_calls{0}; + +int no_indication(unsigned int max_ind, onIndicationReceivedLocked_cb_f cb_locked) +{ + (void) max_ind; + (void) cb_locked; + return 0; +} + +void ignore_indication(wpc_frame_t * frame, unsigned long long timestamp_ms) +{ + (void) frame; + (void) timestamp_ms; +} + +bool wait_for(std::atomic & counter, int expected, std::chrono::milliseconds timeout) +{ + const auto deadline = std::chrono::steady_clock::now() + timeout; + while (std::chrono::steady_clock::now() < deadline) + { + if (counter.load() >= expected) + { + return true; + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + return counter.load() >= expected; +} +} // namespace + +extern "C" bool reassembly_is_queue_empty(void) +{ + return true; +} + +extern "C" void reassembly_garbage_collect(void) +{ + g_gc_calls.fetch_add(1); +} + +extern "C" void ssr_purge_expired(void) +{ + g_ssr_purge_calls.fetch_add(1); +} + +TEST(PlatformSsrUnitTest, IdleDispatchWakeupPurgesExpiredSsrRoutes) +{ + g_gc_calls.store(0); + g_ssr_purge_calls.store(0); + + ASSERT_TRUE(Platform_init(no_indication, ignore_indication)); + EXPECT_TRUE(wait_for(g_gc_calls, 1, std::chrono::milliseconds(6500))); + EXPECT_TRUE(wait_for(g_ssr_purge_calls, 1, std::chrono::milliseconds(6500))); + Platform_close(); +}