Files
FlaxEngine/Source/Engine/Tests/TestCollections.cpp
Wojtek Figat 3a798a70fa Fix collections capacity growing to use the closest power of two
Capacity was incorrectly 2x larger than needed.
Added unit test to ensure it stays correct.
2025-12-04 23:29:15 +01:00

389 lines
11 KiB
C++

// Copyright (c) Wojciech Figat. All rights reserved.
#include "Engine/Core/RandomStream.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Collections/BitArray.h"
#include "Engine/Core/Collections/HashSet.h"
#include "Engine/Core/Collections/Dictionary.h"
#include <ThirdParty/catch2/catch.hpp>
const bool TestBits[] = { true, false, true, false };
template<typename AllocationType = HeapAllocation>
void InitBitArray(BitArray<AllocationType>& array)
{
array.Add(TestBits, ARRAY_COUNT(TestBits));
}
template<typename AllocationType = HeapAllocation>
void CheckBitArray(const BitArray<AllocationType>& array)
{
CHECK(array.Count() == ARRAY_COUNT(TestBits));
for (int32 i = 0; i < ARRAY_COUNT(TestBits); i++)
{
CHECK(array[i] == TestBits[i]);
}
}
TEST_CASE("Array")
{
SECTION("Test Capacity")
{
// Ensure correct collections capacity growing to meet proper memory usage vs safe slack
CHECK(AllocationUtils::CalculateCapacityGrow(1, 0) == 8);
CHECK(AllocationUtils::CalculateCapacityGrow(7, 0) == 8);
CHECK(AllocationUtils::CalculateCapacityGrow(1, 16) == 16);
CHECK(AllocationUtils::CalculateCapacityGrow(31, 0) == 32);
CHECK(AllocationUtils::CalculateCapacityGrow(32, 0) == 32);
CHECK(AllocationUtils::CalculateCapacityGrow(1000, 0) == 1024);
CHECK(AllocationUtils::CalculateCapacityGrow(1024, 0) == 1024);
CHECK(AllocationUtils::CalculateCapacityGrow(1025, 0) == 2048);
}
SECTION("Test Allocators")
{
Array<int32> a1;
Array<int32, InlinedAllocation<8>> a2;
Array<int32, FixedAllocation<8>> a3;
for (int32 i = 0; i < 7; i++)
{
a1.Add(i);
a2.Add(i);
a3.Add(i);
}
CHECK(a1.Count() == 7);
CHECK(a2.Count() == 7);
CHECK(a3.Count() == 7);
for (int32 i = 0; i < 7; i++)
{
CHECK(a1[i] == i);
CHECK(a2[i] == i);
CHECK(a3[i] == i);
}
}
// Generate some random data for testing
Array<uint32> testData;
testData.Resize(32);
RandomStream rand(101);
for (int32 i = 0; i < testData.Count(); i++)
testData[i] = rand.GetUnsignedInt();
SECTION("Test Copy Constructor")
{
const Array<uint32> a1(testData);
const Array<uint32, InlinedAllocation<8>> a2(testData);
const Array<uint32, InlinedAllocation<64>> a3(testData);
const Array<uint32, FixedAllocation<64>> a4(testData);
CHECK(a1 == testData);
CHECK(a2 == testData);
CHECK(a3 == testData);
CHECK(a4 == testData);
}
SECTION("Test Copy Operator")
{
Array<uint32> a1;
a1 = testData;
CHECK(a1 == testData);
}
}
TEST_CASE("BitArray")
{
SECTION("Test Access")
{
BitArray<> a1;
CHECK(a1.Count() == 0);
for (int32 i = 0; i < 310; i++)
{
a1.Add(false);
}
CHECK(a1.Count() == 310);
a1.Resize(300);
CHECK(a1.Count() == 300);
CHECK(a1.Capacity() >= 300);
a1.SetAll(true);
a1.SetAll(false);
for (int32 i = 0; i < 300; i++)
{
a1.Set(i, true);
for (int32 j = 0; j < 300; j++)
{
bool expected = j == i;
CHECK(a1.Get(j) == expected);
}
a1.Set(i, false);
}
}
SECTION("Test Allocators")
{
BitArray<> a1;
BitArray<InlinedAllocation<8>> a2;
BitArray<FixedAllocation<8>> a3;
for (int32 i = 0; i < 7; i++)
{
const bool v = i & 2;
a1.Add(v);
a2.Add(v);
a3.Add(v);
}
CHECK(a1.Count() == 7);
CHECK(a2.Count() == 7);
CHECK(a3.Count() == 7);
for (int32 i = 0; i < 7; i++)
{
const bool v = i & 2;
CHECK(a1.Get(i) == v);
CHECK(a2.Get(i) == v);
CHECK(a3.Get(i) == v);
}
}
SECTION("Test Move/Copy")
{
BitArray<> array1;
BitArray<FixedAllocation<4>> array2;
BitArray<InlinedAllocation<4>> array3;
BitArray<InlinedAllocation<2>> array4;
InitBitArray(array1);
InitBitArray(array2);
InitBitArray(array3);
InitBitArray(array4);
CheckBitArray(array1);
CheckBitArray(array2);
CheckBitArray(array3);
CheckBitArray(array4);
BitArray<> arrayClone1 = array1;
BitArray<FixedAllocation<4>> arrayClone2(array1);
BitArray<FixedAllocation<4>> arrayClone3(MoveTemp(array1));
BitArray<> arrayClone4(MoveTemp(array1));
BitArray<FixedAllocation<4>> arrayClone5 = MoveTemp(array2);
BitArray<InlinedAllocation<4>> arrayClone6 = MoveTemp(array3);
BitArray<InlinedAllocation<2>> arrayClone7 = MoveTemp(array4);
CheckBitArray(arrayClone1);
CheckBitArray(arrayClone2);
CheckBitArray(arrayClone4);
CheckBitArray(arrayClone5);
CheckBitArray(arrayClone6);
CheckBitArray(arrayClone7);
CHECK(array1.Count() == 0);
CHECK(array2.Count() == 0);
CHECK(array3.Count() == 0);
CHECK(array4.Count() == 0);
}
// Generate some random data for testing
BitArray<> testData;
testData.Resize(128);
RandomStream rand(101);
for (int32 i = 0; i < testData.Count(); i++)
testData.Set(i, rand.GetBool());
SECTION("Test Copy Constructor")
{
const BitArray<> a1(testData);
const BitArray<InlinedAllocation<8>> a2(testData);
const BitArray<InlinedAllocation<256>> a3(testData);
const BitArray<FixedAllocation<256>> a4(testData);
CHECK(a1 == testData);
CHECK(a2 == testData);
CHECK(a3 == testData);
CHECK(a4 == testData);
}
SECTION("Test Copy Operator")
{
BitArray<> a1;
a1 = testData;
CHECK(a1 == testData);
}
SECTION("Test Set All")
{
BitArray<> a1;
a1.Resize(9);
CHECK(a1.Count() == 9);
a1.SetAll(true);
for (int32 i = 0; i < a1.Count(); i++)
CHECK(a1[i] == true);
a1.SetAll(false);
for (int32 i = 0; i < a1.Count(); i++)
CHECK(a1[i] == false);
}
}
TEST_CASE("HashSet")
{
SECTION("Test Allocators")
{
HashSet<int32> a1;
HashSet<int32, InlinedAllocation<HASH_SET_DEFAULT_CAPACITY>> a2;
HashSet<int32, FixedAllocation<HASH_SET_DEFAULT_CAPACITY>> a3;
for (int32 i = 0; i < 7; i++)
{
a1.Add(i);
a2.Add(i);
a3.Add(i);
}
CHECK(a1.Count() == 7);
CHECK(a2.Count() == 7);
CHECK(a3.Count() == 7);
for (int32 i = 0; i < 7; i++)
{
CHECK(a1.Contains(i));
CHECK(a2.Contains(i));
CHECK(a3.Contains(i));
}
}
SECTION("Test Resizing")
{
HashSet<int32> a1;
for (int32 i = 0; i < 4000; i++)
a1.Add(i);
CHECK(a1.Count() == 4000);
int32 capacity = a1.Capacity();
for (int32 i = 0; i < 4000; i++)
{
CHECK(a1.Contains(i));
}
a1.Clear();
CHECK(a1.Count() == 0);
CHECK(a1.Capacity() == capacity);
for (int32 i = 0; i < 4000; i++)
a1.Add(i);
CHECK(a1.Count() == 4000);
CHECK(a1.Capacity() == capacity);
for (int32 i = 0; i < 4000; i++)
a1.Remove(i);
CHECK(a1.Count() == 0);
CHECK(a1.Capacity() == capacity);
for (int32 i = 0; i < 4000; i++)
a1.Add(i);
CHECK(a1.Count() == 4000);
CHECK(a1.Capacity() == capacity);
}
SECTION("Test Default Capacity")
{
HashSet<int32> a1;
a1.Add(1);
CHECK(a1.Capacity() <= HASH_SET_DEFAULT_CAPACITY);
}
SECTION("Test Add/Remove")
{
HashSet<int32> a1;
for (int32 i = 0; i < 4000; i++)
{
a1.Add(i);
a1.Remove(i);
}
CHECK(a1.Count() == 0);
CHECK(a1.Capacity() <= HASH_SET_DEFAULT_CAPACITY);
a1.Clear();
for (int32 i = 1; i <= 10; i++)
a1.Add(-i);
for (int32 i = 0; i < 4000; i++)
{
a1.Add(i);
a1.Remove(i);
}
CHECK(a1.Count() == 10);
CHECK(a1.Capacity() <= HASH_SET_DEFAULT_CAPACITY);
}
}
TEST_CASE("Dictionary")
{
SECTION("Test Allocators")
{
Dictionary<int32, int32> a1;
Dictionary<int32, int32, InlinedAllocation<HASH_SET_DEFAULT_CAPACITY>> a2;
Dictionary<int32, int32, FixedAllocation<HASH_SET_DEFAULT_CAPACITY>> a3;
for (int32 i = 0; i < 7; i++)
{
a1.Add(i, i);
a2.Add(i, i);
a3.Add(i, i);
}
CHECK(a1.Count() == 7);
CHECK(a2.Count() == 7);
CHECK(a3.Count() == 7);
for (int32 i = 0; i < 7; i++)
{
CHECK(a1.ContainsKey(i));
CHECK(a2.ContainsKey(i));
CHECK(a3.ContainsKey(i));
CHECK(a1.ContainsValue(i));
CHECK(a2.ContainsValue(i));
CHECK(a3.ContainsValue(i));
}
}
SECTION("Test Resizing")
{
Dictionary<int32, int32> a1;
for (int32 i = 0; i < 4000; i++)
a1.Add(i, i);
CHECK(a1.Count() == 4000);
int32 capacity = a1.Capacity();
for (int32 i = 0; i < 4000; i++)
{
CHECK(a1.ContainsKey(i));
CHECK(a1.ContainsValue(i));
}
a1.Clear();
CHECK(a1.Count() == 0);
CHECK(a1.Capacity() == capacity);
for (int32 i = 0; i < 4000; i++)
a1.Add(i, i);
CHECK(a1.Count() == 4000);
CHECK(a1.Capacity() == capacity);
for (int32 i = 0; i < 4000; i++)
a1.Remove(i);
CHECK(a1.Count() == 0);
CHECK(a1.Capacity() == capacity);
for (int32 i = 0; i < 4000; i++)
a1.Add(i, i);
CHECK(a1.Count() == 4000);
CHECK(a1.Capacity() == capacity);
}
SECTION("Test Default Capacity")
{
Dictionary<int32, int32> a1;
a1.Add(1, 1);
CHECK(a1.Capacity() <= HASH_SET_DEFAULT_CAPACITY);
}
SECTION("Test Add/Remove")
{
Dictionary<int32, int32> a1;
for (int32 i = 0; i < 4000; i++)
{
a1.Add(i, i);
a1.Remove(i);
}
CHECK(a1.Count() == 0);
CHECK(a1.Capacity() <= HASH_SET_DEFAULT_CAPACITY);
a1.Clear();
for (int32 i = 1; i <= 10; i++)
a1.Add(-i, -i);
for (int32 i = 0; i < 4000; i++)
{
a1.Add(i, i);
a1.Remove(i);
}
CHECK(a1.Count() == 10);
CHECK(a1.Capacity() <= HASH_SET_DEFAULT_CAPACITY);
}
}