blob: fcb0bf90b3d0d593ec265f204e5a76c11da80c29 [file] [edit]
/*
* SPDX-FileCopyrightText: Copyright OpenBMC Authors
* SPDX-License-Identifier: Apache-2.0
*/
#include "Inventory.hpp"
#include "MctpMockTestBase.hpp"
#include "MessagePackUnpackUtils.hpp"
#include "MockMctpRequester.hpp"
#include "NvidiaGpuMctpVdm.hpp"
#include "OcpMctpVdm.hpp"
#include "TestUtils.hpp"
#include <cstdint>
#include <memory>
#include <string>
#include <system_error>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
namespace
{
// Build a SUCCESS GET_INVENTORY_INFORMATION response carrying an ASCII string
// payload. A payload of at least 16 bytes decodes cleanly for every queried
// property type: a string for the Asset fields, a valid GUID for the UUID
// interface, and a uint32 for the clock properties.
std::vector<uint8_t> buildInventoryStringResponse(const std::string& value)
{
std::vector<uint8_t> buf(
ocp::accelerator_management::commonResponseSize + value.size());
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_INVENTORY_INFORMATION));
pack.pack(static_cast<uint8_t>(
ocp::accelerator_management::CompletionCode::SUCCESS));
pack.pack(static_cast<uint16_t>(0)); // reserved
pack.pack(static_cast<uint16_t>(value.size())); // data_size
for (const char c : value)
{
pack.pack(static_cast<uint8_t>(c));
}
return buf;
}
std::vector<uint8_t> buildInventoryErrorResponse(uint8_t cc,
uint16_t reasonCode)
{
return test_utils::buildPlatformEnvErrorResponse(
gpu::PlatformEnvironmentalCommands::GET_INVENTORY_INFORMATION, cc,
reasonCode);
}
class InventoryTest : public MctpMockTestBase
{
protected:
static std::shared_ptr<Inventory> createInventory(
const std::string& name = "GPU_INV",
gpu::DeviceIdentification deviceType =
gpu::DeviceIdentification::DEVICE_GPU,
uint8_t eid = test_utils::defaultEid)
{
return std::make_shared<Inventory>(bus(), objects(), name, requester(),
deviceType, eid, ioContext(),
nullptr, nullptr);
}
};
// Constructor — D-Bus interface creation
TEST_F(InventoryTest, ConstructorCreatesAssetInterface)
{
const std::string name = "inv_ctor";
const std::shared_ptr<Inventory> inv = createInventory(name);
const std::string path = "/xyz/openbmc_project/inventory/" + name;
EXPECT_EQ(getProperty<std::string>(
path, "xyz.openbmc_project.Inventory.Decorator.Asset",
"Manufacturer"),
"NVIDIA");
}
// Init — sends requests
TEST_F(InventoryTest, InitSendsAtLeastOneRequest)
{
EXPECT_CALL(mctpMock, sendRecvMsg)
.Times(testing::AtLeast(1))
.WillRepeatedly(mock_mctp::respondWith({}, {}));
const std::shared_ptr<Inventory> inv = createInventory("inv_init");
inv->init();
}
// Init — decoded values populate the Asset properties
TEST_F(InventoryTest, InitSuccessSetsAssetProperty)
{
// Answer every property request with the same valid string response so the
// whole property set resolves in one synchronous pass and the decode ->
// set_property path is exercised for the Asset interface.
const std::string inventoryText = "NVIDIA-INV-TEST-01";
ON_CALL(mctpMock, sendRecvMsg)
.WillByDefault(mock_mctp::respondWith(
{}, buildInventoryStringResponse(inventoryText)));
const std::string name = "inv_success";
const std::shared_ptr<Inventory> inv = createInventory(name);
inv->init();
const std::string path = "/xyz/openbmc_project/inventory/" + name;
const std::string assetIface =
"xyz.openbmc_project.Inventory.Decorator.Asset";
EXPECT_EQ(getProperty<std::string>(path, assetIface, "SerialNumber"),
inventoryText);
EXPECT_EQ(getProperty<std::string>(path, assetIface, "PartNumber"),
inventoryText);
EXPECT_EQ(getProperty<std::string>(path, assetIface, "Model"),
inventoryText);
}
// Error handling — init() must not crash on failed responses
TEST_F(InventoryTest, InitMctpTransportErrorNoCrash)
{
ON_CALL(mctpMock, sendRecvMsg)
.WillByDefault(mock_mctp::respondWith(
std::make_error_code(std::errc::timed_out), {}));
const std::shared_ptr<Inventory> inv = createInventory("inv_mctp_err");
EXPECT_NO_THROW(inv->init());
}
TEST_F(InventoryTest, InitBadCompletionCodeNoCrash)
{
ON_CALL(mctpMock, sendRecvMsg)
.WillByDefault(mock_mctp::respondWith(
{}, buildInventoryErrorResponse(
static_cast<uint8_t>(
ocp::accelerator_management::CompletionCode::ERROR),
0)));
const std::shared_ptr<Inventory> inv = createInventory("inv_bad_cc");
EXPECT_NO_THROW(inv->init());
}
TEST_F(InventoryTest, InitMalformedResponseNoCrash)
{
// A short all-zero buffer fails common-header validation before any
// completion code is read.
ON_CALL(mctpMock, sendRecvMsg)
.WillByDefault(mock_mctp::respondWith({}, std::vector<uint8_t>(5, 0)));
const std::shared_ptr<Inventory> inv = createInventory("inv_malformed");
EXPECT_NO_THROW(inv->init());
}
TEST_F(InventoryTest, InitEmptyBufferNoCrash)
{
ON_CALL(mctpMock, sendRecvMsg)
.WillByDefault(mock_mctp::respondWith({}, {}));
const std::shared_ptr<Inventory> inv = createInventory("inv_empty");
EXPECT_NO_THROW(inv->init());
}
} // namespace