2026-08-15 21:06:43 +02:00
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
2026-07-15 19:17:47 +02:00
# include "Libraries/DirectiveUtilArrayFunctionLibrary.h"
2026-08-15 21:06:43 +02:00
# include "Async/ParallelFor.h"
2026-07-15 19:17:47 +02:00
# include "Tests/DirectiveUtilTestObject.h"
2026-08-15 21:06:43 +02:00
# include <limits>
2026-07-15 19:17:47 +02:00
# include "Misc/AutomationTest.h"
2026-08-15 21:06:43 +02:00
namespace
{
TArray < int32 > BuildReferenceSample ( const int32 SourceCount , const int32 Count , const int32 Seed )
{
TArray < int32 > AvailableIndices ;
AvailableIndices . SetNumUninitialized ( SourceCount ) ;
for ( int32 Index = 0 ; Index < SourceCount ; + + Index )
{
AvailableIndices [ Index ] = Index ;
}
FRandomStream RandomStream ( Seed ) ;
TArray < int32 > Result ;
const int32 SampleCount = FMath : : Min ( SourceCount , Count ) ;
Result . Reserve ( SampleCount ) ;
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 ;
}
TArray < int32 > BuildReferenceDistinct ( const TArray < int32 > & Values )
{
TArray < int32 > Result ;
for ( const int32 Value : Values )
{
if ( ! Result . Contains ( Value ) )
{
Result . Add ( Value ) ;
}
}
return Result ;
}
TArray < int32 > BuildReferenceRemoval (
const TArray < int32 > & Values ,
const TArray < int32 > & Indices )
{
TArray < int32 > Result ;
Result . Reserve ( Values . Num ( ) ) ;
for ( int32 Index = 0 ; Index < Values . Num ( ) ; + + Index )
{
if ( ! Indices . Contains ( Index ) )
{
Result . Add ( Values [ Index ] ) ;
}
}
return Result ;
}
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST ( FDirectiveUtilArrayFunctionLibraryTest , " DirectiveUtilities.ArrayFunctionLibraryTests " , EAutomationTestFlags : : EditorContext | EAutomationTestFlags : : ClientContext | EAutomationTestFlags : : EngineFilter )
2026-07-15 19:17:47 +02:00
bool FDirectiveUtilArrayFunctionLibraryTest : : RunTest ( const FString & Parameters )
{
UDirectiveUtilTestObject * TestObject = NewObject < UDirectiveUtilTestObject > ( ) ;
TestObject - > TestArray = { 1 , 2 , 3 , 4 , 5 } ;
FArrayProperty * ArrayProperty = FindFProperty < FArrayProperty > ( UDirectiveUtilTestObject : : StaticClass ( ) , GET_MEMBER_NAME_CHECKED ( UDirectiveUtilTestObject , TestArray ) ) ;
const int32 NextIndex = UDirectiveUtilArrayFunctionLibrary : : GenericArray_NextIndex ( & TestObject - > TestArray , ArrayProperty , 2 , false ) ;
const int32 PreviousIndex = UDirectiveUtilArrayFunctionLibrary : : GenericArray_PreviousIndex ( & TestObject - > TestArray , ArrayProperty , 2 , false ) ;
TestEqual ( " Array_NextIndex should return the next index in the array " , NextIndex , 3 ) ;
TestEqual ( " Array_PreviousIndex should return the previous index in the array " , PreviousIndex , 1 ) ;
const int32 NextIndexLooped = UDirectiveUtilArrayFunctionLibrary : : GenericArray_NextIndex ( & TestObject - > TestArray , ArrayProperty , 4 , true ) ;
const int32 PreviousIndexLooped = UDirectiveUtilArrayFunctionLibrary : : GenericArray_PreviousIndex ( & TestObject - > TestArray , ArrayProperty , 0 , true ) ;
TestEqual ( " Array_NextIndex should return the first index when looping " , NextIndexLooped , 0 ) ;
TestEqual ( " Array_PreviousIndex should return the last index when looping " , PreviousIndexLooped , 4 ) ;
const int32 NextIndexNonLooped = UDirectiveUtilArrayFunctionLibrary : : GenericArray_NextIndex ( & TestObject - > TestArray , ArrayProperty , 4 , false ) ;
const int32 PreviousIndexNonLooped = UDirectiveUtilArrayFunctionLibrary : : GenericArray_PreviousIndex ( & TestObject - > TestArray , ArrayProperty , 0 , false ) ;
TestEqual ( " Array_NextIndex should return the last index when not looping " , NextIndexNonLooped , 4 ) ;
TestEqual ( " Array_PreviousIndex should return the first index when not looping " , PreviousIndexNonLooped , 0 ) ;
const int32 NextIndexOutOfBounds = UDirectiveUtilArrayFunctionLibrary : : GenericArray_NextIndex ( & TestObject - > TestArray , ArrayProperty , 5 , false ) ;
const int32 PreviousIndexOutOfBounds = UDirectiveUtilArrayFunctionLibrary : : GenericArray_PreviousIndex ( & TestObject - > TestArray , ArrayProperty , - 1 , false ) ;
TestEqual ( " Array_NextIndex should return the last index when out of bounds " , NextIndexOutOfBounds , 4 ) ;
TestEqual ( " Array_PreviousIndex should return the first index when out of bounds " , PreviousIndexOutOfBounds , 0 ) ;
TestEqual ( " Array_NextIndex should clamp a negative input index to 0 (no loop) " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_NextIndex ( & TestObject - > TestArray , ArrayProperty , - 2 , false ) , 0 ) ;
TestEqual ( " Array_NextIndex should clamp a negative input index to 0 (loop) " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_NextIndex ( & TestObject - > TestArray , ArrayProperty , - 2 , true ) , 0 ) ;
TestEqual ( " Array_NextIndex should wrap a large input index to 0 when looping " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_NextIndex ( & TestObject - > TestArray , ArrayProperty , 10 , true ) , 0 ) ;
TestEqual ( " Array_NextIndex should clamp a large input index to the last index when not looping " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_NextIndex ( & TestObject - > TestArray , ArrayProperty , 10 , false ) , 4 ) ;
TestEqual ( " Array_PreviousIndex should clamp a large input index to the last index (no loop) " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_PreviousIndex ( & TestObject - > TestArray , ArrayProperty , 10 , false ) , 4 ) ;
TestEqual ( " Array_PreviousIndex should clamp a large input index to the last index (loop) " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_PreviousIndex ( & TestObject - > TestArray , ArrayProperty , 10 , true ) , 4 ) ;
TestEqual ( " Array_PreviousIndex should wrap a negative input index to the last index when looping " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_PreviousIndex ( & TestObject - > TestArray , ArrayProperty , - 1 , true ) , 4 ) ;
2026-08-15 21:06:43 +02:00
TestEqual ( " Array_NextIndex should handle the maximum integer index " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_NextIndex ( & TestObject - > TestArray , ArrayProperty , MAX_int32 , false ) , 4 ) ;
TestEqual ( " Array_PreviousIndex should handle the minimum integer index " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_PreviousIndex ( & TestObject - > TestArray , ArrayProperty , MIN_int32 , false ) , 0 ) ;
2026-07-15 19:17:47 +02:00
TestObject - > TestArray = { 1 , 2 , 3 , 4 , 5 } ;
int32 FirstItem = - 1 ;
const bool bGotFirst = UDirectiveUtilArrayFunctionLibrary : : GenericArray_GetFirstItem ( & TestObject - > TestArray , ArrayProperty , & FirstItem ) ;
TestTrue ( " GetFirstItem should succeed on a non-empty array " , bGotFirst ) ;
TestEqual ( " GetFirstItem should return the first element " , FirstItem , 1 ) ;
int32 LastItem = - 1 ;
const bool bGotLast = UDirectiveUtilArrayFunctionLibrary : : GenericArray_GetLastItem ( & TestObject - > TestArray , ArrayProperty , & LastItem ) ;
TestTrue ( " GetLastItem should succeed on a non-empty array " , bGotLast ) ;
TestEqual ( " GetLastItem should return the last element " , LastItem , 5 ) ;
int32 IndexItem = - 1 ;
const bool bGotIndex = UDirectiveUtilArrayFunctionLibrary : : GenericArray_GetItemAtIndex ( & TestObject - > TestArray , ArrayProperty , 2 , & IndexItem ) ;
TestTrue ( " GetItemAtIndex should succeed for a valid index " , bGotIndex ) ;
TestEqual ( " GetItemAtIndex should return the element at the index " , IndexItem , 3 ) ;
int32 OutOfRangeItem = 777 ;
const bool bGotOutOfRange = UDirectiveUtilArrayFunctionLibrary : : GenericArray_GetItemAtIndex ( & TestObject - > TestArray , ArrayProperty , 10 , & OutOfRangeItem ) ;
TestFalse ( " GetItemAtIndex should fail for an out-of-range index " , bGotOutOfRange ) ;
TestEqual ( " GetItemAtIndex should reset the output to default on failure " , OutOfRangeItem , 0 ) ;
int32 RandomItem = - 1 ;
int32 RandomIndex = - 1 ;
const bool bGotRandom = UDirectiveUtilArrayFunctionLibrary : : GenericArray_GetRandomItem ( & TestObject - > TestArray , ArrayProperty , & RandomItem , & RandomIndex ) ;
TestTrue ( " GetRandomItem should succeed on a non-empty array " , bGotRandom ) ;
TestTrue ( " GetRandomItem should return a valid index " , TestObject - > TestArray . IsValidIndex ( RandomIndex ) ) ;
if ( TestObject - > TestArray . IsValidIndex ( RandomIndex ) )
{
TestEqual ( " GetRandomItem item should match the element at the returned index " , RandomItem , TestObject - > TestArray [ RandomIndex ] ) ;
}
TestObject - > TestArray . Empty ( ) ;
int32 EmptyItem = 999 ;
TestFalse ( " GetFirstItem should fail on an empty array " , UDirectiveUtilArrayFunctionLibrary : : GenericArray_GetFirstItem ( & TestObject - > TestArray , ArrayProperty , & EmptyItem ) ) ;
TestEqual ( " GetFirstItem should reset the output to default on an empty array " , EmptyItem , 0 ) ;
EmptyItem = 999 ;
TestFalse ( " GetLastItem should fail on an empty array " , UDirectiveUtilArrayFunctionLibrary : : GenericArray_GetLastItem ( & TestObject - > TestArray , ArrayProperty , & EmptyItem ) ) ;
TestEqual ( " GetLastItem should reset the output to default on an empty array " , EmptyItem , 0 ) ;
int32 EmptyRandomItem = 999 ;
int32 EmptyRandomIndex = 5 ;
TestFalse ( " GetRandomItem should fail on an empty array " , UDirectiveUtilArrayFunctionLibrary : : GenericArray_GetRandomItem ( & TestObject - > TestArray , ArrayProperty , & EmptyRandomItem , & EmptyRandomIndex ) ) ;
TestEqual ( " GetRandomItem should return INDEX_NONE on an empty array " , EmptyRandomIndex , static_cast < int32 > ( INDEX_NONE ) ) ;
TestEqual ( " GetRandomItem should reset the output to default on an empty array " , EmptyRandomItem , 0 ) ;
TestObject - > TestArray = { 1 , 2 , 3 } ;
int32 PoppedItem = - 1 ;
const bool bPopped = UDirectiveUtilArrayFunctionLibrary : : GenericArray_Pop ( & TestObject - > TestArray , ArrayProperty , & PoppedItem ) ;
TestTrue ( " Pop should succeed on a non-empty array " , bPopped ) ;
TestEqual ( " Pop should return the last element " , PoppedItem , 3 ) ;
TestEqual ( " Pop should shrink the array by one " , TestObject - > TestArray . Num ( ) , 2 ) ;
TestEqual ( " Pop should leave the new last element intact " , TestObject - > TestArray . Last ( ) , 2 ) ;
TestObject - > TestArray = { 1 , 2 , 3 } ;
int32 PoppedFirst = - 1 ;
const bool bPoppedFirst = UDirectiveUtilArrayFunctionLibrary : : GenericArray_PopFirst ( & TestObject - > TestArray , ArrayProperty , & PoppedFirst ) ;
TestTrue ( " PopFirst should succeed on a non-empty array " , bPoppedFirst ) ;
TestEqual ( " PopFirst should return the first element " , PoppedFirst , 1 ) ;
TestEqual ( " PopFirst should shrink the array by one " , TestObject - > TestArray . Num ( ) , 2 ) ;
TestEqual ( " PopFirst should shift the remaining elements down " , TestObject - > TestArray [ 0 ] , 2 ) ;
TestObject - > TestArray . Empty ( ) ;
int32 PoppedEmpty = 888 ;
TestFalse ( " Pop should fail on an empty array " , UDirectiveUtilArrayFunctionLibrary : : GenericArray_Pop ( & TestObject - > TestArray , ArrayProperty , & PoppedEmpty ) ) ;
TestEqual ( " Pop should reset the output to default on an empty array " , PoppedEmpty , 0 ) ;
TestFalse ( " PopFirst should fail on an empty array " , UDirectiveUtilArrayFunctionLibrary : : GenericArray_PopFirst ( & TestObject - > TestArray , ArrayProperty , & PoppedEmpty ) ) ;
TestObject - > TestArray = { 10 , 20 , 30 , 40 } ;
const bool bRemoved = UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveAtSwap ( & TestObject - > TestArray , ArrayProperty , 1 ) ;
TestTrue ( " RemoveAtSwap should succeed for a valid index " , bRemoved ) ;
TestEqual ( " RemoveAtSwap should shrink the array by one " , TestObject - > TestArray . Num ( ) , 3 ) ;
TestFalse ( " RemoveAtSwap should have removed the target element " , TestObject - > TestArray . Contains ( 20 ) ) ;
TestEqual ( " RemoveAtSwap should move the previously-last element into the removed slot " , TestObject - > TestArray [ 1 ] , 40 ) ;
const bool bRemovedOutOfRange = UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveAtSwap ( & TestObject - > TestArray , ArrayProperty , 99 ) ;
TestFalse ( " RemoveAtSwap should fail for an out-of-range index " , bRemovedOutOfRange ) ;
TestEqual ( " RemoveAtSwap should not change the array on failure " , TestObject - > TestArray . Num ( ) , 3 ) ;
TestObject - > TestArray = { 10 , 20 , 30 } ;
const bool bRemovedLast = UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveAtSwap ( & TestObject - > TestArray , ArrayProperty , 2 ) ;
TestTrue ( " RemoveAtSwap should succeed when removing the last element " , bRemovedLast ) ;
TestEqual ( " RemoveAtSwap on the last element should shrink the array " , TestObject - > TestArray . Num ( ) , 2 ) ;
TestEqual ( " RemoveAtSwap on the last element should preserve the order of the rest " , TestObject - > TestArray [ 1 ] , 20 ) ;
2026-08-15 21:06:43 +02:00
int32 ItemToRemove = 2 ;
TestObject - > TestArray = { 1 , 2 , 3 , 2 , 4 , 2 } ;
TestTrue (
" RemoveAllOccurrences should report matching values " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveAllOccurrences ( & TestObject - > TestArray , ArrayProperty , & ItemToRemove ) ) ;
TestEqual (
" RemoveAllOccurrences should remove every match and preserve survivor order " ,
TestObject - > TestArray ,
TArray < int32 > ( { 1 , 3 , 4 } ) ) ;
ItemToRemove = 9 ;
const TArray < int32 > UnchangedValues = TestObject - > TestArray ;
TestFalse (
" RemoveAllOccurrences should report an absent value " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveAllOccurrences ( & TestObject - > TestArray , ArrayProperty , & ItemToRemove ) ) ;
TestEqual ( " RemoveAllOccurrences should not change an array without matches " , TestObject - > TestArray , UnchangedValues ) ;
TestObject - > TestArray . Empty ( ) ;
TestFalse (
" RemoveAllOccurrences should report false for an empty array " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveAllOccurrences ( & TestObject - > TestArray , ArrayProperty , & ItemToRemove ) ) ;
ItemToRemove = 5 ;
TestObject - > TestArray = { 5 , 5 , 5 } ;
TestTrue (
" RemoveAllOccurrences should remove an all-matching array " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveAllOccurrences ( & TestObject - > TestArray , ArrayProperty , & ItemToRemove ) ) ;
TestTrue ( " RemoveAllOccurrences should leave an all-matching array empty " , TestObject - > TestArray . IsEmpty ( ) ) ;
TestObject - > TestArray = { 0 , 1 , 2 , 3 , 4 , 5 , 6 } ;
TestEqual (
" RemoveAtIndices should remove each valid index once " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveAtIndices (
& TestObject - > TestArray ,
ArrayProperty ,
TArray < int32 > ( { 5 , 1 , 5 , - 1 , 99 , 3 } ) ) ,
3 ) ;
TestEqual (
" RemoveAtIndices should preserve survivor order " ,
TestObject - > TestArray ,
TArray < int32 > ( { 0 , 2 , 4 , 6 } ) ) ;
const TArray < int32 > BeforeInvalidIndices = TestObject - > TestArray ;
TestEqual (
" RemoveAtIndices should report zero for invalid indices " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveAtIndices (
& TestObject - > TestArray ,
ArrayProperty ,
TArray < int32 > ( { - 5 , 10 , 10 } ) ) ,
0 ) ;
TestEqual (
" RemoveAtIndices should not modify the target when no indices are valid " ,
TestObject - > TestArray ,
BeforeInvalidIndices ) ;
TestObject - > TestArray = { 10 , 20 , 30 } ;
TestEqual (
" RemoveAtIndices should remove the full array " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveAtIndices (
& TestObject - > TestArray ,
ArrayProperty ,
TArray < int32 > ( { 2 , 0 , 1 , 1 } ) ) ,
3 ) ;
TestTrue ( " RemoveAtIndices should leave the array empty when every index is removed " , TestObject - > TestArray . IsEmpty ( ) ) ;
TestObject - > TestArray = { 0 , 2 , 4 } ;
TestEqual (
" RemoveAtIndices should support using the target array as the index array " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveAtIndices (
& TestObject - > TestArray ,
ArrayProperty ,
TestObject - > TestArray ) ,
2 ) ;
TestEqual (
" RemoveAtIndices should read aliased indices before modifying the target " ,
TestObject - > TestArray ,
TArray < int32 > ( { 2 } ) ) ;
TestTrue ( " Integer arrays should use the bulk append path " , ArrayProperty - > Inner - > HasAnyPropertyFlags ( CPF_IsPlainOldData ) ) ;
const TArray < int32 > IntegerAppendSource = { 3 , 4 , 5 } ;
TestObject - > TestArray = { 1 , 2 } ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_AppendOptimized (
& TestObject - > TestArray ,
ArrayProperty ,
& IntegerAppendSource ,
ArrayProperty ) ;
TestEqual (
" AppendOptimized should append POD values in order " ,
TestObject - > TestArray ,
TArray < int32 > ( { 1 , 2 , 3 , 4 , 5 } ) ) ;
TestEqual (
" AppendOptimized should not modify the source array " ,
IntegerAppendSource ,
TArray < int32 > ( { 3 , 4 , 5 } ) ) ;
TestObject - > TestArray . Empty ( ) ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_AppendOptimized (
& TestObject - > TestArray ,
ArrayProperty ,
& IntegerAppendSource ,
ArrayProperty ) ;
TestEqual (
" AppendOptimized should append into an empty target array " ,
TestObject - > TestArray ,
IntegerAppendSource ) ;
const TArray < int32 > EmptyAppendSource ;
const TArray < int32 > BeforeEmptyAppend = TestObject - > TestArray ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_AppendOptimized (
& TestObject - > TestArray ,
ArrayProperty ,
& EmptyAppendSource ,
ArrayProperty ) ;
TestEqual ( " AppendOptimized should ignore an empty source array " , TestObject - > TestArray , BeforeEmptyAppend ) ;
TestObject - > TestArray = { 7 , 8 , 9 } ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_AppendOptimized (
& TestObject - > TestArray ,
ArrayProperty ,
& TestObject - > TestArray ,
ArrayProperty ) ;
TestEqual (
" AppendOptimized should support appending an array to itself " ,
TestObject - > TestArray ,
TArray < int32 > ( { 7 , 8 , 9 , 7 , 8 , 9 } ) ) ;
const TArray < int32 > IntegerInsertSource = { 7 , 8 } ;
TestObject - > TestArray = { 1 , 2 , 3 } ;
TestTrue (
" InsertOptimized should insert POD values " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_InsertOptimized (
& TestObject - > TestArray ,
ArrayProperty ,
& IntegerInsertSource ,
ArrayProperty ,
1 ) ) ;
TestEqual (
" InsertOptimized should preserve target and source order " ,
TestObject - > TestArray ,
TArray < int32 > ( { 1 , 7 , 8 , 2 , 3 } ) ) ;
TestEqual (
" InsertOptimized should not modify the source array " ,
IntegerInsertSource ,
TArray < int32 > ( { 7 , 8 } ) ) ;
TestObject - > TestArray = { 1 , 2 , 3 } ;
TestTrue (
" InsertOptimized should insert at the front " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_InsertOptimized (
& TestObject - > TestArray ,
ArrayProperty ,
& IntegerInsertSource ,
ArrayProperty ,
0 ) ) ;
TestEqual (
" InsertOptimized should preserve order at the front " ,
TestObject - > TestArray ,
TArray < int32 > ( { 7 , 8 , 1 , 2 , 3 } ) ) ;
TestObject - > TestArray = { 1 , 2 , 3 } ;
TestTrue (
" InsertOptimized should insert at the end " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_InsertOptimized (
& TestObject - > TestArray ,
ArrayProperty ,
& IntegerInsertSource ,
ArrayProperty ,
TestObject - > TestArray . Num ( ) ) ) ;
TestEqual (
" InsertOptimized should preserve order at the end " ,
TestObject - > TestArray ,
TArray < int32 > ( { 1 , 2 , 3 , 7 , 8 } ) ) ;
const TArray < int32 > EmptyInsertSource ;
const TArray < int32 > BeforeEmptyInsert = TestObject - > TestArray ;
TestFalse (
" InsertOptimized should report an empty source " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_InsertOptimized (
& TestObject - > TestArray ,
ArrayProperty ,
& EmptyInsertSource ,
ArrayProperty ,
1 ) ) ;
TestEqual ( " InsertOptimized should ignore an empty source " , TestObject - > TestArray , BeforeEmptyInsert ) ;
TestFalse (
" InsertOptimized should reject a negative index " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_InsertOptimized (
& TestObject - > TestArray ,
ArrayProperty ,
& IntegerInsertSource ,
ArrayProperty ,
- 1 ) ) ;
TestEqual ( " InsertOptimized should not modify the target for a negative index " , TestObject - > TestArray , BeforeEmptyInsert ) ;
TestFalse (
" InsertOptimized should reject an index beyond the array end " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_InsertOptimized (
& TestObject - > TestArray ,
ArrayProperty ,
& IntegerInsertSource ,
ArrayProperty ,
TestObject - > TestArray . Num ( ) + 1 ) ) ;
TestEqual ( " InsertOptimized should not modify the target for a large index " , TestObject - > TestArray , BeforeEmptyInsert ) ;
TestObject - > TestArray = { 4 , 5 , 6 } ;
TestTrue (
" InsertOptimized should support inserting an array into itself " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_InsertOptimized (
& TestObject - > TestArray ,
ArrayProperty ,
& TestObject - > TestArray ,
ArrayProperty ,
1 ) ) ;
TestEqual (
" InsertOptimized should preserve self-inserted values " ,
TestObject - > TestArray ,
TArray < int32 > ( { 4 , 4 , 5 , 6 , 5 , 6 } ) ) ;
2026-07-15 19:17:47 +02:00
TestObject - > TestArray = { 1 , 2 , 2 , 3 , 1 , 4 } ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveDuplicates ( & TestObject - > TestArray , ArrayProperty ) ;
TestEqual ( " RemoveDuplicates should remove all duplicate entries " , TestObject - > TestArray . Num ( ) , 4 ) ;
if ( TestObject - > TestArray . Num ( ) = = 4 )
{
TestEqual ( " RemoveDuplicates should keep the first occurrence (index 0) " , TestObject - > TestArray [ 0 ] , 1 ) ;
TestEqual ( " RemoveDuplicates should keep the first occurrence (index 1) " , TestObject - > TestArray [ 1 ] , 2 ) ;
TestEqual ( " RemoveDuplicates should keep the first occurrence (index 2) " , TestObject - > TestArray [ 2 ] , 3 ) ;
TestEqual ( " RemoveDuplicates should keep the first occurrence (index 3) " , TestObject - > TestArray [ 3 ] , 4 ) ;
}
TestObject - > TestArray = { 5 , 5 , 5 } ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveDuplicates ( & TestObject - > TestArray , ArrayProperty ) ;
TestEqual ( " RemoveDuplicates should collapse an all-duplicates array to one element " , TestObject - > TestArray . Num ( ) , 1 ) ;
if ( TestObject - > TestArray . Num ( ) = = 1 )
{
TestEqual ( " RemoveDuplicates should keep the single remaining value " , TestObject - > TestArray [ 0 ] , 5 ) ;
}
TestObject - > TestArray . Empty ( ) ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveDuplicates ( & TestObject - > TestArray , ArrayProperty ) ;
TestEqual ( " RemoveDuplicates on an empty array should leave it empty " , TestObject - > TestArray . Num ( ) , 0 ) ;
TestObject - > TestArray = { 10 , 20 , 30 , 40 , 50 } ;
TArray < int32 > SliceOut ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_Slice ( & TestObject - > TestArray , ArrayProperty , 1 , 3 , & SliceOut , ArrayProperty ) ;
TestEqual ( " Slice should copy a contiguous range " , SliceOut , TArray < int32 > ( { 20 , 30 , 40 } ) ) ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_Slice ( & TestObject - > TestArray , ArrayProperty , 3 , 99 , & SliceOut , ArrayProperty ) ;
TestEqual ( " Slice should clamp Count to the available elements " , SliceOut , TArray < int32 > ( { 40 , 50 } ) ) ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_Slice ( & TestObject - > TestArray , ArrayProperty , - 5 , 2 , & SliceOut , ArrayProperty ) ;
TestEqual ( " Slice should clamp a negative start index to 0 " , SliceOut , TArray < int32 > ( { 10 , 20 } ) ) ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_Slice ( & TestObject - > TestArray , ArrayProperty , 0 , 0 , & SliceOut , ArrayProperty ) ;
TestEqual ( " Slice with Count 0 should be empty " , SliceOut . Num ( ) , 0 ) ;
2026-08-15 21:06:43 +02:00
TestObject - > TestArray = { 10 , 20 , 30 , 40 } ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_Slice ( & TestObject - > TestArray , ArrayProperty , 1 , 2 , & TestObject - > TestArray , ArrayProperty ) ;
TestEqual ( " Slice should support using the source array as its output " , TestObject - > TestArray , TArray < int32 > ( { 20 , 30 } ) ) ;
2026-07-15 19:17:47 +02:00
TestObject - > TestArray = { 1 , 2 , 3 , 4 , 5 } ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_Rotate ( & TestObject - > TestArray , ArrayProperty , 2 ) ;
TestEqual ( " Rotate by +2 should rotate toward the end " , TestObject - > TestArray , TArray < int32 > ( { 4 , 5 , 1 , 2 , 3 } ) ) ;
TestObject - > TestArray = { 1 , 2 , 3 , 4 , 5 } ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_Rotate ( & TestObject - > TestArray , ArrayProperty , - 1 ) ;
TestEqual ( " Rotate by -1 should rotate toward the start " , TestObject - > TestArray , TArray < int32 > ( { 2 , 3 , 4 , 5 , 1 } ) ) ;
TestObject - > TestArray = { 1 , 2 , 3 } ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_Rotate ( & TestObject - > TestArray , ArrayProperty , 3 ) ;
TestEqual ( " Rotate by Length should be a no-op " , TestObject - > TestArray , TArray < int32 > ( { 1 , 2 , 3 } ) ) ;
TestObject - > TestArray = { 1 , 2 , 2 , 3 , 1 , 4 } ;
TArray < int32 > DistinctOut ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_GetDistinct ( & TestObject - > TestArray , ArrayProperty , & DistinctOut , ArrayProperty ) ;
TestEqual ( " GetDistinct should keep first occurrences in order " , DistinctOut , TArray < int32 > ( { 1 , 2 , 3 , 4 } ) ) ;
TestEqual ( " GetDistinct should not modify the source array " , TestObject - > TestArray . Num ( ) , 6 ) ;
2026-08-15 21:06:43 +02:00
TestObject - > TestArray = { 1 , 2 , 2 , 3 , 1 } ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_GetDistinct ( & TestObject - > TestArray , ArrayProperty , & TestObject - > TestArray , ArrayProperty ) ;
TestEqual ( " GetDistinct should support using the source array as its output " , TestObject - > TestArray , TArray < int32 > ( { 1 , 2 , 3 } ) ) ;
FArrayProperty * DistinctStringArrayProperty = FindFProperty < FArrayProperty > ( UDirectiveUtilTestObject : : StaticClass ( ) , GET_MEMBER_NAME_CHECKED ( UDirectiveUtilTestObject , TestStringArray ) ) ;
TestNotNull ( " TestStringArray property should be found " , DistinctStringArrayProperty ) ;
if ( DistinctStringArrayProperty )
{
TestObject - > TestStringArray = { TEXT ( " Alpha " ) , TEXT ( " Beta " ) , TEXT ( " Beta " ) } ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_GetDistinct (
& TestObject - > TestStringArray ,
DistinctStringArrayProperty ,
& TestObject - > TestStringArray ,
DistinctStringArrayProperty ) ;
TestEqual ( " GetDistinct should preserve in-place string values " , TestObject - > TestStringArray , TArray < FString > ( { TEXT ( " Alpha " ) , TEXT ( " Beta " ) } ) ) ;
}
2026-07-15 19:17:47 +02:00
TestObject - > TestArray = { 5 , 1 , 5 , 2 , 5 , 3 } ;
int32 ItemToCount = 5 ;
TestEqual ( " CountOccurrences should count matches " , UDirectiveUtilArrayFunctionLibrary : : GenericArray_CountOccurrences ( & TestObject - > TestArray , ArrayProperty , & ItemToCount ) , 3 ) ;
int32 MissingItem = 99 ;
TestEqual ( " CountOccurrences should return 0 for an absent item " , UDirectiveUtilArrayFunctionLibrary : : GenericArray_CountOccurrences ( & TestObject - > TestArray , ArrayProperty , & MissingItem ) , 0 ) ;
TestObject - > TestArray = { 7 , 7 , 8 , 7 , 9 , 8 } ;
int32 MostCommonItem = - 1 ;
int32 MostCommonCount = - 1 ;
const bool bGotMostCommon = UDirectiveUtilArrayFunctionLibrary : : GenericArray_GetMostCommon ( & TestObject - > TestArray , ArrayProperty , & MostCommonItem , & MostCommonCount ) ;
TestTrue ( " GetMostCommon should succeed on a non-empty array " , bGotMostCommon ) ;
TestEqual ( " GetMostCommon should return the most frequent element " , MostCommonItem , 7 ) ;
TestEqual ( " GetMostCommon should return the occurrence count " , MostCommonCount , 3 ) ;
TestObject - > TestArray . Empty ( ) ;
int32 EmptyMostItem = 5 ;
int32 EmptyMostCount = 5 ;
TestFalse ( " GetMostCommon should fail on an empty array " , UDirectiveUtilArrayFunctionLibrary : : GenericArray_GetMostCommon ( & TestObject - > TestArray , ArrayProperty , & EmptyMostItem , & EmptyMostCount ) ) ;
TestEqual ( " GetMostCommon should reset the count on an empty array " , EmptyMostCount , 0 ) ;
2026-08-15 21:06:43 +02:00
const TArray < FName > MutatingFunctions = {
GET_FUNCTION_NAME_CHECKED ( UDirectiveUtilArrayFunctionLibrary , Array_RemoveDuplicates ) ,
GET_FUNCTION_NAME_CHECKED ( UDirectiveUtilArrayFunctionLibrary , Array_AppendOptimized ) ,
GET_FUNCTION_NAME_CHECKED ( UDirectiveUtilArrayFunctionLibrary , Array_InsertOptimized ) ,
GET_FUNCTION_NAME_CHECKED ( UDirectiveUtilArrayFunctionLibrary , Array_Pop ) ,
GET_FUNCTION_NAME_CHECKED ( UDirectiveUtilArrayFunctionLibrary , Array_PopFirst ) ,
GET_FUNCTION_NAME_CHECKED ( UDirectiveUtilArrayFunctionLibrary , Array_RemoveAtSwap ) ,
GET_FUNCTION_NAME_CHECKED ( UDirectiveUtilArrayFunctionLibrary , Array_RemoveAtIndices ) ,
GET_FUNCTION_NAME_CHECKED ( UDirectiveUtilArrayFunctionLibrary , Array_RemoveAllOccurrences ) ,
GET_FUNCTION_NAME_CHECKED ( UDirectiveUtilArrayFunctionLibrary , Array_Rotate )
} ;
for ( const FName FunctionName : MutatingFunctions )
{
const UFunction * Function = UDirectiveUtilArrayFunctionLibrary : : StaticClass ( ) - > FindFunctionByName ( FunctionName ) ;
const FArrayProperty * TargetArrayProperty = Function ? FindFProperty < FArrayProperty > ( Function , TEXT ( " TargetArray " ) ) : nullptr ;
TestNotNull ( * FString : : Printf ( TEXT ( " %s should expose a TargetArray parameter " ) , * FunctionName . ToString ( ) ) , TargetArrayProperty ) ;
if ( TargetArrayProperty )
{
TestFalse ( * FString : : Printf ( TEXT ( " %s should expose TargetArray as mutable " ) , * FunctionName . ToString ( ) ) , TargetArrayProperty - > HasAnyPropertyFlags ( CPF_ConstParm ) ) ;
}
# if WITH_EDITOR
if ( Function )
{
TestFalse (
* FString : : Printf ( TEXT ( " %s should not advertise inert Blueprint thread safety " ) , * FunctionName . ToString ( ) ) ,
Function - > HasMetaData ( TEXT ( " BlueprintThreadSafe " ) ) ) ;
}
# endif
}
TArray < int32 > ParallelResults ;
ParallelResults . SetNumZeroed ( 64 ) ;
ParallelFor ( ParallelResults . Num ( ) , [ & ParallelResults , ArrayProperty ] ( const int32 TaskIndex )
{
const int32 RemovedValue = TaskIndex + 1000 ;
TArray < int32 > Values = { RemovedValue , 7 , RemovedValue , 11 } ;
const TArray < int32 > Appended = { 13 , RemovedValue } ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_AppendOptimized (
& Values , ArrayProperty , & Appended , ArrayProperty ) ;
const bool bRemoved = UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveAllOccurrences (
& Values , ArrayProperty , & RemovedValue ) ;
ParallelResults [ TaskIndex ] = bRemoved & & Values = = TArray < int32 > ( { 7 , 11 , 13 } ) ? 1 : 0 ;
} ) ;
TestTrue ( " Independent array operations should remain correct on worker tasks " , ParallelResults . Find ( 0 ) = = INDEX_NONE ) ;
TestObject - > TestArray = { 10 , 20 , 30 , 40 , 50 } ;
TArray < int32 > SampledValues ;
FRandomStream FirstStream ( 1337 ) ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_Sample ( & TestObject - > TestArray , ArrayProperty , 3 , false , & FirstStream , & SampledValues , ArrayProperty ) ;
TestEqual ( " Sample without replacement should return the requested count " , SampledValues . Num ( ) , 3 ) ;
TestEqual ( " Sample without replacement should contain unique values " , TSet < int32 > ( SampledValues ) . Num ( ) , 3 ) ;
TArray < int32 > RepeatedSample ;
FRandomStream SecondStream ( 1337 ) ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_Sample ( & TestObject - > TestArray , ArrayProperty , 3 , false , & SecondStream , & RepeatedSample , ArrayProperty ) ;
TestEqual ( " Sample from stream should be deterministic " , RepeatedSample , SampledValues ) ;
FRandomStream ClampedStream ( 42 ) ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_Sample ( & TestObject - > TestArray , ArrayProperty , 20 , false , & ClampedStream , & SampledValues , ArrayProperty ) ;
TestEqual ( " Sample without replacement should clamp to the source length " , SampledValues . Num ( ) , TestObject - > TestArray . Num ( ) ) ;
FRandomStream ReplacementStream ( 7 ) ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_Sample ( & TestObject - > TestArray , ArrayProperty , 20 , true , & ReplacementStream , & SampledValues , ArrayProperty ) ;
TestEqual ( " Sample with replacement should return the requested count " , SampledValues . Num ( ) , 20 ) ;
TestObject - > TestArray = { 99 } ;
FRandomStream SingleValueStream ( 7 ) ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_Sample ( & TestObject - > TestArray , ArrayProperty , 4 , true , & SingleValueStream , & SampledValues , ArrayProperty ) ;
TestEqual ( " Sample with replacement should repeat the only available value " , SampledValues , TArray < int32 > ( { 99 , 99 , 99 , 99 } ) ) ;
TestObject - > TestArray . Empty ( ) ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_Sample ( & TestObject - > TestArray , ArrayProperty , 4 , false , nullptr , & SampledValues , ArrayProperty ) ;
TestTrue ( " Sampling an empty array should return an empty array " , SampledValues . IsEmpty ( ) ) ;
TestObject - > TestArray = { 10 , 20 , 30 , 40 , 50 } ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_Sample ( & TestObject - > TestArray , ArrayProperty , 0 , false , nullptr , & SampledValues , ArrayProperty ) ;
TestTrue ( " Sampling zero values should return an empty array " , SampledValues . IsEmpty ( ) ) ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_Sample ( & TestObject - > TestArray , ArrayProperty , 1'000'001 , true , nullptr , & SampledValues , ArrayProperty ) ;
TestTrue ( " Sampling should reject an unsafe output count " , SampledValues . IsEmpty ( ) ) ;
const TArray < float > DeterministicWeights = { 1.0f , 2.0f , 3.0f , 4.0f , 5.0f } ;
TArray < int32 > WeightedSample ;
TArray < int32 > RepeatedWeightedSample ;
FRandomStream FirstWeightedStream ( 90210 ) ;
FRandomStream SecondWeightedStream ( 90210 ) ;
TestTrue (
" Weighted sampling should accept matching inputs " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_SampleWeighted (
& TestObject - > TestArray ,
ArrayProperty ,
DeterministicWeights ,
4 ,
true ,
& FirstWeightedStream ,
& WeightedSample ,
ArrayProperty ) ) ;
TestTrue (
" Weighted sampling from a stream should accept matching inputs " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_SampleWeighted (
& TestObject - > TestArray ,
ArrayProperty ,
DeterministicWeights ,
4 ,
true ,
& SecondWeightedStream ,
& RepeatedWeightedSample ,
ArrayProperty ) ) ;
TestEqual ( " Weighted sampling from a stream should be deterministic " , RepeatedWeightedSample , WeightedSample ) ;
TestFalse (
" Weighted sampling should reject an unsafe output count " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_SampleWeighted (
& TestObject - > TestArray ,
ArrayProperty ,
DeterministicWeights ,
1'000'001 ,
true ,
nullptr ,
& WeightedSample ,
ArrayProperty ) ) ;
TestTrue ( " Rejected weighted sampling should clear the output " , WeightedSample . IsEmpty ( ) ) ;
FRandomStream FirstUniqueWeightedStream ( 31415 ) ;
FRandomStream SecondUniqueWeightedStream ( 31415 ) ;
TestTrue (
" Weighted sampling without replacement should accept matching inputs " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_SampleWeighted (
& TestObject - > TestArray ,
ArrayProperty ,
DeterministicWeights ,
5 ,
false ,
& FirstUniqueWeightedStream ,
& WeightedSample ,
ArrayProperty ) ) ;
TestTrue (
" Repeated weighted sampling without replacement should accept matching inputs " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_SampleWeighted (
& TestObject - > TestArray ,
ArrayProperty ,
DeterministicWeights ,
5 ,
false ,
& SecondUniqueWeightedStream ,
& RepeatedWeightedSample ,
ArrayProperty ) ) ;
TestEqual ( " Weighted sampling without replacement should be deterministic " , RepeatedWeightedSample , WeightedSample ) ;
TestEqual ( " Weighted sampling without replacement should select each available value once " , TSet < int32 > ( WeightedSample ) . Num ( ) , 5 ) ;
FRandomStream UnchangedWeightedStream ( 8675309 ) ;
const int32 InitialWeightedSeed = UnchangedWeightedStream . GetCurrentSeed ( ) ;
TestTrue (
" A zero weighted sample should succeed " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_SampleWeighted (
& TestObject - > TestArray ,
ArrayProperty ,
DeterministicWeights ,
0 ,
false ,
& UnchangedWeightedStream ,
& WeightedSample ,
ArrayProperty ) ) ;
TestEqual ( " A zero weighted sample should not advance its random stream " , UnchangedWeightedStream . GetCurrentSeed ( ) , InitialWeightedSeed ) ;
TestObject - > TestArray = { 0 , 1 } ;
FRandomStream DistributionStream ( 1187 ) ;
TestTrue (
" Weighted sampling should accept proportional weights " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_SampleWeighted (
& TestObject - > TestArray ,
ArrayProperty ,
TArray < float > ( { 1.0f , 9.0f } ) ,
10000 ,
true ,
& DistributionStream ,
& WeightedSample ,
ArrayProperty ) ) ;
int32 HeavySelectionCount = 0 ;
for ( const int32 Value : WeightedSample )
{
HeavySelectionCount + = Value = = 1 ? 1 : 0 ;
}
TestTrue ( " Weighted sampling should follow the supplied proportions " , HeavySelectionCount > 8500 & & HeavySelectionCount < 9500 ) ;
TestObject - > TestArray = { 10 , 20 , 30 , 40 , 50 } ;
const TArray < float > SparseWeights = {
0.0f ,
1.0f ,
- 1.0f ,
std : : numeric_limits < float > : : quiet_NaN ( ) ,
std : : numeric_limits < float > : : infinity ( )
} ;
FRandomStream SparseStream ( 17 ) ;
TestTrue (
" Weighted sampling should ignore non-positive and non-finite weights " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_SampleWeighted (
& TestObject - > TestArray ,
ArrayProperty ,
SparseWeights ,
8 ,
true ,
& SparseStream ,
& WeightedSample ,
ArrayProperty ) ) ;
TestEqual ( " Weighted sampling should select the only positive-weight value " , WeightedSample , TArray < int32 > ( { 20 , 20 , 20 , 20 , 20 , 20 , 20 , 20 } ) ) ;
const TArray < float > TwoPositiveWeights = { 1.0f , 0.0f , 2.0f , 0.0f , 0.0f } ;
FRandomStream UniqueWeightedStream ( 44 ) ;
TestTrue (
" Weighted sampling without replacement should succeed " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_SampleWeighted (
& TestObject - > TestArray ,
ArrayProperty ,
TwoPositiveWeights ,
8 ,
false ,
& UniqueWeightedStream ,
& WeightedSample ,
ArrayProperty ) ) ;
TestEqual ( " Weighted sampling without replacement should clamp to positive-weight entries " , WeightedSample . Num ( ) , 2 ) ;
TestEqual ( " Weighted sampling without replacement should not repeat source indices " , TSet < int32 > ( WeightedSample ) . Num ( ) , 2 ) ;
TestTrue ( " Weighted sampling without replacement should include the first weighted value " , WeightedSample . Contains ( 10 ) ) ;
TestTrue ( " Weighted sampling without replacement should include the second weighted value " , WeightedSample . Contains ( 30 ) ) ;
WeightedSample = { 999 } ;
TestFalse (
" Weighted sampling should reject a mismatched weights array " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_SampleWeighted (
& TestObject - > TestArray ,
ArrayProperty ,
TArray < float > ( { 1.0f } ) ,
1 ,
false ,
nullptr ,
& WeightedSample ,
ArrayProperty ) ) ;
TestTrue ( " Weighted sampling should clear output after a weights mismatch " , WeightedSample . IsEmpty ( ) ) ;
TestFalse (
" Weighted sampling should reject a negative count " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_SampleWeighted (
& TestObject - > TestArray ,
ArrayProperty ,
TwoPositiveWeights ,
- 1 ,
false ,
nullptr ,
& WeightedSample ,
ArrayProperty ) ) ;
TestTrue ( " Weighted sampling should clear output after a negative count " , WeightedSample . IsEmpty ( ) ) ;
TestTrue (
" Weighted sampling should accept a zero count " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_SampleWeighted (
& TestObject - > TestArray ,
ArrayProperty ,
TwoPositiveWeights ,
0 ,
false ,
nullptr ,
& WeightedSample ,
ArrayProperty ) ) ;
TestTrue ( " Weighted sampling should return an empty zero-count result " , WeightedSample . IsEmpty ( ) ) ;
const TArray < float > EmptyWeights = { 0.0f , 0.0f , 0.0f , 0.0f , 0.0f } ;
TestFalse (
" Weighted sampling should reject a positive count when all weights are zero " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_SampleWeighted (
& TestObject - > TestArray ,
ArrayProperty ,
EmptyWeights ,
1 ,
false ,
nullptr ,
& WeightedSample ,
ArrayProperty ) ) ;
TestTrue ( " Weighted sampling should clear output when no selectable entries exist " , WeightedSample . IsEmpty ( ) ) ;
TestObject - > TestArray . Empty ( ) ;
TestTrue (
" Weighted sampling should accept an empty source for a zero count " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_SampleWeighted (
& TestObject - > TestArray ,
ArrayProperty ,
TArray < float > ( ) ,
0 ,
false ,
nullptr ,
& WeightedSample ,
ArrayProperty ) ) ;
TestFalse (
" Weighted sampling should reject a positive count for an empty source " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_SampleWeighted (
& TestObject - > TestArray ,
ArrayProperty ,
TArray < float > ( ) ,
1 ,
false ,
nullptr ,
& WeightedSample ,
ArrayProperty ) ) ;
TestTrue ( " A failed empty weighted sample should leave an empty output " , WeightedSample . IsEmpty ( ) ) ;
TestObject - > TestArray = { 10 , 20 , 30 , 40 } ;
FRandomStream LeadingZeroStream ( 101 ) ;
TestTrue (
" Weighted sampling should handle leading zero weights " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_SampleWeighted (
& TestObject - > TestArray ,
ArrayProperty ,
TArray < float > ( { 0.0f , 0.0f , 0.0f , 1.0f } ) ,
4 ,
true ,
& LeadingZeroStream ,
& WeightedSample ,
ArrayProperty ) ) ;
TestEqual ( " Leading zero weights should not select an unavailable value " , WeightedSample , TArray < int32 > ( { 40 , 40 , 40 , 40 } ) ) ;
FRandomStream ExtremeWeightStream ( 2026 ) ;
TestTrue (
" Weighted sampling should handle maximum finite weights " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_SampleWeighted (
& TestObject - > TestArray ,
ArrayProperty ,
TArray < float > ( { std : : numeric_limits < float > : : max ( ) , std : : numeric_limits < float > : : max ( ) , 0.0f , 0.0f } ) ,
2 ,
false ,
& ExtremeWeightStream ,
& WeightedSample ,
ArrayProperty ) ) ;
TestEqual ( " Maximum finite weights should return both selectable values " , WeightedSample . Num ( ) , 2 ) ;
TestTrue ( " Maximum finite weights should include the first value " , WeightedSample . Contains ( 10 ) ) ;
TestTrue ( " Maximum finite weights should include the second value " , WeightedSample . Contains ( 20 ) ) ;
TestObject - > TestArray = { 10 , 20 , 30 } ;
FRandomStream MixedRangeWeightStream ( 2027 ) ;
TestTrue (
" Weighted sampling should handle mixed finite weight ranges " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_SampleWeighted (
& TestObject - > TestArray ,
ArrayProperty ,
TArray < float > ( { std : : numeric_limits < float > : : max ( ) , 1.0f , 2.0f } ) ,
3 ,
false ,
& MixedRangeWeightStream ,
& WeightedSample ,
ArrayProperty ) ) ;
TestEqual ( " Mixed finite weights should return every selectable value " , WeightedSample . Num ( ) , 3 ) ;
TestEqual ( " Mixed finite weights should not repeat values " , TSet < int32 > ( WeightedSample ) . Num ( ) , 3 ) ;
TestTrue ( " Mixed finite weights should include the first value " , WeightedSample . Contains ( 10 ) ) ;
TestTrue ( " Mixed finite weights should include the second value " , WeightedSample . Contains ( 20 ) ) ;
TestTrue ( " Mixed finite weights should include the third value " , WeightedSample . Contains ( 30 ) ) ;
TestObject - > TestArray = { 1 , 2 , 3 , 4 } ;
FRandomStream AliasedWeightedStream ( 81 ) ;
TestTrue (
" Weighted sampling should support the source array as output " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_SampleWeighted (
& TestObject - > TestArray ,
ArrayProperty ,
TArray < float > ( { 1.0f , 1.0f , 1.0f , 1.0f } ) ,
2 ,
false ,
& AliasedWeightedStream ,
& TestObject - > TestArray ,
ArrayProperty ) ) ;
TestEqual ( " Aliased weighted sampling should return the requested count " , TestObject - > TestArray . Num ( ) , 2 ) ;
TestObject - > TestArray = { 10 , 20 , 30 , 40 , 50 } ;
TArray < int32 > Page ;
int32 PageCount = 0 ;
TestTrue ( " GetPage should return an available page " , UDirectiveUtilArrayFunctionLibrary : : GenericArray_GetPage ( & TestObject - > TestArray , ArrayProperty , 1 , 2 , & Page , ArrayProperty , & PageCount ) ) ;
TestEqual ( " GetPage should return the second page " , Page , TArray < int32 > ( { 30 , 40 } ) ) ;
TestEqual ( " GetPage should return the total page count " , PageCount , 3 ) ;
TestFalse ( " GetPage should reject a negative page " , UDirectiveUtilArrayFunctionLibrary : : GenericArray_GetPage ( & TestObject - > TestArray , ArrayProperty , - 1 , 2 , & Page , ArrayProperty , & PageCount ) ) ;
TestTrue ( " GetPage should clear output for a negative page " , Page . IsEmpty ( ) ) ;
TestFalse ( " GetPage should reject a non-positive page size " , UDirectiveUtilArrayFunctionLibrary : : GenericArray_GetPage ( & TestObject - > TestArray , ArrayProperty , 0 , 0 , & Page , ArrayProperty , & PageCount ) ) ;
TestEqual ( " GetPage should report zero pages for an invalid page size " , PageCount , 0 ) ;
TestObject - > TestArray = { 1 , 2 , 3 , 4 } ;
FRandomStream InPlaceStream ( 11 ) ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_Sample ( & TestObject - > TestArray , ArrayProperty , 2 , false , & InPlaceStream , & TestObject - > TestArray , ArrayProperty ) ;
TestEqual ( " Sample should support the same source and output array " , TestObject - > TestArray . Num ( ) , 2 ) ;
TestObject - > TestArray = { 1 , 2 , 3 , 4 } ;
TestTrue ( " GetPage should support the same source and output array " , UDirectiveUtilArrayFunctionLibrary : : GenericArray_GetPage ( & TestObject - > TestArray , ArrayProperty , 1 , 2 , & TestObject - > TestArray , ArrayProperty , & PageCount ) ) ;
TestEqual ( " In-place GetPage should return the requested values " , TestObject - > TestArray , TArray < int32 > ( { 3 , 4 } ) ) ;
TArray < FString > NaturalStrings = { TEXT ( " Item10 " ) , TEXT ( " Item2 " ) , TEXT ( " Item1 " ) , TEXT ( " アイテム2 " ) } ;
UDirectiveUtilArrayFunctionLibrary : : NaturalSortStringArray ( NaturalStrings ) ;
TestEqual ( " Natural string sort should compare embedded numbers " , NaturalStrings [ 0 ] , FString ( TEXT ( " Item1 " ) ) ) ;
TestEqual ( " Natural string sort should place Item2 before Item10 " , NaturalStrings [ 1 ] , FString ( TEXT ( " Item2 " ) ) ) ;
TestEqual ( " Natural string sort should place Item10 after Item2 " , NaturalStrings [ 2 ] , FString ( TEXT ( " Item10 " ) ) ) ;
TestEqual ( " Natural string sort should preserve Unicode strings " , NaturalStrings [ 3 ] , FString ( TEXT ( " アイテム2 " ) ) ) ;
UDirectiveUtilArrayFunctionLibrary : : NaturalSortStringArray ( NaturalStrings , true ) ;
TestEqual ( " Natural string sort should support descending order " , NaturalStrings . Last ( ) , FString ( TEXT ( " Item1 " ) ) ) ;
TArray < FName > NaturalNames = { FName ( TEXT ( " Actor12 " ) ) , FName ( TEXT ( " Actor3 " ) ) , FName ( TEXT ( " Actor1 " ) ) } ;
UDirectiveUtilArrayFunctionLibrary : : NaturalSortNameArray ( NaturalNames ) ;
TestEqual ( " Natural name sort should compare embedded numbers " , NaturalNames , TArray < FName > ( { FName ( TEXT ( " Actor1 " ) ) , FName ( TEXT ( " Actor3 " ) ) , FName ( TEXT ( " Actor12 " ) ) } ) ) ;
TestObject - > TestArray = { 1 , 2 , 3 , 4 , 5 } ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_Slice ( & TestObject - > TestArray , ArrayProperty , 1 , 3 , & TestObject - > TestArray , ArrayProperty ) ;
TestEqual ( " Slice should support the same source and output array " , TestObject - > TestArray , TArray < int32 > ( { 2 , 3 , 4 } ) ) ;
TestObject - > TestArray = { 3 , 1 , 3 , 2 , 1 } ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_GetDistinct ( & TestObject - > TestArray , ArrayProperty , & TestObject - > TestArray , ArrayProperty ) ;
TestEqual ( " GetDistinct should support the same source and output array " , TestObject - > TestArray , TArray < int32 > ( { 3 , 1 , 2 } ) ) ;
FArrayProperty * StringArrayProperty = FindFProperty < FArrayProperty > (
UDirectiveUtilTestObject : : StaticClass ( ) ,
GET_MEMBER_NAME_CHECKED ( UDirectiveUtilTestObject , TestStringArray ) ) ;
FArrayProperty * TextArrayProperty = FindFProperty < FArrayProperty > (
UDirectiveUtilTestObject : : StaticClass ( ) ,
GET_MEMBER_NAME_CHECKED ( UDirectiveUtilTestObject , TestTextArray ) ) ;
FArrayProperty * BoolArrayProperty = FindFProperty < FArrayProperty > (
UDirectiveUtilTestObject : : StaticClass ( ) ,
GET_MEMBER_NAME_CHECKED ( UDirectiveUtilTestObject , TestBoolArray ) ) ;
FArrayProperty * CollisionArrayProperty = FindFProperty < FArrayProperty > (
UDirectiveUtilTestObject : : StaticClass ( ) ,
GET_MEMBER_NAME_CHECKED ( UDirectiveUtilTestObject , TestCollisionArray ) ) ;
FArrayProperty * PodArrayProperty = FindFProperty < FArrayProperty > (
UDirectiveUtilTestObject : : StaticClass ( ) ,
GET_MEMBER_NAME_CHECKED ( UDirectiveUtilTestObject , TestPodArray ) ) ;
FArrayProperty * ObjectArrayProperty = FindFProperty < FArrayProperty > (
UDirectiveUtilTestObject : : StaticClass ( ) ,
GET_MEMBER_NAME_CHECKED ( UDirectiveUtilTestObject , TestObjectArray ) ) ;
TestNotNull ( " String array property should be available " , StringArrayProperty ) ;
TestNotNull ( " Text array property should be available " , TextArrayProperty ) ;
TestNotNull ( " Boolean array property should be available " , BoolArrayProperty ) ;
TestNotNull ( " Collision array property should be available " , CollisionArrayProperty ) ;
TestNotNull ( " POD struct array property should be available " , PodArrayProperty ) ;
TestNotNull ( " Object array property should be available " , ObjectArrayProperty ) ;
TestFalse ( " String arrays should use the property-aware append path " , StringArrayProperty - > Inner - > HasAnyPropertyFlags ( CPF_IsPlainOldData ) ) ;
const TArray < FString > StringAppendSource = { TEXT ( " Three " ) , TEXT ( " Four " ) } ;
TestObject - > TestStringArray = { TEXT ( " One " ) , TEXT ( " Two " ) } ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_AppendOptimized (
& TestObject - > TestStringArray ,
StringArrayProperty ,
& StringAppendSource ,
StringArrayProperty ) ;
TestEqual (
" AppendOptimized should preserve non-trivial values " ,
TestObject - > TestStringArray ,
TArray < FString > ( { TEXT ( " One " ) , TEXT ( " Two " ) , TEXT ( " Three " ) , TEXT ( " Four " ) } ) ) ;
TestObject - > TestStringArray = { TEXT ( " Alpha " ) , TEXT ( " Beta " ) } ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_AppendOptimized (
& TestObject - > TestStringArray ,
StringArrayProperty ,
& TestObject - > TestStringArray ,
StringArrayProperty ) ;
TestEqual (
" AppendOptimized should support self-appending non-trivial values " ,
TestObject - > TestStringArray ,
TArray < FString > ( { TEXT ( " Alpha " ) , TEXT ( " Beta " ) , TEXT ( " Alpha " ) , TEXT ( " Beta " ) } ) ) ;
const TArray < FString > StringInsertSource = { TEXT ( " Two " ) , TEXT ( " Three " ) } ;
TestObject - > TestStringArray = { TEXT ( " One " ) , TEXT ( " Four " ) } ;
TestTrue (
" InsertOptimized should support non-trivial values " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_InsertOptimized (
& TestObject - > TestStringArray ,
StringArrayProperty ,
& StringInsertSource ,
StringArrayProperty ,
1 ) ) ;
TestEqual (
" InsertOptimized should preserve non-trivial value order " ,
TestObject - > TestStringArray ,
TArray < FString > ( { TEXT ( " One " ) , TEXT ( " Two " ) , TEXT ( " Three " ) , TEXT ( " Four " ) } ) ) ;
TestObject - > TestStringArray = { TEXT ( " Alpha " ) , TEXT ( " Beta " ) , TEXT ( " Gamma " ) } ;
TestTrue (
" InsertOptimized should self-insert non-trivial values " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_InsertOptimized (
& TestObject - > TestStringArray ,
StringArrayProperty ,
& TestObject - > TestStringArray ,
StringArrayProperty ,
2 ) ) ;
TestEqual (
" InsertOptimized should preserve self-inserted non-trivial values " ,
TestObject - > TestStringArray ,
TArray < FString > ( {
TEXT ( " Alpha " ) ,
TEXT ( " Beta " ) ,
TEXT ( " Alpha " ) ,
TEXT ( " Beta " ) ,
TEXT ( " Gamma " ) ,
TEXT ( " Gamma " )
} ) ) ;
TestObject - > TestStringArray = {
TEXT ( " Zero " ) ,
TEXT ( " One " ) ,
TEXT ( " Two " ) ,
TEXT ( " Three " ) ,
TEXT ( " Four " )
} ;
TestEqual (
" RemoveAtIndices should remove non-trivial values " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveAtIndices (
& TestObject - > TestStringArray ,
StringArrayProperty ,
TArray < int32 > ( { 3 , 1 , 3 } ) ) ,
2 ) ;
TestEqual (
" RemoveAtIndices should preserve non-trivial survivor order " ,
TestObject - > TestStringArray ,
TArray < FString > ( { TEXT ( " Zero " ) , TEXT ( " Two " ) , TEXT ( " Four " ) } ) ) ;
const TArray < bool > BoolAppendSource = { false , true , false } ;
TestObject - > TestBoolArray = { true } ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_AppendOptimized (
& TestObject - > TestBoolArray ,
BoolArrayProperty ,
& BoolAppendSource ,
BoolArrayProperty ) ;
TestEqual (
" AppendOptimized should preserve Boolean values " ,
TestObject - > TestBoolArray ,
TArray < bool > ( { true , false , true , false } ) ) ;
TestObject - > TestBoolArray = { true , false , false } ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_AppendOptimized (
& TestObject - > TestBoolArray ,
BoolArrayProperty ,
& TestObject - > TestBoolArray ,
BoolArrayProperty ) ;
TestEqual (
" AppendOptimized should support self-appending Boolean values " ,
TestObject - > TestBoolArray ,
TArray < bool > ( { true , false , false , true , false , false } ) ) ;
auto MakePodValue = [ ] ( const int32 Index , const float Weight )
{
FDirectiveUtilPodValue Value ;
Value . Index = Index ;
Value . Weight = Weight ;
return Value ;
} ;
TestTrue ( " POD struct arrays should use the bulk append path " , PodArrayProperty - > Inner - > HasAnyPropertyFlags ( CPF_IsPlainOldData ) ) ;
const TArray < FDirectiveUtilPodValue > PodAppendSource = {
MakePodValue ( 2 , 2.5f ) ,
MakePodValue ( 3 , 3.5f )
} ;
TestObject - > TestPodArray = { MakePodValue ( 1 , 1.5f ) } ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_AppendOptimized (
& TestObject - > TestPodArray ,
PodArrayProperty ,
& PodAppendSource ,
PodArrayProperty ) ;
TestEqual ( " AppendOptimized should append POD structs " , TestObject - > TestPodArray . Num ( ) , 3 ) ;
for ( int32 Index = 0 ; Index < TestObject - > TestPodArray . Num ( ) ; + + Index )
{
TestEqual ( " AppendOptimized should preserve POD struct indices " , TestObject - > TestPodArray [ Index ] . Index , Index + 1 ) ;
TestEqual ( " AppendOptimized should preserve POD struct weights " , TestObject - > TestPodArray [ Index ] . Weight , static_cast < float > ( Index ) + 1.5f ) ;
}
TestEqual ( " AppendOptimized should not modify the POD struct source " , PodAppendSource . Num ( ) , 2 ) ;
if ( PodAppendSource . Num ( ) = = 2 )
{
TestEqual ( " AppendOptimized should preserve the first POD source index " , PodAppendSource [ 0 ] . Index , 2 ) ;
TestEqual ( " AppendOptimized should preserve the second POD source index " , PodAppendSource [ 1 ] . Index , 3 ) ;
}
TestObject - > TestPodArray = { MakePodValue ( 4 , 4.5f ) , MakePodValue ( 5 , 5.5f ) } ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_AppendOptimized (
& TestObject - > TestPodArray ,
PodArrayProperty ,
& TestObject - > TestPodArray ,
PodArrayProperty ) ;
TestEqual ( " AppendOptimized should self-append POD structs " , TestObject - > TestPodArray . Num ( ) , 4 ) ;
if ( TestObject - > TestPodArray . Num ( ) = = 4 )
{
TestEqual ( " AppendOptimized should preserve the first self-appended POD struct " , TestObject - > TestPodArray [ 2 ] . Index , 4 ) ;
TestEqual ( " AppendOptimized should preserve the second self-appended POD struct " , TestObject - > TestPodArray [ 3 ] . Index , 5 ) ;
}
TestFalse ( " POD struct grouping should use the reflected-value hash path " , PodArrayProperty - > Inner - > HasAllPropertyFlags ( CPF_HasGetValueTypeHash ) ) ;
TestObject - > TestPodArray = {
MakePodValue ( 1 , 1.5f ) ,
MakePodValue ( 2 , 2.5f ) ,
MakePodValue ( 1 , 1.5f ) ,
MakePodValue ( 3 , 3.5f ) ,
MakePodValue ( 1 , 1.5f )
} ;
UDirectiveUtilTestObject * PodResultObject = NewObject < UDirectiveUtilTestObject > ( ) ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_GetDistinct (
& TestObject - > TestPodArray ,
PodArrayProperty ,
& PodResultObject - > TestPodArray ,
PodArrayProperty ) ;
TestEqual ( " GetDistinct should group unhashable POD structs " , PodResultObject - > TestPodArray . Num ( ) , 3 ) ;
if ( PodResultObject - > TestPodArray . Num ( ) = = 3 )
{
TestEqual ( " GetDistinct should retain the first POD value " , PodResultObject - > TestPodArray [ 0 ] . Index , 1 ) ;
TestEqual ( " GetDistinct should retain the second POD value " , PodResultObject - > TestPodArray [ 1 ] . Index , 2 ) ;
TestEqual ( " GetDistinct should retain the third POD value " , PodResultObject - > TestPodArray [ 2 ] . Index , 3 ) ;
}
FDirectiveUtilPodValue MostCommonPod = MakePodValue ( INDEX_NONE , 0.0f ) ;
int32 MostCommonPodCount = 0 ;
TestTrue (
" GetMostCommon should group unhashable POD structs " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_GetMostCommon (
& TestObject - > TestPodArray ,
PodArrayProperty ,
& MostCommonPod ,
& MostCommonPodCount ) ) ;
TestEqual ( " GetMostCommon should return the repeated POD value " , MostCommonPod . Index , 1 ) ;
TestEqual ( " GetMostCommon should report the POD value count " , MostCommonPodCount , 3 ) ;
TestObject - > TestStringArray = { TEXT ( " Zero " ) , TEXT ( " Selected " ) , TEXT ( " Never " ) } ;
TArray < FString > WeightedStrings ;
FRandomStream WeightedStringStream ( 55 ) ;
TestTrue (
" Weighted sampling should preserve non-trivial array values " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_SampleWeighted (
& TestObject - > TestStringArray ,
StringArrayProperty ,
TArray < float > ( { 0.0f , 1.0f , 0.0f } ) ,
3 ,
true ,
& WeightedStringStream ,
& WeightedStrings ,
StringArrayProperty ) ) ;
TestEqual (
" Weighted sampling should copy the selected non-trivial value " ,
WeightedStrings ,
TArray < FString > ( { TEXT ( " Selected " ) , TEXT ( " Selected " ) , TEXT ( " Selected " ) } ) ) ;
TestObject - > TestStringArray = { TEXT ( " One " ) , TEXT ( " Two " ) , TEXT ( " Three " ) , TEXT ( " Four " ) } ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_Rotate ( & TestObject - > TestStringArray , StringArrayProperty , 2 ) ;
TestEqual (
" Rotate should preserve non-trivial values " ,
TestObject - > TestStringArray ,
TArray < FString > ( { TEXT ( " Three " ) , TEXT ( " Four " ) , TEXT ( " One " ) , TEXT ( " Two " ) } ) ) ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_Rotate ( & TestObject - > TestStringArray , StringArrayProperty , - 2 ) ;
TestEqual (
" Rotate should support negative shifts for non-trivial values " ,
TestObject - > TestStringArray ,
TArray < FString > ( { TEXT ( " One " ) , TEXT ( " Two " ) , TEXT ( " Three " ) , TEXT ( " Four " ) } ) ) ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_Slice (
& TestObject - > TestStringArray ,
StringArrayProperty ,
1 ,
2 ,
& TestObject - > TestStringArray ,
StringArrayProperty ) ;
TestEqual ( " Aliased Slice should preserve string values " , TestObject - > TestStringArray , TArray < FString > ( { TEXT ( " Two " ) , TEXT ( " Three " ) } ) ) ;
TestObject - > TestStringArray = { TEXT ( " Remove " ) , TEXT ( " Keep A " ) , TEXT ( " Remove " ) , TEXT ( " Keep B " ) } ;
const FString StringToRemove = TEXT ( " Remove " ) ;
TestTrue (
" RemoveAllOccurrences should support strings " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveAllOccurrences (
& TestObject - > TestStringArray ,
StringArrayProperty ,
& StringToRemove ) ) ;
TestEqual (
" RemoveAllOccurrences should preserve string survivor order " ,
TestObject - > TestStringArray ,
TArray < FString > ( { TEXT ( " Keep A " ) , TEXT ( " Keep B " ) } ) ) ;
UObject * FirstObject = NewObject < UDirectiveUtilTestObject > ( TestObject ) ;
UObject * SecondObject = NewObject < UDirectiveUtilTestObject > ( TestObject ) ;
UObject * ThirdObject = NewObject < UDirectiveUtilTestObject > ( TestObject ) ;
const TArray < TObjectPtr < UObject > > ObjectAppendSource = { SecondObject , ThirdObject } ;
TestObject - > TestObjectArray = { FirstObject } ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_AppendOptimized (
& TestObject - > TestObjectArray ,
ObjectArrayProperty ,
& ObjectAppendSource ,
ObjectArrayProperty ) ;
TestEqual ( " AppendOptimized should preserve object references " , TestObject - > TestObjectArray . Num ( ) , 3 ) ;
if ( TestObject - > TestObjectArray . Num ( ) = = 3 )
{
TestEqual ( " AppendOptimized should retain the target object " , TestObject - > TestObjectArray [ 0 ] . Get ( ) , FirstObject ) ;
TestEqual ( " AppendOptimized should append the first source object " , TestObject - > TestObjectArray [ 1 ] . Get ( ) , SecondObject ) ;
TestEqual ( " AppendOptimized should append the second source object " , TestObject - > TestObjectArray [ 2 ] . Get ( ) , ThirdObject ) ;
}
TestObject - > TestObjectArray = { FirstObject , SecondObject , FirstObject } ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveDuplicates ( & TestObject - > TestObjectArray , ObjectArrayProperty ) ;
TestEqual ( " RemoveDuplicates should preserve object references " , TestObject - > TestObjectArray . Num ( ) , 2 ) ;
if ( TestObject - > TestObjectArray . Num ( ) = = 2 )
{
TestEqual ( " RemoveDuplicates should retain the first object " , TestObject - > TestObjectArray [ 0 ] . Get ( ) , FirstObject ) ;
TestEqual ( " RemoveDuplicates should retain the second object " , TestObject - > TestObjectArray [ 1 ] . Get ( ) , SecondObject ) ;
}
TestObject - > TestObjectArray = { FirstObject , SecondObject , ThirdObject } ;
TestTrue ( " Aliased object GetPage should succeed " , UDirectiveUtilArrayFunctionLibrary : : GenericArray_GetPage (
& TestObject - > TestObjectArray ,
ObjectArrayProperty ,
1 ,
2 ,
& TestObject - > TestObjectArray ,
ObjectArrayProperty ,
& PageCount ) ) ;
TestEqual ( " Aliased object GetPage should preserve the selected reference " , TestObject - > TestObjectArray . Num ( ) , 1 ) ;
if ( TestObject - > TestObjectArray . Num ( ) = = 1 )
{
TestEqual ( " Aliased object GetPage should return the final object " , TestObject - > TestObjectArray [ 0 ] . Get ( ) , ThirdObject ) ;
}
TestObject - > TestObjectArray = { FirstObject , nullptr , SecondObject , FirstObject , nullptr } ;
TestTrue (
" RemoveAllOccurrences should support object references " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveAllOccurrences (
& TestObject - > TestObjectArray ,
ObjectArrayProperty ,
& FirstObject ) ) ;
TestEqual ( " RemoveAllOccurrences should retain object and null survivors " , TestObject - > TestObjectArray . Num ( ) , 3 ) ;
if ( TestObject - > TestObjectArray . Num ( ) = = 3 )
{
TestNull ( " RemoveAllOccurrences should retain the first null " , TestObject - > TestObjectArray [ 0 ] . Get ( ) ) ;
TestEqual ( " RemoveAllOccurrences should retain the unmatched object " , TestObject - > TestObjectArray [ 1 ] . Get ( ) , SecondObject ) ;
TestNull ( " RemoveAllOccurrences should retain the second null " , TestObject - > TestObjectArray [ 2 ] . Get ( ) ) ;
}
UObject * NullObject = nullptr ;
TestTrue (
" RemoveAllOccurrences should remove null object references " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveAllOccurrences (
& TestObject - > TestObjectArray ,
ObjectArrayProperty ,
& NullObject ) ) ;
TestEqual ( " RemoveAllOccurrences should leave the non-null object " , TestObject - > TestObjectArray . Num ( ) , 1 ) ;
TestObject - > TestObjectArray = { FirstObject , nullptr , SecondObject , ThirdObject } ;
TestEqual (
" RemoveAtIndices should preserve object references " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveAtIndices (
& TestObject - > TestObjectArray ,
ObjectArrayProperty ,
TArray < int32 > ( { 1 , 3 } ) ) ,
2 ) ;
TestEqual ( " RemoveAtIndices should retain two object references " , TestObject - > TestObjectArray . Num ( ) , 2 ) ;
if ( TestObject - > TestObjectArray . Num ( ) = = 2 )
{
TestEqual ( " RemoveAtIndices should retain the first object " , TestObject - > TestObjectArray [ 0 ] . Get ( ) , FirstObject ) ;
TestEqual ( " RemoveAtIndices should retain the second object " , TestObject - > TestObjectArray [ 1 ] . Get ( ) , SecondObject ) ;
}
TestObject - > TestStringArray = { TEXT ( " unchanged " ) } ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_AppendOptimized (
& TestObject - > TestStringArray ,
StringArrayProperty ,
& TestObject - > TestArray ,
ArrayProperty ) ;
TestEqual (
" AppendOptimized should reject mismatched array types without changing the target " ,
TestObject - > TestStringArray ,
TArray < FString > ( { TEXT ( " unchanged " ) } ) ) ;
TestFalse (
" InsertOptimized should reject mismatched array types " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_InsertOptimized (
& TestObject - > TestStringArray ,
StringArrayProperty ,
& TestObject - > TestArray ,
ArrayProperty ,
0 ) ) ;
TestEqual (
" InsertOptimized should not change a target with a mismatched source type " ,
TestObject - > TestStringArray ,
TArray < FString > ( { TEXT ( " unchanged " ) } ) ) ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_Slice (
& TestObject - > TestArray ,
ArrayProperty ,
0 ,
1 ,
& TestObject - > TestStringArray ,
StringArrayProperty ) ;
TestEqual ( " Slice should reject mismatched array types without changing output " , TestObject - > TestStringArray , TArray < FString > ( { TEXT ( " unchanged " ) } ) ) ;
TestFalse (
" Weighted sampling should reject mismatched output types " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_SampleWeighted (
& TestObject - > TestArray ,
ArrayProperty ,
TArray < float > ( { 1.0f , 1.0f , 1.0f } ) ,
1 ,
false ,
nullptr ,
& TestObject - > TestStringArray ,
StringArrayProperty ) ) ;
TestEqual ( " Weighted sampling should leave a mismatched output unchanged " , TestObject - > TestStringArray , TArray < FString > ( { TEXT ( " unchanged " ) } ) ) ;
const FText AlphaText = FText : : FromString ( TEXT ( " Alpha " ) ) ;
const FText BetaText = FText : : FromString ( TEXT ( " Beta " ) ) ;
TestObject - > TestTextArray = { AlphaText , BetaText , AlphaText } ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveDuplicates ( & TestObject - > TestTextArray , TextArrayProperty ) ;
TestEqual ( " RemoveDuplicates should support unhashable property types " , TestObject - > TestTextArray . Num ( ) , 2 ) ;
if ( TestObject - > TestTextArray . Num ( ) = = 2 )
{
TestTrue ( " RemoveDuplicates should retain the first unhashable value " , TestObject - > TestTextArray [ 0 ] . IdenticalTo ( AlphaText ) ) ;
TestTrue ( " RemoveDuplicates should retain the second unhashable value " , TestObject - > TestTextArray [ 1 ] . IdenticalTo ( BetaText ) ) ;
}
TestObject - > TestTextArray = { AlphaText , BetaText , AlphaText , BetaText } ;
TestTrue (
" RemoveAllOccurrences should support text values " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveAllOccurrences (
& TestObject - > TestTextArray ,
TextArrayProperty ,
& AlphaText ) ) ;
TestEqual ( " RemoveAllOccurrences should remove matching text values " , TestObject - > TestTextArray . Num ( ) , 2 ) ;
if ( TestObject - > TestTextArray . Num ( ) = = 2 )
{
TestTrue ( " RemoveAllOccurrences should retain the first text survivor " , TestObject - > TestTextArray [ 0 ] . IdenticalTo ( BetaText ) ) ;
TestTrue ( " RemoveAllOccurrences should retain the second text survivor " , TestObject - > TestTextArray [ 1 ] . IdenticalTo ( BetaText ) ) ;
}
TestObject - > TestBoolArray = { true , false , true , false , true } ;
const bool bBoolToRemove = true ;
TestTrue (
" RemoveAllOccurrences should support Boolean values " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveAllOccurrences (
& TestObject - > TestBoolArray ,
BoolArrayProperty ,
& bBoolToRemove ) ) ;
TestEqual ( " RemoveAllOccurrences should remove matching Boolean values " , TestObject - > TestBoolArray , TArray < bool > ( { false , false } ) ) ;
auto AddCollisionValue = [ & TestObject ] ( const int32 Value )
{
FDirectiveUtilCollisionValue & Entry = TestObject - > TestCollisionArray . AddDefaulted_GetRef ( ) ;
Entry . Value = Value ;
} ;
TestObject - > TestCollisionArray . Reset ( ) ;
for ( const int32 Value : { 1 , 2 , 1 , 3 , 2 } )
{
AddCollisionValue ( Value ) ;
}
TestTrue ( " Collision test property should expose a value hash " , CollisionArrayProperty - > Inner - > HasAllPropertyFlags ( CPF_HasGetValueTypeHash ) ) ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveDuplicates ( & TestObject - > TestCollisionArray , CollisionArrayProperty ) ;
TestEqual ( " RemoveDuplicates should resolve hash collisions " , TestObject - > TestCollisionArray . Num ( ) , 3 ) ;
if ( TestObject - > TestCollisionArray . Num ( ) = = 3 )
{
TestEqual ( " Hash collision result should retain the first value " , TestObject - > TestCollisionArray [ 0 ] . Value , 1 ) ;
TestEqual ( " Hash collision result should retain the second value " , TestObject - > TestCollisionArray [ 1 ] . Value , 2 ) ;
TestEqual ( " Hash collision result should retain the third value " , TestObject - > TestCollisionArray [ 2 ] . Value , 3 ) ;
}
TestObject - > TestCollisionArray . Reset ( ) ;
for ( const int32 Value : { 1 , 2 , 1 , 3 , 1 , 4 } )
{
AddCollisionValue ( Value ) ;
}
FDirectiveUtilCollisionValue CollisionToRemove ;
CollisionToRemove . Value = 1 ;
TestTrue (
" RemoveAllOccurrences should support struct equality " ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveAllOccurrences (
& TestObject - > TestCollisionArray ,
CollisionArrayProperty ,
& CollisionToRemove ) ) ;
TestEqual ( " RemoveAllOccurrences should keep struct survivors " , TestObject - > TestCollisionArray . Num ( ) , 3 ) ;
if ( TestObject - > TestCollisionArray . Num ( ) = = 3 )
{
TestEqual ( " RemoveAllOccurrences should retain the first struct survivor " , TestObject - > TestCollisionArray [ 0 ] . Value , 2 ) ;
TestEqual ( " RemoveAllOccurrences should retain the second struct survivor " , TestObject - > TestCollisionArray [ 1 ] . Value , 3 ) ;
TestEqual ( " RemoveAllOccurrences should retain the third struct survivor " , TestObject - > TestCollisionArray [ 2 ] . Value , 4 ) ;
}
TestObject - > TestCollisionArray . Reset ( ) ;
for ( const int32 Value : { 4 , 5 , 5 , 4 } )
{
AddCollisionValue ( Value ) ;
}
FDirectiveUtilCollisionValue MostCommonCollision ;
MostCommonCount = 0 ;
TestTrue ( " GetMostCommon should resolve hash collisions " , UDirectiveUtilArrayFunctionLibrary : : GenericArray_GetMostCommon (
& TestObject - > TestCollisionArray ,
CollisionArrayProperty ,
& MostCommonCollision ,
& MostCommonCount ) ) ;
TestEqual ( " GetMostCommon should use first occurrence to break collision ties " , MostCommonCollision . Value , 4 ) ;
TestEqual ( " GetMostCommon should report the collision value count " , MostCommonCount , 2 ) ;
TestObject - > TestArray . SetNumUninitialized ( 100 ) ;
for ( int32 Index = 0 ; Index < TestObject - > TestArray . Num ( ) ; + + Index )
{
TestObject - > TestArray [ Index ] = Index ;
}
for ( const int32 Count : { 10 , 75 } )
{
FRandomStream CompatibilityStream ( 9917 ) ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_Sample (
& TestObject - > TestArray ,
ArrayProperty ,
Count ,
false ,
& CompatibilityStream ,
& SampledValues ,
ArrayProperty ) ;
TestEqual (
FString : : Printf ( TEXT ( " Sampling %d values should match dense Fisher-Yates " ) , Count ) ,
SampledValues ,
BuildReferenceSample ( TestObject - > TestArray . Num ( ) , Count , 9917 ) ) ;
}
TArray < int32 > SelectionCounts ;
SelectionCounts . Init ( 0 , 5 ) ;
TestObject - > TestArray = { 0 , 1 , 2 , 3 , 4 } ;
FRandomStream UniformityStream ( 77123 ) ;
for ( int32 Iteration = 0 ; Iteration < 5000 ; + + Iteration )
{
UDirectiveUtilArrayFunctionLibrary : : GenericArray_Sample (
& TestObject - > TestArray ,
ArrayProperty ,
1 ,
false ,
& UniformityStream ,
& SampledValues ,
ArrayProperty ) ;
if ( SampledValues . Num ( ) = = 1 & & SelectionCounts . IsValidIndex ( SampledValues [ 0 ] ) )
{
+ + SelectionCounts [ SampledValues [ 0 ] ] ;
}
}
for ( int32 Value = 0 ; Value < SelectionCounts . Num ( ) ; + + Value )
{
TestTrue (
FString : : Printf ( TEXT ( " Sampling frequency for value %d should remain within tolerance " ) , Value ) ,
FMath : : Abs ( SelectionCounts [ Value ] - 1000 ) < = 150 ) ;
}
FRandomStream FuzzStream ( 18181 ) ;
for ( int32 Iteration = 0 ; Iteration < 100 ; + + Iteration )
{
TArray < int32 > SourceValues ;
const int32 ValueCount = FuzzStream . RandRange ( 0 , 128 ) ;
SourceValues . Reserve ( ValueCount ) ;
for ( int32 Index = 0 ; Index < ValueCount ; + + Index )
{
SourceValues . Add ( FuzzStream . RandRange ( - 12 , 12 ) ) ;
}
const TArray < int32 > ExpectedDistinct = BuildReferenceDistinct ( SourceValues ) ;
TestObject - > TestArray = SourceValues ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveDuplicates ( & TestObject - > TestArray , ArrayProperty ) ;
TestEqual ( FString : : Printf ( TEXT ( " RemoveDuplicates fuzz case %d " ) , Iteration ) , TestObject - > TestArray , ExpectedDistinct ) ;
TestObject - > TestArray = SourceValues ;
UDirectiveUtilArrayFunctionLibrary : : GenericArray_GetDistinct ( & TestObject - > TestArray , ArrayProperty , & DistinctOut , ArrayProperty ) ;
TestEqual ( FString : : Printf ( TEXT ( " GetDistinct fuzz case %d " ) , Iteration ) , DistinctOut , ExpectedDistinct ) ;
TArray < int32 > InsertValues ;
const int32 InsertCount = FuzzStream . RandRange ( 0 , 32 ) ;
InsertValues . Reserve ( InsertCount ) ;
for ( int32 Index = 0 ; Index < InsertCount ; + + Index )
{
InsertValues . Add ( FuzzStream . RandRange ( - 20 , 20 ) ) ;
}
const int32 InsertIndex = FuzzStream . RandRange ( 0 , SourceValues . Num ( ) ) ;
TArray < int32 > ExpectedInsert = SourceValues ;
ExpectedInsert . Insert ( InsertValues , InsertIndex ) ;
TestObject - > TestArray = SourceValues ;
TestEqual (
FString : : Printf ( TEXT ( " InsertOptimized fuzz result %d " ) , Iteration ) ,
UDirectiveUtilArrayFunctionLibrary : : GenericArray_InsertOptimized (
& TestObject - > TestArray ,
ArrayProperty ,
& InsertValues ,
ArrayProperty ,
InsertIndex ) ,
! InsertValues . IsEmpty ( ) ) ;
TestEqual (
FString : : Printf ( TEXT ( " InsertOptimized fuzz values %d " ) , Iteration ) ,
TestObject - > TestArray ,
ExpectedInsert ) ;
TArray < int32 > RemovalIndices ;
const int32 RemovalCount = FuzzStream . RandRange ( 0 , 48 ) ;
RemovalIndices . Reserve ( RemovalCount ) ;
for ( int32 Index = 0 ; Index < RemovalCount ; + + Index )
{
RemovalIndices . Add ( FuzzStream . RandRange ( - 8 , SourceValues . Num ( ) + 8 ) ) ;
}
const TArray < int32 > ExpectedRemoval = BuildReferenceRemoval ( SourceValues , RemovalIndices ) ;
TestObject - > TestArray = SourceValues ;
const int32 RemovedCount = UDirectiveUtilArrayFunctionLibrary : : GenericArray_RemoveAtIndices (
& TestObject - > TestArray ,
ArrayProperty ,
RemovalIndices ) ;
TestEqual (
FString : : Printf ( TEXT ( " RemoveAtIndices fuzz count %d " ) , Iteration ) ,
RemovedCount ,
SourceValues . Num ( ) - ExpectedRemoval . Num ( ) ) ;
TestEqual (
FString : : Printf ( TEXT ( " RemoveAtIndices fuzz values %d " ) , Iteration ) ,
TestObject - > TestArray ,
ExpectedRemoval ) ;
}
TArray < FString > StableNaturalStrings = { TEXT ( " ItemA " ) , TEXT ( " itema " ) , TEXT ( " ItemB " ) , TEXT ( " itemb " ) } ;
UDirectiveUtilArrayFunctionLibrary : : NaturalSortStringArray ( StableNaturalStrings ) ;
TestEqual (
" Natural string sort should preserve equivalent-key order " ,
StableNaturalStrings ,
TArray < FString > ( { TEXT ( " ItemA " ) , TEXT ( " itema " ) , TEXT ( " ItemB " ) , TEXT ( " itemb " ) } ) ) ;
UDirectiveUtilArrayFunctionLibrary : : NaturalSortStringArray ( StableNaturalStrings , true ) ;
TestEqual (
" Descending natural string sort should preserve equivalent-key order " ,
StableNaturalStrings ,
TArray < FString > ( { TEXT ( " ItemB " ) , TEXT ( " itemb " ) , TEXT ( " ItemA " ) , TEXT ( " itema " ) } ) ) ;
2026-07-15 19:17:47 +02:00
return true ;
}