///////////////////////////////////////////////////////////////////////////// // Copyright (c) Electronic Arts Inc. All rights reserved. ///////////////////////////////////////////////////////////////////////////// #include "EASTLTest.h" #include "TestMap.h" #include "TestSet.h" #include #include #include #include #include #include #include #include #include EA_DISABLE_ALL_VC_WARNINGS() #include EA_RESTORE_ALL_VC_WARNINGS() using namespace eastl; namespace eastl { template <> struct hash { size_t operator()(const Align32& a32) const { return static_cast(a32.mX); } }; // extension to hash an eastl::pair template struct hash> { size_t operator()(const pair& c) const { return static_cast(hash()(c.first) ^ hash()(c.second)); } }; } // For regression code below. class HashRegressionA { public: int x; }; class HashRegressionB { public: int y; }; // For regression code below. struct Struct { char8_t name[128]; }; // For regression code below. template struct HashTest { template auto operator()(Args&&... args) { return eastl::hash{}(eastl::forward(args)...); } }; // What we are doing here is creating a special case of a hashtable where the key compare // function is not the same as the value operator==. 99% of the time when you create a // hashtable the key compare (predicate) is simply key_equal or something else that's // identical to operator== for the hashtable value type. But for some tests we want // to exercise the case that these aren't different. A result of this difference is that // you can lookup an element in a hash table and the returned value is not == to the // value you looked up, because it succeeds the key compare but not operator==. struct HashtableValue { HashtableValue(eastl_size_t d = 0, eastl_size_t e = 0) : mData(d), mExtra(e){} void Set(eastl_size_t d, eastl_size_t e = 0) { mData = d; mExtra = e; } eastl_size_t mData; eastl_size_t mExtra; }; bool operator==(const HashtableValue& htv1, const HashtableValue& htv2) { return (htv1.mData == htv2.mData) && (htv1.mExtra == htv2.mExtra); // Fully compare the HashTableValue. } struct HashtableValuePredicate { bool operator()(const HashtableValue& htv1, const HashtableValue& htv2) const { return (htv1.mData == htv2.mData); } // Compare just the mData portion of HashTableValue. }; struct HashtableValueHash { size_t operator()(const HashtableValue& htv) const { return static_cast(htv.mData); } }; // Explicit Template instantiations. // These tell the compiler to compile all the functions for the given class. template class eastl::hashtable, eastl::allocator, eastl::use_first>, eastl::equal_to, eastl::hash, mod_range_hashing, default_ranged_hash, prime_rehash_policy, true, // bCacheHashCode true, // bMutableIterators true // bUniqueKeys >; template class eastl::hashtable, eastl::allocator, eastl::use_first>, eastl::equal_to, eastl::hash, mod_range_hashing, default_ranged_hash, prime_rehash_policy, false, // bCacheHashCode true, // bMutableIterators true // bUniqueKeys >; // TODO(rparolin): known compiler error, we should fix this. // template class eastl::hashtable, // eastl::allocator, // eastl::use_first>, // eastl::equal_to, // eastl::hash, // mod_range_hashing, // default_ranged_hash, // prime_rehash_policy, // false, // bCacheHashCode // true, // bMutableIterators // false // bUniqueKeys // >; // Note these will only compile non-inherited functions. We provide explicit // template instantiations for the hashtable base class above to get compiler // coverage of those inherited hashtable functions. template class eastl::hash_set; template class eastl::hash_multiset; template class eastl::hash_map; template class eastl::hash_multimap; template class eastl::hash_set; template class eastl::hash_multiset; template class eastl::hash_map; template class eastl::hash_multimap; // validate static assumptions about hashtable core types typedef eastl::hash_node HashNode1; typedef eastl::hash_node HashNode2; static_assert(eastl::is_default_constructible::value, "hash_node static error"); static_assert(eastl::is_default_constructible::value, "hash_node static error"); static_assert(eastl::is_copy_constructible::value, "hash_node static error"); static_assert(eastl::is_copy_constructible::value, "hash_node static error"); static_assert(eastl::is_move_constructible::value, "hash_node static error"); static_assert(eastl::is_move_constructible::value, "hash_node static error"); // A custom hash function that has a high number of collisions is used to ensure many keys share the same hash value. struct colliding_hash { size_t operator()(const int& val) const { return static_cast(val % 3); } }; int TestHash() { int nErrorCount = 0; { // Test declarations hash_set hashSet; hash_multiset hashMultiSet; hash_map hashMap; hash_multimap hashMultiMap; hash_set hashSet2(hashSet); EATEST_VERIFY(hashSet2.size() == hashSet.size()); EATEST_VERIFY(hashSet2 == hashSet); hash_multiset hashMultiSet2(hashMultiSet); EATEST_VERIFY(hashMultiSet2.size() == hashMultiSet.size()); EATEST_VERIFY(hashMultiSet2 == hashMultiSet); hash_map hashMap2(hashMap); EATEST_VERIFY(hashMap2.size() == hashMap.size()); EATEST_VERIFY(hashMap2 == hashMap); hash_multimap hashMultiMap2(hashMultiMap); EATEST_VERIFY(hashMultiMap2.size() == hashMultiMap.size()); EATEST_VERIFY(hashMultiMap2 == hashMultiMap); // allocator_type& get_allocator(); // void set_allocator(const allocator_type& allocator); hash_set::allocator_type& allocator = hashSet.get_allocator(); hashSet.set_allocator(EASTLAllocatorType()); hashSet.set_allocator(allocator); // To do: Try to find something better to test here. // const key_equal& key_eq() const; // key_equal& key_eq(); hash_set hs; const hash_set hsc; const hash_set::key_equal& ke = hsc.key_eq(); hs.key_eq() = ke; // const char* get_name() const; // void set_name(const char* pName); #if EASTL_NAME_ENABLED hashMap.get_allocator().set_name("test"); const char* pName = hashMap.get_allocator().get_name(); EATEST_VERIFY(equal(pName, pName + 5, "test")); #endif } { hash_set hashSet; // Clear a newly constructed, already empty container. hashSet.clear(true); EATEST_VERIFY(hashSet.validate()); EATEST_VERIFY(hashSet.size() == 0); EATEST_VERIFY(hashSet.bucket_count() == 1); for(int i = 0; i < 100; ++i) hashSet.insert(i); EATEST_VERIFY(hashSet.validate()); EATEST_VERIFY(hashSet.size() == 100); hashSet.clear(true); EATEST_VERIFY(hashSet.validate()); EATEST_VERIFY(hashSet.size() == 0); EATEST_VERIFY(hashSet.bucket_count() == 1); for(int i = 0; i < 100; ++i) hashSet.insert(i); EATEST_VERIFY(hashSet.validate()); EATEST_VERIFY(hashSet.size() == 100); hashSet.clear(true); EATEST_VERIFY(hashSet.validate()); EATEST_VERIFY(hashSet.size() == 0); EATEST_VERIFY(hashSet.bucket_count() == 1); } { // Test hash_set // size_type size() const // bool empty() const // insert_return_type insert(const value_type& value); // insert_return_type insert(const value_type& value, hash_code_t c, node_type* pNodeNew = NULL); // iterator insert(const_iterator, const value_type& value); // iterator find(const key_type& k); // const_iterator find(const key_type& k) const; // size_type count(const key_type& k) const; typedef hash_set HashSetInt; HashSetInt hashSet; const HashSetInt::size_type kCount = 10000; EATEST_VERIFY(hashSet.empty()); EATEST_VERIFY(hashSet.size() == 0); EATEST_VERIFY(hashSet.count(0) == 0); for(int i = 0; i < (int)kCount; i++) hashSet.insert(i); EATEST_VERIFY(!hashSet.empty()); EATEST_VERIFY(hashSet.size() == kCount); EATEST_VERIFY(hashSet.count(0) == 1); for(HashSetInt::iterator it = hashSet.begin(); it != hashSet.end(); ++it) { int value = *it; EATEST_VERIFY(value < (int)kCount); } for(int i = 0; i < (int)kCount * 2; i++) { HashSetInt::iterator it = hashSet.find(i); if(i < (int)kCount) EATEST_VERIFY(it != hashSet.end()); else EATEST_VERIFY(it == hashSet.end()); } // insert_return_type insert(const value_type& value, hash_code_t c, node_type* pNodeNew = NULL); HashSetInt::node_type* pNode = hashSet.allocate_uninitialized_node(); HashSetInt::insert_return_type r = hashSet.insert(eastl::hash()(999999), pNode, 999999); EATEST_VERIFY(r.second == true); pNode = hashSet.allocate_uninitialized_node(); r = hashSet.insert(eastl::hash()(999999), pNode, 999999); EATEST_VERIFY(r.second == false); hashSet.free_uninitialized_node(pNode); hashSet.erase(999999); // iterator begin(); // const_iterator begin() const; // iterator end(); // const_iterator end() const; int* const pIntArray = new int[kCount]; memset(pIntArray, 0, kCount * sizeof(int)); // We want to make sure each element is present only once. int nCount = 0; for(HashSetInt::iterator it = hashSet.begin(); it != hashSet.end(); ++it, ++nCount) { int i = *it; EATEST_VERIFY((i >= 0) && (i < (int)kCount) && (pIntArray[i] == 0)); pIntArray[i] = 1; } EATEST_VERIFY(nCount == (int)kCount); delete[] pIntArray; } { // size_type bucket_count() const // size_type bucket_size(size_type n) const // float load_factor() const // float get_max_load_factor() const; // void set_max_load_factor(float fMaxLoadFactor); // void rehash(size_type n); // const RehashPolicy& rehash_policy() const // void rehash_policy(const RehashPolicy& rehashPolicy); typedef hash_set HashSetInt; HashSetInt hashSet; float fLoadFactor = hashSet.load_factor(); EATEST_VERIFY(fLoadFactor == 0.f); hashSet.set_max_load_factor(65536.f * 512.f); float fMaxLoadFactor = hashSet.get_max_load_factor(); EATEST_VERIFY(fMaxLoadFactor == (65536.f * 512.f)); hashSet.rehash(20); HashSetInt::size_type n = hashSet.bucket_count(); EATEST_VERIFY((n >= 20) && (n < 25)); for(int i = 0; i < 100000; i++) hashSet.insert(i); // This also tests for high loading. HashSetInt::size_type n2 = hashSet.bucket_count(); EATEST_VERIFY(n2 == n); // Verify no rehashing has occured, due to our high load factor. n = hashSet.bucket_size(0); EATEST_VERIFY(n >= ((hashSet.size() / hashSet.bucket_count()) / 2)); // It will be some high value. We divide by 2 to give it some slop. EATEST_VERIFY(hashSet.validate()); hash_set::rehash_policy_type rp = hashSet.rehash_policy(); rp.mfGrowthFactor = 1.5f; hashSet.rehash_policy(rp); EATEST_VERIFY(hashSet.validate()); // local_iterator begin(size_type n); // local_iterator end(size_type n); // const_local_iterator begin(size_type n) const; // const_local_iterator end(size_type n) const; HashSetInt::size_type b = hashSet.bucket_count() - 1; hash IntHash; for(HashSetInt::const_local_iterator cli = hashSet.begin(b); cli != hashSet.end(b); ++cli) { int v = *cli; EATEST_VERIFY((IntHash(v) % hashSet.bucket_count()) == b); } // clear(); hashSet.clear(); EATEST_VERIFY(hashSet.validate()); EATEST_VERIFY(hashSet.empty()); EATEST_VERIFY(hashSet.size() == 0); EATEST_VERIFY(hashSet.count(0) == 0); hashSet.clear(true); EATEST_VERIFY(hashSet.validate()); EATEST_VERIFY(hashSet.bucket_count() == 1); } { // void reserve(size_type nElementCount); nErrorCount += HashContainerReserveTest>()(); nErrorCount += HashContainerReserveTest>()(); nErrorCount += HashContainerReserveTest>()(); nErrorCount += HashContainerReserveTest>()(); } { // Test hash_set with cached hash code. // insert_return_type insert(const value_type& value) ; // iterator find(const key_type& k); // const_iterator find(const key_type& k) const; typedef hash_set, equal_to, EASTLAllocatorType, true> HashSetIntC; HashSetIntC hashSet; const int kCount = 10000; for(int i = 0; i < kCount; i++) hashSet.insert(i); for(HashSetIntC::iterator it = hashSet.begin(); it != hashSet.end(); ++it) { int value = *it; EATEST_VERIFY(value < kCount); } for(int i = 0; i < kCount * 2; i++) { HashSetIntC::iterator it = hashSet.find(i); if(i < kCount) EATEST_VERIFY(it != hashSet.end()); else EATEST_VERIFY(it == hashSet.end()); } } { // ENABLE_IF_HASHCODE_U32(HashCodeT, iterator) find_by_hash(HashCodeT c) // ENABLE_IF_HASHCODE_U32(HashCodeT, const_iterator) find_by_hash(HashCodeT c) const { // NOTE(rparolin): // these overloads of find_by_hash contains a static assert that forces a compiler error in the event it is // used with a hashtable configured to not cache the hash value in the node. } // iterator find_by_hash(const key_type& k, hash_code_t c) // const_iterator find_by_hash(const key_type& k, hash_code_t c) const #ifdef EA_COMPILER_CPP14_ENABLED { auto FindByHashTest = [&nErrorCount](auto& hashSet) { const int kCount = 10000; for(int i = 0; i < kCount; i++) hashSet.insert(i); for(int i = 0; i < kCount * 2; i++) { auto it = hashSet.find_by_hash(i, i); if(i < kCount) EATEST_VERIFY(it != hashSet.end()); else EATEST_VERIFY(it == hashSet.end()); } }; { typedef hash_set, equal_to, EASTLAllocatorType, true> HashSetIntC; HashSetIntC hashSetC; FindByHashTest(hashSetC); typedef hash_set, equal_to, EASTLAllocatorType, false> HashSetInt; HashSetInt hashSet; FindByHashTest(hashSet); } } #endif } { // hash_set(const allocator_type& allocator); // hashtable& operator=(const this_type& x); // bool validate() const; hash_set hashSet1(EASTLAllocatorType("hash_set name")); hash_set hashSet2(hashSet1); for(int i = 0; i < 10; i++) { hashSet1.insert(i); hashSet2.insert(i); } hashSet1 = hashSet2; EATEST_VERIFY(hashSet1.validate()); EATEST_VERIFY(hashSet2.validate()); } { // hash_set(size_type nBucketCount, const Hash& hashFunction = Hash(), const Predicate& predicate = Predicate(), const allocator_type& allocator); // hashtable(const hashtable& x); // hashtable& operator=(const this_type& x); // void swap(this_type& x); // bool validate() const; { hash_set hashSet3(0); hash_set hashSet4(1); hash_set hashSet5(2); hash_set hashSet6(3); hash_set hashSet7(4); hashSet4 = hashSet3; hashSet6 = hashSet5; hashSet3 = hashSet7; for(int i = 0; i < 10; i++) { hashSet3.insert(i); hashSet4.insert(i); hashSet5.insert(i); hashSet6.insert(i); hashSet7.insert(i); } hashSet4 = hashSet3; hashSet6 = hashSet5; hashSet3 = hashSet7; EATEST_VERIFY(hashSet3.validate()); EATEST_VERIFY(hashSet4.validate()); EATEST_VERIFY(hashSet5.validate()); EATEST_VERIFY(hashSet6.validate()); EATEST_VERIFY(hashSet7.validate()); swap(hashSet4, hashSet3); swap(hashSet6, hashSet5); swap(hashSet3, hashSet7); EATEST_VERIFY(hashSet3.validate()); EATEST_VERIFY(hashSet4.validate()); EATEST_VERIFY(hashSet5.validate()); EATEST_VERIFY(hashSet6.validate()); EATEST_VERIFY(hashSet7.validate()); hash_set hashSet8(hashSet6); hash_set hashSet9(hashSet7); hash_set hashSet10(hashSet8); EATEST_VERIFY(hashSet8.validate()); EATEST_VERIFY(hashSet9.validate()); EATEST_VERIFY(hashSet10.validate()); } // test hashtable::swap using different allocator instances { typedef hash_set, eastl::equal_to, InstanceAllocator> HS; HS hashSet1(InstanceAllocator("hash_set1 name", 111)); HS hashSet2(InstanceAllocator("hash_set2 name", 222)); for(int i = 0; i < 10; i++) { hashSet1.insert(i); hashSet2.insert(i+10); } hashSet2.swap(hashSet1); EATEST_VERIFY(hashSet1.validate()); EATEST_VERIFY(hashSet2.validate()); EATEST_VERIFY(hashSet1.get_allocator().mInstanceId == 222); EATEST_VERIFY(hashSet2.get_allocator().mInstanceId == 111); EATEST_VERIFY(eastl::all_of(eastl::begin(hashSet2), eastl::end(hashSet2), [](int i) { return i < 10; })); EATEST_VERIFY(eastl::all_of(eastl::begin(hashSet1), eastl::end(hashSet1), [](int i) { return i >= 10; })); } } { // hash_set(InputIterator first, InputIterator last, size_type nBucketCount = 8, const Hash& hashFunction = Hash(), const Predicate& predicate = Predicate(), const allocator_type& allocator); // bool validate() const; vector intArray; for(int i = 0; i < 1000; i++) intArray.push_back(i); hash_set hashSet1(intArray.begin(), intArray.end(), 0); hash_set hashSet2(intArray.begin(), intArray.end(), 1); hash_set hashSet3(intArray.begin(), intArray.end(), 2); hash_set hashSet4(intArray.begin(), intArray.end(), 3); EATEST_VERIFY(hashSet1.validate()); EATEST_VERIFY(hashSet2.validate()); EATEST_VERIFY(hashSet3.validate()); EATEST_VERIFY(hashSet4.validate()); // bool validate_iterator(const_iterator i) const; hash_set::iterator it; int result = hashSet1.validate_iterator(it); EATEST_VERIFY(result == isf_none); it = hashSet1.begin(); result = hashSet2.validate_iterator(it); EATEST_VERIFY(result == isf_none); result = hashSet1.validate_iterator(it); EATEST_VERIFY(result == (isf_valid | isf_current | isf_can_dereference)); it = hashSet1.end(); result = hashSet1.validate_iterator(it); EATEST_VERIFY(result == (isf_valid | isf_current)); // void reset_lose_memory(); hashSet1.reset_lose_memory(); hashSet1 = hashSet2; EATEST_VERIFY(hashSet1.validate()); EATEST_VERIFY(hashSet2.validate()); hashSet3.reset_lose_memory(); hashSet4 = hashSet3; EATEST_VERIFY(hashSet3.validate()); EATEST_VERIFY(hashSet4.validate()); hashSet2.reset_lose_memory(); hashSet3.reset_lose_memory(); swap(hashSet2, hashSet3); EATEST_VERIFY(hashSet3.validate()); EATEST_VERIFY(hashSet4.validate()); hashSet2 = hashSet3; EATEST_VERIFY(hashSet2.validate()); } { // void insert(InputIterator first, InputIterator last); vector intArray1; vector intArray2; for(int i = 0; i < 1000; i++) { intArray1.push_back(i + 0); intArray2.push_back(i + 500); } hash_set hashSet1(intArray1.begin(), intArray1.end()); hashSet1.insert(intArray2.begin(), intArray2.end()); EATEST_VERIFY(hashSet1.validate()); hash_set hashSet2; hashSet2.insert(intArray1.begin(), intArray1.end()); hashSet2.insert(intArray2.begin(), intArray2.end()); EATEST_VERIFY(hashSet2.validate()); EATEST_VERIFY(hashSet1 == hashSet2); // insert_return_type insert(const_iterator, const value_type& value) for(int j = 0; j < 1000; j++) hashSet1.insert(hashSet1.begin(), j); insert_iterator< hash_set > ii(hashSet1, hashSet1.begin()); for(int j = 0; j < 1000; j++) *ii++ = j; } { // C++11 emplace and related functionality nErrorCount += TestMapCpp11>(); nErrorCount += TestMapCpp11>(); nErrorCount += TestSetCpp11>(); nErrorCount += TestSetCpp11>(); nErrorCount += TestMultimapCpp11>(); nErrorCount += TestMultimapCpp11>(); nErrorCount += TestMultisetCpp11>(); nErrorCount += TestMultisetCpp11>(); nErrorCount += TestMapCpp11NonCopyable>(); nErrorCount += TestMapCpp11NonCopyable>(); } { // C++17 try_emplace and related functionality nErrorCount += TestMapCpp17>(); nErrorCount += TestMapCpp17>(); } { // initializer_list support. // hash_set(std::initializer_list ilist, size_type nBucketCount = 0, const Hash& hashFunction = Hash(), // const Predicate& predicate = Predicate(), const allocator_type& allocator = EASTL_HASH_SET_DEFAULT_ALLOCATOR) // this_type& operator=(std::initializer_list ilist); // void insert(std::initializer_list ilist); hash_set intHashSet = { 12, 13, 14 }; EATEST_VERIFY(intHashSet.size() == 3); EATEST_VERIFY(intHashSet.find(12) != intHashSet.end()); EATEST_VERIFY(intHashSet.find(13) != intHashSet.end()); EATEST_VERIFY(intHashSet.find(14) != intHashSet.end()); intHashSet = { 22, 23, 24 }; EATEST_VERIFY(intHashSet.size() == 3); EATEST_VERIFY(intHashSet.find(22) != intHashSet.end()); EATEST_VERIFY(intHashSet.find(23) != intHashSet.end()); EATEST_VERIFY(intHashSet.find(24) != intHashSet.end()); intHashSet.insert({ 42, 43, 44 }); EATEST_VERIFY(intHashSet.size() == 6); EATEST_VERIFY(intHashSet.find(42) != intHashSet.end()); EATEST_VERIFY(intHashSet.find(43) != intHashSet.end()); EATEST_VERIFY(intHashSet.find(44) != intHashSet.end()); } { // eastl::pair equal_range(const key_type& k); // eastl::pair equal_range(const key_type& k) const; // const_iterator erase(const_iterator, const_iterator); // size_type erase(const key_type&); // To do. } { // hash_set erase_if hash_set m = {0, 1, 2, 3, 4}; eastl::erase_if(m, [](auto i) { return i % 2 == 0; }); VERIFY((m == hash_set{1, 3})); } { // hash_multiset erase_if hash_multiset m = {0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 4}; eastl::erase_if(m, [](auto i) { return i % 2 == 0; }); VERIFY((m == hash_multiset{1, 1, 1, 3})); } { // Test hash_map // insert_return_type insert(const value_type& value); // insert_return_type insert(const key_type& key); // iterator find(const key_type& k); // const_iterator find(const key_type& k) const; typedef hash_map HashMapIntInt; HashMapIntInt hashMap; const int kCount = 10000; for(int i = 0; i < kCount; i++) { HashMapIntInt::value_type vt(i, i); hashMap.insert(vt); } const HashMapIntInt const_hashMap = hashMap; // creating a const version to test for const correctness for(auto& e : hashMap) { int k = e.first; int v = e.second; EATEST_VERIFY(k < kCount); EATEST_VERIFY(v == k); EATEST_VERIFY(hashMap.at(k) == k); EATEST_VERIFY(const_hashMap.at(k) == k); hashMap.at(k) = k << 4; } for(auto& e : hashMap) { int k = e.first; int v = e.second; EATEST_VERIFY(k < kCount); EATEST_VERIFY(v == (k << 4)); } for(int i = 0; i < kCount * 2; i++) { HashMapIntInt::iterator it = hashMap.find(i); if(i < kCount) { EATEST_VERIFY(it != hashMap.end()); int k = (*it).first; int v = (*it).second; EATEST_VERIFY(v == (k << 4)); } else EATEST_VERIFY(it == hashMap.end()); } for(int i = 0; i < kCount; i++) { int v = hashMap.at(i); EATEST_VERIFY(v == (i << 4)); } #if EASTL_EXCEPTIONS_ENABLED try { hashMap.at(kCount); EASTL_ASSERT_MSG(false, "at accessor did not throw out_of_range exception"); } catch(const std::out_of_range) { } catch(const std::exception& e) { string e_msg(e.what()); string msg = "wrong exception with message \"" + e_msg + "\" thrown"; EASTL_ASSERT_MSG(false, msg.c_str()); } #endif HashMapIntInt::insert_return_type result = hashMap.insert(88888); EATEST_VERIFY(result.second == true); result = hashMap.insert(88888); EATEST_VERIFY(result.second == false); result.first->second = 0; // const_iterator erase(const_iterator); size_t nExpectedSize = hashMap.size(); HashMapIntInt::iterator it50 = hashMap.find(50); EATEST_VERIFY(it50 != hashMap.end()); HashMapIntInt::iterator itNext = hashMap.erase(it50); nExpectedSize--; EATEST_VERIFY(itNext != hashMap.end()); // Strictly speaking, this isn't guaranteed to be so. But statistically it is very likely. We'll fix this if it becomes a problem. EATEST_VERIFY(hashMap.size() == nExpectedSize); HashMapIntInt::size_type n = hashMap.erase(10); nExpectedSize--; EATEST_VERIFY(n == 1); EATEST_VERIFY(hashMap.size() == nExpectedSize); HashMapIntInt::iterator it60 = hashMap.find(60); EATEST_VERIFY(itNext != hashMap.end()); HashMapIntInt::iterator it60Incremented(it60); for(int i = 0; (i < 5) && (it60Incremented != hashMap.end()); ++i) { ++it60Incremented; --nExpectedSize; } hashMap.erase(it60, it60Incremented); EATEST_VERIFY(hashMap.size() == nExpectedSize); // insert_return_type insert(const value_type& value, hash_code_t c, node_type* pNodeNew = NULL); HashMapIntInt::node_type* pNode = hashMap.allocate_uninitialized_node(); HashMapIntInt::insert_return_type r = hashMap.insert(eastl::hash()(999999), pNode, HashMapIntInt::value_type(999999, 999999)); EATEST_VERIFY(r.second == true); pNode = hashMap.allocate_uninitialized_node(); r = hashMap.insert(eastl::hash()(999999), pNode, HashMapIntInt::value_type(999999, 999999)); EATEST_VERIFY(r.second == false); hashMap.free_uninitialized_node(pNode); hashMap.erase(999999); // mapped_type& operator[](const key_type& key) // hash_map is unique among the map/set containers in having this function. hashMap.clear(); int x = hashMap[0]; // A default-constructed int (i.e. 0) should be returned. EATEST_VERIFY(x == 0); hashMap[1] = 1; x = hashMap[1]; EATEST_VERIFY(x == 1); // Verify that the value we assigned is returned and a default-constructed value is not returned. hashMap[0] = 10; // Overwrite our previous 0 with 10. hashMap[1] = 11; x = hashMap[0]; EATEST_VERIFY(x == 10); // Verify the value is as expected. x = hashMap[1]; EATEST_VERIFY(x == 11); } { // Test hash_map // Aligned objects should be CustomAllocator instead of the default, because the // EASTL default might be unable to do aligned allocations, but CustomAllocator always can. hash_map, eastl::equal_to, CustomAllocator> hashMap; const int kCount = 10000; for(int i = 0; i < kCount; i++) { Align32 a32(i); // GCC 2.x doesn't like the Align32 object being created in the ctor below. hash_map::value_type vt(a32, i); hashMap.insert(vt); } for(hash_map::iterator it = hashMap.begin(); it != hashMap.end(); ++it) { const Align32& k = (*it).first; int v = (*it).second; EATEST_VERIFY(k.mX < 10000); EATEST_VERIFY(v == k.mX); } for(int i = 0; i < kCount * 2; i++) { hash_map::iterator it = hashMap.find(Align32(i)); if(i < kCount) { EATEST_VERIFY(it != hashMap.end()); const Align32& k = (*it).first; int v = (*it).second; EATEST_VERIFY(v == k.mX); } else EATEST_VERIFY(it == hashMap.end()); } } { // hash_map erase_if hash_map m = {{0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}}; eastl::erase_if(m, [](auto p) { return p.first % 2 == 0; }); VERIFY((m == hash_map{{1, 1}, {3, 3}})); } { // hash_multimap erase_if hash_multimap m = {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {1, 1}, {2, 2}, {2, 2}, {2, 2}, {2, 2}, {3, 3}, {3, 3}, {4, 4}}; eastl::erase_if(m, [](auto p) { return p.first % 2 == 0; }); VERIFY((m == hash_multimap{{1, 1}, {3, 3}, {3, 3}})); } { // template // iterator find_as(const U& u, UHash uhash, BinaryPredicate predicate); // template // const_iterator find_as(const U& u, UHash uhash, BinaryPredicate predicate) const; // template // iterator find_as(const U& u); // template // const_iterator find_as(const U& u) const; typedef hash_set HashSetString; HashSetString hashSet; const int kCount = 100; for(int i = 0; i < kCount; i++) { string::CtorSprintf cs; // GCC 2.x doesn't like this value being created in the ctor below. string s(cs, "%d", i); hashSet.insert(s); } for(int i = 0; i < kCount * 2; i++) { char pString[32]; sprintf(pString, "%d", i); HashSetString::iterator it = hashSet.find_as(pString); if(i < kCount) EATEST_VERIFY(it != hashSet.end()); else EATEST_VERIFY(it == hashSet.end()); it = hashSet.find_as(pString, hash(), equal_to_2()); if(i < kCount) EATEST_VERIFY(it != hashSet.end()); else EATEST_VERIFY(it == hashSet.end()); } } { // Test const containers. const hash_set constHashSet; hash_set::const_iterator i = constHashSet.begin(); hash_set::const_iterator i3 = i; hash_set::iterator i2; i3 = i2; EATEST_VERIFY(i3 == i2); //const std::tr1::unordered_set constUSet; //std::tr1::unordered_set::const_iterator i = constUSet.begin(); //*i = 0; } { // global operator ==, != EASTLTest_Rand rng(EA::UnitTest::GetRandSeed()); const eastl_size_t kIterationCount = 100; const eastl_size_t kDataRange = 50; { typedef hash_set HashSet; HashtableValue value; HashSet h1; HashSet h2; EATEST_VERIFY(h1 == h2); for(eastl_size_t i = 0; i < kIterationCount; i++) { value.mData = rng.RandLimit(kDataRange); h1.insert(value); // Leave value.mExtra as 0. } EATEST_VERIFY(h1 != h2); h2 = h1; EATEST_VERIFY(h1 == h2); // Test the case of the containers being the same size but having a single different value, despite that it's key compare yields equal. HashSet h2Saved(h2); HashSet::iterator it = h2.find(value); HashtableValue valueModified(value.mData, 1); h2.erase(it); h2.insert(valueModified); EATEST_VERIFY(h1 != h2); h2 = h2Saved; // Test the case of the containers being the same size but having a single different key. h2Saved = h2; h2.erase(h2.find(value)); h2.insert(kDataRange); // Insert something that could not have been in h2. EATEST_VERIFY(h1 != h2); h2 = h2Saved; h1.erase(h1.find(value)); // Erase from h1 whatever the last value was. EATEST_VERIFY(h1 != h2); } { typedef hash_multiset HashSet; HashtableValue value; HashSet h1; HashSet h2; EATEST_VERIFY(h1 == h2); for(eastl_size_t i = 0; i < kIterationCount; i++) { value.mData = rng.RandLimit(kDataRange); h1.insert(value); // Leave value.mExtra as 0. } EATEST_VERIFY(h1 != h2); h2 = h1; EATEST_VERIFY(h1 == h2); // Test the case of the containers being the same size but having a single different value, despite that it's key compare yields equal. HashSet h2Saved(h2); HashSet::iterator it = h2.find(value); HashtableValue valueModified(value.mData, 1); h2.erase(it); h2.insert(valueModified); EATEST_VERIFY(h1 != h2); h2 = h2Saved; // Test the case of the containers being the same size but having a single different key. h2Saved = h2; h2.erase(h2.find(value)); h2.insert(kDataRange); // Insert something that could not have been in h2. EATEST_VERIFY(h1 != h2); h2 = h2Saved; h1.erase(h1.find(value)); // Erase from h1 whatever the last value was. EATEST_VERIFY(h1 != h2); } { // For simplicity we duplicate the HashtableValue::mData member as the hash map key. typedef hash_map HashMap; HashtableValue value; HashMap h1; HashMap h2; EATEST_VERIFY(h1 == h2); for(eastl_size_t i = 0; i < kIterationCount; i++) { value.mData = rng.RandLimit(kDataRange); h1.insert(HashMap::value_type(value.mData, value)); // Leave value.mExtra as 0. } EATEST_VERIFY(h1 != h2); h2 = h1; EATEST_VERIFY(h1 == h2); // Test the case of the containers being the same size but having a single different value, despite that it's key compare yields equal. HashMap h2Saved(h2); HashMap::iterator it = h2.find(value.mData); // We are using value.mData as the key as well, so we can do a find via it. HashtableValue valueModified(value.mData, 1); h2.erase(it); h2.insert(HashMap::value_type(valueModified.mData, valueModified)); EATEST_VERIFY(h1 != h2); h2 = h2Saved; // Test the case of the containers being the same size but having a single different key. h2Saved = h2; h2.erase(h2.find(value.mData)); h2.insert(HashMap::value_type(kDataRange, HashtableValue(kDataRange))); // Insert something that could not have been in h2. EATEST_VERIFY(h1 != h2); h2 = h2Saved; h1.erase(h1.find(value.mData)); // Erase from h1 whatever the last value was. EATEST_VERIFY(h1 != h2); } { // For simplicity we duplicate the HashtableValue::mData member as the hash map key. typedef hash_multimap HashMap; HashtableValue value; HashMap h1; HashMap h2; EATEST_VERIFY(h1 == h2); for(eastl_size_t i = 0; i < kIterationCount; i++) { value.mData = rng.RandLimit(kDataRange); h1.insert(HashMap::value_type(value.mData, value)); // Leave value.mExtra as 0. } EATEST_VERIFY(h1 != h2); h2 = h1; EATEST_VERIFY(h1 == h2); // Test the case of the containers being the same size but having a single different value, despite that it's key compare yields equal. HashMap h2Saved(h2); HashMap::iterator it = h2.find(value.mData); // We are using value.mData as the key as well, so we can do a find via it. HashtableValue valueModified(value.mData, 1); h2.erase(it); h2.insert(HashMap::value_type(valueModified.mData, valueModified)); EATEST_VERIFY(h1 != h2); h2 = h2Saved; // Test the case of the containers being the same size but having a single different key. h2Saved = h2; h2.erase(h2.find(value.mData)); h2.insert(HashMap::value_type(kDataRange, HashtableValue(kDataRange))); // Insert something that could not have been in h2. EATEST_VERIFY(h1 != h2); h2 = h2Saved; h1.erase(h1.find(value.mData)); // Erase from h1 whatever the last value was. EATEST_VERIFY(h1 != h2); } } { typedef eastl::hash_multiset HashMultisetInt; HashMultisetInt hashMultiSet; // insert_return_type insert(const value_type& value, hash_code_t c, node_type* pNodeNew = NULL); HashMultisetInt::node_type* pNode = hashMultiSet.allocate_uninitialized_node(); HashMultisetInt::iterator it1 = hashMultiSet.insert(eastl::hash()(999999), pNode, 999999); EATEST_VERIFY(it1 != hashMultiSet.end()); pNode = hashMultiSet.allocate_uninitialized_node(); HashMultisetInt::iterator it2 = hashMultiSet.insert(eastl::hash()(999999), pNode, 999999); EATEST_VERIFY(it2 != hashMultiSet.end() && it2 != it1); } { // Regression of compiler warning reported by Jeff Litz/Godfather regarding // strict aliasing (EASTL 1.09.01) December 2007). typedef eastl::hash_multimap Map; Map* pMap = new Map; delete pMap; } { // Regression of user-reported crash. eastl::hash_map* _hmTextureList; _hmTextureList = new eastl::hash_map(); eastl::string* a = NULL; (*_hmTextureList)[0] = a; delete _hmTextureList; } { // Regression of user-reported Android compiler error. typedef eastl::hash_multimap HMM; HMM m_hash; // Section 1 for (HMM::iterator it = m_hash.begin(); it != m_hash.end(); it++) it->second.y = 1; // Section 2 HashRegressionA* pA = NULL; eastl::pair pair = m_hash.equal_range(pA); (void)pair; } { // Regression of user-reported GCC 4.8 compile failure. typedef eastl::hash_map AuditByBlazeIdMap; AuditByBlazeIdMap auditBlazeIds; AuditByBlazeIdMap tempAuditBlazeIds; auditBlazeIds.swap(tempAuditBlazeIds); // This line was generating an unexpected compiler failure. EATEST_VERIFY(auditBlazeIds.empty() && tempAuditBlazeIds.empty()); } { // This test is designed to designed to use the find_range_by_hash method to walk over all keys in a hash bucket (located by a hash value). // Use the 'colliding_hash' hash function to intentionally create lots of collisions in a predictable way. typedef hash_map HM; HM hashMap; // Add some numbers to the hashMap. for(int i=0; i<90; i++) { hashMap[i] = i; } // Try to find a hash value that doesn't exist { eastl::pair i = hashMap.find_range_by_hash(1000); EATEST_VERIFY(i.first == hashMap.end()); EATEST_VERIFY(i.second == hashMap.end()); } { int iterations = 0; for(eastl::pair i = hashMap.find_range_by_hash(1); i.first != i.second; i.first++) { int nodeValue = i.first.get_node()->mValue.first; EATEST_VERIFY(nodeValue % 3 == 1); // Verify the hash of the node matches the expected value iterations++; } EATEST_VERIFY(iterations == 30); } { const HM &constHashMap = hashMap; int iterations = 0; for(eastl::pair i = constHashMap.find_range_by_hash(1); i.first != i.second; i.first++) { int nodeValue = i.first.get_node()->mValue.first; EATEST_VERIFY(nodeValue % 3 == 1); // Verify the hash of the node matches the expected value iterations++; } EATEST_VERIFY(iterations == 30); } } // test hashtable holding move-only types #if !defined(EA_COMPILER_MSVC_2013) { struct Movable { Movable() {} Movable(Movable&&) = default; Movable& operator=(Movable&&) = default; Movable(const Movable&) = delete; Movable& operator=(const Movable&) = delete; bool operator==(Movable) const { return true; } struct Hash { size_t operator()(Movable) const { return 0; } }; }; eastl::unordered_set a, b; swap(a,b); } #endif { // hashtable(this_type&& x); // hashtable(this_type&& x, const allocator_type& allocator); // this_type& operator=(this_type&& x); // template // insert_return_type emplace(Args&&... args); // template // iterator emplace_hint(const_iterator position, Args&&... args); // template // Requires that "value_type is constructible from forward

