| /* |
| * Copyright (C) 2011 Google Inc. All rights reserved. |
| * Copyright (C) 2017 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| |
| #include "Counters.h" |
| #include "DeletedAddressOfOperator.h" |
| #include "MoveOnly.h" |
| #include "RefLogger.h" |
| #include "Test.h" |
| #include <string> |
| #include <wtf/HashMap.h> |
| #include <wtf/Ref.h> |
| #include <wtf/text/StringConcatenateNumbers.h> |
| #include <wtf/text/StringHash.h> |
| |
| namespace TestWebKitAPI { |
| |
| typedef WTF::HashMap<int, int> IntHashMap; |
| |
| TEST(WTF_HashMap, HashTableIteratorComparison) |
| { |
| IntHashMap map; |
| map.add(1, 2); |
| ASSERT_TRUE(map.begin() != map.end()); |
| ASSERT_FALSE(map.begin() == map.end()); |
| |
| IntHashMap::const_iterator begin = map.begin(); |
| ASSERT_TRUE(begin == map.begin()); |
| ASSERT_TRUE(map.begin() == begin); |
| ASSERT_TRUE(begin != map.end()); |
| ASSERT_TRUE(map.end() != begin); |
| ASSERT_FALSE(begin != map.begin()); |
| ASSERT_FALSE(map.begin() != begin); |
| ASSERT_FALSE(begin == map.end()); |
| ASSERT_FALSE(map.end() == begin); |
| } |
| |
| struct TestDoubleHashTraits : HashTraits<double> { |
| static const int minimumTableSize = 8; |
| }; |
| |
| typedef HashMap<double, int64_t, DefaultHash<double>::Hash, TestDoubleHashTraits> DoubleHashMap; |
| |
| static int bucketForKey(double key) |
| { |
| return DefaultHash<double>::Hash::hash(key) & (TestDoubleHashTraits::minimumTableSize - 1); |
| } |
| |
| template<typename T> struct BigTableHashTraits : public HashTraits<T> { |
| static const int minimumTableSize = WTF::HashTableCapacityForSize<4096>::value; |
| }; |
| |
| template<typename T> struct ZeroHash : public IntHash<T> { |
| static unsigned hash(const T&) { return 0; } |
| }; |
| |
| TEST(WTF_HashMap, DoubleHashCollisions) |
| { |
| // The "clobber" key here is one that ends up stealing the bucket that the -0 key |
| // originally wants to be in. This makes the 0 and -0 keys collide and the test then |
| // fails unless the FloatHash::equals() implementation can distinguish them. |
| const double clobberKey = 6; |
| const double zeroKey = 0; |
| const double negativeZeroKey = -zeroKey; |
| |
| DoubleHashMap map; |
| |
| map.add(clobberKey, 1); |
| map.add(zeroKey, 2); |
| map.add(negativeZeroKey, 3); |
| |
| ASSERT_EQ(bucketForKey(clobberKey), bucketForKey(negativeZeroKey)); |
| ASSERT_EQ(map.get(clobberKey), 1); |
| ASSERT_EQ(map.get(zeroKey), 2); |
| ASSERT_EQ(map.get(negativeZeroKey), 3); |
| } |
| |
| TEST(WTF_HashMap, MoveOnlyValues) |
| { |
| HashMap<unsigned, MoveOnly> moveOnlyValues; |
| |
| for (size_t i = 0; i < 100; ++i) { |
| MoveOnly moveOnly(i + 1); |
| moveOnlyValues.set(i + 1, WTFMove(moveOnly)); |
| } |
| |
| for (size_t i = 0; i < 100; ++i) { |
| auto it = moveOnlyValues.find(i + 1); |
| ASSERT_FALSE(it == moveOnlyValues.end()); |
| } |
| |
| for (size_t i = 0; i < 50; ++i) |
| ASSERT_EQ(moveOnlyValues.take(i + 1).value(), i + 1); |
| |
| for (size_t i = 50; i < 100; ++i) |
| ASSERT_TRUE(moveOnlyValues.remove(i + 1)); |
| |
| ASSERT_TRUE(moveOnlyValues.isEmpty()); |
| } |
| |
| TEST(WTF_HashMap, MoveOnlyKeys) |
| { |
| HashMap<MoveOnly, unsigned> moveOnlyKeys; |
| |
| for (size_t i = 0; i < 100; ++i) { |
| MoveOnly moveOnly(i + 1); |
| moveOnlyKeys.set(WTFMove(moveOnly), i + 1); |
| } |
| |
| for (size_t i = 0; i < 100; ++i) { |
| auto it = moveOnlyKeys.find(MoveOnly(i + 1)); |
| ASSERT_FALSE(it == moveOnlyKeys.end()); |
| } |
| |
| for (size_t i = 0; i < 100; ++i) |
| ASSERT_FALSE(moveOnlyKeys.add(MoveOnly(i + 1), i + 1).isNewEntry); |
| |
| for (size_t i = 0; i < 100; ++i) |
| ASSERT_TRUE(moveOnlyKeys.remove(MoveOnly(i + 1))); |
| |
| ASSERT_TRUE(moveOnlyKeys.isEmpty()); |
| } |
| |
| TEST(WTF_HashMap, InitializerList) |
| { |
| HashMap<unsigned, std::string> map = { |
| { 1, "one" }, |
| { 2, "two" }, |
| { 3, "three" }, |
| { 4, "four" }, |
| }; |
| |
| EXPECT_EQ(4u, map.size()); |
| |
| EXPECT_EQ("one", map.get(1)); |
| EXPECT_EQ("two", map.get(2)); |
| EXPECT_EQ("three", map.get(3)); |
| EXPECT_EQ("four", map.get(4)); |
| EXPECT_EQ(std::string(), map.get(5)); |
| } |
| |
| TEST(WTF_HashMap, EfficientGetter) |
| { |
| HashMap<unsigned, CopyMoveCounter> map; |
| map.set(1, CopyMoveCounter()); |
| |
| { |
| CopyMoveCounter::TestingScope scope; |
| map.get(1); |
| EXPECT_EQ(0U, CopyMoveCounter::constructionCount); |
| EXPECT_EQ(1U, CopyMoveCounter::copyCount); |
| EXPECT_EQ(0U, CopyMoveCounter::moveCount); |
| } |
| |
| { |
| CopyMoveCounter::TestingScope scope; |
| map.get(2); |
| EXPECT_EQ(1U, CopyMoveCounter::constructionCount); |
| EXPECT_EQ(0U, CopyMoveCounter::copyCount); |
| EXPECT_EQ(1U, CopyMoveCounter::moveCount); |
| } |
| } |
| |
| TEST(WTF_HashMap, UniquePtrKey) |
| { |
| ConstructorDestructorCounter::TestingScope scope; |
| |
| HashMap<std::unique_ptr<ConstructorDestructorCounter>, int> map; |
| |
| auto uniquePtr = makeUnique<ConstructorDestructorCounter>(); |
| map.add(WTFMove(uniquePtr), 2); |
| |
| EXPECT_EQ(1u, ConstructorDestructorCounter::constructionCount); |
| EXPECT_EQ(0u, ConstructorDestructorCounter::destructionCount); |
| |
| map.clear(); |
| |
| EXPECT_EQ(1u, ConstructorDestructorCounter::constructionCount); |
| EXPECT_EQ(1u, ConstructorDestructorCounter::destructionCount); |
| } |
| |
| TEST(WTF_HashMap, UniquePtrKey_CustomDeleter) |
| { |
| ConstructorDestructorCounter::TestingScope constructorDestructorCounterScope; |
| DeleterCounter<ConstructorDestructorCounter>::TestingScope deleterCounterScope; |
| |
| HashMap<std::unique_ptr<ConstructorDestructorCounter, DeleterCounter<ConstructorDestructorCounter>>, int> map; |
| |
| std::unique_ptr<ConstructorDestructorCounter, DeleterCounter<ConstructorDestructorCounter>> uniquePtr(new ConstructorDestructorCounter(), DeleterCounter<ConstructorDestructorCounter>()); |
| map.add(WTFMove(uniquePtr), 2); |
| |
| EXPECT_EQ(1u, ConstructorDestructorCounter::constructionCount); |
| EXPECT_EQ(0u, ConstructorDestructorCounter::destructionCount); |
| |
| EXPECT_EQ(0u, DeleterCounter<ConstructorDestructorCounter>::deleterCount()); |
| |
| map.clear(); |
| |
| EXPECT_EQ(1u, ConstructorDestructorCounter::constructionCount); |
| EXPECT_EQ(1u, ConstructorDestructorCounter::destructionCount); |
| |
| EXPECT_EQ(1u, DeleterCounter<ConstructorDestructorCounter>::deleterCount()); |
| } |
| |
| TEST(WTF_HashMap, UniquePtrKey_FindUsingRawPointer) |
| { |
| HashMap<std::unique_ptr<int>, int> map; |
| |
| auto uniquePtr = makeUniqueWithoutFastMallocCheck<int>(5); |
| int* ptr = uniquePtr.get(); |
| map.add(WTFMove(uniquePtr), 2); |
| |
| auto it = map.find(ptr); |
| ASSERT_TRUE(it != map.end()); |
| EXPECT_EQ(ptr, it->key.get()); |
| EXPECT_EQ(2, it->value); |
| } |
| |
| TEST(WTF_HashMap, UniquePtrKey_ContainsUsingRawPointer) |
| { |
| HashMap<std::unique_ptr<int>, int> map; |
| |
| auto uniquePtr = makeUniqueWithoutFastMallocCheck<int>(5); |
| int* ptr = uniquePtr.get(); |
| map.add(WTFMove(uniquePtr), 2); |
| |
| EXPECT_EQ(true, map.contains(ptr)); |
| } |
| |
| TEST(WTF_HashMap, UniquePtrKey_GetUsingRawPointer) |
| { |
| HashMap<std::unique_ptr<int>, int> map; |
| |
| auto uniquePtr = makeUniqueWithoutFastMallocCheck<int>(5); |
| int* ptr = uniquePtr.get(); |
| map.add(WTFMove(uniquePtr), 2); |
| |
| int value = map.get(ptr); |
| EXPECT_EQ(2, value); |
| } |
| |
| TEST(WTF_HashMap, UniquePtrKey_RemoveUsingRawPointer) |
| { |
| ConstructorDestructorCounter::TestingScope scope; |
| |
| HashMap<std::unique_ptr<ConstructorDestructorCounter>, int> map; |
| |
| auto uniquePtr = makeUnique<ConstructorDestructorCounter>(); |
| ConstructorDestructorCounter* ptr = uniquePtr.get(); |
| map.add(WTFMove(uniquePtr), 2); |
| |
| EXPECT_EQ(1u, ConstructorDestructorCounter::constructionCount); |
| EXPECT_EQ(0u, ConstructorDestructorCounter::destructionCount); |
| |
| bool result = map.remove(ptr); |
| EXPECT_EQ(true, result); |
| |
| EXPECT_EQ(1u, ConstructorDestructorCounter::constructionCount); |
| EXPECT_EQ(1u, ConstructorDestructorCounter::destructionCount); |
| } |
| |
| TEST(WTF_HashMap, UniquePtrKey_TakeUsingRawPointer) |
| { |
| ConstructorDestructorCounter::TestingScope scope; |
| |
| HashMap<std::unique_ptr<ConstructorDestructorCounter>, int> map; |
| |
| auto uniquePtr = makeUnique<ConstructorDestructorCounter>(); |
| ConstructorDestructorCounter* ptr = uniquePtr.get(); |
| map.add(WTFMove(uniquePtr), 2); |
| |
| EXPECT_EQ(1u, ConstructorDestructorCounter::constructionCount); |
| EXPECT_EQ(0u, ConstructorDestructorCounter::destructionCount); |
| |
| int result = map.take(ptr); |
| EXPECT_EQ(2, result); |
| |
| EXPECT_EQ(1u, ConstructorDestructorCounter::constructionCount); |
| EXPECT_EQ(1u, ConstructorDestructorCounter::destructionCount); |
| } |
| |
| TEST(WTF_HashMap, RefPtrKey_Add) |
| { |
| DerivedRefLogger a("a"); |
| |
| HashMap<RefPtr<RefLogger>, int> map; |
| |
| RefPtr<RefLogger> ptr(&a); |
| map.add(ptr, 0); |
| |
| ASSERT_STREQ("ref(a) ref(a) ", takeLogStr().c_str()); |
| } |
| |
| TEST(WTF_HashMap, RefPtrKey_AddUsingRelease) |
| { |
| DerivedRefLogger a("a"); |
| |
| HashMap<RefPtr<RefLogger>, int> map; |
| |
| RefPtr<RefLogger> ptr(&a); |
| map.add(WTFMove(ptr), 0); |
| |
| EXPECT_STREQ("ref(a) ", takeLogStr().c_str()); |
| } |
| |
| TEST(WTF_HashMap, RefPtrKey_AddUsingMove) |
| { |
| DerivedRefLogger a("a"); |
| |
| HashMap<RefPtr<RefLogger>, int> map; |
| |
| RefPtr<RefLogger> ptr(&a); |
| map.add(WTFMove(ptr), 0); |
| |
| EXPECT_STREQ("ref(a) ", takeLogStr().c_str()); |
| } |
| |
| TEST(WTF_HashMap, RefPtrKey_AddUsingRaw) |
| { |
| DerivedRefLogger a("a"); |
| |
| HashMap<RefPtr<RefLogger>, int> map; |
| |
| RefPtr<RefLogger> ptr(&a); |
| map.add(ptr.get(), 0); |
| |
| EXPECT_STREQ("ref(a) ref(a) ", takeLogStr().c_str()); |
| } |
| |
| TEST(WTF_HashMap, RefPtrKey_AddKeyAlreadyPresent) |
| { |
| DerivedRefLogger a("a"); |
| |
| HashMap<RefPtr<RefLogger>, int> map; |
| |
| { |
| RefPtr<RefLogger> ptr(&a); |
| map.add(ptr, 0); |
| } |
| |
| EXPECT_STREQ("ref(a) ref(a) deref(a) ", takeLogStr().c_str()); |
| |
| { |
| RefPtr<RefLogger> ptr2(&a); |
| auto addResult = map.add(ptr2, 0); |
| EXPECT_FALSE(addResult.isNewEntry); |
| } |
| |
| EXPECT_STREQ("ref(a) deref(a) ", takeLogStr().c_str()); |
| } |
| |
| TEST(WTF_HashMap, RefPtrKey_AddUsingReleaseKeyAlreadyPresent) |
| { |
| DerivedRefLogger a("a"); |
| |
| HashMap<RefPtr<RefLogger>, int> map; |
| |
| { |
| RefPtr<RefLogger> ptr(&a); |
| map.add(ptr, 0); |
| } |
| |
| EXPECT_STREQ("ref(a) ref(a) deref(a) ", takeLogStr().c_str()); |
| |
| { |
| RefPtr<RefLogger> ptr2(&a); |
| auto addResult = map.add(WTFMove(ptr2), 0); |
| EXPECT_FALSE(addResult.isNewEntry); |
| } |
| |
| EXPECT_STREQ("ref(a) deref(a) ", takeLogStr().c_str()); |
| } |
| |
| TEST(WTF_HashMap, RefPtrKey_AddUsingMoveKeyAlreadyPresent) |
| { |
| DerivedRefLogger a("a"); |
| |
| HashMap<RefPtr<RefLogger>, int> map; |
| |
| { |
| RefPtr<RefLogger> ptr(&a); |
| map.add(ptr, 0); |
| } |
| |
| EXPECT_STREQ("ref(a) ref(a) deref(a) ", takeLogStr().c_str()); |
| |
| { |
| RefPtr<RefLogger> ptr2(&a); |
| auto addResult = map.add(WTFMove(ptr2), 0); |
| EXPECT_FALSE(addResult.isNewEntry); |
| } |
| |
| EXPECT_STREQ("ref(a) deref(a) ", takeLogStr().c_str()); |
| } |
| |
| TEST(WTF_HashMap, RefPtrKey_Set) |
| { |
| DerivedRefLogger a("a"); |
| |
| HashMap<RefPtr<RefLogger>, int> map; |
| |
| RefPtr<RefLogger> ptr(&a); |
| map.set(ptr, 0); |
| |
| ASSERT_STREQ("ref(a) ref(a) ", takeLogStr().c_str()); |
| } |
| |
| TEST(WTF_HashMap, RefPtrKey_SetUsingRelease) |
| { |
| DerivedRefLogger a("a"); |
| |
| HashMap<RefPtr<RefLogger>, int> map; |
| |
| RefPtr<RefLogger> ptr(&a); |
| map.set(WTFMove(ptr), 0); |
| |
| EXPECT_STREQ("ref(a) ", takeLogStr().c_str()); |
| } |
| |
| |
| TEST(WTF_HashMap, RefPtrKey_SetUsingMove) |
| { |
| DerivedRefLogger a("a"); |
| |
| HashMap<RefPtr<RefLogger>, int> map; |
| |
| RefPtr<RefLogger> ptr(&a); |
| map.set(WTFMove(ptr), 0); |
| |
| EXPECT_STREQ("ref(a) ", takeLogStr().c_str()); |
| } |
| |
| TEST(WTF_HashMap, RefPtrKey_SetUsingRaw) |
| { |
| DerivedRefLogger a("a"); |
| |
| HashMap<RefPtr<RefLogger>, int> map; |
| |
| RefPtr<RefLogger> ptr(&a); |
| map.set(ptr.get(), 0); |
| |
| EXPECT_STREQ("ref(a) ref(a) ", takeLogStr().c_str()); |
| } |
| |
| TEST(WTF_HashMap, RefPtrKey_SetKeyAlreadyPresent) |
| { |
| DerivedRefLogger a("a"); |
| |
| HashMap<RefPtr<RefLogger>, int> map; |
| |
| RefPtr<RefLogger> ptr(&a); |
| map.set(ptr, 0); |
| |
| EXPECT_STREQ("ref(a) ref(a) ", takeLogStr().c_str()); |
| |
| { |
| RefPtr<RefLogger> ptr2(&a); |
| auto addResult = map.set(ptr2, 1); |
| EXPECT_FALSE(addResult.isNewEntry); |
| EXPECT_EQ(1, map.get(ptr.get())); |
| } |
| |
| EXPECT_STREQ("ref(a) deref(a) ", takeLogStr().c_str()); |
| } |
| |
| TEST(WTF_HashMap, RefPtrKey_SetUsingReleaseKeyAlreadyPresent) |
| { |
| DerivedRefLogger a("a"); |
| |
| HashMap<RefPtr<RefLogger>, int> map; |
| |
| RefPtr<RefLogger> ptr(&a); |
| map.set(ptr, 0); |
| |
| EXPECT_STREQ("ref(a) ref(a) ", takeLogStr().c_str()); |
| |
| { |
| RefPtr<RefLogger> ptr2(&a); |
| auto addResult = map.set(WTFMove(ptr2), 1); |
| EXPECT_FALSE(addResult.isNewEntry); |
| EXPECT_EQ(1, map.get(ptr.get())); |
| } |
| |
| EXPECT_STREQ("ref(a) deref(a) ", takeLogStr().c_str()); |
| } |
| |
| TEST(WTF_HashMap, RefPtrKey_SetUsingMoveKeyAlreadyPresent) |
| { |
| DerivedRefLogger a("a"); |
| |
| HashMap<RefPtr<RefLogger>, int> map; |
| |
| RefPtr<RefLogger> ptr(&a); |
| map.set(ptr, 0); |
| |
| EXPECT_STREQ("ref(a) ref(a) ", takeLogStr().c_str()); |
| |
| { |
| RefPtr<RefLogger> ptr2(&a); |
| auto addResult = map.set(WTFMove(ptr2), 1); |
| EXPECT_FALSE(addResult.isNewEntry); |
| EXPECT_EQ(1, map.get(ptr.get())); |
| } |
| |
| EXPECT_STREQ("ref(a) deref(a) ", takeLogStr().c_str()); |
| } |
| |
| TEST(WTF_HashMap, Ensure) |
| { |
| HashMap<unsigned, unsigned> map; |
| { |
| auto addResult = map.ensure(1, [] { return 1; }); |
| EXPECT_EQ(1u, addResult.iterator->value); |
| EXPECT_EQ(1u, addResult.iterator->key); |
| EXPECT_TRUE(addResult.isNewEntry); |
| auto addResult2 = map.ensure(1, [] { return 2; }); |
| EXPECT_EQ(1u, addResult2.iterator->value); |
| EXPECT_EQ(1u, addResult2.iterator->key); |
| EXPECT_FALSE(addResult2.isNewEntry); |
| } |
| } |
| |
| TEST(WTF_HashMap, Ensure_MoveOnlyValues) |
| { |
| HashMap<unsigned, MoveOnly> moveOnlyValues; |
| { |
| auto addResult = moveOnlyValues.ensure(1, [] { return MoveOnly(1); }); |
| EXPECT_EQ(1u, addResult.iterator->value.value()); |
| EXPECT_EQ(1u, addResult.iterator->key); |
| EXPECT_TRUE(addResult.isNewEntry); |
| auto addResult2 = moveOnlyValues.ensure(1, [] { return MoveOnly(2); }); |
| EXPECT_EQ(1u, addResult2.iterator->value.value()); |
| EXPECT_EQ(1u, addResult2.iterator->key); |
| EXPECT_FALSE(addResult2.isNewEntry); |
| } |
| } |
| |
| TEST(WTF_HashMap, Ensure_UniquePointer) |
| { |
| HashMap<unsigned, std::unique_ptr<unsigned>> map; |
| { |
| auto addResult = map.ensure(1, [] { return makeUniqueWithoutFastMallocCheck<unsigned>(1); }); |
| EXPECT_EQ(1u, *map.get(1)); |
| EXPECT_EQ(1u, *addResult.iterator->value.get()); |
| EXPECT_EQ(1u, addResult.iterator->key); |
| EXPECT_TRUE(addResult.isNewEntry); |
| auto addResult2 = map.ensure(1, [] { return makeUniqueWithoutFastMallocCheck<unsigned>(2); }); |
| EXPECT_EQ(1u, *map.get(1)); |
| EXPECT_EQ(1u, *addResult2.iterator->value.get()); |
| EXPECT_EQ(1u, addResult2.iterator->key); |
| EXPECT_FALSE(addResult2.isNewEntry); |
| } |
| } |
| |
| TEST(WTF_HashMap, Ensure_RefPtr) |
| { |
| DerivedRefLogger a("a"); |
| |
| HashMap<unsigned, RefPtr<RefLogger>> map; |
| |
| map.ensure(1, [&] { return RefPtr<RefLogger>(&a); }); |
| EXPECT_STREQ("ref(a) ", takeLogStr().c_str()); |
| |
| map.ensure(1, [&] { return RefPtr<RefLogger>(&a); }); |
| EXPECT_STREQ("", takeLogStr().c_str()); |
| } |
| |
| class ObjectWithRefLogger { |
| WTF_MAKE_FAST_ALLOCATED; |
| public: |
| ObjectWithRefLogger(Ref<RefLogger>&& logger) |
| : m_logger(WTFMove(logger)) |
| { |
| } |
| |
| Ref<RefLogger> m_logger; |
| }; |
| |
| |
| void testMovingUsingEnsure(Ref<RefLogger>&& logger) |
| { |
| HashMap<unsigned, std::unique_ptr<ObjectWithRefLogger>> map; |
| |
| map.ensure(1, [&] { return makeUnique<ObjectWithRefLogger>(WTFMove(logger)); }); |
| } |
| |
| void testMovingUsingAdd(Ref<RefLogger>&& logger) |
| { |
| HashMap<unsigned, std::unique_ptr<ObjectWithRefLogger>> map; |
| |
| auto& slot = map.add(1, nullptr).iterator->value; |
| slot = makeUnique<ObjectWithRefLogger>(WTFMove(logger)); |
| } |
| |
| TEST(WTF_HashMap, Ensure_LambdasCapturingByReference) |
| { |
| { |
| DerivedRefLogger a("a"); |
| Ref<RefLogger> ref(a); |
| testMovingUsingEnsure(WTFMove(ref)); |
| |
| EXPECT_STREQ("ref(a) deref(a) ", takeLogStr().c_str()); |
| } |
| |
| { |
| DerivedRefLogger a("a"); |
| Ref<RefLogger> ref(a); |
| testMovingUsingAdd(WTFMove(ref)); |
| |
| EXPECT_STREQ("ref(a) deref(a) ", takeLogStr().c_str()); |
| } |
| } |
| |
| |
| TEST(WTF_HashMap, ValueIsDestructedOnRemove) |
| { |
| struct DestructorObserver { |
| DestructorObserver() = default; |
| |
| DestructorObserver(bool* destructed) |
| : destructed(destructed) |
| { |
| } |
| |
| ~DestructorObserver() |
| { |
| if (destructed) |
| *destructed = true; |
| } |
| |
| DestructorObserver(DestructorObserver&& other) |
| : destructed(other.destructed) |
| { |
| other.destructed = nullptr; |
| } |
| |
| DestructorObserver& operator=(DestructorObserver&& other) |
| { |
| destructed = other.destructed; |
| other.destructed = nullptr; |
| return *this; |
| } |
| |
| bool* destructed { nullptr }; |
| }; |
| |
| HashMap<int, DestructorObserver> map; |
| |
| bool destructed = false; |
| map.add(5, DestructorObserver { &destructed }); |
| |
| EXPECT_FALSE(destructed); |
| |
| bool removeResult = map.remove(5); |
| |
| EXPECT_TRUE(removeResult); |
| EXPECT_TRUE(destructed); |
| } |
| |
| struct DerefObserver { |
| WTF_MAKE_STRUCT_FAST_ALLOCATED; |
| NEVER_INLINE void ref() |
| { |
| ++count; |
| } |
| NEVER_INLINE void deref() |
| { |
| --count; |
| observedBucket = bucketAddress->get(); |
| } |
| unsigned count { 1 }; |
| const RefPtr<DerefObserver>* bucketAddress { nullptr }; |
| const DerefObserver* observedBucket { nullptr }; |
| }; |
| |
| TEST(WTF_HashMap, RefPtrNotZeroedBeforeDeref) |
| { |
| auto observer = makeUnique<DerefObserver>(); |
| |
| HashMap<RefPtr<DerefObserver>, int> map; |
| map.add(adoptRef(observer.get()), 5); |
| |
| auto iterator = map.find(observer.get()); |
| EXPECT_TRUE(iterator != map.end()); |
| |
| observer->bucketAddress = &iterator->key; |
| |
| EXPECT_TRUE(observer->observedBucket == nullptr); |
| EXPECT_TRUE(map.remove(observer.get())); |
| |
| // It if fine to either leave the old value intact at deletion or already set it to the deleted |
| // value. |
| // A zero would be a incorrect outcome as it would mean we nulled the bucket before an opaque |
| // call. |
| EXPECT_TRUE(observer->observedBucket == observer.get() || observer->observedBucket == RefPtr<DerefObserver>::hashTableDeletedValue()); |
| EXPECT_EQ(observer->count, 0u); |
| } |
| |
| TEST(WTF_HashMap, Ref_Key) |
| { |
| { |
| RefLogger a("a"); |
| |
| HashMap<Ref<RefLogger>, int> map; |
| |
| Ref<RefLogger> ref(a); |
| map.add(WTFMove(ref), 1); |
| } |
| |
| ASSERT_STREQ("ref(a) deref(a) ", takeLogStr().c_str()); |
| |
| { |
| RefLogger a("a"); |
| |
| HashMap<Ref<RefLogger>, int> map; |
| |
| Ref<RefLogger> ref(a); |
| map.set(WTFMove(ref), 1); |
| } |
| |
| ASSERT_STREQ("ref(a) deref(a) ", takeLogStr().c_str()); |
| |
| { |
| RefLogger a("a"); |
| |
| HashMap<Ref<RefLogger>, int> map; |
| |
| Ref<RefLogger> refA(a); |
| map.add(WTFMove(refA), 1); |
| |
| Ref<RefLogger> refA2(a); |
| map.set(WTFMove(refA2), 1); |
| } |
| |
| ASSERT_STREQ("ref(a) ref(a) deref(a) deref(a) ", takeLogStr().c_str()); |
| |
| { |
| RefLogger a("a"); |
| |
| HashMap<Ref<RefLogger>, int> map; |
| |
| Ref<RefLogger> ref(a); |
| map.ensure(WTFMove(ref), []() { |
| return 1; |
| }); |
| } |
| |
| ASSERT_STREQ("ref(a) deref(a) ", takeLogStr().c_str()); |
| |
| { |
| RefLogger a("a"); |
| |
| HashMap<Ref<RefLogger>, int> map; |
| |
| Ref<RefLogger> ref(a); |
| map.add(WTFMove(ref), 1); |
| |
| auto it = map.find(&a); |
| ASSERT_TRUE(it != map.end()); |
| |
| ASSERT_EQ(it->key.ptr(), &a); |
| ASSERT_EQ(it->value, 1); |
| } |
| |
| ASSERT_STREQ("ref(a) deref(a) ", takeLogStr().c_str()); |
| |
| { |
| RefLogger a("a"); |
| |
| HashMap<Ref<RefLogger>, int> map; |
| |
| Ref<RefLogger> ref(a); |
| map.add(WTFMove(ref), 1); |
| |
| map.remove(&a); |
| } |
| |
| ASSERT_STREQ("ref(a) deref(a) ", takeLogStr().c_str()); |
| |
| { |
| RefLogger a("a"); |
| |
| HashMap<Ref<RefLogger>, int> map; |
| |
| Ref<RefLogger> ref(a); |
| map.add(WTFMove(ref), 1); |
| |
| int i = map.take(&a); |
| ASSERT_EQ(i, 1); |
| } |
| |
| ASSERT_STREQ("ref(a) deref(a) ", takeLogStr().c_str()); |
| |
| { |
| HashMap<Ref<RefLogger>, int> map; |
| for (int i = 0; i < 64; ++i) { |
| // FIXME: All of these RefLogger objects leak. No big deal for a test I guess. |
| Ref<RefLogger> ref = adoptRef(*new RefLogger("a")); |
| auto* pointer = ref.ptr(); |
| map.add(WTFMove(ref), i + 1); |
| ASSERT_TRUE(map.contains(pointer)); |
| } |
| } |
| |
| ASSERT_STREQ("deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) ", takeLogStr().c_str()); |
| } |
| |
| TEST(WTF_HashMap, Ref_Value) |
| { |
| { |
| RefLogger a("a"); |
| |
| HashMap<int, Ref<RefLogger>> map; |
| |
| Ref<RefLogger> ref(a); |
| map.add(1, WTFMove(ref)); |
| } |
| |
| ASSERT_STREQ("ref(a) deref(a) ", takeLogStr().c_str()); |
| |
| { |
| RefLogger a("a"); |
| |
| HashMap<int, Ref<RefLogger>> map; |
| |
| Ref<RefLogger> ref(a); |
| map.set(1, WTFMove(ref)); |
| } |
| |
| ASSERT_STREQ("ref(a) deref(a) ", takeLogStr().c_str()); |
| |
| { |
| RefLogger a("a"); |
| RefLogger b("b"); |
| |
| HashMap<int, Ref<RefLogger>> map; |
| |
| Ref<RefLogger> refA(a); |
| map.add(1, WTFMove(refA)); |
| |
| Ref<RefLogger> refB(b); |
| map.set(1, WTFMove(refB)); |
| } |
| |
| ASSERT_STREQ("ref(a) ref(b) deref(a) deref(b) ", takeLogStr().c_str()); |
| |
| { |
| RefLogger a("a"); |
| |
| HashMap<int, Ref<RefLogger>> map; |
| |
| Ref<RefLogger> ref(a); |
| map.add(1, WTFMove(ref)); |
| |
| auto aGet = map.get(1); |
| ASSERT_EQ(aGet, &a); |
| } |
| |
| ASSERT_STREQ("ref(a) deref(a) ", takeLogStr().c_str()); |
| |
| { |
| HashMap<int, Ref<RefLogger>> map; |
| |
| auto emptyGet = map.get(1); |
| ASSERT_TRUE(emptyGet == nullptr); |
| } |
| |
| { |
| RefLogger a("a"); |
| |
| HashMap<int, Ref<RefLogger>> map; |
| |
| Ref<RefLogger> ref(a); |
| map.add(1, WTFMove(ref)); |
| |
| auto aOut = map.take(1); |
| ASSERT_TRUE(static_cast<bool>(aOut)); |
| ASSERT_EQ(&a, aOut.value().ptr()); |
| } |
| |
| ASSERT_STREQ("ref(a) deref(a) ", takeLogStr().c_str()); |
| |
| { |
| HashMap<int, Ref<RefLogger>> map; |
| |
| auto emptyTake = map.take(1); |
| ASSERT_FALSE(static_cast<bool>(emptyTake)); |
| } |
| |
| { |
| RefLogger a("a"); |
| |
| HashMap<int, Ref<RefLogger>> map; |
| |
| Ref<RefLogger> ref(a); |
| map.add(1, WTFMove(ref)); |
| map.remove(1); |
| } |
| |
| ASSERT_STREQ("ref(a) deref(a) ", takeLogStr().c_str()); |
| |
| { |
| RefLogger a("a"); |
| |
| HashMap<int, Ref<RefLogger>> map; |
| |
| map.ensure(1, [&]() mutable { |
| Ref<RefLogger> ref(a); |
| return ref; |
| }); |
| } |
| |
| ASSERT_STREQ("ref(a) deref(a) ", takeLogStr().c_str()); |
| |
| { |
| HashMap<int, Ref<RefLogger>> map; |
| for (int i = 0; i < 64; ++i) { |
| // FIXME: All of these RefLogger objects leak. No big deal for a test I guess. |
| Ref<RefLogger> ref = adoptRef(*new RefLogger("a")); |
| map.add(i + 1, WTFMove(ref)); |
| ASSERT_TRUE(map.contains(i + 1)); |
| } |
| } |
| |
| ASSERT_STREQ("deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) deref(a) ", takeLogStr().c_str()); |
| } |
| |
| TEST(WTF_HashMap, DeletedAddressOfOperator) |
| { |
| HashMap<int, DeletedAddressOfOperator> map1; |
| for (auto& value : map1.values()) |
| (void)value; |
| } |
| |
| TEST(WTF_HashMap, RefMappedToNonZeroEmptyValue) |
| { |
| class Value { |
| public: |
| Value() = default; |
| Value(Value&&) = default; |
| Value(const Value&) = default; |
| Value& operator=(Value&&) = default; |
| |
| Value(int32_t f) |
| : m_field(f) |
| { } |
| |
| int32_t field() { return m_field; } |
| |
| private: |
| int32_t m_field { 0xbadbeef }; |
| }; |
| |
| class Key : public RefCounted<Key> { |
| Key() = default; |
| public: |
| static Ref<Key> create() { return adoptRef(*new Key); } |
| }; |
| |
| static_assert(!WTF::HashTraits<Value>::emptyValueIsZero, ""); |
| |
| HashMap<Ref<Key>, Value> map; |
| Vector<std::pair<Ref<Key>, int32_t>> vectorMap; |
| |
| for (int32_t i = 0; i < 160; ++i) { |
| Ref<Key> key = Key::create(); |
| map.add(Ref<Key>(key.get()), Value { i }); |
| vectorMap.append({ WTFMove(key), i }); |
| } |
| |
| for (auto& pair : vectorMap) |
| ASSERT_EQ(pair.second, map.get(pair.first).field()); |
| |
| for (auto& pair : vectorMap) |
| ASSERT_TRUE(map.remove(pair.first)); |
| } |
| |
| TEST(WTF_HashMap, Random_Empty) |
| { |
| HashMap<unsigned, unsigned> map; |
| |
| auto result = map.random(); |
| ASSERT_EQ(result, map.end()); |
| } |
| |
| TEST(WTF_HashMap, Random_WrapAround) |
| { |
| HashMap<unsigned, unsigned, ZeroHash<unsigned>, BigTableHashTraits<unsigned>> map; |
| map.add(1, 1); |
| |
| auto result = map.random(); |
| ASSERT_EQ(result, map.begin()); |
| } |
| |
| TEST(WTF_HashMap, Random_IsEvenlyDistributed) |
| { |
| HashMap<unsigned, unsigned, DefaultHash<unsigned>::Hash, WTF::UnsignedWithZeroKeyHashTraits<unsigned>> map; |
| map.add(0, 0); |
| map.add(1, 1); |
| |
| unsigned zeros = 0; |
| unsigned ones = 0; |
| |
| for (unsigned i = 0; i < 1000; ++i) { |
| auto it = map.random(); |
| if (!it->value) |
| ++zeros; |
| else { |
| ASSERT_EQ(it->value, 1u); |
| ++ones; |
| } |
| } |
| |
| ASSERT_EQ(zeros + ones, 1000u); |
| ASSERT_LE(zeros, 600u); |
| ASSERT_LE(ones, 600u); |
| } |
| |
| TEST(WTF_HashMap, ReserveInitialCapacity) |
| { |
| HashMap<String, String> map; |
| EXPECT_EQ(0u, map.size()); |
| map.reserveInitialCapacity(9999); |
| EXPECT_EQ(0u, map.size()); |
| for (int i = 0; i < 9999; ++i) |
| map.add(makeString("foo", i), makeString("bar", i)); |
| EXPECT_EQ(9999u, map.size()); |
| EXPECT_TRUE(map.contains("foo3"_str)); |
| EXPECT_STREQ("bar3", map.get("foo3"_str).utf8().data()); |
| for (int i = 0; i < 9999; ++i) |
| map.add(makeString("excess", i), makeString("baz", i)); |
| EXPECT_EQ(9999u + 9999u, map.size()); |
| for (int i = 0; i < 9999; ++i) |
| EXPECT_TRUE(map.remove(makeString("foo", i))); |
| EXPECT_EQ(9999u, map.size()); |
| EXPECT_STREQ("baz3", map.get("excess3"_str).utf8().data()); |
| for (int i = 0; i < 9999; ++i) |
| EXPECT_TRUE(map.remove(makeString("excess", i))); |
| EXPECT_EQ(0u, map.size()); |
| |
| HashMap<String, String> map2; |
| map2.reserveInitialCapacity(9999); |
| EXPECT_FALSE(map2.remove("foo1"_s)); |
| for (int i = 0; i < 2000; ++i) |
| map2.add(makeString("foo", i), makeString("bar", i)); |
| EXPECT_EQ(2000u, map2.size()); |
| for (int i = 0; i < 2000; ++i) |
| EXPECT_TRUE(map2.remove(makeString("foo", i))); |
| EXPECT_EQ(0u, map2.size()); |
| } |
| |
| TEST(WTF_HashMap, Random_IsEvenlyDistributedAfterRemove) |
| { |
| for (size_t tableSize = 2; tableSize <= 2 * 6; ++tableSize) { // Our hash tables shrink at a load factor of 1 / 6. |
| HashMap<unsigned, unsigned, DefaultHash<unsigned>::Hash, WTF::UnsignedWithZeroKeyHashTraits<unsigned>> map; |
| for (size_t i = 0; i < tableSize; ++i) |
| map.add(i, i); |
| for (size_t i = 2; i < tableSize; ++i) |
| map.remove(i); |
| |
| unsigned zeros = 0; |
| unsigned ones = 0; |
| |
| for (unsigned i = 0; i < 1000; ++i) { |
| auto it = map.random(); |
| if (!it->value) |
| ++zeros; |
| else { |
| ASSERT_EQ(it->value, 1u); |
| ++ones; |
| } |
| } |
| |
| ASSERT_EQ(zeros + ones, 1000u); |
| ASSERT_LE(zeros, 600u); |
| ASSERT_LE(ones, 600u); |
| } |
| } |
| |
| } // namespace TestWebKitAPI |