| #include "NVMeDevice.hpp" |
| #include "NVMeIntf.hpp" |
| #include "NVMeMi.hpp" |
| #include "NVMeSubsys.hpp" |
| #include "Utils.hpp" |
| |
| #include <boost/asio/steady_timer.hpp> |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| class MockMctpEndpoint : public MctpEndpoint |
| { |
| public: |
| MOCK_METHOD(int, network, (), (const, override)); |
| MOCK_METHOD(uint8_t, eid, (), (const, override)); |
| MOCK_METHOD(void, subscribe, |
| (MctpEndpoint::Event && degraded, |
| MctpEndpoint::Event&& available, |
| MctpEndpoint::Event&& removed), |
| (override)); |
| MOCK_METHOD(void, setMtu, |
| (uint32_t mtu, |
| std::function<void(const std::error_code& ec)>&& completed), |
| (override)); |
| MOCK_METHOD(void, remove, (), (override)); |
| MOCK_METHOD(void, recover, (), (override)); |
| MOCK_METHOD(std::string, describe, (), (const, override)); |
| MOCK_METHOD(bool, isI2cAccessible, (), (const, override)); |
| }; |
| |
| class MockMctpDevice : public MctpDevice |
| { |
| public: |
| MOCK_METHOD(void, setup, |
| (std::function<void(const std::error_code& ec, |
| const std::shared_ptr<MctpEndpoint>& ep)> && |
| action), |
| (override)); |
| MOCK_METHOD(void, remove, (), (override)); |
| MOCK_METHOD(std::string, describe, (), (const, override)); |
| }; |
| |
| class TestNVMeMi : public NVMeMi |
| { |
| public: |
| using NVMeMi::NVMeMi; |
| bool isStarted = false; |
| |
| void start(const std::shared_ptr<MctpEndpoint>& ep) override |
| { |
| isStarted = true; |
| NVMeMi::start(ep); |
| } |
| void stop() override |
| { |
| isStarted = false; |
| NVMeMi::stop(); |
| } |
| }; |
| |
| TEST(NVMeRecovery, optimisationFailure) |
| { |
| boost::asio::io_context io; |
| |
| auto mctpEp = std::make_shared<MockMctpEndpoint>(); |
| EXPECT_CALL(*mctpEp, describe()) |
| .WillRepeatedly(testing::Return("Mock MCTP Endpoint")); |
| EXPECT_CALL(*mctpEp, eid()).WillRepeatedly(testing::Return(9)); |
| EXPECT_CALL(*mctpEp, network()).WillRepeatedly(testing::Return(1)); |
| EXPECT_CALL(*mctpEp, isI2cAccessible()) |
| .WillRepeatedly(testing::Return(true)); |
| |
| boost::asio::spawn(io, [&](boost::asio::yield_context yield) { |
| MctpEndpoint::Event degradedHandler; |
| MctpEndpoint::Event availableHandler; |
| MctpEndpoint::Event removedHandler; |
| EXPECT_CALL(*mctpEp, subscribe(testing::_, testing::_, testing::_)) |
| .WillOnce(testing::DoAll(testing::SaveArg<0>(°radedHandler), |
| testing::SaveArg<1>(&availableHandler), |
| testing::SaveArg<2>(&removedHandler), |
| testing::InvokeWithoutArgs([&]() { |
| io.post([&]() { availableHandler(mctpEp); }); |
| }))); |
| EXPECT_CALL(*mctpEp, recover()) |
| .Times(testing::Between(1, 3)) |
| .WillRepeatedly(testing::InvokeWithoutArgs([&]() { |
| io.post([&]() { degradedHandler(mctpEp); }); |
| io.post([&]() { availableHandler(mctpEp); }); |
| })); |
| |
| boost::asio::steady_timer timer(io); |
| auto mctpDev = std::make_shared<MockMctpDevice>(); |
| EXPECT_CALL(*mctpDev, describe()) |
| .WillRepeatedly(testing::Return("Mock MCTP Device")); |
| EXPECT_CALL(*mctpDev, setup(testing::_)) |
| .WillOnce(testing::InvokeArgument<0>(std::error_code(), mctpEp)); |
| |
| auto systemBus = std::make_shared<sdbusplus::asio::connection>(io); |
| sdbusplus::asio::object_server objectServer(systemBus, true); |
| auto worker = NVMeMiWorker::create(io); |
| auto intf = NVMeIntf::create<NVMeMi>(io, systemBus, mctpDev, worker); |
| SensorData sensorData{}; |
| auto subsys = NVMeSubsystem::create(io, objectServer, systemBus, "/foo", |
| "bar", sensorData, intf, false); |
| auto nvmeDev = NVMeDevice::create(io, mctpDev, std::move(intf), subsys, |
| std::chrono::seconds(2)); |
| nvmeDev->start(); |
| timer.expires_after(std::chrono::seconds(6)); |
| timer.async_wait(yield); |
| // io.run_for(std::chrono::seconds(6)); |
| |
| nvmeDev->stop(); |
| // io.run_for(std::chrono::seconds(1)); |
| timer.expires_after(std::chrono::seconds(1)); |
| timer.async_wait(yield); |
| |
| // https://stackoverflow.com/a/10289205 |
| EXPECT_TRUE(testing::Mock::VerifyAndClearExpectations(mctpEp.get())); |
| EXPECT_TRUE(testing::Mock::VerifyAndClearExpectations(mctpDev.get())); |
| io.stop(); |
| }); |
| |
| io.run(); |
| } |
| |
| TEST(NVMeRecovery, tightEventCycle) |
| { |
| boost::asio::io_context io; |
| |
| auto mctpEp = std::make_shared<MockMctpEndpoint>(); |
| EXPECT_CALL(*mctpEp, describe()) |
| .WillRepeatedly(testing::Return("Mock MCTP Endpoint")); |
| EXPECT_CALL(*mctpEp, eid()).WillRepeatedly(testing::Return(9)); |
| EXPECT_CALL(*mctpEp, network()).WillRepeatedly(testing::Return(1)); |
| EXPECT_CALL(*mctpEp, isI2cAccessible()) |
| .WillRepeatedly(testing::Return(true)); |
| |
| boost::asio::spawn(io, [&](boost::asio::yield_context yield) { |
| MctpEndpoint::Event degradedHandler; |
| MctpEndpoint::Event availableHandler; |
| MctpEndpoint::Event removedHandler; |
| EXPECT_CALL(*mctpEp, subscribe(testing::_, testing::_, testing::_)) |
| .WillOnce(testing::DoAll(testing::SaveArg<0>(°radedHandler), |
| testing::SaveArg<1>(&availableHandler), |
| testing::SaveArg<2>(&removedHandler), |
| testing::InvokeWithoutArgs([&]() { |
| io.post([&]() { availableHandler(mctpEp); }); |
| }))); |
| EXPECT_CALL(*mctpEp, recover()).Times(testing::AnyNumber()); |
| |
| boost::asio::steady_timer timer(io); |
| auto mctpDev = std::make_shared<MockMctpDevice>(); |
| EXPECT_CALL(*mctpDev, describe()) |
| .WillRepeatedly(testing::Return("Mock MCTP Device")); |
| EXPECT_CALL(*mctpDev, setup(testing::_)) |
| .WillOnce(testing::InvokeArgument<0>(std::error_code(), mctpEp)); |
| |
| auto systemBus = std::make_shared<sdbusplus::asio::connection>(io); |
| sdbusplus::asio::object_server objectServer(systemBus, true); |
| auto worker = NVMeMiWorker::create(io); |
| auto intf = NVMeIntf::create<TestNVMeMi>(io, systemBus, mctpDev, |
| worker); |
| auto testMi = std::dynamic_pointer_cast<TestNVMeMi>( |
| std::get<std::shared_ptr<NVMeMiIntf>>(intf.getInferface())); |
| SensorData sensorData{}; |
| auto subsys = NVMeSubsystem::create(io, objectServer, systemBus, "/foo", |
| "bar", sensorData, intf, false); |
| auto nvmeDev = NVMeDevice::create(io, mctpDev, std::move(intf), subsys, |
| std::chrono::seconds(2)); |
| nvmeDev->start(); |
| |
| // Wait for initial available handler to fire |
| timer.expires_after(std::chrono::milliseconds(100)); |
| timer.async_wait(yield); |
| |
| // 1. Degrade |
| degradedHandler(mctpEp); |
| |
| // 2. Wait < 1 sec |
| timer.expires_after(std::chrono::milliseconds(500)); |
| timer.async_wait(yield); |
| |
| // 3. Available |
| availableHandler(mctpEp); |
| |
| // 4. Wait < 1 sec again (before 2s grace period ends) |
| timer.expires_after(std::chrono::milliseconds(500)); |
| timer.async_wait(yield); |
| |
| // 5. Degrade |
| degradedHandler(mctpEp); |
| |
| // Let things settle to ensure no crashes |
| timer.expires_after(std::chrono::seconds(2)); |
| timer.async_wait(yield); |
| |
| // Validate the final state for the mctpEp should be degraded |
| EXPECT_FALSE(testMi->isStarted); |
| |
| nvmeDev->stop(); |
| |
| timer.expires_after(std::chrono::seconds(1)); |
| timer.async_wait(yield); |
| |
| EXPECT_TRUE(testing::Mock::VerifyAndClearExpectations(mctpEp.get())); |
| EXPECT_TRUE(testing::Mock::VerifyAndClearExpectations(mctpDev.get())); |
| io.stop(); |
| }); |
| |
| io.run(); |
| } |
| |
| TEST(NVMeRecovery, consecutiveAvailableBypass) |
| { |
| boost::asio::io_context io; |
| |
| auto mctpEp = std::make_shared<MockMctpEndpoint>(); |
| EXPECT_CALL(*mctpEp, describe()) |
| .WillRepeatedly(testing::Return("Mock MCTP Endpoint")); |
| EXPECT_CALL(*mctpEp, eid()).WillRepeatedly(testing::Return(9)); |
| EXPECT_CALL(*mctpEp, network()).WillRepeatedly(testing::Return(1)); |
| EXPECT_CALL(*mctpEp, isI2cAccessible()) |
| .WillRepeatedly(testing::Return(true)); |
| |
| boost::asio::spawn(io, [&](boost::asio::yield_context yield) { |
| MctpEndpoint::Event degradedHandler; |
| MctpEndpoint::Event availableHandler; |
| MctpEndpoint::Event removedHandler; |
| EXPECT_CALL(*mctpEp, subscribe(testing::_, testing::_, testing::_)) |
| .WillOnce(testing::DoAll(testing::SaveArg<0>(°radedHandler), |
| testing::SaveArg<1>(&availableHandler), |
| testing::SaveArg<2>(&removedHandler), |
| testing::InvokeWithoutArgs([&]() { |
| io.post([&]() { availableHandler(mctpEp); }); |
| }))); |
| EXPECT_CALL(*mctpEp, recover()).Times(testing::AnyNumber()); |
| |
| boost::asio::steady_timer timer(io); |
| auto mctpDev = std::make_shared<MockMctpDevice>(); |
| EXPECT_CALL(*mctpDev, describe()) |
| .WillRepeatedly(testing::Return("Mock MCTP Device")); |
| EXPECT_CALL(*mctpDev, setup(testing::_)) |
| .WillOnce(testing::InvokeArgument<0>(std::error_code(), mctpEp)); |
| |
| auto systemBus = std::make_shared<sdbusplus::asio::connection>(io); |
| sdbusplus::asio::object_server objectServer(systemBus, true); |
| auto worker = NVMeMiWorker::create(io); |
| auto intf = NVMeIntf::create<TestNVMeMi>(io, systemBus, mctpDev, |
| worker); |
| auto testMi = std::dynamic_pointer_cast<TestNVMeMi>( |
| std::get<std::shared_ptr<NVMeMiIntf>>(intf.getInferface())); |
| SensorData sensorData{}; |
| auto subsys = NVMeSubsystem::create(io, objectServer, systemBus, "/foo", |
| "bar", sensorData, intf, false); |
| auto nvmeDev = NVMeDevice::create(io, mctpDev, std::move(intf), subsys, |
| std::chrono::seconds(2)); |
| nvmeDev->start(); |
| |
| // Wait for initial available handler to fire |
| timer.expires_after(std::chrono::milliseconds(100)); |
| timer.async_wait(yield); |
| |
| // 1. Degrade |
| degradedHandler(mctpEp); |
| EXPECT_FALSE(testMi->isStarted); |
| |
| // 2. Available (starts 2s timer) |
| availableHandler(mctpEp); |
| EXPECT_FALSE(testMi->isStarted); |
| |
| // 3. Wait 0.5s, well within the 2s grace period |
| timer.expires_after(std::chrono::milliseconds(500)); |
| timer.async_wait(yield); |
| |
| // 4. Consecutive Available |
| // The current buggy code bypasses the grace period here and calls |
| // start() immediately. |
| availableHandler(mctpEp); |
| EXPECT_FALSE(testMi->isStarted); |
| |
| // 5. Wait for the remainder of the 2s grace period |
| timer.expires_after(std::chrono::seconds(2)); |
| timer.async_wait(yield); |
| |
| EXPECT_TRUE(testMi->isStarted); |
| |
| nvmeDev->stop(); |
| |
| timer.expires_after(std::chrono::seconds(1)); |
| timer.async_wait(yield); |
| |
| EXPECT_TRUE(testing::Mock::VerifyAndClearExpectations(mctpEp.get())); |
| EXPECT_TRUE(testing::Mock::VerifyAndClearExpectations(mctpDev.get())); |
| io.stop(); |
| }); |
| |
| io.run(); |
| } |
| // Unused, but required to link successfully |
| std::unordered_map<std::string, void*> pluginLibMap = {}; |
| |
| int main(int argc, char** argv) |
| { |
| ::testing::InitGoogleTest(&argc, argv); |
| return RUN_ALL_TESTS(); |
| } |