# -----------------------------------------------------------------------------------------------------------
# Copyright (c) 2025-2026 Huawei Technologies Co., Ltd.
# This program is free software, you can redistribute it and/or modify it under the terms and conditions of
# CANN Open Software License Agreement Version 2.0 (the "License").
# Please refer to the License for details. You may not use this file except in compliance with the License.
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
# See LICENSE in the root of the software repository for the full text of the License.
# -----------------------------------------------------------------------------------------------------------

set(PTO_Fwk_STestCaseLibraries             "" CACHE INTERNAL "" FORCE)     # STest 各模块 用例实现二进制
set(PTO_Fwk_STestCaseLdLibrariesExt        "" CACHE INTERNAL "" FORCE)     # STest 各模块 额外 Load 二进制
set(PTO_Fwk_STestCaseGoldenScriptPathList  "" CACHE INTERNAL "" FORCE)     # STest 各模块 Golden 脚本路径配置

# 切换完成前, 增加原有目录
set(PTO_Fwk_STestCaseGoldenScriptPathList ${PTO_Fwk_STestCaseGoldenScriptPathList} ${PTO_FWK_SRC_ROOT}/framework/tests/cmake/scripts/golden/net/deepseekv3/mla CACHE INTERNAL "" FORCE)
set(PTO_Fwk_STestCaseGoldenScriptPathList ${PTO_Fwk_STestCaseGoldenScriptPathList} ${PTO_FWK_SRC_ROOT}/framework/tests/cmake/scripts/golden/net/deepseekv3/nsa CACHE INTERNAL "" FORCE)
set(PTO_Fwk_STestCaseGoldenScriptPathList ${PTO_Fwk_STestCaseGoldenScriptPathList} ${PTO_FWK_SRC_ROOT}/framework/tests/cmake/scripts/golden/op CACHE INTERNAL "" FORCE)
set(PTO_Fwk_STestCaseGoldenScriptPathList ${PTO_Fwk_STestCaseGoldenScriptPathList} ${PTO_FWK_SRC_ROOT}/framework/tests/st/operator/src/test_deepseek_v3.2_exp CACHE INTERNAL "" FORCE)

# 用于添加 STest 测试用例二进制库
#[[
Parameters:
  one_value_keywords:
      TARGET                : [Required] 具体测试用例二进制库名称
  multi_value_keywords:
      SOURCES               : [Required] 编译源码
      LD_LIBRARIES_EXT      : [Optional] 需要在执行时将所在路径配置到环境变量 LD_LIBRARY_PATH 中的 Libraries
      GOLDEN_SCRIPT_DIR     : [Optional] Golden 脚本所在路径, 便于 Golden 处理公共逻辑查找和载入对应脚本
Attention:
    1.  一般 LD_LIBRARIES_EXT 内配置的二进制, 在正常 source CANN 包环境变量后, LD_LIBRARY_PATH 内也应包含其所在路径;
]]
function(PTO_Fwk_STest_AddLib)
    cmake_parse_arguments(
            ARG
            ""
            "TARGET"
            "SOURCES;LD_LIBRARIES_EXT;GOLDEN_SCRIPT_DIR"
            ""
            ${ARGN}
    )
    add_Library(${ARG_TARGET} STATIC)
    target_sources(${ARG_TARGET} PRIVATE ${ARG_SOURCES})
    target_link_libraries(${ARG_TARGET}
            PRIVATE
                ${PTO_Fwk_STestNamePrefix}_utils
                GTest::gtest
    )
    set(PTO_Fwk_STestCaseLibraries            ${PTO_Fwk_STestCaseLibraries}            ${ARG_TARGET}            CACHE INTERNAL "" FORCE)
    set(PTO_Fwk_STestCaseLdLibrariesExt       ${PTO_Fwk_STestCaseLdLibrariesExt}       ${ARG_LD_LIBRARIES_EXT}  CACHE INTERNAL "" FORCE)
    set(PTO_Fwk_STestCaseGoldenScriptPathList ${PTO_Fwk_STestCaseGoldenScriptPathList} ${ARG_GOLDEN_SCRIPT_DIR} CACHE INTERNAL "" FORCE)
endfunction()