(otherValue)." // insert_return_type insert(P&& otherValue); // iterator insert(const_iterator hint, value_type&& value); // Regression of user reported compiler error in hashtable sfinae mechanism { TestObject::Reset(); eastl::hash_set toSet; toSet.emplace(3, 4, 5); } } { // initializer_list support. // hash_map(std::initializer_list ilist, size_type nBucketCount = 0, const Hash& hashFunction = Hash(), // const Predicate& predicate = Predicate(), const allocator_type& allocator = EASTL_HASH_MAP_DEFAULT_ALLOCATOR) // this_type& operator=(std::initializer_list ilist); // void insert(std::initializer_list ilist); // VS2013 has a known issue when dealing with std::initializer_lists // https://connect.microsoft.com/VisualStudio/feedback/details/792355/compiler-confused-about-whether-to-use-a-initializer-list-assignment-operator #if !defined(EA_COMPILER_NO_INITIALIZER_LISTS) && !(defined(_MSC_VER) && _MSC_VER == 1800) hash_map intHashMap = { {12,12.0}, {13,13.0}, {14,14.0} }; EATEST_VERIFY(intHashMap.size() == 3); EATEST_VERIFY(intHashMap.find(12) != intHashMap.end()); EATEST_VERIFY(intHashMap.find(13) != intHashMap.end()); EATEST_VERIFY(intHashMap.find(14) != intHashMap.end()); intHashMap = { {22,22.0}, {23,23.0}, {24,24.0} }; EATEST_VERIFY(intHashMap.size() == 3); EATEST_VERIFY(intHashMap.find(22) != intHashMap.end()); EATEST_VERIFY(intHashMap.find(23) != intHashMap.end()); EATEST_VERIFY(intHashMap.find(24) != intHashMap.end()); intHashMap.insert({ {42,42.0}, {43,43.0}, {44,44.0} }); EATEST_VERIFY(intHashMap.size() == 6); EATEST_VERIFY(intHashMap.find(42) != intHashMap.end()); EATEST_VERIFY(intHashMap.find(43) != intHashMap.end()); EATEST_VERIFY(intHashMap.find(44) != intHashMap.end()); #endif } // Can't use move semantics with hash_map::operator[] // // GCC has a bug with overloading rvalue and lvalue function templates. // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54425 // // error: 'eastl::pair::pair(T1&&) [with T1 = const int&; T2 = const int&]' cannot be overloaded // error: with 'eastl::pair::pair(const T1&) [with T1 = const int&; T2 = const int&]' #if !defined(EA_COMPILER_GNUC) { EA_DISABLE_VC_WARNING(4626) struct Key { Key() {} Key(Key&&) {} Key(const Key&&) {} bool operator==(const Key&) const { return true; } private: Key(const Key&) {} }; EA_RESTORE_VC_WARNING() struct Hash { std::size_t operator()(const Key&) const { return 0; } }; Key key1, key2; eastl::hash_map hm; hm[eastl::move(key1)] = 12345; EATEST_VERIFY(hm[eastl::move(key2)] == 12345); } #endif { using AllocatorType = CountingAllocator; using String = eastl::basic_string; using StringStringMap = eastl::map, AllocatorType>; using StringStringHashMap = eastl::hash_map, eastl::equal_to, AllocatorType>; AllocatorType::resetCount(); { StringStringHashMap myMap(5); // construct map with 5 buckets, so we don't rehash on insert String key("mykey01234567890000000000000000000000000000"); String value("myvalue01234567890000000000000000000000000000"); AllocatorType::resetCount(); myMap.insert(eastl::make_pair(eastl::move(key), eastl::move(value))); EATEST_VERIFY(AllocatorType::getTotalAllocationCount() == 1); } { StringStringHashMap myMap(5); // construct map with 5 buckets, so we don't rehash on insert String key("mykey01234567890000000000000000000000000000"); String value("myvalue01234567890000000000000000000000000000"); AllocatorType::resetCount(); myMap.emplace(eastl::move(key), eastl::move(value)); EATEST_VERIFY(AllocatorType::getTotalAllocationCount() == 1); } { StringStringMap myMap; String key("mykey01234567890000000000000000000000000000"); String value("myvalue01234567890000000000000000000000000000"); AllocatorType::resetCount(); myMap.insert(eastl::make_pair(eastl::move(key), eastl::move(value))); EATEST_VERIFY(AllocatorType::getTotalAllocationCount() == 1); } { StringStringMap myMap; String key("mykey01234567890000000000000000000000000000"); String value("myvalue01234567890000000000000000000000000000"); AllocatorType::resetCount(); myMap.emplace(eastl::move(key), eastl::move(value)); EATEST_VERIFY(AllocatorType::getTotalAllocationCount() == 1); } } { struct name_equals { bool operator()(const eastl::pair& a, const eastl::pair& b) const { if (a.first != b.first) return false; return strcmp(a.second, b.second) == 0; } }; { int n = 42; const char* pCStrName = "electronic arts"; eastl::hash_map, bool, eastl::hash>, name_equals, eastl::allocator> m_TempNames; m_TempNames[eastl::make_pair(n, pCStrName)] = true; auto isFound = (m_TempNames.find(eastl::make_pair(n, pCStrName)) != m_TempNames.end()); VERIFY(isFound); } } { // User reported regression for code changes limiting hash code generated for non-arithmetic types. { VERIFY(HashTest{}('a') == size_t('a')); } { VERIFY(HashTest{}(42) == 42); } { VERIFY(HashTest{}(42) == 42); } { VERIFY(HashTest{}(42) == 42); } { VERIFY(HashTest{}(short(42)) == 42); } { VERIFY(HashTest{}((unsigned short)42) == 42); } { VERIFY(HashTest{}(42) == 42); } { VERIFY(HashTest{}(42) == 42); } { VERIFY(HashTest{}(42) == 42); } { VERIFY(HashTest{}(42) == 42); } { VERIFY(HashTest{}(42) == 42); } { VERIFY(HashTest{}(42) == 42); } #if defined(EA_HAVE_INT128) && EA_HAVE_INT128 { VERIFY(HashTest{}(UINT128_C(0, 42)) == 42); } #endif } return nErrorCount; }