Refactor namespaces part 1 (#817)

Part 1 of refactoring effort
This commit is contained in:
Peter Chen
2023-08-10 13:05:13 -04:00
committed by GitHub
parent db4046e02a
commit 23442ff1a7
195 changed files with 964 additions and 888 deletions

View File

@@ -0,0 +1,197 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2023, 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 <data/BackendFactory.h>
#include <util/Fixtures.h>
#include <boost/json.hpp>
#include <fmt/core.h>
#include <gtest/gtest.h>
namespace {
constexpr static auto contactPoints = "127.0.0.1";
constexpr static auto keyspace = "factory_test";
} // namespace
class BackendCassandraFactoryTest : public SyncAsioContextTest
{
protected:
void
SetUp() override
{
SyncAsioContextTest::SetUp();
}
void
TearDown() override
{
SyncAsioContextTest::TearDown();
}
};
class BackendCassandraFactoryTestWithDB : public BackendCassandraFactoryTest
{
protected:
void
SetUp() override
{
BackendCassandraFactoryTest::SetUp();
}
void
TearDown() override
{
BackendCassandraFactoryTest::TearDown();
// drop the keyspace for next test
data::cassandra::Handle handle{contactPoints};
EXPECT_TRUE(handle.connect());
handle.execute("DROP KEYSPACE " + std::string{keyspace});
}
};
TEST_F(BackendCassandraFactoryTest, NoSuchBackend)
{
util::Config cfg{boost::json::parse(
R"({
"database":
{
"type":"unknown"
}
})")};
EXPECT_THROW(make_Backend(ctx, cfg), std::runtime_error);
}
TEST_F(BackendCassandraFactoryTest, CreateCassandraBackendDBDisconnect)
{
util::Config cfg{boost::json::parse(fmt::format(
R"({{
"database":
{{
"type" : "cassandra",
"cassandra" : {{
"contact_points": "{}",
"keyspace": "{}",
"replication_factor": 1,
"connect_timeout": 2
}}
}}
}})",
"127.0.0.2",
keyspace))};
EXPECT_THROW(make_Backend(ctx, cfg), std::runtime_error);
}
TEST_F(BackendCassandraFactoryTestWithDB, CreateCassandraBackend)
{
util::Config cfg{boost::json::parse(fmt::format(
R"({{
"database":
{{
"type": "cassandra",
"cassandra": {{
"contact_points": "{}",
"keyspace": "{}",
"replication_factor": 1
}}
}}
}})",
contactPoints,
keyspace))};
{
auto backend = make_Backend(ctx, cfg);
EXPECT_TRUE(backend);
// empty db does not have ledger range
EXPECT_FALSE(backend->fetchLedgerRange());
// insert range table
data::cassandra::Handle handle{contactPoints};
EXPECT_TRUE(handle.connect());
handle.execute(fmt::format("INSERT INTO {}.ledger_range (is_latest, sequence) VALUES (False, 100)", keyspace));
handle.execute(fmt::format("INSERT INTO {}.ledger_range (is_latest, sequence) VALUES (True, 500)", keyspace));
}
{
auto backend = make_Backend(ctx, cfg);
EXPECT_TRUE(backend);
auto const range = backend->fetchLedgerRange();
EXPECT_EQ(range->minSequence, 100);
EXPECT_EQ(range->maxSequence, 500);
}
}
TEST_F(BackendCassandraFactoryTestWithDB, CreateCassandraBackendReadOnlyWithEmptyDB)
{
util::Config cfg{boost::json::parse(fmt::format(
R"({{
"read_only": true,
"database":
{{
"type" : "cassandra",
"cassandra" : {{
"contact_points": "{}",
"keyspace": "{}",
"replication_factor": 1
}}
}}
}})",
contactPoints,
keyspace))};
EXPECT_THROW(make_Backend(ctx, cfg), std::runtime_error);
}
TEST_F(BackendCassandraFactoryTestWithDB, CreateCassandraBackendReadOnlyWithDBReady)
{
util::Config cfgReadOnly{boost::json::parse(fmt::format(
R"({{
"read_only": true,
"database":
{{
"type" : "cassandra",
"cassandra" : {{
"contact_points": "{}",
"keyspace": "{}",
"replication_factor": 1
}}
}}
}})",
contactPoints,
keyspace))};
util::Config cfgWrite{boost::json::parse(fmt::format(
R"({{
"read_only": false,
"database":
{{
"type" : "cassandra",
"cassandra" : {{
"contact_points": "{}",
"keyspace": "{}",
"replication_factor": 1
}}
}}
}})",
contactPoints,
keyspace))};
EXPECT_TRUE(make_Backend(ctx, cfgWrite));
EXPECT_TRUE(make_Backend(ctx, cfgReadOnly));
}

View File

