using System; using System.Collections.Generic; using System.Linq; namespace Amphora.UnitTests { public class IndexedPriorityQueueTests : UnitTestSharp.TestFixture { /// /// Makes a queue holding one item per letter of , with the priorities given in order. /// public static IndexedPriorityQueue Filled(string names, params int[] priorities) { var returnMe = new IndexedPriorityQueue(); for (int i = 0; i < names.Length; ++i) { returnMe.Enqueue(names[i].ToString(), priorities[i]); } return returnMe; } public static string Drain(IndexedPriorityQueue queue) { var returnMe = new List(); while (queue.Count > 0) { returnMe.Add(queue.Dequeue()); } return string.Join(",", returnMe); } /// /// Enqueues 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 EnqueueUnreachableItem(IndexedPriorityQueue queue, out int handle) { var item = new object(); handle = queue.Enqueue(item, priority: 1); return new WeakReference(item); } public static void CollectGarbage() { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); } public class CtorTests : UnitTestSharp.TestFixture { public void StartsEmpty() { CheckEqual(0, new IndexedPriorityQueue().Count); } public void ByDefault_ReservesNothing() { CheckEqual(0, new IndexedPriorityQueue().Capacity); } public void ReservesTheCapacityAskedFor() { CheckEqual(10, new IndexedPriorityQueue(capacity: 10).Capacity); } public void NegativeCapacity_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); new IndexedPriorityQueue(capacity: -1); } } public class CountTests : UnitTestSharp.TestFixture { public void CountsTheItemsHeld() { CheckEqual(3, Filled("abc", 1, 2, 3).Count); } public void FallsWhenItemsLeave() { var queue = new IndexedPriorityQueue(); queue.Enqueue("a", 1); queue.Enqueue("b", 2); int third = queue.Enqueue("c", 3); queue.Dequeue(); CheckEqual(2, queue.Count); queue.Remove(third); CheckEqual(1, queue.Count); } } public class CapacityTests : UnitTestSharp.TestFixture { public void WhenFull_DoublesOnTheNextEnqueue() { var queue = new IndexedPriorityQueue(capacity: 2); queue.Enqueue("a", 1); queue.Enqueue("b", 2); CheckEqual(2, queue.Capacity); queue.Enqueue("c", 3); CheckEqual(4, queue.Capacity); } public void WhenEmpty_GrowsToFour() { var queue = new IndexedPriorityQueue(); queue.Enqueue("a", 1); CheckEqual(4, queue.Capacity); } public void IsUnchangedByItemsLeaving() { var queue = Filled("abcde", 5, 4, 3, 2, 1); int capacity = queue.Capacity; queue.Dequeue(); queue.Dequeue(); CheckEqual(capacity, queue.Capacity); } } public class FirstPriorityTests : UnitTestSharp.TestFixture { public void IsTheSmallestPriorityHeld() { CheckEqual(2, Filled("abc", 5, 2, 9).FirstPriority); } public void IsTheNextPriorityOnceTheFirstLeaves() { var queue = Filled("abc", 5, 2, 9); queue.Dequeue(); CheckEqual(5, queue.FirstPriority); } public void LeavesTheItemInTheQueue() { var queue = Filled("abc", 5, 2, 9); CheckEqual(2, queue.FirstPriority); CheckEqual(3, queue.Count); } public void WhenEmpty_Throws() { CheckThrow(typeof(InvalidOperationException)); int priority = new IndexedPriorityQueue().FirstPriority; } } public class FirstTests : UnitTestSharp.TestFixture { public void IsTheItemWithTheSmallestPriority() { CheckEqual("b", Filled("abc", 5, 2, 9).First); } public void LeavesTheItemInTheQueue() { var queue = Filled("abc", 5, 2, 9); CheckEqual("b", queue.First); CheckEqual("b", queue.First); CheckEqual(3, queue.Count); } public void WhenEmpty_Throws() { CheckThrow(typeof(InvalidOperationException)); string item = new IndexedPriorityQueue().First; } } public class ContainsTests : UnitTestSharp.TestFixture { public void AHandleFromEnqueue_IsHeld() { var queue = new IndexedPriorityQueue(); int handle = queue.Enqueue("a", 1); CheckTrue(queue.Contains(handle)); } public void AfterDequeue_IsFalse() { var queue = new IndexedPriorityQueue(); int handle = queue.Enqueue("a", 1); queue.Dequeue(); CheckFalse(queue.Contains(handle)); } public void AfterRemove_IsFalse() { var queue = new IndexedPriorityQueue(); int handle = queue.Enqueue("a", 1); queue.Remove(handle); CheckFalse(queue.Contains(handle)); } public void AfterClear_IsFalse() { var queue = new IndexedPriorityQueue(); int handle = queue.Enqueue("a", 1); queue.Clear(); CheckFalse(queue.Contains(handle)); } public void ANegativeHandle_IsFalse() { CheckFalse(Filled("a", 1).Contains(-1)); } public void AHandleNeverIssued_IsFalse() { var queue = new IndexedPriorityQueue(capacity: 10); queue.Enqueue("a", 1); CheckFalse(queue.Contains(1)); CheckFalse(queue.Contains(10)); } } public class PriorityOfTests : UnitTestSharp.TestFixture { public void IsThePriorityTheItemWasEnqueuedWith() { var queue = new IndexedPriorityQueue(); queue.Enqueue("a", 5); int handle = queue.Enqueue("b", 2); queue.Enqueue("c", 9); CheckEqual(2, queue.PriorityOf(handle)); } public void IsUnchangedByOtherItemsLeaving() { var queue = new IndexedPriorityQueue(); int last = queue.Enqueue("a", 50); for (int i = 0; i < 20; ++i) { queue.Enqueue("x", i); } for (int i = 0; i < 10; ++i) { queue.Dequeue(); } CheckEqual(50, queue.PriorityOf(last)); } public void ForAHandleNotHeld_Throws() { var queue = new IndexedPriorityQueue(); int handle = queue.Enqueue("a", 1); queue.Dequeue(); CheckThrow(typeof(ArgumentException)); queue.PriorityOf(handle); } } public class EnqueueTests : UnitTestSharp.TestFixture { public static readonly IndexedPriorityQueue AllocationQueue = new IndexedPriorityQueue(capacity: 8); public void ReturnsADifferentHandleForEachItemHeld() { var queue = new IndexedPriorityQueue(); var handles = new HashSet(); for (int i = 0; i < 10; ++i) { handles.Add(queue.Enqueue("x", i)); } CheckEqual(10, handles.Count); } public void AfterAnItemLeaves_ReusesItsHandle() { var queue = new IndexedPriorityQueue(); queue.Enqueue("a", 1); int second = queue.Enqueue("b", 2); queue.Enqueue("c", 3); queue.Remove(second); int reused = queue.Enqueue("d", 4); CheckEqual(second, reused); CheckEqual(4, queue.PriorityOf(reused)); } public void ReusesTheHandlesOfSeveralItemsThatLeft() { var queue = new IndexedPriorityQueue(); int first = queue.Enqueue("a", 1); int second = queue.Enqueue("b", 2); queue.Remove(first); queue.Remove(second); var handles = new HashSet { queue.Enqueue("c", 3), queue.Enqueue("d", 4) }; CheckTrue(handles.SetEquals(new[] { first, second })); } public void PastTheCapacity_KeepsEveryItem() { var queue = new IndexedPriorityQueue(capacity: 2); for (int i = 9; i >= 0; --i) { queue.Enqueue("n" + i, i); } CheckEqual("n0,n1,n2,n3,n4,n5,n6,n7,n8,n9", Drain(queue)); } public void PastTheCapacity_KeepsTheHandlesOfItemsHeld() { var queue = new IndexedPriorityQueue(capacity: 2); int first = queue.Enqueue("a", 7); int second = queue.Enqueue("b", 3); for (int i = 0; i < 10; ++i) { queue.Enqueue("x", 100 + i); } CheckEqual(7, queue.PriorityOf(first)); CheckEqual(3, queue.PriorityOf(second)); } public void EqualPriorities_ComeOutInTheOrderEnqueued() { var queue = Filled("abcdefghijklmnopqrst", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1); CheckEqual("a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t", Drain(queue)); } public void EqualPriorities_AfterAnItemWasRemovedAndAnotherAdded_StillComeOutInOrder() { var queue = new IndexedPriorityQueue(); queue.Enqueue("a", 1); int middle = queue.Enqueue("b", 1); queue.Enqueue("c", 1); queue.Remove(middle); queue.Enqueue("d", 1); queue.Enqueue("e", 0); CheckEqual("e,a,c,d", Drain(queue)); } public void TimeSpanPriorities_ComeOutEarliestFirst() { var queue = new IndexedPriorityQueue(); queue.Enqueue("late", TimeSpan.FromSeconds(3)); queue.Enqueue("early", TimeSpan.FromSeconds(1)); queue.Enqueue("soon", TimeSpan.FromSeconds(2)); CheckEqual("early", queue.Dequeue()); CheckEqual("soon", queue.Dequeue()); CheckEqual("late", queue.Dequeue()); } public void NegativePriorities_ComeOutBeforeZero() { var queue = Filled("abc", 0, -5, 3); CheckEqual("b,a,c", Drain(queue)); } [UnitTestSharp.AllocationTest] public void WithinTheCapacity_AllocatesNothing() { AllocationQueue.Clear(); AllocationQueue.Enqueue("a", 2); AllocationQueue.Enqueue("b", 1); AllocationQueue.Dequeue(); CheckNoAllocations(); CheckEqual("a", AllocationQueue.First); } } public class DequeueTests : UnitTestSharp.TestFixture { public void ReturnsTheSmallestPriorityFirst() { CheckEqual("b,d,e,c,a", Drain(Filled("abcde", 5, 1, 4, 2, 3))); } public void ReturnsTheItemsOfADescendingRunInAscendingOrder() { CheckEqual("e,d,c,b,a", Drain(Filled("abcde", 5, 4, 3, 2, 1))); } public void ManyDistinctPriorities_ComeOutInOrder() { var queue = new IndexedPriorityQueue(); for (int i = 0; i < 101; ++i) { int priority = (i * 37) % 101; queue.Enqueue(priority, priority); } for (int expected = 0; expected < 101; ++expected) { CheckEqual(expected, queue.Dequeue()); } } public void ManyItemsWithTiesAndRemovals_ComeOutSortedAndInOrderEnqueued() { var queue = new IndexedPriorityQueue(); var handles = new int[200]; for (int i = 0; i < 200; ++i) { handles[i] = queue.Enqueue(i, (i * 53) % 29); } for (int i = 0; i < 200; i += 3) { queue.Remove(handles[i]); } var expected = Enumerable.Range(0, 200).Where(i => i % 3 != 0).OrderBy(i => (i * 53) % 29).ToList(); var actual = new List(); while (queue.Count > 0) { actual.Add(queue.Dequeue()); } CheckEqual(expected, actual); } public void EndsTheHandleOfTheItem() { var queue = new IndexedPriorityQueue(); int handle = queue.Enqueue("a", 1); queue.Dequeue(); CheckFalse(queue.Contains(handle)); } public void LeavesTheOtherHandlesAlone() { var queue = new IndexedPriorityQueue(); queue.Enqueue("a", 1); int other = queue.Enqueue("b", 2); queue.Enqueue("c", 3); queue.Dequeue(); CheckEqual(2, queue.PriorityOf(other)); } public void ReleasesTheItem() { var queue = new IndexedPriorityQueue(); int handle; WeakReference item = EnqueueUnreachableItem(queue, out handle); queue.Dequeue(); CollectGarbage(); CheckFalse(item.IsAlive); } public void WhenEmpty_Throws() { CheckThrow(typeof(InvalidOperationException)); new IndexedPriorityQueue().Dequeue(); } public static readonly IndexedPriorityQueue AllocationQueue = new IndexedPriorityQueue(capacity: 8); [UnitTestSharp.AllocationTest] public void ThenEnqueuingAgain_AllocatesNothing() { AllocationQueue.Clear(); AllocationQueue.Enqueue("a", 1); AllocationQueue.Enqueue("b", 2); AllocationQueue.Dequeue(); AllocationQueue.Enqueue("c", 3); CheckNoAllocations(); CheckEqual("b", AllocationQueue.First); } } public class RemoveTests : UnitTestSharp.TestFixture { public void ReturnsTheItem() { var queue = new IndexedPriorityQueue(); queue.Enqueue("a", 1); int handle = queue.Enqueue("b", 2); CheckEqual("b", queue.Remove(handle)); } public void TakesTheItemOutOfTheOrder() { var queue = new IndexedPriorityQueue(); queue.Enqueue("a", 1); int handle = queue.Enqueue("b", 2); queue.Enqueue("c", 3); queue.Remove(handle); CheckEqual("a,c", Drain(queue)); } public void OfTheFirstItem_LeavesTheRestInOrder() { var queue = new IndexedPriorityQueue(); int handle = queue.Enqueue("a", 1); queue.Enqueue("b", 2); queue.Enqueue("c", 3); queue.Remove(handle); CheckEqual("b,c", Drain(queue)); } public void OfTheOnlyItem_EmptiesTheQueue() { var queue = new IndexedPriorityQueue(); int handle = queue.Enqueue("a", 1); queue.Remove(handle); CheckEqual(0, queue.Count); } public void OfTheItemInTheLastSlot_LeavesTheRestInOrder() { var queue = new IndexedPriorityQueue(); queue.Enqueue("a", 1); queue.Enqueue("b", 2); int handle = queue.Enqueue("c", 3); queue.Remove(handle); CheckEqual("a,b", Drain(queue)); } public void WhenTheReplacementBelongsHigher_LeavesTheRestInOrder() { var queue = new IndexedPriorityQueue(); queue.Enqueue("a", 1); queue.Enqueue("b", 10); queue.Enqueue("c", 2); int removed = queue.Enqueue("d", 11); queue.Enqueue("e", 12); queue.Enqueue("f", 3); queue.Enqueue("g", 4); queue.Remove(removed); CheckEqual("a,c,f,g,b,e", Drain(queue)); } public void WhenTheReplacementBelongsLower_LeavesTheRestInOrder() { var queue = new IndexedPriorityQueue(); queue.Enqueue("a", 1); int removed = queue.Enqueue("b", 2); queue.Enqueue("c", 3); queue.Enqueue("d", 4); queue.Enqueue("e", 5); queue.Enqueue("f", 6); queue.Enqueue("g", 7); queue.Enqueue("h", 8); queue.Remove(removed); CheckEqual("a,c,d,e,f,g,h", Drain(queue)); } public void AmongEqualPriorities_LeavesTheRestInTheOrderEnqueued() { var queue = new IndexedPriorityQueue(); queue.Enqueue("a", 1); queue.Enqueue("b", 1); int handle = queue.Enqueue("c", 1); queue.Enqueue("d", 1); queue.Enqueue("e", 1); queue.Remove(handle); CheckEqual("a,b,d,e", Drain(queue)); } public void EndsTheHandleOfTheItem() { var queue = new IndexedPriorityQueue(); int handle = queue.Enqueue("a", 1); queue.Remove(handle); CheckFalse(queue.Contains(handle)); } public void LeavesTheOtherHandlesAlone() { var queue = new IndexedPriorityQueue(); int removed = queue.Enqueue("a", 1); int other = queue.Enqueue("b", 2); int last = queue.Enqueue("c", 3); queue.Remove(removed); CheckEqual(2, queue.PriorityOf(other)); CheckEqual(3, queue.PriorityOf(last)); } public void ReleasesTheItem() { var queue = new IndexedPriorityQueue(); int handle; WeakReference item = EnqueueUnreachableItem(queue, out handle); queue.Remove(handle); CollectGarbage(); CheckFalse(item.IsAlive); } public void ForAHandleAlreadyRemoved_Throws() { var queue = new IndexedPriorityQueue(); int handle = queue.Enqueue("a", 1); queue.Remove(handle); CheckThrow(typeof(ArgumentException)); queue.Remove(handle); } public void ForAHandleNeverIssued_Throws() { var queue = Filled("a", 1); CheckThrow(typeof(ArgumentException)); queue.Remove(5); } public void ForANegativeHandle_Throws() { var queue = Filled("a", 1); CheckThrow(typeof(ArgumentException)); queue.Remove(-1); } } public class ClearTests : UnitTestSharp.TestFixture { public void EmptiesTheQueue() { var queue = Filled("abc", 3, 2, 1); queue.Clear(); CheckEqual(0, queue.Count); } public void OfAnEmptyQueue_ChangesNothing() { var queue = new IndexedPriorityQueue(); queue.Clear(); CheckEqual(0, queue.Count); } public void LeavesTheQueueUsable() { var queue = Filled("abc", 3, 2, 1); queue.Dequeue(); queue.Clear(); queue.Enqueue("x", 2); queue.Enqueue("y", 1); CheckEqual("y,x", Drain(queue)); } public void ReleasesTheItems() { var queue = new IndexedPriorityQueue(); int handle; WeakReference item = EnqueueUnreachableItem(queue, out handle); queue.Clear(); CollectGarbage(); CheckFalse(item.IsAlive); } public void KeepsEqualPrioritiesInTheOrderEnqueuedAfterwards() { var queue = Filled("abc", 1, 1, 1); queue.Clear(); queue.Enqueue("x", 1); queue.Enqueue("y", 1); queue.Enqueue("z", 1); CheckEqual("x,y,z", Drain(queue)); } } } }