blob: 0065e8e77c81871eb6db0872f199293d9a42ff0f [file]
#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>(&degradedHandler),
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>(&degradedHandler),
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>(&degradedHandler),
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();
}