###############################################################################
 #
 # MIT License
 #
 # Copyright (C) 2023-2026 Advanced Micro Devices, Inc. All rights reserved.
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
 # of this software and associated documentation files (the "Software"), to deal
 # in the Software without restriction, including without limitation the rights
 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 # copies of the Software, and to permit persons to whom the Software is
 # furnished to do so, subject to the following conditions:
 #
 # The above copyright notice and this permission notice shall be included in
 # all copies or substantial portions of the Software.
 #
 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 # THE SOFTWARE.
 #
 ###############################################################################

include(CMakeDependentOption)

# Find / configure LLVM
add_subdirectory(llvm)

cmake_dependent_option(HIPTENSOR_USE_SYSTEM_GOOGLETEST "Use system Google Test library instead of downloading and building it" OFF "HIPTENSOR_BUILD_TESTS" OFF)

if(HIPTENSOR_USE_SYSTEM_GOOGLETEST)
    find_package(GTest REQUIRED)
else()
    include(FetchContent)

    FetchContent_Declare(
        googletest
        GIT_REPOSITORY https://github.com/google/googletest.git
        GIT_TAG v1.17.0
    )
    FetchContent_GetProperties(googletest)
    if(NOT googletest_POPULATED)
        # Fetch the content using default details
        FetchContent_Populate(googletest)
        # Save the shared libs setting, then force to static libs
        set(BUILD_SHARED_LIBS_OLD ${BUILD_SHARED_LIBS})
        set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "Build SHARED libraries" FORCE)
        # Turn off gtest installation
        set(INSTALL_GTEST OFF)
        # On MSVC-style toolchains gtest defaults to the static CRT (/MT), but the
        # rest of hipTensor compiles against the dynamic CRT (/MD). Force gtest to
        # use the dynamic CRT so lld-link does not reject a RuntimeLibrary mismatch.
        if(WIN32)
            set(gtest_force_shared_crt ON CACHE BOOL "Use shared (DLL) run-time lib for gtest" FORCE)
        endif()
        # Add gtest targets as static libs
        add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR})
        # Restore shared libs setting
        set(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_OLD} CACHE INTERNAL "Build SHARED libraries" FORCE)
    endif()
endif()

get_target_property(composable_kernel_INCLUDES composable_kernel::device_other_operations INTERFACE_INCLUDE_DIRECTORIES)

# Setup a test manifest
set(INSTALL_TEST_FILE "${CMAKE_CURRENT_BINARY_DIR}/install_CTestTestfile.cmake")
file(WRITE "${INSTALL_TEST_FILE}"
[=[
# This is a test file generated by hipTensor for install time.
# It differs slightly from the default testfile, and you may encounter issues because of that.
]=]
)
set(INSTALL_SMOKE_TEST_FILE "${CMAKE_CURRENT_BINARY_DIR}/install_smoke_CTestTestfile.cmake")
file(WRITE "${INSTALL_SMOKE_TEST_FILE}"
[=[
# This is a test file generated by hipTensor for install time.
# It differs slightly from the default testfile, and you may encounter issues because of that.
]=]
)
set(INSTALL_REGRESSION_TEST_FILE "${CMAKE_CURRENT_BINARY_DIR}/install_regression_CTestTestfile.cmake")
file(WRITE "${INSTALL_REGRESSION_TEST_FILE}"
[=[
# This is a test file generated by hipTensor for install time.
# It differs slightly from the default testfile, and you may encounter issues because of that.
]=]
)

# YAML-driven CTest test categorisation for the *installed* tree only.
# The shared helper apply_ctest_category_labels(yaml, install_test_file)
# scans the install-tree CTestTestfile.cmake fragments we hand-roll below
# for `add_test()` lines and appends explicit set_tests_properties() lines
# so `ctest -L <tier>` works against the install tree. Build-tree tests
# are intentionally not labelled here.
set(_HIPTENSOR_TEST_CATEGORIES_YAML "${PROJECT_SOURCE_DIR}/test_categories.yaml")
set(_HIPTENSOR_SHARED_CTEST         "${ROCM_LIBRARIES_ROOT}/shared/ctest/TestCategories.cmake")
if(EXISTS "${_HIPTENSOR_SHARED_CTEST}" AND EXISTS "${_HIPTENSOR_TEST_CATEGORIES_YAML}")
    include("${_HIPTENSOR_SHARED_CTEST}")
    message(STATUS "hipTensor: YAML-based CTest categorization enabled (install tree only)")
else()
    if(NOT EXISTS "${_HIPTENSOR_SHARED_CTEST}")
        message(STATUS "hipTensor: shared/ctest not found at ${_HIPTENSOR_SHARED_CTEST}; skipping CTest categories")
    endif()
    if(NOT EXISTS "${_HIPTENSOR_TEST_CATEGORIES_YAML}")
        message(STATUS "hipTensor: ${_HIPTENSOR_TEST_CATEGORIES_YAML} not found; skipping CTest categories")
    endif()
endif()

# Target that will trigger build of all tests
add_custom_target(hiptensor_tests)

