Files
clio/tests/unit/etl/MonitorTests.cpp
2026-05-13 12:09:06 +01:00

158 lines
5.5 KiB
C++

#include "data/Types.hpp"
#include "etl/impl/AmendmentBlockHandler.hpp"
#include "etl/impl/Monitor.hpp"
#include "util/MockBackendTestFixture.hpp"
#include "util/MockNetworkValidatedLedgers.hpp"
#include "util/MockPrometheus.hpp"
#include "util/async/context/BasicExecutionContext.hpp"
#include <boost/signals2/connection.hpp>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <optional>
#include <semaphore>
using namespace etl::impl;
using namespace data;
namespace {
constexpr auto kStartSeq = 123u;
constexpr auto kNoNewLedgerReportDelay = std::chrono::milliseconds(1u);
} // namespace
struct MonitorTests : util::prometheus::WithPrometheus, MockBackendTest {
protected:
util::async::CoroExecutionContext ctx_;
StrictMockNetworkValidatedLedgersPtr ledgers_;
testing::StrictMock<testing::MockFunction<void(uint32_t)>> actionMock_;
testing::StrictMock<testing::MockFunction<void()>> dbStalledMock_;
etl::impl::Monitor monitor_ =
etl::impl::Monitor(ctx_, backend_, ledgers_, kStartSeq, kNoNewLedgerReportDelay);
};
TEST_F(MonitorTests, ConsumesAndNotifiesForAllOutstandingSequencesAtOnce)
{
uint8_t count = 3;
LedgerRange const range(kStartSeq, kStartSeq + count - 1);
std::binary_semaphore unblock(0);
EXPECT_CALL(*ledgers_, subscribe(testing::_));
EXPECT_CALL(*backend_, hardFetchLedgerRange(testing::_)).WillOnce(testing::Return(range));
EXPECT_CALL(actionMock_, Call).Times(count).WillRepeatedly([&] {
if (--count == 0u)
unblock.release();
});
auto subscription = monitor_.subscribeToNewSequence(actionMock_.AsStdFunction());
monitor_.run(std::chrono::milliseconds{10});
unblock.acquire();
}
TEST_F(MonitorTests, NotifiesForEachSequence)
{
uint8_t count = 3;
LedgerRange range(kStartSeq, kStartSeq);
std::binary_semaphore unblock(0);
EXPECT_CALL(*ledgers_, subscribe(testing::_));
EXPECT_CALL(*backend_, hardFetchLedgerRange(testing::_)).Times(count).WillRepeatedly([&] {
auto tmp = range;
++range.maxSequence;
return tmp;
});
EXPECT_CALL(actionMock_, Call).Times(count).WillRepeatedly([&] {
if (--count == 0u)
unblock.release();
});
auto subscription = monitor_.subscribeToNewSequence(actionMock_.AsStdFunction());
monitor_.run(std::chrono::milliseconds{1});
unblock.acquire();
}
TEST_F(MonitorTests, NotifiesWhenForcedByNewSequenceAvailableFromNetwork)
{
LedgerRange const range(kStartSeq, kStartSeq);
std::binary_semaphore unblock(0);
std::function<void(uint32_t)> pusher;
EXPECT_CALL(*ledgers_, subscribe(testing::_)).WillOnce([&](auto&& subscriber) {
pusher = subscriber;
return boost::signals2::scoped_connection(); // to keep the compiler happy
});
EXPECT_CALL(*backend_, hardFetchLedgerRange(testing::_)).WillOnce(testing::Return(range));
EXPECT_CALL(actionMock_, Call).WillOnce([&] { unblock.release(); });
auto subscription = monitor_.subscribeToNewSequence(actionMock_.AsStdFunction());
monitor_.run(std::chrono::seconds{10}); // expected to be force-invoked sooner than in 10 sec
pusher(kStartSeq); // pretend network validated a new ledger
unblock.acquire();
}
TEST_F(MonitorTests, NotifiesWhenForcedByLedgerLoaded)
{
LedgerRange const range(kStartSeq, kStartSeq);
std::binary_semaphore unblock(0);
EXPECT_CALL(*ledgers_, subscribe(testing::_));
EXPECT_CALL(*backend_, hardFetchLedgerRange(testing::_)).WillOnce(testing::Return(range));
EXPECT_CALL(actionMock_, Call).WillOnce([&] { unblock.release(); });
auto subscription = monitor_.subscribeToNewSequence(actionMock_.AsStdFunction());
monitor_.run(std::chrono::seconds{10}); // expected to be force-invoked sooner than in 10 sec
monitor_.notifySequenceLoaded(kStartSeq); // notify about newly committed ledger
unblock.acquire();
}
TEST_F(MonitorTests, ResumesMonitoringFromNextSequenceAfterWriteConflict)
{
constexpr uint32_t kConflictSeq = 456u;
constexpr uint32_t kExpectedNextSeq = kConflictSeq + 1;
LedgerRange const rangeBeforeConflict(kStartSeq, kStartSeq);
LedgerRange const rangeAfterConflict(kExpectedNextSeq, kExpectedNextSeq);
std::binary_semaphore unblock(0);
EXPECT_CALL(*ledgers_, subscribe(testing::_));
{
testing::InSequence const seq; // second call will produce conflict
EXPECT_CALL(*backend_, hardFetchLedgerRange(testing::_))
.WillOnce(testing::Return(rangeBeforeConflict));
EXPECT_CALL(*backend_, hardFetchLedgerRange(testing::_))
.WillRepeatedly(testing::Return(rangeAfterConflict));
}
EXPECT_CALL(actionMock_, Call(kExpectedNextSeq)).WillOnce([&](uint32_t seq) {
EXPECT_EQ(seq, kExpectedNextSeq);
unblock.release();
});
auto subscription = monitor_.subscribeToNewSequence(actionMock_.AsStdFunction());
monitor_.run(std::chrono::nanoseconds{100});
monitor_.notifyWriteConflict(kConflictSeq);
unblock.acquire();
}
TEST_F(MonitorTests, DbStalledChannelTriggeredWhenTimeoutExceeded)
{
std::binary_semaphore unblock(0);
EXPECT_CALL(*ledgers_, subscribe(testing::_));
EXPECT_CALL(*backend_, hardFetchLedgerRange(testing::_))
.WillRepeatedly(testing::Return(std::nullopt));
EXPECT_CALL(dbStalledMock_, Call()).WillOnce([&]() { unblock.release(); });
auto subscription = monitor_.subscribeToDbStalled(dbStalledMock_.AsStdFunction());
monitor_.run(std::chrono::nanoseconds{100});
unblock.acquire();
}