mirror of
https://github.com/XRPLF/clio.git
synced 2025-12-03 10:05:51 +00:00
15
tests/integration/CMakeLists.txt
Normal file
15
tests/integration/CMakeLists.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
add_executable(clio_integration_tests)
|
||||
|
||||
target_sources(
|
||||
clio_integration_tests
|
||||
PRIVATE data/BackendFactoryTests.cpp data/cassandra/BackendTests.cpp data/cassandra/BaseTests.cpp
|
||||
# Test runner
|
||||
TestGlobals.cpp Main.cpp
|
||||
)
|
||||
|
||||
# Fix for dwarf5 bug on ci. IS STILL NEEDED???
|
||||
target_compile_options(clio_options INTERFACE -gdwarf-4)
|
||||
|
||||
target_include_directories(clio_integration_tests PRIVATE .)
|
||||
target_link_libraries(clio_integration_tests PUBLIC clio_testing_common)
|
||||
set_target_properties(clio_integration_tests PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
39
tests/integration/Main.cpp
Normal file
39
tests/integration/Main.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2024, 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/TerminationHandler.hpp"
|
||||
|
||||
#include <TestGlobals.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
/*
|
||||
* Supported custom command line options for clio_tests:
|
||||
* --backend_host=<host> - sets the cassandra/scylladb host for backend tests
|
||||
* --backend_keyspace=<keyspace> - sets the cassandra/scylladb keyspace for backend tests
|
||||
* --clean-gcda - delete all gcda files defore running tests
|
||||
*/
|
||||
int
|
||||
main(int argc, char* argv[])
|
||||
{
|
||||
util::setTerminationHandler();
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
TestGlobals::instance().parse(argc, argv);
|
||||
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
10
tests/integration/README.md
Normal file
10
tests/integration/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Integration testing
|
||||
|
||||
Tests that hit the real database are separate from the unit test suite found under `tests/unit`.
|
||||
|
||||
## Requirements
|
||||
### Cassandra/ScyllaDB cluster
|
||||
If you wish to test the backend component you will need to have access to a **local (127.0.0.1)** Cassandra cluster, opened at port **9042**. Please ensure that the cluster is successfully running before running these tests.
|
||||
|
||||
## Running
|
||||
To run the DB tests, first build Clio as normal, then execute `./clio_integration_tests` to run all database tests.
|
||||
56
tests/integration/TestGlobals.cpp
Normal file
56
tests/integration/TestGlobals.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2024, 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 <TestGlobals.hpp>
|
||||
#include <boost/program_options/options_description.hpp>
|
||||
#include <boost/program_options/parsers.hpp>
|
||||
#include <boost/program_options/value_semantic.hpp>
|
||||
#include <boost/program_options/variables_map.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
TestGlobals&
|
||||
TestGlobals::instance()
|
||||
{
|
||||
static TestGlobals inst;
|
||||
return inst;
|
||||
}
|
||||
|
||||
void
|
||||
TestGlobals::parse(int argc, char* argv[])
|
||||
{
|
||||
namespace po = boost::program_options;
|
||||
|
||||
// clang-format off
|
||||
po::options_description description("Clio UT options");
|
||||
description.add_options()
|
||||
("backend_host", po::value<std::string>()->default_value(TestGlobals::backendHost),
|
||||
"sets the cassandra/scylladb host for backend tests")
|
||||
("backend_keyspace", po::value<std::string>()->default_value(TestGlobals::backendKeyspace),
|
||||
"sets the cassandra/scylladb keyspace for backend tests")
|
||||
;
|
||||
// clang-format on
|
||||
|
||||
po::variables_map parsed;
|
||||
po::store(po::command_line_parser(argc, argv).options(description).run(), parsed);
|
||||
po::notify(parsed);
|
||||
|
||||
backendHost = parsed["backend_host"].as<std::string>();
|
||||
backendKeyspace = parsed["backend_keyspace"].as<std::string>();
|
||||
}
|
||||
47
tests/integration/TestGlobals.hpp
Normal file
47
tests/integration/TestGlobals.hpp
Normal file
@@ -0,0 +1,47 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of clio: https://github.com/XRPLF/clio
|
||||
Copyright (c) 2024, 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.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
/*
|
||||
* Contains global variables for use in tests.
|
||||
*/
|
||||
struct TestGlobals {
|
||||
std::string backendHost = "127.0.0.1";
|
||||
std::string backendKeyspace = "clio_test";
|
||||
|
||||
static TestGlobals&
|
||||
instance();
|
||||
|
||||
void
|
||||
parse(int argc, char* argv[]);
|
||||
|
||||
private:
|
||||
TestGlobals() = default;
|
||||
|
||||
public:
|
||||
TestGlobals(TestGlobals const&) = delete;
|
||||
TestGlobals(TestGlobals&&) = delete;
|
||||
TestGlobals&
|
||||
operator=(TestGlobals const&) = delete;
|
||||
TestGlobals&
|
||||
operator=(TestGlobals&&) = delete;
|
||||
};
|
||||
207
tests/integration/data/BackendFactoryTests.cpp
Normal file
207
tests/integration/data/BackendFactoryTests.cpp
Normal file
@@ -0,0 +1,207 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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.hpp"
|
||||
#include "data/cassandra/Handle.hpp"
|
||||
#include "util/Fixtures.hpp"
|
||||
#include "util/MockPrometheus.hpp"
|
||||
#include "util/config/Config.hpp"
|
||||
|
||||
#include <TestGlobals.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <fmt/core.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
constexpr auto keyspace = "factory_test";
|
||||
} // namespace
|
||||
|
||||
class BackendCassandraFactoryTest : public SyncAsioContextTest, public util::prometheus::WithPrometheus {
|
||||
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 const handle{TestGlobals::instance().backendHost};
|
||||
EXPECT_TRUE(handle.connect());
|
||||
handle.execute("DROP KEYSPACE " + std::string{keyspace});
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(BackendCassandraFactoryTest, NoSuchBackend)
|
||||
{
|
||||
util::Config const cfg{boost::json::parse(
|
||||
R"({
|
||||
"database":
|
||||
{
|
||||
"type":"unknown"
|
||||
}
|
||||
})"
|
||||
)};
|
||||
EXPECT_THROW(make_Backend(cfg), std::runtime_error);
|
||||
}
|
||||
|
||||
TEST_F(BackendCassandraFactoryTest, CreateCassandraBackendDBDisconnect)
|
||||
{
|
||||
util::Config const 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(cfg), std::runtime_error);
|
||||
}
|
||||
|
||||
TEST_F(BackendCassandraFactoryTestWithDB, CreateCassandraBackend)
|
||||
{
|
||||
util::Config const cfg{boost::json::parse(fmt::format(
|
||||
R"({{
|
||||
"database":
|
||||
{{
|
||||
"type": "cassandra",
|
||||
"cassandra": {{
|
||||
"contact_points": "{}",
|
||||
"keyspace": "{}",
|
||||
"replication_factor": 1
|
||||
}}
|
||||
}}
|
||||
}})",
|
||||
TestGlobals::instance().backendHost,
|
||||
keyspace
|
||||
))};
|
||||
|
||||
{
|
||||
auto backend = make_Backend(cfg);
|
||||
EXPECT_TRUE(backend);
|
||||
|
||||
// empty db does not have ledger range
|
||||
EXPECT_FALSE(backend->fetchLedgerRange());
|
||||
|
||||
// insert range table
|
||||
data::cassandra::Handle const handle{TestGlobals::instance().backendHost};
|
||||
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(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 const cfg{boost::json::parse(fmt::format(
|
||||
R"({{
|
||||
"read_only": true,
|
||||
"database":
|
||||
{{
|
||||
"type" : "cassandra",
|
||||
"cassandra" : {{
|
||||
"contact_points": "{}",
|
||||
"keyspace": "{}",
|
||||
"replication_factor": 1
|
||||
}}
|
||||
}}
|
||||
}})",
|
||||
TestGlobals::instance().backendHost,
|
||||
keyspace
|
||||
))};
|
||||
EXPECT_THROW(make_Backend(cfg), std::runtime_error);
|
||||
}
|
||||
|
||||
TEST_F(BackendCassandraFactoryTestWithDB, CreateCassandraBackendReadOnlyWithDBReady)
|
||||
{
|
||||
util::Config const cfgReadOnly{boost::json::parse(fmt::format(
|
||||
R"({{
|
||||
"read_only": true,
|
||||
"database":
|
||||
{{
|
||||
"type" : "cassandra",
|
||||
"cassandra" : {{
|
||||
"contact_points": "{}",
|
||||
"keyspace": "{}",
|
||||
"replication_factor": 1
|
||||
}}
|
||||
}}
|
||||
}})",
|
||||
TestGlobals::instance().backendHost,
|
||||
keyspace
|
||||
))};
|
||||
|
||||
util::Config const cfgWrite{boost::json::parse(fmt::format(
|
||||
R"({{
|
||||
"read_only": false,
|
||||
"database":
|
||||
{{
|
||||
"type" : "cassandra",
|
||||
"cassandra" : {{
|
||||
"contact_points": "{}",
|
||||
"keyspace": "{}",
|
||||
"replication_factor": 1
|
||||
}}
|
||||
}}
|
||||
}})",
|
||||
TestGlobals::instance().backendHost,
|
||||
keyspace
|
||||
))};
|
||||
|
||||
EXPECT_TRUE(make_Backend(cfgWrite));
|
||||
EXPECT_TRUE(make_Backend(cfgReadOnly));
|
||||
}
|
||||
1265
tests/integration/data/cassandra/BackendTests.cpp
Normal file
1265
tests/integration/data/cassandra/BackendTests.cpp
Normal file
File diff suppressed because it is too large
Load Diff
477
tests/integration/data/cassandra/BaseTests.cpp
Normal file
477
tests/integration/data/cassandra/BaseTests.cpp
Normal file
@@ -0,0 +1,477 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/Handle.hpp"
|
||||
#include "data/cassandra/Types.hpp"
|
||||
#include "util/Fixtures.hpp"
|
||||
|
||||
#include <TestGlobals.hpp>
|
||||
#include <cassandra.h>
|
||||
#include <fmt/core.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <optional>
|
||||
#include <semaphore>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
using namespace data::cassandra;
|
||||
|
||||
class BackendCassandraBaseTest : public NoLoggerFixture {
|
||||
protected:
|
||||
static 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;
|
||||
}
|
||||
|
||||
static void
|
||||
dropKeyspace(Handle const& handle, std::string_view keyspace)
|
||||
{
|
||||
std::string const query = "DROP KEYSPACE " + std::string{keyspace};
|
||||
ASSERT_TRUE(handle.execute(query));
|
||||
}
|
||||
|
||||
static 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 const q2 = "INSERT INTO strings (hash, sequence) VALUES (?, ?)";
|
||||
auto const insert = handle.prepare(q2);
|
||||
|
||||
std::vector<Statement> statements;
|
||||
int64_t idx = 1000;
|
||||
|
||||
statements.reserve(entries.size());
|
||||
for (auto const& entry : entries)
|
||||
statements.push_back(insert.bind(entry, idx++));
|
||||
|
||||
EXPECT_EQ(statements.size(), entries.size());
|
||||
EXPECT_TRUE(handle.execute(statements));
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(BackendCassandraBaseTest, ConnectionSuccess)
|
||||
{
|
||||
Handle const handle{TestGlobals::instance().backendHost};
|
||||
auto const f = handle.asyncConnect();
|
||||
auto const res = f.await();
|
||||
|
||||
ASSERT_TRUE(res);
|
||||
}
|
||||
|
||||
TEST_F(BackendCassandraBaseTest, ConnectionFailFormat)
|
||||
{
|
||||
Handle const 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{.contactPoints = "127.0.0.2", .port = std::nullopt};
|
||||
|
||||
Handle const 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 const handle{TestGlobals::instance().backendHost};
|
||||
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 const handle{TestGlobals::instance().backendHost};
|
||||
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 const handle{TestGlobals::instance().backendHost};
|
||||
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
|
||||
}
|
||||
{
|
||||
auto const 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(TestGlobals::instance().backendHost, "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 const q2 = "INSERT INTO strings (hash, sequence) VALUES (?, ?)";
|
||||
auto insert = handle.prepare(q2);
|
||||
|
||||
// write data
|
||||
{
|
||||
std::vector<Future> futures;
|
||||
int64_t idx = 1000;
|
||||
|
||||
futures.reserve(entries.size());
|
||||
for (auto const& entry : entries)
|
||||
futures.push_back(handle.asyncExecute(insert, entry, 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(TestGlobals::instance().backendHost, "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 const 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;
|
||||
|
||||
statements.reserve(entries.size());
|
||||
for (auto const& entry : entries)
|
||||
statements.push_back(insert.bind(entry, 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(TestGlobals::instance().backendHost, "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 const 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;
|
||||
|
||||
statements.reserve(entries.size());
|
||||
for (auto const& entry : entries)
|
||||
statements.push_back(insert.bind(entry, 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(TestGlobals::instance().backendHost, "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 const update = "ALTER TABLE strings ADD tmp blob";
|
||||
ASSERT_TRUE(handle.execute(update));
|
||||
|
||||
dropKeyspace(handle, "test");
|
||||
}
|
||||
|
||||
TEST_F(BackendCassandraBaseTest, AlterTableMoveToNewTable)
|
||||
{
|
||||
auto handle = createHandle(TestGlobals::instance().backendHost, "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, seq, 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");
|
||||
}
|
||||
Reference in New Issue
Block a user