summaryrefslogtreecommitdiffstats
path: root/wsrep-lib/test
diff options
context:
space:
mode:
Diffstat (limited to 'wsrep-lib/test')
-rw-r--r--wsrep-lib/test/CMakeLists.txt47
-rw-r--r--wsrep-lib/test/buffer_test.cpp28
-rw-r--r--wsrep-lib/test/client_state_fixture.hpp305
-rw-r--r--wsrep-lib/test/gtid_test.cpp62
-rw-r--r--wsrep-lib/test/id_test.cpp69
-rw-r--r--wsrep-lib/test/mock_client_state.cpp49
-rw-r--r--wsrep-lib/test/mock_client_state.hpp256
-rw-r--r--wsrep-lib/test/mock_high_priority_service.cpp170
-rw-r--r--wsrep-lib/test/mock_high_priority_service.hpp111
-rw-r--r--wsrep-lib/test/mock_provider.hpp357
-rw-r--r--wsrep-lib/test/mock_server_state.hpp302
-rw-r--r--wsrep-lib/test/mock_storage_service.cpp94
-rw-r--r--wsrep-lib/test/mock_storage_service.hpp63
-rw-r--r--wsrep-lib/test/nbo_test.cpp171
-rw-r--r--wsrep-lib/test/reporter_test.cpp636
-rw-r--r--wsrep-lib/test/rsu_test.cpp36
-rw-r--r--wsrep-lib/test/server_context_test.cpp914
-rw-r--r--wsrep-lib/test/test_utils.cpp70
-rw-r--r--wsrep-lib/test/test_utils.hpp52
-rw-r--r--wsrep-lib/test/toi_test.cpp66
-rw-r--r--wsrep-lib/test/transaction_test.cpp1667
-rw-r--r--wsrep-lib/test/transaction_test_2pc.cpp290
-rw-r--r--wsrep-lib/test/transaction_test_xa.cpp296
-rw-r--r--wsrep-lib/test/view_test.cpp105
-rw-r--r--wsrep-lib/test/wsrep-lib_test.cpp119
-rw-r--r--wsrep-lib/test/xid_test.cpp90
26 files changed, 6425 insertions, 0 deletions
diff --git a/wsrep-lib/test/CMakeLists.txt b/wsrep-lib/test/CMakeLists.txt
new file mode 100644
index 00000000..366cc478
--- /dev/null
+++ b/wsrep-lib/test/CMakeLists.txt
@@ -0,0 +1,47 @@
+#
+# Copyright (C) 2018 Codership Oy <info@codership.com>
+#
+
+
+set(TEST_SOURCES
+ mock_client_state.cpp
+ mock_high_priority_service.cpp
+ mock_storage_service.cpp
+ test_utils.cpp
+ buffer_test.cpp
+ gtid_test.cpp
+ id_test.cpp
+ nbo_test.cpp
+ rsu_test.cpp
+ server_context_test.cpp
+ toi_test.cpp
+ transaction_test.cpp
+ transaction_test_2pc.cpp
+ transaction_test_xa.cpp
+ view_test.cpp
+ xid_test.cpp
+ wsrep-lib_test.cpp
+ )
+
+if (WSREP_LIB_WITH_UNIT_TESTS_EXTRA)
+ set(TEST_SOURCES ${TEST_SOURCES}
+ reporter_test.cpp
+ )
+endif()
+
+add_executable(wsrep-lib_test ${TEST_SOURCES})
+
+target_link_libraries(wsrep-lib_test wsrep-lib)
+
+add_test(NAME wsrep-lib_test
+ COMMAND wsrep-lib_test)
+
+if (WSREP_LIB_WITH_AUTO_TEST)
+ set(UNIT_TEST wsrep-lib_test)
+ add_custom_command(
+ TARGET ${UNIT_TEST}
+ COMMENT "Run tests"
+ POST_BUILD
+ COMMAND ${UNIT_TEST}
+ )
+endif()
diff --git a/wsrep-lib/test/buffer_test.cpp b/wsrep-lib/test/buffer_test.cpp
new file mode 100644
index 00000000..84d4a483
--- /dev/null
+++ b/wsrep-lib/test/buffer_test.cpp
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2019 Codership Oy <info@codership.com>
+ *
+ * This file is part of wsrep-lib.
+ *
+ * Wsrep-lib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Wsrep-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "wsrep/buffer.hpp"
+#include <boost/test/unit_test.hpp>
+
+BOOST_AUTO_TEST_CASE(buffer_test_empty_access)
+{
+ wsrep::mutable_buffer buf;
+ BOOST_REQUIRE(buf.size() == 0);
+ (void)buf.data();
+}
diff --git a/wsrep-lib/test/client_state_fixture.hpp b/wsrep-lib/test/client_state_fixture.hpp
new file mode 100644
index 00000000..a26adeed
--- /dev/null
+++ b/wsrep-lib/test/client_state_fixture.hpp
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2018 Codership Oy <info@codership.com>
+ *
+ * This file is part of wsrep-lib.
+ *
+ * Wsrep-lib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Wsrep-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef WSREP_TEST_CLIENT_CONTEXT_FIXTURE_HPP
+#define WSREP_TEST_CLIENT_CONTEXT_FIXTURE_HPP
+
+#include "mock_server_state.hpp"
+#include "mock_client_state.hpp"
+
+
+#include <boost/test/unit_test.hpp>
+
+namespace
+{
+ struct replicating_client_fixture_sync_rm
+ {
+ replicating_client_fixture_sync_rm()
+ : server_service(sc)
+ , sc("s1", wsrep::server_state::rm_sync, server_service)
+ , cc(sc, wsrep::client_id(1),
+ wsrep::client_state::m_local)
+ , tc(cc.transaction())
+ {
+ sc.mock_connect();
+ cc.open(cc.id());
+ BOOST_REQUIRE(cc.before_command() == 0);
+ BOOST_REQUIRE(cc.before_statement() == 0);
+ // Verify initial state
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+ }
+ wsrep::mock_server_service server_service;
+ wsrep::mock_server_state sc;
+ wsrep::mock_client cc;
+ const wsrep::transaction& tc;
+ };
+
+ struct replicating_two_clients_fixture_sync_rm
+ {
+ replicating_two_clients_fixture_sync_rm()
+ : server_service(sc)
+ , sc("s1", wsrep::server_state::rm_sync, server_service)
+ , cc1(sc, wsrep::client_id(1),
+ wsrep::client_state::m_local)
+ , cc2(sc, wsrep::client_id(2),
+ wsrep::client_state::m_local)
+ , tc(cc1.transaction())
+ {
+ sc.mock_connect();
+ cc1.open(cc1.id());
+ BOOST_REQUIRE(cc1.before_command() == 0);
+ BOOST_REQUIRE(cc1.before_statement() == 0);
+ cc2.open(cc2.id());
+ BOOST_REQUIRE(cc2.before_command() == 0);
+ BOOST_REQUIRE(cc2.before_statement() == 0);
+ // Verify initial state
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+ }
+ wsrep::mock_server_service server_service;
+ wsrep::mock_server_state sc;
+ wsrep::mock_client cc1;
+ wsrep::mock_client cc2;
+ const wsrep::transaction& tc;
+ };
+
+ struct replicating_client_fixture_async_rm
+ {
+ replicating_client_fixture_async_rm()
+ : server_service(sc)
+ , sc("s1", wsrep::server_state::rm_async, server_service)
+ , cc(sc, wsrep::client_id(1),
+ wsrep::client_state::m_local)
+ , tc(cc.transaction())
+ {
+ sc.mock_connect();
+ cc.open(cc.id());
+ BOOST_REQUIRE(cc.before_command() == 0);
+ BOOST_REQUIRE(cc.before_statement() == 0);
+ // Verify initial state
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+ }
+ wsrep::mock_server_service server_service;
+ wsrep::mock_server_state sc;
+ wsrep::mock_client cc;
+ const wsrep::transaction& tc;
+ };
+
+ struct replicating_client_fixture_2pc
+ {
+ replicating_client_fixture_2pc()
+ : server_service(sc)
+ , sc("s1", wsrep::server_state::rm_sync, server_service)
+ , cc(sc, wsrep::client_id(1),
+ wsrep::client_state::m_local)
+ , tc(cc.transaction())
+ {
+ sc.mock_connect();
+ cc.open(cc.id());
+ cc.do_2pc_ = true;
+ BOOST_REQUIRE(cc.before_command() == 0);
+ BOOST_REQUIRE(cc.before_statement() == 0);
+ // Verify initial state
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+ }
+ wsrep::mock_server_service server_service;
+ wsrep::mock_server_state sc;
+ wsrep::mock_client cc;
+ const wsrep::transaction& tc;
+ };
+
+ struct replicating_client_fixture_autocommit
+ {
+ replicating_client_fixture_autocommit()
+ : server_service(sc)
+ , sc("s1", wsrep::server_state::rm_sync, server_service)
+ , cc(sc, wsrep::client_id(1),
+ wsrep::client_state::m_local)
+ , tc(cc.transaction())
+ {
+ sc.mock_connect();
+ cc.open(cc.id());
+ cc.is_autocommit_ = true;
+ BOOST_REQUIRE(cc.before_command() == 0);
+ BOOST_REQUIRE(cc.before_statement() == 0);
+ // Verify initial state
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+ }
+ wsrep::mock_server_service server_service;
+ wsrep::mock_server_state sc;
+ wsrep::mock_client cc;
+ const wsrep::transaction& tc;
+ };
+
+ struct applying_client_fixture
+ {
+ applying_client_fixture()
+ : server_service(sc)
+ , sc("s1",
+ wsrep::server_state::rm_async, server_service)
+ , cc(sc,
+ wsrep::client_id(1),
+ wsrep::client_state::m_high_priority)
+ , tc(cc.transaction())
+ {
+ sc.mock_connect();
+ cc.open(cc.id());
+ BOOST_REQUIRE(cc.before_command() == 0);
+ BOOST_REQUIRE(cc.before_statement() == 0);
+ }
+ void start_transaction(wsrep::transaction_id id,
+ wsrep::seqno seqno)
+ {
+ wsrep::ws_handle ws_handle(id, (void*)1);
+ wsrep::ws_meta ws_meta(wsrep::gtid(wsrep::id("1"), seqno),
+ wsrep::stid(sc.id(),
+ wsrep::transaction_id(1),
+ cc.id()),
+ wsrep::seqno(0),
+ wsrep::provider::flag::start_transaction |
+ wsrep::provider::flag::commit);
+ BOOST_REQUIRE(cc.start_transaction(ws_handle, ws_meta) == 0);
+ BOOST_REQUIRE(tc.active() == true);
+ BOOST_REQUIRE(tc.certified() == true);
+ BOOST_REQUIRE(tc.ordered() == true);
+ }
+
+ wsrep::mock_server_service server_service;
+ wsrep::mock_server_state sc;
+ wsrep::mock_client cc;
+ const wsrep::transaction& tc;
+ };
+
+ struct applying_client_fixture_2pc
+ {
+ applying_client_fixture_2pc()
+ : server_service(sc)
+ , sc("s1",
+ wsrep::server_state::rm_async, server_service)
+ , cc(sc,
+ wsrep::client_id(1),
+ wsrep::client_state::m_high_priority)
+ , tc(cc.transaction())
+ {
+ sc.mock_connect();
+ cc.open(cc.id());
+ cc.do_2pc_ = true;
+ BOOST_REQUIRE(cc.before_command() == 0);
+ BOOST_REQUIRE(cc.before_statement() == 0);
+ wsrep::ws_handle ws_handle(wsrep::transaction_id(1), (void*)1);
+ wsrep::ws_meta ws_meta(wsrep::gtid(wsrep::id("1"), wsrep::seqno(1)),
+ wsrep::stid(sc.id(),
+ wsrep::transaction_id(1),
+ cc.id()),
+ wsrep::seqno(0),
+ wsrep::provider::flag::start_transaction |
+ wsrep::provider::flag::commit);
+ BOOST_REQUIRE(cc.start_transaction(ws_handle, ws_meta) == 0);
+ BOOST_REQUIRE(tc.active() == true);
+ BOOST_REQUIRE(tc.certified() == true);
+ BOOST_REQUIRE(tc.ordered() == true);
+ }
+ wsrep::mock_server_service server_service;
+ wsrep::mock_server_state sc;
+ wsrep::mock_client cc;
+ const wsrep::transaction& tc;
+ };
+
+ struct streaming_client_fixture_row
+ {
+ streaming_client_fixture_row()
+ : server_service(sc)
+ , sc("s1", wsrep::server_state::rm_sync, server_service)
+ , cc(sc,
+ wsrep::client_id(1),
+ wsrep::client_state::m_local)
+ , tc(cc.transaction())
+ {
+ sc.mock_connect();
+ cc.open(cc.id());
+ BOOST_REQUIRE(cc.before_command() == 0);
+ BOOST_REQUIRE(cc.before_statement() == 0);
+ // Verify initial state
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+ cc.enable_streaming(wsrep::streaming_context::row, 1);
+ }
+
+ wsrep::mock_server_service server_service;
+ wsrep::mock_server_state sc;
+ wsrep::mock_client cc;
+ const wsrep::transaction& tc;
+ };
+
+ struct streaming_client_fixture_byte
+ {
+ streaming_client_fixture_byte()
+ : server_service(sc)
+ , sc("s1", wsrep::server_state::rm_sync, server_service)
+ , cc(sc,
+ wsrep::client_id(1),
+ wsrep::client_state::m_local)
+ , tc(cc.transaction())
+ {
+ sc.mock_connect();
+ cc.open(cc.id());
+ BOOST_REQUIRE(cc.before_command() == 0);
+ BOOST_REQUIRE(cc.before_statement() == 0);
+ // Verify initial state
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+ cc.enable_streaming(wsrep::streaming_context::bytes, 1);
+ }
+ wsrep::mock_server_service server_service;
+ wsrep::mock_server_state sc;
+ wsrep::mock_client cc;
+ const wsrep::transaction& tc;
+ };
+
+ struct streaming_client_fixture_statement
+ {
+ streaming_client_fixture_statement()
+ : server_service(sc)
+ , sc("s1", wsrep::server_state::rm_sync, server_service)
+ , cc(sc,
+ wsrep::client_id(1),
+ wsrep::client_state::m_local)
+ , tc(cc.transaction())
+ {
+ sc.mock_connect();
+ cc.open(cc.id());
+ BOOST_REQUIRE(cc.before_command() == 0);
+ BOOST_REQUIRE(cc.before_statement() == 0);
+ // Verify initial state
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+ cc.enable_streaming(wsrep::streaming_context::statement, 1);
+ }
+
+ wsrep::mock_server_service server_service;
+ wsrep::mock_server_state sc;
+ wsrep::mock_client cc;
+ const wsrep::transaction& tc;
+ };
+}
+#endif // WSREP_TEST_CLIENT_CONTEXT_FIXTURE_HPP
diff --git a/wsrep-lib/test/gtid_test.cpp b/wsrep-lib/test/gtid_test.cpp
new file mode 100644
index 00000000..4f4d644b
--- /dev/null
+++ b/wsrep-lib/test/gtid_test.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2018 Codership Oy <info@codership.com>
+ *
+ * This file is part of wsrep-lib.
+ *
+ * Wsrep-lib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Wsrep-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "wsrep/gtid.hpp"
+#include <boost/test/unit_test.hpp>
+
+BOOST_AUTO_TEST_CASE(gtid_test_scan_from_string_uuid)
+{
+ std::string gtid_str("6a20d44a-6e17-11e8-b1e2-9061aec0cdad:123456");
+ wsrep::gtid gtid;
+ ssize_t ret(wsrep::scan_from_c_str(
+ gtid_str.c_str(),
+ gtid_str.size(), gtid));
+ BOOST_REQUIRE_MESSAGE(ret == ssize_t(gtid_str.size()),
+ "Expected " << gtid_str.size() << " got " << ret);
+ BOOST_REQUIRE(gtid.seqno().get() == 123456);
+}
+
+BOOST_AUTO_TEST_CASE(gtid_test_scan_from_string_uuid_too_long)
+{
+ std::string gtid_str("6a20d44a-6e17-11e8-b1e2-9061aec0cdadx:123456");
+ wsrep::gtid gtid;
+ ssize_t ret(wsrep::scan_from_c_str(
+ gtid_str.c_str(),
+ gtid_str.size(), gtid));
+ BOOST_REQUIRE_MESSAGE(ret == -EINVAL,
+ "Expected " << -EINVAL << " got " << ret);
+}
+
+BOOST_AUTO_TEST_CASE(gtid_test_scan_from_string_seqno_out_of_range)
+{
+ std::string gtid_str("6a20d44a-6e17-11e8-b1e2-9061aec0cdad:9223372036854775808");
+ wsrep::gtid gtid;
+ ssize_t ret(wsrep::scan_from_c_str(
+ gtid_str.c_str(),
+ gtid_str.size(), gtid));
+ BOOST_REQUIRE_MESSAGE(ret == -EINVAL,
+ "Expected " << -EINVAL << " got " << ret);
+
+ gtid_str = "6a20d44a-6e17-11e8-b1e2-9061aec0cdad:-9223372036854775809";
+ ret = wsrep::scan_from_c_str(
+ gtid_str.c_str(),
+ gtid_str.size(), gtid);
+ BOOST_REQUIRE_MESSAGE(ret == -EINVAL,
+ "Expected " << -EINVAL << " got " << ret);
+}
diff --git a/wsrep-lib/test/id_test.cpp b/wsrep-lib/test/id_test.cpp
new file mode 100644
index 00000000..5a87ba16
--- /dev/null
+++ b/wsrep-lib/test/id_test.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2018 Codership Oy <info@codership.com>
+ *
+ * This file is part of wsrep-lib.
+ *
+ * Wsrep-lib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Wsrep-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "wsrep/id.hpp"
+#include <boost/test/unit_test.hpp>
+
+#include <sstream>
+
+namespace
+{
+ bool exception_check(const wsrep::runtime_error&) { return true; }
+}
+
+BOOST_AUTO_TEST_CASE(id_test_uuid)
+{
+ std::string uuid_str("6a20d44a-6e17-11e8-b1e2-9061aec0cdad");
+ wsrep::id id(uuid_str);
+ std::ostringstream os;
+ os << id;
+ BOOST_REQUIRE(uuid_str == os.str());
+}
+
+BOOST_AUTO_TEST_CASE(id_test_string)
+{
+ std::string id_str("1234567890123456");
+ wsrep::id id(id_str);
+ std::ostringstream os;
+ os << id;
+ BOOST_REQUIRE(id_str == os.str());
+}
+
+BOOST_AUTO_TEST_CASE(id_test_string_too_long)
+{
+ std::string id_str("12345678901234567");
+ BOOST_REQUIRE_EXCEPTION(wsrep::id id(id_str), wsrep::runtime_error,
+ exception_check);
+}
+
+BOOST_AUTO_TEST_CASE(id_test_binary)
+{
+ char data[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4 ,5 ,6};
+ wsrep::id id(data, sizeof(data));
+ std::ostringstream os;
+ os << id;
+ BOOST_REQUIRE(os.str() == "01020304-0506-0708-0900-010203040506");
+}
+
+BOOST_AUTO_TEST_CASE(id_test_binary_too_long)
+{
+ char data[17] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4 ,5 ,6, 7};
+ BOOST_REQUIRE_EXCEPTION(wsrep::id id(data, sizeof(data)),
+ wsrep::runtime_error, exception_check);;
+}
diff --git a/wsrep-lib/test/mock_client_state.cpp b/wsrep-lib/test/mock_client_state.cpp
new file mode 100644
index 00000000..03e90d04
--- /dev/null
+++ b/wsrep-lib/test/mock_client_state.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2018 Codership Oy <info@codership.com>
+ *
+ * This file is part of wsrep-lib.
+ *
+ * Wsrep-lib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Wsrep-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "wsrep/transaction.hpp"
+#include "mock_client_state.hpp"
+#include "mock_high_priority_service.hpp"
+
+int wsrep::mock_client_service::bf_rollback()
+{
+ int ret(0);
+ if (client_state_.before_rollback())
+ {
+ ret = 1;
+ }
+ else if (client_state_.after_rollback())
+ {
+ ret = 1;
+ }
+ return ret;
+}
+
+enum wsrep::provider::status
+wsrep::mock_client_service::replay()
+{
+ wsrep::mock_high_priority_service hps(client_state_.server_state(),
+ &client_state_, true);
+ enum wsrep::provider::status ret(
+ client_state_.provider().replay(
+ client_state_.transaction().ws_handle(),
+ &hps));
+ ++replays_;
+ return ret;
+}
diff --git a/wsrep-lib/test/mock_client_state.hpp b/wsrep-lib/test/mock_client_state.hpp
new file mode 100644
index 00000000..56434f73
--- /dev/null
+++ b/wsrep-lib/test/mock_client_state.hpp
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2018 Codership Oy <info@codership.com>
+ *
+ * This file is part of wsrep-lib.
+ *
+ * Wsrep-lib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Wsrep-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef WSREP_MOCK_CLIENT_CONTEXT_HPP
+#define WSREP_MOCK_CLIENT_CONTEXT_HPP
+
+#include "wsrep/client_state.hpp"
+#include "wsrep/mutex.hpp"
+#include "wsrep/compiler.hpp"
+
+#include "test_utils.hpp"
+
+namespace wsrep
+{
+ class mock_client_state : public wsrep::client_state
+ {
+ public:
+ mock_client_state(wsrep::server_state& server_state,
+ wsrep::client_service& client_service,
+ const wsrep::client_id& id,
+ enum wsrep::client_state::mode mode)
+ : wsrep::client_state(mutex_, cond_, server_state, client_service,
+ id, mode)
+ , mutex_()
+ , cond_()
+ { }
+ ~mock_client_state()
+ {
+ if (transaction().active())
+ {
+ (void)client_service().bf_rollback();
+ }
+ }
+ private:
+ wsrep::default_mutex mutex_;
+ wsrep::default_condition_variable cond_;
+ public:
+ private:
+ };
+
+
+ class mock_client_service : public wsrep::client_service
+ {
+ public:
+ mock_client_service(wsrep::mock_client_state& client_state)
+ : wsrep::client_service()
+ , is_autocommit_()
+ , do_2pc_()
+ // , fail_next_applying_()
+ // , fail_next_toi_()
+ , bf_abort_during_wait_()
+ , bf_abort_during_fragment_removal_()
+ , error_during_prepare_data_()
+ , killed_before_certify_()
+ , sync_point_enabled_()
+ , sync_point_action_()
+ , bytes_generated_()
+ , client_state_(client_state)
+ , will_replay_called_()
+ , replays_()
+ , unordered_replays_()
+ , aborts_()
+ { }
+
+ int bf_rollback() WSREP_OVERRIDE;
+
+ bool interrupted(wsrep::unique_lock<wsrep::mutex>&)
+ const WSREP_OVERRIDE
+ { return killed_before_certify_; }
+
+
+ void emergency_shutdown() WSREP_OVERRIDE { ++aborts_; }
+
+ int remove_fragments() WSREP_OVERRIDE
+ {
+ if (bf_abort_during_fragment_removal_)
+ {
+ client_state_.before_rollback();
+ client_state_.after_rollback();
+ return 1;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+
+ void will_replay() WSREP_OVERRIDE { will_replay_called_ = true; }
+
+ void signal_replayed() WSREP_OVERRIDE { }
+
+ enum wsrep::provider::status replay() WSREP_OVERRIDE;
+
+ enum wsrep::provider::status replay_unordered() WSREP_OVERRIDE
+ {
+ unordered_replays_++;
+ return wsrep::provider::success;
+ }
+
+ void wait_for_replayers(
+ wsrep::unique_lock<wsrep::mutex>& lock)
+ WSREP_OVERRIDE
+ {
+ lock.unlock();
+ if (bf_abort_during_wait_)
+ {
+ wsrep_test::bf_abort_unordered(client_state_);
+ }
+ lock.lock();
+ }
+
+ int prepare_data_for_replication() WSREP_OVERRIDE
+ {
+ if (error_during_prepare_data_)
+ {
+ return 1;
+ }
+ static const char buf[1] = { 1 };
+ wsrep::const_buffer data = wsrep::const_buffer(buf, 1);
+ return client_state_.append_data(data);
+ }
+
+ void cleanup_transaction() WSREP_OVERRIDE { }
+
+ size_t bytes_generated() const WSREP_OVERRIDE
+ {
+ return bytes_generated_;
+ }
+
+ bool statement_allowed_for_streaming() const WSREP_OVERRIDE
+ { return true; }
+ int prepare_fragment_for_replication(wsrep::mutable_buffer& buffer, size_t& position)
+ WSREP_OVERRIDE
+ {
+ if (error_during_prepare_data_)
+ {
+ return 1;
+ }
+ static const char buf[1] = { 1 };
+ buffer.push_back(&buf[0], &buf[1]);
+ wsrep::const_buffer data(buffer.data(), buffer.size());
+ position = buffer.size();
+ return client_state_.append_data(data);
+ }
+
+ void store_globals() WSREP_OVERRIDE { }
+ void reset_globals() WSREP_OVERRIDE { }
+
+ enum wsrep::provider::status commit_by_xid() WSREP_OVERRIDE
+ {
+ return wsrep::provider::success;
+ }
+
+ bool is_explicit_xa() WSREP_OVERRIDE
+ {
+ return false;
+ }
+
+ bool is_xa_rollback() WSREP_OVERRIDE
+ {
+ return false;
+ }
+
+ void debug_sync(const char* sync_point) WSREP_OVERRIDE
+ {
+ if (sync_point_enabled_ == sync_point)
+ {
+ switch (sync_point_action_)
+ {
+ case spa_bf_abort_unordered:
+ wsrep_test::bf_abort_unordered(client_state_);
+ break;
+ case spa_bf_abort_ordered:
+ wsrep_test::bf_abort_ordered(client_state_);
+ break;
+ }
+ }
+ }
+
+ void debug_crash(const char*) WSREP_OVERRIDE
+ {
+ // Not going to do this while unit testing
+ }
+
+
+ //
+ // Knobs to tune the behavior
+ //
+ bool is_autocommit_;
+ bool do_2pc_;
+ // bool fail_next_applying_;
+ // bool fail_next_toi_;
+ bool bf_abort_during_wait_;
+ bool bf_abort_during_fragment_removal_;
+ bool error_during_prepare_data_;
+ bool killed_before_certify_;
+ std::string sync_point_enabled_;
+ enum sync_point_action
+ {
+ spa_bf_abort_unordered,
+ spa_bf_abort_ordered
+ } sync_point_action_;
+ size_t bytes_generated_;
+
+ //
+ // Verifying the state
+ //
+ bool will_replay_called() const { return will_replay_called_; }
+ size_t replays() const { return replays_; }
+ size_t unordered_replays() const { return unordered_replays_; }
+ size_t aborts() const { return aborts_; }
+ private:
+ wsrep::mock_client_state& client_state_;
+ bool will_replay_called_;
+ size_t replays_;
+ size_t unordered_replays_;
+ size_t aborts_;
+ };
+
+ class mock_client
+ : public mock_client_state
+ , public mock_client_service
+ {
+ public:
+ mock_client(wsrep::server_state& server_state,
+ const wsrep::client_id& id,
+ enum wsrep::client_state::mode mode)
+ : mock_client_state(server_state, *this, id, mode)
+ , mock_client_service(static_cast<mock_client_state&>(*this))
+ { }
+
+ int after_row()
+ {
+ bytes_generated_++;
+ return wsrep::client_state::after_row();
+ }
+ };
+}
+
+#endif // WSREP_MOCK_CLIENT_CONTEXT_HPP
diff --git a/wsrep-lib/test/mock_high_priority_service.cpp b/wsrep-lib/test/mock_high_priority_service.cpp
new file mode 100644
index 00000000..bb67c9a9
--- /dev/null
+++ b/wsrep-lib/test/mock_high_priority_service.cpp
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2018-2019 Codership Oy <info@codership.com>
+ *
+ * This file is part of wsrep-lib.
+ *
+ * Wsrep-lib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Wsrep-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "mock_high_priority_service.hpp"
+#include "mock_server_state.hpp"
+#include <sstream>
+
+int wsrep::mock_high_priority_service::start_transaction(
+ const wsrep::ws_handle& ws_handle, const wsrep::ws_meta& ws_meta)
+{
+ return client_state_->start_transaction(ws_handle, ws_meta);
+}
+
+int wsrep::mock_high_priority_service::next_fragment(
+ const wsrep::ws_meta& ws_meta)
+{
+ return client_state_->next_fragment(ws_meta);
+}
+
+int wsrep::mock_high_priority_service::adopt_transaction(
+ const wsrep::transaction& transaction)
+{
+ client_state_->adopt_transaction(transaction);
+ if (transaction.state() == wsrep::transaction::s_prepared)
+ {
+ client_state_->restore_xid(transaction.xid());
+ }
+ return 0;
+}
+
+int wsrep::mock_high_priority_service::apply_write_set(
+ const wsrep::ws_meta& meta,
+ const wsrep::const_buffer&,
+ wsrep::mutable_buffer& err)
+{
+ assert(client_state_->toi_meta().seqno().is_undefined());
+ assert(client_state_->transaction().state() == wsrep::transaction::s_executing ||
+ client_state_->transaction().state() == wsrep::transaction::s_prepared ||
+ client_state_->transaction().state() == wsrep::transaction::s_replaying);
+ if (fail_next_applying_)
+ {
+ std::ostringstream os;
+ os << "failed " << meta;
+ err.push_back(os.str());
+ assert(err.size() > 0);
+ return 1;
+ }
+ else
+ {
+ int ret(0);
+ if (!(meta.flags() & wsrep::provider::flag::commit))
+ {
+ client_state_->fragment_applied(meta.seqno());
+ }
+ if ((meta.flags() & wsrep::provider::flag::prepare))
+ {
+ client_state_->assign_xid(wsrep::xid(1, 3, 1, "xid"));
+ ret = client_state_->before_prepare() ||
+ client_state_->after_prepare();
+ }
+ return ret;
+ };
+}
+
+int wsrep::mock_high_priority_service::commit(
+ const wsrep::ws_handle& ws_handle,
+ const wsrep::ws_meta& ws_meta)
+{
+ int ret(0);
+ client_state_->prepare_for_ordering(ws_handle, ws_meta, true);
+ if (do_2pc_)
+ {
+ ret = client_state_->before_prepare() ||
+ client_state_->after_prepare();
+ }
+ const bool is_ordered= !ws_meta.seqno().is_undefined();
+ if (!is_ordered)
+ {
+ client_state_->before_rollback();
+ client_state_->after_rollback();
+ return 0;
+ }
+ else
+ {
+ return (ret || client_state_->before_commit() ||
+ client_state_->ordered_commit() ||
+ client_state_->after_commit());
+ }
+}
+
+int wsrep::mock_high_priority_service::rollback(
+ const wsrep::ws_handle& ws_handle,
+ const wsrep::ws_meta& ws_meta)
+{
+ client_state_->prepare_for_ordering(ws_handle, ws_meta, false);
+ return (client_state_->before_rollback() ||
+ client_state_->after_rollback());
+}
+
+int wsrep::mock_high_priority_service::apply_toi(const wsrep::ws_meta&,
+ const wsrep::const_buffer&,
+ wsrep::mutable_buffer&)
+{
+ assert(client_state_->transaction().active() == false);
+ assert(client_state_->toi_meta().seqno().is_undefined() == false);
+ return (fail_next_toi_ ? 1 : 0);
+}
+
+int wsrep::mock_high_priority_service::apply_nbo_begin(
+ const wsrep::ws_meta& ws_meta,
+ const wsrep::const_buffer&,
+ wsrep::mutable_buffer&)
+{
+ const int nbo_begin_flags __attribute__((unused))
+ (wsrep::provider::flag::isolation |
+ wsrep::provider::flag::start_transaction);
+ assert(ws_meta.flags() & nbo_begin_flags);
+ assert((ws_meta.flags() & ~nbo_begin_flags) == 0);
+
+ if (fail_next_toi_)
+ {
+ return 1;
+ }
+ else
+ {
+ nbo_cs_ = std::unique_ptr<wsrep::mock_client>(
+ new wsrep::mock_client(client_state_->server_state(),
+ wsrep::client_id(1),
+ wsrep::client_state::m_local));
+ nbo_cs_->open(wsrep::client_id(1));
+ nbo_cs_->before_command();
+ nbo_cs_->before_statement();
+ return nbo_cs_->enter_nbo_mode(ws_meta);
+ }
+}
+
+void wsrep::mock_high_priority_service::adopt_apply_error(
+ wsrep::mutable_buffer& err)
+{
+ client_state_->adopt_apply_error(err);
+}
+
+void wsrep::mock_high_priority_service::after_apply()
+{
+ client_state_->after_applying();
+}
+
+int wsrep::mock_high_priority_service::log_dummy_write_set(
+ const wsrep::ws_handle&,
+ const wsrep::ws_meta&,
+ wsrep::mutable_buffer& err)
+{
+ return err.size() > 0;
+}
diff --git a/wsrep-lib/test/mock_high_priority_service.hpp b/wsrep-lib/test/mock_high_priority_service.hpp
new file mode 100644
index 00000000..caf40b2c
--- /dev/null
+++ b/wsrep-lib/test/mock_high_priority_service.hpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2018-2019 Codership Oy <info@codership.com>
+ *
+ * This file is part of wsrep-lib.
+ *
+ * Wsrep-lib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Wsrep-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef WSREP_MOCK_HIGH_PRIORITY_SERVICE_HPP
+#define WSREP_MOCK_HIGH_PRIORITY_SERVICE_HPP
+
+#include "wsrep/high_priority_service.hpp"
+#include "mock_client_state.hpp"
+
+#include <memory>
+
+namespace wsrep
+{
+ class mock_high_priority_service : public wsrep::high_priority_service
+ {
+ public:
+ mock_high_priority_service(
+ wsrep::server_state& server_state,
+ wsrep::mock_client_state* client_state,
+ bool replaying)
+ : wsrep::high_priority_service(server_state)
+ , do_2pc_()
+ , fail_next_applying_()
+ , fail_next_toi_()
+ , client_state_(client_state)
+ , replaying_(replaying)
+ , nbo_cs_()
+ { }
+
+ ~mock_high_priority_service()
+ { }
+ int start_transaction(const wsrep::ws_handle&, const wsrep::ws_meta&)
+ WSREP_OVERRIDE;
+
+ int next_fragment(const wsrep::ws_meta&) WSREP_OVERRIDE;
+
+ const wsrep::transaction& transaction() const WSREP_OVERRIDE
+ { return client_state_->transaction(); }
+ int adopt_transaction(const wsrep::transaction&) WSREP_OVERRIDE;
+ int apply_write_set(const wsrep::ws_meta&,
+ const wsrep::const_buffer&,
+ wsrep::mutable_buffer&) WSREP_OVERRIDE;
+ int append_fragment_and_commit(
+ const wsrep::ws_handle&,
+ const wsrep::ws_meta&,
+ const wsrep::const_buffer&,
+ const wsrep::xid&) WSREP_OVERRIDE
+ { return 0; }
+ int remove_fragments(const wsrep::ws_meta&) WSREP_OVERRIDE
+ { return 0; }
+ int commit(const wsrep::ws_handle&, const wsrep::ws_meta&)
+ WSREP_OVERRIDE;
+ int rollback(const wsrep::ws_handle&, const wsrep::ws_meta&) WSREP_OVERRIDE;
+ int apply_toi(const wsrep::ws_meta&,
+ const wsrep::const_buffer&,
+ wsrep::mutable_buffer&) WSREP_OVERRIDE;
+ int apply_nbo_begin(const wsrep::ws_meta&,
+ const wsrep::const_buffer&,
+ wsrep::mutable_buffer&) WSREP_OVERRIDE;
+ void adopt_apply_error(wsrep::mutable_buffer& err) WSREP_OVERRIDE;
+ void after_apply() WSREP_OVERRIDE;
+ void store_globals() WSREP_OVERRIDE { }
+ void reset_globals() WSREP_OVERRIDE { }
+ void switch_execution_context(wsrep::high_priority_service&)
+ WSREP_OVERRIDE { }
+ int log_dummy_write_set(const wsrep::ws_handle&,
+ const wsrep::ws_meta&,
+ wsrep::mutable_buffer&) WSREP_OVERRIDE;
+ bool is_replaying() const WSREP_OVERRIDE { return replaying_; }
+ void debug_crash(const char*) WSREP_OVERRIDE { /* Not in unit tests*/}
+
+ wsrep::client_state& client_state()
+ {
+ return *client_state_;
+ }
+ bool do_2pc_;
+ bool fail_next_applying_;
+ bool fail_next_toi_;
+
+ wsrep::mock_client* nbo_cs() const { return nbo_cs_.get(); }
+
+ private:
+ mock_high_priority_service(const mock_high_priority_service&);
+ mock_high_priority_service& operator=(const mock_high_priority_service&);
+ wsrep::mock_client_state* client_state_;
+ bool replaying_;
+
+ /* Client state associated to NBO processing. This should be
+ * stored elsewhere (like mock_server_state), but is kept
+ * here for convenience. */
+ std::unique_ptr<wsrep::mock_client> nbo_cs_;
+ };
+}
+
+#endif // WSREP_MOCK_HIGH_PRIORITY_SERVICE_HPP
diff --git a/wsrep-lib/test/mock_provider.hpp b/wsrep-lib/test/mock_provider.hpp
new file mode 100644
index 00000000..c2a89f05
--- /dev/null
+++ b/wsrep-lib/test/mock_provider.hpp
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2018-2019 Codership Oy <info@codership.com>
+ *
+ * This file is part of wsrep-lib.
+ *
+ * Wsrep-lib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Wsrep-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef WSREP_MOCK_PROVIDER_HPP
+#define WSREP_MOCK_PROVIDER_HPP
+
+#include "wsrep/provider.hpp"
+#include "wsrep/logger.hpp"
+#include "wsrep/buffer.hpp"
+#include "wsrep/high_priority_service.hpp"
+
+#include <cstring>
+#include <map>
+#include <iostream> // todo: proper logging
+
+#include <boost/test/unit_test.hpp>
+
+namespace wsrep
+{
+ class mock_provider : public wsrep::provider
+ {
+ public:
+ typedef std::map<wsrep::transaction_id, wsrep::seqno> bf_abort_map;
+
+ mock_provider(wsrep::server_state& server_state)
+ : provider(server_state)
+ , certify_result_()
+ , commit_order_enter_result_()
+ , commit_order_leave_result_()
+ , release_result_()
+ , replay_result_()
+ , group_id_("1")
+ , server_id_("1")
+ , group_seqno_(0)
+ , bf_abort_map_()
+ , start_fragments_()
+ , fragments_()
+ , commit_fragments_()
+ , rollback_fragments_()
+ , toi_write_sets_()
+ , toi_start_transaction_()
+ , toi_commit_()
+ { }
+
+ enum wsrep::provider::status
+ connect(const std::string&, const std::string&, const std::string&,
+ bool) WSREP_OVERRIDE
+ { return wsrep::provider::success; }
+ int disconnect() WSREP_OVERRIDE { return 0; }
+ int capabilities() const WSREP_OVERRIDE { return 0; }
+ int desync() WSREP_OVERRIDE { return 0; }
+ int resync() WSREP_OVERRIDE { return 0; }
+ wsrep::seqno pause() WSREP_OVERRIDE { return wsrep::seqno(0); }
+ int resume() WSREP_OVERRIDE { return 0; }
+ enum wsrep::provider::status run_applier(wsrep::high_priority_service*)
+ WSREP_OVERRIDE
+ {
+ return wsrep::provider::success;
+ }
+ // Provider implemenatation interface
+ int start_transaction(wsrep::ws_handle&) WSREP_OVERRIDE { return 0; }
+ enum wsrep::provider::status
+ certify(wsrep::client_id client_id,
+ wsrep::ws_handle& ws_handle,
+ int flags,
+ wsrep::ws_meta& ws_meta)
+ WSREP_OVERRIDE
+ {
+ ws_handle = wsrep::ws_handle(ws_handle.transaction_id(), (void*)1);
+ wsrep::log_debug() << "provider certify: "
+ << "client: " << client_id.get()
+ << " flags: " << std::hex << flags
+ << std::dec
+ << " certify_status: " << certify_result_;
+ if (certify_result_)
+ {
+ return certify_result_;
+ }
+
+ ++fragments_;
+ if (starts_transaction(flags))
+ {
+ ++start_fragments_;
+ }
+ if (commits_transaction(flags))
+ {
+ ++commit_fragments_;
+ }
+ if (rolls_back_transaction(flags))
+ {
+ ++rollback_fragments_;
+ }
+
+ wsrep::stid stid(server_id_,
+ ws_handle.transaction_id(),
+ client_id);
+ bf_abort_map::iterator it(bf_abort_map_.find(
+ ws_handle.transaction_id()));
+ if (it == bf_abort_map_.end())
+ {
+ ++group_seqno_;
+ wsrep::gtid gtid(group_id_, wsrep::seqno(group_seqno_));
+ ws_meta = wsrep::ws_meta(gtid, stid,
+ wsrep::seqno(group_seqno_ - 1),
+ flags);
+ return wsrep::provider::success;
+ }
+ else
+ {
+ enum wsrep::provider::status ret;
+ if (it->second.is_undefined())
+ {
+ ws_meta = wsrep::ws_meta(wsrep::gtid(), wsrep::stid(),
+ wsrep::seqno::undefined(), 0);
+ ret = wsrep::provider::error_certification_failed;
+ }
+ else
+ {
+ ++group_seqno_;
+ wsrep::gtid gtid(group_id_, wsrep::seqno(group_seqno_));
+ ws_meta = wsrep::ws_meta(gtid, stid,
+ wsrep::seqno(group_seqno_ - 1),
+ flags);
+ ret = wsrep::provider::error_bf_abort;
+ }
+ bf_abort_map_.erase(it);
+ return ret;
+ }
+ }
+
+ enum wsrep::provider::status
+ assign_read_view(wsrep::ws_handle&, const wsrep::gtid*)
+ WSREP_OVERRIDE
+ { return wsrep::provider::success; }
+ int append_key(wsrep::ws_handle&, const wsrep::key&)
+ WSREP_OVERRIDE
+ { return 0; }
+ enum wsrep::provider::status
+ append_data(wsrep::ws_handle&, const wsrep::const_buffer&)
+ WSREP_OVERRIDE
+ { return wsrep::provider::success; }
+ enum wsrep::provider::status rollback(const wsrep::transaction_id)
+ WSREP_OVERRIDE
+ {
+ ++fragments_;
+ ++rollback_fragments_;
+ return wsrep::provider::success;
+ }
+ enum wsrep::provider::status
+ commit_order_enter(const wsrep::ws_handle& ws_handle,
+ const wsrep::ws_meta& ws_meta)
+ WSREP_OVERRIDE
+ {
+ BOOST_REQUIRE(ws_handle.opaque());
+ BOOST_REQUIRE(ws_meta.seqno().is_undefined() == false);
+ return commit_order_enter_result_;
+ }
+
+ int commit_order_leave(const wsrep::ws_handle& ws_handle,
+ const wsrep::ws_meta& ws_meta,
+ const wsrep::mutable_buffer& err)
+ WSREP_OVERRIDE
+ {
+ BOOST_REQUIRE(ws_handle.opaque());
+ BOOST_REQUIRE(ws_meta.seqno().is_undefined() == false);
+ return err.size() > 0 ?
+ wsrep::provider::error_fatal :
+ commit_order_leave_result_;
+ }
+
+ int release(wsrep::ws_handle& )
+ WSREP_OVERRIDE
+ {
+ // BOOST_REQUIRE(ws_handle.opaque());
+ return release_result_;
+ }
+
+ enum wsrep::provider::status replay(
+ const wsrep::ws_handle& ws_handle,
+ wsrep::high_priority_service* hps)
+ WSREP_OVERRIDE
+ {
+ wsrep::mock_high_priority_service& high_priority_service(
+ *static_cast<wsrep::mock_high_priority_service*>(hps));
+ wsrep::mock_client_state& cc(
+ static_cast<wsrep::mock_client_state&>(
+ high_priority_service.client_state()));
+ wsrep::high_priority_context high_priority_context(cc);
+ const wsrep::transaction& tc(cc.transaction());
+ wsrep::ws_meta ws_meta;
+ if (replay_result_ == wsrep::provider::success)
+ {
+ // If the ws_meta was not assigned yet, the certify
+ // returned early due to BF abort.
+ if (tc.ws_meta().seqno().is_undefined())
+ {
+ ++group_seqno_;
+ ws_meta = wsrep::ws_meta(
+ wsrep::gtid(group_id_, wsrep::seqno(group_seqno_)),
+ wsrep::stid(server_id_, tc.id(), cc.id()),
+ wsrep::seqno(group_seqno_ - 1),
+ wsrep::provider::flag::start_transaction |
+ wsrep::provider::flag::commit);
+ }
+ else
+ {
+ ws_meta = tc.ws_meta();
+ }
+ }
+ else
+ {
+ return replay_result_;
+ }
+
+ if (high_priority_service.apply(ws_handle, ws_meta,
+ wsrep::const_buffer()))
+ {
+ return wsrep::provider::error_fatal;
+ }
+ return wsrep::provider::success;
+ }
+
+ enum wsrep::provider::status enter_toi(wsrep::client_id client_id,
+ const wsrep::key_array&,
+ const wsrep::const_buffer&,
+ wsrep::ws_meta& toi_meta,
+ int flags)
+ WSREP_OVERRIDE
+ {
+ ++group_seqno_;
+ wsrep::gtid gtid(group_id_, wsrep::seqno(group_seqno_));
+ wsrep::stid stid(server_id_,
+ wsrep::transaction_id::undefined(),
+ client_id);
+ toi_meta = wsrep::ws_meta(gtid, stid,
+ wsrep::seqno(group_seqno_ - 1),
+ flags);
+ ++toi_write_sets_;
+ if (flags & wsrep::provider::flag::start_transaction)
+ ++toi_start_transaction_;
+ if (flags & wsrep::provider::flag::commit)
+ ++toi_commit_;
+ return certify_result_;
+ }
+
+ enum wsrep::provider::status leave_toi(wsrep::client_id,
+ const wsrep::mutable_buffer&)
+ WSREP_OVERRIDE
+ { return wsrep::provider::success; }
+
+ std::pair<wsrep::gtid, enum wsrep::provider::status>
+ causal_read(int) const WSREP_OVERRIDE
+ {
+ return std::make_pair(wsrep::gtid::undefined(),
+ wsrep::provider::error_not_implemented);
+ }
+ enum wsrep::provider::status wait_for_gtid(const wsrep::gtid&,
+ int) const WSREP_OVERRIDE
+ { return wsrep::provider::success; }
+ wsrep::gtid last_committed_gtid() const WSREP_OVERRIDE
+ { return wsrep::gtid(); }
+ enum wsrep::provider::status sst_sent(const wsrep::gtid&, int)
+ WSREP_OVERRIDE
+ { return wsrep::provider::success; }
+ enum wsrep::provider::status sst_received(const wsrep::gtid&, int)
+ WSREP_OVERRIDE
+ { return wsrep::provider::success; }
+
+ enum wsrep::provider::status enc_set_key(const wsrep::const_buffer&)
+ WSREP_OVERRIDE
+ { return wsrep::provider::success; }
+
+ std::vector<status_variable> status() const WSREP_OVERRIDE
+ {
+ return std::vector<status_variable>();
+ }
+ void reset_status() WSREP_OVERRIDE { }
+ std::string options() const WSREP_OVERRIDE { return ""; }
+ enum wsrep::provider::status options(const std::string&)
+ WSREP_OVERRIDE
+ { return wsrep::provider::success; }
+ std::string name() const WSREP_OVERRIDE { return "mock"; }
+ std::string version() const WSREP_OVERRIDE { return "0.0"; }
+ std::string vendor() const WSREP_OVERRIDE { return "mock"; }
+ void* native() const WSREP_OVERRIDE { return 0; }
+
+ //
+ // Methods to modify mock state
+ //
+ /** Inject BF abort event into the provider.
+ *
+ * @param bf_seqno Aborter sequence number
+ * @param trx_id Trx id to be aborted
+ * @param[out] victim_seqno
+ */
+ enum wsrep::provider::status
+ bf_abort(wsrep::seqno bf_seqno,
+ wsrep::transaction_id trx_id,
+ wsrep::seqno& victim_seqno)
+ WSREP_OVERRIDE
+ {
+ bf_abort_map_.insert(std::make_pair(trx_id, bf_seqno));
+ if (bf_seqno.is_undefined() == false)
+ {
+ group_seqno_ = bf_seqno.get();
+ }
+ victim_seqno = wsrep::seqno::undefined();
+ return wsrep::provider::success;
+ }
+
+ // Parameters to control return value from the call
+ enum wsrep::provider::status certify_result_;
+ enum wsrep::provider::status commit_order_enter_result_;
+ enum wsrep::provider::status commit_order_leave_result_;
+ enum wsrep::provider::status release_result_;
+ enum wsrep::provider::status replay_result_;
+
+ size_t start_fragments() const { return start_fragments_; }
+ size_t fragments() const { return fragments_; }
+ size_t commit_fragments() const { return commit_fragments_; }
+ size_t rollback_fragments() const { return rollback_fragments_; }
+ size_t toi_write_sets() const { return toi_write_sets_; }
+ size_t toi_start_transaction() const { return toi_start_transaction_; }
+ size_t toi_commit() const { return toi_commit_; }
+ private:
+ wsrep::id group_id_;
+ wsrep::id server_id_;
+ long long group_seqno_;
+ bf_abort_map bf_abort_map_;
+ size_t start_fragments_;
+ size_t fragments_;
+ size_t commit_fragments_;
+ size_t rollback_fragments_;
+ size_t toi_write_sets_;
+ size_t toi_start_transaction_;
+ size_t toi_commit_;
+ };
+}
+
+
+#endif // WSREP_MOCK_PROVIDER_HPP
diff --git a/wsrep-lib/test/mock_server_state.hpp b/wsrep-lib/test/mock_server_state.hpp
new file mode 100644
index 00000000..730c9c04
--- /dev/null
+++ b/wsrep-lib/test/mock_server_state.hpp
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2018-2019 Codership Oy <info@codership.com>
+ *
+ * This file is part of wsrep-lib.
+ *
+ * Wsrep-lib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Wsrep-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef WSREP_MOCK_SERVER_STATE_HPP
+#define WSREP_MOCK_SERVER_STATE_HPP
+
+#include "wsrep/server_state.hpp"
+#include "wsrep/server_service.hpp"
+#include "mock_client_state.hpp"
+#include "mock_high_priority_service.hpp"
+#include "mock_storage_service.hpp"
+#include "mock_provider.hpp"
+
+#include "wsrep/compiler.hpp"
+
+namespace wsrep
+{
+ class mock_server_service : public wsrep::server_service
+ {
+ public:
+ mock_server_service(wsrep::server_state& server_state)
+ : sync_point_enabled_()
+ , sync_point_action_()
+ , sst_before_init_()
+ , server_state_(server_state)
+ , last_client_id_(0)
+ , last_transaction_id_(0)
+ , logged_view_()
+ , position_()
+ { }
+
+ wsrep::storage_service* storage_service(wsrep::client_service&)
+ WSREP_OVERRIDE
+ {
+ return new wsrep::mock_storage_service(server_state_,
+ wsrep::client_id(++last_client_id_));
+ }
+
+ wsrep::storage_service* storage_service(wsrep::high_priority_service&)
+ WSREP_OVERRIDE
+ {
+ return new wsrep::mock_storage_service(server_state_,
+ wsrep::client_id(++last_client_id_));
+ }
+
+ void release_storage_service(wsrep::storage_service* storage_service)
+ WSREP_OVERRIDE
+ {
+ delete storage_service;
+ }
+
+ wsrep::high_priority_service* streaming_applier_service(
+ wsrep::client_service&)
+ WSREP_OVERRIDE
+ {
+ wsrep::mock_client* cs(new wsrep::mock_client(
+ server_state_,
+ wsrep::client_id(++last_client_id_),
+ wsrep::client_state::m_high_priority));
+ wsrep::mock_high_priority_service* ret(
+ new wsrep::mock_high_priority_service(server_state_,
+ cs, false));
+ cs->open(cs->id());
+ cs->before_command();
+ return ret;
+ }
+
+ wsrep::high_priority_service* streaming_applier_service(
+ wsrep::high_priority_service&) WSREP_OVERRIDE
+ {
+ wsrep::mock_client* cs(new wsrep::mock_client(
+ server_state_,
+ wsrep::client_id(++last_client_id_),
+ wsrep::client_state::m_high_priority));
+ wsrep::mock_high_priority_service* ret(
+ new wsrep::mock_high_priority_service(server_state_,
+ cs, false));
+ cs->open(cs->id());
+ cs->before_command();
+ return ret;
+ }
+
+ void release_high_priority_service(
+ wsrep::high_priority_service *high_priority_service)
+ WSREP_OVERRIDE
+ {
+ mock_high_priority_service* mhps(
+ static_cast<mock_high_priority_service*>(high_priority_service));
+ wsrep::mock_client* cs(&static_cast<wsrep::mock_client&>(
+ mhps->client_state()));
+ cs->after_command_before_result();
+ cs->after_command_after_result();
+ cs->close();
+ cs->cleanup();
+ delete cs;
+ delete mhps;
+ }
+ void bootstrap() WSREP_OVERRIDE { }
+ void log_message(enum wsrep::log::level level, const char* message)
+ WSREP_OVERRIDE
+ {
+ wsrep::log(level, server_state_.name().c_str()) << message;
+ }
+ void log_dummy_write_set(wsrep::client_state&,
+ const wsrep::ws_meta&)
+ WSREP_OVERRIDE
+ {
+ }
+ void log_view(wsrep::high_priority_service*, const wsrep::view& view)
+ WSREP_OVERRIDE
+ {
+ logged_view_ = view;
+ }
+
+ void recover_streaming_appliers(wsrep::client_service&)
+ WSREP_OVERRIDE
+ { }
+
+ void recover_streaming_appliers(wsrep::high_priority_service&)
+ WSREP_OVERRIDE
+ { }
+
+ wsrep::view get_view(wsrep::client_service&, const wsrep::id& own_id)
+ WSREP_OVERRIDE
+ {
+ int const my_idx(logged_view_.member_index(own_id));
+ wsrep::view my_view(
+ logged_view_.state_id(),
+ logged_view_.view_seqno(),
+ logged_view_.status(),
+ logged_view_.capabilities(),
+ my_idx,
+ logged_view_.protocol_version(),
+ logged_view_.members()
+ );
+ return my_view;
+ }
+
+ wsrep::gtid get_position(wsrep::client_service&) WSREP_OVERRIDE
+ {
+ return position_;
+ }
+
+ void set_position(wsrep::client_service&,
+ const wsrep::gtid& gtid) WSREP_OVERRIDE
+ {
+ position_ = gtid;
+ }
+
+ void log_state_change(enum wsrep::server_state::state,
+ enum wsrep::server_state::state)
+ WSREP_OVERRIDE
+ { }
+ bool sst_before_init() const WSREP_OVERRIDE
+ { return sst_before_init_; }
+ std::string sst_request() WSREP_OVERRIDE { return ""; }
+ int start_sst(const std::string&,
+ const wsrep::gtid&,
+ bool) WSREP_OVERRIDE { return 0; }
+ void background_rollback(wsrep::client_state& client_state)
+ WSREP_OVERRIDE
+ {
+ client_state.before_rollback();
+ client_state.after_rollback();
+ }
+
+ int wait_committing_transactions(int) WSREP_OVERRIDE { return 0; }
+
+ wsrep::transaction_id next_transaction_id()
+ {
+ return wsrep::transaction_id(++last_transaction_id_);
+ }
+
+ void debug_sync(const char* sync_point) WSREP_OVERRIDE
+ {
+ if (sync_point_enabled_ == sync_point)
+ {
+ switch (sync_point_action_)
+ {
+ case spa_none:
+ break;
+ case spa_initialize:
+ server_state_.initialized();
+ break;
+ case spa_initialize_error:
+ throw wsrep::runtime_error("Inject initialization error");
+ break;
+ }
+ }
+ }
+
+ std::string sync_point_enabled_;
+ enum sync_point_action
+ {
+ spa_none,
+ spa_initialize,
+ spa_initialize_error
+
+ } sync_point_action_;
+ bool sst_before_init_;
+
+ void logged_view(const wsrep::view& view)
+ {
+ logged_view_ = view;
+ }
+ void position(const wsrep::gtid& position)
+ {
+ position_ = position;
+ }
+ private:
+ wsrep::server_state& server_state_;
+ unsigned long long last_client_id_;
+ unsigned long long last_transaction_id_;
+ wsrep::view logged_view_;
+ wsrep::gtid position_;
+ };
+
+
+ class mock_server_state : public wsrep::server_state
+ {
+ public:
+ mock_server_state(const std::string& name,
+ enum wsrep::server_state::rollback_mode rollback_mode,
+ wsrep::server_service& server_service)
+ : wsrep::server_state(mutex_, cond_, server_service, NULL,
+ name, "", "", "./",
+ wsrep::gtid::undefined(),
+ 1,
+ rollback_mode)
+ , mutex_()
+ , cond_()
+ , provider_(*this)
+ { }
+
+ wsrep::mock_provider& provider() const WSREP_OVERRIDE
+ { return provider_; }
+
+ // mock connected state for tests without overriding the connect()
+ // method.
+ int mock_connect(const std::string& own_id,
+ const std::string& cluster_name,
+ const std::string& cluster_address,
+ const std::string& state_donor,
+ bool bootstrap)
+ {
+ int const ret(server_state::connect(cluster_name,
+ cluster_address,
+ state_donor,
+ bootstrap));
+ if (0 == ret)
+ {
+ wsrep::id cluster_id("1");
+ wsrep::gtid state_id(cluster_id, wsrep::seqno(0));
+ std::vector<wsrep::view::member> members;
+ members.push_back(wsrep::view::member(wsrep::id(own_id),
+ "name", ""));
+ wsrep::view bootstrap_view(state_id,
+ wsrep::seqno(1),
+ wsrep::view::primary,
+ 0,
+ 0,
+ 1,
+ members);
+ server_state::on_connect(bootstrap_view);
+ }
+ else
+ {
+ assert(0);
+ }
+
+ return ret;
+ }
+
+ int mock_connect()
+ {
+ return mock_connect(name(), "cluster", "local", "0", false);
+ }
+
+ private:
+ wsrep::default_mutex mutex_;
+ wsrep::default_condition_variable cond_;
+ mutable wsrep::mock_provider provider_;
+ };
+}
+
+#endif // WSREP_MOCK_SERVER_STATE_HPP
diff --git a/wsrep-lib/test/mock_storage_service.cpp b/wsrep-lib/test/mock_storage_service.cpp
new file mode 100644
index 00000000..a455b9bf
--- /dev/null
+++ b/wsrep-lib/test/mock_storage_service.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2018-2019 Codership Oy <info@codership.com>
+ *
+ * This file is part of wsrep-lib.
+ *
+ * Wsrep-lib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Wsrep-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "mock_storage_service.hpp"
+#include "mock_server_state.hpp"
+
+#include "wsrep/client_state.hpp"
+
+wsrep::mock_storage_service::mock_storage_service(
+ wsrep::server_state& server_state,
+ wsrep::client_id client_id)
+ : client_service_(client_state_)
+ , client_state_(server_state, client_service_, client_id,
+ wsrep::client_state::m_high_priority)
+{
+ client_state_.open(client_id);
+ client_state_.before_command();
+}
+
+
+wsrep::mock_storage_service::~mock_storage_service()
+{
+ client_state_.after_command_before_result();
+ client_state_.after_command_after_result();
+ client_state_.close();
+ client_state_.cleanup();
+}
+
+int wsrep::mock_storage_service::start_transaction(
+ const wsrep::ws_handle& ws_handle)
+{
+ return client_state_.start_transaction(ws_handle.transaction_id());
+}
+
+void wsrep::mock_storage_service::adopt_transaction(
+ const wsrep::transaction& transaction)
+{
+ client_state_.adopt_transaction(transaction);
+}
+
+int wsrep::mock_storage_service::commit(const wsrep::ws_handle& ws_handle,
+ const wsrep::ws_meta& ws_meta)
+{
+ // the logic here matches mariadb's wsrep_storage_service::commit
+ bool ret = 0;
+ const bool is_ordered = !ws_meta.seqno().is_undefined();
+ if (is_ordered)
+ {
+ ret = ret || client_state_.prepare_for_ordering(ws_handle, ws_meta, true);
+ ret = ret || client_state_.before_commit();
+ ret = ret || client_state_.ordered_commit();
+ ret = ret || client_state_.after_commit();
+ }
+
+ if (!is_ordered)
+ {
+ client_state_.before_rollback();
+ client_state_.after_rollback();
+ }
+ else if (ret)
+ {
+ client_state_.prepare_for_ordering(wsrep::ws_handle(), wsrep::ws_meta(), false);
+ }
+
+ client_state_.after_applying();
+ return ret;
+}
+
+int wsrep::mock_storage_service::rollback(const wsrep::ws_handle& ws_handle,
+ const wsrep::ws_meta& ws_meta)
+{
+ int ret(client_state_.prepare_for_ordering(
+ ws_handle, ws_meta, false) ||
+ client_state_.before_rollback() ||
+ client_state_.after_rollback());
+ client_state_.after_applying();
+ return ret;
+}
diff --git a/wsrep-lib/test/mock_storage_service.hpp b/wsrep-lib/test/mock_storage_service.hpp
new file mode 100644
index 00000000..b0275b97
--- /dev/null
+++ b/wsrep-lib/test/mock_storage_service.hpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2018 Codership Oy <info@codership.com>
+ *
+ * This file is part of wsrep-lib.
+ *
+ * Wsrep-lib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Wsrep-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef WSREP_MOCK_STORAGE_SERVICE_HPP
+#define WSREP_MOCK_STORAGE_SERVICE_HPP
+
+#include "wsrep/storage_service.hpp"
+#include "mock_client_state.hpp"
+
+namespace wsrep
+{
+class mock_server_state;
+ class mock_storage_service : public wsrep::storage_service
+ {
+ public:
+ mock_storage_service(wsrep::server_state&, wsrep::client_id);
+ ~mock_storage_service();
+
+ int start_transaction(const wsrep::ws_handle&) WSREP_OVERRIDE;
+
+ void adopt_transaction(const wsrep::transaction&) WSREP_OVERRIDE;
+
+ int append_fragment(const wsrep::id&,
+ wsrep::transaction_id,
+ int,
+ const wsrep::const_buffer&,
+ const wsrep::xid&) WSREP_OVERRIDE
+ { return 0; }
+
+ int update_fragment_meta(const wsrep::ws_meta&) WSREP_OVERRIDE
+ { return 0; }
+ int remove_fragments() WSREP_OVERRIDE { return 0; }
+ int commit(const wsrep::ws_handle&, const wsrep::ws_meta&)
+ WSREP_OVERRIDE;
+
+ int rollback(const wsrep::ws_handle&, const wsrep::ws_meta&)
+ WSREP_OVERRIDE;
+
+ void store_globals() WSREP_OVERRIDE { }
+ void reset_globals() WSREP_OVERRIDE { }
+ private:
+ wsrep::mock_client_service client_service_;
+ wsrep::mock_client_state client_state_;
+ };
+}
+
+#endif // WSREP_MOCK_STORAGE_SERVICE_HPP
diff --git a/wsrep-lib/test/nbo_test.cpp b/wsrep-lib/test/nbo_test.cpp
new file mode 100644
index 00000000..238a4da5
--- /dev/null
+++ b/wsrep-lib/test/nbo_test.cpp
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2019 Codership Oy <info@codership.com>
+ *
+ * This file is part of wsrep-lib.
+ *
+ * Wsrep-lib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Wsrep-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "wsrep/client_state.hpp"
+
+#include "client_state_fixture.hpp"
+
+#include <boost/test/unit_test.hpp>
+
+BOOST_FIXTURE_TEST_CASE(test_local_nbo,
+ replicating_client_fixture_sync_rm)
+{
+ // NBO is executed in two consecutive TOI operations
+ BOOST_REQUIRE(cc.mode() == wsrep::client_state::m_local);
+ // First phase certifies the write set and enters TOI
+ wsrep::key key(wsrep::key::exclusive);
+ key.append_key_part("k1", 2);
+ key.append_key_part("k2", 2);
+ wsrep::key_array keys{key};
+ std::string data("data");
+ BOOST_REQUIRE(cc.begin_nbo_phase_one(
+ keys,
+ wsrep::const_buffer(data.data(),
+ data.size())) == 0);
+ BOOST_REQUIRE(cc.mode() == wsrep::client_state::m_nbo);
+ BOOST_REQUIRE(cc.in_toi());
+ BOOST_REQUIRE(cc.toi_mode() == wsrep::client_state::m_local);
+ // After required resoureces have been grabbed, NBO leave should
+ // end TOI but leave the client state in m_nbo.
+ const wsrep::mutable_buffer err;
+ BOOST_REQUIRE(cc.end_nbo_phase_one(err) == 0);
+ BOOST_REQUIRE(cc.mode() == wsrep::client_state::m_nbo);
+ BOOST_REQUIRE(cc.in_toi() == false);
+ BOOST_REQUIRE(cc.toi_mode() == wsrep::client_state::m_undefined);
+ // Second phase replicates the NBO end event and grabs TOI
+ // again for finalizing the NBO.
+ BOOST_REQUIRE(cc.begin_nbo_phase_two(keys) == 0);
+ BOOST_REQUIRE(cc.mode() == wsrep::client_state::m_nbo);
+ BOOST_REQUIRE(cc.in_toi());
+ BOOST_REQUIRE(cc.toi_mode() == wsrep::client_state::m_local);
+ // End of NBO phase will leave TOI and put the client state back to
+ // m_local
+ BOOST_REQUIRE(cc.end_nbo_phase_two(err) == 0);
+ BOOST_REQUIRE(cc.mode() == wsrep::client_state::m_local);
+ BOOST_REQUIRE(cc.in_toi() == false);
+ BOOST_REQUIRE(cc.toi_mode() == wsrep::client_state::m_undefined);
+
+ // There must have been two toi write sets, one with
+ // start transaction flag, another with commit flag.
+ BOOST_REQUIRE(sc.provider().toi_write_sets() == 2);
+ BOOST_REQUIRE(sc.provider().toi_start_transaction() == 1);
+ BOOST_REQUIRE(sc.provider().toi_commit() == 1);
+}
+
+BOOST_FIXTURE_TEST_CASE(test_local_nbo_cert_failure,
+ replicating_client_fixture_sync_rm)
+{
+ // NBO is executed in two consecutive TOI operations
+ BOOST_REQUIRE(cc.mode() == wsrep::client_state::m_local);
+ // First phase certifies the write set and enters TOI
+ wsrep::key key(wsrep::key::exclusive);
+ key.append_key_part("k1", 2);
+ key.append_key_part("k2", 2);
+ wsrep::key_array keys{key};
+ std::string data("data");
+ sc.provider().certify_result_ = wsrep::provider::error_certification_failed;
+ BOOST_REQUIRE(cc.begin_nbo_phase_one(
+ keys,
+ wsrep::const_buffer(data.data(),
+ data.size())) == 1);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+ BOOST_REQUIRE(cc.current_error_status() ==
+ wsrep::provider::error_certification_failed);
+ BOOST_REQUIRE(cc.mode() == wsrep::client_state::m_local);
+ BOOST_REQUIRE(cc.in_toi() == false);
+ BOOST_REQUIRE(cc.toi_mode() == wsrep::client_state::m_undefined);
+}
+
+// This test case operates through server_state object in order to
+// verify that the high priority service is called with appropriate
+// arguments.
+BOOST_FIXTURE_TEST_CASE(test_applying_nbo,
+ applying_client_fixture)
+{
+
+ wsrep::mock_high_priority_service hps(sc, &cc, false);
+ wsrep::ws_handle ws_handle(wsrep::transaction_id::undefined(), (void*)(1));
+ const int nbo_begin_flags(wsrep::provider::flag::start_transaction |
+ wsrep::provider::flag::isolation);
+ wsrep::ws_meta ws_meta(wsrep::gtid(wsrep::id("cluster1"),
+ wsrep::seqno(1)),
+ wsrep::stid(wsrep::id("s1"),
+ wsrep::transaction_id::undefined(),
+ wsrep::client_id(1)),
+ wsrep::seqno(0),
+ nbo_begin_flags);
+ std::string nbo_begin("nbo_begin");
+ BOOST_REQUIRE(sc.on_apply(hps, ws_handle, ws_meta,
+ wsrep::const_buffer(nbo_begin.data(),
+ nbo_begin.size())) == 0);
+ wsrep::mock_client* nbo_cs(hps.nbo_cs());
+ BOOST_REQUIRE(nbo_cs);
+ BOOST_REQUIRE(nbo_cs->toi_mode() == wsrep::client_state::m_undefined);
+ BOOST_REQUIRE(nbo_cs->mode() == wsrep::client_state::m_nbo);
+
+ // After this point the control is on local process and applier
+ // has released toi critical section.
+ wsrep::key key(wsrep::key::exclusive);
+ key.append_key_part("k1", 2);
+ key.append_key_part("k2", 2);
+ wsrep::key_array keys{key};
+ // Starting phase two should put nbo_cs in toi mode.
+ BOOST_REQUIRE(nbo_cs->begin_nbo_phase_two(keys) == 0);
+ BOOST_REQUIRE(nbo_cs->mode() == wsrep::client_state::m_nbo);
+ BOOST_REQUIRE(nbo_cs->in_toi());
+ BOOST_REQUIRE(nbo_cs->toi_mode() == wsrep::client_state::m_local);
+ // Ending phase two should make nbo_cs leave TOI mode and
+ // return to m_local mode.
+ const wsrep::mutable_buffer err;
+ BOOST_REQUIRE(nbo_cs->end_nbo_phase_two(err) == 0);
+ BOOST_REQUIRE(nbo_cs->mode() == wsrep::client_state::m_local);
+ BOOST_REQUIRE(nbo_cs->in_toi() == false);
+ BOOST_REQUIRE(nbo_cs->toi_mode() == wsrep::client_state::m_undefined);
+
+ // There must have been one toi write set with commit flag.
+ BOOST_REQUIRE(sc.provider().toi_write_sets() == 1);
+ BOOST_REQUIRE(sc.provider().toi_start_transaction() == 0);
+ BOOST_REQUIRE(sc.provider().toi_commit() == 1);
+}
+
+// This test case operates through server_state object in order to
+// verify that the high priority service is called with appropriate
+// arguments. The test checks that error is returned in the case if
+// launching asynchronous process for NBO fails.
+BOOST_FIXTURE_TEST_CASE(test_applying_nbo_fail,
+ applying_client_fixture)
+{
+
+ wsrep::mock_high_priority_service hps(sc, &cc, false);
+ wsrep::ws_handle ws_handle(wsrep::transaction_id::undefined(), (void*)(1));
+ const int nbo_begin_flags(wsrep::provider::flag::start_transaction |
+ wsrep::provider::flag::isolation);
+ wsrep::ws_meta ws_meta(wsrep::gtid(wsrep::id("cluster1"),
+ wsrep::seqno(1)),
+ wsrep::stid(wsrep::id("s1"),
+ wsrep::transaction_id::undefined(),
+ wsrep::client_id(1)),
+ wsrep::seqno(0),
+ nbo_begin_flags);
+ std::string nbo_begin("nbo_begin");
+ hps.fail_next_toi_ = true;
+ BOOST_REQUIRE(sc.on_apply(hps, ws_handle, ws_meta,
+ wsrep::const_buffer(nbo_begin.data(),
+ nbo_begin.size())) == 1);
+}
diff --git a/wsrep-lib/test/reporter_test.cpp b/wsrep-lib/test/reporter_test.cpp
new file mode 100644
index 00000000..bed7a2a6
--- /dev/null
+++ b/wsrep-lib/test/reporter_test.cpp
@@ -0,0 +1,636 @@
+/*
+ * Copyright (C) 2021 Codership Oy <info@codership.com>
+ *
+ * This file is part of wsrep-lib.
+ *
+ * Wsrep-lib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Wsrep-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "wsrep/reporter.hpp"
+
+#include <boost/test/unit_test.hpp>
+
+#include <boost/json/src.hpp>
+
+#include <fstream>
+#include <deque>
+#include <vector>
+#include <unistd.h> // unlink() for cleanup
+
+namespace json = boost::json;
+
+//////// HELPERS ///////
+
+static inline double
+timestamp()
+{
+ struct timespec time;
+ clock_gettime(CLOCK_REALTIME, &time);
+ return (double(time.tv_sec) + double(time.tv_nsec)*1.0e-9);
+}
+
+static std::string make_progress_string(int const from, int const to,
+ int const total,int const done,
+ int const indefinite)
+{
+ std::ostringstream os;
+
+ os << "{ \"from\": " << from << ", "
+ << "\"to\": " << to << ", "
+ << "\"total\": " << total << ", "
+ << "\"done\": " << done << ", "
+ << "\"indefinite\": " << indefinite << " }";
+
+ return os.str();
+}
+
+
+static json::value
+read_file(const char* const filename)
+{
+ std::ifstream input(filename, std::ios::binary);
+ std::vector<char> buffer(std::istreambuf_iterator<char>(input), {});
+ json::stream_parser parser;
+ json::error_code err;
+
+ parser.write(buffer.data(), buffer.size(), err);
+ if (err)
+ {
+ assert(0);
+ return nullptr;
+ }
+
+ parser.finish(err);
+ if (err)
+ {
+ assert(0);
+ return nullptr;
+ }
+
+ return parser.release();
+}
+
+struct logs
+{
+ std::deque<double> tstamp_;
+ std::deque<std::string> msg_;
+};
+
+struct progress
+{
+ int from_;
+ int to_;
+ int total_;
+ int done_;
+ int indefinite_;
+
+ bool indefinite() const { return total_ == indefinite_; }
+ bool steady() const { return total_ == 0; }
+ progress(int const from, int const to, int const total, int const done,
+ int const indefinite)
+ : from_(from)
+ , to_(to)
+ , total_(total)
+ , done_(done)
+ , indefinite_(indefinite)
+ {}
+};
+
+static bool
+operator==(const progress& left, const progress& right)
+{
+ if (left.indefinite() && right.indefinite()) return true;
+ if (left.steady() && right.steady()) return true;
+
+ return (left.from_ == right.from_ &&
+ left.to_ == right.to_ &&
+ left.total_ == right.total_ &&
+ left.done_ == right.done_ &&
+ left.indefinite_ == right.indefinite_);
+}
+
+static bool
+operator!=(const progress& left, const progress& right)
+{
+ return !(left == right);
+}
+
+static std::ostream&
+operator<<(std::ostream& os, const progress& p)
+{
+ os << make_progress_string(p.from_, p.to_, p.total_, p.done_,p.indefinite_);
+ return os;
+}
+
+struct result
+{
+ logs errors_;
+ logs warnings_;
+ struct
+ {
+ std::string state_;
+ std::string comment_;
+ progress progress_;
+ } status_;
+};
+
+static void
+parse_result(const json::value& value, struct result& res,
+ const std::string& path = "")
+{
+ //std::cout << "Parsing " << path << ": " << value << ": " << value.kind() << std::endl;
+ switch (value.kind())
+ {
+ case json::kind::object:
+ {
+ auto const obj(value.get_object());
+ if (!obj.empty())
+ {
+ for (auto it = obj.begin(); it != obj.end(); ++it)
+ {
+ std::string const key(it->key().data(), it->key().length());
+ parse_result(it->value(), res, path + "." + key);
+ }
+ }
+ return;
+ }
+ case json::kind::array:
+ {
+ auto const arr(value.get_array());
+ if (!arr.empty())
+ {
+ for (auto it = arr.begin(); it != arr.end(); ++it)
+ {
+ parse_result(*it, res, path + ".[]");
+ }
+ }
+ return;
+ }
+ case json::kind::string:
+ {
+ auto const val(value.get_string().c_str());
+ if (path == ".errors.[].msg")
+ {
+ res.errors_.msg_.push_back(val);
+ }
+ else if (path == ".warnings.[].msg")
+ {
+ res.warnings_.msg_.push_back(val);
+ }
+ else if (path == ".status.state")
+ {
+ res.status_.state_ = val;
+ }
+ else if (path == ".status.comment")
+ {
+ res.status_.comment_ = val;
+ }
+ return;
+ }
+ case json::kind::uint64:
+ WSREP_FALLTHROUGH;
+ case json::kind::int64:
+ if (path == ".status.progress.from")
+ {
+ res.status_.progress_.from_ = int(value.get_int64());
+ }
+ else if (path == ".status.progress.to")
+ {
+ res.status_.progress_.to_ = int(value.get_int64());
+ }
+ else if (path == ".status.progress.total")
+ {
+ res.status_.progress_.total_ = int(value.get_int64());
+ }
+ else if (path == ".status.progress.done")
+ {
+ res.status_.progress_.done_ = int(value.get_int64());
+ }
+ else if (path == ".status.progress.indefinite")
+ {
+ res.status_.progress_.indefinite_ = int(value.get_int64());
+ }
+ return;
+ case json::kind::double_:
+ if (path == ".errors.[].timestamp")
+ {
+ res.errors_.tstamp_.push_back(value.get_double());
+ }
+ else if (path == ".warnings.[].timestamp")
+ {
+ res.warnings_.tstamp_.push_back(value.get_double());
+ }
+
+ return;
+ case json::kind::bool_:
+ return;
+ case json::kind::null:
+ return;
+ }
+
+ assert(0);
+}
+
+static bool
+equal(const std::string& left, const std::string& right)
+{
+ return left == right;
+}
+
+static bool
+equal(double const left, double const right)
+{
+ return ::fabs(left - right) < 0.0001; // we are looking for ms precision
+}
+
+template <typename T>
+static bool
+operator!=(const std::deque<T>& left, const std::deque<T>& right)
+{
+ if (left.size() != right.size()) return true;
+
+ for (size_t i(0); i < left.size(); ++i)
+ if (!equal(left[i], right[i])) return true;
+
+ return false;
+}
+
+static bool
+operator!=(const logs& left, const logs& right)
+{
+ if (left.tstamp_ != right.tstamp_) return true;
+ return (left.msg_ != right.msg_);
+}
+
+static bool
+operator==(const result& left, const result& right)
+{
+ if (left.errors_ != right.errors_) return false;
+ if (left.warnings_ != right.warnings_) return false;
+
+ if (left.status_.state_ != right.status_.state_) return false;
+ if (left.status_.comment_ != right.status_.comment_) return false;
+ if (left.status_.progress_ != right.status_.progress_) return false;
+
+ return true;
+}
+
+template <typename T>
+static void
+print_deque(std::ostream& os,const std::deque<T> left,const std::deque<T> right)
+{
+ auto const max(std::max(left.size(), right.size()));
+ for (size_t i(0); i < max; ++i)
+ {
+ os << "|\t'";
+
+ if (i < left.size())
+ os << left[i] << "'";
+ else
+ os << "'\t";
+
+ if (i < right.size())
+ os << "\t'" << right[i] << "'";
+ else
+ os << "\t''";
+
+ if (!equal(left[i], right[i])) os << "\t!!!";
+
+ os << "\n";
+ }
+}
+
+static void
+print_logs(std::ostream& os, const logs& left, const logs& right)
+{
+ os << "|\t" << left.msg_.size() << "\t" << right.msg_.size() << "\n";
+ print_deque(os, left.tstamp_, right.tstamp_);
+ print_deque(os, left.msg_, right.msg_);
+}
+
+// print two results against each other
+static std::string
+print(const result& left, const result& right, size_t it)
+{
+ std::ostringstream os;
+
+ os << std::showpoint << std::setprecision(18);
+
+ os << "Iteration " << it << "\nerrors:\n";
+ print_logs(os, left.errors_, right.errors_);
+ os << "warnings:\n";
+ print_logs(os, left.warnings_, right.warnings_);
+ os << "state:\n";
+ os << "\t" << left.status_.state_ << "\t" << right.status_.state_
+ << "\n";
+ os << "\t" << left.status_.comment_ << "\t" << right.status_.comment_
+ << "\n";
+ os << "\t" << left.status_.progress_ << "\t" << right.status_.progress_
+ << "\n";
+
+ return os.str();
+}
+
+#define VERIFY_RESULT(left, right, it) \
+ BOOST_CHECK_MESSAGE(left == right, print(left, right, it));
+
+static const char*
+const REPORT = "report.json";
+
+static progress
+const indefinite(-1, -1, -1, -1, -1);
+
+static progress
+const steady(-1, -1, 0, 0, -1);
+
+static struct logs
+const LOGS_INIT = { std::deque<double>(), std::deque<std::string>() };
+static struct result
+const RES_INIT = { LOGS_INIT, LOGS_INIT,
+ { "DISCONNECTED", "Disconnected", indefinite } };
+
+static void
+test_log(const char* const fname,
+ wsrep::reporter& rep,
+ result& check,
+ wsrep::reporter::log_level const lvl,
+ double const tstamp,
+ const std::string& msg,
+ size_t const iteration)
+{
+ // this is implementaiton detail, if it changes in the code, it needs
+ // to be changed here
+ size_t const MAX_ERROR(4);
+
+ logs& log(lvl == wsrep::reporter::error ? check.errors_ : check.warnings_);
+ log.tstamp_.push_back(tstamp);
+ if (log.tstamp_.size() > MAX_ERROR) log.tstamp_.pop_front();
+ log.msg_.push_back(msg);
+ if (log.msg_.size() > MAX_ERROR) log.msg_.pop_front();
+
+ rep.report_log_msg(lvl, msg, tstamp);
+
+ auto value = read_file(fname);
+ auto res = RES_INIT;
+ parse_result(value, res);
+ VERIFY_RESULT(res, check, iteration);
+}
+
+static size_t const MAX_MSG = 4;
+
+BOOST_AUTO_TEST_CASE(log_msg_test)
+{
+ wsrep::default_mutex m;
+ wsrep::reporter rep(m, REPORT, MAX_MSG);
+
+ auto value = read_file(REPORT);
+ BOOST_REQUIRE(value != nullptr);
+
+ struct result res(RES_INIT), check(RES_INIT);
+ parse_result(value, res);
+ VERIFY_RESULT(res, check, -1);
+
+ struct entry
+ {
+ double tstamp_;
+ std::string msg_;
+ };
+ std::vector<entry> msgs =
+ {
+ { 0.1, "a" },
+ { 0.2, "bb" },
+ { 0.3, "ccc" },
+ { 0.4, "dddd" },
+ { 0.5, "eeeee" },
+ { 0.6, "ffffff" }
+ };
+ for (size_t i(0); i < msgs.size(); ++i)
+ {
+ test_log(REPORT, rep, check,
+ wsrep::reporter::error, msgs[i].tstamp_, msgs[i].msg_, i);
+ test_log(REPORT, rep, check,
+ wsrep::reporter::warning, msgs[i].tstamp_, msgs[i].msg_, i);
+ }
+
+ // test indefinite timestmap
+ std::string const msg("err");
+ rep.report_log_msg(wsrep::reporter::error, msg);
+ value = read_file(REPORT);
+ res = RES_INIT;
+ parse_result(value, res);
+ BOOST_REQUIRE(res.errors_.tstamp_.back() > 0);
+ BOOST_REQUIRE(res.errors_.msg_.back() == msg);
+
+ ::unlink(REPORT);
+}
+
+BOOST_AUTO_TEST_CASE(state_test)
+{
+ using wsrep::server_state;
+
+ wsrep::default_mutex m;
+ wsrep::reporter rep(m, REPORT, MAX_MSG);
+ double const err_tstamp(timestamp());
+ std::string const err_msg("Error!");
+
+ struct test
+ {
+ struct
+ {
+ enum wsrep::server_state::state state;
+ float progress;
+ } input;
+ struct result output;
+ };
+
+ logs const ERRS_INIT = { {err_tstamp}, {err_msg} };
+
+ std::vector<test> tests =
+ {
+ {{ server_state::s_disconnected, 0 },
+ { ERRS_INIT, LOGS_INIT,
+ { "DISCONNECTED", "Disconnected", indefinite }}},
+ {{ server_state::s_initializing, 0 },
+ { ERRS_INIT, LOGS_INIT,
+ { "DISCONNECTED", "Initializing", indefinite }}},
+ {{ server_state::s_initialized, 0 },
+ { ERRS_INIT, LOGS_INIT,
+ { "DISCONNECTED", "Connecting", indefinite }}},
+ {{ server_state::s_connected, 0 },
+ { ERRS_INIT, LOGS_INIT,
+ { "CONNECTED", "Waiting", indefinite }}},
+ {{ server_state::s_joiner, 0 },
+ { ERRS_INIT, LOGS_INIT,
+ { "JOINING", "Receiving state", indefinite }}},
+ {{ server_state::s_disconnecting, 0 },
+ { ERRS_INIT, LOGS_INIT,
+ { "DISCONNECTING", "Disconnecting", indefinite }}},
+ {{ server_state::s_disconnected, 0 },
+ { ERRS_INIT, LOGS_INIT,
+ { "DISCONNECTED", "Disconnected", indefinite }}},
+ {{ server_state::s_connected, 0 },
+ { ERRS_INIT, LOGS_INIT,
+ { "CONNECTED", "Waiting", indefinite }}},
+ {{ server_state::s_joiner, 0 },
+ { ERRS_INIT, LOGS_INIT,
+ { "JOINING", "Receiving SST", indefinite }}},
+ {{ server_state::s_initializing, 0 },
+ { ERRS_INIT, LOGS_INIT,
+ { "JOINING", "Initializing", indefinite }}},
+ {{ server_state::s_initialized, 0 },
+ { ERRS_INIT, LOGS_INIT,
+ { "JOINING", "Receiving IST", indefinite }}},
+ {{ server_state::s_joined, 0 },
+ { ERRS_INIT, LOGS_INIT,
+ { "JOINED", "Syncing", indefinite }}},
+ {{ server_state::s_synced, 0 },
+ { ERRS_INIT, LOGS_INIT,
+ { "SYNCED", "Operational", steady }}},
+ {{ server_state::s_donor, 0 },
+ { ERRS_INIT, LOGS_INIT,
+ { "DONOR", "Donating SST", indefinite }}},
+ {{ server_state::s_joined, 0 },
+ { ERRS_INIT, LOGS_INIT,
+ { "JOINED", "Syncing", indefinite }}},
+ {{ server_state::s_synced, 0 },
+ { ERRS_INIT, LOGS_INIT,
+ { "SYNCED", "Operational", steady }}},
+ {{ server_state::s_disconnecting, 0 },
+ { ERRS_INIT, LOGS_INIT,
+ { "DISCONNECTING", "Disconnecting", indefinite }}},
+ };
+
+ rep.report_log_msg(wsrep::reporter::error, err_msg, err_tstamp);
+
+ for (auto i(tests.begin()); i != tests.end(); ++i)
+ {
+ rep.report_state(i->input.state);
+ auto value = read_file(REPORT);
+ result res(RES_INIT);
+ parse_result(value, res);
+ auto check(i->output);
+ VERIFY_RESULT(res, check, i - tests.begin());
+ }
+
+ ::unlink(REPORT);
+}
+
+BOOST_AUTO_TEST_CASE(progress_test)
+{
+ using wsrep::server_state;
+
+ wsrep::default_mutex m;
+ wsrep::reporter rep(m, REPORT, MAX_MSG);
+ double const warn_tstamp(timestamp());
+ std::string const warn_msg("Warn!");
+
+ static progress const progress5_0(-1, -1, 5, 0, -1);
+ static progress const progress5_2(-1, -1, 5, 2, -1);
+ static progress const progress5_5(-1, -1, 5, 5, -1);
+
+ struct test
+ {
+ struct
+ {
+ enum wsrep::server_state::state state;
+ int total;
+ int done;
+ } input;
+ struct result output;
+ };
+
+ logs const WARN_INIT = { {warn_tstamp}, {warn_msg} };
+
+ std::vector<test> tests =
+ {
+ {{ server_state::s_initialized, -1, -1 },
+ { LOGS_INIT, WARN_INIT,
+ { "DISCONNECTED", "Connecting", indefinite }}},
+ {{ server_state::s_connected, -1, -1 },
+ { LOGS_INIT, WARN_INIT,
+ { "CONNECTED", "Waiting", indefinite }}},
+ {{ server_state::s_joiner, 5, 0 },
+ { LOGS_INIT, WARN_INIT,
+ { "JOINING", "Receiving state", progress5_0 }}},
+ {{ server_state::s_joiner, 5, 2 },
+ { LOGS_INIT, WARN_INIT,
+ { "JOINING", "Receiving state", progress5_2 }}},
+ {{ server_state::s_disconnected, -1, -1 },
+ { LOGS_INIT, WARN_INIT,
+ { "DISCONNECTED", "Disconnected", indefinite }}},
+ {{ server_state::s_connected, -1, -1 },
+ { LOGS_INIT, WARN_INIT,
+ { "CONNECTED", "Waiting", indefinite }}},
+ {{ server_state::s_joiner, 5, 0 },
+ { LOGS_INIT, WARN_INIT,
+ { "JOINING", "Receiving SST", progress5_0 }}},
+ {{ server_state::s_joiner, 5, 2 },
+ { LOGS_INIT, WARN_INIT,
+ { "JOINING", "Receiving SST", progress5_2 }}},
+ {{ server_state::s_joiner, 5, 5 },
+ { LOGS_INIT, WARN_INIT,
+ { "JOINING", "Receiving SST", progress5_5 }}},
+ {{ server_state::s_initializing, -1, -1 },
+ { LOGS_INIT, WARN_INIT,
+ { "JOINING", "Initializing", indefinite }}},
+ {{ server_state::s_initializing, 5, 2 },
+ { LOGS_INIT, WARN_INIT,
+ { "JOINING", "Initializing", progress5_2 }}},
+ {{ server_state::s_initialized, 5, 0 },
+ { LOGS_INIT, WARN_INIT,
+ { "JOINING", "Receiving IST", progress5_0 }}},
+ {{ server_state::s_initialized, 5, 5 },
+ { LOGS_INIT, WARN_INIT,
+ { "JOINING", "Receiving IST", progress5_5 }}},
+ {{ server_state::s_joined, -1, -1 },
+ { LOGS_INIT, WARN_INIT,
+ { "JOINED", "Syncing", indefinite }}},
+ {{ server_state::s_joined, 5, 2 },
+ { LOGS_INIT, WARN_INIT,
+ { "JOINED", "Syncing", progress5_2 }}},
+ {{ server_state::s_synced, -1, -1 },
+ { LOGS_INIT, WARN_INIT,
+ { "SYNCED", "Operational", steady }}},
+ {{ server_state::s_donor, -1, -1 },
+ { LOGS_INIT, WARN_INIT,
+ { "DONOR", "Donating SST", indefinite }}},
+ {{ server_state::s_donor, 5, 0 },
+ { LOGS_INIT, WARN_INIT,
+ { "DONOR", "Donating SST", progress5_0 }}},
+ {{ server_state::s_joined, -1, -1 },
+ { LOGS_INIT, WARN_INIT,
+ { "JOINED", "Syncing", indefinite }}},
+ {{ server_state::s_synced, -1, -1 },
+ { LOGS_INIT, WARN_INIT,
+ { "SYNCED", "Operational", steady }}},
+ };
+
+ rep.report_log_msg(wsrep::reporter::warning, warn_msg, warn_tstamp);
+
+ for (auto i(tests.begin()); i != tests.end(); ++i)
+ {
+ rep.report_state(i->input.state);
+ if (i->input.total >= 0)
+ rep.report_progress(make_progress_string(-1, -1,
+ i->input.total, i->input.done,
+ -1));
+ auto value = read_file(REPORT);
+ result res(RES_INIT);
+ parse_result(value, res);
+ auto check(i->output);
+ VERIFY_RESULT(res, check, i - tests.begin());
+ }
+
+ ::unlink(REPORT);
+}
diff --git a/wsrep-lib/test/rsu_test.cpp b/wsrep-lib/test/rsu_test.cpp
new file mode 100644
index 00000000..b032ef8c
--- /dev/null
+++ b/wsrep-lib/test/rsu_test.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 Codership Oy <info@codership.com>
+ *
+ * This file is part of wsrep-lib.
+ *
+ * Wsrep-lib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Wsrep-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "wsrep/client_state.hpp"
+
+#include "client_state_fixture.hpp"
+
+#include <boost/test/unit_test.hpp>
+
+BOOST_FIXTURE_TEST_CASE(test_rsu,
+ replicating_client_fixture_sync_rm)
+{
+ BOOST_REQUIRE(cc.begin_rsu(1) == 0);
+ BOOST_REQUIRE(cc.mode() == wsrep::client_state::m_rsu);
+ BOOST_REQUIRE(cc.toi_mode() == wsrep::client_state::m_local);
+
+ BOOST_REQUIRE(cc.end_rsu() == 0);
+ BOOST_REQUIRE(cc.mode() == wsrep::client_state::m_local);
+ BOOST_REQUIRE(cc.toi_mode() == wsrep::client_state::m_undefined);
+}
diff --git a/wsrep-lib/test/server_context_test.cpp b/wsrep-lib/test/server_context_test.cpp
new file mode 100644
index 00000000..1c0b8bde
--- /dev/null
+++ b/wsrep-lib/test/server_context_test.cpp
@@ -0,0 +1,914 @@
+/*
+ * Copyright (C) 2018-2019 Codership Oy <info@codership.com>
+ *
+ * This file is part of wsrep-lib.
+ *
+ * Wsrep-lib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Wsrep-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "mock_server_state.hpp"
+
+#include <boost/test/unit_test.hpp>
+
+namespace
+{
+ struct server_fixture_base
+ {
+ server_fixture_base()
+ : server_service(ss)
+ , ss("s1",
+ wsrep::server_state::rm_sync, server_service)
+ , cc(ss,
+ wsrep::client_id(1),
+ wsrep::client_state::m_high_priority)
+ , hps(ss, &cc, false)
+ , ws_handle(wsrep::transaction_id(1), (void*)1)
+ , ws_meta(wsrep::gtid(wsrep::id("1"), wsrep::seqno(1)),
+ wsrep::stid(wsrep::id("1"), wsrep::transaction_id(1),
+ wsrep::client_id(1)),
+ wsrep::seqno(0),
+ wsrep::provider::flag::start_transaction |
+ wsrep::provider::flag::commit)
+ , cluster_id("1")
+ , bootstrap_view()
+ , second_view()
+ , third_view()
+ {
+ wsrep::gtid state_id(cluster_id, wsrep::seqno(0));
+ std::vector<wsrep::view::member> members;
+ members.push_back(wsrep::view::member(
+ wsrep::id("s1"), "s1", ""));
+ bootstrap_view = wsrep::view(state_id,
+ wsrep::seqno(1),
+ wsrep::view::primary,
+ 0, // capabilities
+ 0, // own index
+ 1, // protocol version
+ members);
+
+ members.push_back(wsrep::view::member(
+ wsrep::id("s2"), "s2", ""));
+ second_view = wsrep::view(wsrep::gtid(cluster_id, wsrep::seqno(1)),
+ wsrep::seqno(2),
+ wsrep::view::primary,
+ 0, // capabilities
+ 1, // own index
+ 1, // protocol version
+ members);
+
+ members.push_back(wsrep::view::member(
+ wsrep::id("s3"), "s3", ""));
+
+ third_view = wsrep::view(wsrep::gtid(cluster_id, wsrep::seqno(2)),
+ wsrep::seqno(3),
+ wsrep::view::primary,
+ 0, // capabilities
+ 1, // own index
+ 1, // protocol version
+ members);
+
+ cc.open(cc.id());
+ BOOST_REQUIRE(cc.before_command() == 0);
+ }
+ wsrep::mock_server_service server_service;
+ wsrep::mock_server_state ss;
+ wsrep::mock_client cc;
+ wsrep::mock_high_priority_service hps;
+ wsrep::ws_handle ws_handle;
+ wsrep::ws_meta ws_meta;
+ wsrep::id cluster_id;
+ wsrep::view bootstrap_view;
+ wsrep::view second_view;
+ wsrep::view third_view;
+
+ void connect_in_view(const wsrep::view& view)
+ {
+ BOOST_REQUIRE(ss.connect("cluster", "local", "0", false) == 0);
+ ss.on_connect(view);
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_connected);
+ }
+
+ void prepare_for_sst()
+ {
+ ss.prepare_for_sst();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joiner);
+ }
+
+ void final_view()
+ {
+ BOOST_REQUIRE(ss.state() != wsrep::server_state::s_disconnected);
+ wsrep::view view(wsrep::gtid(), // state_id
+ wsrep::seqno::undefined(), // view seqno
+ wsrep::view::disconnected, // status
+ 0, // capabilities
+ -1, // own_index
+ 0, // protocol ver
+ std::vector<wsrep::view::member>() // members
+ );
+ ss.on_view(view, &hps);
+ }
+
+ void disconnect()
+ {
+ BOOST_REQUIRE(ss.state() != wsrep::server_state::s_disconnecting);
+ ss.disconnect();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnecting);
+ final_view();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnected);
+ }
+
+ };
+
+ struct applying_server_fixture : server_fixture_base
+ {
+ applying_server_fixture()
+ : server_fixture_base()
+ {
+ ss.mock_connect();
+ }
+ };
+
+ struct sst_first_server_fixture : server_fixture_base
+ {
+ sst_first_server_fixture()
+ : server_fixture_base()
+ {
+ server_service.sst_before_init_ = true;
+ }
+
+ void sst_received_action()
+ {
+ server_service.sync_point_enabled_ = "on_view_wait_initialized";
+ server_service.sync_point_action_ = server_service.spa_initialize;
+ }
+
+ void initialization_failure_action()
+ {
+ server_service.sync_point_enabled_ = "on_view_wait_initialized";
+ server_service.sync_point_action_ =
+ server_service.spa_initialize_error;
+ }
+
+ void clear_sync_point_action()
+ {
+ server_service.sync_point_enabled_ = "";
+ server_service.sync_point_action_ = server_service.spa_none;
+ }
+
+ // Helper method to bootstrap the server with bootstrap view
+ void bootstrap()
+ {
+ connect_in_view(bootstrap_view);
+
+ sst_received_action();
+ ss.on_view(bootstrap_view, &hps);
+ clear_sync_point_action();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joined);
+ ss.on_sync();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced);
+ }
+
+ };
+
+ struct init_first_server_fixture : server_fixture_base
+ {
+ init_first_server_fixture()
+ : server_fixture_base()
+ {
+ server_service.sst_before_init_ = false;
+ }
+
+ // Helper method to bootstrap the server with bootstrap view
+ void bootstrap()
+ {
+ ss.initialized();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_initialized);
+ BOOST_REQUIRE(ss.connect("cluster", "local", "0", false) == 0);
+ ss.on_connect(bootstrap_view);
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_connected);
+ ss.on_view(bootstrap_view, &hps);
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joined);
+ ss.on_sync();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced);
+ }
+ };
+
+ // Helper to pass to BOOST_REQUIRE_EXCEPTION. Always returns true.
+ bool exception_check(const wsrep::runtime_error&) { return true; }
+}
+
+// Test on_apply() method for 1pc
+BOOST_FIXTURE_TEST_CASE(server_state_applying_1pc,
+ applying_server_fixture)
+{
+ char buf[1] = { 1 };
+ BOOST_REQUIRE(ss.on_apply(hps, ws_handle, ws_meta,
+ wsrep::const_buffer(buf, 1)) == 0);
+ const wsrep::transaction& txc(cc.transaction());
+ // ::abort();
+ BOOST_REQUIRE_MESSAGE(
+ txc.state() == wsrep::transaction::s_committed,
+ "Transaction state " << txc.state() << " not committed");
+}
+
+// Test on_apply() method for 2pc
+BOOST_FIXTURE_TEST_CASE(server_state_applying_2pc,
+ applying_server_fixture)
+{
+ hps.do_2pc_ = true;
+ char buf[1] = { 1 };
+ BOOST_REQUIRE(ss.on_apply(hps, ws_handle, ws_meta,
+ wsrep::const_buffer(buf, 1)) == 0);
+ const wsrep::transaction& txc(cc.transaction());
+ BOOST_REQUIRE(txc.state() == wsrep::transaction::s_committed);
+}
+
+// Test on_apply() method for 1pc transaction which
+// fails applying and rolls back
+BOOST_FIXTURE_TEST_CASE(server_state_applying_1pc_rollback,
+ applying_server_fixture)
+{
+ /* make sure default success result is flipped to error_fatal */
+ ss.provider().commit_order_leave_result_ = wsrep::provider::success;
+ hps.fail_next_applying_ = true;
+ char buf[1] = { 1 };
+ BOOST_REQUIRE(ss.on_apply(hps, ws_handle, ws_meta,
+ wsrep::const_buffer(buf, 1)) == 1);
+ const wsrep::transaction& txc(cc.transaction());
+ BOOST_REQUIRE(txc.state() == wsrep::transaction::s_aborted);
+}
+
+// Test on_apply() method for 2pc transaction which
+// fails applying and rolls back
+BOOST_FIXTURE_TEST_CASE(server_state_applying_2pc_rollback,
+ applying_server_fixture)
+{
+ /* make sure default success result is flipped to error_fatal */
+ ss.provider().commit_order_leave_result_ = wsrep::provider::success;
+ hps.do_2pc_ = true;
+ hps.fail_next_applying_ = true;
+ char buf[1] = { 1 };
+ BOOST_REQUIRE(ss.on_apply(hps, ws_handle, ws_meta,
+ wsrep::const_buffer(buf, 1)) == 1);
+ const wsrep::transaction& txc(cc.transaction());
+ BOOST_REQUIRE(txc.state() == wsrep::transaction::s_aborted);
+}
+
+BOOST_FIXTURE_TEST_CASE(server_state_streaming, applying_server_fixture)
+{
+ ws_meta = wsrep::ws_meta(wsrep::gtid(wsrep::id("1"), wsrep::seqno(1)),
+ wsrep::stid(wsrep::id("1"),
+ wsrep::transaction_id(1),
+ wsrep::client_id(1)),
+ wsrep::seqno(0),
+ wsrep::provider::flag::start_transaction);
+ BOOST_REQUIRE(ss.on_apply(hps, ws_handle, ws_meta,
+ wsrep::const_buffer("1", 1)) == 0);
+ BOOST_REQUIRE(ss.find_streaming_applier(
+ ws_meta.server_id(), ws_meta.transaction_id()));
+ ws_meta = wsrep::ws_meta(wsrep::gtid(wsrep::id("1"), wsrep::seqno(2)),
+ wsrep::stid(wsrep::id("1"),
+ wsrep::transaction_id(1),
+ wsrep::client_id(1)),
+ wsrep::seqno(1),
+ 0);
+ BOOST_REQUIRE(ss.on_apply(hps, ws_handle, ws_meta,
+ wsrep::const_buffer("1", 1)) == 0);
+ ws_meta = wsrep::ws_meta(wsrep::gtid(wsrep::id("1"), wsrep::seqno(2)),
+ wsrep::stid(wsrep::id("1"),
+ wsrep::transaction_id(1),
+ wsrep::client_id(1)),
+ wsrep::seqno(1),
+ wsrep::provider::flag::commit);
+ BOOST_REQUIRE(ss.on_apply(hps, ws_handle, ws_meta,
+ wsrep::const_buffer("1", 1)) == 0);
+ BOOST_REQUIRE(ss.find_streaming_applier(
+ ws_meta.server_id(), ws_meta.transaction_id()) == 0);
+}
+
+
+BOOST_AUTO_TEST_CASE(server_state_state_strings)
+{
+ BOOST_REQUIRE(wsrep::to_string(
+ wsrep::server_state::s_disconnected) == "disconnected");
+ BOOST_REQUIRE(wsrep::to_string(
+ wsrep::server_state::s_initializing) == "initializing");
+ BOOST_REQUIRE(wsrep::to_string(
+ wsrep::server_state::s_initialized) == "initialized");
+ BOOST_REQUIRE(wsrep::to_string(
+ wsrep::server_state::s_connected) == "connected");
+ BOOST_REQUIRE(wsrep::to_string(
+ wsrep::server_state::s_joiner) == "joiner");
+ BOOST_REQUIRE(wsrep::to_string(
+ wsrep::server_state::s_joined) == "joined");
+ BOOST_REQUIRE(wsrep::to_string(
+ wsrep::server_state::s_donor) == "donor");
+ BOOST_REQUIRE(wsrep::to_string(
+ wsrep::server_state::s_synced) == "synced");
+ BOOST_REQUIRE(wsrep::to_string(
+ wsrep::server_state::s_disconnecting) == "disconnecting");
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Test cases for SST first //
+///////////////////////////////////////////////////////////////////////////////
+
+BOOST_FIXTURE_TEST_CASE(server_state_sst_first_boostrap,
+ sst_first_server_fixture)
+{
+ bootstrap();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced);
+}
+
+
+BOOST_FIXTURE_TEST_CASE(server_state_sst_first_join_with_sst,
+ sst_first_server_fixture)
+{
+ connect_in_view(second_view);
+ prepare_for_sst();
+ sst_received_action();
+ // Mock server service get_view() gets view from logged_view_.
+ // Get_view() is called from sst_received(). This emulates the
+ // case where SST contains the view in which SST happens.
+ server_service.logged_view(second_view);
+ server_service.position(wsrep::gtid(cluster_id, wsrep::seqno(2)));
+ ss.sst_received(cc, 0);
+ clear_sync_point_action();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joined);
+ ss.on_sync();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced);
+}
+
+BOOST_FIXTURE_TEST_CASE(server_state_sst_first_join_with_ist,
+ sst_first_server_fixture)
+{
+ connect_in_view(second_view);
+ // Mock server service get_view() gets view from logged_view_.
+ // Get_view() is called from sst_received(). This emulates the
+ // case where the view is stored in stable storage.
+ server_service.logged_view(second_view);
+ sst_received_action();
+ ss.on_view(second_view, &hps);
+ clear_sync_point_action();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joined);
+ ss.on_view(third_view, &hps);
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joined);
+ ss.on_sync();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced);
+}
+
+
+// Cycle from synced state to disconnected and back to synced. Server
+// storage engines remain initialized.
+BOOST_FIXTURE_TEST_CASE(
+ server_state_sst_first_synced_disconnected_synced_no_sst,
+ sst_first_server_fixture)
+{
+ bootstrap();
+ ss.disconnect();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnecting);
+ final_view();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnected);
+
+ // Connect back as a sole member in the cluster
+ BOOST_REQUIRE(ss.connect("cluster", "local", "0", false) == 0);
+ // @todo: s_connecting state would be good to have
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnected);
+ // Server state must keep the initialized state
+ BOOST_REQUIRE(ss.is_initialized() == true);
+ std::vector<wsrep::view::member> members;
+ members.push_back(wsrep::view::member(wsrep::id("s1"), "name", ""));
+ wsrep::view view(wsrep::gtid(cluster_id, wsrep::seqno(1)),
+ wsrep::seqno(2),
+ wsrep::view::primary,
+ 0, // capabilities
+ 0, // own index
+ 1, // protocol version
+ members);
+ ss.on_connect(view);
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_connected);
+ // As storage engines have been initialized, there should not be
+ // any reason to wait for initialization. State should jump directly
+ // to s_joined after handling the view.
+ ss.on_view(view, &hps);
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joined);
+ ss.on_sync();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced);
+}
+
+//
+// Error after connecting to cluster. This scenario may happen if SST
+// request preparation fails.
+//
+BOOST_FIXTURE_TEST_CASE(
+ server_state_sst_first_error_on_connect,
+ sst_first_server_fixture)
+{
+ connect_in_view(second_view);
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_connected);
+ disconnect();
+}
+
+// Error during SST.
+BOOST_FIXTURE_TEST_CASE(
+ server_state_sst_first_error_on_joiner,
+ sst_first_server_fixture)
+{
+ connect_in_view(second_view);
+ ss.prepare_for_sst();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joiner);
+ server_service.position(wsrep::gtid::undefined());
+ ss.sst_received(cc, 1);
+ disconnect();
+}
+
+BOOST_FIXTURE_TEST_CASE(
+ server_state_sst_first_error_on_initializing,
+ sst_first_server_fixture)
+{
+ connect_in_view(second_view);
+ ss.prepare_for_sst();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joiner);
+ initialization_failure_action();
+ server_service.position(wsrep::gtid(second_view.state_id()));
+ BOOST_REQUIRE_EXCEPTION(ss.sst_received(cc, 0),
+ wsrep::runtime_error, exception_check);
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_initializing);
+ BOOST_REQUIRE_EXCEPTION(ss.on_view(second_view, &hps),
+ wsrep::runtime_error, exception_check);
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_initializing);
+ disconnect();
+}
+
+// Error or shutdown happens during catchup phase after receiving
+// SST successfully.
+BOOST_FIXTURE_TEST_CASE(
+ server_state_sst_first_error_on_joined,
+ sst_first_server_fixture)
+{
+ connect_in_view(second_view);
+ ss.prepare_for_sst();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joiner);
+ sst_received_action();
+ // Mock server service get_view() gets view from logged_view_.
+ // Get_view() is called from sst_received(). This emulates the
+ // case where SST contains the view in which SST happens.
+ server_service.logged_view(second_view);
+ server_service.position(wsrep::gtid(cluster_id, wsrep::seqno(2)));
+ ss.sst_received(cc, 0);
+ clear_sync_point_action();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joined);
+ disconnect();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnected);
+}
+
+// Error or shutdown happens when donating a snapshot.
+BOOST_FIXTURE_TEST_CASE(
+ server_state_sst_first_error_on_donor,
+ sst_first_server_fixture)
+{
+ bootstrap();
+ ss.start_sst("", wsrep::gtid(cluster_id, wsrep::seqno(2)), false);
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_donor);
+ disconnect();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnected);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Test cases for init first //
+///////////////////////////////////////////////////////////////////////////////
+
+BOOST_FIXTURE_TEST_CASE(server_state_init_first_boostrap,
+ init_first_server_fixture)
+{
+ bootstrap();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced);
+}
+
+BOOST_FIXTURE_TEST_CASE(server_state_init_first_join_with_sst,
+ init_first_server_fixture)
+{
+ ss.initialized();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_initialized);
+ connect_in_view(second_view);
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_connected);
+ prepare_for_sst();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joiner);
+ server_service.logged_view(second_view);
+ server_service.position(wsrep::gtid(cluster_id, wsrep::seqno(2)));
+ ss.sst_received(cc, 0);
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joined);
+ ss.on_sync();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced);
+}
+
+BOOST_FIXTURE_TEST_CASE(server_state_init_first_join_with_ist,
+ init_first_server_fixture)
+{
+ ss.initialized();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_initialized);
+ connect_in_view(second_view);
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_connected);
+ server_service.logged_view(second_view);
+ ss.on_view(second_view, &hps);
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joined);
+ ss.on_view(third_view, &hps);
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joined);
+ ss.on_sync();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced);
+}
+
+
+// Cycle from synced state to disconnected and back to synced. Server
+// storage engines remain initialized.
+BOOST_FIXTURE_TEST_CASE(
+ server_state_init_first_synced_disconnected_synced_no_sst,
+ init_first_server_fixture)
+{
+ bootstrap();
+ ss.disconnect();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnecting);
+ final_view();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnected);
+
+ // Connect back as a sole member in the cluster
+ BOOST_REQUIRE(ss.connect("cluster", "local", "0", false) == 0);
+ // @todo: s_connecting state would be good to have
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnected);
+ // Server state must keep the initialized state
+ BOOST_REQUIRE(ss.is_initialized() == true);
+ std::vector<wsrep::view::member> members;
+ members.push_back(wsrep::view::member(wsrep::id("s1"), "name", ""));
+ wsrep::view view(wsrep::gtid(cluster_id, wsrep::seqno(1)),
+ wsrep::seqno(2),
+ wsrep::view::primary,
+ 0, // capabilities
+ 0, // own index
+ 1, // protocol version
+ members);
+ ss.on_connect(view);
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_connected);
+ // As storage engines have been initialized, there should not be
+ // any reason to wait for initialization. State should jump directly
+ // to s_joined after handling the view.
+ ss.on_view(view, &hps);
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joined);
+ ss.on_sync();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Donor state transitions //
+/////////////////////////////////////////////////////////////////////////////
+
+BOOST_FIXTURE_TEST_CASE(
+ server_state_sst_first_donate_success,
+ sst_first_server_fixture)
+{
+ bootstrap();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced);
+ ss.start_sst("", wsrep::gtid(cluster_id, wsrep::seqno(2)), false);
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_donor);
+ ss.sst_sent(wsrep::gtid(cluster_id, wsrep::seqno(2)), 0);
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joined);
+ ss.on_sync();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced);
+}
+
+BOOST_FIXTURE_TEST_CASE(
+ server_state_sst_first_donate_error,
+ sst_first_server_fixture)
+{
+ bootstrap();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced);
+ ss.start_sst("", wsrep::gtid(cluster_id, wsrep::seqno(2)), false);
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_donor);
+ ss.sst_sent(wsrep::gtid(cluster_id, wsrep::seqno(2)), 1);
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_joined);
+ ss.on_sync();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Pause/Resume and Desync/Resync //
+/////////////////////////////////////////////////////////////////////////////
+
+BOOST_FIXTURE_TEST_CASE(
+ server_state_sst_first_desync_and_pause_resync_and_resume,
+ sst_first_server_fixture)
+{
+ bootstrap();
+ ss.desync_and_pause();
+ // @todo: Should we have here different state than synced
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced);
+ ss.resume_and_resync();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_synced);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Disconnect //
+/////////////////////////////////////////////////////////////////////////////
+
+BOOST_FIXTURE_TEST_CASE(
+ server_state_disconnect,
+ sst_first_server_fixture)
+{
+ bootstrap();
+ ss.disconnect();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnecting);
+ final_view();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnected);
+}
+
+// This test case verifies that the disconnect can be initiated
+// concurrently by several callers. This may happen in failure situations
+// where provider shutdown causes cascading failures and the failing operations
+// try to disconnect the provider.
+BOOST_FIXTURE_TEST_CASE(
+ server_state_disconnect_twice,
+ sst_first_server_fixture)
+{
+ bootstrap();
+ ss.disconnect();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnecting);
+ ss.disconnect();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnecting);
+ final_view();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnected);
+ ss.disconnect();
+ BOOST_REQUIRE(ss.state() == wsrep::server_state::s_disconnected);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Orphaned SR //
+/////////////////////////////////////////////////////////////////////////////
+
+// Test the behavior of server_state::close_orphaned_sr_transactions().
+// In this test we check the scenario where we initially have 3 nodes in
+// the cluster (s1, s2, s3), and this server_state delivers one streaming
+// fragment from s2 and s3 each, followed by view changes:
+//
+// view 1: primary (s1, s2, s3)
+// view 2: primary (s1, s2)
+// view 3: non-primary (s1)
+// view 4: non-primary (s1, s3)
+// view 5: primary (s1, s2, s3)
+//
+// We expect that on view 2, transaction originated from s3 is considered
+// orphaned, so it should be rolled back.
+// Transaction from s2 should never be considered orphaned in this scenario,
+// we expect it to survive until the end of the test. That's because
+// transactions are rolled back in primary views only, and because s2
+// is member of all primary views in this scenario.
+BOOST_FIXTURE_TEST_CASE(server_state_close_orphaned_transactions,
+ sst_first_server_fixture)
+{
+ connect_in_view(third_view);
+ server_service.logged_view(third_view);
+ sst_received_action();
+ ss.on_view(third_view, &hps);
+
+ // initially we have members (s1, s2, s3)
+ std::vector<wsrep::view::member> members(ss.current_view().members());
+
+ // apply a fragment coming from s2
+ wsrep::ws_meta meta_s2(wsrep::gtid(wsrep::id("s2"), wsrep::seqno(1)),
+ wsrep::stid(wsrep::id("s2"),
+ wsrep::transaction_id(1),
+ wsrep::client_id(1)),
+ wsrep::seqno(1),
+ wsrep::provider::flag::start_transaction);
+
+ BOOST_REQUIRE(ss.on_apply(hps, ws_handle, meta_s2,
+ wsrep::const_buffer("1", 1)) == 0);
+ BOOST_REQUIRE(ss.find_streaming_applier(
+ meta_s2.server_id(), meta_s2.transaction_id()));
+
+ // apply a fragment coming from s3
+ wsrep::ws_meta meta_s3(wsrep::gtid(wsrep::id("s3"), wsrep::seqno(2)),
+ wsrep::stid(wsrep::id("s3"),
+ wsrep::transaction_id(1),
+ wsrep::client_id(1)),
+ wsrep::seqno(2),
+ wsrep::provider::flag::start_transaction);
+
+ BOOST_REQUIRE(ss.on_apply(hps, ws_handle, meta_s3,
+ wsrep::const_buffer("1", 1)) == 0);
+ BOOST_REQUIRE(ss.find_streaming_applier(
+ meta_s3.server_id(), meta_s3.transaction_id()));
+
+ // s3 drops out of the cluster, deliver primary view (s1, s2)
+ wsrep::view::member s3(members.back());
+ members.pop_back();
+ ss.on_view(wsrep::view(ss.current_view().state_id(),
+ ss.current_view().view_seqno() + 1,
+ wsrep::view::primary,
+ 0, // capabilities
+ 0, // own index
+ 1, // protocol version
+ members), &hps);
+
+ // transaction from s2 is still present
+ BOOST_REQUIRE(ss.find_streaming_applier(
+ meta_s2.server_id(), meta_s2.transaction_id()));
+ // transaction from s3 is gone
+ BOOST_REQUIRE(not ss.find_streaming_applier(
+ meta_s3.server_id(), meta_s3.transaction_id()));
+
+ // s2 drops out of the cluster, deliver non-primary view (s1)
+ wsrep::view::member s2(members.back());
+ members.pop_back();
+ ss.on_view(wsrep::view(ss.current_view().state_id(),
+ ss.current_view().view_seqno(),
+ wsrep::view::non_primary,
+ 0, // capabilities
+ 0, // own index
+ 1, // protocol version
+ members), &hps);
+
+ // no streaming appliers are closed on non-primary view,
+ // so transaction from s2 is still present
+ BOOST_REQUIRE(ss.find_streaming_applier(
+ meta_s2.server_id(), meta_s2.transaction_id()));
+
+ // s3 comes back, deliver non-primary view (s1, s3)
+ members.push_back(s3);
+ ss.on_view(wsrep::view(ss.current_view().state_id(),
+ ss.current_view().view_seqno() + 1,
+ wsrep::view::non_primary,
+ 0, // capabilities
+ 0, // own index
+ 1, // protocol version
+ members), &hps);
+
+ // transaction s2 is still present after non-primary view
+ BOOST_REQUIRE(ss.find_streaming_applier(
+ meta_s2.server_id(), meta_s2.transaction_id()));
+
+ // s2 comes back, deliver primary-view (s1, s2, s3)
+ members.push_back(s2);
+ ss.on_view(wsrep::view(ss.current_view().state_id(),
+ ss.current_view().view_seqno() + 1,
+ wsrep::view::primary,
+ 0, // capabilities
+ 0, // own index
+ 1, // protocol version
+ members), &hps);
+
+ // finally, transaction from s2 is still present (part of primary view)
+ // and transaction from s3 is gone
+ BOOST_REQUIRE(ss.find_streaming_applier(
+ meta_s2.server_id(), meta_s2.transaction_id()));
+ BOOST_REQUIRE(not ss.find_streaming_applier(
+ meta_s3.server_id(), meta_s3.transaction_id()));
+
+ // cleanup
+ wsrep::ws_meta meta_commit_s2(wsrep::gtid(wsrep::id("s2"), wsrep::seqno(3)),
+ wsrep::stid(wsrep::id("s2"),
+ wsrep::transaction_id(1),
+ wsrep::client_id(1)),
+ wsrep::seqno(3),
+ wsrep::provider::flag::commit);
+
+ BOOST_REQUIRE(ss.on_apply(hps, ws_handle, meta_commit_s2,
+ wsrep::const_buffer("1", 1)) == 0);
+
+ BOOST_REQUIRE(not ss.find_streaming_applier(
+ meta_commit_s2.server_id(), meta_commit_s2.transaction_id()));
+}
+
+
+// Test the case where two consecutive primary views with the
+// same members are delivered (provider may do so).
+// Expect SR transactions to be rolled back on equal consecutive views
+BOOST_FIXTURE_TEST_CASE(server_state_equal_consecutive_views,
+ sst_first_server_fixture)
+{
+ connect_in_view(third_view);
+ server_service.logged_view(third_view);
+ sst_received_action();
+ ss.on_view(third_view, &hps);
+
+ // apply a fragment coming from s2
+ wsrep::ws_meta meta_s2(wsrep::gtid(wsrep::id("s2"), wsrep::seqno(1)),
+ wsrep::stid(wsrep::id("s2"),
+ wsrep::transaction_id(1),
+ wsrep::client_id(1)),
+ wsrep::seqno(1),
+ wsrep::provider::flag::start_transaction);
+
+ BOOST_REQUIRE(ss.on_apply(hps, ws_handle, meta_s2,
+ wsrep::const_buffer("1", 1)) == 0);
+ BOOST_REQUIRE(ss.find_streaming_applier(
+ meta_s2.server_id(), meta_s2.transaction_id()));
+
+ // apply a fragment coming from s3
+ wsrep::ws_meta meta_s3(wsrep::gtid(wsrep::id("s3"), wsrep::seqno(2)),
+ wsrep::stid(wsrep::id("s3"),
+ wsrep::transaction_id(1),
+ wsrep::client_id(1)),
+ wsrep::seqno(2),
+ wsrep::provider::flag::start_transaction);
+
+ BOOST_REQUIRE(ss.on_apply(hps, ws_handle, meta_s3,
+ wsrep::const_buffer("1", 1)) == 0);
+ BOOST_REQUIRE(ss.find_streaming_applier(
+ meta_s3.server_id(), meta_s3.transaction_id()));
+
+ // deliver primary view with the same members (s1, s2, s3)
+ ss.on_view(wsrep::view(ss.current_view().state_id(),
+ ss.current_view().view_seqno() + 1,
+ wsrep::view::primary,
+ 0, // capabilities
+ 0, // own index
+ 1, // protocol version
+ ss.current_view().members()), &hps);
+
+ // transaction from s2 and s3 are gone
+ BOOST_REQUIRE(not ss.find_streaming_applier(
+ meta_s2.server_id(), meta_s2.transaction_id()));
+ BOOST_REQUIRE(not ss.find_streaming_applier(
+ meta_s3.server_id(), meta_s3.transaction_id()));
+}
+
+// Verify that prepared XA transactions are not rolled back
+// by close_orphaned_transactions()
+BOOST_FIXTURE_TEST_CASE(server_state_xa_not_orphaned,
+ sst_first_server_fixture)
+{
+ connect_in_view(third_view);
+ server_service.logged_view(third_view);
+ sst_received_action();
+ ss.on_view(third_view, &hps);
+
+ // initially we have members (s1, s2, s3)
+ std::vector<wsrep::view::member> members(ss.current_view().members());
+
+
+ wsrep::ws_meta meta_s3(wsrep::gtid(wsrep::id("s3"), wsrep::seqno(1)),
+ wsrep::stid(wsrep::id("s3"),
+ wsrep::transaction_id(1),
+ wsrep::client_id(1)),
+ wsrep::seqno(1),
+ wsrep::provider::flag::start_transaction |
+ wsrep::provider::flag::prepare);
+
+ BOOST_REQUIRE(ss.on_apply(hps, ws_handle, meta_s3,
+ wsrep::const_buffer("1", 1)) == 0);
+ BOOST_REQUIRE(ss.find_streaming_applier(
+ meta_s3.server_id(), meta_s3.transaction_id()));
+
+
+ // s3 drops out of the cluster, deliver primary view (s1, s2)
+ wsrep::view::member s3(members.back());
+ members.pop_back();
+ ss.on_view(wsrep::view(ss.current_view().state_id(),
+ ss.current_view().view_seqno() + 1,
+ wsrep::view::primary,
+ 0, // capabilities
+ 0, // own index
+ 1, // protocol version
+ members), &hps);
+
+ // transaction from s3 is still present
+ BOOST_REQUIRE(ss.find_streaming_applier(
+ meta_s3.server_id(), meta_s3.transaction_id()));
+
+ // s3 comes back, deliver primary view (s1, s2, s3)
+ members.push_back(s3);
+ ss.on_view(wsrep::view(ss.current_view().state_id(),
+ ss.current_view().view_seqno() + 1,
+ wsrep::view::primary,
+ 0, // capabilities
+ 0, // own index
+ 1, // protocol version
+ members), &hps);
+
+ // transaction from s3 is still present
+ BOOST_REQUIRE(ss.find_streaming_applier(
+ meta_s3.server_id(), meta_s3.transaction_id()));
+
+ // cleanup
+ wsrep::ws_meta meta_commit_s3(wsrep::gtid(wsrep::id("s3"), wsrep::seqno(3)),
+ wsrep::stid(wsrep::id("s3"),
+ wsrep::transaction_id(1),
+ wsrep::client_id(1)),
+ wsrep::seqno(3),
+ wsrep::provider::flag::commit);
+
+ BOOST_REQUIRE(ss.on_apply(hps, ws_handle, meta_commit_s3,
+ wsrep::const_buffer("1", 1)) == 0);
+ BOOST_REQUIRE(not ss.find_streaming_applier(
+ meta_commit_s3.server_id(), meta_commit_s3.transaction_id()));
+}
diff --git a/wsrep-lib/test/test_utils.cpp b/wsrep-lib/test/test_utils.cpp
new file mode 100644
index 00000000..3646e96c
--- /dev/null
+++ b/wsrep-lib/test/test_utils.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2018 Codership Oy <info@codership.com>
+ *
+ * This file is part of wsrep-lib.
+ *
+ * Wsrep-lib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Wsrep-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "test_utils.hpp"
+#include "wsrep/client_state.hpp"
+#include "mock_server_state.hpp"
+
+
+// Simple BF abort method to BF abort unordered transasctions
+void wsrep_test::bf_abort_unordered(wsrep::client_state& cc)
+{
+ assert(cc.transaction().ordered() == false);
+ cc.bf_abort(wsrep::seqno(1));
+}
+
+void wsrep_test::bf_abort_ordered(wsrep::client_state& cc)
+{
+ assert(cc.transaction().ordered());
+ cc.bf_abort(wsrep::seqno(0));
+}
+// BF abort method to abort transactions via provider
+void wsrep_test::bf_abort_provider(wsrep::mock_server_state& sc,
+ const wsrep::transaction& tc,
+ wsrep::seqno bf_seqno)
+{
+ wsrep::seqno victim_seqno;
+ sc.provider().bf_abort(bf_seqno, tc.id(), victim_seqno);
+ (void)victim_seqno;
+}
+
+void wsrep_test::terminate_streaming_applier(
+ wsrep::mock_server_state& sc,
+ const wsrep::id& server_id,
+ wsrep::transaction_id transaction_id)
+{
+ // Note that all other arguments than server_id and
+ // transaction_id are chosen arbitrarily and it is hoped
+ // that the mock implementation does not freak out about it.
+ wsrep::mock_client mc(sc, wsrep::client_id(10),
+ wsrep::client_state::m_high_priority);
+ mc.open(wsrep::client_id(10));
+ mc.before_command();
+ wsrep::mock_high_priority_service hps(sc, &mc, false);
+ wsrep::ws_handle ws_handle(transaction_id, (void*)(1));
+ wsrep::ws_meta ws_meta(wsrep::gtid(wsrep::id("cluster1"),
+ wsrep::seqno(100)),
+ wsrep::stid(server_id,
+ transaction_id,
+ wsrep::client_id(1)),
+ wsrep::seqno(0),
+ wsrep::provider::flag::rollback);
+ wsrep::const_buffer data(0, 0);
+ sc.on_apply(hps, ws_handle, ws_meta, data);
+}
diff --git a/wsrep-lib/test/test_utils.hpp b/wsrep-lib/test/test_utils.hpp
new file mode 100644
index 00000000..69ce7002
--- /dev/null
+++ b/wsrep-lib/test/test_utils.hpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 Codership Oy <info@codership.com>
+ *
+ * This file is part of wsrep-lib.
+ *
+ * Wsrep-lib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Wsrep-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+// Forward declarations
+namespace wsrep
+{
+ class client_state;
+ class mock_server_state;
+}
+
+#include "wsrep/transaction.hpp"
+#include "wsrep/provider.hpp"
+
+//
+// Utility functions
+//
+namespace wsrep_test
+{
+
+ // Simple BF abort method to BF abort unordered transasctions
+ void bf_abort_unordered(wsrep::client_state& cc);
+
+ // Simple BF abort method to BF abort unordered transasctions
+ void bf_abort_ordered(wsrep::client_state& cc);
+
+ // BF abort method to abort transactions via provider
+ void bf_abort_provider(wsrep::mock_server_state& sc,
+ const wsrep::transaction& tc,
+ wsrep::seqno bf_seqno);
+
+ // Terminate streaming applier by applying rollback fragment.
+ void terminate_streaming_applier(
+ wsrep::mock_server_state& sc,
+ const wsrep::id& server_id,
+ wsrep::transaction_id transaction_id);
+}
diff --git a/wsrep-lib/test/toi_test.cpp b/wsrep-lib/test/toi_test.cpp
new file mode 100644
index 00000000..e6370170
--- /dev/null
+++ b/wsrep-lib/test/toi_test.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 Codership Oy <info@codership.com>
+ *
+ * This file is part of wsrep-lib.
+ *
+ * Wsrep-lib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Wsrep-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "wsrep/client_state.hpp"
+
+#include "client_state_fixture.hpp"
+
+#include <boost/test/unit_test.hpp>
+
+BOOST_FIXTURE_TEST_CASE(test_toi_mode,
+ replicating_client_fixture_sync_rm)
+{
+ BOOST_REQUIRE(cc.mode() == wsrep::client_state::m_local);
+ BOOST_REQUIRE(cc.toi_mode() == wsrep::client_state::m_undefined);
+ wsrep::key key(wsrep::key::exclusive);
+ key.append_key_part("k1", 2);
+ key.append_key_part("k2", 2);
+ wsrep::key_array keys{key};
+ wsrep::const_buffer buf("toi", 3);
+ BOOST_REQUIRE(cc.enter_toi_local(keys, buf) == 0);
+ BOOST_REQUIRE(cc.mode() == wsrep::client_state::m_toi);
+ BOOST_REQUIRE(cc.in_toi());
+ BOOST_REQUIRE(cc.toi_mode() == wsrep::client_state::m_local);
+ wsrep::mutable_buffer err;
+ BOOST_REQUIRE(cc.leave_toi_local(err) == 0);
+ BOOST_REQUIRE(cc.mode() == wsrep::client_state::m_local);
+ BOOST_REQUIRE(cc.toi_mode() == wsrep::client_state::m_undefined);
+ BOOST_REQUIRE(sc.provider().toi_write_sets() == 1);
+ BOOST_REQUIRE(sc.provider().toi_start_transaction() == 1);
+ BOOST_REQUIRE(sc.provider().toi_commit() == 1);
+}
+
+BOOST_FIXTURE_TEST_CASE(test_toi_applying,
+ applying_client_fixture)
+{
+ BOOST_REQUIRE(cc.toi_mode() == wsrep::client_state::m_undefined);
+ wsrep::ws_meta ws_meta(wsrep::gtid(wsrep::id("1"), wsrep::seqno(2)),
+ wsrep::stid(sc.id(),
+ wsrep::transaction_id::undefined(),
+ cc.id()),
+ wsrep::seqno(1),
+ wsrep::provider::flag::start_transaction |
+ wsrep::provider::flag::commit);
+ cc.enter_toi_mode(ws_meta);
+ BOOST_REQUIRE(cc.in_toi());
+ BOOST_REQUIRE(cc.toi_mode() == wsrep::client_state::m_high_priority);
+ cc.leave_toi_mode();
+ BOOST_REQUIRE(cc.toi_mode() == wsrep::client_state::m_undefined);
+ cc.after_applying();
+}
diff --git a/wsrep-lib/test/transaction_test.cpp b/wsrep-lib/test/transaction_test.cpp
new file mode 100644
index 00000000..493bda8d
--- /dev/null
+++ b/wsrep-lib/test/transaction_test.cpp
@@ -0,0 +1,1667 @@
+/*
+ * Copyright (C) 2018-2019 Codership Oy <info@codership.com>
+ *
+ * This file is part of wsrep-lib.
+ *
+ * Wsrep-lib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Wsrep-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "wsrep/transaction.hpp"
+#include "wsrep/provider.hpp"
+
+#include "test_utils.hpp"
+#include "client_state_fixture.hpp"
+
+#include <boost/mpl/vector.hpp>
+
+namespace
+{
+ typedef
+ boost::mpl::vector<replicating_client_fixture_sync_rm,
+ replicating_client_fixture_async_rm>
+ replicating_fixtures;
+}
+
+BOOST_FIXTURE_TEST_CASE(transaction_append_key_data,
+ replicating_client_fixture_sync_rm)
+{
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.is_empty());
+ int vals[3] = {1, 2, 3};
+ wsrep::key key(wsrep::key::exclusive);
+ for (int i(0); i < 3; ++i)
+ {
+ key.append_key_part(&vals[i], sizeof(vals[i]));
+ }
+ BOOST_REQUIRE(cc.append_key(key) == 0);
+ BOOST_REQUIRE(tc.is_empty() == false);
+ wsrep::const_buffer data(&vals[2], sizeof(vals[2]));
+ BOOST_REQUIRE(cc.append_data(data) == 0);
+ BOOST_REQUIRE(cc.before_commit() == 0);
+ BOOST_REQUIRE(cc.ordered_commit() == 0);
+ BOOST_REQUIRE(cc.after_commit() == 0);
+ cc.after_statement();
+}
+//
+// Test a succesful 1PC transaction lifecycle
+//
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(transaction_1pc, T,
+ replicating_fixtures, T)
+{
+ wsrep::mock_client& cc(T::cc);
+ const wsrep::transaction& tc(T::tc);
+ // Start a new transaction with ID 1
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+
+ // Establish default read view
+ BOOST_REQUIRE(0 == cc.assign_read_view(NULL));
+
+ // Verify that the commit can be successfully executed in separate command
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ cc.after_command_before_result();
+ cc.after_command_after_result();
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+ BOOST_REQUIRE(cc.before_command() == 0);
+ BOOST_REQUIRE(cc.before_statement() == 0);
+ // Run before commit
+ BOOST_REQUIRE(cc.before_commit() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committing);
+
+ // Run ordered commit
+ BOOST_REQUIRE(cc.ordered_commit() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_ordered_commit);
+
+ // Run after commit
+ BOOST_REQUIRE(cc.after_commit() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committed);
+
+ // Cleanup after statement
+ cc.after_statement();
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+}
+
+
+//
+// Test a voluntary rollback
+//
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(transaction_rollback, T,
+ replicating_fixtures, T)
+{
+ wsrep::mock_client& cc(T::cc);
+ const wsrep::transaction& tc(T::tc);
+
+ // Start a new transaction with ID 1
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+
+ // Run before commit
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting);
+
+ // Run after commit
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+
+ // Cleanup after statement
+ cc.after_statement();
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+}
+
+//
+// Test a 1PC transaction which gets BF aborted before before_commit
+//
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(
+ transaction_1pc_bf_before_before_commit, T,
+ replicating_fixtures, T)
+{
+ wsrep::mock_client& cc(T::cc);
+ const wsrep::transaction& tc(T::tc);
+
+ // Start a new transaction with ID 1
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+
+ wsrep_test::bf_abort_unordered(cc);
+
+ // Run before commit
+ BOOST_REQUIRE(cc.before_commit());
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+
+ // Rollback sequence
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+
+ // Cleanup after statement
+ cc.after_statement();
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.current_error());
+}
+
+
+
+//
+// Test a 1PC transaction which gets BF aborted during before_commit via
+// provider before the write set was ordered and certified.
+//
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(
+ transaction_1pc_bf_during_before_commit_uncertified, T,
+ replicating_fixtures, T)
+{
+ wsrep::mock_server_state& sc(T::sc);
+ wsrep::mock_client& cc(T::cc);
+ const wsrep::transaction& tc(T::tc);
+
+ // Start a new transaction with ID 1
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+
+ wsrep_test::bf_abort_provider(sc, tc, wsrep::seqno::undefined());
+
+ // Run before commit
+ BOOST_REQUIRE(cc.before_commit());
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_cert_failed);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+
+ // Rollback sequence
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+
+ // Cleanup after statement
+ cc.after_statement();
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.current_error());
+}
+
+//
+// Test a 1PC transaction which gets BF aborted during before_commit
+// when waiting for replayers
+//
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(
+ transaction_1pc_bf_during_commit_wait_for_replayers, T,
+ replicating_fixtures, T)
+{
+ wsrep::mock_client& cc(T::cc);
+ const wsrep::transaction& tc(T::tc);
+
+ // Start a new transaction with ID 1
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+
+ cc.bf_abort_during_wait_ = true;
+
+ // Run before commit
+ BOOST_REQUIRE(cc.before_commit());
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+
+ // Rollback sequence
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+
+ // Cleanup after statement
+ cc.after_statement();
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.current_error());
+}
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(
+ transaction_1pc_bf_during_commit_order_enter, T,
+ replicating_fixtures, T)
+{
+ wsrep::mock_client& cc(T::cc);
+ const wsrep::transaction& tc(T::tc);
+ auto& sc(T::sc);
+
+ // Start a new transaction with ID 1
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+
+ sc.provider().commit_order_enter_result_ = wsrep::provider::error_bf_abort;
+
+ // Run before commit
+ BOOST_REQUIRE(cc.before_commit());
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay);
+ BOOST_REQUIRE(cc.will_replay_called() == true);
+ BOOST_REQUIRE(tc.certified() == true);
+ BOOST_REQUIRE(tc.ordered() == true);
+
+ sc.provider().commit_order_enter_result_ = wsrep::provider::success;
+
+ // Rollback sequence
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay);
+
+ // Replay from after_statement()
+ cc.after_statement();
+ BOOST_REQUIRE(cc.replays() > 0);
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(not cc.current_error());
+}
+
+//
+// Test a 1PC transaction for which prepare data fails
+//
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(
+ transaction_1pc_error_during_prepare_data, T,
+ replicating_fixtures, T)
+{
+ wsrep::mock_client& cc(T::cc);
+ const wsrep::transaction& tc(T::tc);
+
+ // Start a new transaction with ID 1
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+
+ cc.error_during_prepare_data_ = true;
+
+ // Run before commit
+ BOOST_REQUIRE(cc.before_commit());
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_size_exceeded_error);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+
+ // Rollback sequence
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+
+ // Cleanup after statement
+ cc.after_statement();
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.current_error());
+}
+
+//
+// Test a 1PC transaction which gets killed by DBMS before certification
+//
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(
+ transaction_1pc_killed_before_certify, T,
+ replicating_fixtures, T)
+{
+ wsrep::mock_client& cc(T::cc);
+ const wsrep::transaction& tc(T::tc);
+
+ // Start a new transaction with ID 1
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+
+ cc.killed_before_certify_ = true;
+
+ // Run before commit
+ BOOST_REQUIRE(cc.before_commit());
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_interrupted_error);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+
+ // Rollback sequence
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+
+ // Cleanup after statement
+ cc.after_statement();
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.current_error());
+}
+
+//
+// Test a transaction which gets BF aborted inside provider before
+// certification result is known. Replaying will be successful
+//
+BOOST_FIXTURE_TEST_CASE(
+ transaction_bf_before_cert_result_replay_success,
+ replicating_client_fixture_sync_rm)
+{
+ BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0);
+ sc.provider().certify_result_ = wsrep::provider::error_bf_abort;
+ sc.provider().replay_result_ = wsrep::provider::success;
+
+ BOOST_REQUIRE(cc.before_commit());
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay);
+ BOOST_REQUIRE(cc.will_replay_called() == true);
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committed);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+}
+
+//
+// Test a transaction which gets BF aborted inside provider before
+// certification result is known. Replaying will fail because of
+// certification failure.
+//
+BOOST_FIXTURE_TEST_CASE(
+ transaction_bf_before_cert_result_replay_cert_fail,
+ replicating_client_fixture_sync_rm)
+{
+ BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0);
+ sc.provider().certify_result_ = wsrep::provider::error_bf_abort;
+ sc.provider().replay_result_ = wsrep::provider::error_certification_failed;
+
+ BOOST_REQUIRE(cc.before_commit());
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay);
+ BOOST_REQUIRE(cc.will_replay_called() == true);
+ BOOST_REQUIRE(cc.after_statement() );
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+ BOOST_REQUIRE(tc.active() == false);
+}
+
+//
+// Test a 1PC transaction which gets BF aborted during before_commit via
+// provider after the write set was ordered and certified. This must
+// result replaying of transaction.
+//
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(
+ transaction_1pc_bf_during_before_commit_certified, T,
+ replicating_fixtures, T)
+{
+ wsrep::mock_server_state& sc(T::sc);
+ wsrep::mock_client& cc(T::cc);
+ const wsrep::transaction& tc(T::tc);
+
+ // Start a new transaction with ID 1
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+
+ wsrep_test::bf_abort_provider(sc, tc, wsrep::seqno(1));
+
+ // Run before commit
+ BOOST_REQUIRE(cc.before_commit());
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay);
+ BOOST_REQUIRE(cc.will_replay_called() == true);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(tc.ordered() == true);
+
+ // Rollback sequence
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay);
+
+ // Cleanup after statement
+ cc.after_statement();
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+ BOOST_REQUIRE(cc.replays() == 1);
+}
+
+//
+// Test a 1PC transaction which gets BF aborted simultaneously with
+// certification failure. BF abort should not succeed as the
+// transaction is going to roll back anyway. Certification failure
+// should not generate seqno for write set meta.
+//
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(
+ transaction_1pc_bf_before_unordered_cert_failure, T,
+ replicating_fixtures, T)
+{
+ wsrep::mock_server_state& sc(T::sc);
+ wsrep::mock_client& cc(T::cc);
+ const wsrep::transaction& tc(T::tc);
+
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+ cc.sync_point_enabled_ = "wsrep_before_certification";
+ cc.sync_point_action_ = wsrep::mock_client_service::spa_bf_abort_unordered;
+ sc.provider().certify_result_ = wsrep::provider::error_certification_failed;
+ BOOST_REQUIRE(cc.before_commit());
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_cert_failed);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+ BOOST_REQUIRE(cc.after_statement() );
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+}
+
+//
+// Test a 1PC transaction which gets "warning error" from certify call
+//
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(
+ transaction_1pc_warning_error_from_certify, T,
+ replicating_fixtures, T)
+{
+ wsrep::mock_server_state& sc(T::sc);
+ wsrep::mock_client& cc(T::cc);
+ const wsrep::transaction& tc(T::tc);
+
+ // Start a new transaction with ID 1
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+
+ sc.provider().certify_result_ = wsrep::provider::error_warning;
+
+ // Run before commit
+ BOOST_REQUIRE(cc.before_commit());
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+
+ sc.provider().certify_result_ = wsrep::provider::success;
+
+ // Rollback sequence
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+
+ // Cleanup after statement
+ cc.after_statement();
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_error_during_commit);
+}
+
+//
+// Test a 1PC transaction which gets transaction missing from certify call
+//
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(
+ transaction_1pc_transaction_missing_from_certify, T,
+ replicating_fixtures, T)
+{
+ wsrep::mock_server_state& sc(T::sc);
+ wsrep::mock_client& cc(T::cc);
+ const wsrep::transaction& tc(T::tc);
+
+ // Start a new transaction with ID 1
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+
+ sc.provider().certify_result_ = wsrep::provider::error_transaction_missing;
+
+ // Run before commit
+ BOOST_REQUIRE(cc.before_commit());
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+
+ sc.provider().certify_result_ = wsrep::provider::success;
+
+ // Rollback sequence
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+
+ // Cleanup after statement
+ cc.after_statement();
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_error_during_commit);
+}
+
+//
+// Test a 1PC transaction which gets size exceeded error from certify call
+//
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(
+ transaction_1pc_size_exceeded_from_certify, T,
+ replicating_fixtures, T)
+{
+ wsrep::mock_server_state& sc(T::sc);
+ wsrep::mock_client& cc(T::cc);
+ const wsrep::transaction& tc(T::tc);
+
+ // Start a new transaction with ID 1
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+
+ sc.provider().certify_result_ = wsrep::provider::error_size_exceeded;
+
+ // Run before commit
+ BOOST_REQUIRE(cc.before_commit());
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+
+ sc.provider().certify_result_ = wsrep::provider::success;
+
+ // Rollback sequence
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+
+ // Cleanup after statement
+ cc.after_statement();
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_error_during_commit);
+}
+
+//
+// Test a 1PC transaction which gets connection failed error from certify call
+//
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(
+ transaction_1pc_connection_failed_from_certify, T,
+ replicating_fixtures, T)
+{
+ wsrep::mock_server_state& sc(T::sc);
+ wsrep::mock_client& cc(T::cc);
+ const wsrep::transaction& tc(T::tc);
+
+ // Start a new transaction with ID 1
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+
+ sc.provider().certify_result_ = wsrep::provider::error_connection_failed;
+
+ // Run before commit
+ BOOST_REQUIRE(cc.before_commit());
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+
+ sc.provider().certify_result_ = wsrep::provider::success;
+
+ // Rollback sequence
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+
+ // Cleanup after statement
+ cc.after_statement();
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_error_during_commit);
+}
+
+//
+// Test a 1PC transaction which gets not allowed error from certify call
+//
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(
+ transaction_1pc_no_allowed_from_certify, T,
+ replicating_fixtures, T)
+{
+ wsrep::mock_server_state& sc(T::sc);
+ wsrep::mock_client& cc(T::cc);
+ const wsrep::transaction& tc(T::tc);
+
+ // Start a new transaction with ID 1
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+
+ sc.provider().certify_result_ = wsrep::provider::error_not_allowed;
+
+ // Run before commit
+ BOOST_REQUIRE(cc.before_commit());
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+
+ sc.provider().certify_result_ = wsrep::provider::success;
+
+ // Rollback sequence
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+
+ // Cleanup after statement
+ cc.after_statement();
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_error_during_commit);
+}
+
+//
+// Test a 1PC transaction which gets fatal error from certify call
+//
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(
+ transaction_1pc_fatal_from_certify, T,
+ replicating_fixtures, T)
+{
+ wsrep::mock_server_state& sc(T::sc);
+ wsrep::mock_client& cc(T::cc);
+ const wsrep::transaction& tc(T::tc);
+
+ // Start a new transaction with ID 1
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+
+ sc.provider().certify_result_ = wsrep::provider::error_fatal;
+
+ // Run before commit
+ BOOST_REQUIRE(cc.before_commit());
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+
+ sc.provider().certify_result_ = wsrep::provider::success;
+
+ // Rollback sequence
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+
+ // Cleanup after statement
+ cc.after_statement();
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_error_during_commit);
+ BOOST_REQUIRE(cc.aborts() == 1);
+}
+
+//
+// Test a 1PC transaction which gets unknown from certify call
+//
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(
+ transaction_1pc_unknown_from_certify, T,
+ replicating_fixtures, T)
+{
+ wsrep::mock_server_state& sc(T::sc);
+ wsrep::mock_client& cc(T::cc);
+ const wsrep::transaction& tc(T::tc);
+
+ // Start a new transaction with ID 1
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+
+ sc.provider().certify_result_ = wsrep::provider::error_unknown;
+
+ // Run before commit
+ BOOST_REQUIRE(cc.before_commit());
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+
+ sc.provider().certify_result_ = wsrep::provider::success;
+
+ // Rollback sequence
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+
+ // Cleanup after statement
+ cc.after_statement();
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_error_during_commit);
+}
+
+//
+// Test a 1PC transaction which gets BF aborted before grabbing lock
+// after certify call
+//
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(
+ transaction_1pc_bf_abort_before_certify_regain_lock, T,
+ replicating_fixtures, T)
+{
+ wsrep::mock_client& cc(T::cc);
+ const wsrep::transaction& tc(T::tc);
+
+ // Start a new transaction with ID 1
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+
+ cc.sync_point_enabled_ = "wsrep_after_certification";
+ cc.sync_point_action_ = wsrep::mock_client_service::spa_bf_abort_ordered;
+ // Run before commit
+ BOOST_REQUIRE(cc.before_commit());
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay);
+ BOOST_REQUIRE(cc.will_replay_called() == true);
+ BOOST_REQUIRE(tc.certified() == true);
+ BOOST_REQUIRE(tc.ordered() == true);
+
+ // Rollback sequence
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay);
+
+ // Cleanup after statement
+ cc.after_statement();
+ BOOST_REQUIRE(cc.replays() == 1);
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+}
+
+//
+// Test a transaction which gets BF aborted before before_statement.
+//
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(
+ transaction_1pc_bf_before_before_statement, T,
+ replicating_fixtures, T)
+{
+ wsrep::client_state& cc(T::cc);
+ const wsrep::transaction& tc(T::tc);
+
+ // Start a new transaction with ID 1
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+ cc.after_statement();
+ cc.after_command_before_result();
+ cc.after_command_after_result();
+ BOOST_REQUIRE(cc.before_command() == 0);
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+ wsrep_test::bf_abort_unordered(cc);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort);
+ BOOST_REQUIRE(cc.before_statement() == 1);
+ BOOST_REQUIRE(tc.active());
+ cc.after_command_before_result();
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+ cc.after_command_after_result();
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+}
+
+//
+// Test a transaction which gets BF aborted before after_statement.
+//
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(
+ transaction_1pc_bf_before_after_statement, T,
+ replicating_fixtures, T)
+{
+ wsrep::client_state& cc(T::cc);
+ const wsrep::transaction& tc(T::tc);
+
+ // Start a new transaction with ID 1
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+
+ wsrep_test::bf_abort_unordered(cc);
+
+ cc.after_statement();
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.current_error());
+}
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(
+ transaction_1pc_bf_abort_after_after_statement, T,
+ replicating_fixtures, T)
+{
+ wsrep::client_state& cc(T::cc);
+ const wsrep::transaction& tc(T::tc);
+
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ cc.after_statement();
+ BOOST_REQUIRE(cc.state() == wsrep::client_state::s_exec);
+ wsrep_test::bf_abort_unordered(cc);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort);
+ cc.after_command_before_result();
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+ cc.after_command_after_result();
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+}
+
+BOOST_FIXTURE_TEST_CASE(
+ transaction_1pc_autocommit_retry_bf_aborted,
+ replicating_client_fixture_autocommit)
+{
+
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ wsrep_test::bf_abort_unordered(cc);
+ BOOST_REQUIRE(cc.before_commit());
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(cc.after_statement());
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+ BOOST_REQUIRE(cc.state() == wsrep::client_state::s_exec);
+}
+
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(
+ transaction_1pc_bf_abort_after_after_command_before_result, T,
+ replicating_fixtures, T)
+{
+ wsrep::client_state& cc(T::cc);
+ const wsrep::transaction& tc(T::tc);
+
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ cc.after_statement();
+ BOOST_REQUIRE(cc.state() == wsrep::client_state::s_exec);
+ cc.after_command_before_result();
+ BOOST_REQUIRE(cc.state() == wsrep::client_state::s_result);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+ wsrep_test::bf_abort_unordered(cc);
+ // The result is being sent to client. We need to mark transaction
+ // as must_abort but not override error yet as this might cause
+ // a race condition resulting incorrect result returned to the DBMS client.
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+ // After the result has been sent to the DBMS client, the after result
+ // processing should roll back the transaction and set the error.
+ cc.after_command_after_result();
+ BOOST_REQUIRE(cc.state() == wsrep::client_state::s_idle);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+ BOOST_REQUIRE(tc.active() == true);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+ cc.sync_rollback_complete();
+ BOOST_REQUIRE(cc.before_command() == 1);
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+ cc.after_command_before_result();
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+ cc.after_command_after_result();
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+}
+
+BOOST_FIXTURE_TEST_CASE(
+ transaction_1pc_bf_abort_after_after_command_after_result_sync_rm,
+ replicating_client_fixture_sync_rm)
+{
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ cc.after_statement();
+ BOOST_REQUIRE(cc.state() == wsrep::client_state::s_exec);
+ cc.after_command_before_result();
+ BOOST_REQUIRE(cc.state() == wsrep::client_state::s_result);
+ cc.after_command_after_result();
+ BOOST_REQUIRE(cc.state() == wsrep::client_state::s_idle);
+ wsrep_test::bf_abort_unordered(cc);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+ BOOST_REQUIRE(tc.active());
+ cc.sync_rollback_complete();
+ BOOST_REQUIRE(cc.before_command() == 1);
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+ cc.after_command_before_result();
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+ cc.after_command_after_result();
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+ BOOST_REQUIRE(tc.active() == false);
+}
+
+// Check the case where client program calls wait_rollback_complete() to
+// gain control before before_command().
+BOOST_FIXTURE_TEST_CASE(
+ transaction_1pc_bf_abort_after_after_command_after_result_sync_rm_wait_rollback,
+ replicating_client_fixture_sync_rm)
+{
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ cc.after_statement();
+ BOOST_REQUIRE(cc.state() == wsrep::client_state::s_exec);
+ cc.after_command_before_result();
+ BOOST_REQUIRE(cc.state() == wsrep::client_state::s_result);
+ cc.after_command_after_result();
+ BOOST_REQUIRE(cc.state() == wsrep::client_state::s_idle);
+ wsrep_test::bf_abort_unordered(cc);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+ BOOST_REQUIRE(tc.active());
+ cc.sync_rollback_complete();
+ BOOST_REQUIRE(cc.state() == wsrep::client_state::s_idle);
+ cc.wait_rollback_complete_and_acquire_ownership();
+ BOOST_REQUIRE(cc.state() == wsrep::client_state::s_exec);
+ // Idempotent
+ cc.wait_rollback_complete_and_acquire_ownership();
+ BOOST_REQUIRE(cc.state() == wsrep::client_state::s_exec);
+ BOOST_REQUIRE(cc.before_command() == 1);
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+ cc.after_command_before_result();
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+ cc.after_command_after_result();
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+ BOOST_REQUIRE(tc.active() == false);
+}
+
+// Check the case where BF abort happens between client calls to
+// wait_rollback_complete_and_acquire_ownership()
+// and before before_command().
+BOOST_FIXTURE_TEST_CASE(
+ transaction_1pc_bf_abort_after_acquire_before_before_command_sync_rm,
+ replicating_client_fixture_sync_rm)
+{
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ cc.after_statement();
+ BOOST_REQUIRE(cc.state() == wsrep::client_state::s_exec);
+ cc.after_command_before_result();
+ BOOST_REQUIRE(cc.state() == wsrep::client_state::s_result);
+ cc.after_command_after_result();
+ BOOST_REQUIRE(cc.state() == wsrep::client_state::s_idle);
+ cc.wait_rollback_complete_and_acquire_ownership();
+ BOOST_REQUIRE(cc.state() == wsrep::client_state::s_exec);
+ // As the control is now on client, the BF abort must just change
+ // the state to s_must_abort.
+ wsrep_test::bf_abort_unordered(cc);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort);
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(cc.before_command() == 1);
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+ cc.after_command_before_result();
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+ cc.after_command_after_result();
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+ BOOST_REQUIRE(tc.active() == false);
+}
+
+BOOST_FIXTURE_TEST_CASE(
+ transaction_1pc_bf_abort_after_after_command_after_result_async_rm,
+ replicating_client_fixture_async_rm)
+{
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ cc.after_statement();
+ BOOST_REQUIRE(cc.state() == wsrep::client_state::s_exec);
+ cc.after_command_before_result();
+ BOOST_REQUIRE(cc.state() == wsrep::client_state::s_result);
+ cc.after_command_after_result();
+ BOOST_REQUIRE(cc.state() == wsrep::client_state::s_idle);
+ wsrep_test::bf_abort_unordered(cc);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort);
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(cc.before_command() == 1);
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+ cc.after_command_before_result();
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+ cc.after_command_after_result();
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+ BOOST_REQUIRE(tc.active() == false);
+}
+
+//
+// Test before_command() with keep_command_error param
+// Failure free case is not affected by keep_command_error
+//
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(transaction_keep_error, T,
+ replicating_fixtures, T)
+{
+ wsrep::mock_client& cc(T::cc);
+ const wsrep::transaction& tc(T::tc);
+
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ cc.after_statement();
+ cc.after_command_before_result();
+ cc.after_command_after_result();
+
+ bool keep_command_error(true);
+ BOOST_REQUIRE(cc.before_command(keep_command_error) == 0);
+ BOOST_REQUIRE(tc.active());
+ cc.after_command_before_result();
+ cc.after_command_after_result();
+
+ keep_command_error = false;
+ BOOST_REQUIRE(cc.before_command(keep_command_error) == 0);
+ BOOST_REQUIRE(cc.before_statement() == 0);
+ BOOST_REQUIRE(cc.before_commit() == 0);
+ BOOST_REQUIRE(cc.ordered_commit() == 0);
+ BOOST_REQUIRE(cc.after_commit() == 0);
+ cc.after_statement();
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+}
+
+//
+// Test before_command() with keep_command_error param
+// BF abort while idle
+//
+BOOST_FIXTURE_TEST_CASE(transaction_keep_error_bf_idle_sync_rm,
+ replicating_client_fixture_sync_rm)
+{
+ cc.start_transaction(wsrep::transaction_id(1));
+ cc.after_statement();
+ cc.after_command_before_result();
+ cc.after_command_after_result();
+
+ BOOST_REQUIRE(cc.state() == wsrep::client_state::s_idle);
+ wsrep_test::bf_abort_unordered(cc);
+ cc.sync_rollback_complete();
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+
+ bool keep_command_error(true);
+ BOOST_REQUIRE(cc.before_command(keep_command_error) == 0);
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+ cc.after_command_before_result();
+ cc.after_command_after_result();
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+
+ keep_command_error = false;
+ BOOST_REQUIRE(cc.before_command(keep_command_error) == 1);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+ cc.after_command_before_result();
+ cc.after_command_after_result();
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+}
+
+//
+// Test before_command() with keep_command_error param
+// BF abort after ownership is acquired and before before_command()
+//
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(transaction_keep_error_bf_after_ownership, T,
+ replicating_fixtures, T)
+{
+ wsrep::mock_client& cc(T::cc);
+ const wsrep::transaction& tc(T::tc);
+
+ cc.start_transaction(wsrep::transaction_id(1));
+ cc.after_statement();
+ cc.after_command_before_result();
+ cc.after_command_after_result();
+
+ cc.wait_rollback_complete_and_acquire_ownership();
+ wsrep_test::bf_abort_unordered(cc);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort);
+
+ bool keep_command_error(true);
+ BOOST_REQUIRE(cc.before_command(keep_command_error) == 0);
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+ cc.after_command_before_result();
+ cc.after_command_after_result();
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+
+ keep_command_error = false;
+ BOOST_REQUIRE(cc.before_command(keep_command_error) == 1);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+ cc.after_command_before_result();
+ cc.after_command_after_result();
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+}
+
+//
+// Test before_command() with keep_command_error param
+// BF abort right after before_command()
+//
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(transaction_keep_error_bf_after_before_command, T,
+ replicating_fixtures, T)
+{
+ wsrep::mock_client& cc(T::cc);
+ const wsrep::transaction& tc(T::tc);
+
+ cc.start_transaction(wsrep::transaction_id(1));
+ cc.after_statement();
+ cc.after_command_before_result();
+ cc.after_command_after_result();
+
+ bool keep_command_error(true);
+ BOOST_REQUIRE(cc.before_command(keep_command_error) == 0);
+ BOOST_REQUIRE(tc.active());
+
+ wsrep_test::bf_abort_unordered(cc);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort);
+ cc.after_command_before_result();
+ cc.after_command_after_result();
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+
+ keep_command_error = false;
+ BOOST_REQUIRE(cc.before_command(keep_command_error) == 1);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+ cc.after_command_before_result();
+ cc.after_command_after_result();
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+}
+
+//
+// Test before_command() with keep_command_error param
+// BF abort right after after_command_before_result()
+//
+BOOST_FIXTURE_TEST_CASE_TEMPLATE(transaction_keep_error_bf_after_after_command_before_result, T,
+ replicating_fixtures, T)
+{
+ wsrep::mock_client& cc(T::cc);
+ const wsrep::transaction& tc(T::tc);
+
+ cc.start_transaction(wsrep::transaction_id(1));
+ cc.after_statement();
+ cc.after_command_before_result();
+ cc.after_command_after_result();
+
+ bool keep_command_error(true);
+ BOOST_REQUIRE(cc.before_command(keep_command_error) == 0);
+ BOOST_REQUIRE(tc.active());
+
+ cc.after_command_before_result();
+
+ wsrep_test::bf_abort_unordered(cc);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort);
+
+ cc.after_command_after_result();
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+
+ keep_command_error = false;
+ BOOST_REQUIRE(cc.before_command(keep_command_error) == 1);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+ cc.after_command_before_result();
+ cc.after_command_after_result();
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+}
+
+BOOST_FIXTURE_TEST_CASE(transaction_1pc_applying,
+ applying_client_fixture)
+{
+ start_transaction(wsrep::transaction_id(1),
+ wsrep::seqno(1));
+ BOOST_REQUIRE(cc.before_commit() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committing);
+ BOOST_REQUIRE(cc.ordered_commit() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_ordered_commit);
+ BOOST_REQUIRE(cc.after_commit() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committed);
+ cc.after_applying();
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committed);
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+}
+
+
+BOOST_FIXTURE_TEST_CASE(transaction_applying_rollback,
+ applying_client_fixture)
+{
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+ cc.after_applying();
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// STREAMING REPLICATION //
+///////////////////////////////////////////////////////////////////////////////
+
+//
+// Test 1PC with row streaming with one row
+//
+BOOST_FIXTURE_TEST_CASE(transaction_row_streaming_1pc_commit,
+ streaming_client_fixture_row)
+{
+ BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0);
+ BOOST_REQUIRE(cc.after_row() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1);
+ BOOST_REQUIRE(cc.before_commit() == 0);
+ BOOST_REQUIRE(cc.ordered_commit() == 0);
+ BOOST_REQUIRE(cc.after_commit() == 0);
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ BOOST_REQUIRE(sc.provider().fragments() == 2);
+ BOOST_REQUIRE(sc.provider().start_fragments() == 1);
+ BOOST_REQUIRE(sc.provider().commit_fragments() == 1);
+}
+
+//
+// Test 1PC with row streaming with one row
+//
+BOOST_FIXTURE_TEST_CASE(transaction_row_batch_streaming_1pc_commit,
+ streaming_client_fixture_row)
+{
+ BOOST_REQUIRE(cc.enable_streaming(
+ wsrep::streaming_context::row, 2) == 0);
+ BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0);
+ BOOST_REQUIRE(cc.after_row() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 0);
+ BOOST_REQUIRE(cc.after_row() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1);
+ BOOST_REQUIRE(cc.before_commit() == 0);
+ BOOST_REQUIRE(cc.ordered_commit() == 0);
+ BOOST_REQUIRE(cc.after_commit() == 0);
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ BOOST_REQUIRE(sc.provider().fragments() == 2);
+ BOOST_REQUIRE(sc.provider().start_fragments() == 1);
+ BOOST_REQUIRE(sc.provider().commit_fragments() == 1);
+}
+
+//
+// Test 1PC row streaming with two separate statements
+//
+BOOST_FIXTURE_TEST_CASE(
+ transaction_row_streaming_1pc_commit_two_statements,
+ streaming_client_fixture_row)
+{
+ BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0);
+ BOOST_REQUIRE(cc.after_row() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1);
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ BOOST_REQUIRE(cc.before_statement() == 0);
+ BOOST_REQUIRE(cc.after_row() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 2);
+ BOOST_REQUIRE(cc.before_commit() == 0);
+ BOOST_REQUIRE(cc.ordered_commit() == 0);
+ BOOST_REQUIRE(cc.after_commit() == 0);
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ BOOST_REQUIRE(sc.provider().fragments() == 3);
+ BOOST_REQUIRE(sc.provider().start_fragments() == 1);
+ BOOST_REQUIRE(sc.provider().commit_fragments() == 1);
+}
+
+//
+// Fragments are removed in before_prepare in running transaction context.
+// In 1pc the before_prepare() is called from before_commit().
+// However, the BF abort may arrive during this removal and the
+// client_service::remove_fragments() may roll back the transaction
+// internally. This will cause the transaction to leave before_prepare()
+// in aborted state.
+//
+BOOST_FIXTURE_TEST_CASE(transaction_streaming_1pc_bf_abort_during_fragment_removal,
+ streaming_client_fixture_row)
+{
+ BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0);
+ BOOST_REQUIRE(cc.after_row() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1);
+ cc.bf_abort_during_fragment_removal_ = true;
+ BOOST_REQUIRE(cc.before_commit());
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+ BOOST_REQUIRE(cc.after_statement());
+ BOOST_REQUIRE(tc.active() == false);
+ wsrep_test::terminate_streaming_applier(sc, sc.id(),
+ wsrep::transaction_id(1));
+}
+
+//
+// Test streaming rollback
+//
+BOOST_FIXTURE_TEST_CASE(transaction_row_streaming_rollback,
+ streaming_client_fixture_row)
+{
+ BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0);
+ BOOST_REQUIRE(cc.after_row() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1);
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ BOOST_REQUIRE(sc.provider().fragments() == 2);
+ BOOST_REQUIRE(sc.provider().start_fragments() == 1);
+ BOOST_REQUIRE(sc.provider().rollback_fragments() == 1);
+
+ wsrep::high_priority_service* hps(
+ sc.find_streaming_applier(
+ sc.id(), wsrep::transaction_id(1)));
+ BOOST_REQUIRE(hps);
+ hps->rollback(wsrep::ws_handle(), wsrep::ws_meta());
+ hps->after_apply();
+ sc.stop_streaming_applier(sc.id(), wsrep::transaction_id(1));
+ server_service.release_high_priority_service(hps);
+}
+
+//
+// Test streaming BF abort in executing state.
+//
+BOOST_FIXTURE_TEST_CASE(transaction_row_streaming_bf_abort_executing,
+ streaming_client_fixture_row)
+{
+ BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0);
+ BOOST_REQUIRE(cc.after_row() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1);
+ wsrep_test::bf_abort_unordered(cc);
+ BOOST_REQUIRE(tc.streaming_context().rolled_back());
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(cc.after_statement());
+ wsrep_test::terminate_streaming_applier(sc, sc.id(),
+ wsrep::transaction_id(1));
+
+}
+//
+// Test streaming certification failure during fragment replication
+//
+BOOST_FIXTURE_TEST_CASE(transaction_row_streaming_cert_fail_non_commit,
+ streaming_client_fixture_row)
+{
+ BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0);
+ BOOST_REQUIRE(cc.after_row() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1);
+ sc.provider().certify_result_ = wsrep::provider::error_certification_failed;
+ BOOST_REQUIRE(cc.after_row() == 1);
+ sc.provider().certify_result_ = wsrep::provider::success;
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(cc.after_statement() == 1);
+ BOOST_REQUIRE(sc.provider().fragments() == 2);
+ BOOST_REQUIRE(sc.provider().start_fragments() == 1);
+ BOOST_REQUIRE(sc.provider().rollback_fragments() == 1);
+
+ wsrep::high_priority_service* hps(
+ sc.find_streaming_applier(
+ sc.id(), wsrep::transaction_id(1)));
+ BOOST_REQUIRE(hps);
+ hps->rollback(wsrep::ws_handle(), wsrep::ws_meta());
+ hps->after_apply();
+ sc.stop_streaming_applier(sc.id(), wsrep::transaction_id(1));
+ server_service.release_high_priority_service(hps);
+}
+
+//
+// Test streaming certification failure during commit
+//
+BOOST_FIXTURE_TEST_CASE(transaction_row_streaming_cert_fail_commit,
+ streaming_client_fixture_row)
+{
+ BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0);
+ BOOST_REQUIRE(cc.after_row() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1);
+ sc.provider().certify_result_ = wsrep::provider::error_certification_failed;
+ BOOST_REQUIRE(cc.before_commit() == 1);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_cert_failed);
+ sc.provider().certify_result_ = wsrep::provider::success;
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(cc.after_statement() );
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+ BOOST_REQUIRE(sc.provider().fragments() == 2);
+ BOOST_REQUIRE(sc.provider().start_fragments() == 1);
+ BOOST_REQUIRE(sc.provider().rollback_fragments() == 1);
+
+ wsrep::high_priority_service* hps(
+ sc.find_streaming_applier(
+ sc.id(), wsrep::transaction_id(1)));
+ BOOST_REQUIRE(hps);
+ hps->rollback(wsrep::ws_handle(), wsrep::ws_meta());
+ hps->after_apply();
+ sc.stop_streaming_applier(sc.id(), wsrep::transaction_id(1));
+ server_service.release_high_priority_service(hps);
+}
+
+//
+// Test streaming BF abort after succesful certification
+//
+BOOST_FIXTURE_TEST_CASE(transaction_row_streaming_bf_abort_committing,
+ streaming_client_fixture_row)
+{
+ BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0);
+ BOOST_REQUIRE(cc.after_row() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1);
+ BOOST_REQUIRE(cc.before_commit() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committing);
+ wsrep_test::bf_abort_ordered(cc);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort);
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay);
+ BOOST_REQUIRE(cc.will_replay_called() == true);
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committed);
+ BOOST_REQUIRE(sc.provider().fragments() == 2);
+ BOOST_REQUIRE(sc.provider().start_fragments() == 1);
+ BOOST_REQUIRE(sc.provider().commit_fragments() == 1);
+}
+
+
+
+BOOST_FIXTURE_TEST_CASE(transaction_byte_streaming_1pc_commit,
+ streaming_client_fixture_byte)
+{
+ BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0);
+ cc.bytes_generated_ = 1;
+ BOOST_REQUIRE(cc.after_row() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1);
+ BOOST_REQUIRE(cc.before_commit() == 0);
+ BOOST_REQUIRE(cc.ordered_commit() == 0);
+ BOOST_REQUIRE(cc.after_commit() == 0);
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ BOOST_REQUIRE(sc.provider().fragments() == 2);
+ BOOST_REQUIRE(sc.provider().start_fragments() == 1);
+ BOOST_REQUIRE(sc.provider().commit_fragments() == 1);
+}
+
+BOOST_FIXTURE_TEST_CASE(transaction_byte_batch_streaming_1pc_commit,
+ streaming_client_fixture_byte)
+{
+ BOOST_REQUIRE(
+ cc.enable_streaming(
+ wsrep::streaming_context::bytes, 2) == 0);
+ BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0);
+ BOOST_REQUIRE(cc.after_row() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 0);
+ BOOST_REQUIRE(cc.after_row() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1);
+ BOOST_REQUIRE(cc.before_commit() == 0);
+ BOOST_REQUIRE(cc.ordered_commit() == 0);
+ BOOST_REQUIRE(cc.after_commit() == 0);
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ BOOST_REQUIRE(sc.provider().fragments() == 2);
+ BOOST_REQUIRE(sc.provider().start_fragments() == 1);
+ BOOST_REQUIRE(sc.provider().commit_fragments() == 1);
+}
+
+
+BOOST_FIXTURE_TEST_CASE(transaction_statement_streaming_statement_with_no_effect,
+ streaming_client_fixture_statement)
+{
+ BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 0);
+ BOOST_REQUIRE(cc.before_statement() == 0);
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 0);
+ BOOST_REQUIRE(cc.after_row() == 0);
+ BOOST_REQUIRE(cc.before_statement() == 0);
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1);
+ BOOST_REQUIRE(cc.before_statement() == 0);
+ BOOST_REQUIRE(cc.before_commit() == 0);
+ BOOST_REQUIRE(cc.ordered_commit() == 0);
+ BOOST_REQUIRE(cc.after_commit() == 0);
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ BOOST_REQUIRE(sc.provider().fragments() == 2);
+ BOOST_REQUIRE(sc.provider().start_fragments() == 1);
+ BOOST_REQUIRE(sc.provider().commit_fragments() == 1);
+}
+
+BOOST_FIXTURE_TEST_CASE(transaction_statement_streaming_1pc_commit,
+ streaming_client_fixture_statement)
+{
+ BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0);
+ BOOST_REQUIRE(cc.after_row() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 0);
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1);
+ BOOST_REQUIRE(cc.before_statement() == 0);
+ BOOST_REQUIRE(cc.before_commit() == 0);
+ BOOST_REQUIRE(cc.ordered_commit() == 0);
+ BOOST_REQUIRE(cc.after_commit() == 0);
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ BOOST_REQUIRE(sc.provider().fragments() == 2);
+ BOOST_REQUIRE(sc.provider().start_fragments() == 1);
+ BOOST_REQUIRE(sc.provider().commit_fragments() == 1);
+}
+
+BOOST_FIXTURE_TEST_CASE(transaction_statement_batch_streaming_1pc_commit,
+ streaming_client_fixture_statement)
+{
+ BOOST_REQUIRE(
+ cc.enable_streaming(
+ wsrep::streaming_context::statement, 2) == 0);
+ BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0);
+ BOOST_REQUIRE(cc.after_row() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 0);
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 0);
+ BOOST_REQUIRE(cc.before_statement() == 0);
+ BOOST_REQUIRE(cc.after_row() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 0);
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1);
+ BOOST_REQUIRE(cc.before_statement() == 0);
+ BOOST_REQUIRE(cc.before_commit() == 0);
+ BOOST_REQUIRE(cc.ordered_commit() == 0);
+ BOOST_REQUIRE(cc.after_commit() == 0);
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ BOOST_REQUIRE(sc.provider().fragments() == 2);
+ BOOST_REQUIRE(sc.provider().start_fragments() == 1);
+ BOOST_REQUIRE(sc.provider().commit_fragments() == 1);
+}
+
+BOOST_FIXTURE_TEST_CASE(transaction_statement_streaming_cert_fail,
+ streaming_client_fixture_statement)
+{
+ BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0);
+ BOOST_REQUIRE(cc.after_row() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 0);
+ sc.provider().certify_result_ = wsrep::provider::error_certification_failed;
+ BOOST_REQUIRE(cc.after_statement());
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_deadlock_error);
+ // Note: Due to possible limitation in wsrep-API error codes
+ // or a bug in current Galera provider, rollback fragment may be
+ // replicated even in case of certification failure.
+ // If the limitation is lifted later on or the provider is fixed,
+ // the above check should be change for fragments == 0,
+ // rollback_fragments == 0.
+ BOOST_REQUIRE(sc.provider().fragments() == 1);
+ BOOST_REQUIRE(sc.provider().start_fragments() == 0);
+ BOOST_REQUIRE(sc.provider().rollback_fragments() == 1);
+
+ wsrep::high_priority_service* hps(
+ sc.find_streaming_applier(
+ sc.id(), wsrep::transaction_id(1)));
+ BOOST_REQUIRE(hps);
+ hps->rollback(wsrep::ws_handle(), wsrep::ws_meta());
+ hps->after_apply();
+ sc.stop_streaming_applier(sc.id(), wsrep::transaction_id(1));
+ server_service.release_high_priority_service(hps);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// misc //
+///////////////////////////////////////////////////////////////////////////////
+
+BOOST_AUTO_TEST_CASE(transaction_state_strings)
+{
+ BOOST_REQUIRE(wsrep::to_string(
+ wsrep::transaction::s_executing) == "executing");
+ BOOST_REQUIRE(wsrep::to_string(
+ wsrep::transaction::s_preparing) == "preparing");
+ BOOST_REQUIRE(
+ wsrep::to_string(
+ wsrep::transaction::s_certifying) == "certifying");
+ BOOST_REQUIRE(
+ wsrep::to_string(
+ wsrep::transaction::s_committing) == "committing");
+ BOOST_REQUIRE(
+ wsrep::to_string(
+ wsrep::transaction::s_ordered_commit) == "ordered_commit");
+ BOOST_REQUIRE(
+ wsrep::to_string(
+ wsrep::transaction::s_committed) == "committed");
+ BOOST_REQUIRE(
+ wsrep::to_string(
+ wsrep::transaction::s_cert_failed) == "cert_failed");
+ BOOST_REQUIRE(
+ wsrep::to_string(
+ wsrep::transaction::s_must_abort) == "must_abort");
+ BOOST_REQUIRE(
+ wsrep::to_string(
+ wsrep::transaction::s_aborting) == "aborting");
+ BOOST_REQUIRE(
+ wsrep::to_string(
+ wsrep::transaction::s_aborted) == "aborted");
+ BOOST_REQUIRE(
+ wsrep::to_string(
+ wsrep::transaction::s_must_replay) == "must_replay");
+ BOOST_REQUIRE(
+ wsrep::to_string(
+ wsrep::transaction::s_replaying) == "replaying");
+}
diff --git a/wsrep-lib/test/transaction_test_2pc.cpp b/wsrep-lib/test/transaction_test_2pc.cpp
new file mode 100644
index 00000000..8f95828d
--- /dev/null
+++ b/wsrep-lib/test/transaction_test_2pc.cpp
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2018-2019 Codership Oy <info@codership.com>
+ *
+ * This file is part of wsrep-lib.
+ *
+ * Wsrep-lib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Wsrep-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "client_state_fixture.hpp"
+
+//
+// Test a succesful 2PC transaction lifecycle
+//
+BOOST_FIXTURE_TEST_CASE(transaction_2pc,
+ replicating_client_fixture_2pc)
+{
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+ BOOST_REQUIRE(cc.before_prepare() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_preparing);
+ BOOST_REQUIRE(tc.ordered());
+ BOOST_REQUIRE(tc.certified());
+ BOOST_REQUIRE(tc.ws_meta().gtid().is_undefined() == false);
+ BOOST_REQUIRE(cc.after_prepare() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committing);
+ BOOST_REQUIRE(cc.before_commit() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committing);
+ BOOST_REQUIRE(cc.ordered_commit() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_ordered_commit);
+ BOOST_REQUIRE(cc.after_commit() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committed);
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+}
+
+//
+// Test a 2PC transaction which gets BF aborted before before_prepare
+//
+BOOST_FIXTURE_TEST_CASE(
+ transaction_2pc_bf_before_before_prepare,
+ replicating_client_fixture_2pc)
+{
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+ wsrep_test::bf_abort_unordered(cc);
+ BOOST_REQUIRE(cc.before_prepare());
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborting);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+ BOOST_REQUIRE(cc.after_statement() );
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.current_error());
+}
+
+//
+// Test a 2PC transaction which gets BF aborted before before_prepare
+//
+BOOST_FIXTURE_TEST_CASE(
+ transaction_2pc_bf_before_after_prepare,
+ replicating_client_fixture_2pc)
+{
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+ BOOST_REQUIRE(cc.before_prepare() == 0);
+ BOOST_REQUIRE(tc.certified() == true);
+ BOOST_REQUIRE(tc.ordered() == true);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_preparing);
+ wsrep_test::bf_abort_ordered(cc);
+ BOOST_REQUIRE(cc.after_prepare());
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay);
+ BOOST_REQUIRE(cc.will_replay_called() == true);
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay);
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+}
+
+//
+// Test a 2PC transaction which gets BF aborted after_prepare() and
+// the rollback takes place before entering before_commit().
+//
+BOOST_FIXTURE_TEST_CASE(
+ transaction_2pc_bf_after_after_prepare,
+ replicating_client_fixture_2pc)
+{
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+ BOOST_REQUIRE(cc.before_prepare() == 0);
+ BOOST_REQUIRE(cc.after_prepare() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committing);
+ wsrep_test::bf_abort_ordered(cc);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort);
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay);
+ BOOST_REQUIRE(cc.will_replay_called() == true);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay);
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+}
+
+//
+// Test a 2PC transaction which gets BF aborted between after_prepare()
+// and before_commit()
+//
+BOOST_FIXTURE_TEST_CASE(
+ transaction_2pc_bf_before_before_commit,
+ replicating_client_fixture_2pc)
+{
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+ BOOST_REQUIRE(cc.before_prepare() == 0);
+ BOOST_REQUIRE(cc.after_prepare() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committing);
+ wsrep_test::bf_abort_ordered(cc);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_abort);
+ BOOST_REQUIRE(cc.before_commit());
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay);
+ BOOST_REQUIRE(cc.will_replay_called() == true);
+ BOOST_REQUIRE(tc.certified() == true);
+ BOOST_REQUIRE(tc.ordered() == true);
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay);
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+}
+
+
+//
+// Test a 2PC transaction which gets BF aborted when trying to grab
+// commit order.
+//
+BOOST_FIXTURE_TEST_CASE(
+ transaction_2pc_bf_during_commit_order_enter,
+ replicating_client_fixture_2pc)
+{
+ cc.start_transaction(wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+ BOOST_REQUIRE(cc.before_prepare() == 0);
+ BOOST_REQUIRE(cc.after_prepare() == 0);
+ sc.provider().commit_order_enter_result_ = wsrep::provider::error_bf_abort;
+ BOOST_REQUIRE(cc.before_commit());
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay);
+ BOOST_REQUIRE(cc.will_replay_called() == true);
+ BOOST_REQUIRE(tc.certified() == true);
+ BOOST_REQUIRE(tc.ordered() == true);
+ sc.provider().commit_order_enter_result_ = wsrep::provider::success;
+ BOOST_REQUIRE(cc.before_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay);
+ BOOST_REQUIRE(cc.after_rollback() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay);
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// STREAMING REPLICATION //
+///////////////////////////////////////////////////////////////////////////////
+
+
+BOOST_FIXTURE_TEST_CASE(transaction_streaming_2pc_commit,
+ streaming_client_fixture_row)
+{
+ BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0);
+ BOOST_REQUIRE(cc.after_row() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1);
+ BOOST_REQUIRE(cc.before_prepare() == 0);
+ BOOST_REQUIRE(cc.after_prepare() == 0);
+ BOOST_REQUIRE(cc.before_commit() == 0);
+ BOOST_REQUIRE(cc.ordered_commit() == 0);
+ BOOST_REQUIRE(cc.after_commit() == 0);
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ BOOST_REQUIRE(sc.provider().fragments() == 2);
+ BOOST_REQUIRE(sc.provider().start_fragments() == 1);
+ BOOST_REQUIRE(sc.provider().commit_fragments() == 1);
+}
+
+BOOST_FIXTURE_TEST_CASE(transaction_streaming_2pc_commit_two_statements,
+ streaming_client_fixture_row)
+{
+ BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0);
+ BOOST_REQUIRE(cc.after_row() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1);
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ BOOST_REQUIRE(cc.before_statement() == 0);
+ BOOST_REQUIRE(cc.after_row() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 2);
+ BOOST_REQUIRE(cc.before_prepare() == 0);
+ BOOST_REQUIRE(cc.after_prepare() == 0);
+ BOOST_REQUIRE(cc.before_commit() == 0);
+ BOOST_REQUIRE(cc.ordered_commit() == 0);
+ BOOST_REQUIRE(cc.after_commit() == 0);
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ BOOST_REQUIRE(sc.provider().fragments() == 3);
+ BOOST_REQUIRE(sc.provider().start_fragments() == 1);
+ BOOST_REQUIRE(sc.provider().commit_fragments() == 1);
+}
+
+//
+// Fragments are removed in before_prepare in running transaction context.
+// However, the BF abort may arrive during this removal and the
+// client_service::remove_fragments() may roll back the transaction
+// internally. This will cause the transaction to leave before_prepare()
+// in aborted state.
+//
+BOOST_FIXTURE_TEST_CASE(transaction_streaming_2pc_bf_abort_during_fragment_removal,
+ streaming_client_fixture_row)
+{
+ BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0);
+ BOOST_REQUIRE(cc.after_row() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1);
+ cc.bf_abort_during_fragment_removal_ = true;
+ BOOST_REQUIRE(cc.before_prepare());
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+ BOOST_REQUIRE(cc.after_statement());
+ BOOST_REQUIRE(tc.active() == false);
+ wsrep_test::terminate_streaming_applier(sc, sc.id(),
+ wsrep::transaction_id(1));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// APPLYING //
+///////////////////////////////////////////////////////////////////////////////
+
+BOOST_FIXTURE_TEST_CASE(transaction_2pc_applying,
+ applying_client_fixture_2pc)
+{
+ BOOST_REQUIRE(cc.before_prepare() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_preparing);
+ BOOST_REQUIRE(cc.after_prepare() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committing);
+ BOOST_REQUIRE(cc.before_commit() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committing);
+ BOOST_REQUIRE(cc.ordered_commit() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_ordered_commit);
+ BOOST_REQUIRE(cc.after_commit() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committed);
+ cc.after_applying();
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committed);
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+}
diff --git a/wsrep-lib/test/transaction_test_xa.cpp b/wsrep-lib/test/transaction_test_xa.cpp
new file mode 100644
index 00000000..d9fbad27
--- /dev/null
+++ b/wsrep-lib/test/transaction_test_xa.cpp
@@ -0,0 +1,296 @@
+#include "client_state_fixture.hpp"
+#include <iostream>
+
+//
+// Test a successful XA transaction lifecycle
+//
+BOOST_FIXTURE_TEST_CASE(transaction_xa,
+ replicating_client_fixture_sync_rm)
+{
+ wsrep::xid xid(1, 9, 0, "test xid");
+
+ BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0);
+ cc.assign_xid(xid);
+
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.id() == wsrep::transaction_id(1));
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+
+ BOOST_REQUIRE(cc.before_prepare() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_preparing);
+ BOOST_REQUIRE(tc.ordered() == false);
+ // certified() only after the last fragment
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.after_prepare() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_prepared);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1);
+ // XA START + PREPARE fragment
+ BOOST_REQUIRE(sc.provider().start_fragments() == 1);
+ BOOST_REQUIRE(sc.provider().fragments() == 1);
+
+ BOOST_REQUIRE(cc.before_commit() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committing);
+ BOOST_REQUIRE(cc.ordered_commit() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_ordered_commit);
+ BOOST_REQUIRE(tc.ordered());
+ BOOST_REQUIRE(tc.certified());
+ BOOST_REQUIRE(cc.after_commit() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committed);
+ // XA PREPARE and XA COMMIT fragments
+ BOOST_REQUIRE(sc.provider().fragments() == 2);
+ BOOST_REQUIRE(sc.provider().commit_fragments() == 1);
+
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+}
+
+
+//
+// Test detaching of XA transactions
+//
+BOOST_FIXTURE_TEST_CASE(transaction_xa_detach_commit_by_xid,
+ replicating_two_clients_fixture_sync_rm)
+{
+ wsrep::xid xid(1, 1, 1, "id");
+
+ cc1.start_transaction(wsrep::transaction_id(1));
+ cc1.assign_xid(xid);
+ cc1.before_prepare();
+ cc1.after_prepare();
+ BOOST_REQUIRE(sc.provider().fragments() == 1);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1);
+
+ cc1.xa_detach();
+
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+ BOOST_REQUIRE(cc1.after_statement() == 0);
+
+ cc2.start_transaction(wsrep::transaction_id(2));
+ cc2.assign_xid(xid);
+ BOOST_REQUIRE(cc2.client_state::commit_by_xid(xid) == 0);
+ BOOST_REQUIRE(cc2.after_statement() == 0);
+ BOOST_REQUIRE(sc.provider().commit_fragments() == 1);
+
+ // xa_detach() creates a streaming applier, clean it up
+ wsrep::mock_high_priority_service* hps(
+ static_cast<wsrep::mock_high_priority_service*>(
+ sc.find_streaming_applier(xid)));
+ BOOST_REQUIRE(hps);
+ hps->rollback(wsrep::ws_handle(), wsrep::ws_meta());
+ hps->after_apply();
+ sc.stop_streaming_applier(sc.id(), wsrep::transaction_id(1));
+ server_service.release_high_priority_service(hps);
+}
+
+BOOST_FIXTURE_TEST_CASE(transaction_xa_detach_rollback_by_xid,
+ replicating_two_clients_fixture_sync_rm)
+{
+ wsrep::xid xid(1, 1, 1, "id");
+
+ cc1.start_transaction(wsrep::transaction_id(1));
+ cc1.assign_xid(xid);
+ cc1.before_prepare();
+ cc1.after_prepare();
+ BOOST_REQUIRE(sc.provider().fragments() == 1);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1);
+
+ cc1.xa_detach();
+
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_aborted);
+ BOOST_REQUIRE(cc1.after_statement() == 0);
+
+ cc2.start_transaction(wsrep::transaction_id(2));
+ cc2.assign_xid(xid);
+ BOOST_REQUIRE(cc2.rollback_by_xid(xid) == 0);
+ BOOST_REQUIRE(cc2.after_statement() == 0);
+ BOOST_REQUIRE(sc.provider().rollback_fragments() == 1);
+
+ // xa_detach() creates a streaming applier, clean it up
+ wsrep::mock_high_priority_service* hps(
+ static_cast<wsrep::mock_high_priority_service*>(
+ sc.find_streaming_applier(xid)));
+ BOOST_REQUIRE(hps);
+ hps->rollback(wsrep::ws_handle(), wsrep::ws_meta());
+ hps->after_apply();
+ sc.stop_streaming_applier(sc.id(), wsrep::transaction_id(1));
+ server_service.release_high_priority_service(hps);
+}
+
+
+//
+// Test XA replay
+//
+BOOST_FIXTURE_TEST_CASE(transaction_xa_replay,
+ replicating_client_fixture_sync_rm)
+{
+ wsrep::xid xid(1, 1, 1, "id");
+
+ cc.start_transaction(wsrep::transaction_id(1));
+ cc.assign_xid(xid);
+ cc.before_prepare();
+ cc.after_prepare();
+ cc.after_command_before_result();
+ cc.after_command_after_result();
+ BOOST_REQUIRE(cc.state() == wsrep::client_state::s_idle);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_prepared);
+ wsrep_test::bf_abort_unordered(cc);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_must_replay);
+
+ // this is normally done by rollbacker
+ cc.xa_replay();
+ cc.sync_rollback_complete();
+
+ BOOST_REQUIRE(cc.unordered_replays() == 1);
+
+ // xa_replay() creates a streaming applier, clean it up
+ wsrep::mock_high_priority_service* hps(
+ static_cast<wsrep::mock_high_priority_service*>(
+ sc.find_streaming_applier(sc.id(), wsrep::transaction_id(1))));
+ BOOST_REQUIRE(hps);
+ hps->rollback(wsrep::ws_handle(), wsrep::ws_meta());
+ hps->after_apply();
+ sc.stop_streaming_applier(sc.id(), wsrep::transaction_id(1));
+ server_service.release_high_priority_service(hps);
+}
+
+BOOST_FIXTURE_TEST_CASE(transaction_xa_replay_after_command_before_result,
+ replicating_client_fixture_sync_rm)
+{
+ wsrep::xid xid(1, 1, 1, "id");
+
+ cc.start_transaction(wsrep::transaction_id(1));
+ cc.assign_xid(xid);
+ cc.before_prepare();
+ cc.after_prepare();
+ BOOST_REQUIRE(cc.state() == wsrep::client_state::s_exec);
+ wsrep_test::bf_abort_unordered(cc);
+ cc.after_command_before_result();
+ cc.after_command_after_result();
+
+ BOOST_REQUIRE(cc.unordered_replays() == 1);
+
+ // xa_replay() creates a streaming applier, clean it up
+ wsrep::mock_high_priority_service* hps(
+ static_cast<wsrep::mock_high_priority_service*>(
+ sc.find_streaming_applier(sc.id(), wsrep::transaction_id(1))));
+ BOOST_REQUIRE(hps);
+ hps->rollback(wsrep::ws_handle(), wsrep::ws_meta());
+ hps->after_apply();
+ sc.stop_streaming_applier(sc.id(), wsrep::transaction_id(1));
+ server_service.release_high_priority_service(hps);
+}
+
+BOOST_FIXTURE_TEST_CASE(transaction_xa_replay_after_command_after_result,
+ replicating_client_fixture_sync_rm)
+{
+ wsrep::xid xid(1, 1, 1, "id");
+
+ cc.start_transaction(wsrep::transaction_id(1));
+ cc.assign_xid(xid);
+ cc.before_prepare();
+ cc.after_prepare();
+ cc.after_command_before_result();
+ BOOST_REQUIRE(cc.state() == wsrep::client_state::s_result);
+ wsrep_test::bf_abort_unordered(cc);
+
+ cc.after_command_after_result();
+
+ BOOST_REQUIRE(cc.unordered_replays() == 1);
+
+ // xa_replay() creates a a streaming applier, clean it up
+ wsrep::mock_high_priority_service* hps(
+ static_cast<wsrep::mock_high_priority_service*>(
+ sc.find_streaming_applier(sc.id(), wsrep::transaction_id(1))));
+ BOOST_REQUIRE(hps);
+ hps->rollback(wsrep::ws_handle(), wsrep::ws_meta());
+ hps->after_apply();
+ sc.stop_streaming_applier(sc.id(), wsrep::transaction_id(1));
+ server_service.release_high_priority_service(hps);
+}
+
+//
+// Test a successful XA transaction lifecycle (applying side)
+//
+BOOST_FIXTURE_TEST_CASE(transaction_xa_applying,
+ applying_client_fixture)
+{
+ wsrep::xid xid(1, 9, 0, "test xid");
+
+ start_transaction(wsrep::transaction_id(1), wsrep::seqno(1));
+ cc.assign_xid(xid);
+
+ BOOST_REQUIRE(cc.before_prepare() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_preparing);
+ BOOST_REQUIRE(tc.ordered());
+ BOOST_REQUIRE(tc.certified());
+ BOOST_REQUIRE(tc.ws_meta().gtid().is_undefined() == false);
+ BOOST_REQUIRE(cc.after_prepare() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_prepared);
+
+ BOOST_REQUIRE(cc.before_commit() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committing);
+ BOOST_REQUIRE(cc.ordered_commit() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_ordered_commit);
+ BOOST_REQUIRE(cc.after_commit() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committed);
+ cc.after_applying();
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committed);
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// STREAMING REPLICATION //
+///////////////////////////////////////////////////////////////////////////////
+
+//
+// Test a successful XA transaction lifecycle
+//
+BOOST_FIXTURE_TEST_CASE(transaction_xa_sr,
+ streaming_client_fixture_byte)
+{
+ wsrep::xid xid(1, 9, 0, "test xid");
+
+ BOOST_REQUIRE(cc.start_transaction(wsrep::transaction_id(1)) == 0);
+ cc.assign_xid(xid);
+
+ cc.bytes_generated_ = 1;
+ BOOST_REQUIRE(cc.after_row() == 0);
+ BOOST_REQUIRE(tc.streaming_context().fragments_certified() == 1);
+ // XA START fragment with data
+ BOOST_REQUIRE(sc.provider().fragments() == 1);
+ BOOST_REQUIRE(sc.provider().start_fragments() == 1);
+
+ BOOST_REQUIRE(tc.active());
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_executing);
+
+ BOOST_REQUIRE(cc.before_prepare() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_preparing);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.after_prepare() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_prepared);
+ // XA PREPARE fragment
+ BOOST_REQUIRE(sc.provider().fragments() == 2);
+
+ BOOST_REQUIRE(cc.before_commit() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committing);
+ BOOST_REQUIRE(cc.ordered_commit() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_ordered_commit);
+ BOOST_REQUIRE(tc.ordered());
+ BOOST_REQUIRE(tc.certified());
+ BOOST_REQUIRE(cc.after_commit() == 0);
+ BOOST_REQUIRE(tc.state() == wsrep::transaction::s_committed);
+ BOOST_REQUIRE(cc.after_statement() == 0);
+ BOOST_REQUIRE(tc.active() == false);
+ BOOST_REQUIRE(tc.ordered() == false);
+ BOOST_REQUIRE(tc.certified() == false);
+ BOOST_REQUIRE(cc.current_error() == wsrep::e_success);
+ // XA START fragment (with data), XA PREPARE fragment and XA COMMIT fragment
+ BOOST_REQUIRE(sc.provider().fragments() == 3);
+ BOOST_REQUIRE(sc.provider().start_fragments() == 1);
+ BOOST_REQUIRE(sc.provider().commit_fragments() == 1);
+}
diff --git a/wsrep-lib/test/view_test.cpp b/wsrep-lib/test/view_test.cpp
new file mode 100644
index 00000000..1cab1e7c
--- /dev/null
+++ b/wsrep-lib/test/view_test.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2018 Codership Oy <info@codership.com>
+ *
+ * This file is part of wsrep-lib.
+ *
+ * Wsrep-lib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Wsrep-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "wsrep/view.hpp"
+#include <boost/test/unit_test.hpp>
+
+BOOST_AUTO_TEST_CASE(view_test_member_index)
+{
+ std::vector<wsrep::view::member> members;
+ members.push_back(wsrep::view::member(wsrep::id("1"), "", ""));
+ members.push_back(wsrep::view::member(wsrep::id("2"), "", ""));
+ members.push_back(wsrep::view::member(wsrep::id("3"), "", ""));
+
+ wsrep::view view(wsrep::gtid(wsrep::id("cluster"), wsrep::seqno(1)),
+ wsrep::seqno(1),
+ wsrep::view::primary,
+ 0,
+ 1,
+ 0,
+ members);
+ BOOST_REQUIRE(view.member_index(wsrep::id("1")) == 0);
+ BOOST_REQUIRE(view.member_index(wsrep::id("2")) == 1);
+ BOOST_REQUIRE(view.member_index(wsrep::id("3")) == 2);
+ BOOST_REQUIRE(view.member_index(wsrep::id("4")) == -1);
+}
+
+BOOST_AUTO_TEST_CASE(view_test_equal_membership)
+{
+ std::vector<wsrep::view::member> m1;
+ m1.push_back(wsrep::view::member(wsrep::id("1"), "", ""));
+ m1.push_back(wsrep::view::member(wsrep::id("2"), "", ""));
+ m1.push_back(wsrep::view::member(wsrep::id("3"), "", ""));
+
+ std::vector<wsrep::view::member> m2;
+ m2.push_back(wsrep::view::member(wsrep::id("2"), "", ""));
+ m2.push_back(wsrep::view::member(wsrep::id("3"), "", ""));
+ m2.push_back(wsrep::view::member(wsrep::id("1"), "", ""));
+
+ std::vector<wsrep::view::member> m3;
+ m3.push_back(wsrep::view::member(wsrep::id("1"), "", ""));
+ m3.push_back(wsrep::view::member(wsrep::id("2"), "", ""));
+ m3.push_back(wsrep::view::member(wsrep::id("3"), "", ""));
+ m3.push_back(wsrep::view::member(wsrep::id("4"), "", ""));
+
+ wsrep::view v1(wsrep::gtid(wsrep::id("cluster"), wsrep::seqno(1)),
+ wsrep::seqno(1),
+ wsrep::view::primary,
+ 0,
+ 1,
+ 0,
+ m1);
+
+ wsrep::view v2(wsrep::gtid(wsrep::id("cluster"), wsrep::seqno(1)),
+ wsrep::seqno(1),
+ wsrep::view::primary,
+ 0,
+ 1,
+ 0,
+ m2);
+
+ wsrep::view v3(wsrep::gtid(wsrep::id("cluster"), wsrep::seqno(1)),
+ wsrep::seqno(1),
+ wsrep::view::primary,
+ 0,
+ 1,
+ 0,
+ m3);
+
+ BOOST_REQUIRE(v1.equal_membership(v2));
+ BOOST_REQUIRE(v2.equal_membership(v1));
+ BOOST_REQUIRE(!v1.equal_membership(v3));
+ BOOST_REQUIRE(!v3.equal_membership(v1));
+}
+
+BOOST_AUTO_TEST_CASE(view_test_is_member)
+{
+ wsrep::view view(wsrep::gtid(wsrep::id("cluster"), wsrep::seqno(1)),
+ wsrep::seqno(1),
+ wsrep::view::primary,
+ 0,
+ 1,
+ 0,
+ { wsrep::view::member(wsrep::id("1"), "", ""),
+ wsrep::view::member(wsrep::id("2"), "", "") });
+
+ BOOST_REQUIRE(view.is_member(wsrep::id("2")));
+ BOOST_REQUIRE(view.is_member(wsrep::id("1")));
+ BOOST_REQUIRE(not view.is_member(wsrep::id("0")));
+}
diff --git a/wsrep-lib/test/wsrep-lib_test.cpp b/wsrep-lib/test/wsrep-lib_test.cpp
new file mode 100644
index 00000000..4eedaa30
--- /dev/null
+++ b/wsrep-lib/test/wsrep-lib_test.cpp
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2018-2019 Codership Oy <info@codership.com>
+ *
+ * This file is part of wsrep-lib.
+ *
+ * Wsrep-lib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Wsrep-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/** @file wsrep-lib_test.cpp
+ *
+ * Run wsrep-lib unit tests.
+ *
+ * Commandline arguments:
+ *
+ * --wsrep-log-file=<file> Write log from wsrep-lib logging facility
+ * into <file>. If <file> is left empty, the
+ * log is written into stdout.
+ * --wsrep-debug-level=<int> Set debug level
+ * See wsrep::log::debug_level for valid values
+ */
+
+#include "wsrep/logger.hpp"
+#include <fstream>
+
+#define BOOST_TEST_ALTERNATIVE_INIT_API
+#include <boost/test/included/unit_test.hpp>
+
+// Log file to write messages logged via wsrep-lib logging facility.
+static std::string log_file_name("wsrep-lib_test.log");
+static std::ofstream log_file;
+// Debug log level for wsrep-lib logging
+static std::string debug_log_level;
+
+
+static void log_fn(wsrep::log::level level,
+ const char* pfx,
+ const char* msg)
+{
+ log_file << wsrep::log::to_c_string(level) << " " << pfx << msg << std::endl;
+}
+
+static bool parse_arg(const std::string& arg)
+{
+ const std::string delim("=");
+ auto delim_pos(arg.find(delim));
+ const auto parm(arg.substr(0, delim_pos));
+ std::string val;
+ if (delim_pos != std::string::npos)
+ {
+ val = arg.substr(delim_pos + 1);
+ }
+
+ if (parm == "--wsrep-log-file")
+ {
+ log_file_name = val;
+ }
+ else if (parm == "--wsrep-debug-level")
+ {
+ debug_log_level = val;
+ }
+ else
+ {
+ std::cerr << "Error: Unknown argument " << arg << std::endl;
+ return false;
+ }
+ return true;
+}
+
+static bool setup_env(int argc, char* argv[])
+{
+ for (int i(1); i < argc; ++i)
+ {
+ if (parse_arg(argv[i]) == false)
+ {
+ return false;
+ }
+ }
+
+ if (log_file_name.size())
+ {
+ log_file.open(log_file_name);
+ if (!log_file)
+ {
+ int err(errno);
+ std::cerr << "Failed to open '" << log_file_name
+ << "': '" << ::strerror(err) << "'" << std::endl;
+ return false;
+ }
+ std::cout << "Writing wsrep-lib log into '" << log_file_name << "'"
+ << std::endl;
+ wsrep::log::logger_fn(log_fn);
+ }
+
+ if (debug_log_level.size())
+ {
+ int level = std::stoi(debug_log_level);
+ std::cout << "Setting debug level '" << level << "'" << std::endl;
+ wsrep::log::debug_log_level(level);
+ }
+
+ return true;
+}
+
+bool init_unit_test()
+{
+ return setup_env(boost::unit_test::framework::master_test_suite().argc,
+ boost::unit_test::framework::master_test_suite().argv);
+}
diff --git a/wsrep-lib/test/xid_test.cpp b/wsrep-lib/test/xid_test.cpp
new file mode 100644
index 00000000..ce2ffab4
--- /dev/null
+++ b/wsrep-lib/test/xid_test.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2020 Codership Oy <info@codership.com>
+ *
+ * This file is part of wsrep-lib.
+ *
+ * Wsrep-lib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Wsrep-lib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "wsrep/xid.hpp"
+#include <boost/test/unit_test.hpp>
+
+BOOST_AUTO_TEST_CASE(xid_test_is_null)
+{
+ wsrep::xid null_xid;
+ BOOST_REQUIRE(null_xid.is_null());
+ wsrep::xid test_xid(1,0,0,nullptr);
+ BOOST_REQUIRE(!test_xid.is_null());
+}
+
+BOOST_AUTO_TEST_CASE(xid_test_equal)
+{
+ wsrep::xid a(1,1,1,"ab");
+ wsrep::xid b(1,1,1,"ab");
+ BOOST_REQUIRE(a == b);
+}
+
+BOOST_AUTO_TEST_CASE(xid_test_null_equal)
+{
+ wsrep::xid a;
+ wsrep::xid b;
+ BOOST_REQUIRE(a == b);
+ BOOST_REQUIRE(a.is_null());
+}
+
+BOOST_AUTO_TEST_CASE(xid_test_not_equal)
+{
+ wsrep::xid a(1,1,0,"a");
+ wsrep::xid b(1,0,1,"a");
+ wsrep::xid c(-1,1,0,"a");
+ wsrep::xid d(1,1,0,"b");
+ BOOST_REQUIRE(!(a == b));
+ BOOST_REQUIRE(!(a == c));
+ BOOST_REQUIRE(!(a == d));
+}
+
+BOOST_AUTO_TEST_CASE(xid_clear)
+{
+ wsrep::xid null_xid;
+ wsrep::xid to_clear(1, 1, 0, "a");
+ to_clear.clear();
+ BOOST_REQUIRE(to_clear.is_null());
+ BOOST_REQUIRE(null_xid == to_clear);
+}
+
+BOOST_AUTO_TEST_CASE(xid_to_string)
+{
+ wsrep::xid null_xid;
+ std::stringstream null_xid_str;
+ null_xid_str << null_xid;
+ BOOST_REQUIRE(null_xid_str.str() == "");
+
+ wsrep::xid test_xid(1,4,0,"test");
+ std::string xid_str(to_string(test_xid));
+ BOOST_REQUIRE(xid_str == "test");
+}
+
+static bool exception_check(const wsrep::runtime_error&)
+{
+ return true;
+}
+
+BOOST_AUTO_TEST_CASE(xid_too_big)
+{
+ std::string s(65,'a');
+ BOOST_REQUIRE_EXCEPTION(wsrep::xid a(1, 65, 0, s.c_str()),
+ wsrep::runtime_error, exception_check);
+ BOOST_REQUIRE_EXCEPTION(wsrep::xid b(1, 0, 65, s.c_str()),
+ wsrep::runtime_error, exception_check);
+}