Rasagar/Library/PackageCache/com.unity.collections/Unity.Collections.Tests/UnsafeListTests.cs
2024-08-26 23:07:20 +03:00

774 lines
23 KiB
C#

using NUnit.Framework;
using System;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.Tests;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
internal class UnsafeListTests : CollectionsTestCommonBase
{
[Test]
public void UnsafeListT_Init()
{
var container = new UnsafeList<int>(0, Allocator.Persistent, NativeArrayOptions.ClearMemory);
Assert.True(container.IsCreated);
Assert.True(container.IsEmpty);
Assert.DoesNotThrow(() => container.Dispose());
}
[Test]
public unsafe void UnsafeListT_Init_ClearMemory()
{
var list = new UnsafeList<int>(10, Allocator.Persistent, NativeArrayOptions.ClearMemory);
for (var i = 0; i < list.Length; ++i)
{
Assert.AreEqual(0, UnsafeUtility.ReadArrayElement<int>(list.Ptr, i));
}
list.Dispose();
}
[Test]
public unsafe void UnsafeListT_Allocate_Deallocate_Read_Write()
{
var list = new UnsafeList<int>(0, Allocator.Persistent);
Assert.True(list.IsCreated);
Assert.True(list.IsEmpty);
list.Add(1);
list.Add(2);
Assert.AreEqual(2, list.Length);
Assert.AreEqual(1, UnsafeUtility.ReadArrayElement<int>(list.Ptr, 0));
Assert.AreEqual(2, UnsafeUtility.ReadArrayElement<int>(list.Ptr, 1));
list.Dispose();
}
[Test]
public unsafe void UnsafeListT_Resize_ClearMemory()
{
var list = new UnsafeList<int>(5, Allocator.Persistent, NativeArrayOptions.ClearMemory);
list.SetCapacity(32);
var capacity = list.Capacity;
list.Resize(5, NativeArrayOptions.UninitializedMemory);
Assert.AreEqual(capacity, list.Capacity); // list capacity should not change on resize
for (var i = 0; i < 5; ++i)
{
UnsafeUtility.WriteArrayElement(list.Ptr, i, i);
}
list.Resize(10, NativeArrayOptions.ClearMemory);
Assert.AreEqual(capacity, list.Capacity); // list capacity should not change on resize
for (var i = 0; i < 5; ++i)
{
Assert.AreEqual(i, UnsafeUtility.ReadArrayElement<int>(list.Ptr, i));
}
for (var i = 5; i < list.Length; ++i)
{
Assert.AreEqual(0, UnsafeUtility.ReadArrayElement<int>(list.Ptr, i));
}
list.Dispose();
}
[Test]
public unsafe void UnsafeListT_Resize_Zero()
{
var list = new UnsafeList<int>(5, Allocator.Persistent, NativeArrayOptions.ClearMemory);
var capacity = list.Capacity;
list.Add(1);
list.Resize(0);
Assert.AreEqual(0, list.Length);
Assert.AreEqual(capacity, list.Capacity); // list capacity should not change on resize
list.Add(2);
list.Clear();
Assert.AreEqual(0, list.Length);
Assert.AreEqual(capacity, list.Capacity); // list capacity should not change on resize
list.Dispose();
}
[Test]
public unsafe void UnsafeListT_SetCapacity()
{
using (var list = new UnsafeList<int>(1, Allocator.Persistent, NativeArrayOptions.ClearMemory))
{
list.Add(1);
Assert.DoesNotThrow(() => list.SetCapacity(128));
list.Add(1);
Assert.AreEqual(2, list.Length);
#if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG
Assert.Throws<ArgumentOutOfRangeException>(() => list.SetCapacity(1));
#endif
list.RemoveAtSwapBack(0);
Assert.AreEqual(1, list.Length);
Assert.DoesNotThrow(() => list.SetCapacity(1));
list.TrimExcess();
Assert.AreEqual(1, list.Capacity);
}
}
[Test]
public unsafe void UnsafeListT_TrimExcess()
{
using (var list = new UnsafeList<int>(32, Allocator.Persistent, NativeArrayOptions.ClearMemory))
{
list.Add(1);
list.TrimExcess();
Assert.AreEqual(1, list.Length);
Assert.AreEqual(1, list.Capacity);
list.RemoveAtSwapBack(0);
Assert.AreEqual(list.Length, 0);
list.TrimExcess();
Assert.AreEqual(list.Capacity, 0);
list.Add(1);
Assert.AreEqual(list.Length, 1);
Assert.AreNotEqual(list.Capacity, 0);
list.Clear();
}
}
[Test]
public unsafe void UnsafeListT_DisposeJob()
{
var list = new UnsafeList<int>(5, Allocator.Persistent, NativeArrayOptions.ClearMemory);
var disposeJob = list.Dispose(default);
Assert.IsTrue(list.Ptr == null);
disposeJob.Complete();
}
unsafe void Expected(ref UnsafeList<int> list, int expectedLength, int[] expected)
{
Assert.AreEqual(0 == expectedLength, list.IsEmpty);
Assert.AreEqual(list.Length, expectedLength);
for (var i = 0; i < list.Length; ++i)
{
var value = UnsafeUtility.ReadArrayElement<int>(list.Ptr, i);
Assert.AreEqual(expected[i], value);
}
}
[Test]
public void UnsafeListT_AddReplicate()
{
using (var list = new UnsafeList<int>(32, Allocator.Persistent))
{
list.AddReplicate(value: 42, count: 10);
Assert.AreEqual(10, list.Length);
foreach (var item in list)
Assert.AreEqual(42, item);
list.AddReplicate(value: 42, count: 100);
Assert.AreEqual(110, list.Length);
foreach (var item in list)
Assert.AreEqual(42, item);
}
}
[Test]
public unsafe void UnsafeListT_AddNoResize()
{
var container = new UnsafeList<int>(1, Allocator.Persistent, NativeArrayOptions.ClearMemory);
// List's capacity is always cache-line aligned, number of items fills up whole cache-line.
int[] range = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
#if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG
Assert.Throws<InvalidOperationException>(() => { fixed (int* r = range) container.AddRangeNoResize(r, 17); });
#endif
container.SetCapacity(17);
Assert.DoesNotThrow(() => { fixed (int* r = range) container.AddRangeNoResize(r, 17); });
container.Length = 16;
container.TrimExcess();
#if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG
Assert.Throws<InvalidOperationException>(() => { container.AddNoResize(16); });
#endif
container.Dispose();
}
[Test]
public unsafe void UnsafeListT_AddNoResize_Read()
{
var container = new UnsafeList<int>(4, Allocator.Persistent, NativeArrayOptions.ClearMemory);
container.AddNoResize(4);
container.AddNoResize(6);
container.AddNoResize(4);
container.AddNoResize(9);
Expected(ref container, 4, new int[] { 4, 6, 4, 9 });
container.Dispose();
}
[Test]
public unsafe void UnsafeListT_RemoveAtSwapBack()
{
var list = new UnsafeList<int>(10, Allocator.Persistent, NativeArrayOptions.ClearMemory);
int[] range = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// test removing from the end
fixed (int* r = range) list.AddRange(r, 10);
list.RemoveAtSwapBack(list.Length - 1);
Expected(ref list, 9, new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 });
list.Clear();
// test removing from the end
fixed (int* r = range) list.AddRange(r, 10);
list.RemoveAtSwapBack(5);
Expected(ref list, 9, new int[] { 0, 1, 2, 3, 4, 9, 6, 7, 8 });
list.Clear();
list.Dispose();
}
[Test]
public unsafe void UnsafeListT_RemoveRangeSwapBackBE()
{
var list = new UnsafeList<int>(10, Allocator.Persistent, NativeArrayOptions.ClearMemory);
int[] range = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// test removing from the end
fixed (int* r = range) list.AddRange(r, 10);
list.RemoveRangeSwapBack(6, 3);
Expected(ref list, 7, new int[] { 0, 1, 2, 3, 4, 5, 9 });
list.Clear();
// test removing all but one
fixed (int* r = range) list.AddRange(r, 10);
list.RemoveRangeSwapBack(0, 9);
Expected(ref list, 1, new int[] { 9 });
list.Clear();
// test removing from the front
fixed (int* r = range) list.AddRange(r, 10);
list.RemoveRangeSwapBack(0, 3);
Expected(ref list, 7, new int[] { 7, 8, 9, 3, 4, 5, 6 });
list.Clear();
// test removing from the middle
fixed (int* r = range) list.AddRange(r, 10);
list.RemoveRangeSwapBack(0, 3);
Expected(ref list, 7, new int[] { 7, 8, 9, 3, 4, 5, 6 });
list.Clear();
// test removing whole range
fixed (int* r = range) list.AddRange(r, 10);
list.RemoveRangeSwapBack(0, 10);
Expected(ref list, 0, new int[] { 0 });
list.Clear();
list.Dispose();
}
[Test]
public unsafe void UnsafeListT_RemoveAt()
{
var list = new UnsafeList<int>(10, Allocator.Persistent, NativeArrayOptions.ClearMemory);
int[] range = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// test removing from the end
fixed (int* r = range) list.AddRange(r, 10);
list.RemoveAt(list.Length - 1);
Expected(ref list, 9, new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 });
list.Clear();
// test removing from the end
fixed (int* r = range) list.AddRange(r, 10);
list.RemoveAt(5);
Expected(ref list, 9, new int[] { 0, 1, 2, 3, 4, 6, 7, 8, 9 });
list.Clear();
list.Dispose();
}
[Test]
public unsafe void UnsafeListT_RemoveRange()
{
var list = new UnsafeList<int>(10, Allocator.Persistent, NativeArrayOptions.ClearMemory);
int[] range = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// test removing from the end
fixed (int* r = range) list.AddRange(r, 10);
list.RemoveRange(6, 3);
Expected(ref list, 7, new int[] { 0, 1, 2, 3, 4, 5, 9 });
list.Clear();
// test removing all but one
fixed (int* r = range) list.AddRange(r, 10);
list.RemoveRange(0, 9);
Expected(ref list, 1, new int[] { 9 });
list.Clear();
// test removing from the front
fixed (int* r = range) list.AddRange(r, 10);
list.RemoveRange(0, 3);
Expected(ref list, 7, new int[] { 3, 4, 5, 6, 7, 8, 9 });
list.Clear();
// test removing from the middle
fixed (int* r = range) list.AddRange(r, 10);
list.RemoveRange(0, 3);
Expected(ref list, 7, new int[] { 3, 4, 5, 6, 7, 8, 9 });
list.Clear();
// test removing whole range
fixed (int* r = range) list.AddRange(r, 10);
list.RemoveRange(0, 10);
Expected(ref list, 0, new int[] { 0 });
list.Clear();
// Test removing at the end of the list with a zero length.
// This simply must not throw.
list.RemoveRange(list.Length, 0);
list.Dispose();
}
[Test]
[TestRequiresDotsDebugOrCollectionChecks]
public unsafe void UnsafeListT_Remove_Throws()
{
var list = new UnsafeList<int>(10, Allocator.Persistent, NativeArrayOptions.ClearMemory);
Assert.Throws<IndexOutOfRangeException>(() => { list.RemoveAt(0); });
Assert.AreEqual(0, list.Length);
Assert.Throws<IndexOutOfRangeException>(() => { list.RemoveAtSwapBack(0); });
Assert.AreEqual(0, list.Length);
int[] range = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
fixed (int* r = range) list.AddRange(r, 10);
Assert.Throws<IndexOutOfRangeException>(() => { list.RemoveAt(100); });
Assert.AreEqual(10, list.Length);
Assert.Throws<IndexOutOfRangeException>(() => { list.RemoveAtSwapBack(100); });
Assert.AreEqual(10, list.Length);
Assert.Throws<ArgumentOutOfRangeException>(() => { list.RemoveRange(0, 100); });
Assert.AreEqual(10, list.Length);
Assert.Throws<ArgumentOutOfRangeException>(() => { list.RemoveRangeSwapBack(0, 100); });
Assert.AreEqual(10, list.Length);
Assert.Throws<ArgumentOutOfRangeException>(() => { list.RemoveRange(100, -1); });
Assert.AreEqual(10, list.Length);
Assert.Throws<ArgumentOutOfRangeException>(() => { list.RemoveRangeSwapBack(100, -1); });
Assert.AreEqual(10, list.Length);
list.Dispose();
}
[Test]
public unsafe void UnsafeListT_PtrLength()
{
var list = new UnsafeList<int>(10, Allocator.Persistent, NativeArrayOptions.ClearMemory);
int[] range = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
fixed (int* r = range) list.AddRange(r, 10);
var listView = new UnsafeList<int>(list.Ptr + 4, 2);
Expected(ref listView, 2, new int[] { 4, 5 });
listView.Dispose();
list.Dispose();
}
// Burst error BC1071: Unsupported assert type
// [BurstCompile(CompileSynchronously = true)]
struct UnsafeListAsReadOnly : IJob
{
public UnsafeList<int>.ReadOnly list;
public void Execute()
{
Assert.True(list.Contains(123));
}
}
[Test]
public void UnsafeListT_AsReadOnly()
{
var list = new UnsafeList<int>(10, Allocator.Persistent, NativeArrayOptions.ClearMemory);
list.Add(123);
var job = new UnsafeListAsReadOnly
{
list = list.AsReadOnly(),
};
list.Dispose(job.Schedule()).Complete();
}
[BurstCompile(CompileSynchronously = true)]
struct UnsafeListParallelWriter : IJobParallelFor
{
public UnsafeList<int>.ParallelWriter list;
public void Execute(int index)
{
list.AddNoResize(index);
}
}
[Test]
public void UnsafeListT_ParallelWriter()
{
var list = new UnsafeList<int>(256, Allocator.Persistent, NativeArrayOptions.ClearMemory);
var job = new UnsafeListParallelWriter
{
list = list.AsParallelWriter(),
};
job.Schedule(list.Capacity, 1).Complete();
Assert.AreEqual(list.Length, list.Capacity);
list.Sort<int>();
for (int i = 0; i < list.Length; i++)
{
unsafe
{
var value = UnsafeUtility.ReadArrayElement<int>(list.Ptr, i);
Assert.AreEqual(i, value);
}
}
list.Dispose();
}
[BurstCompile(CompileSynchronously = true)]
struct UnsafeListTestParallelWriter : IJob
{
[WriteOnly]
public UnsafeList<int>.ParallelWriter writer;
public unsafe void Execute()
{
var range = stackalloc int[2] { 7, 3 };
writer.AddNoResize(range[0]);
writer.AddRangeNoResize(range, 1);
}
}
[Test]
public void UnsafeListT_ParallelWriter_NoPtrCaching()
{
UnsafeList<int> list;
{
list = new UnsafeList<int>(2, Allocator.Persistent);
var writer = list.AsParallelWriter();
list.SetCapacity(100);
var writerJob = new UnsafeListTestParallelWriter { writer = writer }.Schedule();
writerJob.Complete();
}
Assert.AreEqual(2, list.Length);
Assert.AreEqual(7, list[0]);
Assert.AreEqual(7, list[1]);
list.Dispose();
}
[Test]
public unsafe void UnsafeListT_IndexOf()
{
using (var list = new UnsafeList<int>(10, Allocator.Persistent) { 123, 789 })
{
bool r0 = false, r1 = false, r2 = false;
GCAllocRecorder.ValidateNoGCAllocs(() =>
{
r0 = -1 != list.IndexOf(456);
r1 = list.Contains(123);
r2 = list.Contains(789);
});
Assert.False(r0);
Assert.True(r1);
Assert.True(r2);
}
}
[Test]
public void UnsafeListT_InsertRangeWithBeginEnd()
{
var list = new UnsafeList<byte>(3, Allocator.Persistent);
list.Add(0);
list.Add(3);
list.Add(4);
Assert.AreEqual(3, list.Length);
#if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG
Assert.Throws<ArgumentOutOfRangeException>(() => list.InsertRangeWithBeginEnd(-1, 8));
Assert.Throws<ArgumentException>(() => list.InsertRangeWithBeginEnd(3, 1));
#endif
Assert.DoesNotThrow(() => list.InsertRangeWithBeginEnd(1, 3));
Assert.AreEqual(5, list.Length);
list[1] = 1;
list[2] = 2;
for (var i = 0; i < list.Length; ++i)
{
Assert.AreEqual(i, list[i]);
}
Assert.DoesNotThrow(() => list.InsertRangeWithBeginEnd(5, 8));
Assert.AreEqual(8, list.Length);
list[5] = 5;
list[6] = 6;
list[7] = 7;
for (var i = 0; i < list.Length; ++i)
{
Assert.AreEqual(i, list[i]);
}
list.Dispose();
}
[Test]
public void UnsafeListT_InsertRange()
{
var list = new UnsafeList<byte>(3, Allocator.Persistent);
list.Add(0);
list.Add(3);
list.Add(4);
Assert.AreEqual(3, list.Length);
#if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG
Assert.Throws<ArgumentOutOfRangeException>(() => list.InsertRange(-1, 8));
Assert.Throws<ArgumentException>(() => list.InsertRange(3, -1));
#endif
Assert.DoesNotThrow(() => list.InsertRange(1, 0));
Assert.AreEqual(3, list.Length);
Assert.DoesNotThrow(() => list.InsertRange(1, 2));
Assert.AreEqual(5, list.Length);
list[1] = 1;
list[2] = 2;
for (var i = 0; i < list.Length; ++i)
{
Assert.AreEqual(i, list[i]);
}
Assert.DoesNotThrow(() => list.InsertRange(5, 3));
Assert.AreEqual(8, list.Length);
list[5] = 5;
list[6] = 6;
list[7] = 7;
for (var i = 0; i < list.Length; ++i)
{
Assert.AreEqual(i, list[i]);
}
list.Dispose();
}
[Test]
public void UnsafeListT_ForEach([Values(10, 1000)] int n)
{
var seen = new NativeArray<int>(n, Allocator.Temp);
using (var container = new UnsafeList<int>(32, CommonRwdAllocator.Handle))
{
for (int i = 0; i < n; i++)
{
container.Add(i);
}
var count = 0;
unsafe
{
UnsafeList<int>* test = &container;
foreach (var item in *test)
{
Assert.True(test->Contains(item));
seen[item] = seen[item] + 1;
++count;
}
}
Assert.AreEqual(container.Length, count);
for (int i = 0; i < n; i++)
{
Assert.AreEqual(1, seen[i], $"Incorrect item count {i}");
}
}
}
[Test]
public void UnsafeListT_CustomAllocatorTest()
{
AllocatorManager.Initialize();
var allocatorHelper = new AllocatorHelper<CustomAllocatorTests.CountingAllocator>(AllocatorManager.Persistent);
ref var allocator = ref allocatorHelper.Allocator;
allocator.Initialize();
using (var container = new UnsafeList<byte>(1, allocator.Handle))
{
}
Assert.IsTrue(allocator.WasUsed);
allocator.Dispose();
allocatorHelper.Dispose();
AllocatorManager.Shutdown();
}
[BurstCompile]
struct BurstedCustomAllocatorJob : IJob
{
[NativeDisableUnsafePtrRestriction]
public unsafe CustomAllocatorTests.CountingAllocator* Allocator;
public void Execute()
{
unsafe
{
using (var container = new UnsafeList<byte>(1, Allocator->Handle))
{
}
}
}
}
[Test]
public unsafe void UnsafeListT_BurstedCustomAllocatorTest()
{
AllocatorManager.Initialize();
var allocatorHelper = new AllocatorHelper<CustomAllocatorTests.CountingAllocator>(AllocatorManager.Persistent);
ref var allocator = ref allocatorHelper.Allocator;
allocator.Initialize();
var allocatorPtr = (CustomAllocatorTests.CountingAllocator*)UnsafeUtility.AddressOf(ref allocator);
unsafe
{
var handle = new BurstedCustomAllocatorJob { Allocator = allocatorPtr }.Schedule();
handle.Complete();
}
Assert.IsTrue(allocator.WasUsed);
allocator.Dispose();
allocatorHelper.Dispose();
AllocatorManager.Shutdown();
}
void IIndexableTest<T>(T container)
where T : unmanaged, IIndexable<int>
{
var length = container.Length;
Assert.Throws<IndexOutOfRangeException>(() => container.ElementAt(-1));
Assert.Throws<IndexOutOfRangeException>(() => container.ElementAt(container.Length));
Assert.DoesNotThrow(() => { for (int i = 0, len = container.Length; i < len; ++i) { container.ElementAt(i) = 4; } });
for (int i = 0, len = container.Length; i < len; ++i)
{
Assert.AreEqual(4, container.ElementAt(i));
}
}
void INativeListTest<T>(T container)
where T : unmanaged, INativeList<int>
{
var length = container.Length;
Assert.Throws<IndexOutOfRangeException>(() => container[-1] = 1);
Assert.Throws<IndexOutOfRangeException>(() => container[container.Length] = 1);
Assert.DoesNotThrow(() => { for (int i = 0, len = container.Length; i < len; ++i) { container[i] = 4; } });
Assert.Throws<ArgumentOutOfRangeException>(() => container.Capacity = container.Length - 1);
Assert.DoesNotThrow(() => container.Capacity = container.Length);
Assert.DoesNotThrow(() => container.Capacity = container.Length + 1);
for (int i = 0, len = container.Length; i < len; ++i)
{
Assert.AreEqual(4, container[i]);
}
}
private unsafe void TestInterfaces<T>(T container)
where T : unmanaged, IIndexable<int>, INativeList<int>
{
container.Length = 4;
Assert.DoesNotThrow(() => { for (int i = 0, len = container.Length; i < len; ++i) { container.ElementAt(i) = i; } });
IIndexableTest(container);
INativeListTest(container);
}
private unsafe void TestInterfacesDispose<T>(T container)
where T : unmanaged, IIndexable<int>, INativeList<int>, IDisposable
{
TestInterfaces(container);
container.Dispose();
}
[Test]
[TestRequiresDotsDebugOrCollectionChecks]
public unsafe void UnsafeListT_TestInterfaces() => TestInterfacesDispose(new UnsafeList<int>(1, CommonRwdAllocator.Handle));
[Test]
[TestRequiresDotsDebugOrCollectionChecks]
public unsafe void NativeList_TestInterfaces() => TestInterfacesDispose(new NativeList<int>(1, CommonRwdAllocator.Handle));
[Test]
[TestRequiresDotsDebugOrCollectionChecks]
public unsafe void FixedList32Bytes_TestInterfaces() => TestInterfaces(new FixedList32Bytes<int>());
[Test]
[TestRequiresDotsDebugOrCollectionChecks]
public unsafe void FixedList64Bytes_TestInterfaces() => TestInterfaces(new FixedList64Bytes<int>());
[Test]
[TestRequiresDotsDebugOrCollectionChecks]
public unsafe void FixedList128Bytes_TestInterfaces() => TestInterfaces(new FixedList128Bytes<int>());
[Test]
[TestRequiresDotsDebugOrCollectionChecks]
public unsafe void FixedList512Bytes_TestInterfaces() => TestInterfaces(new FixedList512Bytes<int>());
[Test]
[TestRequiresDotsDebugOrCollectionChecks]
public unsafe void FixedList4096Bytes_TestInterfaces() => TestInterfaces(new FixedList4096Bytes<int>());
}