blob: 5d01b7340cfc5da991fffe686af2d626ebbfba95 [file] [log] [blame]
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "errors.hpp"
#include "handler.hpp"
#include "handler_impl.hpp"
#include <systemd/sd-bus.h>
#include <nlohmann/json.hpp>
#include <sdbusplus/message.hpp>
#include <sdbusplus/test/sdbus_mock.hpp>
#include <stdplus/print.hpp>
#include <charconv>
#include <filesystem>
#include <fstream>
#include <functional>
#include <string>
#include <tuple>
#include <gtest/gtest.h>
namespace google
{
namespace ipmi
{
using testing::_;
using testing::Return;
TEST(HandlerTest, EthCheckValidHappy)
{
Handler h;
std::tuple<std::uint8_t, std::string> result = h.getEthDetails("et");
EXPECT_EQ(12, std::get<0>(result));
EXPECT_STREQ("et", std::get<1>(result).c_str());
}
TEST(HandlerTest, CableCheckIllegalPath)
{
Handler h;
EXPECT_THROW(h.getRxPackets("eth0/../../"), IpmiException);
}
TEST(HandlerTest, readNameFromConfigInstanceVariety)
{
// Make sure it handles the failures and successes as we expect.
struct testCase
{
std::string type;
std::uint8_t instance;
std::string expectedName;
};
std::vector<testCase> tests = {
{"cpu", 5, ""},
{"cpu", 3, "CPU2"},
};
auto j2 = R"(
{
"cpu": [
{"instance": 1, "name": "CPU0"},
{"instance": 2, "name": "CPU1"},
{"instance": 3, "name": "CPU2"},
{"instance": 4, "name": "CPU3"}
]
}
)"_json;
for (const auto& test : tests)
{
EXPECT_STREQ(test.expectedName.c_str(),
readNameFromConfig(test.type, test.instance, j2).c_str());
}
}
// TODO: If we can test with phosphor-logging in the future, there are more
// failure cases.
TEST(HandlerTest, getEntityNameWithNameNotFoundExcepts)
{
const char* testFilename = "test.json";
std::string contents = R"({"cpu": [{"instance": 1, "name": "CPU0"}]})";
std::ofstream outputJson(testFilename);
outputJson << contents;
outputJson.flush();
outputJson.close();
Handler h(testFilename);
EXPECT_THROW(h.getEntityName(0x03, 2), IpmiException);
(void)std::remove(testFilename);
}
TEST(HandlerTest, getEntityNameWithNameFoundReturnsIt)
{
const char* testFilename = "test.json";
std::string contents = R"({"cpu": [{"instance": 1, "name": "CPU0"}]})";
std::ofstream outputJson(testFilename);
outputJson << contents;
outputJson.flush();
outputJson.close();
Handler h(testFilename);
EXPECT_STREQ("CPU0", h.getEntityName(0x03, 1).c_str());
(void)std::remove(testFilename);
}
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::ContainerEq;
using ::testing::DoAll;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::IsNull;
using ::testing::MatcherCast;
using ::testing::NotNull;
using ::testing::Pointee;
using ::testing::Return;
using ::testing::ReturnNull;
using ::testing::SafeMatcherCast;
using ::testing::SetArgPointee;
using ::testing::StrEq;
using ::testing::StrictMock;
using ::testing::StrNe;
using ::testing::WithArg;
class MockDbusHandler : public Handler
{
public:
MockDbusHandler(sdbusplus::SdBusMock& mock,
const std::string& config = "") :
Handler(config), mock_(&mock)
{}
protected:
sdbusplus::bus_t getDbus() const override
{
return sdbusplus::get_mocked_new(
const_cast<sdbusplus::SdBusMock*>(mock_));
}
private:
sdbusplus::SdBusMock* mock_;
};
ACTION_TEMPLATE(AssignReadVal, HAS_1_TEMPLATE_PARAMS(typename, T),
AND_1_VALUE_PARAMS(val))
{
*static_cast<T*>(arg2) = val;
}
ACTION_P(TraceDbus, msg)
{
stdplus::print(stderr, "{}\n", msg);
}
ACTION_P(TraceDbus2, msg)
{
stdplus::print(stderr, "{}({:02x})\n", msg,
*static_cast<const uint8_t*>(arg2));
}
constexpr char object_path[] = "/com/google/customAccel/test/path";
constexpr char property_grpc[] = "com.google.custom_accel.gRPC";
constexpr char value_port[] = "Port";
constexpr uint32_t port = 5000;
constexpr char SD_BUS_TYPE_BYTE_STR[] = {SD_BUS_TYPE_BYTE, '\0'};
// Returns an object that looks like:
// "/com/google/customAccel/test/path": {
// "com.google.custom_accel.gRPC" : {
// "Port" : {
// "type" : "u",
// "data" : 5000
// }
// }
// }
void ExpectGetManagedObjects(StrictMock<sdbusplus::SdBusMock>& mock,
const char* obj_path = object_path)
{
::testing::InSequence s;
// These must be nullptr or sd_bus_message_unref will seg fault.
constexpr sd_bus_message* method = nullptr;
constexpr sd_bus_message* msg = nullptr;
EXPECT_CALL(mock, sd_bus_message_new_method_call(
_, // sd_bus *bus,
NotNull(), // sd_bus_message **m
StrEq("com.google.custom_accel"), StrEq("/"),
StrEq("org.freedesktop.DBus.ObjectManager"),
StrEq("GetManagedObjects")))
.WillOnce(DoAll(SetArgPointee<1>(method), Return(0)));
EXPECT_CALL(mock, sd_bus_call(_, // sd_bus *bus,
method, // sd_bus_message *m
_, // uint64_t timeout
NotNull(), // sd_bus_error *ret_error
NotNull())) // sd_bus_message **reply
.WillOnce(DoAll(SetArgPointee<3>(SD_BUS_ERROR_NULL),
SetArgPointee<4>(msg), // reply
Return(0)));
EXPECT_CALL(mock, sd_bus_message_enter_container(msg, SD_BUS_TYPE_ARRAY,
StrEq("{oa{sa{sv}}}")))
.WillOnce(Return(1));
EXPECT_CALL(mock, sd_bus_message_at_end(msg, 0)).WillOnce(Return(0));
EXPECT_CALL(mock, sd_bus_message_enter_container(
msg, SD_BUS_TYPE_DICT_ENTRY, StrEq("oa{sa{sv}}")))
.WillOnce(Return(1));
EXPECT_CALL(mock, sd_bus_message_read_basic(msg, SD_BUS_TYPE_OBJECT_PATH,
NotNull()))
.WillOnce(DoAll(AssignReadVal<const char*>(obj_path), Return(1)));
EXPECT_CALL(mock, sd_bus_message_enter_container(msg, SD_BUS_TYPE_ARRAY,
StrEq("{sa{sv}}")))
.WillOnce(Return(1));
EXPECT_CALL(mock, sd_bus_message_at_end(msg, 0)).WillOnce(Return(0));
EXPECT_CALL(mock, sd_bus_message_enter_container(
msg, SD_BUS_TYPE_DICT_ENTRY, StrEq("sa{sv}")))
.WillOnce(Return(1));
EXPECT_CALL(mock,
sd_bus_message_read_basic(msg, SD_BUS_TYPE_STRING, NotNull()))
.WillOnce(DoAll(AssignReadVal<const char*>(property_grpc), Return(1)));
EXPECT_CALL(mock, sd_bus_message_enter_container(msg, SD_BUS_TYPE_ARRAY,
StrEq("{sv}")))
.WillOnce(Return(1));
EXPECT_CALL(mock, sd_bus_message_at_end(msg, 0)).WillOnce(Return(0));
EXPECT_CALL(mock, sd_bus_message_enter_container(
msg, SD_BUS_TYPE_DICT_ENTRY, StrEq("sv")))
.WillOnce(Return(1));
EXPECT_CALL(mock,
sd_bus_message_read_basic(msg, SD_BUS_TYPE_STRING, NotNull()))
.WillOnce(DoAll(AssignReadVal<const char*>(value_port), Return(1)));
EXPECT_CALL(
mock, sd_bus_message_verify_type(msg, SD_BUS_TYPE_VARIANT, StrNe("u")))
.Times(AnyNumber())
.WillRepeatedly(Return(0));
EXPECT_CALL(
mock, sd_bus_message_verify_type(msg, SD_BUS_TYPE_VARIANT, StrEq("u")))
.WillOnce(Return(1));
EXPECT_CALL(mock, sd_bus_message_enter_container(msg, SD_BUS_TYPE_VARIANT,
StrEq("u")))
.WillOnce(Return(1));
EXPECT_CALL(mock,
sd_bus_message_read_basic(msg, SD_BUS_TYPE_UINT32, NotNull()))
.WillOnce(DoAll(AssignReadVal<uint32_t>(port), Return(0)));
EXPECT_CALL(
mock, sd_bus_message_verify_type(msg, SD_BUS_TYPE_VARIANT, StrNe("u")))
.Times(AnyNumber())
.WillRepeatedly(Return(0));
EXPECT_CALL(mock, sd_bus_message_exit_container(msg))
.WillOnce(Return(1))
.WillOnce(Return(1));
EXPECT_CALL(mock, sd_bus_message_at_end(msg, 0)).WillOnce(Return(1));
EXPECT_CALL(mock, sd_bus_message_exit_container(msg))
.WillOnce(Return(1))
.WillOnce(Return(1));
EXPECT_CALL(mock, sd_bus_message_at_end(msg, 0)).WillOnce(Return(1));
EXPECT_CALL(mock, sd_bus_message_exit_container(msg))
.WillOnce(Return(1))
.WillOnce(Return(1));
EXPECT_CALL(mock, sd_bus_message_at_end(msg, 0)).WillOnce(Return(1));
EXPECT_CALL(mock, sd_bus_message_exit_container(msg)).WillOnce(Return(1));
}
void ExpectSdBusError(StrictMock<sdbusplus::SdBusMock>& mock,
const std::string& service, const std::string& objPath,
const std::string& interface, const std::string& function)
{
EXPECT_CALL(mock, sd_bus_message_new_method_call(
_, // sd_bus *bus,
NotNull(), // sd_bus_message **m
StrEq(service), StrEq(objPath), StrEq(interface),
StrEq(function)))
.WillOnce(Return(-ENOTCONN));
}
TEST(HandlerTest, accelOobDeviceCount_Success)
{
StrictMock<sdbusplus::SdBusMock> mock;
MockDbusHandler h(mock);
ExpectGetManagedObjects(mock);
EXPECT_EQ(1, h.accelOobDeviceCount());
}
TEST(HandlerTest, accelOobDeviceCount_Fail)
{
StrictMock<sdbusplus::SdBusMock> mock;
MockDbusHandler h(mock);
ExpectSdBusError(mock, "com.google.custom_accel", "/",
"org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
EXPECT_THROW(h.accelOobDeviceCount(), IpmiException);
}
TEST(HandlerTest, accelOobDeviceName_Success)
{
StrictMock<sdbusplus::SdBusMock> mock;
MockDbusHandler h(mock);
ExpectGetManagedObjects(mock);
EXPECT_EQ(std::string("test/path"), h.accelOobDeviceName(0));
}
TEST(HandlerTest, accelOobDeviceName_Fail)
{
StrictMock<sdbusplus::SdBusMock> mock;
MockDbusHandler h(mock);
ExpectSdBusError(mock, "com.google.custom_accel", "/",
"org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
EXPECT_THROW(h.accelOobDeviceName(0), IpmiException);
}
TEST(HandlerTest, accelOobDeviceName_OutOfRange)
{
StrictMock<sdbusplus::SdBusMock> mock;
MockDbusHandler h(mock);
ExpectGetManagedObjects(mock);
EXPECT_THROW(h.accelOobDeviceName(1), IpmiException);
}
TEST(HandlerTest, accelOobDeviceName_InvalidName)
{
constexpr char bad_object_path[] = "/com/google/customAccel2/bad/path";
StrictMock<sdbusplus::SdBusMock> mock;
MockDbusHandler h(mock);
ExpectGetManagedObjects(mock, bad_object_path);
EXPECT_THROW(h.accelOobDeviceName(0), IpmiException);
}
constexpr uint8_t NUM_BYTES_RETURNED_EQ_NUM_BYTES = 0xff;
void ExpectRead(StrictMock<sdbusplus::SdBusMock>& mock, uint64_t address,
uint8_t num_bytes, uint64_t data, int sd_bus_call_return_value,
uint8_t num_bytes_returned = NUM_BYTES_RETURNED_EQ_NUM_BYTES)
{
::testing::InSequence s;
// These must be nullptr or sd_bus_message_unref will seg fault.
constexpr sd_bus_message* method = nullptr;
constexpr sd_bus_message* msg = nullptr;
EXPECT_CALL(mock, sd_bus_message_new_method_call(
_, // sd_bus *bus,
NotNull(), // sd_bus_message **m
StrEq("com.google.custom_accel"),
StrEq("/com/google/customAccel/test/path"),
StrEq("com.google.custom_accel.BAR"), StrEq("Read")))
.WillOnce(DoAll(SetArgPointee<1>(method), Return(0)));
EXPECT_CALL(mock,
sd_bus_message_append_basic(
method, SD_BUS_TYPE_UINT64,
MatcherCast<const void*>(SafeMatcherCast<const uint64_t*>(
Pointee(Eq(address))))))
.WillOnce(Return(1));
EXPECT_CALL(mock,
sd_bus_message_append_basic(
method, SD_BUS_TYPE_UINT64,
MatcherCast<const void*>(SafeMatcherCast<const uint64_t*>(
Pointee(Eq<uint64_t>(num_bytes))))))
.WillOnce(Return(1));
EXPECT_CALL(mock, sd_bus_call(_, // sd_bus *bus,
method, // sd_bus_message *m
_, // uint64_t timeout
NotNull(), // sd_bus_error *ret_error
NotNull())) // sd_bus_message **reply
.WillOnce(DoAll(SetArgPointee<3>(SD_BUS_ERROR_NULL),
SetArgPointee<4>(msg), // reply
Return(sd_bus_call_return_value)));
if (sd_bus_call_return_value >= 0)
{
EXPECT_CALL(mock,
sd_bus_message_enter_container(msg, SD_BUS_TYPE_ARRAY,
StrEq(SD_BUS_TYPE_BYTE_STR)))
.WillOnce(Return(1));
if (num_bytes_returned == NUM_BYTES_RETURNED_EQ_NUM_BYTES)
{
num_bytes_returned = num_bytes;
}
for (auto i = num_bytes_returned - 1; i >= 0; --i)
{
EXPECT_CALL(mock, sd_bus_message_at_end(msg, 0))
.WillOnce(Return(0));
const uint8_t byte = (i >= 8) ? 0 : (data >> (8 * i)) & 0xff;
EXPECT_CALL(mock, sd_bus_message_read_basic(msg, SD_BUS_TYPE_BYTE,
NotNull()))
.WillOnce(DoAll(AssignReadVal<uint8_t>(byte), Return(1)));
}
EXPECT_CALL(mock, sd_bus_message_at_end(msg, 0)).WillOnce(Return(1));
EXPECT_CALL(mock, sd_bus_message_exit_container(msg))
.WillOnce(Return(1));
}
}
TEST(HandlerTest, accelOobRead_Success)
{
StrictMock<sdbusplus::SdBusMock> mock;
MockDbusHandler h(mock);
constexpr uint64_t address = 0x123456789abcdef;
constexpr uint8_t num_bytes = sizeof(uint64_t);
constexpr int sd_bus_call_return_value = 1;
constexpr uint64_t data = 0x13579bdf02468ace;
ExpectRead(mock, address, num_bytes, data, sd_bus_call_return_value);
EXPECT_EQ(data, h.accelOobRead("test/path", address, num_bytes));
}
TEST(HandlerTest, accelOobRead_Fail)
{
StrictMock<sdbusplus::SdBusMock> mock;
MockDbusHandler h(mock);
constexpr uint64_t address = 0x123456789abcdef;
constexpr uint8_t num_bytes = sizeof(uint64_t);
constexpr int sd_bus_call_return_value = -ENOTCONN;
constexpr uint64_t data = 0x13579bdf02468ace;
ExpectRead(mock, address, num_bytes, data, sd_bus_call_return_value);
EXPECT_THROW(h.accelOobRead("test/path", address, num_bytes),
IpmiException);
}
TEST(HandlerTest, accelOobRead_TooFewBytesReturned)
{
StrictMock<sdbusplus::SdBusMock> mock;
MockDbusHandler h(mock);
constexpr uint64_t address = 0x123456789abcdef;
constexpr uint8_t num_bytes = sizeof(uint64_t);
constexpr int sd_bus_call_return_value = 1;
constexpr uint64_t data = 0x13579bdf02468ace;
constexpr uint8_t num_bytes_returned = num_bytes - 1;
ExpectRead(mock, address, num_bytes, data, sd_bus_call_return_value,
num_bytes_returned);
EXPECT_THROW(h.accelOobRead("test/path", address, num_bytes),
IpmiException);
}
TEST(HandlerTest, accelOobRead_TooManyBytesReturned)
{
StrictMock<sdbusplus::SdBusMock> mock;
MockDbusHandler h(mock);
constexpr uint64_t address = 0x123456789abcdef;
constexpr uint8_t num_bytes = sizeof(uint64_t);
constexpr int sd_bus_call_return_value = 1;
constexpr uint64_t data = 0x13579bdf02468ace;
constexpr uint8_t num_bytes_returned = sizeof(uint64_t) + 1;
ExpectRead(mock, address, num_bytes, data, sd_bus_call_return_value,
num_bytes_returned);
EXPECT_THROW(h.accelOobRead("test/path", address, num_bytes),
IpmiException);
}
void ExpectWrite(StrictMock<sdbusplus::SdBusMock>& mock, uint64_t address,
uint8_t num_bytes, uint64_t data, int sd_bus_call_return_value)
{
::testing::InSequence s;
// These must be nullptr or sd_bus_message_unref will seg fault.
constexpr sd_bus_message* method = nullptr;
EXPECT_CALL(mock, sd_bus_message_new_method_call(
_, // sd_bus *bus,
NotNull(), // sd_bus_message **m
StrEq("com.google.custom_accel"),
StrEq("/com/google/customAccel/test/path"),
StrEq("com.google.custom_accel.BAR"), StrEq("Write")))
.WillOnce(DoAll(TraceDbus("sd_bus_message_new_method_call"),
SetArgPointee<1>(method), Return(0)));
EXPECT_CALL(mock,
sd_bus_message_append_basic(
method, SD_BUS_TYPE_UINT64,
MatcherCast<const void*>(SafeMatcherCast<const uint64_t*>(
Pointee(Eq(address))))))
.WillOnce(DoAll(TraceDbus("sd_bus_message_append_basic(address) -> 1"),
Return(1)));
EXPECT_CALL(mock,
sd_bus_message_open_container(method, SD_BUS_TYPE_ARRAY,
StrEq(SD_BUS_TYPE_BYTE_STR)))
.WillOnce(DoAll(TraceDbus("sd_bus_message_open_container(a, y) -> 0"),
Return(0)));
for (auto i = 0; i < num_bytes; ++i)
{
const uint8_t byte = (data >> (8 * i)) & 0xff;
EXPECT_CALL(
mock, sd_bus_message_append_basic(
method, SD_BUS_TYPE_BYTE,
MatcherCast<const void*>(
SafeMatcherCast<const uint8_t*>(Pointee(Eq(byte))))))
.WillOnce(
DoAll(TraceDbus2("sd_bus_message_append_basic"), Return(1)));
}
EXPECT_CALL(mock, sd_bus_message_close_container(method))
.WillOnce(DoAll(TraceDbus("sd_bus_message_close_container() -> 0"),
Return(0)));
EXPECT_CALL(mock, sd_bus_call(_, // sd_bus *bus,
method, // sd_bus_message *m
_, // uint64_t timeout
NotNull(), // sd_bus_error *ret_error
IsNull())) // sd_bus_message **reply
.WillOnce(DoAll(TraceDbus("sd_bus_call() -> ret_val"),
SetArgPointee<3>(SD_BUS_ERROR_NULL),
Return(sd_bus_call_return_value)));
}
TEST(HandlerTest, accelOobWrite_Success)
{
StrictMock<sdbusplus::SdBusMock> mock;
MockDbusHandler h(mock);
constexpr uint64_t address = 0x123456789abcdef;
constexpr uint8_t num_bytes = sizeof(uint64_t);
constexpr int sd_bus_call_return_value = 1;
constexpr uint64_t data = 0x13579bdf02468ace;
ExpectWrite(mock, address, num_bytes, data, sd_bus_call_return_value);
EXPECT_NO_THROW(h.accelOobWrite("test/path", address, num_bytes, data));
}
TEST(HandlerTest, accelOobRead_TooManyBytesRequested)
{
StrictMock<sdbusplus::SdBusMock> mock;
MockDbusHandler h(mock);
constexpr uint64_t address = 0x123456789abcdef;
constexpr uint8_t num_bytes = sizeof(uint64_t) + 1;
constexpr uint64_t data = 0x13579bdf02468ace;
EXPECT_THROW(h.accelOobWrite("test/path", address, num_bytes, data),
IpmiException);
}
TEST(HandlerTest, accelOobWrite_Fail)
{
StrictMock<sdbusplus::SdBusMock> mock;
MockDbusHandler h(mock);
constexpr uint64_t address = 0x123456789abcdef;
constexpr uint8_t num_bytes = sizeof(uint64_t);
constexpr int sd_bus_call_return_value = -ENOTCONN;
constexpr uint64_t data = 0x13579bdf02468ace;
ExpectWrite(mock, address, num_bytes, data, sd_bus_call_return_value);
EXPECT_THROW(h.accelOobWrite("test/path", address, num_bytes, data),
IpmiException);
}
TEST(HandlerTest, PcieBifurcation)
{
const std::string& testJson = "/tmp/test-json";
auto j = R"(
{
"1": [ 1, 3 ],
"3": [ 3, 6 ],
"4": [ 3, 4, 1 ],
"6": [ 8 ]
}
)"_json;
std::ofstream bifurcationJson(testJson);
bifurcationJson << j.dump();
bifurcationJson.flush();
bifurcationJson.close();
BifurcationStatic bifurcationHelper(testJson);
Handler h(std::ref(bifurcationHelper));
std::unordered_map<uint8_t, std::vector<uint8_t>> expectedMapping = {
{1, {1, 3}}, {3, {3, 6}}, {4, {3, 4, 1}}, {6, {8}}};
std::vector<uint8_t> invalidBus = {0, 2, 5, 7};
for (const auto& [bus, output] : expectedMapping)
{
EXPECT_THAT(h.pcieBifurcation(bus), ContainerEq(output));
}
for (const auto& bus : invalidBus)
{
EXPECT_TRUE(h.pcieBifurcation(bus).empty());
}
std::filesystem::remove(testJson.data());
bifurcationHelper = BifurcationStatic(testJson);
Handler h2(std::ref(bifurcationHelper));
for (uint8_t i = 0; i < 8; ++i)
{
auto bifurcation = h2.pcieBifurcation(i);
EXPECT_TRUE(bifurcation.empty());
}
}
TEST(HandlerTest, BmInstanceFailCase)
{
StrictMock<sdbusplus::SdBusMock> mock;
MockDbusHandler h(mock);
// Invalid enum
EXPECT_THROW(h.getBMInstanceProperty(0x07), IpmiException);
// Valid enum but no path exists
EXPECT_THROW(h.getBMInstanceProperty(0x00), IpmiException);
}
// TODO: Add checks for other functions of handler.
} // namespace ipmi
} // namespace google