using System; using System.Collections.Generic; namespace Amphora.UnitTests { public class DynamicArrayTests : UnitTestSharp.TestFixture { public struct Point { public int X; public int Y; } public static DynamicArray Holding(params int[] items) { var returnMe = new DynamicArray(); foreach (int item in items) { returnMe.Add(item); } return returnMe; } public static List ItemsOf(DynamicArray array) { var returnMe = new List(); for (int i = 0; i < array.Count; ++i) { returnMe.Add(array[i]); } return returnMe; } /// /// Adds an item nothing else references, returning a weak reference to it. /// // Out of line so the JIT can't keep the item alive in a local of the test itself. [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] public static WeakReference AddUnreachableItem(DynamicArray array) { var item = new object(); array.Add(item); return new WeakReference(item); } public class CtorTests : UnitTestSharp.TestFixture { public void StartsEmpty() { CheckEqual(0, new DynamicArray().Count); } public void ByDefault_ReservesNothing() { CheckEqual(0, new DynamicArray().Capacity); } public void ReservesTheCapacityAskedFor() { CheckEqual(10, new DynamicArray(capacity: 10).Capacity); } public void NegativeCapacity_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); new DynamicArray(capacity: -1); } public void NoCapacity_SharesOneEmptyStorage() { CheckTrue(ReferenceEquals(new DynamicArray().Storage, new DynamicArray().Storage)); } } public class EmptyTests : UnitTestSharp.TestFixture { public void HoldsNothingAndReservesNothing() { CheckEqual(0, DynamicArray.Empty.Count); CheckEqual(0, DynamicArray.Empty.Capacity); } public void Add_Throws() { CheckThrow(typeof(NotSupportedException)); DynamicArray.Empty.Add(1); } public void AddSlot_Throws() { CheckThrow(typeof(NotSupportedException)); DynamicArray.Empty.AddSlot(); } public void Insert_Throws() { CheckThrow(typeof(NotSupportedException)); DynamicArray.Empty.Insert(0, 1); } public void EnsureCapacity_Throws() { CheckThrow(typeof(NotSupportedException)); DynamicArray.Empty.EnsureCapacity(1); } public void EnsureCapacityOfZero_IsAllowed() { DynamicArray.Empty.EnsureCapacity(0); CheckEqual(0, DynamicArray.Empty.Capacity); } public void Clear_LeavesItsEnumeratorsValid() { DynamicArray.Enumerator enumerator = DynamicArray.Empty.GetEnumerator(); DynamicArray.Empty.Clear(); CheckFalse(enumerator.MoveNext()); } } public class IndexerTests : UnitTestSharp.TestFixture { public void ReadsTheItemsInTheOrderAdded() { CheckEqual(new[] { 3, 1, 4 }, ItemsOf(Holding(3, 1, 4))); } public void ChangesTheItemInPlace() { var array = new DynamicArray(); array.Add(new Point { X = 1, Y = 2 }); array[0].Y += 5; CheckEqual(7, array[0].Y); } public void PastTheCount_Throws() { DynamicArray array = Holding(1, 2); CheckThrow(typeof(ArgumentOutOfRangeException)); _ = array[2]; } public void InsideTheCapacityButPastTheCount_Throws() { var array = new DynamicArray(capacity: 8); array.Add(1); CheckThrow(typeof(ArgumentOutOfRangeException)); _ = array[1]; } public void Negative_Throws() { DynamicArray array = Holding(1, 2); CheckThrow(typeof(ArgumentOutOfRangeException)); _ = array[-1]; } } public class AddTests : UnitTestSharp.TestFixture { public void IncreasesTheCount() { CheckEqual(3, Holding(1, 2, 3).Count); } public void IntoNoCapacity_GrowsToFour() { var array = new DynamicArray(); array.Add(1); CheckEqual(4, array.Capacity); } public void PastTheCapacity_DoublesItAndKeepsEveryItem() { var array = new DynamicArray(capacity: 5); for (int i = 0; i < 6; ++i) { array.Add(i); } CheckEqual(10, array.Capacity); CheckEqual(new[] { 0, 1, 2, 3, 4, 5 }, ItemsOf(array)); } public static readonly DynamicArray AllocationArray = new DynamicArray(capacity: 4); [UnitTestSharp.AllocationTest] public void WithinTheCapacity_AllocatesNothing() { AllocationArray.Clear(); AllocationArray.Add(1); AllocationArray.Add(2); CheckNoAllocations(); CheckEqual(2, AllocationArray.Count); } } public class AddSlotTests : UnitTestSharp.TestFixture { public void IncreasesTheCount() { DynamicArray array = Holding(1, 2); array.AddSlot(); CheckEqual(3, array.Count); } public void ReturnsTheNewLastItemByReference() { var array = new DynamicArray(); array.AddSlot().Y = 5; CheckEqual(5, array[0].Y); } public void OverAStaleSlot_StartsAtDefault() { DynamicArray array = Holding(3, 1); array.Clear(); CheckEqual(0, array.AddSlot()); } public void IntoAFullArray_GrowsAndKeepsEveryItem() { var array = new DynamicArray(capacity: 2); array.Add(3); array.Add(1); array.AddSlot() = 4; CheckEqual(4, array.Capacity); CheckEqual(new[] { 3, 1, 4 }, ItemsOf(array)); } public static readonly DynamicArray AllocationArray = new DynamicArray(capacity: 4); [UnitTestSharp.AllocationTest] public void WithinTheCapacity_AllocatesNothing() { AllocationArray.Clear(); AllocationArray.AddSlot().X = 1; AllocationArray.AddSlot().X = 2; CheckNoAllocations(); CheckEqual(2, AllocationArray.Count); } } public class InsertTests : UnitTestSharp.TestFixture { public void AtTheStart_ShiftsEverythingUp() { DynamicArray array = Holding(1, 2); array.Insert(0, 9); CheckEqual(new[] { 9, 1, 2 }, ItemsOf(array)); } public void InTheMiddle_ShiftsWhatFollows() { DynamicArray array = Holding(1, 2); array.Insert(1, 9); CheckEqual(new[] { 1, 9, 2 }, ItemsOf(array)); } public void AtTheCount_Appends() { DynamicArray array = Holding(1, 2); array.Insert(2, 9); CheckEqual(new[] { 1, 2, 9 }, ItemsOf(array)); } public void IntoAFullArray_Grows() { var array = new DynamicArray(capacity: 2); array.Add(1); array.Add(2); array.Insert(0, 9); CheckEqual(4, array.Capacity); CheckEqual(new[] { 9, 1, 2 }, ItemsOf(array)); } public void PastTheCount_Throws() { DynamicArray array = Holding(1, 2); CheckThrow(typeof(ArgumentOutOfRangeException)); array.Insert(3, 9); } public void Negative_Throws() { DynamicArray array = Holding(1, 2); CheckThrow(typeof(ArgumentOutOfRangeException)); array.Insert(-1, 9); } } public class RemoveAtTests : UnitTestSharp.TestFixture { public void ShiftsWhatFollowsDown() { DynamicArray array = Holding(1, 2, 3); array.RemoveAt(0); CheckEqual(new[] { 2, 3 }, ItemsOf(array)); } public void TheLast_JustShortensIt() { DynamicArray array = Holding(1, 2, 3); array.RemoveAt(2); CheckEqual(new[] { 1, 2 }, ItemsOf(array)); } public void PastTheCount_Throws() { DynamicArray array = Holding(1, 2); CheckThrow(typeof(ArgumentOutOfRangeException)); array.RemoveAt(2); } public void Negative_Throws() { DynamicArray array = Holding(1, 2); CheckThrow(typeof(ArgumentOutOfRangeException)); array.RemoveAt(-1); } public void LetsGoOfTheRemovedItemsReference() { var array = new DynamicArray(); array.Add(new object()); WeakReference item = AddUnreachableItem(array); array.RemoveAt(1); GC.Collect(); GC.WaitForPendingFinalizers(); bool itemSurvived = item.IsAlive; GC.KeepAlive(array); CheckFalse(itemSurvived); } } public class IndexOfTests : UnitTestSharp.TestFixture { public void IsTheFirstMatch() { CheckEqual(1, Holding(5, 7, 7).IndexOf(7)); } public void NoMatch_IsNegativeOne() { CheckEqual(-1, Holding(5, 7).IndexOf(9)); } public void IgnoresWhatsPastTheCount() { DynamicArray array = Holding(5, 7); array.RemoveAt(1); CheckEqual(-1, array.IndexOf(7)); } public void FindsNull() { var array = new DynamicArray(); array.Add("a"); array.Add(null); CheckEqual(1, array.IndexOf(null)); } } public class ContainsTests : UnitTestSharp.TestFixture { public void HeldItem_IsTrue() { CheckTrue(Holding(5, 7).Contains(7)); } public void MissingItem_IsFalse() { CheckFalse(Holding(5, 7).Contains(9)); } } public class RemoveTests : UnitTestSharp.TestFixture { public void HeldItem_RemovesTheFirstAndIsTrue() { DynamicArray array = Holding(5, 7, 7); CheckTrue(array.Remove(7)); CheckEqual(new[] { 5, 7 }, ItemsOf(array)); } public void MissingItem_ChangesNothingAndIsFalse() { DynamicArray array = Holding(5, 7); CheckFalse(array.Remove(9)); CheckEqual(new[] { 5, 7 }, ItemsOf(array)); } } public class CopyToTests : UnitTestSharp.TestFixture { public void CopiesEveryItemStartingAtTheIndex() { var target = new int[5]; Holding(1, 2, 3).CopyTo(target, 1); CheckEqual(new[] { 0, 1, 2, 3, 0 }, target); } } public class GetEnumeratorTests : UnitTestSharp.TestFixture { public void ForEach_VisitsTheItemsInOrder() { var visited = new List(); foreach (int item in Holding(3, 1, 4)) { visited.Add(item); } CheckEqual(new[] { 3, 1, 4 }, visited); } public void Empty_VisitsNothing() { int visits = 0; foreach (int item in new DynamicArray()) { ++visits; } CheckEqual(0, visits); } public void ForEachByRef_ChangesTheItemsInPlace() { var array = new DynamicArray(); array.Add(new Point { X = 1 }); array.Add(new Point { X = 2 }); foreach (ref Point point in array) { point.Y = point.X * 10; } CheckEqual(10, array[0].Y); CheckEqual(20, array[1].Y); } public void AddingMidway_Throws() { DynamicArray array = Holding(1, 2); CheckThrow(typeof(InvalidOperationException)); foreach (int item in array) { array.Add(item); } } public void InsertingMidway_Throws() { DynamicArray array = Holding(1, 2); CheckThrow(typeof(InvalidOperationException)); foreach (int item in array) { array.Insert(0, item); } } public void RemovingMidway_Throws() { DynamicArray array = Holding(1, 2); CheckThrow(typeof(InvalidOperationException)); foreach (int item in array) { array.RemoveAt(0); } } public void RemovingThenAddingBackMidway_Throws() { DynamicArray array = Holding(1, 2); CheckThrow(typeof(InvalidOperationException)); foreach (int item in array) { array.RemoveAt(0); array.Add(item); } } public void WritingThroughTheIndexerMidway_IsAllowed() { DynamicArray array = Holding(1, 2); foreach (int item in array) { array[1] = 9; } CheckEqual(new[] { 1, 9 }, ItemsOf(array)); } public void Reset_StartsOver() { DynamicArray.Enumerator enumerator = Holding(3, 1).GetEnumerator(); enumerator.MoveNext(); enumerator.MoveNext(); enumerator.Reset(); enumerator.MoveNext(); CheckEqual(3, enumerator.Current); } public void ResetAfterAChange_Throws() { DynamicArray array = Holding(3, 1); DynamicArray.Enumerator enumerator = array.GetEnumerator(); array.Clear(); CheckThrow(typeof(InvalidOperationException)); enumerator.Reset(); } public void AsIEnumerableOfT_VisitsTheItemsInOrder() { IEnumerable items = Holding(3, 1, 4); CheckEqual(new[] { 3, 1, 4 }, new List(items)); } public void AsIEnumerable_VisitsTheItemsInOrder() { var visited = new List(); foreach (object item in (System.Collections.IEnumerable)Holding(3, 1)) { visited.Add(item); } CheckEqual(new object[] { 3, 1 }, visited); } public static readonly DynamicArray AllocationArray = Holding(3, 1, 4); [UnitTestSharp.AllocationTest] public void ForEach_AllocatesNothing() { int sum = 0; foreach (int item in AllocationArray) { sum += item; } CheckNoAllocations(); CheckEqual(8, sum); } } public class InterfaceTests : UnitTestSharp.TestFixture { public void ListIndexer_ReadsAndWrites() { IList list = Holding(1, 2); list[0] = 9; CheckEqual(9, list[0]); } public void ReadOnlyListIndexer_Reads() { IReadOnlyList list = Holding(1, 2); CheckEqual(2, list[1]); } public void CollectionAdd_Appends() { ICollection collection = Holding(1); collection.Add(2); CheckEqual(new[] { 1, 2 }, new List(collection)); } public void IsNotReadOnly() { CheckFalse(((ICollection)Holding(1)).IsReadOnly); } public void Empty_IsReadOnly() { CheckTrue(((ICollection)DynamicArray.Empty).IsReadOnly); } } public struct HoldsAReference { public int Number; public string Text; } public struct HoldsANestedReference { public Point Point; public HoldsAReference Inner; } public class ContainsReferencesTests : UnitTestSharp.TestFixture { public void Primitive_IsFalse() { CheckFalse(DynamicArray.ContainsReferences(typeof(int))); } public void Enum_IsFalse() { CheckFalse(DynamicArray.ContainsReferences(typeof(DayOfWeek))); } public void Class_IsTrue() { CheckTrue(DynamicArray.ContainsReferences(typeof(string))); } public void StructOfPrimitives_IsFalse() { CheckFalse(DynamicArray.ContainsReferences(typeof(Point))); } public void StructWithAReferenceField_IsTrue() { CheckTrue(DynamicArray.ContainsReferences(typeof(HoldsAReference))); } public void StructWithAReferenceInsideANestedStruct_IsTrue() { CheckTrue(DynamicArray.ContainsReferences(typeof(HoldsANestedReference))); } } public class ItemsHoldReferencesTests : UnitTestSharp.TestFixture { public void IsWhetherTheItemTypeContainsReferences() { CheckFalse(DynamicArray.ItemsHoldReferences); CheckTrue(DynamicArray.ItemsHoldReferences); } } public class StorageTests : UnitTestSharp.TestFixture { public void HoldsTheItemsInOrder() { int[] storage = Holding(3, 1, 4).Storage; CheckEqual(new[] { 3, 1, 4 }, new[] { storage[0], storage[1], storage[2] }); } public void IsTheWholeCapacity() { CheckEqual(8, new DynamicArray(capacity: 8).Storage.Length); } public void IsReplacedWhenTheArrayGrows() { var array = new DynamicArray(capacity: 1); int[] before = array.Storage; array.Add(1); array.Add(2); CheckFalse(ReferenceEquals(before, array.Storage)); } } public class EnsureCapacityTests : UnitTestSharp.TestFixture { public void AlreadyEnough_KeepsTheStorage() { var array = new DynamicArray(capacity: 8); int[] before = array.Storage; array.EnsureCapacity(8); CheckTrue(ReferenceEquals(before, array.Storage)); } public void OneMore_DoublesIt() { var array = new DynamicArray(capacity: 8); array.EnsureCapacity(9); CheckEqual(16, array.Capacity); } public void FarMore_GrowsToWhatWasAsked() { var array = new DynamicArray(capacity: 8); array.EnsureCapacity(100); CheckEqual(100, array.Capacity); } public void FromNothing_GrowsToAtLeastFour() { var array = new DynamicArray(); array.EnsureCapacity(1); CheckEqual(4, array.Capacity); } public void KeepsTheItems() { DynamicArray array = Holding(3, 1); array.EnsureCapacity(100); CheckEqual(new[] { 3, 1 }, ItemsOf(array)); } } public class SetCountTests : UnitTestSharp.TestFixture { public void GrowingWithoutClearing_TakesInWhatWasWrittenToTheStorage() { var array = new DynamicArray(capacity: 4); array.Storage[0] = 3; array.Storage[1] = 1; array.SetCount(2, clearNewItems: false); CheckEqual(new[] { 3, 1 }, ItemsOf(array)); } public void Growing_ByDefault_StartsTheNewItemsAtDefault() { DynamicArray array = Holding(3, 1, 4); array.SetCount(1); array.SetCount(3); CheckEqual(new[] { 3, 0, 0 }, ItemsOf(array)); } public void Growing_KeepsTheItemsAlreadyCounted() { DynamicArray array = Holding(3, 1); array.SetCount(3); CheckEqual(new[] { 3, 1, 0 }, ItemsOf(array)); } public void Shrinking_DropsTheItemsPastIt() { DynamicArray array = Holding(3, 1, 4); array.SetCount(1); CheckEqual(new[] { 3 }, ItemsOf(array)); } public void PastTheCapacity_Throws() { var array = new DynamicArray(capacity: 4); CheckThrow(typeof(ArgumentOutOfRangeException)); array.SetCount(5); } public void Negative_Throws() { var array = new DynamicArray(capacity: 4); CheckThrow(typeof(ArgumentOutOfRangeException)); array.SetCount(-1); } public void ShrinkingPastAReference_LetsGoOfIt() { var array = new DynamicArray(); array.Add(new object()); WeakReference item = AddUnreachableItem(array); array.SetCount(1); GC.Collect(); GC.WaitForPendingFinalizers(); bool itemSurvived = item.IsAlive; GC.KeepAlive(array); CheckFalse(itemSurvived); } public void Midway_ThrowsForAnEnumerator() { DynamicArray array = Holding(1, 2); CheckThrow(typeof(InvalidOperationException)); foreach (int item in array) { array.SetCount(1); } } } public class ClearTests : UnitTestSharp.TestFixture { public void ItemsWithoutReferences_AreLeftInTheStorage() { DynamicArray array = Holding(3, 1); array.Clear(); CheckEqual(3, array.Storage[0]); } public void EmptiesIt() { DynamicArray array = Holding(1, 2, 3); array.Clear(); CheckEqual(0, array.Count); } public void KeepsTheCapacity() { var array = new DynamicArray(capacity: 8); array.Add(1); array.Clear(); CheckEqual(8, array.Capacity); } public void LetsGoOfTheItemsReferences() { var array = new DynamicArray(); WeakReference item = AddUnreachableItem(array); array.Clear(); GC.Collect(); GC.WaitForPendingFinalizers(); bool itemSurvived = item.IsAlive; GC.KeepAlive(array); CheckFalse(itemSurvived); } } } }