// record_store_test_harness.cpp
/**
* Copyright (C) 2014 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 .
*
* 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/platform/basic.h"
#include "mongo/db/storage/record_store_test_harness.h"
#include "mongo/db/storage/record_store.h"
#include "mongo/unittest/unittest.h"
namespace mongo {
namespace {
using std::unique_ptr;
using std::string;
TEST(RecordStoreTestHarness, Simple1) {
const auto harnessHelper(newRecordStoreHarnessHelper());
unique_ptr rs(harnessHelper->newNonCappedRecordStore());
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
ASSERT_EQUALS(0, rs->numRecords(opCtx.get()));
}
string s = "eliot was here";
RecordId loc1;
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
{
WriteUnitOfWork uow(opCtx.get());
StatusWith res =
rs->insertRecord(opCtx.get(), s.c_str(), s.size() + 1, false);
ASSERT_OK(res.getStatus());
loc1 = res.getValue();
uow.commit();
}
ASSERT_EQUALS(s, rs->dataFor(opCtx.get(), loc1).data());
}
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
ASSERT_EQUALS(s, rs->dataFor(opCtx.get(), loc1).data());
ASSERT_EQUALS(1, rs->numRecords(opCtx.get()));
RecordData rd;
ASSERT(!rs->findRecord(opCtx.get(), RecordId(111, 17), &rd));
ASSERT(rd.data() == NULL);
ASSERT(rs->findRecord(opCtx.get(), loc1, &rd));
ASSERT_EQUALS(s, rd.data());
}
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
{
WriteUnitOfWork uow(opCtx.get());
StatusWith res =
rs->insertRecord(opCtx.get(), s.c_str(), s.size() + 1, false);
ASSERT_OK(res.getStatus());
uow.commit();
}
}
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
ASSERT_EQUALS(2, rs->numRecords(opCtx.get()));
}
}
namespace {
class DummyDocWriter final : public DocWriter {
public:
virtual ~DummyDocWriter() {}
virtual void writeDocument(char* buf) const {
memcpy(buf, "eliot", 6);
}
virtual size_t documentSize() const {
return 6;
}
virtual bool addPadding() const {
return false;
}
};
}
TEST(RecordStoreTestHarness, Simple1InsertDocWroter) {
const auto harnessHelper(newRecordStoreHarnessHelper());
unique_ptr rs(harnessHelper->newNonCappedRecordStore());
RecordId loc1;
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
{
WriteUnitOfWork uow(opCtx.get());
DummyDocWriter dw;
StatusWith res = rs->insertRecordWithDocWriter(opCtx.get(), &dw);
ASSERT_OK(res.getStatus());
loc1 = res.getValue();
uow.commit();
}
ASSERT_EQUALS(string("eliot"), rs->dataFor(opCtx.get(), loc1).data());
}
}
TEST(RecordStoreTestHarness, Delete1) {
const auto harnessHelper(newRecordStoreHarnessHelper());
unique_ptr rs(harnessHelper->newNonCappedRecordStore());
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
ASSERT_EQUALS(0, rs->numRecords(opCtx.get()));
}
string s = "eliot was here";
RecordId loc;
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
{
WriteUnitOfWork uow(opCtx.get());
StatusWith res =
rs->insertRecord(opCtx.get(), s.c_str(), s.size() + 1, false);
ASSERT_OK(res.getStatus());
loc = res.getValue();
uow.commit();
}
ASSERT_EQUALS(s, rs->dataFor(opCtx.get(), loc).data());
}
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
ASSERT_EQUALS(1, rs->numRecords(opCtx.get()));
}
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
{
WriteUnitOfWork uow(opCtx.get());
rs->deleteRecord(opCtx.get(), loc);
uow.commit();
}
ASSERT_EQUALS(0, rs->numRecords(opCtx.get()));
}
}
TEST(RecordStoreTestHarness, Delete2) {
const auto harnessHelper(newRecordStoreHarnessHelper());
unique_ptr rs(harnessHelper->newNonCappedRecordStore());
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
ASSERT_EQUALS(0, rs->numRecords(opCtx.get()));
}
string s = "eliot was here";
RecordId loc;
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
{
WriteUnitOfWork uow(opCtx.get());
StatusWith res =
rs->insertRecord(opCtx.get(), s.c_str(), s.size() + 1, false);
ASSERT_OK(res.getStatus());
res = rs->insertRecord(opCtx.get(), s.c_str(), s.size() + 1, false);
ASSERT_OK(res.getStatus());
loc = res.getValue();
uow.commit();
}
}
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
ASSERT_EQUALS(s, rs->dataFor(opCtx.get(), loc).data());
ASSERT_EQUALS(2, rs->numRecords(opCtx.get()));
}
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
{
WriteUnitOfWork uow(opCtx.get());
rs->deleteRecord(opCtx.get(), loc);
uow.commit();
}
}
}
TEST(RecordStoreTestHarness, Update1) {
const auto harnessHelper(newRecordStoreHarnessHelper());
unique_ptr rs(harnessHelper->newNonCappedRecordStore());
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
ASSERT_EQUALS(0, rs->numRecords(opCtx.get()));
}
string s1 = "eliot was here";
string s2 = "eliot was here again";
RecordId loc;
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
{
WriteUnitOfWork uow(opCtx.get());
StatusWith res =
rs->insertRecord(opCtx.get(), s1.c_str(), s1.size() + 1, false);
ASSERT_OK(res.getStatus());
loc = res.getValue();
uow.commit();
}
}
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
ASSERT_EQUALS(s1, rs->dataFor(opCtx.get(), loc).data());
}
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
{
WriteUnitOfWork uow(opCtx.get());
Status status =
rs->updateRecord(opCtx.get(), loc, s2.c_str(), s2.size() + 1, false, NULL);
if (ErrorCodes::NeedsDocumentMove == status) {
// NeedsDocumentMove should only be possible under MMAPv1. We don't have the means
// to check storageEngine here but asserting 'supportsDocLocking()' is false
// provides an equivalent check as only MMAPv1 will/should return false.
ASSERT_FALSE(harnessHelper->supportsDocLocking());
StatusWith newLocation =
rs->insertRecord(opCtx.get(), s2.c_str(), s2.size() + 1, false);
ASSERT_OK(newLocation.getStatus());
rs->deleteRecord(opCtx.get(), loc);
loc = newLocation.getValue();
} else {
ASSERT_OK(status);
}
uow.commit();
}
}
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
ASSERT_EQUALS(1, rs->numRecords(opCtx.get()));
ASSERT_EQUALS(s2, rs->dataFor(opCtx.get(), loc).data());
}
}
TEST(RecordStoreTestHarness, UpdateInPlace1) {
const auto harnessHelper(newRecordStoreHarnessHelper());
unique_ptr rs(harnessHelper->newNonCappedRecordStore());
if (!rs->updateWithDamagesSupported())
return;
string s1 = "aaa111bbb";
string s2 = "aaa222bbb";
RecordId loc;
const RecordData s1Rec(s1.c_str(), s1.size() + 1);
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
{
WriteUnitOfWork uow(opCtx.get());
StatusWith res =
rs->insertRecord(opCtx.get(), s1Rec.data(), s1Rec.size(), -1);
ASSERT_OK(res.getStatus());
loc = res.getValue();
uow.commit();
}
}
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
ASSERT_EQUALS(s1, rs->dataFor(opCtx.get(), loc).data());
}
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
{
WriteUnitOfWork uow(opCtx.get());
const char* damageSource = "222";
mutablebson::DamageVector dv;
dv.push_back(mutablebson::DamageEvent());
dv[0].sourceOffset = 0;
dv[0].targetOffset = 3;
dv[0].size = 3;
auto newRecStatus = rs->updateWithDamages(opCtx.get(), loc, s1Rec, damageSource, dv);
ASSERT_OK(newRecStatus.getStatus());
ASSERT_EQUALS(s2, newRecStatus.getValue().data());
uow.commit();
}
}
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
ASSERT_EQUALS(s2, rs->dataFor(opCtx.get(), loc).data());
}
}
TEST(RecordStoreTestHarness, Truncate1) {
const auto harnessHelper(newRecordStoreHarnessHelper());
unique_ptr rs(harnessHelper->newNonCappedRecordStore());
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
ASSERT_EQUALS(0, rs->numRecords(opCtx.get()));
}
string s = "eliot was here";
RecordId loc;
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
{
WriteUnitOfWork uow(opCtx.get());
StatusWith res =
rs->insertRecord(opCtx.get(), s.c_str(), s.size() + 1, false);
ASSERT_OK(res.getStatus());
loc = res.getValue();
uow.commit();
}
}
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
ASSERT_EQUALS(s, rs->dataFor(opCtx.get(), loc).data());
}
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
ASSERT_EQUALS(1, rs->numRecords(opCtx.get()));
}
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
{
WriteUnitOfWork uow(opCtx.get());
rs->truncate(opCtx.get()).transitional_ignore();
uow.commit();
}
}
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
ASSERT_EQUALS(0, rs->numRecords(opCtx.get()));
}
}
TEST(RecordStoreTestHarness, Cursor1) {
const int N = 10;
const auto harnessHelper(newRecordStoreHarnessHelper());
unique_ptr rs(harnessHelper->newNonCappedRecordStore());
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
ASSERT_EQUALS(0, rs->numRecords(opCtx.get()));
}
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
{
WriteUnitOfWork uow(opCtx.get());
for (int i = 0; i < N; i++) {
string s = str::stream() << "eliot" << i;
ASSERT_OK(
rs->insertRecord(opCtx.get(), s.c_str(), s.size() + 1, false).getStatus());
}
uow.commit();
}
}
{
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
ASSERT_EQUALS(N, rs->numRecords(opCtx.get()));
}
{
int x = 0;
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
auto cursor = rs->getCursor(opCtx.get());
while (auto record = cursor->next()) {
string s = str::stream() << "eliot" << x++;
ASSERT_EQUALS(s, record->data.data());
}
ASSERT_EQUALS(N, x);
ASSERT(!cursor->next());
}
{
int x = N;
ServiceContext::UniqueOperationContext opCtx(harnessHelper->newOperationContext());
auto cursor = rs->getCursor(opCtx.get(), false);
while (auto record = cursor->next()) {
string s = str::stream() << "eliot" << --x;
ASSERT_EQUALS(s, record->data.data());
}
ASSERT_EQUALS(0, x);
ASSERT(!cursor->next());
}
}
} // namespace
} // namespace mongo