| #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(); |
| } |