@@ -0,0 +1,165 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2023, 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 <data/cassandra/impl/FakesAndMocks.h>
#include <util/Fixtures.h>
#include <data/cassandra/Error.h>
#include <data/cassandra/impl/AsyncExecutor.h>
#include <gmock/gmock.h>
using namespace data::cassandra;
using namespace data::cassandra::detail;
using namespace testing;
class BackendCassandraAsyncExecutorTest : public SyncAsioContextTest
{
};
TEST_F(BackendCassandraAsyncExecutorTest, CompletionCalledOnSuccess)
{
auto statement = FakeStatement{};
auto handle = MockHandle{};
ON_CALL(handle, asyncExecute(An<FakeStatement const&>(), An<std::function<void(FakeResultOrError)>&&>()))
.WillByDefault([this](auto const&, auto&& cb) {
ctx.post([cb = std::move(cb)]() { cb({}); });
return FakeFutureWithCallback{};
});
EXPECT_CALL(handle, asyncExecute(An<FakeStatement const&>(), An<std::function<void(FakeResultOrError)>&&>()))
.Times(AtLeast(1));
auto called = std::atomic_bool{false};
auto work = std::optional<boost::asio::io_context::work>{ctx};
AsyncExecutor<FakeStatement, MockHandle>::run(ctx, handle, std::move(statement), [&called, &work](auto&&) {
called = true;
work.reset();
});
ctx.run();
ASSERT_TRUE(called);
}
TEST_F(BackendCassandraAsyncExecutorTest, ExecutedMultipleTimesByRetryPolicyOnMainThread)
{
auto callCount = std::atomic_int{0};
auto statement = FakeStatement{};
auto handle = MockHandle{};
// emulate successfull execution after some attempts
ON_CALL(handle, asyncExecute(An<FakeStatement const&>(), An<std::function<void(FakeResultOrError)>&&>()))
.WillByDefault([&callCount](auto const&, auto&& cb) {
++callCount;
if (callCount >= 3)
cb({});
else
cb({CassandraError{"timeout", CASS_ERROR_LIB_REQUEST_TIMED_OUT}});
return FakeFutureWithCallback{};
});
EXPECT_CALL(handle, asyncExecute(An<FakeStatement const&>(), An<std::function<void(FakeResultOrError)>&&>()))
.Times(3);
auto called = std::atomic_bool{false};
auto work = std::optional<boost::asio::io_context::work>{ctx};
AsyncExecutor<FakeStatement, MockHandle>::run(ctx, handle, std::move(statement), [&called, &work](auto&&) {
called = true;
work.reset();
});
ctx.run();
ASSERT_TRUE(callCount >= 3);
ASSERT_TRUE(called);
}
TEST_F(BackendCassandraAsyncExecutorTest, ExecutedMultipleTimesByRetryPolicyOnOtherThread)
{
auto callCount = std::atomic_int{0};
auto statement = FakeStatement{};
auto handle = MockHandle{};
auto threadedCtx = boost::asio::io_context{};
auto work = std::optional<boost::asio::io_context::work>{threadedCtx};
auto thread = std::thread{[&threadedCtx] { threadedCtx.run(); }};
// emulate successfull execution after some attempts
ON_CALL(handle, asyncExecute(An<FakeStatement const&>(), An<std::function<void(FakeResultOrError)>&&>()))
.WillByDefault([&callCount](auto const&, auto&& cb) {
++callCount;
if (callCount >= 3)
cb({});
else
cb({CassandraError{"timeout", CASS_ERROR_LIB_REQUEST_TIMED_OUT}});
return FakeFutureWithCallback{};
});
EXPECT_CALL(handle, asyncExecute(An<FakeStatement const&>(), An<std::function<void(FakeResultOrError)>&&>()))
.Times(3);
auto called = std::atomic_bool{false};
auto work2 = std::optional<boost::asio::io_context::work>{ctx};
AsyncExecutor<FakeStatement, MockHandle>::run(
threadedCtx, handle, std::move(statement), [&called, &work, &work2](auto&&) {
called = true;
work.reset();
work2.reset();
});
ctx.run();
ASSERT_TRUE(callCount >= 3);
ASSERT_TRUE(called);
threadedCtx.stop();
thread.join();
}
TEST_F(BackendCassandraAsyncExecutorTest, CompletionCalledOnFailureAfterRetryCountExceeded)
{
auto statement = FakeStatement{};
auto handle = MockHandle{};
// FakeRetryPolicy returns false for shouldRetry in which case we should
// still call onComplete giving it whatever error we have raised internally.
ON_CALL(handle, asyncExecute(An<FakeStatement const&>(), An<std::function<void(FakeResultOrError)>&&>()))
.WillByDefault([](auto const&, auto&& cb) {
cb({CassandraError{"not a timeout", CASS_ERROR_LIB_INTERNAL_ERROR}});
return FakeFutureWithCallback{};
});
EXPECT_CALL(handle, asyncExecute(An<FakeStatement const&>(), An<std::function<void(FakeResultOrError)>&&>()))
.Times(1);
auto called = std::atomic_bool{false};
auto work = std::optional<boost::asio::io_context::work>{ctx};
AsyncExecutor<FakeStatement, MockHandle, FakeRetryPolicy>::run(
ctx, handle, std::move(statement), [&called, &work](auto&& res) {
EXPECT_FALSE(res);
EXPECT_EQ(res.error().code(), CASS_ERROR_LIB_INTERNAL_ERROR);
EXPECT_EQ(res.error().message(), "not a timeout");
called = true;
work.reset();
});
ctx.run();
ASSERT_TRUE(called);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,457 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2023, 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/Fixtures.h>
#include <data/cassandra/Handle.h>
#include <fmt/core.h>
#include <gtest/gtest.h>
#include <semaphore>
using namespace std;
using namespace data::cassandra;
namespace json = boost::json;
class BackendCassandraBaseTest : public NoLoggerFixture
{
protected:
Handle
createHandle(std::string_view contactPoints, std::string_view keyspace)
{
Handle handle{contactPoints};
EXPECT_TRUE(handle.connect());
auto const query = fmt::format(
R"(
CREATE KEYSPACE IF NOT EXISTS {}
WITH replication = {{'class': 'SimpleStrategy', 'replication_factor': '1'}}
AND durable_writes = true
)",
keyspace);
EXPECT_TRUE(handle.execute(query));
EXPECT_TRUE(handle.reconnect(keyspace));
return handle;
}
void
dropKeyspace(Handle const& handle, std::string_view keyspace)
{
std::string query = "DROP KEYSPACE " + std::string{keyspace};
ASSERT_TRUE(handle.execute(query));
}
void
prepStringsTable(Handle const& handle)
{
auto const entries = std::vector<std::string>{
"first",
"second",
"third",
"fourth",
"fifth",
};
auto const q1 = fmt::format(
R"(
CREATE TABLE IF NOT EXISTS strings (hash blob PRIMARY KEY, sequence bigint)
WITH default_time_to_live = {}
)",
to_string(5000));
auto const f1 = handle.asyncExecute(q1);
auto const rc = f1.await();
ASSERT_TRUE(rc) << rc.error();
std::string q2 = "INSERT INTO strings (hash, sequence) VALUES (?, ?)";
auto const insert = handle.prepare(q2);
std::vector<Statement> statements;
int64_t idx = 1000;
for (auto const& entry : entries)
statements.push_back(insert.bind(entry, static_cast<int64_t>(idx++)));
EXPECT_EQ(statements.size(), entries.size());
EXPECT_TRUE(handle.execute(statements));
}
};
TEST_F(BackendCassandraBaseTest, ConnectionSuccess)
{
Handle handle{"127.0.0.1"};
auto const f = handle.asyncConnect();
auto const res = f.await();
ASSERT_TRUE(res);
}
TEST_F(BackendCassandraBaseTest, ConnectionFailFormat)
{
Handle handle{"127.0.0."};
auto const f = handle.asyncConnect();
auto const res = f.await();
ASSERT_FALSE(res);
EXPECT_EQ(res.error(), "No hosts available: Unable to connect to any contact points");
EXPECT_EQ(res.error().code(), CASS_ERROR_LIB_NO_HOSTS_AVAILABLE);
}
TEST_F(BackendCassandraBaseTest, ConnectionFailTimeout)
{
Settings settings;
settings.connectionTimeout = std::chrono::milliseconds{30};
settings.connectionInfo = Settings::ContactPoints{"127.0.0.2"};
Handle handle{settings};
auto const f = handle.asyncConnect();
auto const res = f.await();
ASSERT_FALSE(res);
// scylla and cassandra produce different text
EXPECT_TRUE(res.error().message().starts_with("No hosts available: Underlying connection error:"));
EXPECT_EQ(res.error().code(), CASS_ERROR_LIB_NO_HOSTS_AVAILABLE);
}
TEST_F(BackendCassandraBaseTest, FutureCallback)
{
Handle handle{"127.0.0.1"};
ASSERT_TRUE(handle.connect());
auto const statement = handle.prepare("SELECT keyspace_name FROM system_schema.keyspaces").bind();
bool complete = false;
auto const f = handle.asyncExecute(statement, [&complete](auto const res) {
complete = true;
EXPECT_TRUE(res.value().hasRows());
for (auto [ks] : extract<std::string>(res.value()))
EXPECT_TRUE(not ks.empty()); // keyspace got some name
});
auto const res = f.await(); // callback should still be called
ASSERT_TRUE(res);
ASSERT_TRUE(complete);
}
TEST_F(BackendCassandraBaseTest, FutureCallbackSurviveMove)
{
Handle handle{"127.0.0.1"};
ASSERT_TRUE(handle.connect());
auto const statement = handle.prepare("SELECT keyspace_name FROM system_schema.keyspaces").bind();
bool complete = false;
std::vector<FutureWithCallback> futures;
std::binary_semaphore sem{0};
futures.push_back(handle.asyncExecute(statement, [&complete, &sem](auto const res) {
complete = true;
EXPECT_TRUE(res.value().hasRows());
for (auto [ks] : extract<std::string>(res.value()))
EXPECT_TRUE(not ks.empty()); // keyspace got some name
sem.release();
}));
sem.acquire();
for (auto const& f : futures)
ASSERT_TRUE(f.await());
ASSERT_TRUE(complete);
}
TEST_F(BackendCassandraBaseTest, KeyspaceManipulation)
{
Handle handle{"127.0.0.1"};
std::string keyspace = "test_keyspace_manipulation";
{
auto const f = handle.asyncConnect(keyspace);
auto const rc = f.await();
ASSERT_FALSE(rc); // initially expecting the keyspace does not exist
}
{
auto const f = handle.asyncConnect();
auto const rc = f.await();
ASSERT_TRUE(rc); // expect that we can still connect without keyspace
}
{
const auto query = fmt::format(
R"(
CREATE KEYSPACE {}
WITH replication = {{'class': 'SimpleStrategy', 'replication_factor': '1'}}
AND durable_writes = true
)",
keyspace);
auto const f = handle.asyncExecute(query);
auto const rc = f.await();
ASSERT_TRUE(rc); // keyspace created
}
{
auto const rc = handle.reconnect(keyspace);
ASSERT_TRUE(rc); // connect to the keyspace we created earlier
}
{
auto const f = handle.asyncExecute("DROP KEYSPACE " + keyspace);
auto const rc = f.await();
ASSERT_TRUE(rc); // dropped the keyspace
}
{
auto const f = handle.asyncExecute("DROP KEYSPACE " + keyspace);
auto const rc = f.await();
ASSERT_FALSE(rc); // keyspace already does not exist
}
}
TEST_F(BackendCassandraBaseTest, CreateTableWithStrings)
{
auto const entries = std::vector<std::string>{
"first",
"second",
"third",
"fourth",
"fifth",
};
auto handle = createHandle("127.0.0.1", "test");
auto q1 = fmt::format(
R"(
CREATE TABLE IF NOT EXISTS strings (hash blob PRIMARY KEY, sequence bigint)
WITH default_time_to_live = {}
)",
5000);
auto const f1 = handle.asyncExecute(q1);
auto const rc = f1.await();
ASSERT_TRUE(rc) << rc.error();
std::string q2 = "INSERT INTO strings (hash, sequence) VALUES (?, ?)";
auto insert = handle.prepare(q2);
// write data
{
std::vector<Future> futures;
int64_t idx = 1000;
for (auto const& entry : entries)
futures.push_back(handle.asyncExecute(insert, entry, static_cast<int64_t>(idx++)));
ASSERT_EQ(futures.size(), entries.size());
for (auto const& f : futures)
{
auto const rc = f.await();
ASSERT_TRUE(rc) << rc.error();
}
}
// read data back
{
auto const res = handle.execute("SELECT hash, sequence FROM strings");
ASSERT_TRUE(res) << res.error();
auto const& results = res.value();
auto const totalRows = results.numRows();
EXPECT_EQ(totalRows, entries.size());
for (auto [hash, seq] : extract<std::string, int64_t>(results))
EXPECT_TRUE(std::find(std::begin(entries), std::end(entries), hash) != std::end(entries));
}
// delete everything
{
auto const res = handle.execute("DROP TABLE strings");
ASSERT_TRUE(res) << res.error();
dropKeyspace(handle, "test");
}
}
TEST_F(BackendCassandraBaseTest, BatchInsert)
{
auto const entries = std::vector<std::string>{
"first",
"second",
"third",
"fourth",
"fifth",
};
auto handle = createHandle("127.0.0.1", "test");
auto const q1 = fmt::format(
R"(
CREATE TABLE IF NOT EXISTS strings (hash blob PRIMARY KEY, sequence bigint)
WITH default_time_to_live = {}
)",
5000);
auto const f1 = handle.asyncExecute(q1);
auto const rc = f1.await();
ASSERT_TRUE(rc) << rc.error();
std::string q2 = "INSERT INTO strings (hash, sequence) VALUES (?, ?)";
auto const insert = handle.prepare(q2);
// write data in bulk
{
std::vector<Statement> statements;
int64_t idx = 1000;
for (auto const& entry : entries)
statements.push_back(insert.bind(entry, static_cast<int64_t>(idx++)));
ASSERT_EQ(statements.size(), entries.size());
auto const rc = handle.execute(statements);
ASSERT_TRUE(rc) << rc.error();
}
// read data back
{
auto const res = handle.execute("SELECT hash, sequence FROM strings");
ASSERT_TRUE(res) << res.error();
auto const& results = res.value();
auto const totalRows = results.numRows();
EXPECT_EQ(totalRows, entries.size());
for (auto [hash, seq] : extract<std::string, int64_t>(results))
EXPECT_TRUE(std::find(std::begin(entries), std::end(entries), hash) != std::end(entries));
}
dropKeyspace(handle, "test");
}
TEST_F(BackendCassandraBaseTest, BatchInsertAsync)
{
using std::to_string;
auto const entries = std::vector<std::string>{
"first",
"second",
"third",
"fourth",
"fifth",
};
auto handle = createHandle("127.0.0.1", "test");
auto const q1 = fmt::format(
R"(
CREATE TABLE IF NOT EXISTS strings (hash blob PRIMARY KEY, sequence bigint)
WITH default_time_to_live = {}
)",
5000);
auto const f1 = handle.asyncExecute(q1);
auto const rc = f1.await();
ASSERT_TRUE(rc) << rc.error();
std::string q2 = "INSERT INTO strings (hash, sequence) VALUES (?, ?)";
auto const insert = handle.prepare(q2);
// write data in bulk
{
bool complete = false;
std::optional<data::cassandra::FutureWithCallback> fut;
{
std::vector<Statement> statements;
int64_t idx = 1000;
for (auto const& entry : entries)
statements.push_back(insert.bind(entry, static_cast<int64_t>(idx++)));
ASSERT_EQ(statements.size(), entries.size());
fut.emplace(handle.asyncExecute(statements, [&](auto const res) {
complete = true;
EXPECT_TRUE(res);
}));
// statements are destructed here, async execute needs to survive
}
auto const res = fut.value().await(); // future should still signal it finished
EXPECT_TRUE(res);
ASSERT_TRUE(complete);
}
dropKeyspace(handle, "test");
}
TEST_F(BackendCassandraBaseTest, AlterTableAddColumn)
{
auto handle = createHandle("127.0.0.1", "test");
auto const q1 = fmt::format(
R"(
CREATE TABLE IF NOT EXISTS strings (hash blob PRIMARY KEY, sequence bigint)
WITH default_time_to_live = {}
)",
5000);
ASSERT_TRUE(handle.execute(q1));
std::string update = "ALTER TABLE strings ADD tmp blob";
ASSERT_TRUE(handle.execute(update));
dropKeyspace(handle, "test");
}
TEST_F(BackendCassandraBaseTest, AlterTableMoveToNewTable)
{
auto handle = createHandle("127.0.0.1", "test");
prepStringsTable(handle);
auto const newTable = fmt::format(
R"(
CREATE TABLE IF NOT EXISTS strings_v2 (hash blob PRIMARY KEY, sequence bigint, tmp bigint)
WITH default_time_to_live = {}
)",
5000);
ASSERT_TRUE(handle.execute(newTable));
// now migrate data; tmp column will just get the sequence number + 1 stored
std::vector<Statement> migrationStatements;
auto const migrationInsert = handle.prepare("INSERT INTO strings_v2 (hash, sequence, tmp) VALUES (?, ?, ?)");
auto const res = handle.execute("SELECT hash, sequence FROM strings");
ASSERT_TRUE(res);
auto const& results = res.value();
for (auto [hash, seq] : extract<std::string, int64_t>(results))
{
static_assert(std::is_same_v<decltype(hash), std::string>);
static_assert(std::is_same_v<decltype(seq), int64_t>);
migrationStatements.push_back(
migrationInsert.bind(hash, static_cast<int64_t>(seq), static_cast<int64_t>(seq + 1u)));
}
EXPECT_TRUE(handle.execute(migrationStatements));
// now let's read back the v2 table and compare
auto const resV2 = handle.execute("SELECT sequence, tmp FROM strings_v2");
EXPECT_TRUE(resV2);
auto const& resultsV2 = resV2.value();
EXPECT_EQ(results.numRows(), resultsV2.numRows());
for (auto [seq, tmp] : extract<int64_t, int64_t>(resultsV2))
{
static_assert(std::is_same_v<decltype(seq), int64_t>);
static_assert(std::is_same_v<decltype(tmp), int64_t>);
EXPECT_EQ(seq + 1, tmp);
}
dropKeyspace(handle, "test");
}

