// repltests.cpp : Unit tests for replication
//
/**
* Copyright (C) 2009-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.
*/
#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault
#include "mongo/platform/basic.h"
#include "mongo/bson/mutable/document.h"
#include "mongo/bson/mutable/mutable_bson_test_utils.h"
#include "mongo/db/catalog/collection.h"
#include "mongo/db/client.h"
#include "mongo/db/db.h"
#include "mongo/db/db_raii.h"
#include "mongo/db/dbdirectclient.h"
#include "mongo/db/json.h"
#include "mongo/db/op_observer_impl.h"
#include "mongo/db/ops/update.h"
#include "mongo/db/repl/master_slave.h"
#include "mongo/db/repl/oplog.h"
#include "mongo/db/repl/repl_client_info.h"
#include "mongo/db/repl/replication_coordinator_global.h"
#include "mongo/db/repl/replication_coordinator_mock.h"
#include "mongo/db/repl/sync_tail.h"
#include "mongo/dbtests/dbtests.h"
#include "mongo/util/log.h"
using namespace mongo::repl;
namespace ReplTests {
using std::unique_ptr;
using std::endl;
using std::string;
using std::stringstream;
using std::vector;
BSONObj f(const char* s) {
return fromjson(s);
}
class Base {
protected:
const ServiceContext::UniqueOperationContext _txnPtr = cc().makeOperationContext();
OperationContext& _opCtx = *_txnPtr;
mutable DBDirectClient _client;
public:
Base() : _client(&_opCtx) {
ReplSettings replSettings;
replSettings.setOplogSizeBytes(10 * 1024 * 1024);
replSettings.setMaster(true);
setGlobalReplicationCoordinator(
new repl::ReplicationCoordinatorMock(_opCtx.getServiceContext(), replSettings));
// Since the Client object persists across tests, even though the global
// ReplicationCoordinator does not, we need to clear the last op associated with the client
// to avoid the invariant in ReplClientInfo::setLastOp that the optime only goes forward.
repl::ReplClientInfo::forClient(_opCtx.getClient()).clearLastOp_forTest();
getGlobalServiceContext()->setOpObserver(stdx::make_unique());
setOplogCollectionName();
createOplog(&_opCtx);
OldClientWriteContext ctx(&_opCtx, ns());
WriteUnitOfWork wuow(&_opCtx);
Collection* c = ctx.db()->getCollection(ns());
if (!c) {
c = ctx.db()->createCollection(&_opCtx, ns());
}
ASSERT(c->getIndexCatalog()->haveIdIndex(&_opCtx));
wuow.commit();
}
~Base() {
try {
deleteAll(ns());
deleteAll(cllNS());
ReplSettings replSettings;
replSettings.setOplogSizeBytes(10 * 1024 * 1024);
setGlobalReplicationCoordinator(
new repl::ReplicationCoordinatorMock(_opCtx.getServiceContext(), replSettings));
} catch (...) {
FAIL("Exception while cleaning up test");
}
}
protected:
static const char* ns() {
return "unittests.repltests";
}
static const char* cllNS() {
return "local.oplog.$main";
}
BSONObj one(const BSONObj& query = BSONObj()) const {
return _client.findOne(ns(), query);
}
void checkOne(const BSONObj& o) const {
check(o, one(o));
}
void checkAll(const BSONObj& o) const {
unique_ptr c = _client.query(ns(), o);
verify(c->more());
while (c->more()) {
check(o, c->next());
}
}
void check(const BSONObj& expected, const BSONObj& got) const {
if (expected.woCompare(got)) {
::mongo::log() << "expected: " << expected.toString() << ", got: " << got.toString()
<< endl;
}
ASSERT_BSONOBJ_EQ(expected, got);
}
BSONObj oneOp() const {
return _client.findOne(cllNS(), BSONObj());
}
int count() const {
ScopedTransaction transaction(&_opCtx, MODE_X);
Lock::GlobalWrite lk(_opCtx.lockState());
OldClientContext ctx(&_opCtx, ns());
Database* db = ctx.db();
Collection* coll = db->getCollection(ns());
if (!coll) {
WriteUnitOfWork wunit(&_opCtx);
coll = db->createCollection(&_opCtx, ns());
wunit.commit();
}
int count = 0;
auto cursor = coll->getCursor(&_opCtx);
while (auto record = cursor->next()) {
++count;
}
return count;
}
int opCount() {
return DBDirectClient(&_opCtx).query(cllNS(), BSONObj())->itcount();
}
void applyAllOperations() {
ScopedTransaction transaction(&_opCtx, MODE_X);
Lock::GlobalWrite lk(_opCtx.lockState());
vector ops;
{
DBDirectClient db(&_opCtx);
auto cursor = db.query(cllNS(), BSONObj());
while (cursor->more()) {
ops.push_back(cursor->nextSafeOwned());
}
}
{
OldClientContext ctx(&_opCtx, ns());
BSONObjBuilder b;
b.append("host", "localhost");
b.appendTimestamp("syncedTo", 0);
ReplSource a(&_opCtx, b.obj());
for (vector::iterator i = ops.begin(); i != ops.end(); ++i) {
if (0) {
mongo::unittest::log() << "op: " << *i << endl;
}
_opCtx.setReplicatedWrites(false);
a.applyOperation(&_opCtx, ctx.db(), *i);
_opCtx.setReplicatedWrites(true);
}
}
}
void printAll(const char* ns) {
ScopedTransaction transaction(&_opCtx, MODE_X);
Lock::GlobalWrite lk(_opCtx.lockState());
OldClientContext ctx(&_opCtx, ns);
Database* db = ctx.db();
Collection* coll = db->getCollection(ns);
if (!coll) {
WriteUnitOfWork wunit(&_opCtx);
coll = db->createCollection(&_opCtx, ns);
wunit.commit();
}
auto cursor = coll->getCursor(&_opCtx);
::mongo::log() << "all for " << ns << endl;
while (auto record = cursor->next()) {
::mongo::log() << record->data.releaseToBson() << endl;
}
}
// These deletes don't get logged.
void deleteAll(const char* ns) const {
ScopedTransaction transaction(&_opCtx, MODE_X);
Lock::GlobalWrite lk(_opCtx.lockState());
OldClientContext ctx(&_opCtx, ns);
WriteUnitOfWork wunit(&_opCtx);
Database* db = ctx.db();
Collection* coll = db->getCollection(ns);
if (!coll) {
coll = db->createCollection(&_opCtx, ns);
}
ASSERT_OK(coll->truncate(&_opCtx));
wunit.commit();
}
void insert(const BSONObj& o) const {
ScopedTransaction transaction(&_opCtx, MODE_X);
Lock::GlobalWrite lk(_opCtx.lockState());
OldClientContext ctx(&_opCtx, ns());
WriteUnitOfWork wunit(&_opCtx);
Database* db = ctx.db();
Collection* coll = db->getCollection(ns());
if (!coll) {
coll = db->createCollection(&_opCtx, ns());
}
OpDebug* const nullOpDebug = nullptr;
if (o.hasField("_id")) {
_opCtx.setReplicatedWrites(false);
coll->insertDocument(&_opCtx, o, nullOpDebug, true);
_opCtx.setReplicatedWrites(true);
wunit.commit();
return;
}
class BSONObjBuilder b;
OID id;
id.init();
b.appendOID("_id", &id);
b.appendElements(o);
_opCtx.setReplicatedWrites(false);
coll->insertDocument(&_opCtx, b.obj(), nullOpDebug, true);
_opCtx.setReplicatedWrites(true);
wunit.commit();
}
static BSONObj wid(const char* json) {
class BSONObjBuilder b;
OID id;
id.init();
b.appendOID("_id", &id);
b.appendElements(fromjson(json));
return b.obj();
}
};
class LogBasic : public Base {
public:
void run() {
ASSERT_EQUALS(2, opCount());
_client.insert(ns(), fromjson("{\"a\":\"b\"}"));
ASSERT_EQUALS(3, opCount());
}
};
namespace Idempotence {
class Base : public ReplTests::Base {
public:
virtual ~Base() {}
void run() {
reset();
doIt();
int nOps = opCount();
check();
applyAllOperations();
check();
ASSERT_EQUALS(nOps, opCount());
reset();
applyAllOperations();
check();
ASSERT_EQUALS(nOps, opCount());
applyAllOperations();
check();
ASSERT_EQUALS(nOps, opCount());
}
protected:
virtual void doIt() const = 0;
virtual void check() const = 0;
virtual void reset() const = 0;
};
class InsertTimestamp : public Base {
public:
void doIt() const {
BSONObjBuilder b;
b.append("a", 1);
b.appendTimestamp("t");
_client.insert(ns(), b.done());
date_ = _client.findOne(ns(), QUERY("a" << 1)).getField("t").date();
}
void check() const {
BSONObj o = _client.findOne(ns(), QUERY("a" << 1));
ASSERT(Date_t{} != o.getField("t").date());
ASSERT_EQUALS(date_, o.getField("t").date());
}
void reset() const {
deleteAll(ns());
}
private:
mutable Date_t date_;
};
class InsertAutoId : public Base {
public:
InsertAutoId() : o_(fromjson("{\"a\":\"b\"}")) {}
void doIt() const {
_client.insert(ns(), o_);
}
void check() const {
ASSERT_EQUALS(1, count());
}
void reset() const {
deleteAll(ns());
}
protected:
BSONObj o_;
};
class InsertWithId : public InsertAutoId {
public:
InsertWithId() {
o_ = fromjson("{\"_id\":ObjectId(\"0f0f0f0f0f0f0f0f0f0f0f0f\"),\"a\":\"b\"}");
}
void check() const {
ASSERT_EQUALS(1, count());
checkOne(o_);
}
};
class InsertTwo : public Base {
public:
InsertTwo() : o_(fromjson("{'_id':1,a:'b'}")), t_(fromjson("{'_id':2,c:'d'}")) {}
void doIt() const {
vector v;
v.push_back(o_);
v.push_back(t_);
_client.insert(ns(), v);
}
void check() const {
ASSERT_EQUALS(2, count());
checkOne(o_);
checkOne(t_);
}
void reset() const {
deleteAll(ns());
}
private:
BSONObj o_;
BSONObj t_;
};
class InsertTwoIdentical : public Base {
public:
InsertTwoIdentical() : o_(fromjson("{\"a\":\"b\"}")) {}
void doIt() const {
_client.insert(ns(), o_);
_client.insert(ns(), o_);
}
void check() const {
ASSERT_EQUALS(2, count());
}
void reset() const {
deleteAll(ns());
}
private:
BSONObj o_;
};
class UpdateTimestamp : public Base {
public:
void doIt() const {
BSONObjBuilder b;
b.append("_id", 1);
b.appendTimestamp("t");
_client.update(ns(), BSON("_id" << 1), b.done());
date_ = _client.findOne(ns(), QUERY("_id" << 1)).getField("t").date();
}
void check() const {
BSONObj o = _client.findOne(ns(), QUERY("_id" << 1));
ASSERT(Date_t{} != o.getField("t").date());
ASSERT_EQUALS(date_, o.getField("t").date());
}
void reset() const {
deleteAll(ns());
insert(BSON("_id" << 1));
}
private:
mutable Date_t date_;
};
class UpdateSameField : public Base {
public:
UpdateSameField()
: q_(fromjson("{a:'b'}")),
o1_(wid("{a:'b'}")),
o2_(wid("{a:'b'}")),
u_(fromjson("{a:'c'}")) {}
void doIt() const {
_client.update(ns(), q_, u_);
}
void check() const {
ASSERT_EQUALS(2, count());
ASSERT(!_client.findOne(ns(), q_).isEmpty());
ASSERT(!_client.findOne(ns(), u_).isEmpty());
}
void reset() const {
deleteAll(ns());
insert(o1_);
insert(o2_);
}
private:
BSONObj q_, o1_, o2_, u_;
};
class UpdateSameFieldWithId : public Base {
public:
UpdateSameFieldWithId()
: o_(fromjson("{'_id':1,a:'b'}")),
q_(fromjson("{a:'b'}")),
u_(fromjson("{'_id':1,a:'c'}")) {}
void doIt() const {
_client.update(ns(), q_, u_);
}
void check() const {
ASSERT_EQUALS(2, count());
ASSERT(!_client.findOne(ns(), q_).isEmpty());
ASSERT(!_client.findOne(ns(), u_).isEmpty());
}
void reset() const {
deleteAll(ns());
insert(o_);
insert(fromjson("{'_id':2,a:'b'}"));
}
private:
BSONObj o_, q_, u_;
};
class UpdateSameFieldExplicitId : public Base {
public:
UpdateSameFieldExplicitId()
: o_(fromjson("{'_id':1,a:'b'}")), u_(fromjson("{'_id':1,a:'c'}")) {}
void doIt() const {
_client.update(ns(), o_, u_);
}
void check() const {
ASSERT_EQUALS(1, count());
checkOne(u_);
}
void reset() const {
deleteAll(ns());
insert(o_);
}
protected:
BSONObj o_, u_;
};
class UpdateDifferentFieldExplicitId : public Base {
public:
UpdateDifferentFieldExplicitId()
: o_(fromjson("{'_id':1,a:'b'}")),
q_(fromjson("{'_id':1}")),
u_(fromjson("{'_id':1,a:'c'}")) {}
void doIt() const {
_client.update(ns(), q_, u_);
}
void check() const {
ASSERT_EQUALS(1, count());
checkOne(u_);
}
void reset() const {
deleteAll(ns());
insert(o_);
}
protected:
BSONObj o_, q_, u_;
};
class UpsertUpdateNoMods : public UpdateDifferentFieldExplicitId {
void doIt() const {
_client.update(ns(), q_, u_, true);
}
};
class UpsertInsertNoMods : public InsertAutoId {
void doIt() const {
_client.update(ns(), fromjson("{a:'c'}"), o_, true);
}
};
class UpdateSet : public Base {
public:
UpdateSet()
: o_(fromjson("{'_id':1,a:5}")),
q_(fromjson("{a:5}")),
u_(fromjson("{$set:{a:7}}")),
ou_(fromjson("{'_id':1,a:7}")) {}
void doIt() const {
_client.update(ns(), q_, u_);
}
void check() const {
ASSERT_EQUALS(1, count());
checkOne(ou_);
}
void reset() const {
deleteAll(ns());
insert(o_);
}
protected:
BSONObj o_, q_, u_, ou_;
};
class UpdateInc : public Base {
public:
UpdateInc()
: o_(fromjson("{'_id':1,a:5}")),
q_(fromjson("{a:5}")),
u_(fromjson("{$inc:{a:3}}")),
ou_(fromjson("{'_id':1,a:8}")) {}
void doIt() const {
_client.update(ns(), q_, u_);
}
void check() const {
ASSERT_EQUALS(1, count());
checkOne(ou_);
}
void reset() const {
deleteAll(ns());
insert(o_);
}
protected:
BSONObj o_, q_, u_, ou_;
};
class UpdateInc2 : public Base {
public:
UpdateInc2()
: o_(fromjson("{'_id':1,a:5}")),
q_(fromjson("{a:5}")),
u_(fromjson("{$inc:{a:3},$set:{x:5}}")),
ou_(fromjson("{'_id':1,a:8,x:5}")) {}
void doIt() const {
_client.update(ns(), q_, u_);
}
void check() const {
ASSERT_EQUALS(1, count());
checkOne(ou_);
}
void reset() const {
deleteAll(ns());
insert(o_);
}
protected:
BSONObj o_, q_, u_, ou_;
};
class IncEmbedded : public Base {
public:
IncEmbedded()
: o_(fromjson("{'_id':1,a:{b:3},b:{b:1}}")),
q_(fromjson("{'_id':1}")),
u_(fromjson("{$inc:{'a.b':1,'b.b':1}}")),
ou_(fromjson("{'_id':1,a:{b:4},b:{b:2}}")) {}
void doIt() const {
_client.update(ns(), q_, u_);
}
void check() const {
ASSERT_EQUALS(1, count());
checkOne(ou_);
}
void reset() const {
deleteAll(ns());
insert(o_);
}
protected:
BSONObj o_, q_, u_, ou_;
};
class IncCreates : public Base {
public:
IncCreates()
: o_(fromjson("{'_id':1}")),
q_(fromjson("{'_id':1}")),
u_(fromjson("{$inc:{'a':1}}")),
ou_(fromjson("{'_id':1,a:1}")) {}
void doIt() const {
_client.update(ns(), q_, u_);
}
void check() const {
ASSERT_EQUALS(1, count());
checkOne(ou_);
}
void reset() const {
deleteAll(ns());
insert(o_);
}
protected:
BSONObj o_, q_, u_, ou_;
};
class UpsertInsertIdMod : public Base {
public:
UpsertInsertIdMod()
: q_(fromjson("{'_id':5,a:4}")),
u_(fromjson("{$inc:{a:3}}")),
ou_(fromjson("{'_id':5,a:7}")) {}
void doIt() const {
_client.update(ns(), q_, u_, true);
}
void check() const {
ASSERT_EQUALS(1, count());
checkOne(ou_);
}
void reset() const {
deleteAll(ns());
}
protected:
BSONObj q_, u_, ou_;
};
class UpsertInsertSet : public Base {
public:
UpsertInsertSet()
: q_(fromjson("{a:5}")), u_(fromjson("{$set:{a:7}}")), ou_(fromjson("{a:7}")) {}
void doIt() const {
_client.update(ns(), q_, u_, true);
}
void check() const {
ASSERT_EQUALS(2, count());
ASSERT(!_client.findOne(ns(), ou_).isEmpty());
}
void reset() const {
deleteAll(ns());
insert(fromjson("{'_id':7,a:7}"));
}
protected:
BSONObj o_, q_, u_, ou_;
};
class UpsertInsertInc : public Base {
public:
UpsertInsertInc()
: q_(fromjson("{a:5}")), u_(fromjson("{$inc:{a:3}}")), ou_(fromjson("{a:8}")) {}
void doIt() const {
_client.update(ns(), q_, u_, true);
}
void check() const {
ASSERT_EQUALS(1, count());
ASSERT(!_client.findOne(ns(), ou_).isEmpty());
}
void reset() const {
deleteAll(ns());
}
protected:
BSONObj o_, q_, u_, ou_;
};
class MultiInc : public Base {
public:
string s() const {
stringstream ss;
unique_ptr cc = _client.query(ns(), Query().sort(BSON("_id" << 1)));
bool first = true;
while (cc->more()) {
if (first)
first = false;
else
ss << ",";
BSONObj o = cc->next();
ss << o["x"].numberInt();
}
return ss.str();
}
void doIt() const {
_client.insert(ns(), BSON("_id" << 1 << "x" << 1));
_client.insert(ns(), BSON("_id" << 2 << "x" << 5));
ASSERT_EQUALS("1,5", s());
_client.update(ns(), BSON("_id" << 1), BSON("$inc" << BSON("x" << 1)));
ASSERT_EQUALS("2,5", s());
_client.update(ns(), BSONObj(), BSON("$inc" << BSON("x" << 1)));
ASSERT_EQUALS("3,5", s());
_client.update(ns(), BSONObj(), BSON("$inc" << BSON("x" << 1)), false, true);
check();
}
void check() const {
ASSERT_EQUALS("4,6", s());
}
void reset() const {
deleteAll(ns());
}
};
class UpdateWithoutPreexistingId : public Base {
public:
UpdateWithoutPreexistingId()
: o_(fromjson("{a:5}")), u_(fromjson("{a:5}")), ot_(fromjson("{b:4}")) {}
void doIt() const {
_client.update(ns(), o_, u_);
}
void check() const {
ASSERT_EQUALS(2, count());
checkOne(u_);
checkOne(ot_);
}
void reset() const {
deleteAll(ns());
insert(ot_);
insert(o_);
}
protected:
BSONObj o_, u_, ot_;
};
class Remove : public Base {
public:
Remove()
: o1_(f("{\"_id\":\"010101010101010101010101\",\"a\":\"b\"}")),
o2_(f("{\"_id\":\"010101010101010101010102\",\"a\":\"b\"}")),
q_(f("{\"a\":\"b\"}")) {}
void doIt() const {
_client.remove(ns(), q_);
}
void check() const {
ASSERT_EQUALS(0, count());
}
void reset() const {
deleteAll(ns());
insert(o1_);
insert(o2_);
}
protected:
BSONObj o1_, o2_, q_;
};
class RemoveOne : public Remove {
void doIt() const {
_client.remove(ns(), q_, true);
}
void check() const {
ASSERT_EQUALS(1, count());
}
};
class FailingUpdate : public Base {
public:
FailingUpdate() : o_(fromjson("{'_id':1,a:'b'}")), u_(fromjson("{'_id':1,c:'d'}")) {}
void doIt() const {
_client.update(ns(), o_, u_);
_client.insert(ns(), o_);
}
void check() const {
ASSERT_EQUALS(1, count());
checkOne(o_);
}
void reset() const {
deleteAll(ns());
}
protected:
BSONObj o_, u_;
};
class SetNumToStr : public Base {
public:
void doIt() const {
_client.update(ns(),
BSON("_id" << 0),
BSON("$set" << BSON("a"
<< "bcd")));
}
void check() const {
ASSERT_EQUALS(1, count());
checkOne(BSON("_id" << 0 << "a"
<< "bcd"));
}
void reset() const {
deleteAll(ns());
insert(BSON("_id" << 0 << "a" << 4.0));
}
};
class Push : public Base {
public:
void doIt() const {
_client.update(ns(), BSON("_id" << 0), BSON("$push" << BSON("a" << 5.0)));
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
check(fromjson("{'_id':0,a:[4,5]}"), one(fromjson("{'_id':0}")));
}
void reset() const {
deleteAll(ns());
insert(fromjson("{'_id':0,a:[4]}"));
}
};
class PushUpsert : public Base {
public:
void doIt() const {
_client.update(ns(), BSON("_id" << 0), BSON("$push" << BSON("a" << 5.0)), true);
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
check(fromjson("{'_id':0,a:[4,5]}"), one(fromjson("{'_id':0}")));
}
void reset() const {
deleteAll(ns());
insert(fromjson("{'_id':0,a:[4]}"));
}
};
class MultiPush : public Base {
public:
void doIt() const {
_client.update(ns(),
BSON("_id" << 0),
BSON("$push" << BSON("a" << 5.0) << "$push" << BSON("b.c" << 6.0)));
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
check(fromjson("{'_id':0,a:[4,5],b:{c:[6]}}"), one(fromjson("{'_id':0}")));
}
void reset() const {
deleteAll(ns());
insert(fromjson("{'_id':0,a:[4]}"));
}
};
class EmptyPush : public Base {
public:
void doIt() const {
_client.update(ns(), BSON("_id" << 0), BSON("$push" << BSON("a" << 5.0)));
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
check(fromjson("{'_id':0,a:[5]}"), one(fromjson("{'_id':0}")));
}
void reset() const {
deleteAll(ns());
insert(fromjson("{'_id':0}"));
}
};
class EmptyPushSparseIndex : public EmptyPush {
public:
EmptyPushSparseIndex() {
_client.insert("unittests.system.indexes",
BSON("ns" << ns() << "key" << BSON("a" << 1) << "name"
<< "foo"
<< "sparse"
<< true));
}
~EmptyPushSparseIndex() {
_client.dropIndexes(ns());
}
};
class PushAll : public Base {
public:
void doIt() const {
_client.update(ns(), BSON("_id" << 0), fromjson("{$pushAll:{a:[5.0,6.0]}}"));
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
check(fromjson("{'_id':0,a:[4,5,6]}"), one(fromjson("{'_id':0}")));
}
void reset() const {
deleteAll(ns());
insert(fromjson("{'_id':0,a:[4]}"));
}
};
class PushWithDollarSigns : public Base {
void doIt() const {
_client.update(ns(), BSON("_id" << 0), BSON("$push" << BSON("a" << BSON("$foo" << 1))));
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
check(fromjson("{'_id':0, a:[0, {'$foo':1}]}"), one(fromjson("{'_id':0}")));
}
void reset() const {
deleteAll(ns());
insert(BSON("_id" << 0 << "a" << BSON_ARRAY(0)));
}
};
class PushSlice : public Base {
void doIt() const {
_client.update(
ns(),
BSON("_id" << 0),
BSON("$push" << BSON("a" << BSON("$each" << BSON_ARRAY(3) << "$slice" << -2))));
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
check(fromjson("{'_id':0, a:[2,3]}"), one(fromjson("{'_id':0}")));
}
void reset() const {
deleteAll(ns());
insert(BSON("_id" << 0 << "a" << BSON_ARRAY(1 << 2)));
}
};
class PushSliceInitiallyInexistent : public Base {
void doIt() const {
_client.update(
ns(),
BSON("_id" << 0),
BSON("$push" << BSON("a" << BSON("$each" << BSON_ARRAY(1 << 2) << "$slice" << -2))));
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
check(fromjson("{'_id':0, a:[1,2] }"), one(fromjson("{'_id':0}")));
}
void reset() const {
deleteAll(ns());
insert(BSON("_id" << 0));
}
};
class PushSliceToZero : public Base {
void doIt() const {
_client.update(
ns(),
BSON("_id" << 0),
BSON("$push" << BSON("a" << BSON("$each" << BSON_ARRAY(3) << "$slice" << 0))));
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
check(fromjson("{'_id':0, a:[]}"), one(fromjson("{'_id':0}")));
}
void reset() const {
deleteAll(ns());
insert(BSON("_id" << 0));
}
};
class PushAllUpsert : public Base {
public:
void doIt() const {
_client.update(ns(), BSON("_id" << 0), fromjson("{$pushAll:{a:[5.0,6.0]}}"), true);
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
check(fromjson("{'_id':0,a:[4,5,6]}"), one(fromjson("{'_id':0}")));
}
void reset() const {
deleteAll(ns());
insert(fromjson("{'_id':0,a:[4]}"));
}
};
class EmptyPushAll : public Base {
public:
void doIt() const {
_client.update(ns(), BSON("_id" << 0), fromjson("{$pushAll:{a:[5.0,6.0]}}"));
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
check(fromjson("{'_id':0,a:[5,6]}"), one(fromjson("{'_id':0}")));
}
void reset() const {
deleteAll(ns());
insert(fromjson("{'_id':0}"));
}
};
class Pull : public Base {
public:
void doIt() const {
_client.update(ns(), BSON("_id" << 0), BSON("$pull" << BSON("a" << 4.0)));
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
check(fromjson("{'_id':0,a:[5]}"), one(fromjson("{'_id':0}")));
}
void reset() const {
deleteAll(ns());
insert(fromjson("{'_id':0,a:[4,5]}"));
}
};
class PullNothing : public Base {
public:
void doIt() const {
_client.update(ns(), BSON("_id" << 0), BSON("$pull" << BSON("a" << 6.0)));
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
check(fromjson("{'_id':0,a:[4,5]}"), one(fromjson("{'_id':0}")));
}
void reset() const {
deleteAll(ns());
insert(fromjson("{'_id':0,a:[4,5]}"));
}
};
class PullAll : public Base {
public:
void doIt() const {
_client.update(ns(), BSON("_id" << 0), fromjson("{$pullAll:{a:[4,5]}}"));
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
check(fromjson("{'_id':0,a:[6]}"), one(fromjson("{'_id':0}")));
}
void reset() const {
deleteAll(ns());
insert(fromjson("{'_id':0,a:[4,5,6]}"));
}
};
class Pop : public Base {
public:
void doIt() const {
_client.update(ns(), BSON("_id" << 0), fromjson("{$pop:{a:1}}"));
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
check(fromjson("{'_id':0,a:[4,5]}"), one(fromjson("{'_id':0}")));
}
void reset() const {
deleteAll(ns());
insert(fromjson("{'_id':0,a:[4,5,6]}"));
}
};
class PopReverse : public Base {
public:
void doIt() const {
_client.update(ns(), BSON("_id" << 0), fromjson("{$pop:{a:-1}}"));
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
check(fromjson("{'_id':0,a:[5,6]}"), one(fromjson("{'_id':0}")));
}
void reset() const {
deleteAll(ns());
insert(fromjson("{'_id':0,a:[4,5,6]}"));
}
};
class BitOp : public Base {
public:
void doIt() const {
_client.update(ns(), BSON("_id" << 0), fromjson("{$bit:{a:{and:2,or:8}}}"));
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
check(BSON("_id" << 0 << "a" << ((3 & 2) | 8)), one(fromjson("{'_id':0}")));
}
void reset() const {
deleteAll(ns());
insert(fromjson("{'_id':0,a:3}"));
}
};
class Rename : public Base {
public:
void doIt() const {
_client.update(ns(), BSON("_id" << 0), fromjson("{$rename:{a:'b'}}"));
_client.update(ns(), BSON("_id" << 0), fromjson("{$set:{a:50}}"));
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
ASSERT_EQUALS(mutablebson::unordered(BSON("_id" << 0 << "a" << 50 << "b" << 3)),
mutablebson::unordered(one(fromjson("{'_id':0}"))));
}
void reset() const {
deleteAll(ns());
insert(fromjson("{'_id':0,a:3}"));
}
};
class RenameReplace : public Base {
public:
void doIt() const {
_client.update(ns(), BSON("_id" << 0), fromjson("{$rename:{a:'b'}}"));
_client.update(ns(), BSON("_id" << 0), fromjson("{$set:{a:50}}"));
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
ASSERT_EQUALS(mutablebson::unordered(BSON("_id" << 0 << "a" << 50 << "b" << 3)),
mutablebson::unordered(one(fromjson("{'_id':0}"))));
}
void reset() const {
deleteAll(ns());
insert(fromjson("{'_id':0,a:3,b:100}"));
}
};
class RenameOverwrite : public Base {
public:
void doIt() const {
_client.update(ns(), BSON("_id" << 0), fromjson("{$rename:{a:'b'}}"));
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
ASSERT_EQUALS(mutablebson::unordered(BSON("_id" << 0 << "b" << 3 << "z" << 1)),
mutablebson::unordered(one(fromjson("{'_id':0}"))));
}
void reset() const {
deleteAll(ns());
insert(fromjson("{'_id':0,z:1,a:3}"));
}
};
class NoRename : public Base {
public:
void doIt() const {
_client.update(ns(), BSON("_id" << 0), fromjson("{$rename:{c:'b'},$set:{z:1}}"));
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
check(BSON("_id" << 0 << "a" << 3 << "z" << 1), one(fromjson("{'_id':0}")));
}
void reset() const {
deleteAll(ns());
insert(fromjson("{'_id':0,a:3}"));
}
};
class NestedNoRename : public Base {
public:
void doIt() const {
_client.update(ns(), BSON("_id" << 0), fromjson("{$rename:{'a.b':'c.d'},$set:{z:1}}"));
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
check(BSON("_id" << 0 << "z" << 1), one(fromjson("{'_id':0}")));
}
void reset() const {
deleteAll(ns());
insert(fromjson("{'_id':0}"));
}
};
class SingletonNoRename : public Base {
public:
void doIt() const {
_client.update(ns(), BSONObj(), fromjson("{$rename:{a:'b'}}"));
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
check(fromjson("{_id:0,z:1}"), one(fromjson("{'_id':0}")));
}
void reset() const {
deleteAll(ns());
insert(fromjson("{'_id':0,z:1}"));
}
};
class IndexedSingletonNoRename : public Base {
public:
void doIt() const {
_client.update(ns(), BSONObj(), fromjson("{$rename:{a:'b'}}"));
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
check(fromjson("{_id:0,z:1}"), one(fromjson("{'_id':0}")));
}
void reset() const {
deleteAll(ns());
// Add an index on 'a'. This prevents the update from running 'in place'.
ASSERT_OK(dbtests::createIndex(&_opCtx, ns(), BSON("a" << 1)));
insert(fromjson("{'_id':0,z:1}"));
}
};
class AddToSetEmptyMissing : public Base {
public:
void doIt() const {
_client.update(ns(), BSON("_id" << 0), fromjson("{$addToSet:{a:{$each:[]}}}"));
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
check(fromjson("{_id:0,a:[]}"), one(fromjson("{'_id':0}")));
}
void reset() const {
deleteAll(ns());
insert(fromjson("{'_id':0}"));
}
};
class AddToSetWithDollarSigns : public Base {
void doIt() const {
_client.update(ns(), BSON("_id" << 0), BSON("$addToSet" << BSON("a" << BSON("$foo" << 1))));
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
check(fromjson("{'_id':0, a:[0, {'$foo':1}]}"), one(fromjson("{'_id':0}")));
}
void reset() const {
deleteAll(ns());
insert(BSON("_id" << 0 << "a" << BSON_ARRAY(0)));
}
};
//
// replay cases
//
class ReplaySetPreexistingNoOpPull : public Base {
public:
void doIt() const {
_client.update(ns(), BSONObj(), fromjson("{$unset:{z:1}}"));
// This is logged as {$set:{'a.b':[]},$set:{z:1}}, which might not be
// replayable against future versions of a document (here {_id:0,a:1,z:1}) due
// to SERVER-4781. As a result the $set:{z:1} will not be replayed in such
// cases (and also an exception may abort replication). If this were instead
// logged as {$set:{z:1}}, SERVER-4781 would not be triggered.
_client.update(ns(), BSONObj(), fromjson("{$pull:{'a.b':1}, $set:{z:1}}"));
_client.update(ns(), BSONObj(), fromjson("{$set:{a:1}}"));
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
check(fromjson("{_id:0,a:1,z:1}"), one(fromjson("{'_id':0}")));
}
void reset() const {
deleteAll(ns());
insert(fromjson("{'_id':0,a:{b:[]},z:1}"));
}
};
class ReplayArrayFieldNotAppended : public Base {
public:
void doIt() const {
_client.update(ns(), BSONObj(), fromjson("{$push:{'a.0.b':2}}"));
_client.update(ns(), BSONObj(), fromjson("{$set:{'a.0':1}}"));
}
using ReplTests::Base::check;
void check() const {
ASSERT_EQUALS(1, count());
check(fromjson("{_id:0,a:[1,{b:[1]}]}"), one(fromjson("{'_id':0}")));
}
void reset() const {
deleteAll(ns());
insert(fromjson("{'_id':0,a:[{b:[0]},{b:[1]}]}"));
}
};
} // namespace Idempotence
class DeleteOpIsIdBased : public Base {
public:
void run() {
insert(BSON("_id" << 0 << "a" << 10));
insert(BSON("_id" << 1 << "a" << 11));
insert(BSON("_id" << 3 << "a" << 10));
_client.remove(ns(), BSON("a" << 10));
ASSERT_EQUALS(1U, _client.count(ns(), BSONObj()));
insert(BSON("_id" << 0 << "a" << 11));
insert(BSON("_id" << 2 << "a" << 10));
insert(BSON("_id" << 3 << "a" << 10));
applyAllOperations();
ASSERT_EQUALS(2U, _client.count(ns(), BSONObj()));
ASSERT(!one(BSON("_id" << 1)).isEmpty());
ASSERT(!one(BSON("_id" << 2)).isEmpty());
}
};
class DatabaseIgnorerBasic {
public:
void run() {
DatabaseIgnorer d;
ASSERT(!d.ignoreAt("a", Timestamp(4, 0)));
d.doIgnoreUntilAfter("a", Timestamp(5, 0));
ASSERT(d.ignoreAt("a", Timestamp(4, 0)));
ASSERT(!d.ignoreAt("b", Timestamp(4, 0)));
ASSERT(d.ignoreAt("a", Timestamp(4, 10)));
ASSERT(d.ignoreAt("a", Timestamp(5, 0)));
ASSERT(!d.ignoreAt("a", Timestamp(5, 1)));
// Ignore state is expired.
ASSERT(!d.ignoreAt("a", Timestamp(4, 0)));
}
};
class DatabaseIgnorerUpdate {
public:
void run() {
DatabaseIgnorer d;
d.doIgnoreUntilAfter("a", Timestamp(5, 0));
d.doIgnoreUntilAfter("a", Timestamp(6, 0));
ASSERT(d.ignoreAt("a", Timestamp(5, 5)));
ASSERT(d.ignoreAt("a", Timestamp(6, 0)));
ASSERT(!d.ignoreAt("a", Timestamp(6, 1)));
d.doIgnoreUntilAfter("a", Timestamp(5, 0));
d.doIgnoreUntilAfter("a", Timestamp(6, 0));
d.doIgnoreUntilAfter("a", Timestamp(6, 0));
d.doIgnoreUntilAfter("a", Timestamp(5, 0));
ASSERT(d.ignoreAt("a", Timestamp(5, 5)));
ASSERT(d.ignoreAt("a", Timestamp(6, 0)));
ASSERT(!d.ignoreAt("a", Timestamp(6, 1)));
}
};
class SyncTest : public SyncTail {
public:
bool returnEmpty;
SyncTest() : SyncTail(nullptr, SyncTail::MultiSyncApplyFunc()), returnEmpty(false) {}
virtual ~SyncTest() {}
virtual BSONObj getMissingDoc(OperationContext* opCtx, Database* db, const BSONObj& o) {
if (returnEmpty) {
BSONObj o;
return o;
}
return BSON("_id"
<< "on remote"
<< "foo"
<< "baz");
}
};
class ShouldRetry : public Base {
public:
void run() {
bool threw = false;
BSONObj o = BSON("ns" << ns() << "o" << BSON("foo"
<< "bar")
<< "o2"
<< BSON("_id"
<< "in oplog"
<< "foo"
<< "bar"));
ScopedTransaction transaction(&_opCtx, MODE_X);
Lock::GlobalWrite lk(_opCtx.lockState());
// this should fail because we can't connect
try {
SyncTail badSource(nullptr, SyncTail::MultiSyncApplyFunc());
badSource.setHostname("localhost:123");
OldClientContext ctx(&_opCtx, ns());
badSource.getMissingDoc(&_opCtx, ctx.db(), o);
} catch (DBException&) {
threw = true;
}
verify(threw);
// now this should succeed
SyncTest t;
verify(t.shouldRetry(&_opCtx, o));
verify(!_client
.findOne(ns(),
BSON("_id"
<< "on remote"))
.isEmpty());
// force it not to find an obj
t.returnEmpty = true;
verify(!t.shouldRetry(&_opCtx, o));
}
};
class All : public Suite {
public:
All() : Suite("repl") {}
void setupTests() {
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add(); // SERVER-716
add(); // SERVER-717
add();
add();
add();
add();
// Don't worry about this until someone wants this functionality.
// add< Idempotence::UpdateWithoutPreexistingId >();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
}
};
SuiteInstance myall;
} // namespace ReplTests