A game about forced loneliness, made by TACStudios
1using NUnit.Framework; 2using System; 3using Unity.Burst; 4using Unity.Collections; 5using Unity.Collections.NotBurstCompatible; 6using Unity.Collections.LowLevel.Unsafe; 7using Unity.Collections.Tests; 8using Unity.Jobs; 9using UnityEngine; 10using UnityEngine.TestTools; 11using System.Text.RegularExpressions; 12 13internal class NativeListTests : CollectionsTestFixture 14{ 15 static void ExpectedLength<T>(ref NativeList<T> container, int expected) 16 where T : unmanaged 17 { 18 Assert.AreEqual(expected == 0, container.IsEmpty); 19 Assert.AreEqual(expected, container.Length); 20 } 21 22 [Test] 23 [TestRequiresCollectionChecks] 24 public void NullListThrow() 25 { 26 var list = new NativeList<int>(); 27 Assert.Throws<NullReferenceException>(() => list[0] = 5); 28 Assert.Throws<ObjectDisposedException>( 29 () => list.Add(1)); 30 } 31 32 [Test] 33 public void NativeList_Allocate_Deallocate_Read_Write() 34 { 35 var list = new NativeList<int>(Allocator.Persistent); 36 37 list.Add(1); 38 list.Add(2); 39 40 ExpectedLength(ref list, 2); 41 Assert.AreEqual(1, list[0]); 42 Assert.AreEqual(2, list[1]); 43 44 list.Dispose(); 45 } 46 47 [Test] 48 public void NativeArrayFromNativeList() 49 { 50 var list = new NativeList<int>(Allocator.Persistent); 51 list.Add(42); 52 list.Add(2); 53 54 NativeArray<int> array = list.AsArray(); 55 56 Assert.AreEqual(2, array.Length); 57 Assert.AreEqual(42, array[0]); 58 Assert.AreEqual(2, array[1]); 59 60 list.Dispose(); 61 } 62 63 [Test] 64 [TestRequiresCollectionChecks] 65 public void NativeArrayFromNativeListInvalidatesOnAdd() 66 { 67 var list = new NativeList<int>(Allocator.Persistent); 68 69 // This test checks that adding an element without reallocation invalidates the native array 70 // (length changes) 71 list.Capacity = 2; 72 list.Add(42); 73 74 NativeArray<int> array = list.AsArray(); 75 76 list.Add(1000); 77 78 ExpectedLength(ref list, 2); 79 Assert.Throws<ObjectDisposedException>( 80 () => { array[0] = 1; }); 81 82 list.Dispose(); 83 } 84 85 [Test] 86 [TestRequiresCollectionChecks] 87 public void NativeArrayFromNativeListInvalidatesOnCapacityChange() 88 { 89 var list = new NativeList<int>(Allocator.Persistent); 90 list.Add(42); 91 92 NativeArray<int> array = list.AsArray(); 93 94 ExpectedLength(ref list, 1); 95 list.Capacity = 10; 96 97 //Assert.AreEqual(1, array.Length); - temporarily commenting out updated assert checks to ensure editor version promotion succeeds 98 Assert.Throws<ObjectDisposedException>( 99 () => { array[0] = 1; }); 100 101 list.Dispose(); 102 } 103 104 [Test] 105 [TestRequiresCollectionChecks] 106 public void NativeArrayFromNativeListInvalidatesOnDispose() 107 { 108 var list = new NativeList<int>(Allocator.Persistent); 109 list.Add(42); 110 NativeArray<int> array = list.AsArray(); 111 list.Dispose(); 112 113 Assert.Throws<ObjectDisposedException>( 114 () => { array[0] = 1; }); 115 116 Assert.Throws<ObjectDisposedException>( 117 () => { list[0] = 1; }); 118 } 119 120 [Test] 121 public void NativeArrayFromNativeListMayDeallocate() 122 { 123 var list = new NativeList<int>(Allocator.Persistent); 124 list.Add(42); 125 126 NativeArray<int> array = list.AsArray(); 127 Assert.DoesNotThrow(() => { array.Dispose(); }); 128 list.Dispose(); 129 } 130 131 [Test] 132 public void CopiedNativeListIsKeptInSync() 133 { 134 var list = new NativeList<int>(Allocator.Persistent); 135 var listCpy = list; 136 list.Add(42); 137 138 Assert.AreEqual(42, listCpy[0]); 139 Assert.AreEqual(42, list[0]); 140 Assert.AreEqual(1, listCpy.Length); 141 ExpectedLength(ref list, 1); 142 143 list.Dispose(); 144 } 145 146 [Test] 147 public void NativeList_CopyFrom_Managed() 148 { 149 var list = new NativeList<float>(4, Allocator.Persistent); 150 var ar = new float[] { 0, 1, 2, 3, 4, 5, 6, 7 }; 151 list.CopyFromNBC(ar); 152 ExpectedLength(ref list, 8); 153 for (int i = 0; i < list.Length; ++i) 154 { 155 Assert.AreEqual(i, list[i]); 156 } 157 list.Dispose(); 158 } 159 160 [Test] 161 public void NativeList_CopyFrom_OtherContainers() 162 { 163 var list = new NativeList<int>(4, Allocator.Persistent); 164 165 { 166 var container = new NativeArray<int>(new int[] { 0, 1, 2, 3, 4, 5, 6, 7 }, Allocator.Persistent); 167 168 list.CopyFrom(container); 169 ExpectedLength(ref list, 8); 170 for (int i = 0; i < list.Length; ++i) 171 { 172 Assert.AreEqual(i, list[i]); 173 } 174 175 container.Dispose(); 176 } 177 178 list.Add(123); 179 180 { 181 var container = new NativeList<int>(32, Allocator.Persistent) { 0, 1, 2, 3, 4, 5, 6, 7 }; 182 183 list.CopyFrom(container); 184 ExpectedLength(ref list, 8); 185 for (int i = 0; i < list.Length; ++i) 186 { 187 Assert.AreEqual(i, list[i]); 188 } 189 190 container.Dispose(); 191 } 192 193 list.Add(345); 194 195 { 196 var container = new UnsafeList<int>(32, Allocator.Persistent) { 0, 1, 2, 3, 4, 5, 6, 7 }; 197 198 list.CopyFrom(container); 199 ExpectedLength(ref list, 8); 200 for (int i = 0; i < list.Length; ++i) 201 { 202 Assert.AreEqual(i, list[i]); 203 } 204 205 container.Dispose(); 206 } 207 208 list.Add(789); 209 210 { 211 var container = new NativeHashSet<int>(32, Allocator.Persistent) { 0, 1, 2, 3, 4, 5, 6, 7 }; 212 213 using (var array = container.ToNativeArray(Allocator.TempJob)) 214 { 215 list.CopyFrom(array); 216 } 217 ExpectedLength(ref list, 8); 218 for (int i = 0; i < list.Length; ++i) 219 { 220 list.Contains(i); 221 } 222 223 container.Dispose(); 224 } 225 226 list.Add(123); 227 228 { 229 var container = new UnsafeHashSet<int>(32, Allocator.Persistent) { 0, 1, 2, 3, 4, 5, 6, 7 }; 230 231 using (var array = container.ToNativeArray(Allocator.TempJob)) 232 { 233 list.CopyFrom(array); 234 } 235 ExpectedLength(ref list, 8); 236 for (int i = 0; i < list.Length; ++i) 237 { 238 list.Contains(i); 239 } 240 241 container.Dispose(); 242 } 243 244 list.Dispose(); 245 } 246 247 [BurstCompile(CompileSynchronously = true)] 248 struct TempListInJob : IJob 249 { 250 public NativeArray<int> Output; 251 public void Execute() 252 { 253 var list = new NativeList<int>(Allocator.Temp); 254 255 list.Add(17); 256 257 Output[0] = list[0]; 258 259 list.Dispose(); 260 } 261 } 262 263 264 [Test] 265 [Ignore("Unstable on CI, DOTS-1965")] 266 public void TempListInBurstJob() 267 { 268 var job = new TempListInJob() { Output = CollectionHelper.CreateNativeArray<int>(1, CommonRwdAllocator.Handle) }; 269 job.Schedule().Complete(); 270 Assert.AreEqual(17, job.Output[0]); 271 272 job.Output.Dispose(); 273 } 274 275 [Test] 276 public void SetCapacityLessThanLength() 277 { 278 var list = new NativeList<int>(Allocator.Persistent); 279 list.Resize(10, NativeArrayOptions.UninitializedMemory); 280#if ENABLE_UNITY_COLLECTIONS_CHECKS 281 Assert.Throws<ArgumentOutOfRangeException>(() => { list.Capacity = 5; }); 282#endif 283 284 list.Dispose(); 285 } 286 287 [Test] 288 public void DisposingNativeListDerivedArrayDoesNotThrow() 289 { 290 var list = new NativeList<int>(Allocator.Persistent); 291 list.Add(1); 292 293 NativeArray<int> array = list.AsArray(); 294 Assert.DoesNotThrow(() => { array.Dispose(); }); 295 296 list.Dispose(); 297 } 298 299 [Test] 300 public void NativeList_DisposeJob() 301 { 302 var container = new NativeList<int>(Allocator.Persistent); 303 Assert.True(container.IsCreated); 304 Assert.DoesNotThrow(() => { container.Add(0); }); 305 Assert.DoesNotThrow(() => { container.Contains(0); }); 306 307 var disposeJob = container.Dispose(default); 308 Assert.False(container.IsCreated); 309#if ENABLE_UNITY_COLLECTIONS_CHECKS 310 Assert.Throws<ObjectDisposedException>( 311 () => { container.Contains(0); }); 312#endif 313 314 disposeJob.Complete(); 315 } 316 317 [Test] 318 public void ForEachWorks() 319 { 320 var container = new NativeList<int>(Allocator.Persistent); 321 container.Add(10); 322 container.Add(20); 323 324 int sum = 0; 325 int count = 0; 326 GCAllocRecorder.ValidateNoGCAllocs(() => 327 { 328 sum = 0; 329 count = 0; 330 foreach (var p in container) 331 { 332 sum += p; 333 count++; 334 } 335 }); 336 337 Assert.AreEqual(30, sum); 338 Assert.AreEqual(2, count); 339 340 container.Dispose(); 341 } 342 343 [Test] 344 [TestRequiresCollectionChecks] 345 public void NativeList_UseAfterFree_UsesCustomOwnerTypeName() 346 { 347 var list = new NativeList<int>(10, CommonRwdAllocator.Handle); 348 list.Add(17); 349 list.Dispose(); 350 Assert.That(() => list[0], 351 Throws.Exception.TypeOf<ObjectDisposedException>() 352 .With.Message.Contains($"The {list.GetType()} has been deallocated")); 353 } 354 355 [Test] 356 [TestRequiresCollectionChecks] 357 public void AtomicSafetyHandle_AllocatorTemp_UniqueStaticSafetyIds() 358 { 359 // All collections that use Allocator.Temp share the same core AtomicSafetyHandle. 360 // This test verifies that containers can proceed to assign unique static safety IDs to each 361 // AtomicSafetyHandle value, which will not be shared by other containers using Allocator.Temp. 362 var listInt = new NativeList<int>(10, Allocator.Temp); 363 var listFloat = new NativeList<float>(10, Allocator.Temp); 364 listInt.Add(17); 365 listInt.Dispose(); 366 Assert.That(() => listInt[0], 367 Throws.Exception.TypeOf<ObjectDisposedException>() 368 .With.Message.Contains($"The {listInt.GetType()} has been deallocated")); 369 listFloat.Add(1.0f); 370 listFloat.Dispose(); 371 Assert.That(() => listFloat[0], 372 Throws.Exception.TypeOf<ObjectDisposedException>() 373 .With.Message.Contains($"The {listFloat.GetType()} has been deallocated")); 374 } 375 376 [BurstCompile(CompileSynchronously = true)] 377 struct NativeListCreateAndUseAfterFreeBurst : IJob 378 { 379 public void Execute() 380 { 381 var list = new NativeList<int>(10, Allocator.Temp); 382 list.Add(17); 383 list.Dispose(); 384 list.Add(42); 385 } 386 } 387 388 [Test] 389 [TestRequiresCollectionChecks] 390 public void NativeList_CreateAndUseAfterFreeInBurstJob_UsesCustomOwnerTypeName() 391 { 392 // Make sure this isn't the first container of this type ever created, so that valid static safety data exists 393 var list = new NativeList<int>(10, CommonRwdAllocator.Handle); 394 list.Dispose(); 395 396 var job = new NativeListCreateAndUseAfterFreeBurst 397 { 398 }; 399 400 // Two things: 401 // 1. This exception is logged, not thrown; thus, we use LogAssert to detect it. 402 // 2. Calling write operation after container.Dispose() emits an unintuitive error message. For now, all this test cares about is whether it contains the 403 // expected type name. 404 job.Run(); 405 LogAssert.Expect(LogType.Exception, 406 new Regex($"InvalidOperationException: The {Regex.Escape(list.GetType().ToString())} has been declared as \\[ReadOnly\\] in the job, but you are writing to it")); 407 } 408 409 [Test] 410 public unsafe void NativeList_IndexOf() 411 { 412 using (var list = new NativeList<int>(10, Allocator.Persistent) { 123, 789 }) 413 { 414 bool r0 = false, r1 = false, r2 = false; 415 416 GCAllocRecorder.ValidateNoGCAllocs(() => 417 { 418 r0 = -1 != list.IndexOf(456); 419 r1 = list.Contains(123); 420 r2 = list.Contains(789); 421 }); 422 423 Assert.False(r0); 424 Assert.True(r1); 425 Assert.True(r2); 426 } 427 } 428 429 [Test] 430 public void NativeList_InsertRangeWithBeginEnd() 431 { 432 var list = new NativeList<byte>(3, Allocator.Persistent); 433 list.Add(0); 434 list.Add(3); 435 list.Add(4); 436 Assert.AreEqual(3, list.Length); 437 438#if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG 439 Assert.Throws<ArgumentOutOfRangeException>(() => list.InsertRangeWithBeginEnd(-1, 8)); 440 Assert.Throws<ArgumentException>(() => list.InsertRangeWithBeginEnd(3, 1)); 441#endif 442 443 Assert.DoesNotThrow(() => list.InsertRangeWithBeginEnd(1, 3)); 444 Assert.AreEqual(5, list.Length); 445 446 list[1] = 1; 447 list[2] = 2; 448 449 for (var i = 0; i < list.Length; ++i) 450 { 451 Assert.AreEqual(i, list[i]); 452 } 453 454 Assert.DoesNotThrow(() => list.InsertRangeWithBeginEnd(5, 8)); 455 Assert.AreEqual(8, list.Length); 456 457 list[5] = 5; 458 list[6] = 6; 459 list[7] = 7; 460 461 for (var i = 0; i < list.Length; ++i) 462 { 463 Assert.AreEqual(i, list[i]); 464 } 465 466 list.Dispose(); 467 } 468 469 [Test] 470 public void NativeList_InsertRange() 471 { 472 var list = new NativeList<byte>(3, Allocator.Persistent); 473 list.Add(0); 474 list.Add(3); 475 list.Add(4); 476 Assert.AreEqual(3, list.Length); 477 478#if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG 479 Assert.Throws<ArgumentOutOfRangeException>(() => list.InsertRange(-1, 8)); 480 Assert.Throws<ArgumentException>(() => list.InsertRange(3, -1)); 481#endif 482 483 Assert.DoesNotThrow(() => list.InsertRange(1, 0)); 484 Assert.AreEqual(3, list.Length); 485 486 Assert.DoesNotThrow(() => list.InsertRange(1, 2)); 487 Assert.AreEqual(5, list.Length); 488 489 list[1] = 1; 490 list[2] = 2; 491 492 for (var i = 0; i < list.Length; ++i) 493 { 494 Assert.AreEqual(i, list[i]); 495 } 496 497 Assert.DoesNotThrow(() => list.InsertRange(5, 3)); 498 Assert.AreEqual(8, list.Length); 499 500 list[5] = 5; 501 list[6] = 6; 502 list[7] = 7; 503 504 for (var i = 0; i < list.Length; ++i) 505 { 506 Assert.AreEqual(i, list[i]); 507 } 508 509 list.Dispose(); 510 } 511 512 [Test] 513 public void NativeList_CustomAllocatorTest() 514 { 515 AllocatorManager.Initialize(); 516 var allocatorHelper = new AllocatorHelper<CustomAllocatorTests.CountingAllocator>(AllocatorManager.Persistent); 517 ref var allocator = ref allocatorHelper.Allocator; 518 allocator.Initialize(); 519 520 using (var container = new NativeList<byte>(1, allocator.Handle)) 521 { 522 } 523 524 Assert.IsTrue(allocator.WasUsed); 525 allocator.Dispose(); 526 allocatorHelper.Dispose(); 527 AllocatorManager.Shutdown(); 528 } 529 530 [BurstCompile] 531 struct BurstedCustomAllocatorJob : IJob 532 { 533 [NativeDisableUnsafePtrRestriction] 534 public unsafe CustomAllocatorTests.CountingAllocator* Allocator; 535 536 public void Execute() 537 { 538 unsafe 539 { 540 using (var container = new NativeList<byte>(1, Allocator->Handle)) 541 { 542 } 543 } 544 } 545 } 546 547 [Test] 548 public unsafe void NativeList_BurstedCustomAllocatorTest() 549 { 550 AllocatorManager.Initialize(); 551 var allocatorHelper = new AllocatorHelper<CustomAllocatorTests.CountingAllocator>(AllocatorManager.Persistent); 552 ref var allocator = ref allocatorHelper.Allocator; 553 allocator.Initialize(); 554 555 var allocatorPtr = (CustomAllocatorTests.CountingAllocator*)UnsafeUtility.AddressOf<CustomAllocatorTests.CountingAllocator>(ref allocator); 556 unsafe 557 { 558 var handle = new BurstedCustomAllocatorJob {Allocator = allocatorPtr}.Schedule(); 559 handle.Complete(); 560 } 561 562 Assert.IsTrue(allocator.WasUsed); 563 allocator.Dispose(); 564 allocatorHelper.Dispose(); 565 AllocatorManager.Shutdown(); 566 } 567 568 [Test] 569 public unsafe void NativeList_SetCapacity() 570 { 571 using (var list = new NativeList<int>(1, Allocator.Persistent)) 572 { 573 list.Add(1); 574 Assert.DoesNotThrow(() => list.SetCapacity(128)); 575 576 list.Add(1); 577 Assert.AreEqual(2, list.Length); 578#if ENABLE_UNITY_COLLECTIONS_CHECKS || UNITY_DOTS_DEBUG 579 Assert.Throws<ArgumentOutOfRangeException>(() => list.SetCapacity(1)); 580#endif 581 582 list.RemoveAtSwapBack(0); 583 Assert.AreEqual(1, list.Length); 584 Assert.DoesNotThrow(() => list.SetCapacity(1)); 585 586 list.TrimExcess(); 587 Assert.AreEqual(1, list.Capacity); 588 } 589 } 590 591 [Test] 592 public unsafe void NativeList_TrimExcess() 593 { 594 using (var list = new NativeList<int>(32, Allocator.Persistent)) 595 { 596 list.Add(1); 597 list.TrimExcess(); 598 Assert.AreEqual(1, list.Length); 599 Assert.AreEqual(1, list.Capacity); 600 601 list.RemoveAtSwapBack(0); 602 Assert.AreEqual(list.Length, 0); 603 list.TrimExcess(); 604 Assert.AreEqual(list.Capacity, 0); 605 606 list.Add(1); 607 Assert.AreEqual(list.Length, 1); 608 Assert.AreNotEqual(list.Capacity, 0); 609 610 list.Clear(); 611 } 612 } 613 614 public struct NestedContainer 615 { 616 public NativeList<int> data; 617 } 618 619 [Test] 620 public void NativeList_Nested() 621 { 622 var inner = new NativeList<int>(CommonRwdAllocator.Handle); 623 NestedContainer nestedStruct = new NestedContainer { data = inner }; 624 625 var containerNestedStruct = new NativeList<NestedContainer>(CommonRwdAllocator.Handle); 626 var containerNested = new NativeList<NativeList<int>>(CommonRwdAllocator.Handle); 627 628 containerNested.Add(inner); 629 containerNestedStruct.Add(nestedStruct); 630 631 containerNested.Dispose(); 632 containerNestedStruct.Dispose(); 633 inner.Dispose(); 634 } 635 636 [Test] 637 public void NativeList_AddReplicate() 638 { 639 using (var list = new NativeList<int>(32, Allocator.Persistent)) 640 { 641 list.AddReplicate(value: 42, count: 10); 642 Assert.AreEqual(10, list.Length); 643 foreach (var item in list) 644 Assert.AreEqual(42, item); 645 646 list.AddReplicate(value: 42, count: 100); 647 Assert.AreEqual(110, list.Length); 648 foreach (var item in list) 649 Assert.AreEqual(42, item); 650 } 651 } 652}