// Copyright Epic Games, Inc. All Rights Reserved. #include "HashMap.h" #include "Catch2Includes.h" #include "ObjectLifetimeHelper.h" #include using HashMapIntToInt = AutoRTFM::THashMap; using HashMapIntToObj = AutoRTFM::THashMap; using HashMapObjToInt = AutoRTFM::THashMap; using HashMapObjToObj = AutoRTFM::THashMap; TEMPLATE_TEST_CASE("HashMap", "", HashMapIntToInt, HashMapIntToObj, HashMapObjToInt, HashMapObjToObj) { REQUIRE(AutoRTFMTestUtils::FObjectLifetimeHelper::ConstructorCalls == 0); REQUIRE(AutoRTFMTestUtils::FObjectLifetimeHelper::DestructorCalls == 0); using MapType = TestType; using KeyType = typename TestType::Key; using ValueType = typename TestType::Value; auto Check = [&](MapType& Map, const std::unordered_map& Expected) { // Check the reported count is as expected. REQUIRE(Map.Num() == Expected.size()); REQUIRE(Map.IsEmpty() == Expected.empty()); // Check the non-const begin() / end(). { size_t I = 0; for (AutoRTFM::TKeyAndValue& Item : Map) { I++; auto ExpectedIt = Expected.find(Item.Key); REQUIRE(ExpectedIt != Expected.end()); REQUIRE(ExpectedIt->second == Item.Value); // Check mutability of the value Item.Value = 99; REQUIRE(Item.Value == 99); Item.Value = ExpectedIt->second; } REQUIRE(I == Expected.size()); } // Check the const begin() / end(). { size_t I = 0; for (const AutoRTFM::TKeyAndValue& Item : const_cast(Map)) { I++; auto ExpectedIt = Expected.find(Item.Key); REQUIRE(ExpectedIt != Expected.end()); REQUIRE(ExpectedIt->second == Item.Value); } REQUIRE(I == Expected.size()); } // Check Contains() & Find() for all elements in Expected. { for (auto It : Expected) { REQUIRE(Map.Contains(It.first)); ValueType* FindValue = Map.Find(It.first); REQUIRE(FindValue); REQUIRE(*FindValue == It.second); } } }; SECTION("Add") { MapType Map; Check(Map, {}); Map.Add(10, 100); Check(Map, {{10, 100}}); Map.Add(20, 200); Check(Map, {{10, 100}, {20, 200}}); Map.Add(30, 300); Check(Map, {{10, 100}, {20, 200}, {30, 300}}); Map.Add(20, 123); Check(Map, {{10, 100}, {20, 123}, {30, 300}}); Map.Add(10, 321); Check(Map, {{10, 321}, {20, 123}, {30, 300}}); Map.Add(40, 400); Check(Map, {{10, 321}, {20, 123}, {30, 300}, {40, 400}}); } SECTION("Find") { MapType Map; Check(Map, {}); Map.Add(10, 100); Map.Add(20, 200); Map.Add(30, 300); Check(Map, {{10, 100}, {20, 200}, {30, 300}}); { ValueType* Value = Map.Find(KeyType{0}); REQUIRE(!Value); } { ValueType* Value = Map.Find(KeyType{10}); REQUIRE((Value && *Value == 100)); } { ValueType* Value = Map.Find(KeyType{20}); REQUIRE((Value && *Value == 200)); } { ValueType* Value = Map.Find(KeyType{30}); REQUIRE((Value && *Value == 300)); } { ValueType* Value = Map.Find(KeyType{40}); REQUIRE(!Value); } Check(Map, {{10, 100}, {20, 200}, {30, 300}}); } SECTION("FindOrAdd") { MapType Map; Check(Map, {}); Map.Add(10, 100); Map.Add(20, 200); Map.Add(30, 300); Check(Map, {{10, 100}, {20, 200}, {30, 300}}); REQUIRE(Map.FindOrAdd(0) == 0); Check(Map, {{0, 0}, {10, 100}, {20, 200}, {30, 300}}); REQUIRE(Map.FindOrAdd(10) == 100); Check(Map, {{0, 0}, {10, 100}, {20, 200}, {30, 300}}); REQUIRE(Map.FindOrAdd(20) == 200); Check(Map, {{0, 0}, {10, 100}, {20, 200}, {30, 300}}); REQUIRE(Map.FindOrAdd(30) == 300); Check(Map, {{0, 0}, {10, 100}, {20, 200}, {30, 300}}); REQUIRE(Map.FindOrAdd(40) == 0); Check(Map, {{0, 0}, {10, 100}, {20, 200}, {30, 300}, {40, 0}}); } SECTION("Remove") { MapType Map; Check(Map, {}); Map.Add(10, 100); Map.Add(20, 200); Map.Add(30, 300); Check(Map, {{10, 100}, {20, 200}, {30, 300}}); Map.Remove(0); Check(Map, {{10, 100}, {20, 200}, {30, 300}}); Map.Remove(10); Check(Map, {{20, 200}, {30, 300}}); Map.Remove(20); Check(Map, {{30, 300}}); Map.Remove(30); Check(Map, {}); Map.Remove(40); Check(Map, {}); } SECTION("Contains") { MapType Map; Check(Map, {}); Map.Add(10, 100); Map.Add(20, 200); Map.Add(30, 300); Check(Map, {{10, 100}, {20, 200}, {30, 300}}); REQUIRE(Map.Contains(0) == false); REQUIRE(Map.Contains(10) == true); REQUIRE(Map.Contains(20) == true); REQUIRE(Map.Contains(30) == true); REQUIRE(Map.Contains(40) == false); Check(Map, {{10, 100}, {20, 200}, {30, 300}}); } SECTION("Empty") { MapType Map; Check(Map, {}); Map.Empty(); Check(Map, {}); Map.Add(10, 100); Map.Add(20, 200); Map.Add(30, 300); Check(Map, {{10, 100}, {20, 200}, {30, 300}}); Map.Empty(); Check(Map, {}); } SECTION("Reset") { MapType Map; Check(Map, {}); Map.Reset(); Check(Map, {}); Map.Add(10, 100); Map.Add(20, 200); Map.Add(30, 300); Check(Map, {{10, 100}, {20, 200}, {30, 300}}); Map.Reset(); Check(Map, {}); } SECTION("Copy Construct") { SECTION("Empty") { MapType Source; MapType Target(Source); Check(Source, {}); Check(Target, {}); } SECTION("Non-empty") { MapType Source; Source.Add(10, 100); Source.Add(20, 200); Source.Add(30, 300); MapType Target(Source); Check(Source, {{10, 100}, {20, 200}, {30, 300}}); Check(Target, {{10, 100}, {20, 200}, {30, 300}}); } } SECTION("Move Construct") { SECTION("Empty") { MapType Source; MapType Target(std::move(Source)); Check(Source, {}); Check(Target, {}); } SECTION("Non-empty") { MapType Source; Source.Add(10, 100); Source.Add(20, 200); Source.Add(30, 300); MapType Target(std::move(Source)); Check(Source, {}); Check(Target, {{10, 100}, {20, 200}, {30, 300}}); } } SECTION("Copy Assign") { SECTION("Empty") { MapType Source; MapType Target; Target = Source; Check(Source, {}); Check(Target, {}); } SECTION("Non-empty") { MapType Source; Source.Add(10, 100); Source.Add(20, 200); Source.Add(30, 300); MapType Target; Target = Source; Check(Source, {{10, 100}, {20, 200}, {30, 300}}); Check(Target, {{10, 100}, {20, 200}, {30, 300}}); } } SECTION("Copy Assign Self") { SECTION("Empty") { MapType Map; MapType& Alias = Map; Map = Alias; Check(Map, {}); } SECTION("Non-empty") { MapType Map; MapType& Alias = Map; Map.Add(10, 100); Map.Add(20, 200); Map.Add(30, 300); Map = Alias; Check(Map, {{10, 100}, {20, 200}, {30, 300}}); } } SECTION("Move Assign") { SECTION("Empty") { MapType Source; MapType Target; Target = std::move(Source); Check(Source, {}); Check(Target, {}); } SECTION("Non-empty") { MapType Source; Source.Add(10, 100); Source.Add(20, 200); Source.Add(30, 300); MapType Target; Target = std::move(Source); Check(Source, {}); Check(Target, {{10, 100}, {20, 200}, {30, 300}}); } } SECTION("Move Assign Self") { SECTION("Empty") { MapType Map; MapType& Alias = Map; Map = std::move(Alias); Check(Map, {}); } SECTION("Non-empty") { MapType Map; MapType& Alias = Map; Map.Add(10, 100); Map.Add(20, 200); Map.Add(30, 300); Map = std::move(Alias); Check(Map, {{10, 100}, {20, 200}, {30, 300}}); } } SECTION("Soak") { MapType Map; std::unordered_map Expected; for (int I = 0; I < 10000; I++) { int Mod100 = (I * 15485863) % 100; KeyType Key = KeyType{(I * 804889) % 1000}; switch (Mod100) { case 0: { ValueType* Value = Map.Find(Key); if (Value != nullptr) { auto ExpectedIt = Expected.find(Key); REQUIRE(ExpectedIt != Expected.end()); REQUIRE(*Value == ExpectedIt->second); } else { REQUIRE(Expected.find(Key) == Expected.end()); } break; } case 1: { ValueType Value = Map.FindOrAdd(Key); auto ExpectedIt = Expected.find(Key); if (ExpectedIt != Expected.end()) { REQUIRE(Value == ExpectedIt->second); } else { REQUIRE(Value == ValueType{}); Expected[Key] = ValueType{}; } break; } case 2: { Map.Remove(Key); Expected.erase(Key); break; } case 3: { REQUIRE(Map.Contains(Key) == (Expected.count(Key) != 0)); break; } case 4: { Map.Empty(); Expected.clear(); break; } case 5: { Map.Reset(); Expected.clear(); break; } default: { Map.Add(Key, ValueType{I}); Expected[Key] = ValueType{I}; break; } } Check(Map, Expected); } } REQUIRE(AutoRTFMTestUtils::FObjectLifetimeHelper::ConstructorCalls == AutoRTFMTestUtils::FObjectLifetimeHelper::DestructorCalls); AutoRTFMTestUtils::FObjectLifetimeHelper::ConstructorCalls = 0; AutoRTFMTestUtils::FObjectLifetimeHelper::DestructorCalls = 0; }