No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

NativeListTests.cs 18KB

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