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}