function(PTO_Fwk_STest_GetGTestFilterList GTEST_FILTER_LIST)
    get_filename_component(_ClsFile "${PTO_FWK_SRC_ROOT}/framework/tests/st/configs" REALPATH)
    PTO_Fwk_GTest_GetGTestFilterStr(GTestFilterStr
            CLASSIFY        ${_ClsFile}
            TESTS_TYPE      stest
            TESTS_GROUP     ${ENABLE_STEST_GROUP}
            CHANGED_FILE    ${ENABLE_TESTS_EXECUTE_CHANGED_FILE}
    )
    string(REPLACE ":" ";" GTestFilterList "${GTestFilterStr}")
    list(LENGTH GTestFilterList YamlGTestFilterListLen)
    list(REMOVE_DUPLICATES GTestFilterList)
    set(${GTEST_FILTER_LIST} ${GTestFilterList} PARENT_SCOPE)
    list(LENGTH GTestFilterList RstGTestFilterListLen)
    message(STATUS "GetSTestFilterList: Yaml(${YamlGTestFilterListLen}), Total(${RstGTestFilterListLen})")
endfunction()

# STest 生成 Golden 数据
#[[
Parameters:
  one_value_keywords:
      TARGET             : [Required] 指定所依赖的目标(POST_BUILD)
  multi_value_keywords:
      GTEST_FILTER_LIST  : [Required] GTestFilter 配置, Filter 间以 ';' 分割
]]
function(PTO_Fwk_STest_RunExe_GenerateGolden)
    cmake_parse_arguments(
            ARG
            ""
            "TARGET"
            "GTEST_FILTER_LIST"
            ""
            ${ARGN}
    )
    if (ENABLE_TESTS_EXECUTE)
        set(_Args)
        list(APPEND _Args "-o=${ENABLE_STEST_GOLDEN_PATH}")

        string(REPLACE ";" ":" GTestFilterStr "${ARG_GTEST_FILTER_LIST}")
        list(APPEND _Args "-c=${GTestFilterStr}")
        list(REMOVE_DUPLICATES PTO_Fwk_STestCaseGoldenScriptPathList)
        foreach (_Path ${PTO_Fwk_STestCaseGoldenScriptPathList})
            list(APPEND _Args "--path=${_Path}")
        endforeach ()

        if (ENABLE_STEST_GOLDEN_PATH_CLEAN)
            list(APPEND _Args "--clean")
        endif ()

        get_filename_component(GoldenCtrlPy    "${PTO_FWK_SRC_ROOT}/cmake/scripts/golden_ctrl.py" REALPATH)
        get_filename_component(GoldenCtrlPyCwd "${PTO_FWK_SRC_ROOT}/cmake/scripts" REALPATH)
        list(LENGTH ARG_GTEST_FILTER_LIST GTestFilterListLen)
        add_custom_command(
                TARGET ${ARG_TARGET} POST_BUILD
                COMMAND ${Python3_EXECUTABLE} ${GoldenCtrlPy} ARGS ${_Args}
                COMMENT "Generator Golden(${GTestFilterListLen}) for ${ARG_TARGET}"
                WORKING_DIRECTORY ${GoldenCtrlPyCwd}
        )
    endif ()
endfunction()

# STest 执行可执行程序
#[[
Parameters:
  one_value_keywords:
      TARGET             : [Required] 用于指定具体 GTest 可执行目标, 用例会在该目标编译完成后(POST_BUILD)启动执行
  multi_value_keywords:
      LD_LIBRARIES_EXT   : [Optional] 需要在执行时将所在路径配置到环境变量 LD_LIBRARY_PATH 中的 Libraries
      ENV_LINES_EXT      : [Optional] 需要额外配置的环境变量, 按照 "K=V" 格式组织
      GTEST_FILTER_LIST  : [Optional] GTestFilter 配置, Filter 间以 ';' 分割
