fix: Windows MinGW CTest compatibility - DLL loading and module paths

- Add cmake -E chdir wrapper for CTest on Windows to resolve DLL loading
- Auto-copy MinGW runtime DLLs to build directories during configure
- Fix module paths in integration tests (.so -> .dll for Windows)
- Update grove_add_test macro for cross-platform test registration

Tests now pass: 55% (16/29) on Windows MinGW

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
StillHammer 2025-12-30 20:04:44 +07:00
parent 0540fbf526
commit edf4d76844
16 changed files with 5497 additions and 5407 deletions

View File

@ -199,6 +199,24 @@ option(GROVE_BUILD_TESTS "Build GroveEngine tests" ON)
if(GROVE_BUILD_TESTS)
enable_testing()
# Windows/MinGW: Copy runtime DLLs to build directories for CTest
if(WIN32 AND MINGW)
get_filename_component(MINGW_BIN_DIR ${CMAKE_CXX_COMPILER} DIRECTORY)
set(MINGW_RUNTIME_DLLS libgcc_s_seh-1.dll libstdc++-6.dll libwinpthread-1.dll)
foreach(DLL ${MINGW_RUNTIME_DLLS})
set(DLL_PATH "${MINGW_BIN_DIR}/${DLL}")
if(EXISTS "${DLL_PATH}")
file(COPY "${DLL_PATH}" DESTINATION "${CMAKE_BINARY_DIR}")
file(COPY "${DLL_PATH}" DESTINATION "${CMAKE_BINARY_DIR}/tests")
file(COPY "${DLL_PATH}" DESTINATION "${CMAKE_BINARY_DIR}/external/StillHammer/topictree/tests")
endif()
endforeach()
message(STATUS "MinGW runtime DLLs copied for CTest")
endif()
add_subdirectory(tests)
endif()

View File

