blob: f77721901361a4a1e933a6bcd7f5887f3e7e26cb [file] [edit]
/*
* SPDX-FileCopyrightText: Copyright OpenBMC Authors
* SPDX-License-Identifier: Apache-2.0
*/
#include "MctpMockTestBase.hpp"
#include "MockMctpRequester.hpp"
#include "NvidiaGpuMctpVdm.hpp"
#include "NvidiaGpuPowerSensor.hpp"
#include "OcpMctpVdm.hpp"
#include "TestUtils.hpp"
#include "Thresholds.hpp"
#include <cmath>
#include <cstdint>
#include <memory>
#include <string>
#include <system_error>
#include <utility>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
namespace
{
std::vector<uint8_t> buildPowerResponse(uint32_t powerMilliwatts)
{
return test_utils::buildPlatformEnvSuccessResponse(
gpu::PlatformEnvironmentalCommands::GET_CURRENT_POWER_DRAW,
powerMilliwatts);
}
std::vector<uint8_t> buildPowerErrorResponse(uint8_t cc, uint16_t reasonCode)
{
return test_utils::buildPlatformEnvErrorResponse(
gpu::PlatformEnvironmentalCommands::GET_CURRENT_POWER_DRAW, cc,
reasonCode);
}
class NvidiaGpuPowerSensorTest : public MctpMockTestBase
{
protected:
static std::shared_ptr<NvidiaGpuPowerSensor> createSensor(
uint8_t sensorId = gpuPowerSensorId,
const std::string& name = "GPU_POWER",
std::vector<thresholds::Threshold> thresholds = {},
gpu::DeviceIdentification deviceType =
gpu::DeviceIdentification::DEVICE_GPU,
uint8_t eid = test_utils::defaultEid)
{
return std::make_shared<NvidiaGpuPowerSensor>(
bus(), requester(), name, "/test/config", eid, sensorId, objects(),
std::move(thresholds), deviceType);
}
static std::string powerPath(const std::string& name)
{
return "/xyz/openbmc_project/sensors/power/" + name;
}
};
// Constructor — D-Bus interface creation
TEST_F(NvidiaGpuPowerSensorTest, ConstructorCreatesDbusInterfaces)
{
const std::string name = "pwr_ctor_iface";
const std::shared_ptr<NvidiaGpuPowerSensor> sensor =
createSensor(gpuPowerSensorId, name);
const std::string path = powerPath(name);
EXPECT_TRUE(std::isnan(getProperty<double>(
path, "xyz.openbmc_project.Sensor.Value", "Value")));
EXPECT_EQ(getProperty<double>(path, "xyz.openbmc_project.Sensor.Value",
"MaxValue"),
5000.0);
EXPECT_EQ(getProperty<double>(path, "xyz.openbmc_project.Sensor.Value",
"MinValue"),
0.0);
EXPECT_EQ(getProperty<std::string>(path, "xyz.openbmc_project.Sensor.Value",
"Unit"),
"xyz.openbmc_project.Sensor.Value.Unit.Watts");
}
// Constructor — PhysicalContext
TEST_F(NvidiaGpuPowerSensorTest, ConstructorWithGpuPhysicalContext)
{
const std::string name = "pwr_gpu_ctx";
const std::shared_ptr<NvidiaGpuPowerSensor> sensor = createSensor(
gpuPowerSensorId, name, {}, gpu::DeviceIdentification::DEVICE_GPU);
const std::string path = powerPath(name);
EXPECT_EQ(
getProperty<std::string>(
path, "xyz.openbmc_project.Common.PhysicalContext", "Type"),
"xyz.openbmc_project.Common.PhysicalContext.PhysicalContextType.Accelerator");
}
// Constructor — threshold interfaces
TEST_F(NvidiaGpuPowerSensorTest, ConstructorWithWarningThreshold)
{
const std::string name = "pwr_warn_thr";
const std::shared_ptr<NvidiaGpuPowerSensor> sensor = createSensor(
gpuPowerSensorId, name,
{thresholds::Threshold{thresholds::Level::WARNING,
thresholds::Direction::HIGH, 85.0}});
const std::string path = powerPath(name);
EXPECT_EQ(getProperty<double>(
path, "xyz.openbmc_project.Sensor.Threshold.Warning",
"WarningHigh"),
85.0);
}
TEST_F(NvidiaGpuPowerSensorTest, ConstructorWithCriticalThreshold)
{
const std::string name = "pwr_crit_thr";
const std::shared_ptr<NvidiaGpuPowerSensor> sensor = createSensor(
gpuPowerSensorId, name,
{thresholds::Threshold{thresholds::Level::CRITICAL,
thresholds::Direction::HIGH, 100.0}});
const std::string path = powerPath(name);
EXPECT_EQ(getProperty<double>(
path, "xyz.openbmc_project.Sensor.Threshold.Critical",
"CriticalHigh"),
100.0);
}
// Update — successful power readings
TEST_F(NvidiaGpuPowerSensorTest, UpdateSuccessValidPower)
{
EXPECT_CALL(mctpMock, sendRecvMsg)
.WillOnce(mock_mctp::respondWith({}, buildPowerResponse(150000)));
const std::shared_ptr<NvidiaGpuPowerSensor> sensor =
createSensor(gpuPowerSensorId, "pwr_valid");
sensor->update();
EXPECT_DOUBLE_EQ(sensor->value, 150.0);
}
TEST_F(NvidiaGpuPowerSensorTest, UpdateSuccessZeroPower)
{
EXPECT_CALL(mctpMock, sendRecvMsg)
.WillOnce(mock_mctp::respondWith({}, buildPowerResponse(0)));
const std::shared_ptr<NvidiaGpuPowerSensor> sensor =
createSensor(gpuPowerSensorId, "pwr_zero");
sensor->update();
EXPECT_DOUBLE_EQ(sensor->value, 0.0);
}
TEST_F(NvidiaGpuPowerSensorTest, UpdateSuccessFractionalPower)
{
EXPECT_CALL(mctpMock, sendRecvMsg)
.WillOnce(mock_mctp::respondWith({}, buildPowerResponse(1500)));
const std::shared_ptr<NvidiaGpuPowerSensor> sensor =
createSensor(gpuPowerSensorId, "pwr_frac");
sensor->update();
EXPECT_DOUBLE_EQ(sensor->value, 1.5);
}
TEST_F(NvidiaGpuPowerSensorTest, UpdateSuccessTwiceOverwritesValue)
{
EXPECT_CALL(mctpMock, sendRecvMsg)
.WillOnce(mock_mctp::respondWith({}, buildPowerResponse(150000)))
.WillOnce(mock_mctp::respondWith({}, buildPowerResponse(200000)));
const std::shared_ptr<NvidiaGpuPowerSensor> sensor =
createSensor(gpuPowerSensorId, "pwr_twice");
sensor->update();
EXPECT_DOUBLE_EQ(sensor->value, 150.0);
sensor->update();
EXPECT_DOUBLE_EQ(sensor->value, 200.0);
}
// Update — error handling
TEST_F(NvidiaGpuPowerSensorTest, UpdateMctpTransportError)
{
EXPECT_CALL(mctpMock, sendRecvMsg)
.WillOnce(mock_mctp::respondWith(
std::make_error_code(std::errc::timed_out), {}));
const std::shared_ptr<NvidiaGpuPowerSensor> sensor =
createSensor(gpuPowerSensorId, "pwr_mctp_err");
sensor->update();
EXPECT_TRUE(std::isnan(sensor->value));
}
TEST_F(NvidiaGpuPowerSensorTest, UpdateBadCompletionCodeError)
{
EXPECT_CALL(mctpMock, sendRecvMsg)
.WillOnce(mock_mctp::respondWith(
{}, buildPowerErrorResponse(
static_cast<uint8_t>(
ocp::accelerator_management::CompletionCode::ERROR),
0)));
const std::shared_ptr<NvidiaGpuPowerSensor> sensor =
createSensor(gpuPowerSensorId, "pwr_bad_cc");
sensor->update();
EXPECT_TRUE(std::isnan(sensor->value));
}
TEST_F(NvidiaGpuPowerSensorTest, UpdateDecodeFailTruncatedBuffer)
{
EXPECT_CALL(mctpMock, sendRecvMsg)
.WillOnce(mock_mctp::respondWith({}, std::vector<uint8_t>(5, 0)));
const std::shared_ptr<NvidiaGpuPowerSensor> sensor =
createSensor(gpuPowerSensorId, "pwr_trunc");
EXPECT_NO_THROW(sensor->update());
EXPECT_TRUE(std::isnan(sensor->value));
}
TEST_F(NvidiaGpuPowerSensorTest, UpdateDecodeFailEmptyBuffer)
{
EXPECT_CALL(mctpMock, sendRecvMsg).WillOnce(mock_mctp::respondWith({}, {}));
const std::shared_ptr<NvidiaGpuPowerSensor> sensor =
createSensor(gpuPowerSensorId, "pwr_empty");
EXPECT_NO_THROW(sensor->update());
EXPECT_TRUE(std::isnan(sensor->value));
}
// Update — value retention after errors
TEST_F(NvidiaGpuPowerSensorTest, UpdateSuccessThenErrorKeepsPreviousValue)
{
EXPECT_CALL(mctpMock, sendRecvMsg)
.WillOnce(mock_mctp::respondWith({}, buildPowerResponse(150000)))
.WillOnce(mock_mctp::respondWith(
{}, buildPowerErrorResponse(
static_cast<uint8_t>(
ocp::accelerator_management::CompletionCode::ERROR),
0)));
const std::shared_ptr<NvidiaGpuPowerSensor> sensor =
createSensor(gpuPowerSensorId, "pwr_retain");
sensor->update();
EXPECT_DOUBLE_EQ(sensor->value, 150.0);
sensor->update();
EXPECT_DOUBLE_EQ(sensor->value, 150.0);
}
// Update — request encoding verification
TEST_F(NvidiaGpuPowerSensorTest, UpdateRequestContainsCorrectEid)
{
constexpr uint8_t testEid = 42;
EXPECT_CALL(mctpMock, sendRecvMsg(testEid, testing::_, testing::_))
.WillOnce(mock_mctp::respondWith({}, buildPowerResponse(150000)));
const std::shared_ptr<NvidiaGpuPowerSensor> sensor =
createSensor(gpuPowerSensorId, "pwr_eid_test", {},
gpu::DeviceIdentification::DEVICE_GPU, testEid);
sensor->update();
}
// Destructor
TEST_F(NvidiaGpuPowerSensorTest, DestructorDoesNotCrash)
{
std::shared_ptr<NvidiaGpuPowerSensor> sensor =
createSensor(gpuPowerSensorId, "pwr_dtor");
ASSERT_NE(sensor, nullptr);
ASSERT_NE(sensor->sensorInterface, nullptr);
sensor.reset();
ASSERT_EQ(sensor, nullptr);
}
} // namespace