802 lines
25 KiB
C++
802 lines
25 KiB
C++
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
|
|
|
#include "Libraries/DirectiveUtilArrayFunctionLibrary.h"
|
|
#include "Tests/DirectiveUtilTestObject.h"
|
|
|
|
#include "Algo/Reverse.h"
|
|
#include "Misc/AutomationTest.h"
|
|
|
|
namespace
|
|
{
|
|
FArrayProperty* GetIntegerArrayProperty()
|
|
{
|
|
return FindFProperty<FArrayProperty>(
|
|
UDirectiveUtilTestObject::StaticClass(),
|
|
GET_MEMBER_NAME_CHECKED(UDirectiveUtilTestObject, TestArray));
|
|
}
|
|
|
|
TArray<int32> MakeSequentialValues(const int32 Count)
|
|
{
|
|
TArray<int32> Values;
|
|
Values.SetNumUninitialized(Count);
|
|
for (int32 Index = 0; Index < Count; ++Index)
|
|
{
|
|
Values[Index] = Index;
|
|
}
|
|
return Values;
|
|
}
|
|
|
|
TArray<int32> MakeRepeatingValues(const int32 Count, const int32 DistinctCount)
|
|
{
|
|
TArray<int32> Values;
|
|
Values.Reserve(Count);
|
|
for (int32 Index = 0; Index < Count; ++Index)
|
|
{
|
|
Values.Add(Index % DistinctCount);
|
|
}
|
|
return Values;
|
|
}
|
|
|
|
TArray<int32> MakeDistinctReference(const TArray<int32>& Values)
|
|
{
|
|
TArray<int32> Result;
|
|
for (const int32 Value : Values)
|
|
{
|
|
Result.AddUnique(Value);
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
bool FindMostCommonReference(const TArray<int32>& Values, int32& OutValue, int32& OutCount)
|
|
{
|
|
OutValue = 0;
|
|
OutCount = 0;
|
|
TMap<int32, int32> Counts;
|
|
for (const int32 Value : Values)
|
|
{
|
|
++Counts.FindOrAdd(Value);
|
|
}
|
|
for (const TPair<int32, int32>& Pair : Counts)
|
|
{
|
|
OutCount = FMath::Max(OutCount, Pair.Value);
|
|
}
|
|
for (const int32 Value : Values)
|
|
{
|
|
if (Counts.FindRef(Value) == OutCount)
|
|
{
|
|
OutValue = Value;
|
|
break;
|
|
}
|
|
}
|
|
return !Values.IsEmpty();
|
|
}
|
|
|
|
int32 CountReference(const TArray<int32>& Values, const int32 QueryValue)
|
|
{
|
|
int32 Count = 0;
|
|
for (const int32 Value : Values)
|
|
{
|
|
Count += Value == QueryValue ? 1 : 0;
|
|
}
|
|
return Count;
|
|
}
|
|
|
|
TArray<int32> RemoveAllReference(const TArray<int32>& Values, const int32 QueryValue)
|
|
{
|
|
TArray<int32> Result;
|
|
Result.Reserve(Values.Num());
|
|
for (const int32 Value : Values)
|
|
{
|
|
if (Value != QueryValue)
|
|
{
|
|
Result.Add(Value);
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
TArray<int32> SliceReference(const TArray<int32>& Values, const int32 StartIndex, const int32 Count)
|
|
{
|
|
TArray<int32> Result;
|
|
Result.Reserve(Count);
|
|
for (int32 Index = 0; Index < Count; ++Index)
|
|
{
|
|
Result.Add(Values[StartIndex + Index]);
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
TArray<int32> MakeSampleReference(const int32 SourceCount, const int32 RequestedCount, const int32 Seed)
|
|
{
|
|
TArray<int32> AvailableIndices = MakeSequentialValues(SourceCount);
|
|
TArray<int32> Result;
|
|
const int32 SampleCount = FMath::Clamp(RequestedCount, 0, SourceCount);
|
|
Result.Reserve(SampleCount);
|
|
FRandomStream RandomStream(Seed);
|
|
for (int32 SampleIndex = 0; SampleIndex < SampleCount; ++SampleIndex)
|
|
{
|
|
const int32 SelectedIndex = RandomStream.RandRange(0, AvailableIndices.Num() - 1);
|
|
Result.Add(AvailableIndices[SelectedIndex]);
|
|
AvailableIndices.RemoveAtSwap(SelectedIndex, 1, EAllowShrinking::No);
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
template <typename ValueType>
|
|
TArray<ValueType> MakeRotationReference(const TArray<ValueType>& Values, const int32 Shift)
|
|
{
|
|
if (Values.IsEmpty())
|
|
{
|
|
return Values;
|
|
}
|
|
|
|
int32 NormalizedShift = Shift % Values.Num();
|
|
if (NormalizedShift < 0)
|
|
{
|
|
NormalizedShift += Values.Num();
|
|
}
|
|
|
|
TArray<ValueType> Result;
|
|
Result.Reserve(Values.Num());
|
|
for (int32 Index = 0; Index < Values.Num(); ++Index)
|
|
{
|
|
Result.Add(Values[(Index - NormalizedShift + Values.Num()) % Values.Num()]);
|
|
}
|
|
return Result;
|
|
}
|
|
}
|
|
|
|
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
|
FDirectiveUtilArrayCardinalityTest,
|
|
"DirectiveUtilities.ArrayScenarios.Cardinality",
|
|
EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter)
|
|
|
|
bool FDirectiveUtilArrayCardinalityTest::RunTest(const FString& Parameters)
|
|
{
|
|
FArrayProperty* ArrayProperty = GetIntegerArrayProperty();
|
|
if (!TestNotNull("Integer array property should be available", ArrayProperty))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
UDirectiveUtilTestObject* TestObject = NewObject<UDirectiveUtilTestObject>();
|
|
struct FScenario
|
|
{
|
|
int32 ItemCount;
|
|
int32 DistinctCount;
|
|
};
|
|
const TArray<FScenario> Scenarios = {
|
|
{0, 1},
|
|
{1, 1},
|
|
{2, 1},
|
|
{3, 2},
|
|
{16, 16},
|
|
{31, 7},
|
|
{64, 4},
|
|
{257, 257},
|
|
{1024, 1},
|
|
{4096, 257},
|
|
{16384, 1024}
|
|
};
|
|
|
|
for (const FScenario& Scenario : Scenarios)
|
|
{
|
|
const TArray<int32> Source = MakeRepeatingValues(Scenario.ItemCount, Scenario.DistinctCount);
|
|
const TArray<int32> ExpectedDistinct = MakeDistinctReference(Source);
|
|
const FString Label = FString::Printf(
|
|
TEXT("items=%d distinct=%d"),
|
|
Scenario.ItemCount,
|
|
FMath::Min(Scenario.ItemCount, Scenario.DistinctCount));
|
|
|
|
TestObject->TestArray = Source;
|
|
UDirectiveUtilArrayFunctionLibrary::GenericArray_RemoveDuplicates(&TestObject->TestArray, ArrayProperty);
|
|
TestEqual(Label + TEXT(" RemoveDuplicates"), TestObject->TestArray, ExpectedDistinct);
|
|
|
|
TArray<int32> DistinctResult;
|
|
UDirectiveUtilArrayFunctionLibrary::GenericArray_GetDistinct(
|
|
&Source,
|
|
ArrayProperty,
|
|
&DistinctResult,
|
|
ArrayProperty);
|
|
TestEqual(Label + TEXT(" GetDistinct"), DistinctResult, ExpectedDistinct);
|
|
|
|
int32 MostCommonValue = INDEX_NONE;
|
|
int32 MostCommonCount = INDEX_NONE;
|
|
const bool bFoundMostCommon = UDirectiveUtilArrayFunctionLibrary::GenericArray_GetMostCommon(
|
|
&Source,
|
|
ArrayProperty,
|
|
&MostCommonValue,
|
|
&MostCommonCount);
|
|
if (Source.IsEmpty())
|
|
{
|
|
TestFalse(Label + TEXT(" GetMostCommon should fail"), bFoundMostCommon);
|
|
TestEqual(Label + TEXT(" GetMostCommon count"), MostCommonCount, 0);
|
|
}
|
|
else
|
|
{
|
|
TestTrue(Label + TEXT(" GetMostCommon should succeed"), bFoundMostCommon);
|
|
TestEqual(Label + TEXT(" GetMostCommon value"), MostCommonValue, 0);
|
|
TestEqual(
|
|
Label + TEXT(" GetMostCommon count"),
|
|
MostCommonCount,
|
|
FMath::DivideAndRoundUp(Scenario.ItemCount, Scenario.DistinctCount));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
|
FDirectiveUtilArraySamplingScenarioTest,
|
|
"DirectiveUtilities.ArrayScenarios.Sampling",
|
|
EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter)
|
|
|
|
bool FDirectiveUtilArraySamplingScenarioTest::RunTest(const FString& Parameters)
|
|
{
|
|
FArrayProperty* ArrayProperty = GetIntegerArrayProperty();
|
|
if (!TestNotNull("Integer array property should be available", ArrayProperty))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
constexpr int32 Seed = 7351;
|
|
for (const int32 SourceCount : {0, 1, 2, 3, 4, 17, 100, 1000})
|
|
{
|
|
const TArray<int32> Source = MakeSequentialValues(SourceCount);
|
|
const TArray<int32> RequestedCounts = {-1, 0, 1, SourceCount / 4, SourceCount, SourceCount + 5};
|
|
for (const int32 RequestedCount : RequestedCounts)
|
|
{
|
|
FRandomStream FirstStream(Seed);
|
|
FRandomStream SecondStream(Seed);
|
|
TArray<int32> FirstSample;
|
|
TArray<int32> SecondSample;
|
|
UDirectiveUtilArrayFunctionLibrary::GenericArray_Sample(
|
|
&Source,
|
|
ArrayProperty,
|
|
RequestedCount,
|
|
false,
|
|
&FirstStream,
|
|
&FirstSample,
|
|
ArrayProperty);
|
|
UDirectiveUtilArrayFunctionLibrary::GenericArray_Sample(
|
|
&Source,
|
|
ArrayProperty,
|
|
RequestedCount,
|
|
false,
|
|
&SecondStream,
|
|
&SecondSample,
|
|
ArrayProperty);
|
|
|
|
const FString Label = FString::Printf(TEXT("source=%d requested=%d"), SourceCount, RequestedCount);
|
|
TestEqual(Label + TEXT(" count"), FirstSample.Num(), FMath::Clamp(RequestedCount, 0, SourceCount));
|
|
TestEqual(Label + TEXT(" deterministic"), FirstSample, SecondSample);
|
|
TSet<int32> UniqueValues;
|
|
for (const int32 Value : FirstSample)
|
|
{
|
|
TestTrue(Label + TEXT(" source membership"), Source.Contains(Value));
|
|
UniqueValues.Add(Value);
|
|
}
|
|
TestEqual(Label + TEXT(" uniqueness"), UniqueValues.Num(), FirstSample.Num());
|
|
}
|
|
}
|
|
|
|
const TArray<int32> BoundarySource = MakeSequentialValues(100);
|
|
for (const int32 RequestedCount : {24, 25, 26, 75, 100})
|
|
{
|
|
FRandomStream RandomStream(Seed);
|
|
TArray<int32> Sample;
|
|
UDirectiveUtilArrayFunctionLibrary::GenericArray_Sample(
|
|
&BoundarySource,
|
|
ArrayProperty,
|
|
RequestedCount,
|
|
false,
|
|
&RandomStream,
|
|
&Sample,
|
|
ArrayProperty);
|
|
TestEqual(
|
|
FString::Printf(TEXT("threshold requested=%d"), RequestedCount),
|
|
Sample,
|
|
MakeSampleReference(BoundarySource.Num(), RequestedCount, Seed));
|
|
}
|
|
|
|
const TArray<int32> ReplacementSource = MakeSequentialValues(7);
|
|
for (const int32 RequestedCount : {0, 1, 7, 14, 100})
|
|
{
|
|
FRandomStream FirstStream(Seed);
|
|
FRandomStream SecondStream(Seed);
|
|
TArray<int32> FirstSample;
|
|
TArray<int32> SecondSample;
|
|
UDirectiveUtilArrayFunctionLibrary::GenericArray_Sample(
|
|
&ReplacementSource,
|
|
ArrayProperty,
|
|
RequestedCount,
|
|
true,
|
|
&FirstStream,
|
|
&FirstSample,
|
|
ArrayProperty);
|
|
UDirectiveUtilArrayFunctionLibrary::GenericArray_Sample(
|
|
&ReplacementSource,
|
|
ArrayProperty,
|
|
RequestedCount,
|
|
true,
|
|
&SecondStream,
|
|
&SecondSample,
|
|
ArrayProperty);
|
|
const FString Label = FString::Printf(TEXT("replacement requested=%d"), RequestedCount);
|
|
TestEqual(Label + TEXT(" count"), FirstSample.Num(), RequestedCount);
|
|
TestEqual(Label + TEXT(" deterministic"), FirstSample, SecondSample);
|
|
for (const int32 Value : FirstSample)
|
|
{
|
|
TestTrue(Label + TEXT(" source membership"), ReplacementSource.Contains(Value));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
|
FDirectiveUtilArrayWeightedSamplingScenarioTest,
|
|
"DirectiveUtilities.ArrayScenarios.WeightedSampling",
|
|
EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter)
|
|
|
|
bool FDirectiveUtilArrayWeightedSamplingScenarioTest::RunTest(const FString& Parameters)
|
|
{
|
|
FArrayProperty* ArrayProperty = GetIntegerArrayProperty();
|
|
if (!TestNotNull("Integer array property should be available", ArrayProperty))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
constexpr int32 Seed = 4815;
|
|
for (const int32 SourceCount : {0, 1, 2, 3, 17, 100, 1000, 4096})
|
|
{
|
|
const TArray<int32> Source = MakeSequentialValues(SourceCount);
|
|
TArray<float> Weights;
|
|
Weights.Reserve(SourceCount);
|
|
TSet<int32> SelectableValues;
|
|
for (int32 Index = 0; Index < SourceCount; ++Index)
|
|
{
|
|
const float Weight = Index % 3 == 0 ? 0.0f : static_cast<float>((Index % 7) + 1);
|
|
Weights.Add(Weight);
|
|
if (Weight > 0.0f)
|
|
{
|
|
SelectableValues.Add(Index);
|
|
}
|
|
}
|
|
|
|
const TArray<int32> RequestedCounts = {0, 1, SourceCount / 4, SourceCount, SourceCount + 5};
|
|
for (const int32 RequestedCount : RequestedCounts)
|
|
{
|
|
for (const bool bWithReplacement : {false, true})
|
|
{
|
|
FRandomStream FirstStream(Seed);
|
|
FRandomStream SecondStream(Seed);
|
|
TArray<int32> FirstSample;
|
|
TArray<int32> SecondSample;
|
|
const bool bFirstSucceeded = UDirectiveUtilArrayFunctionLibrary::GenericArray_SampleWeighted(
|
|
&Source,
|
|
ArrayProperty,
|
|
Weights,
|
|
RequestedCount,
|
|
bWithReplacement,
|
|
&FirstStream,
|
|
&FirstSample,
|
|
ArrayProperty);
|
|
const bool bSecondSucceeded = UDirectiveUtilArrayFunctionLibrary::GenericArray_SampleWeighted(
|
|
&Source,
|
|
ArrayProperty,
|
|
Weights,
|
|
RequestedCount,
|
|
bWithReplacement,
|
|
&SecondStream,
|
|
&SecondSample,
|
|
ArrayProperty);
|
|
const FString Label = FString::Printf(
|
|
TEXT("source=%d requested=%d replacement=%d"),
|
|
SourceCount,
|
|
RequestedCount,
|
|
bWithReplacement);
|
|
|
|
const bool bExpectedSuccess = RequestedCount == 0 || !SelectableValues.IsEmpty();
|
|
TestEqual(Label + TEXT(" first validity"), bFirstSucceeded, bExpectedSuccess);
|
|
TestEqual(Label + TEXT(" second validity"), bSecondSucceeded, bExpectedSuccess);
|
|
TestEqual(Label + TEXT(" deterministic"), FirstSample, SecondSample);
|
|
const int32 ExpectedCount = !bExpectedSuccess
|
|
? 0
|
|
: bWithReplacement
|
|
? RequestedCount
|
|
: FMath::Min(RequestedCount, SelectableValues.Num());
|
|
TestEqual(Label + TEXT(" count"), FirstSample.Num(), ExpectedCount);
|
|
|
|
TSet<int32> UniqueValues;
|
|
for (const int32 Value : FirstSample)
|
|
{
|
|
TestTrue(Label + TEXT(" selectable membership"), SelectableValues.Contains(Value));
|
|
UniqueValues.Add(Value);
|
|
}
|
|
if (!bWithReplacement)
|
|
{
|
|
TestEqual(Label + TEXT(" unique source indices"), UniqueValues.Num(), FirstSample.Num());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
|
FDirectiveUtilArrayRangeScenarioTest,
|
|
"DirectiveUtilities.ArrayScenarios.RangesAndPages",
|
|
EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter)
|
|
|
|
bool FDirectiveUtilArrayRangeScenarioTest::RunTest(const FString& Parameters)
|
|
{
|
|
FArrayProperty* ArrayProperty = GetIntegerArrayProperty();
|
|
if (!TestNotNull("Integer array property should be available", ArrayProperty))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
struct FPageScenario
|
|
{
|
|
int32 ItemCount;
|
|
int32 PageIndex;
|
|
int32 PageSize;
|
|
bool bExpectedValid;
|
|
int32 ExpectedPageCount;
|
|
};
|
|
const TArray<FPageScenario> PageScenarios = {
|
|
{0, 0, 1, false, 0},
|
|
{1, 0, 1, true, 1},
|
|
{2, 1, 1, true, 2},
|
|
{5, 0, 2, true, 3},
|
|
{5, 1, 2, true, 3},
|
|
{5, 2, 2, true, 3},
|
|
{5, 3, 2, false, 3},
|
|
{10, 3, 3, true, 4},
|
|
{10, 0, 10, true, 1},
|
|
{10, 0, 11, true, 1},
|
|
{10, -1, 3, false, 0},
|
|
{10, 0, 0, false, 0},
|
|
{10, 0, -1, false, 0},
|
|
{10, MAX_int32, 3, false, 4}
|
|
};
|
|
|
|
for (const FPageScenario& Scenario : PageScenarios)
|
|
{
|
|
const TArray<int32> Source = MakeSequentialValues(Scenario.ItemCount);
|
|
TArray<int32> Page;
|
|
int32 PageCount = INDEX_NONE;
|
|
const bool bValid = UDirectiveUtilArrayFunctionLibrary::GenericArray_GetPage(
|
|
&Source,
|
|
ArrayProperty,
|
|
Scenario.PageIndex,
|
|
Scenario.PageSize,
|
|
&Page,
|
|
ArrayProperty,
|
|
&PageCount);
|
|
const FString Label = FString::Printf(
|
|
TEXT("items=%d page=%d size=%d"),
|
|
Scenario.ItemCount,
|
|
Scenario.PageIndex,
|
|
Scenario.PageSize);
|
|
TestEqual(Label + TEXT(" validity"), bValid, Scenario.bExpectedValid);
|
|
TestEqual(Label + TEXT(" page count"), PageCount, Scenario.ExpectedPageCount);
|
|
|
|
TArray<int32> ExpectedPage;
|
|
if (Scenario.bExpectedValid)
|
|
{
|
|
const int32 StartIndex = Scenario.PageIndex * Scenario.PageSize;
|
|
for (int32 Index = StartIndex; Index < FMath::Min(StartIndex + Scenario.PageSize, Source.Num()); ++Index)
|
|
{
|
|
ExpectedPage.Add(Source[Index]);
|
|
}
|
|
}
|
|
TestEqual(Label + TEXT(" values"), Page, ExpectedPage);
|
|
}
|
|
|
|
const TArray<int32> SliceSource = MakeSequentialValues(10);
|
|
struct FSliceScenario
|
|
{
|
|
int32 StartIndex;
|
|
int32 Count;
|
|
};
|
|
const TArray<FSliceScenario> SliceScenarios = {
|
|
{-5, 3},
|
|
{0, -1},
|
|
{0, 0},
|
|
{0, 10},
|
|
{0, 20},
|
|
{5, 3},
|
|
{9, 5},
|
|
{10, 1},
|
|
{11, 1},
|
|
{MAX_int32, MAX_int32}
|
|
};
|
|
for (const FSliceScenario& Scenario : SliceScenarios)
|
|
{
|
|
const int32 StartIndex = FMath::Clamp(Scenario.StartIndex, 0, SliceSource.Num());
|
|
const int32 CopyCount = Scenario.Count > 0
|
|
? FMath::Min(Scenario.Count, SliceSource.Num() - StartIndex)
|
|
: 0;
|
|
TArray<int32> ExpectedSlice;
|
|
for (int32 Offset = 0; Offset < CopyCount; ++Offset)
|
|
{
|
|
ExpectedSlice.Add(SliceSource[StartIndex + Offset]);
|
|
}
|
|
|
|
TArray<int32> Slice;
|
|
UDirectiveUtilArrayFunctionLibrary::GenericArray_Slice(
|
|
&SliceSource,
|
|
ArrayProperty,
|
|
Scenario.StartIndex,
|
|
Scenario.Count,
|
|
&Slice,
|
|
ArrayProperty);
|
|
const FString Label = FString::Printf(TEXT("start=%d count=%d"), Scenario.StartIndex, Scenario.Count);
|
|
TestEqual(Label + TEXT(" separate output"), Slice, ExpectedSlice);
|
|
|
|
TArray<int32> AliasedSlice = SliceSource;
|
|
UDirectiveUtilArrayFunctionLibrary::GenericArray_Slice(
|
|
&AliasedSlice,
|
|
ArrayProperty,
|
|
Scenario.StartIndex,
|
|
Scenario.Count,
|
|
&AliasedSlice,
|
|
ArrayProperty);
|
|
TestEqual(Label + TEXT(" aliased output"), AliasedSlice, ExpectedSlice);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
|
FDirectiveUtilArrayRotationScenarioTest,
|
|
"DirectiveUtilities.ArrayScenarios.Rotation",
|
|
EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter)
|
|
|
|
bool FDirectiveUtilArrayRotationScenarioTest::RunTest(const FString& Parameters)
|
|
{
|
|
FArrayProperty* IntegerArrayProperty = GetIntegerArrayProperty();
|
|
FArrayProperty* StringArrayProperty = FindFProperty<FArrayProperty>(
|
|
UDirectiveUtilTestObject::StaticClass(),
|
|
GET_MEMBER_NAME_CHECKED(UDirectiveUtilTestObject, TestStringArray));
|
|
if (!TestNotNull("Integer array property should be available", IntegerArrayProperty)
|
|
|| !TestNotNull("String array property should be available", StringArrayProperty))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (const int32 ItemCount : {0, 1, 2, 3, 4, 5, 16, 257, 10000})
|
|
{
|
|
const TArray<int32> Source = MakeSequentialValues(ItemCount);
|
|
const TArray<int32> Shifts = {
|
|
0,
|
|
1,
|
|
-1,
|
|
ItemCount,
|
|
ItemCount + 1,
|
|
-ItemCount - 1,
|
|
MAX_int32,
|
|
MIN_int32
|
|
};
|
|
for (const int32 Shift : Shifts)
|
|
{
|
|
TArray<int32> Rotated = Source;
|
|
UDirectiveUtilArrayFunctionLibrary::GenericArray_Rotate(&Rotated, IntegerArrayProperty, Shift);
|
|
TestEqual(
|
|
FString::Printf(TEXT("POD items=%d shift=%d"), ItemCount, Shift),
|
|
Rotated,
|
|
MakeRotationReference(Source, Shift));
|
|
}
|
|
}
|
|
|
|
for (const int32 ItemCount : {0, 1, 2, 5, 32})
|
|
{
|
|
TArray<FString> Source;
|
|
Source.Reserve(ItemCount);
|
|
for (int32 Index = 0; Index < ItemCount; ++Index)
|
|
{
|
|
Source.Add(FString::Printf(TEXT("Value%d"), Index));
|
|
}
|
|
for (const int32 Shift : {0, 1, -1, 7, -11, MAX_int32, MIN_int32})
|
|
{
|
|
TArray<FString> Rotated = Source;
|
|
UDirectiveUtilArrayFunctionLibrary::GenericArray_Rotate(&Rotated, StringArrayProperty, Shift);
|
|
TestEqual(
|
|
FString::Printf(TEXT("managed items=%d shift=%d"), ItemCount, Shift),
|
|
Rotated,
|
|
MakeRotationReference(Source, Shift));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
|
FDirectiveUtilArrayOrderingScenarioTest,
|
|
"DirectiveUtilities.ArrayScenarios.NaturalOrdering",
|
|
EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter)
|
|
|
|
bool FDirectiveUtilArrayOrderingScenarioTest::RunTest(const FString& Parameters)
|
|
{
|
|
for (const int32 ItemCount : {0, 1, 2, 10, 100, 1000})
|
|
{
|
|
TArray<FString> Strings;
|
|
TArray<FString> ExpectedStrings;
|
|
TArray<FName> Names;
|
|
TArray<FName> ExpectedNames;
|
|
Strings.Reserve(ItemCount);
|
|
ExpectedStrings.Reserve(ItemCount);
|
|
Names.Reserve(ItemCount);
|
|
ExpectedNames.Reserve(ItemCount);
|
|
for (int32 Index = 0; Index < ItemCount; ++Index)
|
|
{
|
|
ExpectedStrings.Add(FString::Printf(TEXT("Item%d"), Index));
|
|
ExpectedNames.Add(FName(*FString::Printf(TEXT("Actor%d"), Index)));
|
|
}
|
|
for (int32 Index = ItemCount - 1; Index >= 0; --Index)
|
|
{
|
|
Strings.Add(ExpectedStrings[Index]);
|
|
Names.Add(ExpectedNames[Index]);
|
|
}
|
|
|
|
UDirectiveUtilArrayFunctionLibrary::NaturalSortStringArray(Strings);
|
|
UDirectiveUtilArrayFunctionLibrary::NaturalSortNameArray(Names);
|
|
const FString Label = FString::Printf(TEXT("items=%d"), ItemCount);
|
|
TestEqual(Label + TEXT(" string ascending"), Strings, ExpectedStrings);
|
|
TestEqual(Label + TEXT(" name ascending"), Names, ExpectedNames);
|
|
|
|
UDirectiveUtilArrayFunctionLibrary::NaturalSortStringArray(Strings, true);
|
|
UDirectiveUtilArrayFunctionLibrary::NaturalSortNameArray(Names, true);
|
|
Algo::Reverse(ExpectedStrings);
|
|
Algo::Reverse(ExpectedNames);
|
|
TestEqual(Label + TEXT(" string descending"), Strings, ExpectedStrings);
|
|
TestEqual(Label + TEXT(" name descending"), Names, ExpectedNames);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
|
FDirectiveUtilArrayDeterministicFuzzTest,
|
|
"DirectiveUtilities.ArrayScenarios.DeterministicFuzz",
|
|
EAutomationTestFlags::EditorContext | EAutomationTestFlags::ClientContext | EAutomationTestFlags::EngineFilter)
|
|
|
|
bool FDirectiveUtilArrayDeterministicFuzzTest::RunTest(const FString& Parameters)
|
|
{
|
|
FArrayProperty* ArrayProperty = GetIntegerArrayProperty();
|
|
if (!TestNotNull("Integer array property should be available", ArrayProperty))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
UDirectiveUtilTestObject* TestObject = NewObject<UDirectiveUtilTestObject>();
|
|
const TArray<int32> ItemCounts = {0, 1, 2, 3, 7, 16, 31, 32, 127, 128, 1024, 4096};
|
|
for (const int32 Seed : {17, 271, 4099, 65537, 104729})
|
|
{
|
|
for (int32 CountIndex = 0; CountIndex < ItemCounts.Num(); ++CountIndex)
|
|
{
|
|
const int32 ItemCount = ItemCounts[CountIndex];
|
|
FRandomStream Stream(Seed + ItemCount * 31);
|
|
TArray<int32> Source;
|
|
Source.Reserve(ItemCount);
|
|
for (int32 Index = 0; Index < ItemCount; ++Index)
|
|
{
|
|
Source.Add(Stream.RandRange(-64, 64));
|
|
}
|
|
const FString Label = FString::Printf(TEXT("seed=%d items=%d"), Seed, ItemCount);
|
|
|
|
const TArray<int32> ExpectedDistinct = MakeDistinctReference(Source);
|
|
TestObject->TestArray = Source;
|
|
UDirectiveUtilArrayFunctionLibrary::GenericArray_RemoveDuplicates(&TestObject->TestArray, ArrayProperty);
|
|
TestEqual(Label + TEXT(" RemoveDuplicates"), TestObject->TestArray, ExpectedDistinct);
|
|
|
|
TArray<int32> DistinctResult;
|
|
UDirectiveUtilArrayFunctionLibrary::GenericArray_GetDistinct(
|
|
&Source,
|
|
ArrayProperty,
|
|
&DistinctResult,
|
|
ArrayProperty);
|
|
TestEqual(Label + TEXT(" GetDistinct"), DistinctResult, ExpectedDistinct);
|
|
|
|
int32 ExpectedMostCommon = 0;
|
|
int32 ExpectedMostCommonCount = 0;
|
|
const bool bExpectedMostCommon = FindMostCommonReference(
|
|
Source,
|
|
ExpectedMostCommon,
|
|
ExpectedMostCommonCount);
|
|
int32 MostCommon = 0;
|
|
int32 MostCommonCount = 0;
|
|
const bool bFoundMostCommon = UDirectiveUtilArrayFunctionLibrary::GenericArray_GetMostCommon(
|
|
&Source,
|
|
ArrayProperty,
|
|
&MostCommon,
|
|
&MostCommonCount);
|
|
TestEqual(Label + TEXT(" GetMostCommon result"), bFoundMostCommon, bExpectedMostCommon);
|
|
TestEqual(Label + TEXT(" GetMostCommon value"), MostCommon, ExpectedMostCommon);
|
|
TestEqual(Label + TEXT(" GetMostCommon count"), MostCommonCount, ExpectedMostCommonCount);
|
|
|
|
const int32 QueryValue = Stream.RandRange(-70, 70);
|
|
TestEqual(
|
|
Label + TEXT(" CountOccurrences"),
|
|
UDirectiveUtilArrayFunctionLibrary::GenericArray_CountOccurrences(&Source, ArrayProperty, &QueryValue),
|
|
CountReference(Source, QueryValue));
|
|
|
|
const TArray<int32> ExpectedRemoved = RemoveAllReference(Source, QueryValue);
|
|
TestObject->TestArray = Source;
|
|
const bool bRemoved = UDirectiveUtilArrayFunctionLibrary::GenericArray_RemoveAllOccurrences(
|
|
&TestObject->TestArray,
|
|
ArrayProperty,
|
|
&QueryValue);
|
|
TestEqual(Label + TEXT(" RemoveAllOccurrences result"), bRemoved, ExpectedRemoved.Num() != Source.Num());
|
|
TestEqual(Label + TEXT(" RemoveAllOccurrences values"), TestObject->TestArray, ExpectedRemoved);
|
|
|
|
TArray<int32> ExpectedAppended = Source;
|
|
ExpectedAppended.Append(Source);
|
|
TestObject->TestArray = Source;
|
|
UDirectiveUtilArrayFunctionLibrary::GenericArray_AppendOptimized(
|
|
&TestObject->TestArray,
|
|
ArrayProperty,
|
|
&TestObject->TestArray,
|
|
ArrayProperty);
|
|
TestEqual(Label + TEXT(" AppendOptimized self append"), TestObject->TestArray, ExpectedAppended);
|
|
|
|
for (const int32 Shift : {MIN_int32, -ItemCount - 1, -1, 0, 1, ItemCount + 1, MAX_int32})
|
|
{
|
|
TestObject->TestArray = Source;
|
|
UDirectiveUtilArrayFunctionLibrary::GenericArray_Rotate(&TestObject->TestArray, ArrayProperty, Shift);
|
|
TestEqual(
|
|
Label + FString::Printf(TEXT(" Rotate shift=%d"), Shift),
|
|
TestObject->TestArray,
|
|
MakeRotationReference(Source, Shift));
|
|
}
|
|
|
|
const int32 StartIndex = Stream.RandRange(-ItemCount - 2, ItemCount + 2);
|
|
const int32 SliceCount = Stream.RandRange(-2, ItemCount + 2);
|
|
const int32 ExpectedStart = FMath::Clamp(StartIndex, 0, ItemCount);
|
|
const int32 ExpectedSliceCount = FMath::Max(0, FMath::Min(SliceCount, ItemCount - ExpectedStart));
|
|
const TArray<int32> ExpectedSlice = SliceReference(Source, ExpectedStart, ExpectedSliceCount);
|
|
TArray<int32> SliceResult;
|
|
UDirectiveUtilArrayFunctionLibrary::GenericArray_Slice(
|
|
&Source,
|
|
ArrayProperty,
|
|
StartIndex,
|
|
SliceCount,
|
|
&SliceResult,
|
|
ArrayProperty);
|
|
TestEqual(Label + TEXT(" Slice"), SliceResult, ExpectedSlice);
|
|
|
|
const int32 PageSize = CountIndex % 4 == 0 ? 0 : 1 << (CountIndex % 7);
|
|
const int32 ExpectedPageCount = PageSize > 0 ? FMath::DivideAndRoundUp(ItemCount, PageSize) : 0;
|
|
for (const int32 PageIndex : {-1, 0, FMath::Max(0, ExpectedPageCount - 1), ExpectedPageCount})
|
|
{
|
|
TArray<int32> PageResult;
|
|
int32 PageCount = -1;
|
|
const bool bPageValid = UDirectiveUtilArrayFunctionLibrary::GenericArray_GetPage(
|
|
&Source,
|
|
ArrayProperty,
|
|
PageIndex,
|
|
PageSize,
|
|
&PageResult,
|
|
ArrayProperty,
|
|
&PageCount);
|
|
const bool bExpectedValid = PageSize > 0 && PageIndex >= 0 && PageIndex < ExpectedPageCount;
|
|
const TArray<int32> ExpectedPage = bExpectedValid
|
|
? SliceReference(Source, PageIndex * PageSize, FMath::Min(PageSize, ItemCount - PageIndex * PageSize))
|
|
: TArray<int32>();
|
|
const FString PageLabel = Label + FString::Printf(TEXT(" Page index=%d size=%d"), PageIndex, PageSize);
|
|
TestEqual(PageLabel + TEXT(" validity"), bPageValid, bExpectedValid);
|
|
TestEqual(
|
|
PageLabel + TEXT(" count"),
|
|
PageCount,
|
|
PageSize > 0 && PageIndex >= 0 ? ExpectedPageCount : 0);
|
|
TestEqual(PageLabel + TEXT(" values"), PageResult, ExpectedPage);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|