// 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( UDirectiveUtilTestObject::StaticClass(), GET_MEMBER_NAME_CHECKED(UDirectiveUtilTestObject, TestArray)); } TArray MakeSequentialValues(const int32 Count) { TArray Values; Values.SetNumUninitialized(Count); for (int32 Index = 0; Index < Count; ++Index) { Values[Index] = Index; } return Values; } TArray MakeRepeatingValues(const int32 Count, const int32 DistinctCount) { TArray Values; Values.Reserve(Count); for (int32 Index = 0; Index < Count; ++Index) { Values.Add(Index % DistinctCount); } return Values; } TArray MakeDistinctReference(const TArray& Values) { TArray Result; for (const int32 Value : Values) { Result.AddUnique(Value); } return Result; } bool FindMostCommonReference(const TArray& Values, int32& OutValue, int32& OutCount) { OutValue = 0; OutCount = 0; TMap Counts; for (const int32 Value : Values) { ++Counts.FindOrAdd(Value); } for (const TPair& 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& Values, const int32 QueryValue) { int32 Count = 0; for (const int32 Value : Values) { Count += Value == QueryValue ? 1 : 0; } return Count; } TArray RemoveAllReference(const TArray& Values, const int32 QueryValue) { TArray Result; Result.Reserve(Values.Num()); for (const int32 Value : Values) { if (Value != QueryValue) { Result.Add(Value); } } return Result; } TArray SliceReference(const TArray& Values, const int32 StartIndex, const int32 Count) { TArray Result; Result.Reserve(Count); for (int32 Index = 0; Index < Count; ++Index) { Result.Add(Values[StartIndex + Index]); } return Result; } TArray MakeSampleReference(const int32 SourceCount, const int32 RequestedCount, const int32 Seed) { TArray AvailableIndices = MakeSequentialValues(SourceCount); TArray 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 TArray MakeRotationReference(const TArray& Values, const int32 Shift) { if (Values.IsEmpty()) { return Values; } int32 NormalizedShift = Shift % Values.Num(); if (NormalizedShift < 0) { NormalizedShift += Values.Num(); } TArray 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(); struct FScenario { int32 ItemCount; int32 DistinctCount; }; const TArray 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 Source = MakeRepeatingValues(Scenario.ItemCount, Scenario.DistinctCount); const TArray 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 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 Source = MakeSequentialValues(SourceCount); const TArray RequestedCounts = {-1, 0, 1, SourceCount / 4, SourceCount, SourceCount + 5}; for (const int32 RequestedCount : RequestedCounts) { FRandomStream FirstStream(Seed); FRandomStream SecondStream(Seed); TArray FirstSample; TArray 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 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 BoundarySource = MakeSequentialValues(100); for (const int32 RequestedCount : {24, 25, 26, 75, 100}) { FRandomStream RandomStream(Seed); TArray 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 ReplacementSource = MakeSequentialValues(7); for (const int32 RequestedCount : {0, 1, 7, 14, 100}) { FRandomStream FirstStream(Seed); FRandomStream SecondStream(Seed); TArray FirstSample; TArray 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 Source = MakeSequentialValues(SourceCount); TArray Weights; Weights.Reserve(SourceCount); TSet SelectableValues; for (int32 Index = 0; Index < SourceCount; ++Index) { const float Weight = Index % 3 == 0 ? 0.0f : static_cast((Index % 7) + 1); Weights.Add(Weight); if (Weight > 0.0f) { SelectableValues.Add(Index); } } const TArray 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 FirstSample; TArray 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 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 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 Source = MakeSequentialValues(Scenario.ItemCount); TArray 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 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 SliceSource = MakeSequentialValues(10); struct FSliceScenario { int32 StartIndex; int32 Count; }; const TArray 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 ExpectedSlice; for (int32 Offset = 0; Offset < CopyCount; ++Offset) { ExpectedSlice.Add(SliceSource[StartIndex + Offset]); } TArray 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 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( 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 Source = MakeSequentialValues(ItemCount); const TArray Shifts = { 0, 1, -1, ItemCount, ItemCount + 1, -ItemCount - 1, MAX_int32, MIN_int32 }; for (const int32 Shift : Shifts) { TArray 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 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 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 Strings; TArray ExpectedStrings; TArray Names; TArray 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(); const TArray 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 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 ExpectedDistinct = MakeDistinctReference(Source); TestObject->TestArray = Source; UDirectiveUtilArrayFunctionLibrary::GenericArray_RemoveDuplicates(&TestObject->TestArray, ArrayProperty); TestEqual(Label + TEXT(" RemoveDuplicates"), TestObject->TestArray, ExpectedDistinct); TArray 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 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 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 ExpectedSlice = SliceReference(Source, ExpectedStart, ExpectedSliceCount); TArray 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 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 ExpectedPage = bExpectedValid ? SliceReference(Source, PageIndex * PageSize, FMath::Min(PageSize, ItemCount - PageIndex * PageSize)) : TArray(); 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; }