Separate unit tests and integration tests (#1393)

Fixes #1391
This commit is contained in:
Alex Kremer
2024-05-07 15:12:48 +01:00
committed by GitHub
parent 98ef83d470
commit cbc856b190
177 changed files with 168 additions and 106 deletions

View 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})

View 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();
}

View 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.

View 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>();
}

View 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;
};

View 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));
}

File diff suppressed because it is too large Load Diff

View 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");
}