/** * Copyright (C) 2015 10gen 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 #include "mongo/db/catalog/collection.h" #include "mongo/db/catalog/index_catalog.h" #include "mongo/db/catalog/index_create.h" #include "mongo/db/db_raii.h" #include "mongo/db/dbdirectclient.h" #include "mongo/db/dbhelpers.h" #include "mongo/db/service_context_d.h" #include "mongo/db/service_context.h" #include "mongo/db/index/index_descriptor.h" #include "mongo/db/operation_context_impl.h" #include "mongo/dbtests/dbtests.h" namespace ValidateTests { using std::unique_ptr; static const char* const _ns = "unittests.validate_tests"; /** * Test fixture for a write locked test using collection _ns. Includes functionality to * partially construct a new IndexDetails in a manner that supports proper cleanup in * dropCollection(). */ class ValidateBase { public: explicit ValidateBase(bool full) : _ctx(&_txn, _ns), _client(&_txn), _full(full) { _client.createCollection(_ns); } ~ValidateBase() { _client.dropCollection(_ns); getGlobalServiceContext()->unsetKillAllOperations(); } Collection* collection() { return _ctx.getCollection(); } protected: bool checkValid() { ValidateResults results; BSONObjBuilder output; ASSERT_OK(collection()->validate(&_txn, _full, false, &results, &output)); // Check if errors are reported if and only if valid is set to false. ASSERT_EQ(results.valid, results.errors.empty()); return results.valid; } OperationContextImpl _txn; OldClientWriteContext _ctx; DBDirectClient _client; bool _full; }; template class ValidateIdIndexCount : public ValidateBase { public: ValidateIdIndexCount() : ValidateBase(full) {} void run() { // Create a new collection, insert records { _id: 1 } and {_id: 2} and check it's valid. Database* db = _ctx.db(); Collection* coll; RecordId id1; { WriteUnitOfWork wunit(&_txn); ASSERT_OK(db->dropCollection(&_txn, _ns)); coll = db->createCollection(&_txn, _ns); ASSERT_OK(coll->insertDocument(&_txn, BSON("_id" << 1), true)); id1 = coll->getCursor(&_txn)->next()->id; ASSERT_OK(coll->insertDocument(&_txn, BSON("_id" << 2), true)); wunit.commit(); } ASSERT_TRUE(checkValid()); RecordStore* rs = coll->getRecordStore(); // Remove a { _id: 1 } from the record store, so we get more _id entries than records, and // verify validate fails. { WriteUnitOfWork wunit(&_txn); rs->deleteRecord(&_txn, id1); wunit.commit(); } ASSERT_FALSE(checkValid()); // Insert records { _id: 0} and { _id: 1} , so we get too few _id entries, and verify // validate fails. { WriteUnitOfWork wunit(&_txn); for (int j = 0; j < 2; j++) { auto doc = BSON("_id" << j); ASSERT_OK( rs->insertRecord(&_txn, doc.objdata(), doc.objsize(), /*enforceQuota*/ false)); } wunit.commit(); } ASSERT_FALSE(checkValid()); } }; template class ValidateSecondaryIndexCount : public ValidateBase { public: ValidateSecondaryIndexCount() : ValidateBase(full) {} void run() { // Create a new collection, insert two documents. Database* db = _ctx.db(); Collection* coll; RecordId id1; { WriteUnitOfWork wunit(&_txn); ASSERT_OK(db->dropCollection(&_txn, _ns)); coll = db->createCollection(&_txn, _ns); ASSERT_OK(coll->insertDocument(&_txn, BSON("_id" << 1 << "a" << 1), true)); id1 = coll->getCursor(&_txn)->next()->id; ASSERT_OK(coll->insertDocument(&_txn, BSON("_id" << 2 << "a" << 2), true)); wunit.commit(); } dbtests::createIndex(&_txn, coll->ns().ns(), BSON("name" << "a" << "ns" << coll->ns().ns() << "key" << BSON("a" << 1) << "background" << false)); ASSERT_TRUE(checkValid()); RecordStore* rs = coll->getRecordStore(); // Remove a record, so we get more _id entries than records, and verify validate fails. { WriteUnitOfWork wunit(&_txn); rs->deleteRecord(&_txn, id1); wunit.commit(); } ASSERT_FALSE(checkValid()); // Insert two more records, so we get too few entries for a non-sparse index, and // verify validate fails. { WriteUnitOfWork wunit(&_txn); for (int j = 0; j < 2; j++) { auto doc = BSON("_id" << j); ASSERT_OK( rs->insertRecord(&_txn, doc.objdata(), doc.objsize(), /*enforceQuota*/ false)); } wunit.commit(); } ASSERT_FALSE(checkValid()); } }; class ValidateTests : public Suite { public: ValidateTests() : Suite("validate_tests") {} void setupTests() { // Add tests for both full validate and non-full validate. add>(); add>(); add>(); add>(); } } validateTests; } // namespace ValidateTests