@ -9,6 +9,17 @@ FetchContent_Declare(
)
FetchContent_MakeAvailable(Catch2)
# Windows/MinGW: Copy runtime DLLs at configure time
if(WIN32 AND MINGW)
get_filename_component(MINGW_BIN_DIR ${CMAKE_CXX_COMPILER} DIRECTORY)
set(MINGW_DLLS libgcc_s_seh-1.dll libstdc++-6.dll libwinpthread-1.dll)
foreach(DLL ${MINGW_DLLS})
if(EXISTS "${MINGW_BIN_DIR}/${DLL}")
file(COPY "${MINGW_BIN_DIR}/${DLL}" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}")
endif()
endforeach()
endif()
# Helper macro to create test executables
macro(add_topictree_test test_name)
add_executable(${test_name} ${test_name}.cpp)
@ -17,7 +28,14 @@ macro(add_topictree_test test_name)
Catch2::Catch2WithMain
)
target_compile_features(${test_name} PRIVATE cxx_std_17)
add_test(NAME ${test_name} COMMAND ${test_name})
if(WIN32)
# Use cmd.exe start to properly find DLLs in working directory
add_test(NAME ${test_name}
COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_CURRENT_BINARY_DIR}
$<TARGET_FILE:${test_name}>)
else()
add_test(NAME ${test_name} COMMAND ${test_name} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
endif()
endmacro()
# Add all scenario tests

View File

@ -60,6 +60,33 @@ add_custom_command(TARGET test_engine_hotreload POST_BUILD
# Integration Tests
# ================================================================================
# ================================================================================
# Windows/MinGW: CTest helper for DLL loading
# ================================================================================
# Copy MinGW runtime DLLs at configure time
if(WIN32 AND MINGW)
get_filename_component(MINGW_BIN_DIR ${CMAKE_CXX_COMPILER} DIRECTORY)
set(MINGW_DLLS libgcc_s_seh-1.dll libstdc++-6.dll libwinpthread-1.dll)
foreach(DLL ${MINGW_DLLS})
if(EXISTS "${MINGW_BIN_DIR}/${DLL}")
file(COPY "${MINGW_BIN_DIR}/${DLL}" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}")
endif()
endforeach()
endif()
# Test macro - uses cmake -E chdir on Windows to ensure proper DLL loading
macro(grove_add_test test_name test_target working_dir)
if(WIN32 AND MINGW)
add_test(NAME ${test_name}
COMMAND ${CMAKE_COMMAND} -E chdir ${working_dir} $<TARGET_FILE:${test_target}>)
else()
add_test(NAME ${test_name} COMMAND ${test_target} WORKING_DIRECTORY ${working_dir})
endif()
endmacro()
# Helpers library (partagée par tous les tests)
add_library(test_helpers STATIC
helpers/TestMetrics.cpp
@ -108,7 +135,7 @@ target_link_libraries(test_01_production_hotreload PRIVATE
add_dependencies(test_01_production_hotreload TankModule)
# CTest integration
add_test(NAME ProductionHotReload COMMAND test_01_production_hotreload WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
grove_add_test(ProductionHotReload test_01_production_hotreload ${CMAKE_CURRENT_BINARY_DIR})
# ChaosModule pour tests de robustesse
add_library(ChaosModule SHARED
@ -135,7 +162,7 @@ target_link_libraries(test_02_chaos_monkey PRIVATE
add_dependencies(test_02_chaos_monkey ChaosModule)
# CTest integration
add_test(NAME ChaosMonkey COMMAND test_02_chaos_monkey WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
grove_add_test(ChaosMonkey test_02_chaos_monkey ${CMAKE_CURRENT_BINARY_DIR})
# StressModule pour tests de stabilité long-terme
add_library(StressModule SHARED
@ -162,7 +189,7 @@ target_link_libraries(test_03_stress_test PRIVATE
add_dependencies(test_03_stress_test StressModule)
# CTest integration
add_test(NAME StressTest COMMAND test_03_stress_test WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
grove_add_test(StressTest test_03_stress_test ${CMAKE_CURRENT_BINARY_DIR})
# Test 04: Race Condition Hunter - Concurrent compilation & reload
add_executable(test_04_race_condition
@ -179,7 +206,7 @@ target_link_libraries(test_04_race_condition PRIVATE
add_dependencies(test_04_race_condition TestModule)
# CTest integration
add_test(NAME RaceConditionHunter COMMAND test_04_race_condition WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
grove_add_test(RaceConditionHunter test_04_race_condition ${CMAKE_CURRENT_BINARY_DIR})
# LeakTestModule pour memory leak detection
add_library(LeakTestModule SHARED
@ -205,7 +232,7 @@ target_link_libraries(test_05_memory_leak PRIVATE
add_dependencies(test_05_memory_leak LeakTestModule)
# CTest integration
add_test(NAME MemoryLeakHunter COMMAND test_05_memory_leak WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
grove_add_test(MemoryLeakHunter test_05_memory_leak ${CMAKE_CURRENT_BINARY_DIR})
# Memory leak profiler (detailed analysis)
# TODO: Implement profile_memory_leak.cpp
@ -246,7 +273,7 @@ target_link_libraries(test_06_error_recovery PRIVATE
add_dependencies(test_06_error_recovery ErrorRecoveryModule)
# CTest integration
add_test(NAME ErrorRecovery COMMAND test_06_error_recovery WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
grove_add_test(ErrorRecovery test_06_error_recovery ${CMAKE_CURRENT_BINARY_DIR})
# HeavyStateModule pour tests de limites
add_library(HeavyStateModule SHARED
@ -284,8 +311,8 @@ target_link_libraries(test_12_datanode PRIVATE
)
# CTest integration
add_test(NAME LimitsTest COMMAND test_07_limits WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
add_test(NAME DataNodeTest COMMAND test_12_datanode WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
grove_add_test(LimitsTest test_07_limits ${CMAKE_CURRENT_BINARY_DIR})
grove_add_test(DataNodeTest test_12_datanode ${CMAKE_CURRENT_BINARY_DIR})
# ConfigWatcherModule for cross-system integration tests
add_library(ConfigWatcherModule SHARED
@ -350,7 +377,7 @@ add_dependencies(test_13_cross_system
)
# CTest integration
add_test(NAME CrossSystemIntegration COMMAND test_13_cross_system WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
grove_add_test(CrossSystemIntegration test_13_cross_system ${CMAKE_CURRENT_BINARY_DIR})
# ConfigurableModule pour tests de config hot-reload
add_library(ConfigurableModule SHARED
@ -377,7 +404,7 @@ target_link_libraries(test_08_config_hotreload PRIVATE
add_dependencies(test_08_config_hotreload ConfigurableModule)
# CTest integration
add_test(NAME ConfigHotReload COMMAND test_08_config_hotreload WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
grove_add_test(ConfigHotReload test_08_config_hotreload ${CMAKE_CURRENT_BINARY_DIR})
# BaseModule for dependency testing (no dependencies)
add_library(BaseModule SHARED
@ -426,7 +453,7 @@ target_link_libraries(test_09_module_dependencies PRIVATE
add_dependencies(test_09_module_dependencies BaseModule DependentModule IndependentModule)
# CTest integration
add_test(NAME ModuleDependencies COMMAND test_09_module_dependencies WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
grove_add_test(ModuleDependencies test_09_module_dependencies ${CMAKE_CURRENT_BINARY_DIR})
# GameLogicModuleV1 for multi-version testing (baseline version)
add_library(GameLogicModuleV1 SHARED
@ -475,7 +502,7 @@ target_link_libraries(test_10_multiversion_coexistence PRIVATE
add_dependencies(test_10_multiversion_coexistence GameLogicModuleV1 GameLogicModuleV2 GameLogicModuleV3)
# CTest integration
add_test(NAME MultiVersionCoexistence COMMAND test_10_multiversion_coexistence WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
grove_add_test(MultiVersionCoexistence test_10_multiversion_coexistence ${CMAKE_CURRENT_BINARY_DIR})
# ================================================================================
# IO System Test Modules (Scenario 11)
@ -550,7 +577,7 @@ target_link_libraries(test_11_io_system PRIVATE
add_dependencies(test_11_io_system ProducerModule ConsumerModule BroadcastModule BatchModule IOStressModule)
# CTest integration
add_test(NAME IOSystemStress COMMAND test_11_io_system WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
grove_add_test(IOSystemStress test_11_io_system ${CMAKE_CURRENT_BINARY_DIR})
# ================================================================================
# Benchmarks
@ -648,7 +675,7 @@ if(GROVE_BUILD_BGFX_RENDERER)
)
# CTest integration
add_test(NAME BgfxRHI COMMAND test_20_bgfx_rhi WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
grove_add_test(BgfxRHI test_20_bgfx_rhi ${CMAKE_CURRENT_BINARY_DIR})
# Test 21: Visual Triangle Test (requires SDL2 and display)
find_package(SDL2 QUIET)
@ -1081,9 +1108,9 @@ if(GROVE_BUILD_BGFX_RENDERER)
Catch2::Catch2WithMain
)
add_test(NAME PipelineHeadless COMMAND test_pipeline_headless WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
grove_add_test(PipelineHeadless test_pipeline_headless ${CMAKE_BINARY_DIR})
add_test(NAME BgfxSpritesHeadless COMMAND test_22_bgfx_sprites_headless WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
grove_add_test(BgfxSpritesHeadless test_22_bgfx_sprites_headless ${CMAKE_CURRENT_BINARY_DIR})
endif()
# ================================================================================
@ -1117,7 +1144,7 @@ if(GROVE_BUILD_UI_MODULE AND GROVE_BUILD_BGFX_RENDERER)
add_dependencies(IT_014_ui_module_integration TestControllerModule)
# CTest integration
add_test(NAME UIModuleIntegration COMMAND IT_014_ui_module_integration WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
grove_add_test(UIModuleIntegration IT_014_ui_module_integration ${CMAKE_CURRENT_BINARY_DIR})
message(STATUS "Integration test 'IT_014_ui_module_integration' enabled")
endif()
@ -1138,7 +1165,7 @@ if(GROVE_BUILD_UI_MODULE)
)
# CTest integration
add_test(NAME InputUIIntegration COMMAND IT_015_input_ui_integration WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
grove_add_test(InputUIIntegration IT_015_input_ui_integration ${CMAKE_CURRENT_BINARY_DIR})
message(STATUS "Integration test 'IT_015_input_ui_integration' enabled (simplified, no SDL2)")
endif()
@ -1156,7 +1183,7 @@ target_link_libraries(IT_015_input_ui_integration_minimal PRIVATE
)
# CTest integration
add_test(NAME InputUIIntegration_Minimal COMMAND IT_015_input_ui_integration_minimal WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
grove_add_test(InputUIIntegration_Minimal IT_015_input_ui_integration_minimal ${CMAKE_CURRENT_BINARY_DIR})
message(STATUS "Integration test 'IT_015_input_ui_integration_minimal' enabled (IIO-only)")

View File

@ -29,6 +29,9 @@ int main() {
// Charger module
std::string modulePath = "./libTankModule.so";
#ifdef _WIN32
modulePath = "./libTankModule.dll";
#endif
auto module = loader.load(modulePath, "TankModule", false);
// Config

View File

@ -48,6 +48,9 @@ int main() {
// Load module
std::string modulePath = "./libChaosModule.so";
#ifdef _WIN32
modulePath = "./libChaosModule.dll";
#endif
auto module = loader.load(modulePath, "ChaosModule", false);
// Configure module avec seed ALÉATOIRE basé sur le temps

View File

@ -52,7 +52,13 @@ constexpr int TOTAL_FRAMES = EXPECTED_RELOADS * RELOAD_INTERVAL; // 36000 frame
constexpr size_t MAX_MEMORY_GROWTH_MB = 50;
// Paths
#ifdef _WIN32
const std::string MODULE_PATH = "./libStressModule.dll";
#else
const std::string MODULE_PATH = "./libStressModule.so";
#endif
#ifdef _WIN32
#endif
int main() {
TestReporter reporter("Stress Test - 10 Minute Stability");

View File

@ -31,6 +31,9 @@ int main() {
const float FRAME_TIME = 1.0f / TARGET_FPS;
std::string modulePath = "./libTestModule.so";
#ifdef _WIN32
modulePath = "./libTestModule.dll";
#endif
// Test runs from build/tests/, so source files are at ../../tests/modules/
std::string sourcePath = "../../tests/modules/TestModule.cpp";
std::string buildDir = "build";

View File

@ -204,6 +204,9 @@ int main() {
// Find module path
fs::path modulePath = "./libLeakTestModule.so";
#ifdef _WIN32
modulePath = "./libLeakTestModule.dll";
#endif
if (!fs::exists(modulePath)) {
std::cerr << "❌ Module not found: " << modulePath << "\n";
return 1;

View File

@ -49,6 +49,9 @@ int main() {
// Charger module
std::string modulePath = "./libErrorRecoveryModule.so";
#ifdef _WIN32
modulePath = "./libErrorRecoveryModule.dll";
#endif
auto module = loader.load(modulePath, "ErrorRecoveryModule", false);
// Config: crash à frame 60, type runtime_error

View File

@ -42,6 +42,9 @@ int main() {
auto moduleSystem = std::make_unique<SequentialModuleSystem>();
std::string modulePath = "./libHeavyStateModule.so";
#ifdef _WIN32
modulePath = "./libHeavyStateModule.dll";
#endif
auto module = loader.load(modulePath, "HeavyStateModule", false);
// Config: particules réduites pour test rapide, mais assez pour être significatif

View File

@ -48,6 +48,9 @@ int main() {
// Charger module
std::string modulePath = "./libConfigurableModule.so";
#ifdef _WIN32
modulePath = "./libConfigurableModule.dll";
#endif
auto module = loader.load(modulePath, "ConfigurableModule", false);
// Config initiale

View File

@ -368,11 +368,11 @@ int main() {
// === SETUP: Load modules with dependencies ===
std::cout << "=== Setup: Load modules with dependencies ===\n";
ASSERT_TRUE(engine.loadModule("BaseModule", "./libBaseModule.so"),
ASSERT_TRUE(engine.loadModule("BaseModule", "./libBaseModule.dll"),
"Should load BaseModule");
ASSERT_TRUE(engine.loadModule("DependentModule", "./libDependentModule.so"),
ASSERT_TRUE(engine.loadModule("DependentModule", "./libDependentModule.dll"),
"Should load DependentModule");
ASSERT_TRUE(engine.loadModule("IndependentModule", "./libIndependentModule.so"),
ASSERT_TRUE(engine.loadModule("IndependentModule", "./libIndependentModule.dll"),
"Should load IndependentModule");
reporter.addAssertion("modules_loaded", true);

View File

@ -327,7 +327,7 @@ int main() {
std::cout << "=== Phase 0: Setup Baseline (v1 with 100 entities) ===\n";
// Load v1
std::string v1Path = "./libGameLogicModuleV1.so";
std::string v1Path = "./libGameLogicModuleV1.dll";
ASSERT_TRUE(engine.loadModuleVersion("GameLogic", 1, v1Path),
"Load GameLogic v1");
@ -347,7 +347,7 @@ int main() {
auto phase1Start = std::chrono::high_resolution_clock::now();
// Load v2
std::string v2Path = "./libGameLogicModuleV2.so";
std::string v2Path = "./libGameLogicModuleV2.dll";
ASSERT_TRUE(engine.loadModuleVersion("GameLogic", 2, v2Path),
"Load GameLogic v2");
@ -482,7 +482,7 @@ int main() {
std::cout << "\n=== Phase 5: Three-Way Coexistence (v1, v2, v3) ===\n";
// Load v3
std::string v3Path = "./libGameLogicModuleV3.so";
std::string v3Path = "./libGameLogicModuleV3.dll";
ASSERT_TRUE(engine.loadModuleVersion("GameLogic", 3, v3Path),
"Load GameLogic v3");

View File

@ -183,11 +183,11 @@ int main() {
// Load all IO test modules
bool loadSuccess = true;
loadSuccess &= engine.loadModule("ProducerModule", "./libProducerModule.so");
loadSuccess &= engine.loadModule("ConsumerModule", "./libConsumerModule.so");
loadSuccess &= engine.loadModule("BroadcastModule", "./libBroadcastModule.so");
loadSuccess &= engine.loadModule("BatchModule", "./libBatchModule.so");
loadSuccess &= engine.loadModule("IOStressModule", "./libIOStressModule.so");
loadSuccess &= engine.loadModule("ProducerModule", "./libProducerModule.dll");
loadSuccess &= engine.loadModule("ConsumerModule", "./libConsumerModule.dll");
loadSuccess &= engine.loadModule("BroadcastModule", "./libBroadcastModule.dll");
loadSuccess &= engine.loadModule("BatchModule", "./libBatchModule.dll");
loadSuccess &= engine.loadModule("IOStressModule", "./libIOStressModule.dll");
if (!loadSuccess) {
std::cerr << "❌ Failed to load required modules\n";

View File

@ -32,7 +32,7 @@ public:
private:
std::vector<Tank> tanks;
int frameCount = 0;
std::string moduleVersion = "v1.0";std::shared_ptr<spdlog::logger> logger;
std::string moduleVersion = "v2.0 HOT-RELOADED";:shared_ptr<spdlog::logger> logger;
std::unique_ptr<IDataNode> config;
void updateTank(Tank& tank, float dt);

View File

@ -5,7 +5,7 @@
#include <memory>
// This line will be modified by AutoCompiler during race condition tests
std::string moduleVersion = "v1";
std::string moduleVersion = "v10";
namespace grove {