| /* |
| * SPDX-FileCopyrightText: Copyright OpenBMC Authors |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include "MctpMockTestBase.hpp" |
| #include "MockMctpRequester.hpp" |
| #include "NvidiaGpuMctpVdm.hpp" |
| #include "NvidiaSmaLeakSensor.hpp" |
| #include "OcpMctpVdm.hpp" |
| #include "TestUtils.hpp" |
| #include "Thresholds.hpp" |
| |
| #include <MessagePackUnpackUtils.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> buildLeakDetectionInfoResponse(uint16_t adcReadingMv) |
| { |
| // dataSize = numSensors(1) + numThresholdLevels(1) + |
| // numSensors * (sensorId(1) + leakState(1) + |
| // thresholds(2*numThresholdLevels) + adcReadingMv(2)) |
| // We mock 1 sensor with 0 thresholds |
| uint16_t dataSize = 1 + 1 + 1 * (1 + 1 + 0 + 2); |
| |
| std::vector<uint8_t> buf( |
| ocp::accelerator_management::commonResponseSize + dataSize); |
| PackBuffer pack(buf); |
| ocp::accelerator_management::packHeader( |
| pack, gpu::nvidiaPciVendorId, |
| ocp::accelerator_management::MessageType::RESPONSE, 0, |
| static_cast<uint8_t>(gpu::MessageType::PLATFORM_ENVIRONMENTAL)); |
| pack.pack(static_cast<uint8_t>( |
| gpu::PlatformEnvironmentalCommands::GET_LEAK_DETECTION_INFO)); |
| pack.pack(static_cast<uint8_t>( |
| ocp::accelerator_management::CompletionCode::SUCCESS)); |
| pack.pack(static_cast<uint16_t>(0)); // reserved |
| pack.pack(dataSize); |
| |
| // payload |
| pack.pack(static_cast<uint8_t>(1)); // numSensors |
| pack.pack(static_cast<uint8_t>(0)); // numThresholdLevels |
| |
| // sensor 1 |
| pack.pack(static_cast<uint8_t>(0)); // sensorId |
| pack.pack(static_cast<uint8_t>(0)); // leakState |
| pack.pack(adcReadingMv); // adcReadingMv |
| |
| return buf; |
| } |
| |
| std::vector<uint8_t> buildLeakDetectionInfoEmptyResponse() |
| { |
| // dataSize = numSensors(1) + numThresholdLevels(1) |
| // We mock 0 sensors |
| uint16_t dataSize = 1 + 1; |
| |
| std::vector<uint8_t> buf( |
| ocp::accelerator_management::commonResponseSize + dataSize); |
| PackBuffer pack(buf); |
| ocp::accelerator_management::packHeader( |
| pack, gpu::nvidiaPciVendorId, |
| ocp::accelerator_management::MessageType::RESPONSE, 0, |
| static_cast<uint8_t>(gpu::MessageType::PLATFORM_ENVIRONMENTAL)); |
| pack.pack(static_cast<uint8_t>( |
| gpu::PlatformEnvironmentalCommands::GET_LEAK_DETECTION_INFO)); |
| pack.pack(static_cast<uint8_t>( |
| ocp::accelerator_management::CompletionCode::SUCCESS)); |
| pack.pack(static_cast<uint16_t>(0)); // reserved |
| pack.pack(dataSize); |
| |
| // payload |
| pack.pack(static_cast<uint8_t>(0)); // numSensors |
| pack.pack(static_cast<uint8_t>(0)); // numThresholdLevels |
| |
| return buf; |
| } |
| |
| std::vector<uint8_t> buildLeakErrorResponse(uint8_t cc, uint16_t reasonCode) |
| { |
| return test_utils::buildPlatformEnvErrorResponse( |
| gpu::PlatformEnvironmentalCommands::GET_LEAK_DETECTION_INFO, cc, |
| reasonCode); |
| } |
| |
| class NvidiaSmaLeakSensorTest : public MctpMockTestBase |
| { |
| protected: |
| static std::shared_ptr<NvidiaSmaLeakSensor> createSensor( |
| const std::string& name = "GPU_LEAK_0", |
| std::vector<thresholds::Threshold> thresholds = {}, |
| gpu::DeviceIdentification deviceType = |
| gpu::DeviceIdentification::DEVICE_GPU, |
| uint8_t eid = test_utils::defaultEid, uint8_t sensorId = 0) |
| { |
| return std::make_shared<NvidiaSmaLeakSensor>( |
| bus(), requester(), name, "/test/config", eid, sensorId, objects(), |
| std::move(thresholds), deviceType); |
| } |
| |
| static std::string leakPath(const std::string& name) |
| { |
| // sensor path prefix in NvidiaSmaLeakSensor.cpp is |
| // "/xyz/openbmc_project/sensors/" and it appends "voltage/" + |
| // escapeName(name) |
| return "/xyz/openbmc_project/sensors/voltage/" + name; |
| } |
| }; |
| |
| // Constructor — D-Bus interface creation |
| |
| TEST_F(NvidiaSmaLeakSensorTest, ConstructorCreatesDbusInterfaces) |
| { |
| const std::string name = "leak_ctor_iface"; |
| const std::shared_ptr<NvidiaSmaLeakSensor> sensor = createSensor(name); |
| const std::string path = leakPath(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"), |
| 5.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.Volts"); |
| } |
| |
| // Constructor — threshold interfaces |
| |
| TEST_F(NvidiaSmaLeakSensorTest, ConstructorWithWarningThreshold) |
| { |
| const std::string name = "warn_thr"; |
| const std::shared_ptr<NvidiaSmaLeakSensor> sensor = createSensor( |
| name, {thresholds::Threshold{thresholds::Level::WARNING, |
| thresholds::Direction::HIGH, 1.7}}); |
| const std::string path = leakPath(name); |
| |
| EXPECT_EQ(getProperty<double>( |
| path, "xyz.openbmc_project.Sensor.Threshold.Warning", |
| "WarningHigh"), |
| 1.7); |
| } |
| |
| TEST_F(NvidiaSmaLeakSensorTest, ConstructorWithCriticalThreshold) |
| { |
| const std::string name = "crit_thr"; |
| const std::shared_ptr<NvidiaSmaLeakSensor> sensor = createSensor( |
| name, {thresholds::Threshold{thresholds::Level::CRITICAL, |
| thresholds::Direction::HIGH, 1.8}}); |
| const std::string path = leakPath(name); |
| |
| EXPECT_EQ(getProperty<double>( |
| path, "xyz.openbmc_project.Sensor.Threshold.Critical", |
| "CriticalHigh"), |
| 1.8); |
| } |
| |
| TEST_F(NvidiaSmaLeakSensorTest, ConstructorWithMultipleThresholds) |
| { |
| const std::string name = "multi_thr"; |
| const std::shared_ptr<NvidiaSmaLeakSensor> sensor = createSensor( |
| name, {thresholds::Threshold{thresholds::Level::WARNING, |
| thresholds::Direction::HIGH, 1.7}, |
| thresholds::Threshold{thresholds::Level::CRITICAL, |
| thresholds::Direction::HIGH, 1.8}}); |
| const std::string path = leakPath(name); |
| |
| EXPECT_EQ(getProperty<double>( |
| path, "xyz.openbmc_project.Sensor.Threshold.Warning", |
| "WarningHigh"), |
| 1.7); |
| EXPECT_EQ(getProperty<double>( |
| path, "xyz.openbmc_project.Sensor.Threshold.Critical", |
| "CriticalHigh"), |
| 1.8); |
| } |
| |
| // Sensor Update — successful readings |
| |
| TEST_F(NvidiaSmaLeakSensorTest, UpdateSuccessValidReading) |
| { |
| EXPECT_CALL(mctpMock, sendRecvMsg) |
| .WillOnce( |
| mock_mctp::respondWith({}, buildLeakDetectionInfoResponse(1500))); |
| |
| const std::shared_ptr<NvidiaSmaLeakSensor> sensor = |
| createSensor("leak_valid_0"); |
| sensor->update(); |
| |
| EXPECT_DOUBLE_EQ( |
| getProperty<double>(leakPath("leak_valid_0"), |
| "xyz.openbmc_project.Sensor.Value", "Value"), |
| 1.5); |
| } |
| |
| TEST_F(NvidiaSmaLeakSensorTest, UpdateSuccessZeroReading) |
| { |
| EXPECT_CALL(mctpMock, sendRecvMsg) |
| .WillOnce( |
| mock_mctp::respondWith({}, buildLeakDetectionInfoResponse(0))); |
| |
| const std::shared_ptr<NvidiaSmaLeakSensor> sensor = |
| createSensor("leak_zero_0"); |
| sensor->update(); |
| |
| EXPECT_DOUBLE_EQ( |
| getProperty<double>(leakPath("leak_zero_0"), |
| "xyz.openbmc_project.Sensor.Value", "Value"), |
| 0.0); |
| } |
| |
| TEST_F(NvidiaSmaLeakSensorTest, UpdateSuccessEmptySensors) |
| { |
| EXPECT_CALL(mctpMock, sendRecvMsg) |
| .WillOnce( |
| mock_mctp::respondWith({}, buildLeakDetectionInfoEmptyResponse())); |
| |
| const std::shared_ptr<NvidiaSmaLeakSensor> sensor = |
| createSensor("leak_empty_0"); |
| sensor->update(); |
| |
| // With empty response, the sensor retains its initial NaN value |
| EXPECT_TRUE(std::isnan( |
| getProperty<double>(leakPath("leak_empty_0"), |
| "xyz.openbmc_project.Sensor.Value", "Value"))); |
| } |
| |
| // Sensor Update — threshold crossed reading |
| |
| TEST_F(NvidiaSmaLeakSensorTest, UpdateWarningThresholdCrossed) |
| { |
| EXPECT_CALL(mctpMock, sendRecvMsg) |
| .WillOnce( |
| mock_mctp::respondWith({}, buildLeakDetectionInfoResponse(1000))); |
| |
| const std::string name = "warn_thr_cros_0"; |
| const std::shared_ptr<NvidiaSmaLeakSensor> sensor = createSensor( |
| name, {thresholds::Threshold{thresholds::Level::WARNING, |
| thresholds::Direction::LOW, 1.5}}); |
| sensor->update(); |
| |
| EXPECT_DOUBLE_EQ( |
| getProperty<double>(leakPath(name), "xyz.openbmc_project.Sensor.Value", |
| "Value"), |
| 1.0); |
| |
| // Verify the dbus alarm property was asserted |
| EXPECT_TRUE(getProperty<bool>( |
| leakPath(name), "xyz.openbmc_project.Sensor.Threshold.Warning", |
| "WarningAlarmLow")); |
| } |
| |
| TEST_F(NvidiaSmaLeakSensorTest, UpdateCriticalThresholdCrossed) |
| { |
| EXPECT_CALL(mctpMock, sendRecvMsg) |
| .WillOnce( |
| mock_mctp::respondWith({}, buildLeakDetectionInfoResponse(1000))); |
| |
| const std::string name = "crit_thr_cros_0"; |
| const std::shared_ptr<NvidiaSmaLeakSensor> sensor = createSensor( |
| name, {thresholds::Threshold{thresholds::Level::CRITICAL, |
| thresholds::Direction::LOW, 1.2}}); |
| sensor->update(); |
| |
| EXPECT_DOUBLE_EQ( |
| getProperty<double>(leakPath(name), "xyz.openbmc_project.Sensor.Value", |
| "Value"), |
| 1.0); |
| |
| // Verify the dbus alarm property was asserted |
| EXPECT_TRUE(getProperty<bool>( |
| leakPath(name), "xyz.openbmc_project.Sensor.Threshold.Critical", |
| "CriticalAlarmLow")); |
| } |
| |
| TEST_F(NvidiaSmaLeakSensorTest, UpdateMultipleThresholdsCrossed) |
| { |
| EXPECT_CALL(mctpMock, sendRecvMsg) |
| .WillOnce( |
| mock_mctp::respondWith({}, buildLeakDetectionInfoResponse(1000))); |
| |
| const std::string name = "mult_thr_cros_0"; |
| const std::shared_ptr<NvidiaSmaLeakSensor> sensor = createSensor( |
| name, {thresholds::Threshold{thresholds::Level::WARNING, |
| thresholds::Direction::LOW, 1.5}, |
| thresholds::Threshold{thresholds::Level::CRITICAL, |
| thresholds::Direction::LOW, 1.2}}); |
| sensor->update(); |
| |
| EXPECT_DOUBLE_EQ( |
| getProperty<double>(leakPath(name), "xyz.openbmc_project.Sensor.Value", |
| "Value"), |
| 1.0); |
| |
| // Verify the dbus alarm property was asserted |
| EXPECT_TRUE(getProperty<bool>( |
| leakPath(name), "xyz.openbmc_project.Sensor.Threshold.Critical", |
| "CriticalAlarmLow")); |
| EXPECT_TRUE(getProperty<bool>( |
| leakPath(name), "xyz.openbmc_project.Sensor.Threshold.Warning", |
| "WarningAlarmLow")); |
| } |
| |
| // Sensor Update — error handling |
| |
| TEST_F(NvidiaSmaLeakSensorTest, UpdateMctpTransportError) |
| { |
| EXPECT_CALL(mctpMock, sendRecvMsg) |
| .WillOnce(mock_mctp::respondWith( |
| std::make_error_code(std::errc::timed_out), {})); |
| |
| const std::shared_ptr<NvidiaSmaLeakSensor> sensor = |
| createSensor("leak_mctp_err_0"); |
| sensor->update(); |
| |
| EXPECT_TRUE(std::isnan( |
| getProperty<double>(leakPath("leak_mctp_err_0"), |
| "xyz.openbmc_project.Sensor.Value", "Value"))); |
| } |
| |
| TEST_F(NvidiaSmaLeakSensorTest, UpdateBadCompletionCodeError) |
| { |
| EXPECT_CALL(mctpMock, sendRecvMsg) |
| .WillOnce(mock_mctp::respondWith( |
| {}, buildLeakErrorResponse( |
| static_cast<uint8_t>( |
| ocp::accelerator_management::CompletionCode::ERROR), |
| 0))); |
| |
| const std::shared_ptr<NvidiaSmaLeakSensor> sensor = |
| createSensor("leak_bad_cc_0"); |
| sensor->update(); |
| |
| EXPECT_TRUE(std::isnan( |
| getProperty<double>(leakPath("leak_bad_cc_0"), |
| "xyz.openbmc_project.Sensor.Value", "Value"))); |
| } |
| |
| // Destructor |
| |
| TEST_F(NvidiaSmaLeakSensorTest, DestructorDoesNotCrash) |
| { |
| std::shared_ptr<NvidiaSmaLeakSensor> sensor = createSensor("leak_dtor"); |
| ASSERT_NE(sensor, nullptr); |
| ASSERT_NE(sensor->sensorInterface, nullptr); |
| sensor.reset(); |
| } |
| |
| } // namespace |