cmake_minimum_required(VERSION 3.21)

project(ps2xTest LANGUAGES C CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include(CTest)
if(BUILD_TESTING)
    add_subdirectory(gs_cache)
endif()

# Static library with test logic (no main), used by ps2xStudio
add_library(ps2_test_function_table OBJECT
    src/test_function_table.cpp
)

target_include_directories(ps2_test_function_table PRIVATE
    ${CMAKE_SOURCE_DIR}/ps2xRuntime/include
    ${CMAKE_SOURCE_DIR}/ps2xIOP/include
)

# Execute synthetic CodeGenerator output instead of matching its formatting.
add_executable(ps2x_vu0_fixture_generator EXCLUDE_FROM_ALL src/vu0_fixture_generator.cpp)
target_link_libraries(ps2x_vu0_fixture_generator PRIVATE ps2_recomp_lib)
set(vu0_fixtures "${CMAKE_CURRENT_BINARY_DIR}/vu0_test_fixtures.cpp")
add_custom_command(OUTPUT "${vu0_fixtures}"
    COMMAND $<TARGET_FILE:ps2x_vu0_fixture_generator> "${vu0_fixtures}"
    DEPENDS ps2x_vu0_fixture_generator
    VERBATIM)

if(NOT MSVC)
    set_source_files_properties("${vu0_fixtures}" PROPERTIES COMPILE_OPTIONS "-msse4.1")
endif()

add_library(ps2_test_lib STATIC
    "${vu0_fixtures}"
    src/code_generator_tests.cpp
    src/r5900_decoder_tests.cpp
    src/elf_analyzer_tests.cpp
    src/pad_input_tests.cpp
    src/ps2_runtime_io_tests.cpp
    src/ps2_runtime_kernel_tests.cpp
    src/ps2_runtime_interrupt_tests.cpp
    src/ps2_memory_tests.cpp
    src/ps2_vu1_tests.cpp
    src/ps2_vu_tests.cpp
    src/ps2_gs_tests.cpp
    src/ps2_iop_tests.cpp
    src/ps2_sif_rpc_tests.cpp
    src/ps2_sif_dma_tests.cpp
    src/ps2_recompiler_tests.cpp
    src/ps2_runtime_expansion_tests.cpp
    src/ps2_float_semantics_tests.cpp
)

target_include_directories(ps2_test_lib PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/include
    ${CMAKE_SOURCE_DIR}/ps2xRecomp/include
    ${CMAKE_SOURCE_DIR}/ps2xAnalyzer/include
    ${CMAKE_SOURCE_DIR}/ps2xRuntime/include
    ${CMAKE_SOURCE_DIR}/ps2xRuntime/src/lib
)

target_link_libraries(ps2_test_lib PRIVATE
    ps2_recomp_lib
    ps2_analyzer_lib
    ps2_runtime
    ps2_iop
)

if(UNIX AND NOT APPLE)
    target_link_libraries(ps2_test_lib PRIVATE ${CMAKE_DL_LIBS})
endif()

add_executable(ps2x_tests
    src/main.cpp
    $<TARGET_OBJECTS:ps2_test_function_table>
)

option(PRINT_GENERATED_CODE "Print generated code in tests" OFF)
if(PRINT_GENERATED_CODE)

    target_compile_definitions(ps2x_tests PRIVATE PRINT_GENERATED_CODE)
    target_compile_definitions(ps2_test_lib PRIVATE PRINT_GENERATED_CODE)
endif()

target_include_directories(ps2x_tests PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/include
    ${CMAKE_SOURCE_DIR}/ps2xRecomp/include
    ${CMAKE_SOURCE_DIR}/ps2xAnalyzer/include
    ${CMAKE_SOURCE_DIR}/ps2xRuntime/include
)

target_link_libraries(ps2x_tests PRIVATE
    ps2_test_lib
    ps2_recomp_lib
    ps2_analyzer_lib
    ps2_runtime
)

# VU/GS tests intentionally instantiate large interpreter state.
# Windows executables default to a much smaller stack than Linux, and
# sanitizer/debug instrumentation can push these test frames over 1 MiB.
if(MSVC)
    target_link_options(ps2x_tests PRIVATE "/STACK:8388608")
elseif(MINGW)
    target_link_options(ps2x_tests PRIVATE "-Wl,--stack,8388608")
endif()

if(COMMAND ps2x_stage_ffmpeg_runtime_dlls)
    ps2x_stage_ffmpeg_runtime_dlls(ps2x_tests)
endif()

add_executable(ps2x_watchdog_test
    src/watchdog_test.cpp
    $<TARGET_OBJECTS:ps2_test_function_table>
)

target_include_directories(ps2x_watchdog_test PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/include
    ${CMAKE_SOURCE_DIR}/ps2xRuntime/include
)

target_link_libraries(ps2x_watchdog_test PRIVATE
    ps2_runtime
)

if(UNIX AND NOT APPLE)
    target_link_libraries(ps2x_watchdog_test PRIVATE ${CMAKE_DL_LIBS})
endif()

add_executable(ps2x_checkpoint_test
    src/checkpoint_test.cpp
    $<TARGET_OBJECTS:ps2_test_function_table>
)

target_include_directories(ps2x_checkpoint_test PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/include
    ${CMAKE_SOURCE_DIR}/ps2xRuntime/include
)

target_link_libraries(ps2x_checkpoint_test PRIVATE
    ps2_runtime
)

if(UNIX AND NOT APPLE)
    target_link_libraries(ps2x_checkpoint_test PRIVATE ${CMAKE_DL_LIBS})
endif()

# Same stack as ps2x_tests: both construct a full PS2Runtime on the main thread, which overflows Windows' 1 MiB default.
foreach(dbziw_test ps2x_watchdog_test ps2x_checkpoint_test)
    if(MSVC)
        target_link_options(${dbziw_test} PRIVATE "/STACK:8388608")
    elseif(MINGW)
        target_link_options(${dbziw_test} PRIVATE "-Wl,--stack,8388608")
    endif()
endforeach()

include("${CMAKE_SOURCE_DIR}/ps2xRuntime/cmake/ReleaseMode.cmake")

if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
    EnableFastReleaseMode(ps2_test_lib)
    EnableFastReleaseMode(ps2x_tests)
    EnableFastReleaseMode(ps2x_watchdog_test)
    EnableFastReleaseMode(ps2x_checkpoint_test)
endif()

# Focused callback-fed MPEG regressions; same production runtime and synthetic function table.
add_executable(ps2x_mpeg_input_tests src/mpeg_input_tests.cpp $<TARGET_OBJECTS:ps2_test_function_table>)
target_include_directories(ps2x_mpeg_input_tests PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/include
    ${CMAKE_SOURCE_DIR}/ps2xRuntime/include
    ${CMAKE_SOURCE_DIR}/ps2xRuntime/src/lib/Kernel)
target_link_libraries(ps2x_mpeg_input_tests PRIVATE ps2_runtime ps2_iop)
if(COMMAND ps2x_stage_ffmpeg_runtime_dlls)
    ps2x_stage_ffmpeg_runtime_dlls(ps2x_mpeg_input_tests)
endif()
if(MSVC)
    target_link_options(ps2x_mpeg_input_tests PRIVATE "/STACK:8388608")
elseif(MINGW)
    target_link_options(ps2x_mpeg_input_tests PRIVATE "-Wl,--stack,8388608")
endif()
if(BUILD_TESTING)
    add_test(NAME mpeg_input_service COMMAND ps2x_mpeg_input_tests)
endif()

# Independent raw inputs go through the full recompiler, then actual emitted C++ is executed.
add_executable(ps2x_boundary_fixture_generator EXCLUDE_FROM_ALL src/boundary_fixture_generator.cpp)
target_include_directories(ps2x_boundary_fixture_generator PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(ps2x_boundary_fixture_generator PRIVATE ps2_recomp_lib)
set(boundary_fixture_dir "${CMAKE_CURRENT_BINARY_DIR}/boundary_fixtures")
set(boundary_fixture_cpp "${boundary_fixture_dir}/ps2_recompiled_functions.cpp")
set(boundary_fixture_table "${boundary_fixture_dir}/register_functions.cpp")
add_custom_command(OUTPUT "${boundary_fixture_cpp}" "${boundary_fixture_table}"
    COMMAND $<TARGET_FILE:ps2x_boundary_fixture_generator> "${boundary_fixture_dir}"
    DEPENDS ps2x_boundary_fixture_generator VERBATIM)
add_executable(ps2x_boundary_native_tests src/boundary_native_tests.cpp src/tlb_native_tests.cpp
    "${boundary_fixture_cpp}" "${boundary_fixture_table}" $<TARGET_OBJECTS:ps2_test_function_table>)
target_include_directories(ps2x_boundary_native_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
    ${CMAKE_SOURCE_DIR}/ps2xRuntime/include ${CMAKE_SOURCE_DIR}/ps2xRuntime/src/lib/Kernel
    ${CMAKE_SOURCE_DIR}/ps2xIOP/include "${boundary_fixture_dir}/units" "${boundary_fixture_dir}")
target_link_libraries(ps2x_boundary_native_tests PRIVATE ps2_runtime ps2_iop)
if(NOT MSVC)
    target_compile_options(ps2x_boundary_native_tests PRIVATE -msse4.1)
endif()
if(COMMAND ps2x_stage_ffmpeg_runtime_dlls)
    ps2x_stage_ffmpeg_runtime_dlls(ps2x_boundary_native_tests)
endif()
# Match the other production-runtime test executables on Windows.
if(MSVC)
    target_link_options(ps2x_boundary_native_tests PRIVATE "/STACK:8388608")
elseif(MINGW)
    target_link_options(ps2x_boundary_native_tests PRIVATE "-Wl,--stack,8388608")
endif()
if(BUILD_TESTING)
    add_test(NAME native_function_boundaries COMMAND ps2x_boundary_native_tests)
endif()