View File

@@ -0,0 +1,307 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2023, 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 <data/cassandra/impl/FakesAndMocks.h>
#include <util/Fixtures.h>
#include <data/cassandra/impl/ExecutionStrategy.h>
#include <gtest/gtest.h>
using namespace data::cassandra;
using namespace data::cassandra::detail;
using namespace testing;
class BackendCassandraExecutionStrategyTest : public SyncAsioContextTest
{
};
TEST_F(BackendCassandraExecutionStrategyTest, ReadOneInCoroutineSuccessful)
{
auto handle = MockHandle{};
auto strat = DefaultExecutionStrategy{Settings{}, handle};
ON_CALL(handle, asyncExecute(An<FakeStatement const&>(), An<std::function<void(FakeResultOrError)>&&>()))
.WillByDefault([](auto const& statement, auto&& cb) {
cb({}); // pretend we got data
return FakeFutureWithCallback{};
});
EXPECT_CALL(handle, asyncExecute(An<FakeStatement const&>(), An<std::function<void(FakeResultOrError)>&&>()))
.Times(1);
runSpawn([&strat](boost::asio::yield_context yield) {
auto statement = FakeStatement{};
strat.read(yield, statement);
});
}
TEST_F(BackendCassandraExecutionStrategyTest, ReadOneInCoroutineThrowsOnTimeoutFailure)
{
auto handle = MockHandle{};
auto strat = DefaultExecutionStrategy{Settings{}, handle};
ON_CALL(handle, asyncExecute(An<FakeStatement const&>(), An<std::function<void(FakeResultOrError)>&&>()))
.WillByDefault([](auto const&, auto&& cb) {
auto res = FakeResultOrError{CassandraError{"timeout", CASS_ERROR_LIB_REQUEST_TIMED_OUT}};
cb(res); // notify that item is ready
return FakeFutureWithCallback{res};
});
EXPECT_CALL(handle, asyncExecute(An<FakeStatement const&>(), An<std::function<void(FakeResultOrError)>&&>()))
.Times(1);
runSpawn([&strat](boost::asio::yield_context yield) {
auto statement = FakeStatement{};
EXPECT_THROW(strat.read(yield, statement), DatabaseTimeout);
});
}
TEST_F(BackendCassandraExecutionStrategyTest, ReadOneInCoroutineThrowsOnInvalidQueryFailure)
{
auto handle = MockHandle{};
auto strat = DefaultExecutionStrategy{Settings{}, handle};
ON_CALL(handle, asyncExecute(An<FakeStatement const&>(), An<std::function<void(FakeResultOrError)>&&>()))
.WillByDefault([](auto const&, auto&& cb) {
auto res = FakeResultOrError{CassandraError{"invalid", CASS_ERROR_SERVER_INVALID_QUERY}};
cb(res); // notify that item is ready
return FakeFutureWithCallback{res};
});
EXPECT_CALL(handle, asyncExecute(An<FakeStatement const&>(), An<std::function<void(FakeResultOrError)>&&>()))
.Times(1);
runSpawn([&strat](boost::asio::yield_context yield) {
auto statement = FakeStatement{};
EXPECT_THROW(strat.read(yield, statement), std::runtime_error);
});
}
TEST_F(BackendCassandraExecutionStrategyTest, ReadBatchInCoroutineSuccessful)
{
auto handle = MockHandle{};
auto strat = DefaultExecutionStrategy{Settings{}, handle};
ON_CALL(
handle, asyncExecute(An<std::vector<FakeStatement> const&>(), An<std::function<void(FakeResultOrError)>&&>()))
.WillByDefault([](auto const& statements, auto&& cb) {
EXPECT_EQ(statements.size(), 3);
cb({}); // pretend we got data
return FakeFutureWithCallback{};
});
EXPECT_CALL(
handle, asyncExecute(An<std::vector<FakeStatement> const&>(), An<std::function<void(FakeResultOrError)>&&>()))
.Times(1);
runSpawn([&strat](boost::asio::yield_context yield) {
auto statements = std::vector<FakeStatement>(3);
strat.read(yield, statements);
});
}
TEST_F(BackendCassandraExecutionStrategyTest, ReadBatchInCoroutineThrowsOnTimeoutFailure)
{
auto handle = MockHandle{};
auto strat = DefaultExecutionStrategy{Settings{}, handle};
ON_CALL(
handle, asyncExecute(An<std::vector<FakeStatement> const&>(), An<std::function<void(FakeResultOrError)>&&>()))
.WillByDefault([](auto const& statements, auto&& cb) {
EXPECT_EQ(statements.size(), 3);
auto res = FakeResultOrError{CassandraError{"timeout", CASS_ERROR_LIB_REQUEST_TIMED_OUT}};
cb(res); // notify that item is ready
return FakeFutureWithCallback{res};
});
EXPECT_CALL(
handle, asyncExecute(An<std::vector<FakeStatement> const&>(), An<std::function<void(FakeResultOrError)>&&>()))
.Times(1);
runSpawn([&strat](boost::asio::yield_context yield) {
auto statements = std::vector<FakeStatement>(3);
EXPECT_THROW(strat.read(yield, statements), DatabaseTimeout);
});
}
TEST_F(BackendCassandraExecutionStrategyTest, ReadBatchInCoroutineThrowsOnInvalidQueryFailure)
{
auto handle = MockHandle{};
auto strat = DefaultExecutionStrategy{Settings{}, handle};
ON_CALL(
handle, asyncExecute(An<std::vector<FakeStatement> const&>(), An<std::function<void(FakeResultOrError)>&&>()))
.WillByDefault([](auto const& statements, auto&& cb) {
EXPECT_EQ(statements.size(), 3);
auto res = FakeResultOrError{CassandraError{"invalid", CASS_ERROR_SERVER_INVALID_QUERY}};
cb(res); // notify that item is ready
return FakeFutureWithCallback{res};
});
EXPECT_CALL(
handle, asyncExecute(An<std::vector<FakeStatement> const&>(), An<std::function<void(FakeResultOrError)>&&>()))
.Times(1);
runSpawn([&strat](boost::asio::yield_context yield) {
auto statements = std::vector<FakeStatement>(3);
EXPECT_THROW(strat.read(yield, statements), std::runtime_error);
});
}
TEST_F(BackendCassandraExecutionStrategyTest, ReadBatchInCoroutineMarksBusyIfRequestsOutstandingExceeded)
{
auto handle = MockHandle{};
auto settings = Settings{};
settings.maxReadRequestsOutstanding = 2;
auto strat = DefaultExecutionStrategy{settings, handle};
ON_CALL(
handle, asyncExecute(An<std::vector<FakeStatement> const&>(), An<std::function<void(FakeResultOrError)>&&>()))
.WillByDefault([&strat](auto const& statements, auto&& cb) {
EXPECT_EQ(statements.size(), 3);
EXPECT_TRUE(strat.isTooBusy()); // 2 was the limit, we sent 3
cb({}); // notify that item is ready
return FakeFutureWithCallback{};
});
EXPECT_CALL(
handle, asyncExecute(An<std::vector<FakeStatement> const&>(), An<std::function<void(FakeResultOrError)>&&>()))
.Times(1);
runSpawn([&strat](boost::asio::yield_context yield) {
EXPECT_FALSE(strat.isTooBusy()); // 2 was the limit, 0 atm
auto statements = std::vector<FakeStatement>(3);
strat.read(yield, statements);
EXPECT_FALSE(strat.isTooBusy()); // after read completes it's 0 again
});
}
TEST_F(BackendCassandraExecutionStrategyTest, ReadEachInCoroutineSuccessful)
{
auto handle = MockHandle{};
auto strat = DefaultExecutionStrategy{Settings{}, handle};
ON_CALL(handle, asyncExecute(An<FakeStatement const&>(), An<std::function<void(FakeResultOrError)>&&>()))
.WillByDefault([](auto const&, auto&& cb) {
cb({}); // pretend we got data
return FakeFutureWithCallback{};
});
EXPECT_CALL(
handle,
asyncExecute(
An<FakeStatement const&>(),
An<std::function<void(FakeResultOrError)>&&>()))
.Times(3); // once per statement
runSpawn([&strat](boost::asio::yield_context yield) {
auto statements = std::vector<FakeStatement>(3);
auto res = strat.readEach(yield, statements);
EXPECT_EQ(res.size(), statements.size());
});
}
TEST_F(BackendCassandraExecutionStrategyTest, ReadEachInCoroutineThrowsOnFailure)
{
auto handle = MockHandle{};
auto strat = DefaultExecutionStrategy{Settings{}, handle};
auto callCount = std::atomic_int{0};
ON_CALL(handle, asyncExecute(An<FakeStatement const&>(), An<std::function<void(FakeResultOrError)>&&>()))
.WillByDefault([&callCount](auto const&, auto&& cb) {
if (callCount == 1) // error happens on one of the entries
cb({CassandraError{"invalid data", CASS_ERROR_LIB_INVALID_DATA}});
else
cb({}); // pretend we got data
++callCount;
return FakeFutureWithCallback{};
});
EXPECT_CALL(
handle,
asyncExecute(
An<FakeStatement const&>(),
An<std::function<void(FakeResultOrError)>&&>()))
.Times(3); // once per statement
runSpawn([&strat](boost::asio::yield_context yield) {
auto statements = std::vector<FakeStatement>(3);
EXPECT_THROW(strat.readEach(yield, statements), DatabaseTimeout);
});
}
TEST_F(BackendCassandraExecutionStrategyTest, WriteSyncFirstTrySuccessful)
{
auto handle = MockHandle{};
auto strat = DefaultExecutionStrategy{Settings{}, handle};
ON_CALL(handle, execute(An<FakeStatement const&>())).WillByDefault([](auto const&) { return FakeResultOrError{}; });
EXPECT_CALL(handle,
execute(An<FakeStatement const&>())).Times(1); // first one will succeed
EXPECT_TRUE(strat.writeSync({}));
}
TEST_F(BackendCassandraExecutionStrategyTest, WriteSyncRetrySuccessful)
{
auto handle = MockHandle{};
auto strat = DefaultExecutionStrategy{Settings{}, handle};
auto callCount = 0;
ON_CALL(handle, execute(An<FakeStatement const&>())).WillByDefault([&callCount](auto const&) {
if (callCount++ == 1)
return FakeResultOrError{};
return FakeResultOrError{CassandraError{"invalid data", CASS_ERROR_LIB_INVALID_DATA}};
});
EXPECT_CALL(handle,
execute(An<FakeStatement const&>())).Times(2); // first one will fail, second will succeed
EXPECT_TRUE(strat.writeSync({}));
}
TEST_F(BackendCassandraExecutionStrategyTest, WriteMultipleAndCallSyncSucceeds)
{
auto handle = MockHandle{};
auto strat = DefaultExecutionStrategy{Settings{}, handle};
auto totalRequests = 1024u;
auto callCount = std::atomic_uint{0u};
auto work = std::optional<boost::asio::io_context::work>{ctx};
auto thread = std::thread{[this]() { ctx.run(); }};
ON_CALL(
handle, asyncExecute(An<std::vector<FakeStatement> const&>(), An<std::function<void(FakeResultOrError)>&&>()))
.WillByDefault([this, &callCount](auto const&, auto&& cb) {
// run on thread to emulate concurrency model of real asyncExecute
boost::asio::post(ctx, [&callCount, cb = std::move(cb)] {
++callCount;
cb({}); // pretend we got data
});
return FakeFutureWithCallback{};
});
EXPECT_CALL(
handle,
asyncExecute(
An<std::vector<FakeStatement> const&>(),
An<std::function<void(FakeResultOrError)>&&>()))
.Times(totalRequests); // one per write call
auto makeStatements = [] { return std::vector<FakeStatement>(16); };
for (auto i = 0u; i < totalRequests; ++i)
strat.write(makeStatements());
strat.sync(); // make sure all above writes are finished
EXPECT_EQ(callCount, totalRequests); // all requests should finish
work.reset();
thread.join();
}

