# Unit test infrastructure using Catch2 v3 (amalgamated) # Catch2 amalgamated as a library target add_library(catch2_main STATIC ${CMAKE_SOURCE_DIR}/extern/catch2/catch_amalgamated.cpp ) target_include_directories(catch2_main PUBLIC ${CMAKE_SOURCE_DIR}/extern/catch2 ) # Catch2 v3 needs C++17 minimum target_compile_features(catch2_main PUBLIC cxx_std_17) # ── ASAN / UBSan propagation ──────────────────────────────── # Collect all test target names so we can apply sanitizer flags at the end. set(ALL_TEST_TARGETS "") # Helper: register a test target for ASAN/UBSan if enabled. macro(register_test_target _target) list(APPEND ALL_TEST_TARGETS ${_target}) endmacro() # Shared source files used across multiple tests set(TEST_COMMON_SOURCES ${CMAKE_SOURCE_DIR}/src/core/logger.cpp ) # Include directories matching the main target set(TEST_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/src ) set(TEST_SYSTEM_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/extern ) # ── test_packet ────────────────────────────────────────────── add_executable(test_packet test_packet.cpp ${TEST_COMMON_SOURCES} ${CMAKE_SOURCE_DIR}/src/network/packet.cpp ) target_include_directories(test_packet PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_packet SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_packet PRIVATE catch2_main) add_test(NAME packet COMMAND test_packet) register_test_target(test_packet) # ── test_srp ───────────────────────────────────────────────── add_executable(test_srp test_srp.cpp ${TEST_COMMON_SOURCES} ${CMAKE_SOURCE_DIR}/src/auth/srp.cpp ${CMAKE_SOURCE_DIR}/src/auth/big_num.cpp ${CMAKE_SOURCE_DIR}/src/auth/crypto.cpp ) target_include_directories(test_srp PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_srp SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_srp PRIVATE catch2_main OpenSSL::SSL OpenSSL::Crypto) add_test(NAME srp COMMAND test_srp) register_test_target(test_srp) # ── test_spline ────────────────────────────────────────────── add_executable(test_spline test_spline.cpp ${TEST_COMMON_SOURCES} ${CMAKE_SOURCE_DIR}/src/math/spline.cpp ) target_include_directories(test_spline PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_spline SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_spline PRIVATE catch2_main) if(TARGET glm::glm) target_link_libraries(test_spline PRIVATE glm::glm) endif() add_test(NAME spline COMMAND test_spline) register_test_target(test_spline) # ── test_transport_path_repo ───────────────────────────────── add_executable(test_transport_path_repo test_transport_path_repo.cpp ${TEST_COMMON_SOURCES} ${CMAKE_SOURCE_DIR}/src/game/transport_path_repository.cpp ${CMAKE_SOURCE_DIR}/src/math/spline.cpp ${CMAKE_SOURCE_DIR}/src/pipeline/dbc_loader.cpp ) target_include_directories(test_transport_path_repo PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_transport_path_repo SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_transport_path_repo PRIVATE catch2_main) if(TARGET glm::glm) target_link_libraries(test_transport_path_repo PRIVATE glm::glm) endif() add_test(NAME transport_path_repo COMMAND test_transport_path_repo) register_test_target(test_transport_path_repo) # ── test_opcode_table ──────────────────────────────────────── add_executable(test_opcode_table test_opcode_table.cpp ${TEST_COMMON_SOURCES} ${CMAKE_SOURCE_DIR}/src/game/opcode_table.cpp ) target_include_directories(test_opcode_table PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_opcode_table SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_opcode_table PRIVATE catch2_main) add_test(NAME opcode_table COMMAND test_opcode_table) register_test_target(test_opcode_table) # ── test_entity ────────────────────────────────────────────── add_executable(test_entity test_entity.cpp ${TEST_COMMON_SOURCES} ${CMAKE_SOURCE_DIR}/src/game/entity.cpp ${CMAKE_SOURCE_DIR}/src/math/spline.cpp ) target_include_directories(test_entity PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_entity SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_entity PRIVATE catch2_main) if(TARGET glm::glm) target_link_libraries(test_entity PRIVATE glm::glm) endif() add_test(NAME entity COMMAND test_entity) register_test_target(test_entity) # ── test_dbc_loader ────────────────────────────────────────── add_executable(test_dbc_loader test_dbc_loader.cpp ${TEST_COMMON_SOURCES} ${CMAKE_SOURCE_DIR}/src/pipeline/dbc_loader.cpp ) target_include_directories(test_dbc_loader PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_dbc_loader SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_dbc_loader PRIVATE catch2_main) add_test(NAME dbc_loader COMMAND test_dbc_loader) register_test_target(test_dbc_loader) # ── test_m2_structs ────────────────────────────────────────── # Header-only struct layout tests — no source files needed add_executable(test_m2_structs test_m2_structs.cpp ) target_include_directories(test_m2_structs PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_m2_structs SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_m2_structs PRIVATE catch2_main) if(TARGET glm::glm) target_link_libraries(test_m2_structs PRIVATE glm::glm) endif() add_test(NAME m2_structs COMMAND test_m2_structs) register_test_target(test_m2_structs) # ── test_blp_loader ────────────────────────────────────────── add_executable(test_blp_loader test_blp_loader.cpp ${TEST_COMMON_SOURCES} ${CMAKE_SOURCE_DIR}/src/pipeline/blp_loader.cpp ) target_include_directories(test_blp_loader PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_blp_loader SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_blp_loader PRIVATE catch2_main) add_test(NAME blp_loader COMMAND test_blp_loader) register_test_target(test_blp_loader) # ── test_frustum ───────────────────────────────────────────── add_executable(test_frustum test_frustum.cpp ${CMAKE_SOURCE_DIR}/src/rendering/frustum.cpp ) target_include_directories(test_frustum PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_frustum SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_frustum PRIVATE catch2_main) if(TARGET glm::glm) target_link_libraries(test_frustum PRIVATE glm::glm) endif() add_test(NAME frustum COMMAND test_frustum) register_test_target(test_frustum) # ── test_animation_ids ─────────────────────────────────────── add_executable(test_animation_ids test_animation_ids.cpp ${TEST_COMMON_SOURCES} ${CMAKE_SOURCE_DIR}/src/rendering/animation/animation_ids.cpp ${CMAKE_SOURCE_DIR}/src/pipeline/dbc_loader.cpp ) target_include_directories(test_animation_ids PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_animation_ids SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_animation_ids PRIVATE catch2_main) add_test(NAME animation_ids COMMAND test_animation_ids) register_test_target(test_animation_ids) # ── test_locomotion_fsm ────────────────────────────────────── add_executable(test_locomotion_fsm test_locomotion_fsm.cpp ${TEST_COMMON_SOURCES} ${CMAKE_SOURCE_DIR}/src/rendering/animation/locomotion_fsm.cpp ) target_include_directories(test_locomotion_fsm PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_locomotion_fsm SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_locomotion_fsm PRIVATE catch2_main) add_test(NAME locomotion_fsm COMMAND test_locomotion_fsm) register_test_target(test_locomotion_fsm) # ── test_combat_fsm ────────────────────────────────────────── add_executable(test_combat_fsm test_combat_fsm.cpp ${TEST_COMMON_SOURCES} ${CMAKE_SOURCE_DIR}/src/rendering/animation/combat_fsm.cpp ) target_include_directories(test_combat_fsm PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_combat_fsm SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_combat_fsm PRIVATE catch2_main) add_test(NAME combat_fsm COMMAND test_combat_fsm) register_test_target(test_combat_fsm) # ── test_activity_fsm ──────────────────────────────────────── add_executable(test_activity_fsm test_activity_fsm.cpp ${TEST_COMMON_SOURCES} ${CMAKE_SOURCE_DIR}/src/rendering/animation/activity_fsm.cpp ) target_include_directories(test_activity_fsm PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_activity_fsm SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_activity_fsm PRIVATE catch2_main) add_test(NAME activity_fsm COMMAND test_activity_fsm) register_test_target(test_activity_fsm) # NPC animator tests removed — NpcAnimator replaced by generic CharacterAnimator # ── test_anim_capability ───────────────────────────────────── # Header-only struct tests — no source files needed add_executable(test_anim_capability test_anim_capability.cpp ) target_include_directories(test_anim_capability PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_anim_capability SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_anim_capability PRIVATE catch2_main) add_test(NAME anim_capability COMMAND test_anim_capability) register_test_target(test_anim_capability) # ── test_indoor_shadows ────────────────────────────────────── add_executable(test_indoor_shadows test_indoor_shadows.cpp ) target_include_directories(test_indoor_shadows PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_indoor_shadows SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_indoor_shadows PRIVATE catch2_main) if(TARGET glm::glm) target_link_libraries(test_indoor_shadows PRIVATE glm::glm) endif() add_test(NAME indoor_shadows COMMAND test_indoor_shadows) register_test_target(test_indoor_shadows) # ── test_transport_components ──────────────────────────────── add_executable(test_transport_components test_transport_components.cpp ${TEST_COMMON_SOURCES} ${CMAKE_SOURCE_DIR}/src/game/transport_clock_sync.cpp ${CMAKE_SOURCE_DIR}/src/game/transport_animator.cpp ${CMAKE_SOURCE_DIR}/src/math/spline.cpp ) target_include_directories(test_transport_components PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_transport_components SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_transport_components PRIVATE catch2_main) if(TARGET glm::glm) target_link_libraries(test_transport_components PRIVATE glm::glm) endif() add_test(NAME transport_components COMMAND test_transport_components) register_test_target(test_transport_components) # ── test_world_map ──────────────────────────────────────────── add_executable(test_world_map test_world_map.cpp ) target_include_directories(test_world_map PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_world_map SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_world_map PRIVATE catch2_main) if(TARGET glm::glm) target_link_libraries(test_world_map PRIVATE glm::glm) endif() add_test(NAME world_map COMMAND test_world_map) register_test_target(test_world_map) # ── test_world_map_coordinate_projection ────────────────────── add_executable(test_world_map_coordinate_projection test_world_map_coordinate_projection.cpp ${CMAKE_SOURCE_DIR}/src/rendering/world_map/coordinate_projection.cpp ) target_include_directories(test_world_map_coordinate_projection PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_world_map_coordinate_projection SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_world_map_coordinate_projection PRIVATE catch2_main) if(TARGET glm::glm) target_link_libraries(test_world_map_coordinate_projection PRIVATE glm::glm) endif() add_test(NAME world_map_coordinate_projection COMMAND test_world_map_coordinate_projection) register_test_target(test_world_map_coordinate_projection) # ── test_world_map_map_resolver ─────────────────────────────── add_executable(test_world_map_map_resolver test_world_map_map_resolver.cpp ${CMAKE_SOURCE_DIR}/src/rendering/world_map/map_resolver.cpp ${CMAKE_SOURCE_DIR}/src/rendering/world_map/coordinate_projection.cpp ${TEST_COMMON_SOURCES} ) target_include_directories(test_world_map_map_resolver PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_world_map_map_resolver SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_world_map_map_resolver PRIVATE catch2_main) if(TARGET glm::glm) target_link_libraries(test_world_map_map_resolver PRIVATE glm::glm) endif() add_test(NAME world_map_map_resolver COMMAND test_world_map_map_resolver) register_test_target(test_world_map_map_resolver) # ── test_world_map_view_state_machine ───────────────────────── add_executable(test_world_map_view_state_machine test_world_map_view_state_machine.cpp ${CMAKE_SOURCE_DIR}/src/rendering/world_map/view_state_machine.cpp ) target_include_directories(test_world_map_view_state_machine PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_world_map_view_state_machine SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_world_map_view_state_machine PRIVATE catch2_main) if(TARGET glm::glm) target_link_libraries(test_world_map_view_state_machine PRIVATE glm::glm) endif() add_test(NAME world_map_view_state_machine COMMAND test_world_map_view_state_machine) register_test_target(test_world_map_view_state_machine) # ── test_world_map_exploration_state ────────────────────────── add_executable(test_world_map_exploration_state test_world_map_exploration_state.cpp ${CMAKE_SOURCE_DIR}/src/rendering/world_map/exploration_state.cpp ${CMAKE_SOURCE_DIR}/src/rendering/world_map/coordinate_projection.cpp ) target_include_directories(test_world_map_exploration_state PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_world_map_exploration_state SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_world_map_exploration_state PRIVATE catch2_main) if(TARGET glm::glm) target_link_libraries(test_world_map_exploration_state PRIVATE glm::glm) endif() add_test(NAME world_map_exploration_state COMMAND test_world_map_exploration_state) register_test_target(test_world_map_exploration_state) # ── test_world_map_zone_metadata ────────────────────────────── add_executable(test_world_map_zone_metadata test_world_map_zone_metadata.cpp ${CMAKE_SOURCE_DIR}/src/rendering/world_map/zone_metadata.cpp ) target_include_directories(test_world_map_zone_metadata PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_world_map_zone_metadata SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_world_map_zone_metadata PRIVATE catch2_main) if(TARGET glm::glm) target_link_libraries(test_world_map_zone_metadata PRIVATE glm::glm) endif() add_test(NAME world_map_zone_metadata COMMAND test_world_map_zone_metadata) register_test_target(test_world_map_zone_metadata) # ── test_chat_markup_parser ────────────────────────────────── add_executable(test_chat_markup_parser test_chat_markup_parser.cpp ${TEST_COMMON_SOURCES} ${CMAKE_SOURCE_DIR}/src/ui/chat/chat_markup_parser.cpp ) target_include_directories(test_chat_markup_parser PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_chat_markup_parser SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/extern/imgui ) target_link_libraries(test_chat_markup_parser PRIVATE catch2_main) add_test(NAME chat_markup_parser COMMAND test_chat_markup_parser) register_test_target(test_chat_markup_parser) # ── test_macro_evaluator ───────────────────────────────────── add_executable(test_macro_evaluator test_macro_evaluator.cpp ${TEST_COMMON_SOURCES} ${CMAKE_SOURCE_DIR}/src/ui/chat/macro_evaluator.cpp ) target_include_directories(test_macro_evaluator PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_macro_evaluator SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_macro_evaluator PRIVATE catch2_main) add_test(NAME macro_evaluator COMMAND test_macro_evaluator) register_test_target(test_macro_evaluator) # ── test_chat_tab_completer ────────────────────────────────── add_executable(test_chat_tab_completer test_chat_tab_completer.cpp ${TEST_COMMON_SOURCES} ${CMAKE_SOURCE_DIR}/src/ui/chat/chat_tab_completer.cpp ) target_include_directories(test_chat_tab_completer PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_chat_tab_completer SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_chat_tab_completer PRIVATE catch2_main) add_test(NAME chat_tab_completer COMMAND test_chat_tab_completer) register_test_target(test_chat_tab_completer) # ── test_gm_commands ───────────────────────────────────────── add_executable(test_gm_commands test_gm_commands.cpp ${TEST_COMMON_SOURCES} ) target_include_directories(test_gm_commands PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_gm_commands SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_gm_commands PRIVATE catch2_main) add_test(NAME gm_commands COMMAND test_gm_commands) register_test_target(test_gm_commands) # ── test_open_formats ──────────────────────────────────────── add_executable(test_open_formats test_open_formats.cpp ${CMAKE_SOURCE_DIR}/src/pipeline/wowee_building.cpp ${CMAKE_SOURCE_DIR}/src/pipeline/wowee_collision.cpp ${CMAKE_SOURCE_DIR}/src/pipeline/wowee_terrain_loader.cpp ${CMAKE_SOURCE_DIR}/src/pipeline/wmo_loader.cpp ${CMAKE_SOURCE_DIR}/src/core/logger.cpp ) target_include_directories(test_open_formats PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_open_formats SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_open_formats PRIVATE catch2_main) add_test(NAME open_formats COMMAND test_open_formats) register_test_target(test_open_formats) # ── test_camera ────────────────────────────────────────────── add_executable(test_camera test_camera.cpp ${CMAKE_SOURCE_DIR}/src/rendering/camera.cpp ${CMAKE_SOURCE_DIR}/src/core/logger.cpp ) target_include_directories(test_camera PRIVATE ${TEST_INCLUDE_DIRS}) target_include_directories(test_camera SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS}) target_link_libraries(test_camera PRIVATE catch2_main) add_test(NAME camera COMMAND test_camera) register_test_target(test_camera) # ── test_editor_units (SQL escape, quest validation, …) ───── add_executable(test_editor_units test_editor_units.cpp ${CMAKE_SOURCE_DIR}/tools/editor/sql_exporter.cpp ${CMAKE_SOURCE_DIR}/tools/editor/npc_spawner.cpp ${CMAKE_SOURCE_DIR}/tools/editor/quest_editor.cpp ${CMAKE_SOURCE_DIR}/tools/editor/content_pack.cpp ${CMAKE_SOURCE_DIR}/tools/editor/editor_brush.cpp ${CMAKE_SOURCE_DIR}/tools/editor/object_placer.cpp ${CMAKE_SOURCE_DIR}/src/core/logger.cpp ) target_include_directories(test_editor_units PRIVATE ${TEST_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/tools/editor ) target_include_directories(test_editor_units SYSTEM PRIVATE ${TEST_SYSTEM_INCLUDE_DIRS} ${CMAKE_SOURCE_DIR}/extern/nlohmann ) target_link_libraries(test_editor_units PRIVATE catch2_main) add_test(NAME editor_units COMMAND test_editor_units) register_test_target(test_editor_units) # ── ASAN / UBSan for test targets ──────────────────────────── if(WOWEE_ENABLE_ASAN AND NOT MSVC) foreach(_t IN LISTS ALL_TEST_TARGETS) target_compile_options(${_t} PRIVATE -fsanitize=address,undefined -fno-omit-frame-pointer) target_link_options(${_t} PRIVATE -fsanitize=address,undefined) endforeach() # catch2_main must also be compiled with the same flags target_compile_options(catch2_main PRIVATE -fsanitize=address,undefined -fno-omit-frame-pointer) target_link_options(catch2_main PRIVATE -fsanitize=address,undefined) message(STATUS "Test targets: ASAN + UBSan ENABLED") endif()