summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorXiangyu Yao <xiangyu.yao@mongodb.com>2018-03-19 18:01:06 -0400
committerXiangyu Yao <xiangyu.yao@mongodb.com>2018-03-23 17:32:28 -0400
commit3c39645662cca8467de11f32bb2d0032a0c4458e (patch)
tree691b4c4ff2b4293b4aa9d5c49ccab8e30a4dd401 /src
parent877302630e10739e3e3fa0d9c96f45bc437f3660 (diff)
downloadmongo-3c39645662cca8467de11f32bb2d0032a0c4458e.tar.gz
SERVER-33834 Add unit tests for WiredTigerRecoveryUnit
Diffstat (limited to 'src')
-rw-r--r--src/mongo/db/storage/SConscript12
-rw-r--r--src/mongo/db/storage/recovery_unit_test_harness.cpp104
-rw-r--r--src/mongo/db/storage/recovery_unit_test_harness.h47
-rw-r--r--src/mongo/db/storage/wiredtiger/SConscript12
-rw-r--r--src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit_test.cpp232
5 files changed, 407 insertions, 0 deletions
diff --git a/src/mongo/db/storage/SConscript b/src/mongo/db/storage/SConscript
index 1c435160278..95a8a6bd74e 100644
--- a/src/mongo/db/storage/SConscript
+++ b/src/mongo/db/storage/SConscript
@@ -166,6 +166,18 @@ env.Library(
],
)
+
+env.Library(
+ target='recovery_unit_test_harness',
+ source=[
+ 'recovery_unit_test_harness.cpp'
+ ],
+ LIBDEPS=[
+ '$BUILD_DIR/mongo/unittest/unittest',
+ 'test_harness_helper',
+ ],
+ )
+
env.Library(
target='storage_engine_lock_file',
source=[
diff --git a/src/mongo/db/storage/recovery_unit_test_harness.cpp b/src/mongo/db/storage/recovery_unit_test_harness.cpp
new file mode 100644
index 00000000000..776aca750fd
--- /dev/null
+++ b/src/mongo/db/storage/recovery_unit_test_harness.cpp
@@ -0,0 +1,104 @@
+/**
+ * Copyright (C) 2018 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/db/storage/recovery_unit_test_harness.h"
+#include "mongo/db/repl/read_concern_level.h"
+#include "mongo/db/repl/replication_coordinator.h"
+#include "mongo/db/storage/record_store.h"
+#include "mongo/db/storage/recovery_unit.h"
+#include "mongo/unittest/death_test.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+namespace {
+
+class RecoveryUnitTestHarness : public unittest::Test {
+public:
+ void setUp() override {
+ harnessHelper = newRecoveryUnitHarnessHelper();
+ opCtx = harnessHelper->newOperationContext();
+ ru = opCtx->recoveryUnit();
+ }
+
+ std::unique_ptr<RecoveryUnitHarnessHelper> harnessHelper;
+ ServiceContext::UniqueOperationContext opCtx;
+ RecoveryUnit* ru;
+};
+
+
+TEST_F(RecoveryUnitTestHarness, SetReadConcernLevel) {
+ ru->setReadConcernLevelAndReplicationMode(repl::ReadConcernLevel::kAvailableReadConcern,
+ repl::ReplicationCoordinator::modeReplSet);
+ ASSERT_TRUE(repl::ReadConcernLevel::kAvailableReadConcern == ru->getReadConcernLevel());
+}
+
+TEST_F(RecoveryUnitTestHarness, CommitUnitOfWork) {
+ const auto rs = harnessHelper->createRecordStore(opCtx.get(), "table1");
+ ru->beginUnitOfWork(opCtx.get());
+ StatusWith<RecordId> s = rs->insertRecord(opCtx.get(), "data", 4, Timestamp(), false);
+ ASSERT_TRUE(s.isOK());
+ ASSERT_EQUALS(1, rs->numRecords(NULL));
+ ru->commitUnitOfWork();
+ RecordData rd;
+ ASSERT_TRUE(rs->findRecord(opCtx.get(), s.getValue(), &rd));
+}
+
+TEST_F(RecoveryUnitTestHarness, AbortUnitOfWork) {
+ const auto rs = harnessHelper->createRecordStore(opCtx.get(), "table1");
+ ru->beginUnitOfWork(opCtx.get());
+ StatusWith<RecordId> s = rs->insertRecord(opCtx.get(), "data", 4, Timestamp(), false);
+ ASSERT_TRUE(s.isOK());
+ ASSERT_EQUALS(1, rs->numRecords(NULL));
+ ru->abortUnitOfWork();
+ ASSERT_FALSE(rs->findRecord(opCtx.get(), s.getValue(), nullptr));
+}
+
+DEATH_TEST_F(RecoveryUnitTestHarness, CommitMustBeInUnitOfWork, "invariant") {
+ opCtx->recoveryUnit()->commitUnitOfWork();
+}
+
+DEATH_TEST_F(RecoveryUnitTestHarness, AbortMustBeInUnitOfWork, "invariant") {
+ opCtx->recoveryUnit()->abortUnitOfWork();
+}
+
+DEATH_TEST_F(RecoveryUnitTestHarness, PrepareMustBeInUnitOfWork, "invariant") {
+ opCtx->recoveryUnit()->prepareUnitOfWork();
+}
+
+DEATH_TEST_F(RecoveryUnitTestHarness, WaitUntilDurableMustBeOutOfUnitOfWork, "invariant") {
+ opCtx->recoveryUnit()->beginUnitOfWork(opCtx.get());
+ opCtx->recoveryUnit()->waitUntilDurable();
+}
+
+DEATH_TEST_F(RecoveryUnitTestHarness, AbandonSnapshotMustBeOutOfUnitOfWork, "invariant") {
+ opCtx->recoveryUnit()->beginUnitOfWork(opCtx.get());
+ opCtx->recoveryUnit()->abandonSnapshot();
+}
+
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/db/storage/recovery_unit_test_harness.h b/src/mongo/db/storage/recovery_unit_test_harness.h
new file mode 100644
index 00000000000..577f51949cc
--- /dev/null
+++ b/src/mongo/db/storage/recovery_unit_test_harness.h
@@ -0,0 +1,47 @@
+/**
+ * Copyright (C) 2018 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#pragma once
+
+#include "mongo/db/storage/test_harness_helper.h"
+
+namespace mongo {
+
+class RecordStore;
+
+class RecoveryUnitHarnessHelper : public HarnessHelper {
+public:
+ virtual std::unique_ptr<RecoveryUnit> newRecoveryUnit() = 0;
+ virtual std::unique_ptr<RecordStore> createRecordStore(OperationContext* opCtx,
+ const std::string& ns) = 0;
+};
+
+inline std::unique_ptr<RecoveryUnitHarnessHelper> newRecoveryUnitHarnessHelper() {
+ return dynamic_ptr_cast<RecoveryUnitHarnessHelper>(newHarnessHelper());
+}
+} // namespace mongo
diff --git a/src/mongo/db/storage/wiredtiger/SConscript b/src/mongo/db/storage/wiredtiger/SConscript
index 81ac066b7fa..ab82be68684 100644
--- a/src/mongo/db/storage/wiredtiger/SConscript
+++ b/src/mongo/db/storage/wiredtiger/SConscript
@@ -128,6 +128,18 @@ if wiredtiger:
],
)
+ wtEnv.CppUnitTest(
+ target='storage_wiredtiger_recovery_unit_test',
+ source=[
+ 'wiredtiger_recovery_unit_test.cpp',
+ ],
+ LIBDEPS=[
+ 'storage_wiredtiger_mock',
+ '$BUILD_DIR/mongo/db/storage/recovery_unit_test_harness',
+ '$BUILD_DIR/mongo/util/clock_source_mock',
+ ]
+ )
+
wtEnv.Library(
target='additional_wiredtiger_record_store_tests',
source=[
diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit_test.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit_test.cpp
new file mode 100644
index 00000000000..405d893c9c1
--- /dev/null
+++ b/src/mongo/db/storage/wiredtiger/wiredtiger_recovery_unit_test.cpp
@@ -0,0 +1,232 @@
+/**
+ * Copyright (C) 2018 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/db/storage/wiredtiger/wiredtiger_recovery_unit.h"
+#include "mongo/base/checked_cast.h"
+#include "mongo/db/storage/recovery_unit_test_harness.h"
+#include "mongo/db/storage/wiredtiger/wiredtiger_kv_engine.h"
+#include "mongo/db/storage/wiredtiger/wiredtiger_record_store.h"
+#include "mongo/db/storage/wiredtiger/wiredtiger_session_cache.h"
+#include "mongo/db/storage/wiredtiger/wiredtiger_util.h"
+#include "mongo/unittest/temp_dir.h"
+#include "mongo/unittest/unittest.h"
+#include "mongo/util/clock_source_mock.h"
+
+namespace mongo {
+namespace {
+
+class WiredTigerRecoveryUnitHarnessHelper final : public RecoveryUnitHarnessHelper {
+public:
+ WiredTigerRecoveryUnitHarnessHelper()
+ : _dbpath("wt_test"),
+ _engine(kWiredTigerEngineName, // .canonicalName
+ _dbpath.path(), // .path
+ &_cs, // .cs
+ "", // .extraOpenOptions
+ 1, // .cacheSizeGB
+ false, // .durable
+ false, // .ephemeral
+ false, // .repair
+ false // .readOnly
+ ) {}
+
+ ~WiredTigerRecoveryUnitHarnessHelper() {}
+
+ virtual std::unique_ptr<RecoveryUnit> newRecoveryUnit() final {
+ return std::unique_ptr<RecoveryUnit>(_engine.newRecoveryUnit());
+ }
+
+ virtual std::unique_ptr<RecordStore> createRecordStore(OperationContext* opCtx,
+ const std::string& ns) final {
+ std::string uri = "table:" + ns;
+ const bool prefixed = false;
+ StatusWith<std::string> result = WiredTigerRecordStore::generateCreateString(
+ kWiredTigerEngineName, ns, CollectionOptions(), "", prefixed);
+ ASSERT_TRUE(result.isOK());
+ std::string config = result.getValue();
+
+ {
+ WriteUnitOfWork uow(opCtx);
+ WiredTigerRecoveryUnit* ru =
+ checked_cast<WiredTigerRecoveryUnit*>(opCtx->recoveryUnit());
+ WT_SESSION* s = ru->getSession()->getSession();
+ invariantWTOK(s->create(s, uri.c_str(), config.c_str()));
+ uow.commit();
+ }
+
+ WiredTigerRecordStore::Params params;
+ params.ns = ns;
+ params.uri = uri;
+ params.engineName = kWiredTigerEngineName;
+ params.isCapped = false;
+ params.isEphemeral = false;
+ params.cappedMaxSize = -1;
+ params.cappedMaxDocs = -1;
+ params.cappedCallback = nullptr;
+ params.sizeStorer = nullptr;
+
+ auto ret = stdx::make_unique<StandardWiredTigerRecordStore>(&_engine, opCtx, params);
+ ret->postConstructorInit(opCtx);
+ return std::move(ret);
+ }
+
+private:
+ unittest::TempDir _dbpath;
+ ClockSourceMock _cs;
+ WiredTigerKVEngine _engine;
+};
+
+std::unique_ptr<HarnessHelper> makeHarnessHelper() {
+ return stdx::make_unique<WiredTigerRecoveryUnitHarnessHelper>();
+}
+
+MONGO_INITIALIZER(RegisterHarnessFactory)(InitializerContext* const) {
+ mongo::registerHarnessHelperFactory(makeHarnessHelper);
+ return Status::OK();
+}
+
+class WiredTigerRecoveryUnitTestFixture : public unittest::Test {
+public:
+ typedef std::pair<ServiceContext::UniqueClient, ServiceContext::UniqueOperationContext>
+ ClientAndCtx;
+
+ ClientAndCtx makeClientAndOpCtx(RecoveryUnitHarnessHelper* harnessHelper,
+ const std::string& clientName) {
+ auto sc = harnessHelper->serviceContext();
+ auto client = sc->makeClient(clientName);
+ auto opCtx = client->makeOperationContext();
+ opCtx->setRecoveryUnit(harnessHelper->newRecoveryUnit().release(),
+ OperationContext::kNotInUnitOfWork);
+ return std::make_pair(std::move(client), std::move(opCtx));
+ }
+
+ void getCursor(WiredTigerRecoveryUnit* ru, WT_CURSOR** cursor) {
+ WT_SESSION* wt_session = ru->getSession()->getSession();
+ invariantWTOK(wt_session->create(wt_session, wt_uri, wt_config));
+ invariantWTOK(wt_session->open_cursor(wt_session, wt_uri, NULL, NULL, cursor));
+ }
+
+ void setUp() override {
+ harnessHelper = newRecoveryUnitHarnessHelper();
+ clientAndCtx1 = makeClientAndOpCtx(harnessHelper.get(), "writer");
+ clientAndCtx2 = makeClientAndOpCtx(harnessHelper.get(), "reader");
+ ru1 = checked_cast<WiredTigerRecoveryUnit*>(clientAndCtx1.second->recoveryUnit());
+ ru2 = checked_cast<WiredTigerRecoveryUnit*>(clientAndCtx2.second->recoveryUnit());
+ }
+
+ std::unique_ptr<RecoveryUnitHarnessHelper> harnessHelper;
+ ClientAndCtx clientAndCtx1, clientAndCtx2;
+ WiredTigerRecoveryUnit *ru1, *ru2;
+
+private:
+ const char* wt_uri = "table:prepare_transaction";
+ const char* wt_config = "key_format=S,value_format=S";
+};
+
+TEST_F(WiredTigerRecoveryUnitTestFixture,
+ LocalReadOnADocumentBeingPreparedTriggersPrepareConflict) {
+ // Prepare but don't commit a transaction
+ ru1->setReadConcernLevelAndReplicationMode(repl::ReadConcernLevel::kLocalReadConcern,
+ repl::ReplicationCoordinator::modeNone);
+ ru1->beginUnitOfWork(clientAndCtx1.second.get());
+ WT_CURSOR* cursor;
+ getCursor(ru1, &cursor);
+ cursor->set_key(cursor, "key");
+ cursor->set_value(cursor, "value");
+ invariantWTOK(cursor->insert(cursor));
+ ru1->setPrepareTimestamp({1, 1});
+ ru1->prepareUnitOfWork();
+
+ // Transaction with local readConcern triggers WT_PREPARE_CONFLICT
+ ru2->beginUnitOfWork(clientAndCtx2.second.get());
+ ru2->setReadConcernLevelAndReplicationMode(repl::ReadConcernLevel::kLocalReadConcern,
+ repl::ReplicationCoordinator::modeNone);
+ getCursor(ru2, &cursor);
+ cursor->set_key(cursor, "key");
+ int ret = cursor->search(cursor);
+ ASSERT_EQ(WT_PREPARE_CONFLICT, ret);
+
+ ru1->abortUnitOfWork();
+ ru2->abortUnitOfWork();
+}
+
+TEST_F(WiredTigerRecoveryUnitTestFixture,
+ availableReadOnADocumentBeingPreparedDoesNotTriggerPrepareConflict) {
+ // Prepare but don't commit a transaction
+ ru1->setReadConcernLevelAndReplicationMode(repl::ReadConcernLevel::kLocalReadConcern,
+ repl::ReplicationCoordinator::modeNone);
+ ru1->beginUnitOfWork(clientAndCtx1.second.get());
+ WT_CURSOR* cursor;
+ getCursor(ru1, &cursor);
+ cursor->set_key(cursor, "key");
+ cursor->set_value(cursor, "value");
+ invariantWTOK(cursor->insert(cursor));
+ ru1->setPrepareTimestamp({1, 1});
+ ru1->prepareUnitOfWork();
+
+ // Transaction with available readConcern wouldn't trigger
+ // WT_PREPARE_CONFLICT
+ ru2->beginUnitOfWork(clientAndCtx2.second.get());
+ ru2->setReadConcernLevelAndReplicationMode(repl::ReadConcernLevel::kAvailableReadConcern,
+ repl::ReplicationCoordinator::modeNone);
+ getCursor(ru2, &cursor);
+ cursor->set_key(cursor, "key");
+ int ret = cursor->search(cursor);
+ ASSERT_EQ(WT_NOTFOUND, ret);
+
+ ru1->abortUnitOfWork();
+ ru2->abortUnitOfWork();
+}
+
+TEST_F(WiredTigerRecoveryUnitTestFixture, WriteOnADocumentBeingPreparedTriggersWTRollback) {
+ // Prepare but don't commit a transaction
+ ru1->setReadConcernLevelAndReplicationMode(repl::ReadConcernLevel::kLocalReadConcern,
+ repl::ReplicationCoordinator::modeNone);
+ ru1->beginUnitOfWork(clientAndCtx1.second.get());
+ WT_CURSOR* cursor;
+ getCursor(ru1, &cursor);
+ cursor->set_key(cursor, "key");
+ cursor->set_value(cursor, "value");
+ invariantWTOK(cursor->insert(cursor));
+ ru1->setPrepareTimestamp({1, 1});
+ ru1->prepareUnitOfWork();
+
+ // Another transaction with wirte triggers WT_ROLLBACK
+ ru2->beginUnitOfWork(clientAndCtx2.second.get());
+ getCursor(ru2, &cursor);
+ cursor->set_key(cursor, "key");
+ cursor->set_value(cursor, "value2");
+ int ret = cursor->insert(cursor);
+ ASSERT_EQ(WT_ROLLBACK, ret);
+
+ ru1->abortUnitOfWork();
+ ru2->abortUnitOfWork();
+}
+
+} // namespace
+} // namespace mongo