# On Windows, DLL search order is: application dir -> System32 -> PATH.
# A system-installed ROCm places amdhip64_7.dll and amd_comgr_3.dll in
# System32, which always wins over PATH. If the test binary is compiled with
# a newer toolchain but an older runtime is loaded from System32, kernel launches
# may crash (SEH 0xC0000005) if the code-object format is incompatible.
if(WIN32 AND CREATE_TEST_APP_LOCAL_DEPLOY)
    # hip_DIR is set by find_package(HIP) to <HIP_ROOT>/lib/cmake/hip.
    # Walk up three directory levels to reach <HIP_ROOT>, then append /bin.
    get_filename_component(_hip_cmake_dir  "${hip_DIR}"          DIRECTORY) # .../lib/cmake
    get_filename_component(_hip_lib_dir    "${_hip_cmake_dir}"   DIRECTORY) # .../lib
    get_filename_component(_hip_root       "${_hip_lib_dir}"     DIRECTORY) # <HIP_ROOT>
    set(_hip_bin_dir "${_hip_root}/bin")

    message(STATUS "HIP runtime DLL source: ${_hip_bin_dir}")
    message(STATUS "HIP runtime DLL destination: ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")

    if(EXISTS "${_hip_bin_dir}")
        file(MAKE_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")
        file(GLOB _hip_dlls
            "${_hip_bin_dir}/amd_comgr*.dll"   # code-object manager
            "${_hip_bin_dir}/amdhip64*.dll"    # HIP runtime
            "${_hip_bin_dir}/amdhiprtc*.dll"   # HIP RTC
            "${_hip_bin_dir}/rocm_kpack*.dll"  # kernel packaging
        )
        if(_hip_dlls)
            foreach(_dll ${_hip_dlls})
                file(COPY "${_dll}" DESTINATION "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")
            endforeach()
            list(LENGTH _hip_dlls _hip_dll_count)
            message(STATUS "Staged ${_hip_dll_count} HIP runtime DLL(s) into ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")
        else()
            message(WARNING "No DLLs found in ${_hip_bin_dir} - tests may load wrong HIP runtime from System32")
        endif()
    else()
        message(WARNING "HIP DLL source directory not found: ${_hip_bin_dir} - "
                        "tests may load wrong HIP runtime from System32")
    endif()
endif()

set(HIPTENSOR_COMMON_TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/common.cpp
                                  ${CMAKE_CURRENT_SOURCE_DIR}/hip_resource.cpp
                                  ${CMAKE_CURRENT_SOURCE_DIR}/hiptensor_gtest_main.cpp)


# Create test executables and deploy
function(add_hiptensor_test BINARY_NAME YAML_CONFIG_FILE TEST_SOURCES)

    # Make sure that all sources are appended to the list.
    list(APPEND TEST_SOURCES ${ARGN})

    message(STATUS "adding hiptensor test: ${BINARY_NAME}")
    message(STATUS "test config file: ${YAML_CONFIG_FILE}")

    # Read in the YAML config file that we wish to bundle
    file(READ ${YAML_CONFIG_FILE} hiptensor_test_YAML_STRING)

    # Build a file header that includes the YAML config string
    # Must define in cmake:
    # - hiptensor_test_NAME
    # - hiptensor_test_YAML_STRING
    # - hiptensor_test_YAML_INCLUDE_FILE
    get_filename_component(hiptensor_test_YAML_DIR "${YAML_CONFIG_FILE}" PATH)
    get_filename_component(hiptensor_test_NAME "${YAML_CONFIG_FILE}" NAME_WE)
    set(hiptensor_test_YAML_INCLUDE_FILE "${hiptensor_test_NAME}.hpp")
    configure_file("${PROJECT_SOURCE_DIR}/test/hiptensor-test-yaml.hpp.in"
                  "${hiptensor_test_YAML_DIR}/${hiptensor_test_YAML_INCLUDE_FILE}")

    # message(STATUS "test config dir: ${hiptensor_test_YAML_DIR}")
    # message(STATUS "test name: ${hiptensor_test_NAME}")
    # message(STATUS "test yaml config: ${hiptensor_test_YAML_STRING}")
    # message(STATUS "test config include: ${hiptensor_test_YAML_INCLUDE_FILE}")

    add_executable(${BINARY_NAME} ${TEST_SOURCES})
    target_compile_options(${BINARY_NAME} PRIVATE ${CLANG_DRIVER_MODE})
    target_link_options(${BINARY_NAME} PRIVATE ${CLANG_DRIVER_MODE})

    # Test must propagate the build interface includes to make sure
    # hiptensor includes are captured at runtime.
    if(WIN32)
        target_link_libraries(${BINARY_NAME} PRIVATE hiptensor::hiptensor hiptensor_llvm GTest::gtest "-L${HIPTENSOR_CLANG_ROOT}/lib")
    else()
        target_link_libraries(${BINARY_NAME} PRIVATE hiptensor::hiptensor hiptensor_llvm GTest::gtest "-L${HIPTENSOR_CLANG_ROOT}/lib"
                              "-Wl,-rpath=$ORIGIN/../${CMAKE_INSTALL_LIBDIR}" "-fno-rtlib-add-rpath")
    endif()

    target_include_directories(${BINARY_NAME} PRIVATE
                               ${CMAKE_CURRENT_SOURCE_DIR}
                               ${composable_kernel_INCLUDES}
                               ${PROJECT_SOURCE_DIR}/library/include
                               ${PROJECT_SOURCE_DIR}/library/src/include
                               ${PROJECT_SOURCE_DIR}/library/src
                               ${PROJECT_SOURCE_DIR}/test
                               ${PROJECT_BINARY_DIR}/include/hiptensor
                               ${PROJECT_BINARY_DIR}/include
                               ${hiptensor_test_YAML_DIR})

    # -Wno-ignored-attributes to avoid warning: __declspec attribute 'dllimport' is not supported [-Wignored-attributes] which is used by msvc compiler
    if(WIN32 AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
        target_compile_options(${BINARY_NAME} PRIVATE -Wno-ignored-attributes)
    endif()

    # C++ code handles the following symbols defined:
    # - HIPTENSOR_TEST_YAML_INCLUDE
    # When defined, the test code will attempt to load bundled YAML string.
    target_compile_definitions(${BINARY_NAME} PRIVATE -D HIPTENSOR_TEST_YAML_INCLUDE=\"${hiptensor_test_YAML_INCLUDE_FILE}\")

    # Build this test under custom target
    add_dependencies(hiptensor_tests ${BINARY_NAME})

    # Register with ctest
    add_test(NAME ${BINARY_NAME} COMMAND ${BINARY_NAME})
    set_property(TEST ${BINARY_NAME} PROPERTY SKIP_REGULAR_EXPRESSION "HIPTENSOR_STATUS_ARCH_MISMATCH" "unsupported host device")
    # Set working directory to bin/ for proper DLL/resource loading on Windows
    set_property(TEST ${BINARY_NAME} PROPERTY WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})

    # On Windows, ensure the ROCm/HIP DLLs (amdhip64_7.dll, etc.) are discoverable by ctest.
    # ctest may not inherit the user's PATH that includes the ROCm bin directory, causing
    # HIP runtime DLL load failures (SEH 0xC0000005) on the first HIP call in a test fixture.
    if(WIN32)
        set_property(TEST ${BINARY_NAME} PROPERTY ENVIRONMENT
            "PATH=${CMAKE_INSTALL_PREFIX}/bin$<SEMICOLON>${CMAKE_RUNTIME_OUTPUT_DIRECTORY}$<SEMICOLON>$ENV{PATH}")
    endif()

    # Install with rocm pkg
    rocm_install_targets(
    TARGETS ${BINARY_NAME}
    COMPONENT tests
    )

    # Add to testing manifest
    get_target_property(EXE_NAME ${BINARY_NAME} RUNTIME_OUTPUT_NAME)
    if(EXE_NAME STREQUAL "EXE_NAME-NOTFOUND")
        get_target_property(EXE_NAME ${BINARY_NAME} OUTPUT_NAME)
        if(EXE_NAME STREQUAL "EXE_NAME-NOTFOUND")
            set(EXE_NAME "${BINARY_NAME}")
        endif()
    endif()
    file(APPEND "${INSTALL_TEST_FILE}" "add_test(${BINARY_NAME} \"../${BINARY_NAME}\")\n")
    file(APPEND "${INSTALL_TEST_FILE}" "set_tests_properties(${BINARY_NAME} PROPERTIES SKIP_REGULAR_EXPRESSION \"HIPTENSOR_STATUS_ARCH_MISMATCH;unsupported host device\")\n")

    file(APPEND "${INSTALL_SMOKE_TEST_FILE}" "add_test(\"${BINARY_NAME} smoke\" \"../../${BINARY_NAME}\")\n")
    file(APPEND "${INSTALL_SMOKE_TEST_FILE}" "set_tests_properties(\"${BINARY_NAME} smoke\" PROPERTIES SKIP_REGULAR_EXPRESSION \"HIPTENSOR_STATUS_ARCH_MISMATCH;unsupported host device\")\n")

    file(APPEND "${INSTALL_REGRESSION_TEST_FILE}" "add_test(\"${BINARY_NAME} regression\" \"../../${BINARY_NAME}\")\n")
    file(APPEND "${INSTALL_REGRESSION_TEST_FILE}" "set_tests_properties(\"${BINARY_NAME} regression\" PROPERTIES SKIP_REGULAR_EXPRESSION \"HIPTENSOR_STATUS_ARCH_MISMATCH;unsupported host device\")\n")
endfunction()

# Create unit test executables and deploy
# Differs from hiptensor_test in that src include directory is linked
function(add_hiptensor_unit_test)
    set(BINARY_NAME ${ARGV0})
    message(STATUS "adding hiptensor unit test: ${BINARY_NAME}")

    list(REMOVE_AT ARGV 0)
    add_executable(${BINARY_NAME} ${ARGV})

    target_compile_options(${BINARY_NAME} PRIVATE ${CLANG_DRIVER_MODE})
    target_link_options(${BINARY_NAME} PRIVATE ${CLANG_DRIVER_MODE})

    # Test must propagate the build interface includes to make sure
    # hiptensor includes are captured at runtime.
    if(WIN32)
        target_link_libraries(${BINARY_NAME} PRIVATE hiptensor::hiptensor hiptensor_llvm GTest::gtest "-L${HIPTENSOR_CLANG_ROOT}/lib")
    else()
        target_link_libraries(${BINARY_NAME} PRIVATE hiptensor::hiptensor hiptensor_llvm GTest::gtest "-L${HIPTENSOR_CLANG_ROOT}/lib"
                              "-Wl,-rpath=$ORIGIN/../${CMAKE_INSTALL_LIBDIR}")
    endif()
    target_include_directories(${BINARY_NAME} PRIVATE
                               ${CMAKE_CURRENT_SOURCE_DIR}
                               ${composable_kernel_INCLUDES}
                               ${PROJECT_SOURCE_DIR}/library/include
                               ${PROJECT_SOURCE_DIR}/library/src/include
                               ${PROJECT_SOURCE_DIR}/library/src
                               ${PROJECT_SOURCE_DIR}/test
                               ${PROJECT_BINARY_DIR}/include/hiptensor
                               ${PROJECT_BINARY_DIR}/include)

    # -Wno-ignored-attributes to avoid warning: __declspec attribute 'dllimport' is not supported [-Wignored-attributes] which is used by msvc compiler
    if(WIN32 AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
        target_compile_options(${BINARY_NAME} PRIVATE -Wno-ignored-attributes)
    endif()

    # Build this test under custom target
    add_dependencies(hiptensor_tests ${BINARY_NAME})

    # Register with ctest
    add_test(NAME ${BINARY_NAME} COMMAND ${BINARY_NAME})
    set_property(TEST ${BINARY_NAME} PROPERTY SKIP_REGULAR_EXPRESSION "HIPTENSOR_STATUS_ARCH_MISMATCH" "unsupported host device")
    # Set working directory to bin/ for proper DLL/resource loading on Windows
    set_property(TEST ${BINARY_NAME} PROPERTY WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})

    # On Windows, ensure the ROCm/HIP DLLs (amdhip64_7.dll, etc.) are discoverable by ctest.
    # ctest may not inherit the user's PATH that includes the ROCm bin directory, causing
    # HIP runtime DLL load failures (SEH 0xC0000005) on the first HIP call in a test fixture.
    if(WIN32)
        set_property(TEST ${BINARY_NAME} PROPERTY ENVIRONMENT
            "PATH=${CMAKE_INSTALL_PREFIX}/bin$<SEMICOLON>${CMAKE_RUNTIME_OUTPUT_DIRECTORY}$<SEMICOLON>$ENV{PATH}")
    endif()

    # Install with rocm pkg
    rocm_install_targets(
    TARGETS ${BINARY_NAME}
    COMPONENT tests
    )

    # Add to testing manifest
    get_target_property(EXE_NAME ${BINARY_NAME} RUNTIME_OUTPUT_NAME)
    if(EXE_NAME STREQUAL "EXE_NAME-NOTFOUND")
        get_target_property(EXE_NAME ${BINARY_NAME} OUTPUT_NAME)
        if(EXE_NAME STREQUAL "EXE_NAME-NOTFOUND")
            set(EXE_NAME "${BINARY_NAME}")
        endif()
    endif()
    file(APPEND "${INSTALL_TEST_FILE}" "add_test(${BINARY_NAME} \"../${BINARY_NAME}\")\n")
    file(APPEND "${INSTALL_TEST_FILE}" "set_tests_properties(${BINARY_NAME} PROPERTIES SKIP_REGULAR_EXPRESSION \"HIPTENSOR_STATUS_ARCH_MISMATCH;unsupported host device\")\n")

    file(APPEND "${INSTALL_SMOKE_TEST_FILE}" "add_test(\"${BINARY_NAME} smoke\" \"../../${BINARY_NAME}\")\n")
    file(APPEND "${INSTALL_SMOKE_TEST_FILE}" "set_tests_properties(\"${BINARY_NAME} smoke\" PROPERTIES SKIP_REGULAR_EXPRESSION \"HIPTENSOR_STATUS_ARCH_MISMATCH;unsupported host device\")\n")

    file(APPEND "${INSTALL_REGRESSION_TEST_FILE}" "add_test(\"${BINARY_NAME} regression\" \"../../${BINARY_NAME}\")\n")
    file(APPEND "${INSTALL_REGRESSION_TEST_FILE}" "set_tests_properties(\"${BINARY_NAME} regression\" PROPERTIES SKIP_REGULAR_EXPRESSION \"HIPTENSOR_STATUS_ARCH_MISMATCH;unsupported host device\")\n")

endfunction()

add_subdirectory(00_unit)
add_subdirectory(01_contraction)
add_subdirectory(02_elementwise)
add_subdirectory(03_reduction)

add_subdirectory(python)
# Test entries for the install-tree category filters.
#
# Two building-block lists keep the three filters distinct in intent:
#   _HIPTENSOR_HOST_TESTS   - host-only (non-device) tests. They take no config and
#                             no run-count flags.
#   _HIPTENSOR_DEVICE_TESTS - device tests. Each runs with --hot_runs 1 --cold_runs 0
#                             and, where applicable, a custom config via -y.
#
# From these, families of install-tree entries are emitted:
#   filter          test name suffix   members         config dir
#   quick           " quick"           host + device   validation/quick
#   standard        " standard"        host + device   validation/standard
#   comprehensive   " comprehensive"   host + device   validation/comprehensive
#   full            " full"            host + device   validation/full
#   ffm-quick       " ffm-quick"       device only      emulation/quick
#   ffm-full        " ffm-full"        device only      emulation/full
#   bench           " bench"           device (bench)   bench
# where "config dir" is the bin/hiptensor/configs/<dir>/ subtree the -y path
# points at. Host tests are emitted once under quick; the shared parser's tier
# nesting (quick->standard->comprehensive->full) carries them up, so they run
# under every tier without duplicate entries. Labels are attached by the shared
# category parser from test_categories.yaml.

set(_HIPTENSOR_HOST_TESTS
  contraction_mode_test
  elementwise_cpu_test
  elementwise_op_test
  hiptensor_options_test
  logger_test
  reduction_cpu_impl_test
  util_test
  yaml_test
)

# Device tests encoded as "binary|config-or-NONE". The config path is
# relative to a per-category config root (bin/hiptensor/configs/<category>/),
# with <category> supplied per family below. NONE means no -y (the binary
# carries its own config) but the run-count flags are still applied.
set(_HIPTENSOR_DEVICE_TESTS
  "bilinear_contraction_test_m1n1k1|01_contraction/bilinear_test_params_rank1.yaml"
  "bilinear_contraction_test_m2n2k2|01_contraction/bilinear_test_params_rank2.yaml"
  "bilinear_contraction_test_m3n3k3|01_contraction/bilinear_test_params_rank3.yaml"
  "bilinear_contraction_test_m4n4k4|01_contraction/bilinear_test_params_rank4.yaml"
  "bilinear_contraction_test_m5n5k5|01_contraction/bilinear_test_params_rank5.yaml"
  "bilinear_contraction_test_m6n6k6|01_contraction/bilinear_test_params_rank6.yaml"
  "complex_bilinear_contraction_test_m1n1k1|01_contraction/complex_bilinear_test_params_rank1.yaml"
  "complex_bilinear_contraction_test_m2n2k2|01_contraction/complex_bilinear_test_params_rank2.yaml"
  "complex_bilinear_contraction_test_m3n3k3|01_contraction/complex_bilinear_test_params_rank3.yaml"
  "complex_bilinear_contraction_test_m4n4k4|01_contraction/complex_bilinear_test_params_rank4.yaml"
  "complex_bilinear_contraction_test_m5n5k5|01_contraction/complex_bilinear_test_params_rank5.yaml"
  "complex_bilinear_contraction_test_m6n6k6|01_contraction/complex_bilinear_test_params_rank6.yaml"
  "scale_contraction_test_m1n1k1|01_contraction/scale_test_params_rank1.yaml"
  "scale_contraction_test_m2n2k2|01_contraction/scale_test_params_rank2.yaml"
  "scale_contraction_test_m3n3k3|01_contraction/scale_test_params_rank3.yaml"
  "scale_contraction_test_m4n4k4|01_contraction/scale_test_params_rank4.yaml"
  "scale_contraction_test_m5n5k5|01_contraction/scale_test_params_rank5.yaml"
  "scale_contraction_test_m6n6k6|01_contraction/scale_test_params_rank6.yaml"
  "complex_scale_contraction_test_m1n1k1|01_contraction/complex_scale_test_params_rank1.yaml"
  "complex_scale_contraction_test_m2n2k2|01_contraction/complex_scale_test_params_rank2.yaml"
  "complex_scale_contraction_test_m3n3k3|01_contraction/complex_scale_test_params_rank3.yaml"
  "complex_scale_contraction_test_m4n4k4|01_contraction/complex_scale_test_params_rank4.yaml"
  "complex_scale_contraction_test_m5n5k5|01_contraction/complex_scale_test_params_rank5.yaml"
  "complex_scale_contraction_test_m6n6k6|01_contraction/complex_scale_test_params_rank6.yaml"
  "trinary_bilinear_contraction_test|NONE"
  "trinary_scale_contraction_test|NONE"
  "bilinear_contraction_with_unary_ops_test|NONE"
  "scale_contraction_with_unary_ops_test|NONE"
  "rank2_elementwise_permute_test|02_elementwise/permutation/rank2_test_params.yaml"
  "rank3_elementwise_permute_test|02_elementwise/permutation/rank3_test_params.yaml"
  "rank4_elementwise_permute_test|02_elementwise/permutation/rank4_test_params.yaml"
  "rank5_elementwise_permute_test|02_elementwise/permutation/rank5_test_params.yaml"
  "rank6_elementwise_permute_test|02_elementwise/permutation/rank6_test_params.yaml"
  "rank2_elementwise_binary_op_test|02_elementwise/binary_op/rank2_binary_op_test_params.yaml"
  "rank3_elementwise_binary_op_test|02_elementwise/binary_op/rank3_binary_op_test_params.yaml"
  "rank4_elementwise_binary_op_test|02_elementwise/binary_op/rank4_binary_op_test_params.yaml"
  "rank5_elementwise_binary_op_test|02_elementwise/binary_op/rank5_binary_op_test_params.yaml"
  "rank6_elementwise_binary_op_test|02_elementwise/binary_op/rank6_binary_op_test_params.yaml"
  "rank2_elementwise_trinary_op_test|02_elementwise/trinary_op/rank2_trinary_op_test_params.yaml"
  "rank3_elementwise_trinary_op_test|02_elementwise/trinary_op/rank3_trinary_op_test_params.yaml"
  "rank4_elementwise_trinary_op_test|02_elementwise/trinary_op/rank4_trinary_op_test_params.yaml"
  "rank5_elementwise_trinary_op_test|02_elementwise/trinary_op/rank5_trinary_op_test_params.yaml"
  "rank6_elementwise_trinary_op_test|02_elementwise/trinary_op/rank6_trinary_op_test_params.yaml"
  "rank1_reduction_test|03_reduction/rank1_test_params.yaml"
  "rank2_reduction_test|03_reduction/rank2_test_params.yaml"
  "rank3_reduction_test|03_reduction/rank3_test_params.yaml"
  "rank4_reduction_test|03_reduction/rank4_test_params.yaml"
  "rank5_reduction_test|03_reduction/rank5_test_params.yaml"
  "rank6_reduction_test|03_reduction/rank6_test_params.yaml"
)

# Bench (performance) tests encoded as "binary|config". Same encoding as
# _HIPTENSOR_DEVICE_TESTS, but the bench config set is distinct: it has no
# trinary/unary contraction variants, and its elementwise configs are flat
# (no permutation/binary_op/trinary_op subdirs). Paths are relative to the
# bench config root (bin/hiptensor/configs/bench/). Bench runs use different
# run-count flags (see the bench family below).
set(_HIPTENSOR_BENCH_TESTS
  "bilinear_contraction_test_m1n1k1|01_contraction/bilinear_test_params_rank1.yaml"
  "bilinear_contraction_test_m2n2k2|01_contraction/bilinear_test_params_rank2.yaml"
  "bilinear_contraction_test_m3n3k3|01_contraction/bilinear_test_params_rank3.yaml"
  "bilinear_contraction_test_m4n4k4|01_contraction/bilinear_test_params_rank4.yaml"
  "bilinear_contraction_test_m5n5k5|01_contraction/bilinear_test_params_rank5.yaml"
  "bilinear_contraction_test_m6n6k6|01_contraction/bilinear_test_params_rank6.yaml"
  "complex_bilinear_contraction_test_m1n1k1|01_contraction/complex_bilinear_test_params_rank1.yaml"
  "complex_bilinear_contraction_test_m2n2k2|01_contraction/complex_bilinear_test_params_rank2.yaml"
  "complex_bilinear_contraction_test_m3n3k3|01_contraction/complex_bilinear_test_params_rank3.yaml"
  "complex_bilinear_contraction_test_m4n4k4|01_contraction/complex_bilinear_test_params_rank4.yaml"
  "complex_bilinear_contraction_test_m5n5k5|01_contraction/complex_bilinear_test_params_rank5.yaml"
  "complex_bilinear_contraction_test_m6n6k6|01_contraction/complex_bilinear_test_params_rank6.yaml"
  "scale_contraction_test_m1n1k1|01_contraction/scale_test_params_rank1.yaml"
  "scale_contraction_test_m2n2k2|01_contraction/scale_test_params_rank2.yaml"
  "scale_contraction_test_m3n3k3|01_contraction/scale_test_params_rank3.yaml"
  "scale_contraction_test_m4n4k4|01_contraction/scale_test_params_rank4.yaml"
  "scale_contraction_test_m5n5k5|01_contraction/scale_test_params_rank5.yaml"
  "scale_contraction_test_m6n6k6|01_contraction/scale_test_params_rank6.yaml"
  "complex_scale_contraction_test_m1n1k1|01_contraction/complex_scale_test_params_rank1.yaml"
  "complex_scale_contraction_test_m2n2k2|01_contraction/complex_scale_test_params_rank2.yaml"
  "complex_scale_contraction_test_m3n3k3|01_contraction/complex_scale_test_params_rank3.yaml"
  "complex_scale_contraction_test_m4n4k4|01_contraction/complex_scale_test_params_rank4.yaml"
  "complex_scale_contraction_test_m5n5k5|01_contraction/complex_scale_test_params_rank5.yaml"
  "complex_scale_contraction_test_m6n6k6|01_contraction/complex_scale_test_params_rank6.yaml"
  "rank2_elementwise_permute_test|02_elementwise/rank2_test_params.yaml"
  "rank2_elementwise_binary_op_test|02_elementwise/rank2_binary_op_test_params.yaml"
  "rank2_elementwise_trinary_op_test|02_elementwise/rank2_trinary_op_test_params.yaml"
  "rank3_elementwise_permute_test|02_elementwise/rank3_test_params.yaml"
  "rank3_elementwise_binary_op_test|02_elementwise/rank3_binary_op_test_params.yaml"
  "rank3_elementwise_trinary_op_test|02_elementwise/rank3_trinary_op_test_params.yaml"
  "rank4_elementwise_permute_test|02_elementwise/rank4_test_params.yaml"
  "rank4_elementwise_binary_op_test|02_elementwise/rank4_binary_op_test_params.yaml"
  "rank4_elementwise_trinary_op_test|02_elementwise/rank4_trinary_op_test_params.yaml"
  "rank5_elementwise_permute_test|02_elementwise/rank5_test_params.yaml"
  "rank5_elementwise_binary_op_test|02_elementwise/rank5_binary_op_test_params.yaml"
  "rank5_elementwise_trinary_op_test|02_elementwise/rank5_trinary_op_test_params.yaml"
  "rank6_elementwise_permute_test|02_elementwise/rank6_test_params.yaml"
  "rank6_elementwise_binary_op_test|02_elementwise/rank6_binary_op_test_params.yaml"
  "rank6_elementwise_trinary_op_test|02_elementwise/rank6_trinary_op_test_params.yaml"
  "rank1_reduction_test|03_reduction/rank1_test_params.yaml"
  "rank2_reduction_test|03_reduction/rank2_test_params.yaml"
  "rank3_reduction_test|03_reduction/rank3_test_params.yaml"
  "rank4_reduction_test|03_reduction/rank4_test_params.yaml"
  "rank5_reduction_test|03_reduction/rank5_test_params.yaml"
  "rank6_reduction_test|03_reduction/rank6_test_params.yaml"
)

# Helper: append one add_test + set_tests_properties to the main install
# fragment. WORKING_DIRECTORY is the install prefix root (../.. from the
# installed bin/hiptensor/ fragment) so ./bin/<binary> and the -y config
# path resolve regardless of ctest's cwd.
function(_hiptensor_emit_test test_name command)
    file(APPEND "${INSTALL_TEST_FILE}" "add_test(\"${test_name}\" ${command})\n")
    file(APPEND "${INSTALL_TEST_FILE}" "set_tests_properties(\"${test_name}\" PROPERTIES WORKING_DIRECTORY \"../..\" SKIP_REGULAR_EXPRESSION \"HIPTENSOR_STATUS_ARCH_MISMATCH;unsupported host device\")\n")
endfunction()

# A config yaml is "effectively empty" when it carries no test parameters.
# This is TRUE when the file:
#   - does not exist, or
#   - is completely empty (0 bytes), or
#   - contains nothing beyond YAML document markers (---, ...), blank lines,
#     and comments.
# Any of these is a deliberate signal that a device test is fully covered by an
# earlier tier and should NOT run in this one: registering it would hand the
# test an empty parameter set, which the GTest-parameterized harness cannot
# digest (empty ValuesIn / out-of-bounds param access -> SegFault). We resolve
# this at configure time by not emitting the add_test for that (test, tier).
function(_hiptensor_config_is_empty config_path out_var)
    # Default TRUE covers the non-existent and 0-byte cases (file(STRINGS) on an
    # empty file yields no lines, so the loop below never flips it to FALSE).
    set(${out_var} TRUE PARENT_SCOPE)
    if(NOT EXISTS "${config_path}")
        return()
    endif()
    file(STRINGS "${config_path}" _lines)
    foreach(_line IN LISTS _lines)
        string(REGEX REPLACE "#.*$" "" _stripped "${_line}")   # drop comments
        string(STRIP "${_stripped}" _stripped)
        if(_stripped STREQUAL "" OR _stripped STREQUAL "---" OR _stripped STREQUAL "...")
            continue()
        endif()
        set(${out_var} FALSE PARENT_SCOPE)
        return()
    endforeach()
endfunction()

# Tiers (quick / standard / comprehensive / full): host + device.
# Each family is "<test name suffix>|<config dir under configs/>".
# Tier & ffm entries omit run-count flags (the binary defaults are
# --hot_runs 1 --cold_runs 0). Host tests are emitted once, under quick, and
# nest up into the higher tiers via the parser.
foreach(_hiptensor_family "quick|validation/quick" "standard|validation/standard" "comprehensive|validation/comprehensive" "full|validation/full")
    string(REPLACE "|" ";" _hiptensor_ff "${_hiptensor_family}")
    list(GET _hiptensor_ff 0 _hiptensor_suffix)
    list(GET _hiptensor_ff 1 _hiptensor_cfgdir)

    if(_hiptensor_suffix STREQUAL "quick")
        foreach(_hiptensor_host IN LISTS _HIPTENSOR_HOST_TESTS)
            _hiptensor_emit_test("${_hiptensor_host} ${_hiptensor_suffix}" "\"./bin/${_hiptensor_host}\"")
        endforeach()
    endif()

    foreach(_hiptensor_dev IN LISTS _HIPTENSOR_DEVICE_TESTS)
        string(REPLACE "|" ";" _hiptensor_dev_fields "${_hiptensor_dev}")
        list(GET _hiptensor_dev_fields 0 _hiptensor_dev_bin)
        list(GET _hiptensor_dev_fields 1 _hiptensor_dev_cfg)
        set(_hiptensor_cmd "\"./bin/${_hiptensor_dev_bin}\"")
        if(NOT _hiptensor_dev_cfg STREQUAL "NONE")
            # dev_cfg is "<component>/<rest>"; the on-disk source lives at
            # <component>/configs/<cfgdir>/<rest>. Skip this (test, tier) when
            # that config is empty (covered by an earlier tier).
            string(FIND "${_hiptensor_dev_cfg}" "/" _hiptensor_dev_slash)
            string(SUBSTRING "${_hiptensor_dev_cfg}" 0 ${_hiptensor_dev_slash} _hiptensor_dev_comp)
            math(EXPR _hiptensor_dev_after "${_hiptensor_dev_slash}+1")
            string(SUBSTRING "${_hiptensor_dev_cfg}" ${_hiptensor_dev_after} -1 _hiptensor_dev_rest)
            _hiptensor_config_is_empty(
                "${CMAKE_CURRENT_SOURCE_DIR}/${_hiptensor_dev_comp}/configs/${_hiptensor_cfgdir}/${_hiptensor_dev_rest}"
                _hiptensor_cfg_empty)
            if(_hiptensor_cfg_empty)
                continue()
            endif()
            string(APPEND _hiptensor_cmd " -y ./bin/${PROJECT_NAME}/configs/${_hiptensor_cfgdir}/${_hiptensor_dev_cfg}")
        endif()
        _hiptensor_emit_test("${_hiptensor_dev_bin} ${_hiptensor_suffix}" "${_hiptensor_cmd}")
    endforeach()
endforeach()

# ffm-quick / ffm-full: device only. ffm-quick uses emulation/quick, ffm-full
# uses emulation/full (they are now distinct sets, not aliases).
foreach(_hiptensor_family "ffm-quick|emulation/quick" "ffm-full|emulation/full")
    string(REPLACE "|" ";" _hiptensor_ff "${_hiptensor_family}")
    list(GET _hiptensor_ff 0 _hiptensor_suffix)
    list(GET _hiptensor_ff 1 _hiptensor_cfgdir)

    foreach(_hiptensor_dev IN LISTS _HIPTENSOR_DEVICE_TESTS)
        string(REPLACE "|" ";" _hiptensor_dev_fields "${_hiptensor_dev}")
        list(GET _hiptensor_dev_fields 0 _hiptensor_dev_bin)
        list(GET _hiptensor_dev_fields 1 _hiptensor_dev_cfg)
        set(_hiptensor_cmd "\"./bin/${_hiptensor_dev_bin}\"")
        if(NOT _hiptensor_dev_cfg STREQUAL "NONE")
            # Skip this (test, tier) when its config is empty (see tier loop).
            string(FIND "${_hiptensor_dev_cfg}" "/" _hiptensor_dev_slash)
            string(SUBSTRING "${_hiptensor_dev_cfg}" 0 ${_hiptensor_dev_slash} _hiptensor_dev_comp)
            math(EXPR _hiptensor_dev_after "${_hiptensor_dev_slash}+1")
            string(SUBSTRING "${_hiptensor_dev_cfg}" ${_hiptensor_dev_after} -1 _hiptensor_dev_rest)
            _hiptensor_config_is_empty(
                "${CMAKE_CURRENT_SOURCE_DIR}/${_hiptensor_dev_comp}/configs/${_hiptensor_cfgdir}/${_hiptensor_dev_rest}"
                _hiptensor_cfg_empty)
            if(_hiptensor_cfg_empty)
                continue()
            endif()
            string(APPEND _hiptensor_cmd " -y ./bin/${PROJECT_NAME}/configs/${_hiptensor_cfgdir}/${_hiptensor_dev_cfg}")
        endif()
        _hiptensor_emit_test("${_hiptensor_dev_bin} ${_hiptensor_suffix}" "${_hiptensor_cmd}")
    endforeach()
endforeach()

# bench: performance runs. Uses the "bench" config category and performance
# run-count flags (more hot runs for stable timings, matching the reference
# scripts under scripts/performance/). Named "<binary> bench".
foreach(_hiptensor_bench IN LISTS _HIPTENSOR_BENCH_TESTS)
    string(REPLACE "|" ";" _hiptensor_bench_fields "${_hiptensor_bench}")
    list(GET _hiptensor_bench_fields 0 _hiptensor_bench_bin)
    list(GET _hiptensor_bench_fields 1 _hiptensor_bench_cfg)
    set(_hiptensor_cmd "\"./bin/${_hiptensor_bench_bin}\" -y ./bin/${PROJECT_NAME}/configs/bench/${_hiptensor_bench_cfg}")
    string(APPEND _hiptensor_cmd " --hot_runs 5 --cold_runs 1")
    _hiptensor_emit_test("${_hiptensor_bench_bin} bench" "${_hiptensor_cmd}")
endforeach()

# Bake YAML-driven category labels into each install-tree
# CTestTestfile.cmake fragment so `ctest --test-dir
# <prefix>/bin/hiptensor[/{smoke,regression}] -L <tier>` works against
# the install tree. Passing the install file as the 2nd argument tells
# the shared helper to scan it for the test names already written via
# add_test() above and emit explicit set_tests_properties(... LABELS ...)
# lines (the only label form ctest's script interpreter exposes through
# --print-labels / -L).
if(COMMAND apply_ctest_category_labels)
    apply_ctest_category_labels(
        "${_HIPTENSOR_TEST_CATEGORIES_YAML}"
        "${INSTALL_TEST_FILE}"
    )
    apply_ctest_category_labels(
        "${_HIPTENSOR_TEST_CATEGORIES_YAML}"
        "${INSTALL_SMOKE_TEST_FILE}"
    )
    apply_ctest_category_labels(
        "${_HIPTENSOR_TEST_CATEGORIES_YAML}"
        "${INSTALL_REGRESSION_TEST_FILE}"
    )
endif()

include(../cmake/Functions/CopyYamlFolder.cmake)

# Copy multiple configuration folders
copy_emulation_config_folders(
    SOURCE_FOLDERS
        "01_contraction/configs/emulation"
        "02_elementwise/configs/emulation"
        "03_reduction/configs/emulation"
    SOURCE_SCRIPTS
        "../rtest.py"
        "../rtest.xml"
    DESTINATION_PREFIX
        "test_emulation_config"
)

copy_code_coverage_config_files(
    ${CMAKE_CURRENT_SOURCE_DIR}
    "${CMAKE_BINARY_DIR}/code_coverage_config"
)

rocm_install(
    FILES "${INSTALL_TEST_FILE}"
    DESTINATION "${CMAKE_INSTALL_BINDIR}/${PROJECT_NAME}"
    COMPONENT tests
    RENAME "CTestTestfile.cmake"
)

rocm_install(
    FILES "${INSTALL_SMOKE_TEST_FILE}"
    DESTINATION "${CMAKE_INSTALL_BINDIR}/${PROJECT_NAME}/smoke"
    COMPONENT tests
    RENAME "CTestTestfile.cmake"
)

rocm_install(
    FILES "${INSTALL_REGRESSION_TEST_FILE}"
    DESTINATION "${CMAKE_INSTALL_BINDIR}/${PROJECT_NAME}/regression"
    COMPONENT tests
    RENAME "CTestTestfile.cmake"
)

# Install the config subtrees so the validation / ffm / bench entries' `-y` paths
# resolve at ctest runtime. The on-disk config layout mirrors the install
# layout 1:1, so the install path segment IS the on-disk source subtree:
#   src  <component>/configs/<dir>/
#   dest bin/hiptensor/configs/<dir>/<component>/
foreach(_hiptensor_cfg_dir
        validation/quick validation/standard validation/comprehensive validation/full
        emulation/quick emulation/full
        bench)
    foreach(_hiptensor_cfg_component 01_contraction 02_elementwise 03_reduction)
        rocm_install(
            DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${_hiptensor_cfg_component}/configs/${_hiptensor_cfg_dir}/"
            DESTINATION "${CMAKE_INSTALL_BINDIR}/${PROJECT_NAME}/configs/${_hiptensor_cfg_dir}/${_hiptensor_cfg_component}"
            COMPONENT tests
        )
    endforeach()
endforeach()

rocm_install(DIRECTORY ${CMAKE_SOURCE_DIR}/scripts/performance/
    DESTINATION "${CMAKE_INSTALL_BINDIR}/${PROJECT_NAME}/bench/scripts"
    COMPONENT tests
    FILE_PERMISSIONS
        OWNER_READ OWNER_EXECUTE OWNER_WRITE
        GROUP_READ GROUP_EXECUTE
        WORLD_READ WORLD_EXECUTE
    DIRECTORY_PERMISSIONS
        OWNER_READ OWNER_EXECUTE OWNER_WRITE
        GROUP_READ GROUP_EXECUTE GROUP_WRITE
        WORLD_READ WORLD_EXECUTE
)