Attention:
    1. 可以多次调用本函数以添加多个'执行任务'; 单次调用本函数时, 可以通过在 GTEST_FILTER_LIST 中配置多个过滤条件('gtest_filter') 以实现执行多用例;
]]
function(PTO_Fwk_STest_RunExe)
    cmake_parse_arguments(
            ARG
            ""
            "TARGET"
            "LD_LIBRARIES_EXT;ENV_LINES_EXT;GTEST_FILTER_LIST"
            ""
            ${ARGN}
    )
    if (ENABLE_TESTS_EXECUTE)
        # 命令行参数处理
        PTO_Fwk_GTest_RunExe_GetPreExecSetup(PyCmdSetup PyEnvLines BashCmdSetup
                TARGET              ${ARG_TARGET}
                ENV_LINES_EXT       ${ARG_ENV_LINES_EXT}
                LD_LIBRARIES_EXT    ${ARG_LD_LIBRARIES_EXT}
        )
        # 执行流程
        list(LENGTH ARG_GTEST_FILTER_LIST GtestFilterListLen)
        string(REPLACE ";" ":" GtestFilterStr "${ARG_GTEST_FILTER_LIST}")
        list(LENGTH PTO_Fwk_StestExecuteDeviceIdList DeviceIdListLen)
        string(REPLACE ";" ", " DeviceIdStr "${PTO_Fwk_StestExecuteDeviceIdList}")
        message(STATUS "Run GTest(${ARG_TARGET}), XSAN(ASAN:${ENABLE_ASAN} UBSAN:${ENABLE_UBSAN}), Device(${DeviceIdListLen})=[${DeviceIdStr}], GTestFilter(${GtestFilterListLen})=${GtestFilterStr}")
        set(Comment "Run GTest(${ARG_TARGET}), XSAN(ASAN:${ENABLE_ASAN} UBSAN:${ENABLE_UBSAN})")

        if (ARG_GTEST_FILTER_LIST)
            if (ENABLE_TESTS_EXECUTE_PARALLEL OR (DeviceIdListLen GREATER 1))
                # 仅在使能并行执行全局开关, 且需要做 filter 时才进行执行加速
                set(_File $<TARGET_FILE:${ARG_TARGET}>)
                set(_Args "-t=${_File}" "--cases=${GtestFilterStr}" "--halt_on_error")
                foreach (DevId ${PTO_Fwk_StestExecuteDeviceIdList})
                    list(APPEND _Args "--device=${DevId}")
                endforeach ()
                if (PyEnvLines)
                    list(APPEND _Args "--env" "${PyEnvLines}")
                endif ()
                get_filename_component(ParallelPy    "${PTO_FWK_SRC_ROOT}/cmake/scripts/stest_accelerate.py" REALPATH)
                get_filename_component(ParallelPyCwd "${PTO_FWK_SRC_ROOT}/cmake/scripts" REALPATH)
                add_custom_command(
                        TARGET ${ARG_TARGET} POST_BUILD
                        COMMAND ${PyCmdSetup} ${Python3_EXECUTABLE} ${ParallelPy} ARGS ${_Args}
                        COMMENT "${Comment} With Parallel Execute Accelerate"
                        WORKING_DIRECTORY ${ParallelPyCwd}
                )
            else ()
                set(GtestFilterListIdx 1)
                foreach (Filter ${ARG_GTEST_FILTER_LIST})
                    add_custom_command(
                            TARGET ${ARG_TARGET} POST_BUILD
                            COMMAND ${BashCmdSetup} ./${ARG_TARGET} ARGS '--gtest_filter=${Filter}'
                            COMMENT "${Comment} [${GtestFilterListIdx}/${GtestFilterListLen}] With --gtest_filter=${Filter}"
                            WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
                    )
                    math(EXPR GtestFilterListIdx "${GtestFilterListIdx} + 1")
                endforeach ()
            endif ()
        else ()
            message(STATUS "No cases need to run.")
        endif ()
    endif ()
endfunction()

