/** * Copyright (C) 2018-present MongoDB, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the Server Side Public License, version 1, * as published by MongoDB, Inc. * * 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 * Server Side Public License for more details. * * You should have received a copy of the Server Side 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 Server Side 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/platform/basic.h" #include "mongo/bson/bsontypes.h" #include "mongo/db/hasher.h" #include "mongo/db/jsobj.h" #include "mongo/db/json.h" #include "mongo/unittest/unittest.h" namespace mongo { namespace { // Helper methods long long hashIt(const BSONObj& object, HashSeed seed) { return BSONElementHasher::hash64(object.firstElement(), seed); } long long hashIt(const BSONObj& object) { HashSeed seed = 0; return hashIt(object, seed); } // Test different oids hash to different things TEST(BSONElementHasher, DifferentOidsAreDifferentHashes) { HashSeed 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) { HashSeed 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::fromMillisSinceEpoch(0x5566778811223344LL)); ASSERT_EQUALS(hashIt(o), 4476222765095560467LL); o = builder1.append("check", Timestamp(0x55667788LL, 0x11223344LL)).obj(); ASSERT_EQUALS(hashIt(o), 4873046866288452390LL); o = BSON("check" << Date_t()); ASSERT_EQUALS(hashIt(o), -1178696894582842035LL); o = builder2.appendTimestamp("check", 0).obj(); ASSERT_EQUALS(hashIt(o), -7867208682377458672LL); } 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); } TEST(BSONElementHasher, HashWithNonZeroSeed) { HashSeed seed = 40513; BSONObj o = BSON("check" << 42); ASSERT_EQUALS(hashIt(o, seed), 4302929669663179197LL); o = BSON("check" << BSON_ARRAY("sunflower" << "sesame" << "mustard")); ASSERT_EQUALS(hashIt(o, seed), -9222615859251096151LL); } } // namespace } // namespace mongo