View File

@@ -0,0 +1,83 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2023, 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/Fixtures.h>
#include <data/cassandra/Error.h>
#include <data/cassandra/impl/RetryPolicy.h>
#include <gtest/gtest.h>
using namespace data::cassandra;
using namespace data::cassandra::detail;
using namespace testing;
class BackendCassandraRetryPolicyTest : public SyncAsioContextTest
{
};
TEST_F(BackendCassandraRetryPolicyTest, ShouldRetryAlwaysTrue)
{
auto retryPolicy = ExponentialBackoffRetryPolicy{ctx};
EXPECT_TRUE(retryPolicy.shouldRetry(CassandraError{"timeout", CASS_ERROR_LIB_REQUEST_TIMED_OUT}));
EXPECT_TRUE(retryPolicy.shouldRetry(CassandraError{"invalid data", CASS_ERROR_LIB_INVALID_DATA}));
EXPECT_TRUE(retryPolicy.shouldRetry(CassandraError{"invalid query", CASS_ERROR_SERVER_INVALID_QUERY}));
// this policy actually always returns true
auto const err = CassandraError{"ok", CASS_OK};
for (auto i = 0; i < 1024; ++i)
{
EXPECT_TRUE(retryPolicy.shouldRetry(err));
}
}
TEST_F(BackendCassandraRetryPolicyTest, CheckComputedBackoffDelayIsCorrect)
{
auto retryPolicy = ExponentialBackoffRetryPolicy{ctx};
EXPECT_EQ(retryPolicy.calculateDelay(0).count(), 1);
EXPECT_EQ(retryPolicy.calculateDelay(1).count(), 2);
EXPECT_EQ(retryPolicy.calculateDelay(2).count(), 4);
EXPECT_EQ(retryPolicy.calculateDelay(3).count(), 8);
EXPECT_EQ(retryPolicy.calculateDelay(4).count(), 16);
EXPECT_EQ(retryPolicy.calculateDelay(5).count(), 32);
EXPECT_EQ(retryPolicy.calculateDelay(6).count(), 64);
EXPECT_EQ(retryPolicy.calculateDelay(7).count(), 128);
EXPECT_EQ(retryPolicy.calculateDelay(8).count(), 256);
EXPECT_EQ(retryPolicy.calculateDelay(9).count(), 512);
EXPECT_EQ(retryPolicy.calculateDelay(10).count(), 1024);
EXPECT_EQ(retryPolicy.calculateDelay(11).count(),
1024); // 10 is max, same after that
}
TEST_F(BackendCassandraRetryPolicyTest, RetryCorrectlyExecuted)
{
auto callCount = std::atomic_int{0};
auto work = std::optional<boost::asio::io_context::work>{ctx};
auto retryPolicy = ExponentialBackoffRetryPolicy{ctx};
retryPolicy.retry([&callCount]() { ++callCount; });
retryPolicy.retry([&callCount]() { ++callCount; });
retryPolicy.retry([&callCount, &work]() {
++callCount;
work.reset();
});
ctx.run();
ASSERT_EQ(callCount, 3);
}

