* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
* MindIE is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* 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 FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
#include <gtest/gtest.h>
#include <mockcpp/mockcpp.hpp>
#include <thread>
#include <chrono>
#include <vector>
#define private public
#include "health_checker.h"
#include "health_checker.cpp"
#include "../utils/mock_util.h"
#include "config_manager_impl.h"
using namespace mindie_llm;
#define MOCKER_CPP(api, TT) (MOCKCPP_NS::mockAPI((#api), (reinterpret_cast<TT>(api))))
MOCKER_CPP_OVERLOAD_EQ(BackendConfig)
MOCKER_CPP_OVERLOAD_EQ(ServerConfig)
MOCKER_CPP_OVERLOAD_EQ(Error)
class HealthCheckerTest : public ::testing::Test {
protected:
void SetUp() override
{
GlobalMockObject::verify();
MOCKER_CPP(&CanonicalPath, bool (*)(std::string &)).stubs().will(returnValue(true));
MOCKER_CPP(&GetConfigPath, Error (*)(std::string &)).stubs().will(returnValue(Error(Error::Code::OK)));
MOCKER_CPP(&ServerConfigManager::InitFromJson, bool (*)()).stubs().will(returnValue(true));
MOCKER_CPP(&BackendConfigManager::InitFromJson, bool (*)()).stubs().will(returnValue(true));
MOCKER_CPP(&ScheduleConfigManager::InitFromJson, bool (*)()).stubs().will(returnValue(true));
MOCKER_CPP(&ModelDeployConfigManager::InitFromJson, bool (*)()).stubs().will(returnValue(true));
MOCKER_CPP(&LogConfigManager::InitFromJson, bool (*)()).stubs().will(returnValue(true));
MOCKER_CPP(&ConfigManager::CheckAllParam, bool (*)()).stubs().will(returnValue(true));
BackendConfig backendConfig;
backendConfig.backendName = "test_backend";
backendConfig.npuDeviceIds = {{0}};
MOCKER_CPP(GetBackendConfig, const BackendConfig& (*)())
.stubs()
.will(returnValue(backendConfig));
ServerConfig serverConfig;
serverConfig.inferMode = "standard";
MOCKER_CPP(GetServerConfig, const ServerConfig& (*)())
.stubs()
.will(returnValue(serverConfig));
auto mockInferInstance = std::make_shared<InferInstance>();
MOCKER_CPP(GetInferInstance, InferInstance* (*)())
.stubs()
.will(returnValue(mockInferInstance.get()));
MOCKER_CPP(&InferInstance::IsLlmEngineReady, bool (*)())
.stubs()
.will(returnValue(true));
ConfigManager::CreateInstance("mockPath");
HealthChecker &checker = HealthChecker::GetInstance();
(void)checker;
}
void TearDown() override
{
HealthChecker &checker = HealthChecker::GetInstance();
if (checker.mRunning.load()) {
checker.mRunning.store(false);
if (checker.mCheckerThread.joinable()) {
checker.mCheckerThread.join();
}
}
{
if (checker.mSimulateRunner != nullptr && checker.mSimulateTaskStarted.load()) {
checker.mSimulateRunner->Stop();
checker.mSimulateTaskStarted.store(false);
}
checker.mSimulateRunner.reset();
checker.mSimulateExecutor.reset();
}
GlobalMockObject::verify();
}
};
TEST_F(HealthCheckerTest, GetInstanceReturnsSingleton)
{
HealthChecker &instance1 = HealthChecker::GetInstance();
HealthChecker &instance2 = HealthChecker::GetInstance();
EXPECT_EQ(&instance1, &instance2);
}
TEST_F(HealthCheckerTest, StatusToString)
{
HealthChecker &checker = HealthChecker::GetInstance();
EXPECT_EQ(checker.StatusToString(SERVICE_READY), "SERVICE_READY");
EXPECT_EQ(checker.StatusToString(SERVICE_NORMAL), "SERVICE_NORMAL");
EXPECT_EQ(checker.StatusToString(SERVICE_ABNORMAL), "SERVICE_ABNORMAL");
EXPECT_EQ(checker.StatusToString(SERVICE_PAUSE), "SERVICE_PAUSE");
EXPECT_EQ(checker.StatusToString(SERVICE_INIT), "SERVICE_INIT");
EXPECT_EQ(checker.StatusToString(SERVICE_BUSY), "SERVICE_BUSY");
EXPECT_EQ(checker.StatusToString(static_cast<ServiceStatus>(999)), "UNKNOWN");
}
TEST_F(HealthCheckerTest, EnqueueErrorMessage)
{
HealthChecker &checker = HealthChecker::GetInstance();
checker.mServiceStatus.store(SERVICE_NORMAL);
checker.EnqueueErrorMessage("TEST_ERROR_001", "TestModule");
EXPECT_EQ(checker.GetServiceStatus(), SERVICE_ABNORMAL);
std::vector<ErrorItem> errorList;
ServiceStatus status;
checker.GetStatusAndErrorList(status, errorList);
EXPECT_EQ(status, SERVICE_ABNORMAL);
EXPECT_FALSE(errorList.empty());
EXPECT_EQ(errorList[0].errCode, "TEST_ERROR_001");
EXPECT_EQ(errorList[0].createdBy, "TestModule");
}
TEST_F(HealthCheckerTest, CheckErrorListEmpty)
{
HealthChecker &checker = HealthChecker::GetInstance();
ServiceStatus status;
std::vector<ErrorItem> errorList;
checker.GetStatusAndErrorList(status, errorList);
EXPECT_TRUE(checker.CheckErrorListEmpty());
checker.EnqueueErrorMessage("ERROR_001", "Module1");
EXPECT_FALSE(checker.CheckErrorListEmpty());
checker.GetStatusAndErrorList(status, errorList);
EXPECT_TRUE(checker.CheckErrorListEmpty());
}
TEST_F(HealthCheckerTest, UpdateNpuDeviceIds)
{
HealthChecker &checker = HealthChecker::GetInstance();
checker.mChipPerCard = 2;
std::set<int> npuDeviceIds = {0, 1, 2, 3};
checker.UpdateNpuDeviceIds(npuDeviceIds);
std::shared_lock<std::shared_mutex> lock(checker.mNpuDevicesMutex);
const std::vector<std::pair<int, int>> expected = {{0, 0}, {0, 1}, {1, 0}, {1, 1}};
EXPECT_EQ(checker.mNpuDeviceCardIds, expected);
}
TEST_F(HealthCheckerTest, UpdateNpuDeviceIdsSingleChip)
{
HealthChecker &checker = HealthChecker::GetInstance();
checker.mChipPerCard = 1;
std::set<int> npuDeviceIds = {0, 1, 2, 3};
checker.UpdateNpuDeviceIds(npuDeviceIds);
std::shared_lock<std::shared_mutex> lock(checker.mNpuDevicesMutex);
const std::vector<std::pair<int, int>> expected = {{0, 0}, {1, 0}, {2, 0}, {3, 0}};
EXPECT_EQ(checker.mNpuDeviceCardIds, expected);
}
TEST_F(HealthCheckerTest, IsValidStatusTransition)
{
HealthChecker &checker = HealthChecker::GetInstance();
EXPECT_TRUE(checker.IsValidStatusTransition(SERVICE_INIT, SERVICE_NORMAL));
EXPECT_TRUE(checker.IsValidStatusTransition(SERVICE_INIT, SERVICE_BUSY));
EXPECT_TRUE(checker.IsValidStatusTransition(SERVICE_NORMAL, SERVICE_PAUSE));
EXPECT_TRUE(checker.IsValidStatusTransition(SERVICE_NORMAL, SERVICE_ABNORMAL));
EXPECT_TRUE(checker.IsValidStatusTransition(SERVICE_NORMAL, SERVICE_BUSY));
EXPECT_TRUE(checker.IsValidStatusTransition(SERVICE_BUSY, SERVICE_PAUSE));
EXPECT_TRUE(checker.IsValidStatusTransition(SERVICE_BUSY, SERVICE_ABNORMAL));
EXPECT_TRUE(checker.IsValidStatusTransition(SERVICE_BUSY, SERVICE_NORMAL));
EXPECT_TRUE(checker.IsValidStatusTransition(SERVICE_ABNORMAL, SERVICE_NORMAL));
EXPECT_TRUE(checker.IsValidStatusTransition(SERVICE_ABNORMAL, SERVICE_BUSY));
EXPECT_TRUE(checker.IsValidStatusTransition(SERVICE_PAUSE, SERVICE_READY));
EXPECT_TRUE(checker.IsValidStatusTransition(SERVICE_PAUSE, SERVICE_NORMAL));
EXPECT_TRUE(checker.IsValidStatusTransition(SERVICE_READY, SERVICE_NORMAL));
EXPECT_TRUE(checker.IsValidStatusTransition(SERVICE_READY, SERVICE_BUSY));
EXPECT_FALSE(checker.IsValidStatusTransition(SERVICE_INIT, SERVICE_PAUSE));
EXPECT_FALSE(checker.IsValidStatusTransition(SERVICE_NORMAL, SERVICE_READY));
EXPECT_FALSE(checker.IsValidStatusTransition(SERVICE_ABNORMAL, SERVICE_PAUSE));
}
TEST_F(HealthCheckerTest, UpdateStatusValidTransition)
{
HealthChecker &checker = HealthChecker::GetInstance();
checker.mServiceStatus.store(SERVICE_INIT);
checker.UpdateStatus(SERVICE_NORMAL);
EXPECT_EQ(checker.GetServiceStatus(), SERVICE_NORMAL);
checker.UpdateStatus(SERVICE_BUSY);
EXPECT_EQ(checker.GetServiceStatus(), SERVICE_BUSY);
}
TEST_F(HealthCheckerTest, UpdateStatusSameStatus)
{
HealthChecker &checker = HealthChecker::GetInstance();
checker.mServiceStatus.store(SERVICE_NORMAL);
checker.UpdateStatus(SERVICE_NORMAL);
EXPECT_EQ(checker.GetServiceStatus(), SERVICE_NORMAL);
}
TEST_F(HealthCheckerTest, StartAndStop)
{
HealthChecker &checker = HealthChecker::GetInstance();
if (checker.mRunning.load()) {
checker.mRunning.store(false);
if (checker.mCheckerThread.joinable()) {
checker.mCheckerThread.join();
}
}
EXPECT_TRUE(checker.Start());
EXPECT_TRUE(checker.mRunning.load());
EXPECT_FALSE(checker.Start());
checker.Stop();
EXPECT_FALSE(checker.mRunning.load());
}
TEST_F(HealthCheckerTest, GetStatusAndErrorList)
{
HealthChecker &checker = HealthChecker::GetInstance();
checker.mServiceStatus.store(SERVICE_NORMAL);
checker.EnqueueErrorMessage("ERR1", "Module1");
checker.EnqueueErrorMessage("ERR2", "Module2");
ServiceStatus status;
std::vector<ErrorItem> errorList;
checker.GetStatusAndErrorList(status, errorList);
EXPECT_EQ(status, SERVICE_ABNORMAL);
EXPECT_EQ(errorList.size(), 2);
EXPECT_TRUE(checker.CheckErrorListEmpty());
}
TEST_F(HealthCheckerTest, PrintNpuDeviceIds)
{
HealthChecker &checker = HealthChecker::GetInstance();
checker.mChipPerCard = 1;
std::set<int> npuDeviceIds = {0, 1, 2};
checker.UpdateNpuDeviceIds(npuDeviceIds);
EXPECT_NO_THROW(checker.PrintNpuDeviceIds());
}
TEST_F(HealthCheckerTest, SimulateExecutorFactoryPattern)
{
auto standardExecutor = SimulateRequestExecutor::Create(InferReqType::REQ_STAND_INFER);
EXPECT_NE(standardExecutor, nullptr);
auto prefillExecutor = SimulateRequestExecutor::Create(InferReqType::REQ_PREFILL);
EXPECT_NE(prefillExecutor, nullptr);
}
TEST_F(HealthCheckerTest, StartSimulateTaskAlreadyStarted)
{
HealthChecker &checker = HealthChecker::GetInstance();
{
checker.mSimulateRunner = std::make_unique<SimulateTaskRunner>();
checker.mSimulateTaskStarted.store(true);
}
bool result = checker.StartSimulateTask();
EXPECT_TRUE(result);
{
checker.mSimulateTaskStarted.store(false);
checker.mSimulateRunner.reset();
}
}
TEST_F(HealthCheckerTest, StartSimulateTaskWhenNotRunning)
{
HealthChecker &checker = HealthChecker::GetInstance();
checker.mRunning.store(false);
{
checker.mSimulateTaskStarted.store(false);
checker.mSimulateRunner.reset();
}
checker.mServiceStatus.store(SERVICE_NORMAL);
EXPECT_NO_THROW({
bool result = checker.StartSimulateTask();
(void)result;
});
}
TEST_F(HealthCheckerTest, StartSimulateTaskInPauseState)
{
HealthChecker &checker = HealthChecker::GetInstance();
checker.mRunning.store(true);
checker.mServiceStatus.store(SERVICE_PAUSE);
{
checker.mSimulateTaskStarted.store(false);
checker.mSimulateRunner.reset();
}
std::thread statusChanger([&checker]() {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
checker.mServiceStatus.store(SERVICE_NORMAL);
});
statusChanger.detach();
auto startTime = std::chrono::steady_clock::now();
bool result = false;
EXPECT_NO_THROW({
result = checker.StartSimulateTask();
});
auto endTime = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime);
EXPECT_LT(duration.count(), 2000);
checker.mRunning.store(false);
}
TEST_F(HealthCheckerTest, InitNpuDeviceCardIdsDmiModeEmpty)
{
HealthChecker &checker = HealthChecker::GetInstance();
{
std::unique_lock<std::shared_mutex> lock(checker.mNpuDevicesMutex);
checker.mNpuDeviceCardIds.clear();
}
bool result = checker.InitNpuDeviceCardIds();
EXPECT_FALSE(result);
}
TEST_F(HealthCheckerTest, CheckSimulateTaskNotStarted)
{
HealthChecker &checker = HealthChecker::GetInstance();
{
checker.mSimulateTaskStarted.store(false);
checker.mSimulateRunner.reset();
}
ServiceStatus result = checker.CheckSimulateTask();
EXPECT_EQ(result, SERVICE_ABNORMAL);
}
TEST_F(HealthCheckerTest, CheckSimulateTaskStartedRunnerNull)
{
HealthChecker &checker = HealthChecker::GetInstance();
{
checker.mSimulateTaskStarted.store(true);
checker.mSimulateRunner.reset();
}
ServiceStatus result = checker.CheckSimulateTask();
EXPECT_EQ(result, SERVICE_ABNORMAL);
}