using System; using NUnit.Framework; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Collections.Tests; #if !UNITY_DOTSRUNTIME && ENABLE_UNITY_COLLECTIONS_CHECKS internal class RewindableAllocatorTests { AllocatorHelper m_AllocatorHelper; protected ref RewindableAllocator RwdAllocator => ref m_AllocatorHelper.Allocator; [SetUp] public void Setup() { m_AllocatorHelper = new AllocatorHelper(Allocator.Persistent); m_AllocatorHelper.Allocator.Initialize(128 * 1024, true); } [TearDown] public void TearDown() { m_AllocatorHelper.Allocator.Dispose(); m_AllocatorHelper.Dispose(); } [Test] public unsafe void RewindTestVersionOverflow() { // Check allocator version overflow for (int i = 0; i < 65536 + 100; i++) { var container = RwdAllocator.AllocateNativeList(RwdAllocator.InitialSizeInBytes / 1000); container.Resize(1, NativeArrayOptions.ClearMemory); container[0] = 0xFE; RwdAllocator.Rewind(); CollectionHelper.CheckAllocator(RwdAllocator.ToAllocator); } } #if UNITY_2022_3_OR_NEWER [Test] public unsafe void NativeArrayCustomAllocatorExceptionWorks() { NativeArray array = default; Assert.Throws(() => { array = new NativeArray(2, RwdAllocator.ToAllocator); }); } #endif public unsafe void RewindInvalidatesNativeList() { var container = RwdAllocator.AllocateNativeList(RwdAllocator.InitialSizeInBytes / 1000); container.Resize(1, NativeArrayOptions.ClearMemory); container[0] = 0xFE; RwdAllocator.Rewind(); Assert.Throws(() => { container[0] = 0xEF; }); } [Test] public unsafe void RewindInvalidatesNativeArray() { var container = RwdAllocator.AllocateNativeArray(RwdAllocator.InitialSizeInBytes / 1000); container[0] = 0xFE; RwdAllocator.Rewind(); Assert.Throws(() => { container[0] = 0xEF; }); } [Test] public unsafe void NativeListCanBeCreatedViaMemberFunction() { var container = RwdAllocator.AllocateNativeList(RwdAllocator.InitialSizeInBytes / 1000); container.Resize(1, NativeArrayOptions.ClearMemory); container[0] = 0xFE; } [Test] public unsafe void NativeListCanBeDisposed() { var container = RwdAllocator.AllocateNativeList(RwdAllocator.InitialSizeInBytes / 1000); container.Resize(1, NativeArrayOptions.ClearMemory); container[0] = 0xFE; container.Dispose(); RwdAllocator.Rewind(); } [Test] public void NativeArrayCanBeDisposed() { var container = RwdAllocator.AllocateNativeArray(RwdAllocator.InitialSizeInBytes / 1000); container[0] = 0xFE; container.Dispose(); RwdAllocator.Rewind(); } [Test] public void NumberOfBlocksIsTemporarilyStable() { RwdAllocator.AllocateNativeList(RwdAllocator.InitialSizeInBytes * 10); var blocksBefore = RwdAllocator.BlocksAllocated; RwdAllocator.Rewind(); var blocksAfter = RwdAllocator.BlocksAllocated; Assert.AreEqual(blocksAfter, blocksBefore); } [Test] public void NumberOfBlocksEventuallyDrops() { RwdAllocator.AllocateNativeList(RwdAllocator.InitialSizeInBytes * 10); var blocksBefore = RwdAllocator.BlocksAllocated; RwdAllocator.Rewind(); RwdAllocator.Rewind(); var blocksAfter = RwdAllocator.BlocksAllocated; Assert.IsTrue(blocksAfter < blocksBefore); } [Test] public void PossibleToAllocateGigabytes() { const int giga = 1024 * 1024 * 1024; var container0 = RwdAllocator.AllocateNativeList(giga); var container1 = RwdAllocator.AllocateNativeList(giga); var container2 = RwdAllocator.AllocateNativeList(giga); container0.Resize(1, NativeArrayOptions.ClearMemory); container1.Resize(1, NativeArrayOptions.ClearMemory); container2.Resize(1, NativeArrayOptions.ClearMemory); container0[0] = 0; container1[0] = 1; container2[0] = 2; Assert.AreEqual((byte)0, container0[0]); Assert.AreEqual((byte)1, container1[0]); Assert.AreEqual((byte)2, container2[0]); } [Test] public void ExhaustsFirstBlockBeforeAllocatingMore() { for (var i = 0; i < 50; ++i) { RwdAllocator.AllocateNativeList(RwdAllocator.InitialSizeInBytes / 100); Assert.AreEqual(1, RwdAllocator.BlocksAllocated); } RwdAllocator.AllocateNativeList(RwdAllocator.InitialSizeInBytes); Assert.AreEqual(2, RwdAllocator.BlocksAllocated); } unsafe struct ListProvider { NativeList m_Bytes; public ListProvider(AllocatorManager.AllocatorHandle allocatorHandle) => m_Bytes = new NativeList(allocatorHandle); public void Append(ref T data) where T : unmanaged => m_Bytes.AddRange(UnsafeUtility.AddressOf(ref data), UnsafeUtility.SizeOf()); } static void TriggerBug(AllocatorManager.AllocatorHandle allocatorHandle, NativeArray data) { var listProvider = new ListProvider(allocatorHandle); var datum = 0u; listProvider.Append(ref datum); // 'data' is now invalid after call to AtomicSafetyHandle.CheckWriteAndBumpSecondaryVersion(m_Safety); Assert.That(data[0], Is.EqualTo(0)); } [Test] public void AddRange_WhenCalledOnStructMember_DoesNotInvalidateUnrelatedListHigherOnCallStack() { AllocatorManager.AllocatorHandle allocatorHandle = RwdAllocator.Handle; var unrelatedList = new NativeList(allocatorHandle) { 0, 0 }; Assert.That(unrelatedList.Length, Is.EqualTo(2)); Assert.That(unrelatedList[0], Is.EqualTo(0)); TriggerBug(allocatorHandle, unrelatedList); } } #endif