# STest 用于添加并执行 可执行文件
#[[
Parameters:
  one_value_keywords:
      TARGET                        : [Required] 用于指定具体 GTest 可执行目标
]]
function(PTO_Fwk_STest_AddExe_RunExe)
    cmake_parse_arguments(
            ARG
            ""
            "TARGET"
            ""
            ""
            ${ARGN}
    )
    #
    # 编译
    #
    set(_Sources ${CMAKE_CURRENT_BINARY_DIR}/${PTO_Fwk_STestNamePrefix}_main_stub.cpp)
    execute_process(COMMAND touch ${_Sources})
    list(REMOVE_DUPLICATES PTO_Fwk_STestCaseLibraries)
    set(PTO_Fwk_Libraries
            tile_fwk_utils
            tile_fwk_adapter
            tile_fwk_cann_host_runtime
            tile_fwk_platform
            tile_fwk_interface
            tile_fwk_codegen
            tile_fwk_compiler
            tile_fwk_runtime
            tile_fwk_simulation
            tile_fwk_operator
    )
    PTO_Fwk_GTest_AddExe(
            TARGET                      ${ARG_TARGET}
            SOURCES                     ${_Sources}
            PRIVATE_LINK_LIBRARIES      ${PTO_Fwk_STestNamePrefix}_utils ${PTO_Fwk_Libraries} ${PTO_Fwk_STestCaseLibraries}
    )
    if (ENABLE_TORCH_VERIFIER)
        add_dependencies(${ARG_TARGET} tile_fwk_calculator)
    endif()

    #
    # 执行
    #
    set(EnvLinesExt
            "TILE_FWK_STEST_GOLDEN_PATH=${ENABLE_STEST_GOLDEN_PATH}"
    )
    PTO_Fwk_STest_GetGTestFilterList(GTestFilterList)
    if (NOT "${ENABLE_STEST}" STREQUAL "ON")
        set(GTestFilterList ${ENABLE_STEST})
        string(REPLACE ":" ";" GTestFilterList "${GTestFilterList}")
    endif ()
    list(REMOVE_DUPLICATES GTestFilterList)
    list(REMOVE_DUPLICATES PTO_Fwk_STestCaseLdLibrariesExt)

    if (NOT "$ENV{GTEST_START}" STREQUAL "")
        list(FIND GTestFilterList $ENV{GTEST_START} idx)
        if (NOT ${idx} EQUAL -1)
            list(SUBLIST GTestFilterList ${idx} -1 GTestFilterList)
        endif ()
    endif ()

    if ("${GTestFilterList}x" STREQUAL "x")
        message(STATUS "No Case to Execute")
    else ()
        # 精度用例
        PTO_Fwk_STest_RunExe_GenerateGolden(TARGET ${ARG_TARGET} GTEST_FILTER_LIST ${GTestFilterList})
        PTO_Fwk_STest_RunExe(
                TARGET              ${ARG_TARGET}
                ENV_LINES_EXT       ${EnvLinesExt}
                LD_LIBRARIES_EXT    ${PTO_Fwk_STestCaseLdLibrariesExt}
                GTEST_FILTER_LIST   ${GTestFilterList}
        )
    endif ()
endfunction()

# Distributed 获取 GTestFilterList
#[[
Parameters:
  multi_value_keywords:
      GTEST_FILTER_CONFIG   : [Required] GtestFilterConfig, 以 CaseName RankSize 顺序存储
      GTEST_FILTER_LIST     : [Required] GtestFilterList, 仅包含 CaseName
]]
function(PTO_Fwk_STest_Distributed_GetGTestFilterList GTEST_FILTER_LIST)
    cmake_parse_arguments(
            ARG
            ""
            ""
            "GTEST_FILTER_CONFIG"
            ""
            ${ARGN}
    )
    # Config 到 List 的转换
    set(Idx 0)
    set(FilterList)
    foreach (CFG ${ARG_GTEST_FILTER_CONFIG})
        math(EXPR Idx "${Idx} + 1")
        math(EXPR Remainder "${Idx} % 2")  # 计算索引除以2的余数
        if (Remainder EQUAL 1)
            # 支持由 ENABLE_STEST_DISTRIBUTED 传入指定的 Filter
            if ("${ENABLE_STEST_DISTRIBUTED}" STREQUAL "ON")
                list(APPEND FilterList ${CFG})
            else ()
                string(REPLACE ":" ";" SpecifyGtestFilterList ${ENABLE_STEST_DISTRIBUTED})
                list(FIND SpecifyGtestFilterList ${ENABLE_STEST_DISTRIBUTED} _CfgIdx)
                if (NOT "${_SepIdx}" STREQUAL "-1")
                    list(APPEND FilterList ${ENABLE_STEST_DISTRIBUTED})
                endif ()
            endif ()
        endif()
    endforeach()
    list(REMOVE_DUPLICATES FilterList)
    set(${GTEST_FILTER_LIST} ${FilterList} PARENT_SCOPE)