View File

@@ -0,0 +1,195 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2023, 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/Fixtures.h>
#include <util/TmpFile.h>
#include <data/cassandra/SettingsProvider.h>
#include <util/config/Config.h>
#include <boost/json/parse.hpp>
#include <fmt/core.h>
#include <gtest/gtest.h>
#include <thread>
#include <variant>
using namespace util;
using namespace std;
namespace json = boost::json;
using namespace data::cassandra;
class SettingsProviderTest : public NoLoggerFixture
{
};
TEST_F(SettingsProviderTest, Defaults)
{
Config cfg{json::parse(R"({"contact_points": "127.0.0.1"})")};
SettingsProvider provider{cfg};
auto const settings = provider.getSettings();
EXPECT_EQ(settings.threads, std::thread::hardware_concurrency());
EXPECT_EQ(settings.enableLog, false);
EXPECT_EQ(settings.connectionTimeout, std::chrono::milliseconds{10000});
EXPECT_EQ(settings.requestTimeout, std::chrono::milliseconds{0});
EXPECT_EQ(settings.maxWriteRequestsOutstanding, 10'000);
EXPECT_EQ(settings.maxReadRequestsOutstanding, 100'000);
EXPECT_EQ(settings.maxConnectionsPerHost, 2);
EXPECT_EQ(settings.coreConnectionsPerHost, 2);
EXPECT_EQ(settings.maxConcurrentRequestsThreshold, (100'000 + 10'000) / 2);
EXPECT_EQ(settings.certificate, std::nullopt);
EXPECT_EQ(settings.username, std::nullopt);
EXPECT_EQ(settings.password, std::nullopt);
EXPECT_EQ(settings.queueSizeIO, std::nullopt);
EXPECT_EQ(settings.queueSizeEvent, std::nullopt);
EXPECT_EQ(settings.writeBytesHighWatermark, std::nullopt);
EXPECT_EQ(settings.writeBytesLowWatermark, std::nullopt);
EXPECT_EQ(settings.pendingRequestsHighWatermark, std::nullopt);
EXPECT_EQ(settings.pendingRequestsLowWatermark, std::nullopt);
EXPECT_EQ(settings.maxRequestsPerFlush, std::nullopt);
EXPECT_EQ(settings.maxConcurrentCreation, std::nullopt);
auto const* cp = std::get_if<Settings::ContactPoints>(&settings.connectionInfo);
ASSERT_TRUE(cp != nullptr);
EXPECT_EQ(cp->contactPoints, "127.0.0.1");
EXPECT_FALSE(cp->port);
EXPECT_EQ(provider.getKeyspace(), "clio");
EXPECT_EQ(provider.getReplicationFactor(), 3);
EXPECT_EQ(provider.getTablePrefix(), std::nullopt);
}
TEST_F(SettingsProviderTest, SimpleConfig)
{
Config cfg{json::parse(R"({
"contact_points": "123.123.123.123",
"port": 1234,
"keyspace": "test",
"replication_factor": 42,
"table_prefix": "prefix",
"threads": 24
})")};
SettingsProvider provider{cfg};
auto const settings = provider.getSettings();
EXPECT_EQ(settings.threads, 24);
auto const* cp = std::get_if<Settings::ContactPoints>(&settings.connectionInfo);
ASSERT_TRUE(cp != nullptr);
EXPECT_EQ(cp->contactPoints, "123.123.123.123");
EXPECT_EQ(cp->port, 1234);
EXPECT_EQ(provider.getKeyspace(), "test");
EXPECT_EQ(provider.getReplicationFactor(), 42);
EXPECT_EQ(provider.getTablePrefix(), "prefix");
}
TEST_F(SettingsProviderTest, DriverOptionCalculation)
{
Config cfg{json::parse(R"({
"contact_points": "123.123.123.123",
"max_write_requests_outstanding": 100,
"max_read_requests_outstanding": 200
})")};
SettingsProvider provider{cfg};
auto const settings = provider.getSettings();
EXPECT_EQ(settings.maxReadRequestsOutstanding, 200);
EXPECT_EQ(settings.maxWriteRequestsOutstanding, 100);
EXPECT_EQ(settings.maxConnectionsPerHost, 2);
EXPECT_EQ(settings.coreConnectionsPerHost, 2);
EXPECT_EQ(settings.maxConcurrentRequestsThreshold, 150); // calculated from above
}
TEST_F(SettingsProviderTest, DriverOptionSecifiedMaxConcurrentRequestsThreshold)
{
Config cfg{json::parse(R"({
"contact_points": "123.123.123.123",
"max_write_requests_outstanding": 100,
"max_read_requests_outstanding": 200,
"max_connections_per_host": 5,
"core_connections_per_host": 4,
"max_concurrent_requests_threshold": 1234
})")};
SettingsProvider provider{cfg};
auto const settings = provider.getSettings();
EXPECT_EQ(settings.maxReadRequestsOutstanding, 200);
EXPECT_EQ(settings.maxWriteRequestsOutstanding, 100);
EXPECT_EQ(settings.maxConnectionsPerHost, 5);
EXPECT_EQ(settings.coreConnectionsPerHost, 4);
EXPECT_EQ(settings.maxConcurrentRequestsThreshold, 1234);
}
TEST_F(SettingsProviderTest, DriverOptionalOptionsSpecified)
{
Config cfg{json::parse(R"({
"contact_points": "123.123.123.123",
"queue_size_event": 1,
"queue_size_io": 2,
"write_bytes_high_water_mark": 3,
"write_bytes_low_water_mark": 4,
"pending_requests_high_water_mark": 5,
"pending_requests_low_water_mark": 6,
"max_requests_per_flush": 7,
"max_concurrent_creation": 8
})")};
SettingsProvider provider{cfg};
auto const settings = provider.getSettings();
EXPECT_EQ(settings.queueSizeEvent, 1);
EXPECT_EQ(settings.queueSizeIO, 2);
EXPECT_EQ(settings.writeBytesHighWatermark, 3);
EXPECT_EQ(settings.writeBytesLowWatermark, 4);
EXPECT_EQ(settings.pendingRequestsHighWatermark, 5);
EXPECT_EQ(settings.pendingRequestsLowWatermark, 6);
EXPECT_EQ(settings.maxRequestsPerFlush, 7);
EXPECT_EQ(settings.maxConcurrentCreation, 8);
}
TEST_F(SettingsProviderTest, SecureBundleConfig)
{
Config cfg{json::parse(R"({"secure_connect_bundle": "bundleData"})")};
SettingsProvider provider{cfg};
auto const settings = provider.getSettings();
auto const* sb = std::get_if<Settings::SecureConnectionBundle>(&settings.connectionInfo);
ASSERT_TRUE(sb != nullptr);
EXPECT_EQ(sb->bundle, "bundleData");
}
TEST_F(SettingsProviderTest, CertificateConfig)
{
TmpFile file{"certificateData"};
Config cfg{json::parse(fmt::format(
R"({{
"contact_points": "127.0.0.1",
"certfile": "{}"
}})",
file.path))};
SettingsProvider provider{cfg};
auto const settings = provider.getSettings();
EXPECT_EQ(settings.certificate, "certificateData");
}

