//@file btreebuildertests.cpp : mongo/db/btreebuilder.{h,cpp} tests /** * Copyright (C) 2012 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 . */ #include "mongo/db/btreebuilder.h" #include "mongo/db/btreecursor.h" #include "mongo/db/pdfile.h" #include "mongo/platform/cstdint.h" #include "mongo/dbtests/dbtests.h" namespace BtreeBuilderTests { static const char* const _ns = "unittests.btreebuilder"; DBDirectClient _client; /** * 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 IndexBuildBase { public: IndexBuildBase() : _ctx( _ns ) { _client.createCollection( _ns ); } ~IndexBuildBase() { _client.dropCollection( _ns ); } protected: /** @return IndexDetails for a new index on a:1, with the info field populated. */ IndexDetails& addIndexWithInfo() { BSONObj indexInfo = BSON( "v" << 1 << "key" << BSON( "a" << 1 ) << "ns" << _ns << "name" << "a_1" ); int32_t lenWHdr = indexInfo.objsize() + Record::HeaderSize; const char* systemIndexes = "unittests.system.indexes"; DiskLoc infoLoc = allocateSpaceForANewRecord( systemIndexes, nsdetails( systemIndexes ), lenWHdr, false ); Record* infoRecord = reinterpret_cast( getDur().writingPtr( infoLoc.rec(), lenWHdr ) ); memcpy( infoRecord->data(), indexInfo.objdata(), indexInfo.objsize() ); addRecordToRecListInExtent( infoRecord, infoLoc ); IndexDetails& id = nsdetails( _ns )->getNextIndexDetails( _ns ); nsdetails( _ns )->addIndex( _ns ); id.info.writing() = infoLoc; return id; } private: Client::WriteContext _ctx; }; /** * BtreeBuilder::commit() constructs a btree from the keys provided to BtreeBuilder::addKey(). */ class Commit : public IndexBuildBase { public: void run() { IndexDetails& id = addIndexWithInfo(); // Create a btree builder. BtreeBuilder builder( false, id ); // Add some keys to the builder, in order. int32_t nKeys = 1000; for( int32_t i = 0; i < nKeys; ++i ) { BSONObj key = BSON( "a" << i ); builder.addKey( key, /* dummy location */ DiskLoc() ); } // The root of the index has not yet been set. ASSERT( id.head.isNull() ); // Call commit on the builder to finish building the btree. builder.commit( true ); // The root of the index is now set. ASSERT( !id.head.isNull() ); // Create a cursor over the index. scoped_ptr cursor( BtreeCursor::make( nsdetails( _ns ), id, BSON( "" << -1 ), // startKey below minimum key value. BSON( "" << nKeys ), // endKey above maximum key value. true, // endKeyInclusive true. 1 // direction forward. ) ); // Check that the keys in the index are the expected ones. int32_t expectedKey = 0; for( ; cursor->ok(); cursor->advance(), ++expectedKey ) { ASSERT_EQUALS( expectedKey, cursor->currKey().firstElement().number() ); } ASSERT_EQUALS( nKeys, expectedKey ); } }; /** * BtreeBuilder::commit() is interrupted if there is a request to kill the current operation. */ class InterruptCommit : public IndexBuildBase { public: InterruptCommit( bool mayInterrupt ) : _mayInterrupt( mayInterrupt ) { } void run() { IndexDetails& id = addIndexWithInfo(); // Create a btree builder. BtreeBuilder builder( false, id ); // Add some keys to the builder, in order. We need enough keys to build an internal // node in order to check for an interrupt. int32_t nKeys = 1000; for( int32_t i = 0; i < nKeys; ++i ) { BSONObj key = BSON( "a" << i ); builder.addKey( key, /* dummy location */ DiskLoc() ); } // The root of the index has not yet been set. ASSERT( id.head.isNull() ); // Register a request to kill the current operation. cc().curop()->kill(); if ( _mayInterrupt ) { // Call commit on the builder, which will be aborted due to the kill request. ASSERT_THROWS( builder.commit( _mayInterrupt ), UserException ); // The root of the index is not set because commit() did not complete. ASSERT( id.head.isNull() ); } else { // Call commit on the builder, which will not be aborted because mayInterrupt is // false. builder.commit( _mayInterrupt ); // The root of the index is set because commit() completed. ASSERT( !id.head.isNull() ); } } private: bool _mayInterrupt; }; class BtreeBuilderTests : public Suite { public: BtreeBuilderTests() : Suite( "btreebuilder" ) { } void setupTests() { add(); add( false ); add( true ); } } btreeBuilderTests; } // namespace BtreeBuilderTests