endfunction()

# Distributed 查询 RankSize
#[[
Parameters:
  multi_value_keywords:
      GTEST_FILTER_CONFIG   : [Required] GtestFilterConfig, 以 CaseName RankSize 顺序存储
      RANK_SIZE             : [Required] RankSize
]]
function(PTO_Fwk_STest_Distributed_GetRankSize RANK_SIZE)
    cmake_parse_arguments(
            ARG
            ""
            "GTEST_FILTER"
            "GTEST_FILTER_CONFIG"
            ""
            ${ARGN}
    )
    set(Idx 0)
    set(RandSize)
    foreach (CFG ${ARG_GTEST_FILTER_CONFIG})
        if ("${CFG}" STREQUAL "${ARG_GTEST_FILTER}")
            math(EXPR RankSizeIdx "${Idx} + 1")
            list(GET ARG_GTEST_FILTER_CONFIG ${RankSizeIdx} RandSize)
            break()
        endif ()
        math(EXPR Idx "${Idx} + 1")
    endforeach()
    if (NOT RandSize)
        message(FATAL_ERROR "Can't get RandSize, GTestFilter(${ARG_GTEST_FILTER})")
    endif ()
    set(${RANK_SIZE} ${RandSize} PARENT_SCOPE)
endfunction()

# 用于执行 Distributed 相关的 ST 用例
#[[
Parameters:
  one_value_keywords:
      TARGET             : [Required] 用于指定具体 GTest 可执行文件, 用例会在该目标编译完成后启动执行
  multi_value_keywords:
      LD_LIBRARIES_EXT   : [Optional] 需要在执行时将所在路径配置到环境变量 LD_LIBRARY_PATH 中的 Libraries
      ENV_LINES_EXT      : [Optional] 需要额外配置的环境变量, 按照 "K=V" 格式组织
      GTEST_FILTER_LIST  : [Optional] GTestFilter 配置, Filter 间以 ';' 分割
      GOLDEN_SCRIPT_DIR  : [Optional] Golden 脚本所在路径, 便于 Golden 处理公共逻辑查找和载入对应脚本
]]
function(PTO_Fwk_STest_Distributed_RunExe)
    cmake_parse_arguments(
            ARG
            ""
            "TARGET"
            "LD_LIBRARIES_EXT;ENV_LINES_EXT;GTEST_FILTER_CONFIG;GOLDEN_SCRIPT_DIR"
            ""
            ${ARGN}
    )
    set(PTO_Fwk_STestCaseGoldenScriptPathList ${PTO_Fwk_STestCaseGoldenScriptPathList} ${ARG_GOLDEN_SCRIPT_DIR} CACHE INTERNAL "" FORCE)
    if (ENABLE_TESTS_EXECUTE)
        # Config 到 List 转换, 并处理由 ENABLE_STEST_DISTRIBUTED 传入指定的 Filter 的情况
        PTO_Fwk_STest_Distributed_GetGTestFilterList(GTestFilterList
                GTEST_FILTER_CONFIG ${ARG_GTEST_FILTER_CONFIG}
        )
        # Distributed 用例当前需配置 RankSize, 故此处做判空处理:
        # 1. 当指定执行的某个用例不在 Distributed 范围, 此处 GTestFilterList 为空, 不触发执行;
        # 2. 当指定执行的某个用例属于 Distributed 范围, 此处 GTestFilterList 非空, 会触发执行;
        #    此时 tile_fwk_stest 由于无法判断指定用例是否在执行范围, 默认会触发执行, 但由于对应用例不在 tile_fwk_stest 承载,
        #    执行会报错; 所以此种场景需要 Distributed 指定具体 target, 如:
        #    python3 build_ci.py -t=tile_fwk_stest_distributed -stest_distributed=xxx
        set(MaxRankSize 0)
        foreach (Filter ${GTestFilterList})
            PTO_Fwk_STest_Distributed_GetRankSize(RankSize
                    GTEST_FILTER        ${Filter}
                    GTEST_FILTER_CONFIG ${ARG_GTEST_FILTER_CONFIG}
            )
            # 比较并更新最大RankSize
            if (${RankSize} GREATER ${MaxRankSize})
                set(MaxRankSize ${RankSize})
            endif ()
        endforeach ()
        if (GTestFilterList)
            # 命令行参数处理
            PTO_Fwk_GTest_RunExe_GetPreExecSetup(PyCmdSetup PyEnvLines BashCmdSetup
                    TARGET              ${ARG_TARGET}
                    ENV_LINES_EXT       ${ARG_ENV_LINES_EXT}
                    LD_LIBRARIES_EXT    ${ARG_LD_LIBRARIES_EXT}
            )
            # Golden 生成
            PTO_Fwk_STest_RunExe_GenerateGolden(TARGET ${ARG_TARGET} GTEST_FILTER_LIST ${GTestFilterList})
            # 执行流程
            math(EXPR MaxRankSizeTimes2 "${MaxRankSize} * 2")
            string(REPLACE ";" ":" GtestFilterStr "${GTestFilterList}")
            list(LENGTH PTO_Fwk_StestExecuteDeviceIdList DeviceIdListLen)
            set(Comment "Run GTest(${ARG_TARGET}) XSAN(ASAN:${ENABLE_ASAN} UBSAN:${ENABLE_UBSAN})")
            if(ENABLE_TESTS_EXECUTE_PARALLEL OR (DeviceIdListLen GREATER_EQUAL MaxRankSizeTimes2))
                set(_File $<TARGET_FILE:${ARG_TARGET}>)
                set(_Args "-t=${_File}" "--cases=${GtestFilterStr}" "--halt_on_error")
                foreach (DevId ${PTO_Fwk_StestExecuteDeviceIdList})
                    list(APPEND _Args "--device=${DevId}")
                endforeach ()
                if (ENABLE_TESTS_EXECUTE_PARALLEL_TIMEOUT)
                    list(APPEND _Args "--timeout=${ENABLE_TESTS_EXECUTE_PARALLEL_TIMEOUT}")
                endif ()
                if (PyEnvLines)
                    list(APPEND _Args "--env" "${PyEnvLines}")
                endif ()
                list(APPEND _Args "--rank_size" "${MaxRankSize}")
                get_filename_component(ParallelPy    "${PTO_FWK_SRC_ROOT}/cmake/scripts/distributed_stest_accelerate.py" REALPATH)
                get_filename_component(ParallelPyCwd "${PTO_FWK_SRC_ROOT}/cmake/scripts" REALPATH)
                add_custom_command(
                        TARGET ${ARG_TARGET} POST_BUILD
                        COMMAND ${PyCmdSetup} ${Python3_EXECUTABLE} ${ParallelPy} ARGS ${_Args}
                        COMMENT "${Comment} With Parallel Execute Accelerate"
                        WORKING_DIRECTORY ${ParallelPyCwd}
                )
            else ()
                set(GtestFilterListIdx 1)
                message(STATUS "Run GTest(${ARG_TARGET}) XSAN(ASAN:${ENABLE_ASAN} UBSAN:${ENABLE_UBSAN}) GTestFilter(${GtestFilterListLen})=${GtestFilterStr}")
                foreach (Filter ${GTestFilterList})
                    PTO_Fwk_STest_Distributed_GetRankSize(RankSize
                            GTEST_FILTER        ${Filter}
                            GTEST_FILTER_CONFIG ${ARG_GTEST_FILTER_CONFIG}
                    )
                    add_custom_command(
                            TARGET ${ARG_TARGET} POST_BUILD
                            COMMAND ${BashCmdSetup} mpirun -n ${RankSize} ./${ARG_TARGET} ARGS '--gtest_filter=${Filter}'
                            COMMENT "${Comment} [${GtestFilterListIdx}/${GtestFilterListLen}] With --gtest_filter=${Filter}"
                            WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
                    )
                    math(EXPR GtestFilterListIdx "${GtestFilterListIdx} + 1")
                endforeach ()
            endif ()
        endif ()
    endif ()
endfunction()