* Copyright (c) 2025 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.
*/
#include <gtest/gtest.h>
#include <mockcpp/mockcpp.hpp>
#include <securec.h>
#include "hccl_communicator.h"
#include "coll_alg_param.h"
#include "aicpu_operator_pub.h"
#include "mem_host_pub.h"
#include "mem_device_pub.h"
#include "stream_pub.h"
#include "launch_aicpu.h"
#include "adapter_rts_common.h"
using namespace hccl;
static int g_launchCallCount = 0;
static HcclResult LaunchCountStub(
const rtStream_t, void *, u32, aclrtBinHandle, const std::string &, bool, u16, void *, u32)
{
++g_launchCallCount;
return HCCL_SUCCESS;
}
class AclgraphCommunicatorTest : public testing::Test {
protected:
void SetUp() override
{
GlobalMockObject::reset();
}
void TearDown() override
{
GlobalMockObject::reset();
HcclCommunicator::linkResMap_.clear();
communicator_.resMap_.clear();
communicator_.tagsRequiringHostCleanup_.clear();
communicator_.rankTagRemoteRes_.clear();
communicator_.hostMemVec_.clear();
communicator_.deviceMemVec_.clear();
communicator_.hostResMap_.clear();
}
HcclCommunicator communicator_;
};
* @brief TC-COMM-01: ClearResMap 正常路径
* 验证:resMap_ 中存在 tag → findTag=true,tag 被清除
*/
TEST_F(AclgraphCommunicatorTest, ClearResMap_Normal)
{
std::string tag = "test_tag";
bool findTag = false;
communicator_.resMap_[tag] = AlgResourceResponse();
HcclResult ret = communicator_.ClearResMap(tag, findTag, false);
EXPECT_EQ(ret, HCCL_SUCCESS);
EXPECT_TRUE(findTag);
EXPECT_EQ(communicator_.resMap_.count(tag), 0);
}
* @brief TC-COMM-02: ClearResMap tag 不存在
* 验证:resMap_ 中不存在 tag → findTag=false
*/
TEST_F(AclgraphCommunicatorTest, ClearResMap_TagNotFound)
{
std::string tag = "nonexistent_tag";
bool findTag = false;
HcclResult ret = communicator_.ClearResMap(tag, findTag, false);
EXPECT_EQ(ret, HCCL_SUCCESS);
EXPECT_FALSE(findTag);
}
* @brief TC-COMM-03: ClearResMap 传递 aclGraphDestroyCbk 参数
* 验证:aclGraphDestroyCbk=true 时正常传递到 DestroyAlgResource
*/
TEST_F(AclgraphCommunicatorTest, ClearResMap_WithDestroyCbk)
{
std::string tag = "test_tag";
bool findTag = false;
communicator_.resMap_[tag] = AlgResourceResponse();
HcclResult ret = communicator_.ClearResMap(tag, findTag, true);
EXPECT_EQ(ret, HCCL_SUCCESS);
EXPECT_TRUE(findTag);
EXPECT_EQ(communicator_.resMap_.count(tag), 0);
}
* @brief TC-COMM-04: ClearOpResource 清除三种后缀
* 验证:tag/tag_host/tag_device 三种后缀全部被清除
*/
TEST_F(AclgraphCommunicatorTest, ClearOpResource_AllSuffixes)
{
std::string tag = "test_tag";
communicator_.resMap_[tag] = AlgResourceResponse();
communicator_.resMap_[tag + "_host"] = AlgResourceResponse();
communicator_.resMap_[tag + "_device"] = AlgResourceResponse();
HcclResult ret = communicator_.ClearOpResource(tag, false);
EXPECT_EQ(ret, HCCL_SUCCESS);
EXPECT_EQ(communicator_.resMap_.count(tag), 0);
EXPECT_EQ(communicator_.resMap_.count(tag + "_host"), 0);
EXPECT_EQ(communicator_.resMap_.count(tag + "_device"), 0);
}
* @brief TC-COMM-05: ClearOpResource 部分后缀存在
* 验证:只存在 tag/tag_host 时,不崩溃,正常清除存在的条目
*/
TEST_F(AclgraphCommunicatorTest, ClearOpResource_PartialSuffixes)
{
std::string tag = "test_tag";
communicator_.resMap_[tag] = AlgResourceResponse();
communicator_.resMap_[tag + "_host"] = AlgResourceResponse();
HcclResult ret = communicator_.ClearOpResource(tag, false);
EXPECT_EQ(ret, HCCL_SUCCESS);
EXPECT_EQ(communicator_.resMap_.count(tag), 0);
EXPECT_EQ(communicator_.resMap_.count(tag + "_host"), 0);
}
* @brief TC-COMM-06: HasRoceTransportLinks 有 RDMA
* 验证:transportRequests 中存在 isUsedRdma=true → true
*/
TEST_F(AclgraphCommunicatorTest, HasRoceTransportLinks_WithRdma)
{
OpCommTransport tpt;
tpt.resize(1);
tpt[0].resize(1);
tpt[0][0].transportRequests.resize(1);
tpt[0][0].transportRequests[0].isUsedRdma = true;
EXPECT_TRUE(communicator_.HasRoceTransportLinks(tpt));
}
* @brief TC-COMM-07: HasRoceTransportLinks 无 RDMA
* 验证:transportRequests 中 isUsedRdma=false → false
*/
TEST_F(AclgraphCommunicatorTest, HasRoceTransportLinks_NoRdma)
{
OpCommTransport tpt;
tpt.resize(1);
tpt[0].resize(1);
tpt[0][0].transportRequests.resize(1);
tpt[0][0].transportRequests[0].isUsedRdma = false;
EXPECT_FALSE(communicator_.HasRoceTransportLinks(tpt));
}
* @brief TC-COMM-08: DestroyAlgResource 参数传递验证
* 验证:aclGraphDestroyCbk=true 时,DestroyOpTransportResponse 中
* 非 RDMA 链路被保留(linkResMap_ 中不擦除),验证参数正确传递
*/
TEST_F(AclgraphCommunicatorTest, DestroyAlgResource_WithDestroyCbk)
{
std::string tag = "test_tag";
bool findTag = false;
AlgResourceResponse res;
res.opTransportResponse.resize(1);
res.opTransportResponse[0].resize(1);
res.opTransportResponse[0][0].virtualLinks.resize(1);
res.opTransportResponse[0][0].virtualLinks[0] = std::make_shared<Transport>();
res.opTransportResponse[0][0].links.resize(1);
res.opTransportResponse[0][0].links[0] = std::make_shared<Transport>();
res.opTransportResponse[0][0].transportRequests.resize(1);
res.opTransportResponse[0][0].transportRequests[0].isUsedRdma = false;
res.opTransportResponse[0][0].transportRequests[0].isValid = true;
communicator_.resMap_[tag] = res;
communicator_.linkResMap_[res.opTransportResponse[0][0].virtualLinks[0].get()] = LinkInfo();
communicator_.linkResMap_[res.opTransportResponse[0][0].links[0].get()] = LinkInfo();
HcclResult ret = communicator_.ClearResMap(tag, findTag, true);
EXPECT_EQ(ret, HCCL_SUCCESS);
EXPECT_TRUE(findTag);
EXPECT_EQ(communicator_.resMap_.count(tag), 0);
EXPECT_EQ(communicator_.linkResMap_.size(), 2);
communicator_.linkResMap_.clear();
}
* @brief TC-COMM-09: ClearAclgraphHostLinks 简化版
* 验证:tag 在 tagsRequiringHostCleanup_ 中时,清理后 tag 被移除
*/
TEST_F(AclgraphCommunicatorTest, ClearAclgraphHostLinks_Simplified)
{
std::string tag = "test_tag";
communicator_.tagsRequiringHostCleanup_.insert(tag);
HcclResult ret = communicator_.ClearAclgraphHostLinks({tag});
EXPECT_EQ(ret, HCCL_SUCCESS);
EXPECT_EQ(communicator_.tagsRequiringHostCleanup_.count(tag), 0);
}
* @brief TC-COMM-10: ClearAclgraphHostLinks tag 不在集合中
* 验证:tag 不在 tagsRequiringHostCleanup_ 中时,无操作,返回 SUCCESS
*/
TEST_F(AclgraphCommunicatorTest, ClearAclgraphHostLinks_TagNotTracked)
{
HcclResult ret = communicator_.ClearAclgraphHostLinks({"untracked_tag"});
EXPECT_EQ(ret, HCCL_SUCCESS);
}
* @brief TC-COMM-10b: ClearAclgraphHostLinks 完整路径
* 验证:rankTagRemoteRes_ 有匹配 tag、hostPtr 非空、hostMemVec_ 含匹配指针时,
* ListCommonRemove 被调用,三容器配对 erase
*/
TEST_F(AclgraphCommunicatorTest, ClearAclgraphHostLinks_FullPath)
{
const std::string tag = "test_tag";
const u32 rankId = 0;
HccltagRemoteResV2 remoteResV2;
ListCommonInit(&remoteResV2.nextTagRes, &remoteResV2.nextTagRes);
HccltagRemoteResV2 *hostPtr = &remoteResV2;
auto hostMem = std::make_shared<HostMem>(HostMem::create(hostPtr, sizeof(remoteResV2)));
communicator_.hostMemVec_.push_back(hostMem);
communicator_.deviceMemVec_.push_back(nullptr);
communicator_.tagsRequiringHostCleanup_.insert(tag);
HccltagRemoteResV3 remoteResV3;
remoteResV3.tagRemoteResPtr = hostPtr;
communicator_.rankTagRemoteRes_[rankId][tag] = remoteResV3;
HcclResult ret = communicator_.ClearAclgraphHostLinks({tag});
EXPECT_EQ(ret, HCCL_SUCCESS);
EXPECT_EQ(communicator_.tagsRequiringHostCleanup_.count(tag), 0);
EXPECT_EQ(communicator_.rankTagRemoteRes_[rankId].count(tag), 0);
bool foundInHostMemVec = false;
for (auto &hm : communicator_.hostMemVec_) {
if (hm && hm->ptr() == hostPtr) {
foundInHostMemVec = true;
break;
}
}
EXPECT_FALSE(foundInHostMemVec);
}
* @brief TC-COMM-11: AicpuKfcClearOpResLaunch 空 tags
* 验证:传入空集合时直接返回 SUCCESS
*/
TEST_F(AclgraphCommunicatorTest, KfcClearOpResLaunch_EmptyTags)
{
HcclResult ret = communicator_.AicpuKfcClearOpResLaunch({});
EXPECT_EQ(ret, HCCL_SUCCESS);
}
* @brief TC-COMM-12: AicpuKfcClearOpResLaunch binHandle_ 为空
* 验证:binHandle_=nullptr 时跳过 aicpu 清理,返回 SUCCESS
*/
TEST_F(AclgraphCommunicatorTest, KfcClearOpResLaunch_NullBinHandle)
{
communicator_.binHandle_ = nullptr;
HcclResult ret = communicator_.AicpuKfcClearOpResLaunch({"tag1"});
EXPECT_EQ(ret, HCCL_SUCCESS);
}
* @brief TC-COMM-13: AicpuKfcClearOpResLaunch opStream_ 为空
* 验证:opStream_.ptr()=nullptr 时跳过 aicpu 清理,返回 SUCCESS
*/
TEST_F(AclgraphCommunicatorTest, KfcClearOpResLaunch_NullOpStream)
{
communicator_.binHandle_ = reinterpret_cast<aclrtBinHandle>(0x1);
HcclResult ret = communicator_.AicpuKfcClearOpResLaunch({"tag1"});
EXPECT_EQ(ret, HCCL_SUCCESS);
}
* @brief TC-COMM-14: AicpuKfcClearOpResLaunch launch 路径(单批)
* 验证:binHandle_ 和 opStream_ 均有效时,进入 launch 路径,
* mock hrtMemSyncCopy/AicpuAclKernelLaunchV2/hcclStreamSynchronize 返回成功
*/
TEST_F(AclgraphCommunicatorTest, KfcClearOpResLaunch_LaunchPath)
{
communicator_.binHandle_ = reinterpret_cast<aclrtBinHandle>(0x1);
communicator_.opStream_.stream_ = reinterpret_cast<void *>(0x1234);
communicator_.identifier_ = "test_group";
communicator_.aicpuCleanupBuf_ = DeviceMem(reinterpret_cast<void *>(0x5678), 4096, false);
communicator_.aicpuCleanupHostBuf_.reset(new (std::nothrow) HcclKfcClearOpResTilingData());
ASSERT_NE(communicator_.aicpuCleanupHostBuf_, nullptr);
MOCKER(hrtMemSyncCopy).stubs().will(returnValue(HCCL_SUCCESS));
MOCKER(AicpuAclKernelLaunchV2).stubs().will(returnValue(HCCL_SUCCESS));
MOCKER(hcclStreamSynchronize).stubs().will(returnValue(HCCL_SUCCESS));
HcclResult ret = communicator_.AicpuKfcClearOpResLaunch({"tag1", "tag2"});
EXPECT_EQ(ret, HCCL_SUCCESS);
}
* @brief TC-COMM-15: AicpuKfcClearOpResLaunch launch 路径(分批)
* 验证:tags 数量超过 MAX_BATCH 时自动分批,验证 launch 调用次数
*/
TEST_F(AclgraphCommunicatorTest, KfcClearOpResLaunch_MultiBatch)
{
communicator_.binHandle_ = reinterpret_cast<aclrtBinHandle>(0x1);
communicator_.opStream_.stream_ = reinterpret_cast<void *>(0x1234);
communicator_.identifier_ = "test_group";
communicator_.aicpuCleanupBuf_ = DeviceMem(reinterpret_cast<void *>(0x5678), 4096, false);
communicator_.aicpuCleanupHostBuf_.reset(new (std::nothrow) HcclKfcClearOpResTilingData());
ASSERT_NE(communicator_.aicpuCleanupHostBuf_, nullptr);
std::unordered_set<std::string> manyTags;
for (u32 i = 0; i < HCCL_KFC_CLEAR_OP_RES_MAX_BATCH + 10; i++) {
manyTags.insert("tag_" + std::to_string(i));
}
g_launchCallCount = 0;
MOCKER(hrtMemSyncCopy).stubs().will(returnValue(HCCL_SUCCESS));
MOCKER(AicpuAclKernelLaunchV2).stubs().will(invoke(LaunchCountStub));
MOCKER(hcclStreamSynchronize).stubs().will(returnValue(HCCL_SUCCESS));
HcclResult ret = communicator_.AicpuKfcClearOpResLaunch(manyTags);
EXPECT_EQ(ret, HCCL_SUCCESS);
EXPECT_EQ(g_launchCallCount, 2);
}
* @brief AIT-01: AicpuInitOpTilingDataAicpuCache enable=0
* 验证:aicpuCacheEnable=0 时,不修改 tilingData 值
*/
TEST_F(AclgraphCommunicatorTest, AicpuInitTilingCache_Disabled)
{
OpParam opParam;
opParam.aicpuCacheEnable = 0;
OpTilingData tilingData;
tilingData.aicpuCacheEnable = 0;
HcclResult ret = communicator_.AicpuInitOpTilingDataAicpuCache(opParam, HCCL_CMD_ALLREDUCE, &tilingData);
EXPECT_EQ(ret, HCCL_SUCCESS);
EXPECT_EQ(tilingData.aicpuCacheEnable, 0);
}
* @brief AIT-02: AicpuInitOpTilingDataAicpuCache enable=1 + isCapture
* 验证:算子模式 isCapture=true 情况下能正常返回
*/
TEST_F(AclgraphCommunicatorTest, AicpuInitTilingCache_ForceOpbaseWithCapture)
{
OpParam opParam;
opParam.aicpuCacheEnable = 1;
opParam.isCapture = true;
opParam.isZeroCopy = true;
OpTilingData tilingData;
tilingData.aicpuCacheEnable = 0;
HcclResult ret = communicator_.AicpuInitOpTilingDataAicpuCache(opParam, HCCL_CMD_ALLREDUCE, &tilingData);
EXPECT_EQ(ret, HCCL_SUCCESS);
}
* @brief AIT-03: AicpuInitOpTilingDataAicpuCache enable >= 10
* 验证:aicpuCacheEnable >= 10 时触发内部错误检查
*/
TEST_F(AclgraphCommunicatorTest, AicpuInitTilingCache_EnableTooHigh)
{
OpParam opParam;
opParam.aicpuCacheEnable = 10;
OpTilingData tilingData;
HcclResult ret = communicator_.AicpuInitOpTilingDataAicpuCache(opParam, HCCL_CMD_ALLREDUCE, &tilingData);
EXPECT_EQ(ret, HCCL_SUCCESS);
EXPECT_EQ(tilingData.aicpuCacheEnable, 10U);
}
* @brief AIT-04: AicpuInitOpTilingDataAicpuCache 单算子模式
* 验证:非图模式下,直接赋值 aicpuCacheEnable 不变
*/
TEST_F(AclgraphCommunicatorTest, AicpuInitTilingCache_OpsMode)
{
OpParam opParam;
opParam.aicpuCacheEnable = 1;
OpTilingData tilingData;
tilingData.aicpuCacheEnable = 0;
HcclResult ret = communicator_.AicpuInitOpTilingDataAicpuCache(opParam, HCCL_CMD_ALLREDUCE, &tilingData);
EXPECT_EQ(ret, HCCL_SUCCESS);
EXPECT_EQ(tilingData.aicpuCacheEnable, 1);
}