blob: eca82969e4de0c0405892551faf16ff14152cdb4 [file]
#include "NVMeMetricStore.hpp"
#include <fcntl.h>
#include <unistd.h>
#include <boost/asio.hpp>
#include <boost/endian/arithmetic.hpp>
#include <cstring>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
std::vector<std::shared_ptr<MetricBase<>>>
MetricStore::getVendorMatrics(const std::string& /* path */)
{
return {};
}
class MetricMock : public MetricBase<std::chrono::steady_clock>
{
public:
MetricMock(std::shared_ptr<Scheduler<std::chrono::steady_clock>> scheduler,
std::string_view id) :
MetricBase<std::chrono::steady_clock>(
std::move(scheduler), std::chrono::steady_clock::duration::max()),
id(id)
{}
MOCK_METHOD(
void, readDevice,
(std::function<void(std::error_code ec, size_t size, bool complete)> &&
cb),
(noexcept, override));
std::string_view getIdentifier() const noexcept override
{
return id;
}
MOCK_METHOD(bool, isCacheValid, (), (const, noexcept, override));
MOCK_METHOD((std::tuple<std::chrono::time_point<ClockType>,
std::chrono::time_point<ClockType>,
std::span<const uint8_t>>),
getCache, (), (const, noexcept, override));
private:
std::string id;
};
class TestNVMeRefresh : public testing::Test
{
public:
boost::asio::io_context io;
std::shared_ptr<Scheduler<std::chrono::steady_clock>> scheduler;
std::shared_ptr<sdbusplus::asio::connection> conn;
TestNVMeRefresh()
{
scheduler = std::make_shared<Scheduler<std::chrono::steady_clock>>(io);
conn = std::make_shared<sdbusplus::asio::connection>(io);
scheduler->start();
scheduler->dequeue();
}
static sdbusplus::message::unix_fd refreshMetric(MetricStore& store,
std::string name)
{
return store.refreshMetric(std::move(name));
}
};
TEST_F(TestNVMeRefresh, RefreshEmptyName)
{
MetricStore store(io, conn, "/test/path");
EXPECT_THROW(
refreshMetric(store, ""),
sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
}
TEST_F(TestNVMeRefresh, RefreshUnknownName)
{
MetricStore store(io, conn, "/test/path");
EXPECT_THROW(
refreshMetric(store, "UnknownMetric"),
sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
}
TEST_F(TestNVMeRefresh, RefreshSuccess)
{
auto mtx = std::make_shared<MetricMock>(scheduler, "TestMetric");
testing::Mock::AllowLeak(mtx.get());
std::vector<std::shared_ptr<MetricBase<std::chrono::steady_clock>>>
metrics = {mtx};
{
MetricStore store(io, conn, "/test/path", metrics.begin(),
metrics.end());
std::chrono::steady_clock::time_point requestTime =
std::chrono::steady_clock::now();
// Simulation time: startTime is 5 seconds after requestTime, finishTime
// is 10 seconds after requestTime
std::chrono::steady_clock::time_point startTime =
requestTime + std::chrono::seconds(5);
std::chrono::steady_clock::time_point finishTime =
requestTime + std::chrono::seconds(10);
std::vector<uint8_t> data = {1, 2, 3};
EXPECT_CALL(*mtx, getCache())
.WillRepeatedly(testing::Return(std::make_tuple(
startTime, finishTime,
std::span<const uint8_t>(data.begin(), data.end()))));
EXPECT_CALL(*mtx, readDevice(testing::_))
.WillOnce(testing::Invoke(
[](std::function<void(std::error_code ec, size_t size,
bool complete)>&& cb) {
cb({}, 3, true);
}));
sdbusplus::message::unix_fd fd = refreshMetric(store, "TestMetric");
int readFd = fd.fd;
EXPECT_GE(readFd, 0);
// Run IO to trigger the refresh and subsequent write to pipe
bool dataRead = false;
std::vector<uint8_t> readBuffer(sizeof(MetricHeader) + data.size());
auto asyncPipe =
std::make_shared<boost::asio::posix::stream_descriptor>(io, readFd);
boost::asio::async_read(
*asyncPipe, boost::asio::buffer(readBuffer),
[&](const boost::system::error_code& ec, std::size_t n) {
EXPECT_FALSE(ec);
EXPECT_EQ(n, readBuffer.size());
dataRead = true;
});
while (!dataRead)
{
io.poll();
}
MetricHeader header{};
std::memcpy(&header, readBuffer.data(), sizeof(header));
EXPECT_EQ(header.lens, boost::endian::little_uint32_t(sizeof(header)));
EXPECT_EQ(header.startTime, boost::endian::little_uint64_t(
startTime.time_since_epoch().count()));
EXPECT_EQ(header.finishTime,
boost::endian::little_uint64_t(
finishTime.time_since_epoch().count()));
std::vector<uint8_t> actualData(readBuffer.begin() + sizeof(header),
readBuffer.end());
EXPECT_EQ(actualData, data);
}
// Explicitly clear remaining refs
mtx.reset();
metrics.clear();
scheduler.reset();
}
TEST_F(TestNVMeRefresh, RefreshDouble)
{
auto mtx = std::make_shared<MetricMock>(scheduler, "TestMetric");
testing::Mock::AllowLeak(mtx.get());
std::vector<std::shared_ptr<MetricBase<std::chrono::steady_clock>>>
metrics = {mtx};
MetricStore store(io, conn, "/test/path", metrics.begin(), metrics.end());
// 1. Simulate an ongoing refresh that started BEFORE the request
std::chrono::steady_clock::time_point preRequestTime =
std::chrono::steady_clock::now() - std::chrono::seconds(10);
std::vector<uint8_t> data = {1, 2, 3};
EXPECT_CALL(*mtx, readDevice(testing::_))
.Times(2)
.WillRepeatedly(testing::Invoke(
[](std::function<void(std::error_code ec, size_t size,
bool complete)>&& cb) { cb({}, 3, true); }));
std::chrono::steady_clock::time_point secondStartTime;
std::chrono::steady_clock::time_point secondFinishTime;
EXPECT_CALL(*mtx, getCache())
.WillOnce(testing::Return(std::make_tuple(
preRequestTime, preRequestTime,
std::span<const uint8_t>(data.begin(), data.end()))))
.WillRepeatedly(testing::Invoke([&]() {
secondStartTime = std::chrono::steady_clock::now() +
std::chrono::seconds(5);
secondFinishTime = secondStartTime + std::chrono::seconds(5);
return std::make_tuple(
secondStartTime, secondFinishTime,
std::span<const uint8_t>(data.begin(), data.end()));
}));
sdbusplus::message::unix_fd fd = refreshMetric(store, "TestMetric");
int readFd = fd.fd;
EXPECT_GE(readFd, 0);
bool dataRead = false;
std::vector<uint8_t> readBuffer(sizeof(MetricHeader) + data.size());
auto asyncPipe =
std::make_shared<boost::asio::posix::stream_descriptor>(io, readFd);
boost::asio::async_read(
*asyncPipe, boost::asio::buffer(readBuffer),
[&](const boost::system::error_code& ec, std::size_t n) {
EXPECT_FALSE(ec);
EXPECT_EQ(n, readBuffer.size());
dataRead = true;
});
while (!dataRead)
{
io.poll();
}
MetricHeader header{};
std::memcpy(&header, readBuffer.data(), sizeof(header));
EXPECT_EQ(header.startTime,
boost::endian::little_uint64_t(
secondStartTime.time_since_epoch().count()));
}
int main(int argc, char** argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}