View File

@@ -0,0 +1,135 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2023, 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 <data/cassandra/Error.h>
#include <data/cassandra/impl/AsyncExecutor.h>
#include <gmock/gmock.h>
#include <vector>
using namespace data::cassandra;
using namespace data::cassandra::detail;
struct FakeResult
{
};
struct FakeResultOrError
{
CassandraError err{"<default>", CASS_OK};
operator bool() const
{
return err.code() == CASS_OK;
}
CassandraError
error() const
{
return err;
}
FakeResult
value() const
{
return FakeResult{};
}
};
struct FakeMaybeError
{
};
struct FakeStatement
{
};
struct FakePreparedStatement
{
};
struct FakeFuture
{
FakeResultOrError data;
FakeResultOrError
get() const
{
return data;
}
FakeMaybeError
await() const
{
return {};
}
};
struct FakeFutureWithCallback : public FakeFuture
{
};
struct MockHandle
{
using ResultOrErrorType = FakeResultOrError;
using MaybeErrorType = FakeMaybeError;
using FutureWithCallbackType = FakeFutureWithCallback;
using FutureType = FakeFuture;
using StatementType = FakeStatement;
using PreparedStatementType = FakePreparedStatement;
using ResultType = FakeResult;
MOCK_METHOD(
FutureWithCallbackType,
asyncExecute,
(StatementType const&, std::function<void(ResultOrErrorType)>&&),
(const));
MOCK_METHOD(
FutureWithCallbackType,
asyncExecute,
(std::vector<StatementType> const&, std::function<void(ResultOrErrorType)>&&),
(const));
MOCK_METHOD(ResultOrErrorType, execute, (StatementType const&), (const));
};
struct FakeRetryPolicy
{
FakeRetryPolicy(boost::asio::io_context&){}; // required by concept
std::chrono::milliseconds
calculateDelay(uint32_t attempt)
{
return std::chrono::milliseconds{1};
}
bool shouldRetry(CassandraError) const
{
return false;
}
template <typename Fn>
void
retry(Fn&& fn)
{
fn();
}
};