mirror of
https://github.com/XRPLF/clio.git
synced 2025-12-06 17:27:58 +00:00
@@ -21,10 +21,14 @@
|
||||
|
||||
#include "util/LoggerFixtures.hpp"
|
||||
|
||||
#include <boost/asio/executor_work_guard.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/io_service.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
@@ -94,6 +98,38 @@ struct SyncAsioContextTest : virtual public NoLoggerFixture {
|
||||
runContext();
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void
|
||||
runSpawnWithTimeout(std::chrono::steady_clock::duration timeout, F&& f, bool allowMockLeak = false)
|
||||
{
|
||||
using namespace boost::asio;
|
||||
|
||||
boost::asio::io_context timerCtx;
|
||||
steady_timer timer{timerCtx, timeout};
|
||||
spawn(timerCtx, [this, &timer](yield_context yield) {
|
||||
boost::system::error_code errorCode;
|
||||
timer.async_wait(yield[errorCode]);
|
||||
ctx_.stop();
|
||||
EXPECT_TRUE(false) << "Test timed out";
|
||||
});
|
||||
std::thread timerThread{[&timerCtx]() { timerCtx.run(); }};
|
||||
|
||||
testing::MockFunction<void()> call;
|
||||
if (allowMockLeak)
|
||||
testing::Mock::AllowLeak(&call);
|
||||
|
||||
spawn(ctx_, [&](yield_context yield) {
|
||||
f(yield);
|
||||
call.Call();
|
||||
});
|
||||
|
||||
EXPECT_CALL(call, Call());
|
||||
runContext();
|
||||
|
||||
timerCtx.stop();
|
||||
timerThread.join();
|
||||
}
|
||||
|
||||
void
|
||||
runContext()
|
||||
{
|
||||
@@ -108,6 +144,15 @@ struct SyncAsioContextTest : virtual public NoLoggerFixture {
|
||||
ctx_.reset();
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
static void
|
||||
runSyncOperation(F&& f)
|
||||
{
|
||||
boost::asio::io_service ioc;
|
||||
boost::asio::spawn(ioc, f);
|
||||
ioc.run();
|
||||
}
|
||||
|
||||
protected:
|
||||
boost::asio::io_context ctx_;
|
||||
};
|
||||
|
||||
@@ -211,6 +211,8 @@ struct MockBackend : public BackendInterface {
|
||||
|
||||
MOCK_METHOD(void, doWriteLedgerObject, (std::string&&, std::uint32_t const, std::string&&), (override));
|
||||
|
||||
MOCK_METHOD(void, waitForWritesToFinish, (), (override));
|
||||
|
||||
MOCK_METHOD(bool, doFinishWrites, (), (override));
|
||||
|
||||
MOCK_METHOD(void, writeMPTHolders, (std::vector<MPTHolderData> const&), (override));
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include "etl/Source.hpp"
|
||||
#include "feed/SubscriptionManagerInterface.hpp"
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "util/newconfig/ConfigDefinition.hpp"
|
||||
#include "util/newconfig/ObjectView.hpp"
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
@@ -49,6 +48,7 @@
|
||||
|
||||
struct MockSource : etl::SourceBase {
|
||||
MOCK_METHOD(void, run, (), (override));
|
||||
MOCK_METHOD(void, stop, (boost::asio::yield_context), (override));
|
||||
MOCK_METHOD(bool, isConnected, (), (const, override));
|
||||
MOCK_METHOD(void, setForwarding, (bool), (override));
|
||||
MOCK_METHOD(boost::json::object, toJson, (), (const, override));
|
||||
@@ -89,6 +89,12 @@ public:
|
||||
mock_->run();
|
||||
}
|
||||
|
||||
void
|
||||
stop(boost::asio::yield_context yield) override
|
||||
{
|
||||
mock_->stop(yield);
|
||||
}
|
||||
|
||||
bool
|
||||
isConnected() const override
|
||||
{
|
||||
|
||||
@@ -102,6 +102,8 @@ struct MockSubscriptionManager : feed::SubscriptionManagerInterface {
|
||||
MOCK_METHOD(void, unsubProposedTransactions, (feed::SubscriberSharedPtr const&), (override));
|
||||
|
||||
MOCK_METHOD(boost::json::object, report, (), (const, override));
|
||||
|
||||
MOCK_METHOD(void, stop, (), (override));
|
||||
};
|
||||
|
||||
template <template <typename> typename MockType = ::testing::NiceMock>
|
||||
|
||||
@@ -54,7 +54,7 @@ struct MockConnectionImpl : web::ng::Connection {
|
||||
using ReceiveReturnType = std::expected<web::ng::Request, web::ng::Error>;
|
||||
MOCK_METHOD(ReceiveReturnType, receive, (boost::asio::yield_context), (override));
|
||||
|
||||
MOCK_METHOD(void, close, (boost::asio::yield_context));
|
||||
MOCK_METHOD(void, close, (boost::asio::yield_context), (override));
|
||||
};
|
||||
|
||||
using MockConnection = testing::NiceMock<MockConnectionImpl>;
|
||||
|
||||
@@ -57,7 +57,7 @@ struct MockHttpConnectionImpl : web::ng::impl::UpgradableConnection {
|
||||
using ReceiveReturnType = std::expected<web::ng::Request, web::ng::Error>;
|
||||
MOCK_METHOD(ReceiveReturnType, receive, (boost::asio::yield_context), (override));
|
||||
|
||||
MOCK_METHOD(void, close, (boost::asio::yield_context));
|
||||
MOCK_METHOD(void, close, (boost::asio::yield_context), (override));
|
||||
|
||||
using IsUpgradeRequestedReturnType = std::expected<bool, web::ng::Error>;
|
||||
MOCK_METHOD(IsUpgradeRequestedReturnType, isUpgradeRequested, (boost::asio::yield_context), (override));
|
||||
|
||||
@@ -47,7 +47,7 @@ struct MockWsConnectionImpl : web::ng::impl::WsConnectionBase {
|
||||
using ReceiveReturnType = std::expected<web::ng::Request, web::ng::Error>;
|
||||
MOCK_METHOD(ReceiveReturnType, receive, (boost::asio::yield_context), (override));
|
||||
|
||||
MOCK_METHOD(void, close, (boost::asio::yield_context));
|
||||
MOCK_METHOD(void, close, (boost::asio::yield_context), (override));
|
||||
|
||||
using SendBufferReturnType = std::optional<web::ng::Error>;
|
||||
MOCK_METHOD(SendBufferReturnType, sendBuffer, (boost::asio::const_buffer, boost::asio::yield_context), (override));
|
||||
|
||||
@@ -5,6 +5,7 @@ target_sources(
|
||||
PRIVATE # Common
|
||||
ConfigTests.cpp
|
||||
app/CliArgsTests.cpp
|
||||
app/StopperTests.cpp
|
||||
app/VerifyConfigTests.cpp
|
||||
app/WebHandlersTests.cpp
|
||||
data/AmendmentCenterTests.cpp
|
||||
@@ -151,6 +152,7 @@ target_sources(
|
||||
util/RepeatTests.cpp
|
||||
util/ResponseExpirationCacheTests.cpp
|
||||
util/SignalsHandlerTests.cpp
|
||||
util/StopHelperTests.cpp
|
||||
util/TimeUtilsTests.cpp
|
||||
util/TxUtilTests.cpp
|
||||
util/WithTimeout.cpp
|
||||
|
||||
116
tests/unit/app/StopperTests.cpp
Normal file
116
tests/unit/app/StopperTests.cpp
Normal file
@@ -0,0 +1,116 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2025, the clio developers.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
#include "app/Stopper.hpp"
|
||||
#include "etl/ETLService.hpp"
|
||||
#include "etl/LoadBalancer.hpp"
|
||||
#include "util/AsioContextTestFixture.hpp"
|
||||
#include "util/LoggerFixtures.hpp"
|
||||
#include "util/MockBackend.hpp"
|
||||
#include "util/MockPrometheus.hpp"
|
||||
#include "util/MockSubscriptionManager.hpp"
|
||||
#include "util/newconfig/ConfigDefinition.hpp"
|
||||
#include "web/ng/Server.hpp"
|
||||
|
||||
#include <boost/asio/executor_work_guard.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <thread>
|
||||
|
||||
using namespace app;
|
||||
|
||||
struct StopperTest : NoLoggerFixture {
|
||||
protected:
|
||||
// Order here is important, stopper_ should die before mockCallback_, otherwise UB
|
||||
testing::StrictMock<testing::MockFunction<void(boost::asio::yield_context)>> mockCallback_;
|
||||
Stopper stopper_;
|
||||
};
|
||||
|
||||
TEST_F(StopperTest, stopCallsCallback)
|
||||
{
|
||||
stopper_.setOnStop(mockCallback_.AsStdFunction());
|
||||
EXPECT_CALL(mockCallback_, Call);
|
||||
stopper_.stop();
|
||||
}
|
||||
|
||||
TEST_F(StopperTest, stopCalledMultipleTimes)
|
||||
{
|
||||
stopper_.setOnStop(mockCallback_.AsStdFunction());
|
||||
EXPECT_CALL(mockCallback_, Call);
|
||||
stopper_.stop();
|
||||
stopper_.stop();
|
||||
stopper_.stop();
|
||||
stopper_.stop();
|
||||
}
|
||||
|
||||
struct StopperMakeCallbackTest : util::prometheus::WithPrometheus, SyncAsioContextTest {
|
||||
struct ServerMock : web::ng::ServerTag {
|
||||
MOCK_METHOD(void, stop, (boost::asio::yield_context), ());
|
||||
};
|
||||
struct LoadBalancerMock : etl::LoadBalancerTag {
|
||||
MOCK_METHOD(void, stop, (boost::asio::yield_context), ());
|
||||
};
|
||||
struct ETLServiceMock : etl::ETLServiceTag {
|
||||
MOCK_METHOD(void, stop, (), ());
|
||||
};
|
||||
|
||||
protected:
|
||||
testing::StrictMock<ServerMock> serverMock_;
|
||||
testing::StrictMock<LoadBalancerMock> loadBalancerMock_;
|
||||
testing::StrictMock<ETLServiceMock> etlServiceMock_;
|
||||
testing::StrictMock<MockSubscriptionManager> subscriptionManagerMock_;
|
||||
testing::StrictMock<MockBackend> backendMock_{util::config::ClioConfigDefinition{}};
|
||||
boost::asio::io_context ioContextToStop_;
|
||||
|
||||
bool
|
||||
isContextStopped() const
|
||||
{
|
||||
return ioContextToStop_.stopped();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(StopperMakeCallbackTest, makeCallbackTest)
|
||||
{
|
||||
auto contextWorkGuard = boost::asio::make_work_guard(ioContextToStop_);
|
||||
std::thread t{[this]() { ioContextToStop_.run(); }};
|
||||
|
||||
auto callback = Stopper::makeOnStopCallback(
|
||||
serverMock_, loadBalancerMock_, etlServiceMock_, subscriptionManagerMock_, backendMock_, ioContextToStop_
|
||||
);
|
||||
|
||||
testing::Sequence s1, s2;
|
||||
EXPECT_CALL(serverMock_, stop).InSequence(s1).WillOnce([this]() { EXPECT_FALSE(isContextStopped()); });
|
||||
EXPECT_CALL(loadBalancerMock_, stop).InSequence(s2).WillOnce([this]() { EXPECT_FALSE(isContextStopped()); });
|
||||
EXPECT_CALL(etlServiceMock_, stop).InSequence(s1, s2).WillOnce([this]() { EXPECT_FALSE(isContextStopped()); });
|
||||
EXPECT_CALL(subscriptionManagerMock_, stop).InSequence(s1, s2).WillOnce([this]() {
|
||||
EXPECT_FALSE(isContextStopped());
|
||||
});
|
||||
EXPECT_CALL(backendMock_, waitForWritesToFinish).InSequence(s1, s2).WillOnce([this]() {
|
||||
EXPECT_FALSE(isContextStopped());
|
||||
});
|
||||
|
||||
runSpawn([&](boost::asio::yield_context yield) {
|
||||
callback(yield);
|
||||
EXPECT_TRUE(isContextStopped());
|
||||
});
|
||||
|
||||
t.join();
|
||||
}
|
||||
@@ -318,6 +318,15 @@ TEST_F(LoadBalancerOnConnectHookTests, sourcesConnect_BothSourcesAreNotConnected
|
||||
sourceFactory_.callbacksAt(0).onConnect();
|
||||
}
|
||||
|
||||
struct LoadBalancerStopTests : LoadBalancerOnConnectHookTests, SyncAsioContextTest {};
|
||||
|
||||
TEST_F(LoadBalancerStopTests, stopCallsSourcesStop)
|
||||
{
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(0), stop);
|
||||
EXPECT_CALL(sourceFactory_.sourceAt(1), stop);
|
||||
runSyncOperation([this](boost::asio::yield_context yield) { loadBalancer_->stop(yield); });
|
||||
}
|
||||
|
||||
struct LoadBalancerOnDisconnectHookTests : LoadBalancerOnConnectHookTests {
|
||||
LoadBalancerOnDisconnectHookTests()
|
||||
{
|
||||
|
||||
@@ -58,7 +58,7 @@ struct SubscriptionSourceMock {
|
||||
MOCK_METHOD(void, setForwarding, (bool));
|
||||
MOCK_METHOD(std::chrono::steady_clock::time_point, lastMessageTime, (), (const));
|
||||
MOCK_METHOD(std::string, validatedRange, (), (const));
|
||||
MOCK_METHOD(void, stop, ());
|
||||
MOCK_METHOD(void, stop, (boost::asio::yield_context));
|
||||
};
|
||||
|
||||
struct ForwardingSourceMock {
|
||||
@@ -103,6 +103,14 @@ TEST_F(SourceImplTest, run)
|
||||
source_.run();
|
||||
}
|
||||
|
||||
TEST_F(SourceImplTest, stop)
|
||||
{
|
||||
EXPECT_CALL(*subscriptionSourceMock_, stop);
|
||||
boost::asio::io_context ctx;
|
||||
boost::asio::spawn(ctx, [&](boost::asio::yield_context yield) { source_.stop(yield); });
|
||||
ctx.run();
|
||||
}
|
||||
|
||||
TEST_F(SourceImplTest, isConnected)
|
||||
{
|
||||
EXPECT_CALL(*subscriptionSourceMock_, isConnected()).WillOnce(testing::Return(true));
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
//==============================================================================
|
||||
|
||||
#include "etl/impl/SubscriptionSource.hpp"
|
||||
#include "util/LoggerFixtures.hpp"
|
||||
#include "util/AsioContextTestFixture.hpp"
|
||||
#include "util/MockNetworkValidatedLedgers.hpp"
|
||||
#include "util/MockPrometheus.hpp"
|
||||
#include "util/MockSubscriptionManager.hpp"
|
||||
@@ -45,12 +45,18 @@ using namespace etl::impl;
|
||||
using testing::MockFunction;
|
||||
using testing::StrictMock;
|
||||
|
||||
struct SubscriptionSourceConnectionTestsBase : public NoLoggerFixture {
|
||||
struct SubscriptionSourceConnectionTestsBase : SyncAsioContextTest {
|
||||
SubscriptionSourceConnectionTestsBase()
|
||||
{
|
||||
subscriptionSource_.run();
|
||||
}
|
||||
|
||||
void
|
||||
stopSubscriptionSource()
|
||||
{
|
||||
boost::asio::spawn(ctx_, [this](auto&& yield) { subscriptionSource_.stop(yield); });
|
||||
}
|
||||
|
||||
[[maybe_unused]] TestWsConnection
|
||||
serverConnection(boost::asio::yield_context yield)
|
||||
{
|
||||
@@ -73,8 +79,7 @@ struct SubscriptionSourceConnectionTestsBase : public NoLoggerFixture {
|
||||
}
|
||||
|
||||
protected:
|
||||
boost::asio::io_context ioContext_;
|
||||
TestWsServer wsServer_{ioContext_, "0.0.0.0"};
|
||||
TestWsServer wsServer_{ctx_, "0.0.0.0"};
|
||||
|
||||
StrictMockNetworkValidatedLedgersPtr networkValidatedLedgers_;
|
||||
StrictMockSubscriptionManagerSharedPtr subscriptionManager_;
|
||||
@@ -84,7 +89,7 @@ protected:
|
||||
StrictMock<MockFunction<void()>> onLedgerClosedHook_;
|
||||
|
||||
SubscriptionSource subscriptionSource_{
|
||||
ioContext_,
|
||||
ctx_,
|
||||
"127.0.0.1",
|
||||
wsServer_.port(),
|
||||
networkValidatedLedgers_,
|
||||
@@ -101,43 +106,43 @@ struct SubscriptionSourceConnectionTests : util::prometheus::WithPrometheus, Sub
|
||||
|
||||
TEST_F(SubscriptionSourceConnectionTests, ConnectionFailed)
|
||||
{
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
ioContext_.run();
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(SubscriptionSourceConnectionTests, ConnectionFailed_Retry_ConnectionFailed)
|
||||
{
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([]() {}).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
ioContext_.run();
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([]() {}).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(SubscriptionSourceConnectionTests, ReadError)
|
||||
{
|
||||
boost::asio::spawn(ioContext_, [this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [this](boost::asio::yield_context yield) {
|
||||
auto connection = serverConnection(yield);
|
||||
connection.close(yield);
|
||||
});
|
||||
|
||||
EXPECT_CALL(onConnectHook_, Call());
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
ioContext_.run();
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(SubscriptionSourceConnectionTests, ReadTimeout)
|
||||
{
|
||||
boost::asio::spawn(ioContext_, [this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [this](boost::asio::yield_context yield) {
|
||||
auto connection = serverConnection(yield);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds{10});
|
||||
});
|
||||
|
||||
EXPECT_CALL(onConnectHook_, Call());
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
ioContext_.run();
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(SubscriptionSourceConnectionTests, ReadError_Reconnect)
|
||||
{
|
||||
boost::asio::spawn(ioContext_, [this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [this](boost::asio::yield_context yield) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
auto connection = serverConnection(yield);
|
||||
connection.close(yield);
|
||||
@@ -145,14 +150,14 @@ TEST_F(SubscriptionSourceConnectionTests, ReadError_Reconnect)
|
||||
});
|
||||
|
||||
EXPECT_CALL(onConnectHook_, Call()).Times(2);
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([]() {}).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
ioContext_.run();
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([]() {}).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(SubscriptionSourceConnectionTests, IsConnected)
|
||||
{
|
||||
EXPECT_FALSE(subscriptionSource_.isConnected());
|
||||
boost::asio::spawn(ioContext_, [this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [this](boost::asio::yield_context yield) {
|
||||
auto connection = serverConnection(yield);
|
||||
connection.close(yield);
|
||||
});
|
||||
@@ -160,9 +165,9 @@ TEST_F(SubscriptionSourceConnectionTests, IsConnected)
|
||||
EXPECT_CALL(onConnectHook_, Call()).WillOnce([this]() { EXPECT_TRUE(subscriptionSource_.isConnected()); });
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() {
|
||||
EXPECT_FALSE(subscriptionSource_.isConnected());
|
||||
subscriptionSource_.stop();
|
||||
stopSubscriptionSource();
|
||||
});
|
||||
ioContext_.run();
|
||||
runContext();
|
||||
}
|
||||
|
||||
struct SubscriptionSourceReadTestsBase : public SubscriptionSourceConnectionTestsBase {
|
||||
@@ -180,7 +185,7 @@ struct SubscriptionSourceReadTests : util::prometheus::WithPrometheus, Subscript
|
||||
|
||||
TEST_F(SubscriptionSourceReadTests, GotWrongMessage_Reconnect)
|
||||
{
|
||||
boost::asio::spawn(ioContext_, [this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [this](boost::asio::yield_context yield) {
|
||||
auto connection = connectAndSendMessage("something", yield);
|
||||
// We have to schedule receiving to receive close frame and boost will handle it automatically
|
||||
connection.receive(yield);
|
||||
@@ -188,38 +193,38 @@ TEST_F(SubscriptionSourceReadTests, GotWrongMessage_Reconnect)
|
||||
});
|
||||
|
||||
EXPECT_CALL(onConnectHook_, Call()).Times(2);
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([]() {}).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
ioContext_.run();
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([]() {}).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(SubscriptionSourceReadTests, GotResult)
|
||||
{
|
||||
boost::asio::spawn(ioContext_, [this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [this](boost::asio::yield_context yield) {
|
||||
auto connection = connectAndSendMessage(R"({"result":{})", yield);
|
||||
connection.close(yield);
|
||||
});
|
||||
|
||||
EXPECT_CALL(onConnectHook_, Call());
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
ioContext_.run();
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(SubscriptionSourceReadTests, GotResultWithLedgerIndex)
|
||||
{
|
||||
boost::asio::spawn(ioContext_, [this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [this](boost::asio::yield_context yield) {
|
||||
auto connection = connectAndSendMessage(R"({"result":{"ledger_index":123}})", yield);
|
||||
connection.close(yield);
|
||||
});
|
||||
|
||||
EXPECT_CALL(onConnectHook_, Call());
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
EXPECT_CALL(*networkValidatedLedgers_, push(123));
|
||||
ioContext_.run();
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(SubscriptionSourceReadTests, GotResultWithLedgerIndexAsString_Reconnect)
|
||||
{
|
||||
boost::asio::spawn(ioContext_, [this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [this](boost::asio::yield_context yield) {
|
||||
auto connection = connectAndSendMessage(R"({"result":{"ledger_index":"123"}})", yield);
|
||||
// We have to schedule receiving to receive close frame and boost will handle it automatically
|
||||
connection.receive(yield);
|
||||
@@ -227,13 +232,13 @@ TEST_F(SubscriptionSourceReadTests, GotResultWithLedgerIndexAsString_Reconnect)
|
||||
});
|
||||
|
||||
EXPECT_CALL(onConnectHook_, Call()).Times(2);
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([]() {}).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
ioContext_.run();
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([]() {}).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(SubscriptionSourceReadTests, GotResultWithValidatedLedgersAsNumber_Reconnect)
|
||||
{
|
||||
boost::asio::spawn(ioContext_, [this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [this](boost::asio::yield_context yield) {
|
||||
auto connection = connectAndSendMessage(R"({"result":{"validated_ledgers":123}})", yield);
|
||||
// We have to schedule receiving to receive close frame and boost will handle it automatically
|
||||
connection.receive(yield);
|
||||
@@ -241,8 +246,8 @@ TEST_F(SubscriptionSourceReadTests, GotResultWithValidatedLedgersAsNumber_Reconn
|
||||
});
|
||||
|
||||
EXPECT_CALL(onConnectHook_, Call()).Times(2);
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([]() {}).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
ioContext_.run();
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([]() {}).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(SubscriptionSourceReadTests, GotResultWithValidatedLedgers)
|
||||
@@ -257,14 +262,14 @@ TEST_F(SubscriptionSourceReadTests, GotResultWithValidatedLedgers)
|
||||
EXPECT_FALSE(subscriptionSource_.hasLedger(789));
|
||||
EXPECT_FALSE(subscriptionSource_.hasLedger(790));
|
||||
|
||||
boost::asio::spawn(ioContext_, [this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [this](boost::asio::yield_context yield) {
|
||||
auto connection = connectAndSendMessage(R"({"result":{"validated_ledgers":"123-456,789,32"}})", yield);
|
||||
connection.close(yield);
|
||||
});
|
||||
|
||||
EXPECT_CALL(onConnectHook_, Call());
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
ioContext_.run();
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
runContext();
|
||||
|
||||
EXPECT_TRUE(subscriptionSource_.hasLedger(123));
|
||||
EXPECT_TRUE(subscriptionSource_.hasLedger(124));
|
||||
@@ -281,7 +286,7 @@ TEST_F(SubscriptionSourceReadTests, GotResultWithValidatedLedgers)
|
||||
|
||||
TEST_F(SubscriptionSourceReadTests, GotResultWithValidatedLedgersWrongValue_Reconnect)
|
||||
{
|
||||
boost::asio::spawn(ioContext_, [this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [this](boost::asio::yield_context yield) {
|
||||
auto connection = connectAndSendMessage(R"({"result":{"validated_ledgers":"123-456-789,32"}})", yield);
|
||||
// We have to schedule receiving to receive close frame and boost will handle it automatically
|
||||
connection.receive(yield);
|
||||
@@ -289,8 +294,8 @@ TEST_F(SubscriptionSourceReadTests, GotResultWithValidatedLedgersWrongValue_Reco
|
||||
});
|
||||
|
||||
EXPECT_CALL(onConnectHook_, Call()).Times(2);
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([]() {}).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
ioContext_.run();
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([]() {}).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(SubscriptionSourceReadTests, GotResultWithLedgerIndexAndValidatedLedgers)
|
||||
@@ -301,15 +306,15 @@ TEST_F(SubscriptionSourceReadTests, GotResultWithLedgerIndexAndValidatedLedgers)
|
||||
EXPECT_FALSE(subscriptionSource_.hasLedger(3));
|
||||
EXPECT_FALSE(subscriptionSource_.hasLedger(4));
|
||||
|
||||
boost::asio::spawn(ioContext_, [this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [this](boost::asio::yield_context yield) {
|
||||
auto connection = connectAndSendMessage(R"({"result":{"ledger_index":123,"validated_ledgers":"1-3"}})", yield);
|
||||
connection.close(yield);
|
||||
});
|
||||
|
||||
EXPECT_CALL(onConnectHook_, Call());
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
EXPECT_CALL(*networkValidatedLedgers_, push(123));
|
||||
ioContext_.run();
|
||||
runContext();
|
||||
|
||||
EXPECT_EQ(subscriptionSource_.validatedRange(), "1-3");
|
||||
EXPECT_FALSE(subscriptionSource_.hasLedger(0));
|
||||
@@ -321,21 +326,21 @@ TEST_F(SubscriptionSourceReadTests, GotResultWithLedgerIndexAndValidatedLedgers)
|
||||
|
||||
TEST_F(SubscriptionSourceReadTests, GotLedgerClosed)
|
||||
{
|
||||
boost::asio::spawn(ioContext_, [this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [this](boost::asio::yield_context yield) {
|
||||
auto connection = connectAndSendMessage(R"({"type":"ledgerClosed"})", yield);
|
||||
connection.close(yield);
|
||||
});
|
||||
|
||||
EXPECT_CALL(onConnectHook_, Call());
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
ioContext_.run();
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(SubscriptionSourceReadTests, GotLedgerClosedForwardingIsSet)
|
||||
{
|
||||
subscriptionSource_.setForwarding(true);
|
||||
|
||||
boost::asio::spawn(ioContext_, [this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [this](boost::asio::yield_context yield) {
|
||||
auto connection = connectAndSendMessage(R"({"type": "ledgerClosed"})", yield);
|
||||
connection.close(yield);
|
||||
});
|
||||
@@ -344,27 +349,27 @@ TEST_F(SubscriptionSourceReadTests, GotLedgerClosedForwardingIsSet)
|
||||
EXPECT_CALL(onLedgerClosedHook_, Call());
|
||||
EXPECT_CALL(onDisconnectHook_, Call(true)).WillOnce([this]() {
|
||||
EXPECT_FALSE(subscriptionSource_.isForwarding());
|
||||
subscriptionSource_.stop();
|
||||
stopSubscriptionSource();
|
||||
});
|
||||
ioContext_.run();
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(SubscriptionSourceReadTests, GotLedgerClosedWithLedgerIndex)
|
||||
{
|
||||
boost::asio::spawn(ioContext_, [this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [this](boost::asio::yield_context yield) {
|
||||
auto connection = connectAndSendMessage(R"({"type": "ledgerClosed","ledger_index": 123})", yield);
|
||||
connection.close(yield);
|
||||
});
|
||||
|
||||
EXPECT_CALL(onConnectHook_, Call());
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
EXPECT_CALL(*networkValidatedLedgers_, push(123));
|
||||
ioContext_.run();
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(SubscriptionSourceReadTests, GotLedgerClosedWithLedgerIndexAsString_Reconnect)
|
||||
{
|
||||
boost::asio::spawn(ioContext_, [this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [this](boost::asio::yield_context yield) {
|
||||
auto connection = connectAndSendMessage(R"({"type":"ledgerClosed","ledger_index":"123"}})", yield);
|
||||
// We have to schedule receiving to receive close frame and boost will handle it automatically
|
||||
connection.receive(yield);
|
||||
@@ -372,13 +377,13 @@ TEST_F(SubscriptionSourceReadTests, GotLedgerClosedWithLedgerIndexAsString_Recon
|
||||
});
|
||||
|
||||
EXPECT_CALL(onConnectHook_, Call()).Times(2);
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([]() {}).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
ioContext_.run();
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([]() {}).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(SubscriptionSourceReadTests, GorLedgerClosedWithValidatedLedgersAsNumber_Reconnect)
|
||||
{
|
||||
boost::asio::spawn(ioContext_, [this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [this](boost::asio::yield_context yield) {
|
||||
auto connection = connectAndSendMessage(R"({"type":"ledgerClosed","validated_ledgers":123})", yield);
|
||||
// We have to schedule receiving to receive close frame and boost will handle it automatically
|
||||
connection.receive(yield);
|
||||
@@ -386,8 +391,8 @@ TEST_F(SubscriptionSourceReadTests, GorLedgerClosedWithValidatedLedgersAsNumber_
|
||||
});
|
||||
|
||||
EXPECT_CALL(onConnectHook_, Call()).Times(2);
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([]() {}).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
ioContext_.run();
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([]() {}).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(SubscriptionSourceReadTests, GotLedgerClosedWithValidatedLedgers)
|
||||
@@ -397,14 +402,14 @@ TEST_F(SubscriptionSourceReadTests, GotLedgerClosedWithValidatedLedgers)
|
||||
EXPECT_FALSE(subscriptionSource_.hasLedger(2));
|
||||
EXPECT_FALSE(subscriptionSource_.hasLedger(3));
|
||||
|
||||
boost::asio::spawn(ioContext_, [this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [this](boost::asio::yield_context yield) {
|
||||
auto connection = connectAndSendMessage(R"({"type":"ledgerClosed","validated_ledgers":"1-2"})", yield);
|
||||
connection.close(yield);
|
||||
});
|
||||
|
||||
EXPECT_CALL(onConnectHook_, Call());
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
ioContext_.run();
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
runContext();
|
||||
|
||||
EXPECT_FALSE(subscriptionSource_.hasLedger(0));
|
||||
EXPECT_TRUE(subscriptionSource_.hasLedger(1));
|
||||
@@ -420,16 +425,16 @@ TEST_F(SubscriptionSourceReadTests, GotLedgerClosedWithLedgerIndexAndValidatedLe
|
||||
EXPECT_FALSE(subscriptionSource_.hasLedger(2));
|
||||
EXPECT_FALSE(subscriptionSource_.hasLedger(3));
|
||||
|
||||
boost::asio::spawn(ioContext_, [this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [this](boost::asio::yield_context yield) {
|
||||
auto connection =
|
||||
connectAndSendMessage(R"({"type":"ledgerClosed","ledger_index":123,"validated_ledgers":"1-2"})", yield);
|
||||
connection.close(yield);
|
||||
});
|
||||
|
||||
EXPECT_CALL(onConnectHook_, Call());
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
EXPECT_CALL(*networkValidatedLedgers_, push(123));
|
||||
ioContext_.run();
|
||||
runContext();
|
||||
|
||||
EXPECT_FALSE(subscriptionSource_.hasLedger(0));
|
||||
EXPECT_TRUE(subscriptionSource_.hasLedger(1));
|
||||
@@ -440,14 +445,14 @@ TEST_F(SubscriptionSourceReadTests, GotLedgerClosedWithLedgerIndexAndValidatedLe
|
||||
|
||||
TEST_F(SubscriptionSourceReadTests, GotTransactionIsForwardingFalse)
|
||||
{
|
||||
boost::asio::spawn(ioContext_, [this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [this](boost::asio::yield_context yield) {
|
||||
auto connection = connectAndSendMessage(R"({"transaction":"some_transaction_data"})", yield);
|
||||
connection.close(yield);
|
||||
});
|
||||
|
||||
EXPECT_CALL(onConnectHook_, Call());
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
ioContext_.run();
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(SubscriptionSourceReadTests, GotTransactionIsForwardingTrue)
|
||||
@@ -455,15 +460,15 @@ TEST_F(SubscriptionSourceReadTests, GotTransactionIsForwardingTrue)
|
||||
subscriptionSource_.setForwarding(true);
|
||||
boost::json::object const message = {{"transaction", "some_transaction_data"}};
|
||||
|
||||
boost::asio::spawn(ioContext_, [&message, this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [&message, this](boost::asio::yield_context yield) {
|
||||
auto connection = connectAndSendMessage(boost::json::serialize(message), yield);
|
||||
connection.close(yield);
|
||||
});
|
||||
|
||||
EXPECT_CALL(onConnectHook_, Call());
|
||||
EXPECT_CALL(onDisconnectHook_, Call(true)).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
EXPECT_CALL(onDisconnectHook_, Call(true)).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
EXPECT_CALL(*subscriptionManager_, forwardProposedTransaction(message));
|
||||
ioContext_.run();
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(SubscriptionSourceReadTests, GotTransactionWithMetaIsForwardingFalse)
|
||||
@@ -471,27 +476,27 @@ TEST_F(SubscriptionSourceReadTests, GotTransactionWithMetaIsForwardingFalse)
|
||||
subscriptionSource_.setForwarding(true);
|
||||
boost::json::object const message = {{"transaction", "some_transaction_data"}, {"meta", "some_meta_data"}};
|
||||
|
||||
boost::asio::spawn(ioContext_, [&message, this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [&message, this](boost::asio::yield_context yield) {
|
||||
auto connection = connectAndSendMessage(boost::json::serialize(message), yield);
|
||||
connection.close(yield);
|
||||
});
|
||||
|
||||
EXPECT_CALL(onConnectHook_, Call());
|
||||
EXPECT_CALL(onDisconnectHook_, Call(true)).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
EXPECT_CALL(onDisconnectHook_, Call(true)).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
EXPECT_CALL(*subscriptionManager_, forwardProposedTransaction(message)).Times(0);
|
||||
ioContext_.run();
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(SubscriptionSourceReadTests, GotValidationReceivedIsForwardingFalse)
|
||||
{
|
||||
boost::asio::spawn(ioContext_, [this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [this](boost::asio::yield_context yield) {
|
||||
auto connection = connectAndSendMessage(R"({"type":"validationReceived"})", yield);
|
||||
connection.close(yield);
|
||||
});
|
||||
|
||||
EXPECT_CALL(onConnectHook_, Call());
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
ioContext_.run();
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(SubscriptionSourceReadTests, GotValidationReceivedIsForwardingTrue)
|
||||
@@ -499,27 +504,27 @@ TEST_F(SubscriptionSourceReadTests, GotValidationReceivedIsForwardingTrue)
|
||||
subscriptionSource_.setForwarding(true);
|
||||
boost::json::object const message = {{"type", "validationReceived"}};
|
||||
|
||||
boost::asio::spawn(ioContext_, [&message, this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [&message, this](boost::asio::yield_context yield) {
|
||||
auto connection = connectAndSendMessage(boost::json::serialize(message), yield);
|
||||
connection.close(yield);
|
||||
});
|
||||
|
||||
EXPECT_CALL(onConnectHook_, Call());
|
||||
EXPECT_CALL(onDisconnectHook_, Call(true)).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
EXPECT_CALL(onDisconnectHook_, Call(true)).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
EXPECT_CALL(*subscriptionManager_, forwardValidation(message));
|
||||
ioContext_.run();
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(SubscriptionSourceReadTests, GotManiefstReceivedIsForwardingFalse)
|
||||
{
|
||||
boost::asio::spawn(ioContext_, [this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [this](boost::asio::yield_context yield) {
|
||||
auto connection = connectAndSendMessage(R"({"type":"manifestReceived"})", yield);
|
||||
connection.close(yield);
|
||||
});
|
||||
|
||||
EXPECT_CALL(onConnectHook_, Call());
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
ioContext_.run();
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(SubscriptionSourceReadTests, GotManifestReceivedIsForwardingTrue)
|
||||
@@ -527,27 +532,27 @@ TEST_F(SubscriptionSourceReadTests, GotManifestReceivedIsForwardingTrue)
|
||||
subscriptionSource_.setForwarding(true);
|
||||
boost::json::object const message = {{"type", "manifestReceived"}};
|
||||
|
||||
boost::asio::spawn(ioContext_, [&message, this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [&message, this](boost::asio::yield_context yield) {
|
||||
auto connection = connectAndSendMessage(boost::json::serialize(message), yield);
|
||||
connection.close(yield);
|
||||
});
|
||||
|
||||
EXPECT_CALL(onConnectHook_, Call());
|
||||
EXPECT_CALL(onDisconnectHook_, Call(true)).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
EXPECT_CALL(onDisconnectHook_, Call(true)).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
EXPECT_CALL(*subscriptionManager_, forwardManifest(message));
|
||||
ioContext_.run();
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(SubscriptionSourceReadTests, LastMessageTime)
|
||||
{
|
||||
boost::asio::spawn(ioContext_, [this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [this](boost::asio::yield_context yield) {
|
||||
auto connection = connectAndSendMessage("some_message", yield);
|
||||
connection.close(yield);
|
||||
});
|
||||
|
||||
EXPECT_CALL(onConnectHook_, Call());
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
ioContext_.run();
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
runContext();
|
||||
|
||||
auto const actualLastTimeMessage = subscriptionSource_.lastMessageTime();
|
||||
auto const now = std::chrono::steady_clock::now();
|
||||
@@ -563,18 +568,18 @@ TEST_F(SubscriptionSourcePrometheusCounterTests, LastMessageTime)
|
||||
auto& lastMessageTimeMock = makeMock<util::prometheus::GaugeInt>(
|
||||
"subscription_source_last_message_time", fmt::format("{{source=\"127.0.0.1:{}\"}}", wsServer_.port())
|
||||
);
|
||||
boost::asio::spawn(ioContext_, [this](boost::asio::yield_context yield) {
|
||||
boost::asio::spawn(ctx_, [this](boost::asio::yield_context yield) {
|
||||
auto connection = connectAndSendMessage("some_message", yield);
|
||||
connection.close(yield);
|
||||
});
|
||||
|
||||
EXPECT_CALL(onConnectHook_, Call());
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { subscriptionSource_.stop(); });
|
||||
EXPECT_CALL(onDisconnectHook_, Call(false)).WillOnce([this]() { stopSubscriptionSource(); });
|
||||
EXPECT_CALL(lastMessageTimeMock, set).WillOnce([](int64_t value) {
|
||||
auto const now =
|
||||
std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
EXPECT_LE(now - value, 1);
|
||||
});
|
||||
ioContext_.run();
|
||||
runContext();
|
||||
}
|
||||
|
||||
62
tests/unit/util/StopHelperTests.cpp
Normal file
62
tests/unit/util/StopHelperTests.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2025, the clio developers.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include "util/AsioContextTestFixture.hpp"
|
||||
#include "util/StopHelper.hpp"
|
||||
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
using namespace util;
|
||||
|
||||
struct StopHelperTests : SyncAsioContextTest {
|
||||
protected:
|
||||
StopHelper stopHelper_;
|
||||
testing::StrictMock<testing::MockFunction<void()>> readyToStopCalled_;
|
||||
testing::StrictMock<testing::MockFunction<void()>> asyncWaitForStopFinished_;
|
||||
};
|
||||
|
||||
TEST_F(StopHelperTests, asyncWaitForStopWaitsForReadyToStop)
|
||||
{
|
||||
testing::Sequence const sequence;
|
||||
EXPECT_CALL(readyToStopCalled_, Call).InSequence(sequence);
|
||||
EXPECT_CALL(asyncWaitForStopFinished_, Call).InSequence(sequence);
|
||||
|
||||
boost::asio::spawn(ctx_, [this](boost::asio::yield_context yield) {
|
||||
stopHelper_.asyncWaitForStop(yield);
|
||||
asyncWaitForStopFinished_.Call();
|
||||
});
|
||||
|
||||
runSpawn([this](auto&&) {
|
||||
stopHelper_.readyToStop();
|
||||
readyToStopCalled_.Call();
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(StopHelperTests, readyToStopCalledBeforeAsyncWait)
|
||||
{
|
||||
stopHelper_.readyToStop();
|
||||
EXPECT_CALL(asyncWaitForStopFinished_, Call);
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
stopHelper_.asyncWaitForStop(yield);
|
||||
asyncWaitForStopFinished_.Call();
|
||||
});
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "util/AsioContextTestFixture.hpp"
|
||||
#include "util/AssignRandomPort.hpp"
|
||||
#include "util/LoggerFixtures.hpp"
|
||||
#include "util/MockPrometheus.hpp"
|
||||
#include "util/NameGenerator.hpp"
|
||||
#include "util/Taggable.hpp"
|
||||
#include "util/TestHttpClient.hpp"
|
||||
@@ -45,6 +46,7 @@
|
||||
#include <boost/beast/http/status.hpp>
|
||||
#include <boost/beast/http/string_body.hpp>
|
||||
#include <boost/beast/http/verb.hpp>
|
||||
#include <boost/beast/websocket/error.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <gmock/gmock.h>
|
||||
@@ -149,7 +151,7 @@ INSTANTIATE_TEST_CASE_P(
|
||||
tests::util::kNAME_GENERATOR
|
||||
);
|
||||
|
||||
struct ServerTest : SyncAsioContextTest {
|
||||
struct ServerTest : util::prometheus::WithPrometheus, SyncAsioContextTest {
|
||||
ServerTest()
|
||||
{
|
||||
[&]() { ASSERT_TRUE(server_.has_value()); }();
|
||||
@@ -421,6 +423,35 @@ TEST_F(ServerHttpTest, OnDisconnectHook)
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(ServerHttpTest, ClientIsDisconnectedIfServerStopped)
|
||||
{
|
||||
HttpAsyncClient client{ctx_};
|
||||
boost::asio::spawn(ctx_, [&](boost::asio::yield_context yield) {
|
||||
auto maybeError =
|
||||
client.connect("127.0.0.1", std::to_string(serverPort_), yield, std::chrono::milliseconds{100});
|
||||
[&]() { ASSERT_FALSE(maybeError.has_value()) << maybeError->message(); }();
|
||||
|
||||
// Have to send a request here because the server does async_detect_ssl() which waits for some data to appear
|
||||
maybeError = client.send(
|
||||
http::request<http::string_body>{http::verb::get, "/", 11, requestMessage_},
|
||||
yield,
|
||||
std::chrono::milliseconds{100}
|
||||
);
|
||||
[&]() { ASSERT_FALSE(maybeError.has_value()) << maybeError->message(); }();
|
||||
|
||||
auto message = client.receive(yield, std::chrono::milliseconds{100});
|
||||
EXPECT_TRUE(message.has_value()) << message.error().message();
|
||||
EXPECT_EQ(message->result(), http::status::service_unavailable);
|
||||
EXPECT_EQ(message->body(), "This Clio node is shutting down. Please try another node.");
|
||||
|
||||
ctx_.stop();
|
||||
});
|
||||
|
||||
server_->run();
|
||||
runSyncOperation([this](auto yield) { server_->stop(yield); });
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_P(ServerHttpTest, RequestResponse)
|
||||
{
|
||||
HttpAsyncClient client{ctx_};
|
||||
@@ -533,3 +564,20 @@ TEST_F(ServerTest, WsRequestResponse)
|
||||
|
||||
runContext();
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, WsClientIsDisconnectedIfServerStopped)
|
||||
{
|
||||
WebSocketAsyncClient client{ctx_};
|
||||
boost::asio::spawn(ctx_, [&](boost::asio::yield_context yield) {
|
||||
auto maybeError =
|
||||
client.connect("127.0.0.1", std::to_string(serverPort_), yield, std::chrono::milliseconds{100});
|
||||
EXPECT_TRUE(maybeError.has_value());
|
||||
EXPECT_EQ(maybeError.value().value(), static_cast<int>(boost::beast::websocket::error::upgrade_declined));
|
||||
|
||||
ctx_.stop();
|
||||
});
|
||||
|
||||
server_->run();
|
||||
runSyncOperation([this](auto yield) { server_->stop(yield); });
|
||||
runContext();
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
//==============================================================================
|
||||
|
||||
#include "util/AsioContextTestFixture.hpp"
|
||||
#include "util/MockPrometheus.hpp"
|
||||
#include "util/Taggable.hpp"
|
||||
#include "util/UnsupportedType.hpp"
|
||||
#include "util/newconfig/ConfigDefinition.hpp"
|
||||
@@ -64,7 +65,7 @@ namespace beast = boost::beast;
|
||||
namespace http = boost::beast::http;
|
||||
namespace websocket = boost::beast::websocket;
|
||||
|
||||
struct ConnectionHandlerTest : SyncAsioContextTest {
|
||||
struct ConnectionHandlerTest : prometheus::WithPrometheus, SyncAsioContextTest {
|
||||
ConnectionHandlerTest(ProcessingPolicy policy, std::optional<size_t> maxParallelConnections)
|
||||
: tagFactory{util::config::ClioConfigDefinition{
|
||||
{"log_tag_style", config::ConfigValue{config::ConfigType::String}.defaultValue("uint")}
|
||||
@@ -136,6 +137,10 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, ReceiveError_CloseConnection)
|
||||
{
|
||||
EXPECT_CALL(*mockHttpConnection, wasUpgraded).WillOnce(Return(false));
|
||||
EXPECT_CALL(*mockHttpConnection, receive).WillOnce(Return(makeError(boost::asio::error::timed_out)));
|
||||
EXPECT_CALL(
|
||||
*mockHttpConnection,
|
||||
setTimeout(std::chrono::steady_clock::duration{ConnectionHandler::kCLOSE_CONNECTION_TIMEOUT})
|
||||
);
|
||||
EXPECT_CALL(*mockHttpConnection, close);
|
||||
EXPECT_CALL(onDisconnectMock, Call).WillOnce([connectionPtr = mockHttpConnection.get()](Connection const& c) {
|
||||
EXPECT_EQ(&c, connectionPtr);
|
||||
@@ -352,6 +357,10 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, SubscriptionContextIsNullForHt
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
EXPECT_CALL(
|
||||
*mockHttpConnection,
|
||||
setTimeout(std::chrono::steady_clock::duration{ConnectionHandler::kCLOSE_CONNECTION_TIMEOUT})
|
||||
);
|
||||
EXPECT_CALL(*mockHttpConnection, close);
|
||||
|
||||
EXPECT_CALL(onDisconnectMock, Call).WillOnce([connectionPtr = mockHttpConnection.get()](Connection const& c) {
|
||||
@@ -394,6 +403,10 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, Receive_Handle_Send_Loop)
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
EXPECT_CALL(
|
||||
*mockHttpConnection,
|
||||
setTimeout(std::chrono::steady_clock::duration{ConnectionHandler::kCLOSE_CONNECTION_TIMEOUT})
|
||||
);
|
||||
EXPECT_CALL(*mockHttpConnection, close);
|
||||
|
||||
EXPECT_CALL(onDisconnectMock, Call).WillOnce([connectionPtr = mockHttpConnection.get()](Connection const& c) {
|
||||
@@ -451,7 +464,7 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, Stop)
|
||||
std::string const responseMessage = "some response";
|
||||
bool connectionClosed = false;
|
||||
|
||||
EXPECT_CALL(*mockWsConnection, wasUpgraded).WillOnce(Return(true));
|
||||
EXPECT_CALL(*mockWsConnection, wasUpgraded).Times(2).WillRepeatedly(Return(true));
|
||||
EXPECT_CALL(*mockWsConnection, receive).Times(4).WillRepeatedly([&](auto&&) -> std::expected<Request, Error> {
|
||||
if (connectionClosed) {
|
||||
return makeError(websocket::error::closed);
|
||||
@@ -465,16 +478,33 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, Stop)
|
||||
});
|
||||
|
||||
size_t numCalls = 0;
|
||||
EXPECT_CALL(*mockWsConnection, send).Times(3).WillRepeatedly([&](Response response, auto&&) {
|
||||
EXPECT_EQ(response.message(), responseMessage);
|
||||
EXPECT_CALL(
|
||||
*mockWsConnection,
|
||||
send(testing::ResultOf([](Response const& r) { return r.message(); }, responseMessage), testing::_)
|
||||
)
|
||||
.Times(3)
|
||||
.WillRepeatedly([&](auto&&, auto&&) {
|
||||
++numCalls;
|
||||
if (numCalls == 3)
|
||||
boost::asio::spawn(ctx_, [this](auto yield) { connectionHandler.stop(yield); });
|
||||
|
||||
++numCalls;
|
||||
if (numCalls == 3)
|
||||
connectionHandler.stop();
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
return std::nullopt;
|
||||
});
|
||||
EXPECT_CALL(
|
||||
*mockWsConnection,
|
||||
send(
|
||||
testing::ResultOf(
|
||||
[](Response const& r) { return r.message(); },
|
||||
"This Clio node is shutting down. Please try another node."
|
||||
),
|
||||
testing::_
|
||||
)
|
||||
);
|
||||
|
||||
EXPECT_CALL(
|
||||
*mockWsConnection, setTimeout(std::chrono::steady_clock::duration{ConnectionHandler::kCLOSE_CONNECTION_TIMEOUT})
|
||||
);
|
||||
EXPECT_CALL(*mockWsConnection, close).WillOnce([&connectionClosed]() { connectionClosed = true; });
|
||||
|
||||
EXPECT_CALL(onDisconnectMock, Call).WillOnce([connectionPtr = mockWsConnection.get()](Connection const& c) {
|
||||
@@ -486,6 +516,36 @@ TEST_F(ConnectionHandlerSequentialProcessingTest, Stop)
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ConnectionHandlerSequentialProcessingTest, ProcessCalledAfterStop)
|
||||
{
|
||||
testing::StrictMock<testing::MockFunction<
|
||||
Response(Request const&, ConnectionMetadata const&, web::SubscriptionContextPtr, boost::asio::yield_context)>>
|
||||
wsHandlerMock;
|
||||
connectionHandler.onWs(wsHandlerMock.AsStdFunction());
|
||||
|
||||
runSyncOperation([this](boost::asio::yield_context yield) { connectionHandler.stop(yield); });
|
||||
|
||||
EXPECT_CALL(*mockWsConnection, wasUpgraded).WillOnce(Return(true));
|
||||
EXPECT_CALL(
|
||||
*mockWsConnection,
|
||||
send(
|
||||
testing::ResultOf(
|
||||
[](Response const& r) { return r.message(); }, testing::HasSubstr("This Clio node is shutting down")
|
||||
),
|
||||
testing::_
|
||||
)
|
||||
);
|
||||
|
||||
EXPECT_CALL(
|
||||
*mockWsConnection, setTimeout(std::chrono::steady_clock::duration{ConnectionHandler::kCLOSE_CONNECTION_TIMEOUT})
|
||||
);
|
||||
EXPECT_CALL(*mockWsConnection, close);
|
||||
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
connectionHandler.processConnection(std::move(mockWsConnection), yield);
|
||||
});
|
||||
}
|
||||
|
||||
struct ConnectionHandlerParallelProcessingTest : ConnectionHandlerTest {
|
||||
static constexpr size_t kMAX_PARALLEL_REQUESTS = 3;
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
//==============================================================================
|
||||
|
||||
#include "util/AsioContextTestFixture.hpp"
|
||||
#include "util/CoroutineGroup.hpp"
|
||||
#include "util/Taggable.hpp"
|
||||
#include "util/TestHttpServer.hpp"
|
||||
#include "util/TestWebSocketClient.hpp"
|
||||
@@ -308,3 +309,28 @@ TEST_F(WebWsConnectionTests, CloseWhenConnectionIsAlreadyClosed)
|
||||
wsConnection->close(yield);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(WebWsConnectionTests, CloseCalledFromMultipleSubCoroutines)
|
||||
{
|
||||
boost::asio::spawn(ctx_, [this](boost::asio::yield_context yield) {
|
||||
auto maybeError = wsClient_.connect("localhost", httpServer_.port(), yield, std::chrono::milliseconds{100});
|
||||
[&]() { ASSERT_FALSE(maybeError.has_value()) << maybeError.value().message(); }();
|
||||
});
|
||||
|
||||
testing::StrictMock<testing::MockFunction<void()>> closeCalled;
|
||||
EXPECT_CALL(closeCalled, Call).Times(2);
|
||||
|
||||
runSpawnWithTimeout(std::chrono::seconds{1}, [&](boost::asio::yield_context yield) {
|
||||
auto wsConnection = acceptConnection(yield);
|
||||
util::CoroutineGroup coroutines{yield};
|
||||
for ([[maybe_unused]] int i : std::ranges::iota_view{0, 2}) {
|
||||
coroutines.spawn(yield, [&wsConnection, &closeCalled](boost::asio::yield_context innerYield) {
|
||||
wsConnection->close(innerYield);
|
||||
closeCalled.Call();
|
||||
});
|
||||
}
|
||||
auto const receivedMessage = wsConnection->receive(yield);
|
||||
EXPECT_FALSE(receivedMessage.has_value());
|
||||
coroutines.asyncWait(yield);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user