/** * Copyright (C) 2013 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. */ /** Unit tests for BSONElementHasher. */ #include "mongo/db/hasher.h" #include "mongo/db/jsobj.h" #include "mongo/db/json.h" #include "mongo/bson/bsontypes.h" #include "mongo/unittest/unittest.h" namespace mongo { namespace { // Helper methods long long hashIt( const BSONObj& object, int seed ) { return BSONElementHasher::hash64( object.firstElement(), seed ); } long long hashIt( const BSONObj& object ) { int seed = 0; return hashIt( object, seed ); } // Test different oids hash to different things TEST( BSONElementHasher, DifferentOidsAreDifferentHashes ) { int seed = 0; long long int oidHash = BSONElementHasher::hash64( BSONObjBuilder().genOID().obj().firstElement() , seed ); long long int oidHash2 = BSONElementHasher::hash64( BSONObjBuilder().genOID().obj().firstElement() , seed ); long long int oidHash3 = BSONElementHasher::hash64( BSONObjBuilder().genOID().obj().firstElement() , seed ); ASSERT_NOT_EQUALS( oidHash , oidHash2 ); ASSERT_NOT_EQUALS( oidHash , oidHash3 ); ASSERT_NOT_EQUALS( oidHash3 , oidHash2 ); } // Test 32-bit ints, 64-bit ints, doubles hash to same thing TEST( BSONElementHasher, ConsistentHashOfIntLongAndDouble ) { int i = 3; BSONObj p1 = BSON("a" << i); long long int intHash = hashIt( p1 ); long long int ilong = 3; BSONObj p2 = BSON("a" << ilong); long long int longHash = hashIt( p2 ); double d = 3.1; BSONObj p3 = BSON("a" << d); long long int doubleHash = hashIt( p3 ); ASSERT_EQUALS( intHash, longHash ); ASSERT_EQUALS( doubleHash, longHash ); } // Test different ints don't hash to same thing TEST( BSONElementHasher, DifferentIntHashesDiffer ) { ASSERT_NOT_EQUALS( hashIt( BSON("a" << 3) ) , hashIt( BSON("a" << 4) ) ); } // Test seed makes a difference TEST( BSONElementHasher, SeedMatters ) { ASSERT_NOT_EQUALS( hashIt( BSON("a" << 4), 0 ) , hashIt( BSON("a" << 4), 1 ) ); } // Test strings hash to different things TEST( BSONElementHasher, IntAndStringHashesDiffer ) { ASSERT_NOT_EQUALS( hashIt( BSON("a" << 3) ) , hashIt( BSON("a" << "3") ) ); } // Test regexps and strings hash to different things TEST( BSONElementHasher, RegexAndStringHashesDiffer ) { BSONObjBuilder builder; ASSERT_NOT_EQUALS( hashIt( BSON("a" << "3") ) , hashIt( builder.appendRegex("a","3").obj() ) ); } // Test arrays and subobject hash to different things TEST( BSONElementHasher, ArrayAndSubobjectHashesDiffer ) { ASSERT_NOT_EQUALS( hashIt( fromjson("{a : {'0' : 0 , '1' : 1}}") ) , hashIt( fromjson("{a : [0,1]}") ) ); } // Testing sub-document grouping TEST( BSONElementHasher, SubDocumentGroupingHashesDiffer ) { ASSERT_NOT_EQUALS( hashIt( fromjson("{x : {a : {}, b : 1}}") ) , hashIt( fromjson("{x : {a : {b : 1}}}") ) ); } // Testing codeWscope scope squashing TEST( BSONElementHasher, CodeWithScopeSquashesScopeIntsAndDoubles ) { int seed = 0; BSONObjBuilder b1; b1.appendCodeWScope("a","print('this is some stupid code')", BSON("a" << 3)); BSONObj p10 = b1.obj(); BSONObjBuilder b2; b2.appendCodeWScope("a","print('this is some stupid code')", BSON("a" << 3.1)); BSONObjBuilder b3; b3.appendCodeWScope("a","print('this is \nsome stupider code')", BSON("a" << 3)); ASSERT_EQUALS( BSONElementHasher::hash64( p10.firstElement() , seed ) , BSONElementHasher::hash64( b2.obj().firstElement() , seed ) ); ASSERT_NOT_EQUALS( BSONElementHasher::hash64( p10.firstElement() , seed ) , BSONElementHasher::hash64( b3.obj().firstElement() , seed ) ); } // Test some recursive squashing TEST( BSONElementHasher, RecursiveSquashingIntsAndDoubles ) { ASSERT_EQUALS( hashIt( fromjson("{x : {a : 3 , b : [ 3.1, {c : 3 }]}}") ) , hashIt( fromjson("{x : {a : 3.1 , b : [ 3, {c : 3.0}]}}") ) ); } // Test minkey and maxkey don't hash to same thing TEST( BSONElementHasher, MinKeyMaxKeyHashesDiffer ) { ASSERT_NOT_EQUALS( hashIt( BSON("a" << MAXKEY) ) , hashIt( BSON("a" << MINKEY) ) ); } // Test squashing very large doubles and very small doubles TEST( BSONElementHasher, VeryLargeAndSmallDoubles ) { long long maxInt = std::numeric_limits::max(); double smallerDouble = maxInt/2; double biggerDouble = ( (double)maxInt )*( (double)maxInt ); ASSERT_NOT_EQUALS( hashIt( BSON("a" << maxInt ) ) , hashIt( BSON("a" << smallerDouble ) ) ); ASSERT_EQUALS( hashIt( BSON("a" << maxInt ) ) , hashIt( BSON("a" << biggerDouble ) ) ); long long minInt = std::numeric_limits::min(); double negativeDouble = -( (double)maxInt )*( (double)maxInt ); ASSERT_EQUALS( hashIt( BSON("a" << minInt ) ) , hashIt( BSON("a" << negativeDouble ) ) ); } // Remaining tests are hard-coded checks to ensure the hash function is // consistent across platforms and server versions // // All of the values in the remaining tests have been determined experimentally. TEST( BSONElementHasher, HashIntOrLongOrDouble ) { BSONObj o = BSON( "check" << 42 ); ASSERT_EQUALS( hashIt( o ), -944302157085130861LL ); o = BSON( "check" << 42.123 ); ASSERT_EQUALS( hashIt( o ), -944302157085130861LL ); o = BSON( "check" << (long long) 42 ); ASSERT_EQUALS( hashIt( o ), -944302157085130861LL ); o = BSON( "check" << 0 ); ASSERT_EQUALS( hashIt( o ), 4854801880128277513LL ); o = BSON( "check" << 0.456 ); ASSERT_EQUALS( hashIt( o ), 4854801880128277513LL ); o = BSON( "check" << (long long) 0 ); ASSERT_EQUALS( hashIt( o ), 4854801880128277513LL ); // NAN is treated as zero. o = BSON( "check" << std::numeric_limits::signaling_NaN() ); ASSERT_EQUALS( hashIt( o ), 4854801880128277513LL ); o = BSON( "check" << std::numeric_limits::quiet_NaN() ); ASSERT_EQUALS( hashIt( o ), 4854801880128277513LL ); o = BSON( "check" << 1 ); ASSERT_EQUALS( hashIt( o ), 5902408780260971510LL ); o = BSON( "check" << 1.987 ); ASSERT_EQUALS( hashIt( o ), 5902408780260971510LL ); o = BSON( "check" << (long long) 1 ); ASSERT_EQUALS( hashIt( o ), 5902408780260971510LL ); o = BSON( "check" << -1 ); ASSERT_EQUALS( hashIt( o ), 1140205862565771219LL ); o = BSON( "check" << -1.789 ); ASSERT_EQUALS( hashIt( o ), 1140205862565771219LL ); o = BSON( "check" << (long long) -1 ); ASSERT_EQUALS( hashIt( o ), 1140205862565771219LL ); o = BSON( "check" << std::numeric_limits::min() ); ASSERT_EQUALS( hashIt( o ), 6165898260261354870LL ); o = BSON( "check" << (double) std::numeric_limits::min() ); ASSERT_EQUALS( hashIt( o ), 6165898260261354870LL ); o = BSON( "check" << (long long) std::numeric_limits::min() ); ASSERT_EQUALS( hashIt( o ), 6165898260261354870LL ); o = BSON( "check" << std::numeric_limits::max() ); ASSERT_EQUALS( hashIt( o ), 1143184177162245883LL ); o = BSON( "check" << (double) std::numeric_limits::max() ); ASSERT_EQUALS( hashIt( o ), 1143184177162245883LL ); o = BSON( "check" << (long long) std::numeric_limits::max() ); ASSERT_EQUALS( hashIt( o ), 1143184177162245883LL ); // Large/small double values. ASSERT( std::numeric_limits::max() < std::numeric_limits::max() ); o = BSON( "check" << std::numeric_limits::max() ); ASSERT_EQUALS( hashIt( o ), 921523596458303250LL ); o = BSON( "check" << std::numeric_limits::max() ); // 9223372036854775807 ASSERT_EQUALS( hashIt( o ), 921523596458303250LL ); // Have to create our own small double. // std::numeric_limits::lowest() - Not available until C++11 // std::numeric_limits::min() - Closest positive value to zero, not most negative. double smallDouble = - std::numeric_limits::max(); ASSERT( smallDouble < static_cast( std::numeric_limits::min() ) ); o = BSON( "check" << smallDouble ); ASSERT_EQUALS( hashIt( o ), 4532067210535695462LL ); o = BSON( "check" << std::numeric_limits::min() ); // -9223372036854775808 ASSERT_EQUALS( hashIt( o ), 4532067210535695462LL ); } TEST( BSONElementHasher, HashMinKey ) { BSONObj o = BSON( "check" << MINKEY ); ASSERT_EQUALS( hashIt( o ), 7961148599568647290LL ); } TEST( BSONElementHasher, HashMaxKey ) { BSONObj o = BSON( "check" << MAXKEY ); ASSERT_EQUALS( hashIt( o ), 5504842513779440750LL ); } TEST( BSONElementHasher, HashUndefined ) { BSONObj o = BSON( "check" << BSONUndefined ); ASSERT_EQUALS( hashIt( o ), 40158834000849533LL ); } TEST( BSONElementHasher, HashNull ) { BSONObj o = BSON( "check" << BSONNULL ); ASSERT_EQUALS( hashIt( o ), 2338878944348059895LL ); } TEST( BSONElementHasher, HashString ) { BSONObj o = BSON( "check" << "abc" ); ASSERT_EQUALS( hashIt( o ), 8478485326885698097LL ); o = BSON( "check" << BSONSymbol( "abc" ) ); ASSERT_EQUALS( hashIt( o ), 8478485326885698097LL ); o = BSON( "check" << "" ); ASSERT_EQUALS( hashIt( o ), 2049396243249673340LL ); o = BSON( "check" << BSONSymbol( "" ) ); ASSERT_EQUALS( hashIt( o ), 2049396243249673340LL ); } TEST( BSONElementHasher, HashObject ) { BSONObj o = BSON( "check" << BSON( "a" << "abc" << "b" << 123LL ) ); ASSERT_EQUALS( hashIt( o ), 4771603801758380216LL ); o = BSON( "check" << BSONObj() ); ASSERT_EQUALS( hashIt( o ), 7980500913326740417LL ); } TEST( BSONElementHasher, HashArray ) { BSONObj o = BSON( "check" << BSON_ARRAY( "bar" << "baz" << "qux" ) ); ASSERT_EQUALS( hashIt( o ), -2938911267422831539LL ); o = BSON( "check" << BSONArray() ); ASSERT_EQUALS( hashIt( o ), 8849948234993459283LL ); } TEST( BSONElementHasher, HashBinary ) { uint8_t bytes[] = { 0, 1, 2, 3, 4, 6 }; BSONObj o = BSON( "check" << BSONBinData( bytes, 6, BinDataGeneral ) ); ASSERT_EQUALS( hashIt( o ), 7252465090394235301LL ); o = BSON( "check" << BSONBinData( bytes, 6, bdtCustom ) ); ASSERT_EQUALS( hashIt( o ), 5736670452907618262LL ); uint8_t uuidBytes[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; o = BSON( "check" << BSONBinData( uuidBytes, 16, newUUID ) ); ASSERT_EQUALS( hashIt( o ), 6084661258071355978LL ); } TEST( BSONElementHasher, HashObjectId ) { BSONObj o = BSON( "check" << OID( "010203040506070809101112" ) ); ASSERT_EQUALS( hashIt( o ), -5588663249627035708LL ); o = BSON( "check" << OID( "000000000000000000000000" ) ); ASSERT_EQUALS( hashIt( o ), -4293118519463489418LL ); } TEST( BSONElementHasher, HashBoolean ) { BSONObj o = BSON( "check" << true ); ASSERT_EQUALS( hashIt( o ), 6405873908747105701LL ); o = BSON( "check" << false ); ASSERT_EQUALS( hashIt( o ), 6289544573401934092LL ); } TEST( BSONElementHasher, HashTimeStamp ) { BSONObjBuilder builder1; BSONObjBuilder builder2; BSONObj o = BSON( "check" << Date_t( 0x5566778811223344LL ) ); ASSERT_EQUALS( hashIt( o ), 4476222765095560467LL ); o = builder1.appendTimestamp( "check", 0x55667788LL * 1000LL, 0x11223344LL ).obj(); ASSERT_EQUALS( hashIt( o ), 4476222765095560467LL ); o = BSON( "check" << Date_t( 0 ) ); ASSERT_EQUALS( hashIt( o ), -1178696894582842035LL ); o = builder2.appendTimestamp( "check", 0 ).obj(); ASSERT_EQUALS( hashIt( o ), -1178696894582842035LL ); } TEST( BSONElementHasher, HashRegEx ) { BSONObj o = BSON( "check" << BSONRegEx( "mongodb" ) ); ASSERT_EQUALS( hashIt( o ), -7275792090268217043LL ); o = BSON( "check" << BSONRegEx( ".*", "i" ) ); ASSERT_EQUALS( hashIt( o ), 7095855029187981886LL ); } TEST( BSONElementHasher, HashDBRef ) { BSONObj o = BSON( "check" << BSONDBRef( "c", OID( "010203040506070809101112" ) ) ); ASSERT_EQUALS( hashIt( o ), 940175826736461384LL ); o = BSON( "check" << BSONDBRef( "db.c", OID( "010203040506070809101112" ) ) ); ASSERT_EQUALS( hashIt( o ), 2426768198104018194LL ); } TEST( BSONElementHasher, HashCode ) { BSONObj o = BSON( "check" << BSONCode( "func f() { return 1; }" ) ); ASSERT_EQUALS( hashIt( o ), 6861638109178014270LL ); } TEST( BSONElementHasher, HashCodeWScope ) { BSONObj o = BSON( "check" << BSONCodeWScope( "func f() { return 1; }", BSON( "c" << true ) ) ); ASSERT_EQUALS( hashIt( o ), 501342939894575968LL ); } } // namespace } // namespace mongo