Files
UnrealEngine/Engine/Source/Programs/AutoRTFMTests/Private/HashMapTests.cpp
2025-05-18 13:04:45 +08:00

437 lines
9.0 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "HashMap.h"
#include "Catch2Includes.h"
#include "ObjectLifetimeHelper.h"
#include <unordered_map>
using HashMapIntToInt = AutoRTFM::THashMap<int, int>;
using HashMapIntToObj = AutoRTFM::THashMap<int, AutoRTFMTestUtils::FObjectLifetimeHelper>;
using HashMapObjToInt = AutoRTFM::THashMap<AutoRTFMTestUtils::FObjectLifetimeHelper, int>;
using HashMapObjToObj = AutoRTFM::THashMap<AutoRTFMTestUtils::FObjectLifetimeHelper, AutoRTFMTestUtils::FObjectLifetimeHelper>;
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<KeyType, ValueType>& 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<const KeyType, ValueType>& 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<const KeyType, ValueType>& Item : const_cast<const MapType&>(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<KeyType, ValueType> 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;
}