#region Apache License 2.0 /* Nuclex .NET Framework Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #endregion // Apache License 2.0 using System; using System.Collections; using System.Collections.Generic; using NUnit.Framework; namespace Nuclex.Support.Collections { /// Unit Test for the double ended queue [TestFixture] internal class DequeTest { /// Verifies that the AddLast() method of the deque is working [Test] public void TestAddLast() { Deque intDeque = new Deque(16); for(int item = 0; item < 48; ++item) { intDeque.AddLast(item); } for(int item = 0; item < 48; ++item) { Assert.AreEqual(item, intDeque[item]); } } /// Verifies that the AddFirst() method of the deque is working [Test] public void TestAddFirst() { Deque intDeque = new Deque(16); for(int item = 0; item < 48; ++item) { intDeque.AddFirst(item); } for(int item = 0; item < 48; ++item) { Assert.AreEqual(47 - item, intDeque[item]); } } /// /// Verifies that the RemoveFirst() method of the deque is working /// [Test] public void TestRemoveFirst() { Deque intDeque = new Deque(16); for(int item = 0; item < 48; ++item) { intDeque.AddLast(item); } for(int item = 0; item < 48; ++item) { Assert.AreEqual(item, intDeque.First); Assert.AreEqual(48 - item, intDeque.Count); intDeque.RemoveFirst(); } } /// /// Verifies that the RemoveLast() method of the deque is working /// [Test] public void TestRemoveLast() { Deque intDeque = new Deque(16); for(int item = 0; item < 48; ++item) { intDeque.AddLast(item); } for(int item = 0; item < 48; ++item) { Assert.AreEqual(47 - item, intDeque.Last); Assert.AreEqual(48 - item, intDeque.Count); intDeque.RemoveLast(); } } /// Verifies that the Insert() method works in all cases /// /// We have several different cases here that will be tested. The deque can /// shift items to the left or right (depending on which end is closer to /// the insertion point) and the insertion point may fall in an only partially /// occupied block, requiring elaborate index calculations /// [Test] public void TestInsert() { for(int testedIndex = 0; testedIndex <= 96; ++testedIndex) { Deque intDeque = createDeque(96); intDeque.Insert(testedIndex, 12345); Assert.AreEqual(97, intDeque.Count); for(int index = 0; index < testedIndex; ++index) { Assert.AreEqual(index, intDeque[index]); } Assert.AreEqual(12345, intDeque[testedIndex]); for(int index = testedIndex + 1; index < 97; ++index) { Assert.AreEqual(index - 1, intDeque[index]); } } } /// /// Verifies that the Insert() method works in all cases when the deque doesn't /// start at a block boundary /// [Test] public void TestInsertNonNormalized() { for(int testedIndex = 0; testedIndex <= 96; ++testedIndex) { Deque intDeque = createNonNormalizedDeque(96); intDeque.Insert(testedIndex, 12345); Assert.AreEqual(97, intDeque.Count); for(int index = 0; index < testedIndex; ++index) { Assert.AreEqual(index, intDeque[index]); } Assert.AreEqual(12345, intDeque[testedIndex]); for(int index = testedIndex + 1; index < 97; ++index) { Assert.AreEqual(index - 1, intDeque[index]); } } } /// Verifies the the RemoveAt() method works in all cases [Test] public void TestRemoveAt() { for(int testedIndex = 0; testedIndex < 96; ++testedIndex) { Deque intDeque = new Deque(16); for(int item = 0; item < 96; ++item) { intDeque.AddLast(item); } intDeque.RemoveAt(testedIndex); Assert.AreEqual(95, intDeque.Count); for(int index = 0; index < testedIndex; ++index) { Assert.AreEqual(index, intDeque[index]); } for(int index = testedIndex; index < 95; ++index) { Assert.AreEqual(index + 1, intDeque[index]); } } } /// /// Verifies the the RemoveAt() method works in all cases when the deque doesn't /// start at a block boundary /// [Test] public void TestRemoveAtNonNormalized() { for(int testedIndex = 0; testedIndex < 96; ++testedIndex) { Deque intDeque = new Deque(16); for(int item = 4; item < 96; ++item) { intDeque.AddLast(item); } intDeque.AddFirst(3); intDeque.AddFirst(2); intDeque.AddFirst(1); intDeque.AddFirst(0); intDeque.RemoveAt(testedIndex); Assert.AreEqual(95, intDeque.Count); for(int index = 0; index < testedIndex; ++index) { Assert.AreEqual(index, intDeque[index]); } for(int index = testedIndex; index < 95; ++index) { Assert.AreEqual(index + 1, intDeque[index]); } } } /// /// Tests whether the RemoveAt() method keeps the state of the deque intact when /// it has to remove a block from the left end of the deque /// [Test] public void TestRemoveAtEmptiesLeftBlock() { Deque intDeque = new Deque(16); for(int item = 1; item <= 16; ++item) { intDeque.AddLast(item); } intDeque.AddFirst(0); intDeque.RemoveAt(3); Assert.AreEqual(16, intDeque.Count); for(int index = 0; index < 3; ++index) { Assert.AreEqual(index, intDeque[index]); } for(int index = 3; index < 16; ++index) { Assert.AreEqual(index + 1, intDeque[index]); } } /// /// Tests whether the RemoveAt() method keeps the state of the deque intact when /// it has to remove a block from the right end of the deque /// [Test] public void TestRemoveAtEmptiesRightBlock() { Deque intDeque = new Deque(16); for(int item = 0; item <= 16; ++item) { intDeque.AddLast(item); } intDeque.RemoveAt(13); Assert.AreEqual(16, intDeque.Count); for(int index = 0; index < 13; ++index) { Assert.AreEqual(index, intDeque[index]); } for(int index = 13; index < 16; ++index) { Assert.AreEqual(index + 1, intDeque[index]); } } /// /// Validates that an exception is thrown if the 'First' property is accessed /// in an empty deque /// [Test] public void TestThrowOnAccessFirstInEmptyDeque() { Deque intDeque = new Deque(); Assert.Throws( delegate() { Console.WriteLine(intDeque.First); } ); } /// /// Validates that an exception is thrown if the 'Last' property is accessed /// in an empty deque /// [Test] public void TestThrowOnAccessLastInEmptyDeque() { Deque intDeque = new Deque(); Assert.Throws( delegate() { Console.WriteLine(intDeque.Last); } ); } /// /// Validates that an exception is thrown if the first item is attempted to be /// removed from an empty deque /// [Test] public void TestThrowOnRemoveFirstFromEmptyDeque() { Deque intDeque = new Deque(); Assert.Throws( delegate() { intDeque.RemoveFirst(); } ); } /// /// Validates that an exception is thrown if the last item is attempted to be /// removed from an empty deque /// [Test] public void TestThrowOnRemoveLastFromEmptyDeque() { Deque intDeque = new Deque(); Assert.Throws( delegate() { intDeque.RemoveLast(); } ); } /// /// Verifies that items can be assigned by their index /// [Test] public void TestIndexAssignment() { Deque intDeque = createDeque(32); intDeque[16] = 12345; intDeque[17] = 54321; for(int index = 0; index < 16; ++index) { intDeque.RemoveFirst(); } Assert.AreEqual(12345, intDeque.First); intDeque.RemoveFirst(); Assert.AreEqual(54321, intDeque.First); } /// /// Verifies that an exception is thrown if an invalid index is accessed /// [Test] public void TestThrowOnInvalidIndex() { Deque intDeque = new Deque(16); for(int item = 0; item < 32; ++item) { intDeque.AddLast(item); } Assert.Throws( delegate() { Console.WriteLine(intDeque[32]); } ); } /// Tests the IndexOf() method [Test, TestCase(0), TestCase(16), TestCase(32), TestCase(48)] public void TestIndexOf(int count) { Deque intDeque = new Deque(16); for(int item = 0; item < count; ++item) { intDeque.AddLast(item); } for(int item = 0; item < count; ++item) { Assert.AreEqual(item, intDeque.IndexOf(item)); } Assert.AreEqual(-1, intDeque.IndexOf(count)); } /// /// Tests the IndexOf() method with the deque not starting at a block boundary /// [Test, TestCase(0), TestCase(16), TestCase(32), TestCase(48)] public void TestIndexOfNonNormalized(int count) { Deque intDeque = createNonNormalizedDeque(count); for(int item = 0; item < count; ++item) { Assert.AreEqual(item, intDeque.IndexOf(item)); } Assert.AreEqual(-1, intDeque.IndexOf(count)); } /// Verifies that the deque's enumerator works [Test] public void TestEnumerator() { Deque intDeque = createNonNormalizedDeque(40); for(int testRun = 0; testRun < 2; ++testRun) { using(IEnumerator enumerator = intDeque.GetEnumerator()) { for(int index = 0; index < intDeque.Count; ++index) { Assert.IsTrue(enumerator.MoveNext()); Assert.AreEqual(index, enumerator.Current); } Assert.IsFalse(enumerator.MoveNext()); enumerator.Reset(); } } } /// Verifies that the deque's enumerator works [Test] public void TestObjectEnumerator() { Deque intDeque = createNonNormalizedDeque(40); for(int testRun = 0; testRun < 2; ++testRun) { IEnumerator enumerator = ((IEnumerable)intDeque).GetEnumerator(); for(int index = 0; index < intDeque.Count; ++index) { Assert.IsTrue(enumerator.MoveNext()); Assert.AreEqual(index, enumerator.Current); } Assert.IsFalse(enumerator.MoveNext()); enumerator.Reset(); } } /// /// Verifies that an exception is thrown if the enumerator is accessed in /// an invalid position /// [Test] public void TestThrowOnInvalidEnumeratorPosition() { Deque intDeque = createNonNormalizedDeque(40); using(IEnumerator enumerator = intDeque.GetEnumerator()) { Assert.Throws( delegate() { Console.WriteLine(enumerator.Current); } ); while(enumerator.MoveNext()) { } Assert.Throws( delegate() { Console.WriteLine(enumerator.Current); } ); } } /// Tests whether a small deque can be cleared [Test] public void TestClearSmallDeque() { Deque intDeque = createDeque(12); intDeque.Clear(); Assert.AreEqual(0, intDeque.Count); } /// Tests whether a large deque can be cleared [Test] public void TestClearLargeDeque() { Deque intDeque = createDeque(40); intDeque.Clear(); Assert.AreEqual(0, intDeque.Count); } /// Verifies that the non-typesafe Add() method is working [Test] public void TestAddObject() { Deque intDeque = new Deque(); Assert.AreEqual(0, ((IList)intDeque).Add(123)); Assert.AreEqual(1, intDeque.Count); } /// /// Tests whether an exception is thrown if the non-typesafe Add() method is /// used to add an incompatible object into the deque /// [Test] public void TestThrowOnAddIncompatibleObject() { Deque intDeque = new Deque(); Assert.Throws( delegate() { ((IList)intDeque).Add("Hello World"); } ); } /// Verifies that the Add() method is working [Test] public void TestAdd() { Deque intDeque = new Deque(); ((IList)intDeque).Add(123); Assert.AreEqual(1, intDeque.Count); } /// Tests whether the Contains() method is working [Test] public void TestContains() { Deque intDeque = createDeque(16); Assert.IsTrue(intDeque.Contains(14)); Assert.IsFalse(intDeque.Contains(16)); } /// Tests the non-typesafe Contains() method [Test] public void TestContainsObject() { Deque intDeque = createDeque(16); Assert.IsTrue(((IList)intDeque).Contains(14)); Assert.IsFalse(((IList)intDeque).Contains(16)); Assert.IsFalse(((IList)intDeque).Contains("Hello World")); } /// Tests the non-typesafe Contains() method [Test] public void TestIndexOfObject() { Deque intDeque = createDeque(16); Assert.AreEqual(14, ((IList)intDeque).IndexOf(14)); Assert.AreEqual(-1, ((IList)intDeque).IndexOf(16)); Assert.AreEqual(-1, ((IList)intDeque).IndexOf("Hello World")); } /// Tests wether the non-typesafe Insert() method is working [Test] public void TestInsertObject() { for(int testedIndex = 0; testedIndex <= 96; ++testedIndex) { Deque intDeque = createDeque(96); ((IList)intDeque).Insert(testedIndex, 12345); Assert.AreEqual(97, intDeque.Count); for(int index = 0; index < testedIndex; ++index) { Assert.AreEqual(index, intDeque[index]); } Assert.AreEqual(12345, intDeque[testedIndex]); for(int index = testedIndex + 1; index < 97; ++index) { Assert.AreEqual(index - 1, intDeque[index]); } } } /// /// Verifies that an exception is thrown if an incompatible object is inserted /// into the deque /// [Test] public void TestThrowOnInsertIncompatibleObject() { Deque intDeque = createDeque(12); Assert.Throws( delegate() { ((IList)intDeque).Insert(8, "Hello World"); } ); } /// Validates that the IsFixedObject property is set to false [Test] public void TestIsFixedObject() { Deque intDeque = new Deque(); Assert.IsFalse(((IList)intDeque).IsFixedSize); } /// Validates that the IsSynchronized property is set to false [Test] public void TestIsSynchronized() { Deque intDeque = new Deque(); Assert.IsFalse(((IList)intDeque).IsSynchronized); } /// /// Verifies that items can be assigned by their index using the non-typesafe /// IList interface /// [Test] public void TestObjectIndexAssignment() { Deque intDeque = createDeque(32); ((IList)intDeque)[16] = 12345; ((IList)intDeque)[17] = 54321; Assert.AreEqual(12345, ((IList)intDeque)[16]); Assert.AreEqual(54321, ((IList)intDeque)[17]); } /// /// Tests whether an exception is thrown if an incompatible object is assigned /// to the deque /// [Test] public void TestIncompatibleObjectIndexAssignment() { Deque intDeque = createDeque(2); Assert.Throws( delegate() { ((IList)intDeque)[0] = "Hello World"; } ); } /// Verifies that the Remove() method is working correctly [Test] public void TestRemove() { Deque intDeque = createDeque(16); Assert.AreEqual(16, intDeque.Count); Assert.IsTrue(intDeque.Remove(13)); Assert.IsFalse(intDeque.Remove(13)); Assert.AreEqual(15, intDeque.Count); } /// Tests the non-typesafe remove method [Test] public void TestRemoveObject() { Deque intDeque = createDeque(10); Assert.IsTrue(intDeque.Contains(8)); Assert.AreEqual(10, intDeque.Count); ((IList)intDeque).Remove(8); Assert.IsFalse(intDeque.Contains(8)); Assert.AreEqual(9, intDeque.Count); } /// /// Tests the non-typesafe remove method used to remove an incompatible object /// [Test] public void TestRemoveIncompatibleObject() { Deque intDeque = createDeque(10); ((IList)intDeque).Remove("Hello World"); // should simply do nothing Assert.AreEqual(10, intDeque.Count); } /// /// Verifies that the IsSynchronized property and the SyncRoot property are working /// [Test] public void TestSynchronization() { Deque intDeque = new Deque(); if(!(intDeque as ICollection).IsSynchronized) { lock((intDeque as ICollection).SyncRoot) { Assert.AreEqual(0, intDeque.Count); } } } /// /// Validates that the IsReadOnly property of the deque returns false /// [Test] public void TestIsReadOnly() { Deque intDeque = new Deque(); Assert.IsFalse(((IList)intDeque).IsReadOnly); Assert.IsFalse(((ICollection)intDeque).IsReadOnly); } /// Tests the non-typesafe CopyTo() method [Test] public void TestCopyToObjectArray() { Deque intDeque = createNonNormalizedDeque(40); int[] array = new int[40]; ((ICollection)intDeque).CopyTo(array, 0); Assert.AreEqual(intDeque, array); } /// Tests the CopyTo() method [Test] public void TestCopyToArray() { Deque intDeque = createDeque(12); intDeque.RemoveFirst(); intDeque.RemoveFirst(); int[] array = new int[14]; intDeque.CopyTo(array, 4); Assert.AreEqual( new int[] { 0, 0, 0, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }, array ); } /// /// Verifies that the non-typesafe CopyTo() method throws an exception if /// the array is of an incompatible type /// [Test] public void TestThrowOnCopyToIncompatibleObjectArray() { Deque intDeque = createDeque(4); short[] array = new short[4]; Assert.Throws( delegate() { ((ICollection)intDeque).CopyTo(array, 4); } ); } /// /// Verifies that the CopyTo() method throws an exception if the target array /// is too small /// [Test] public void TestThrowOnCopyToTooSmallArray() { Deque intDeque = createDeque(8); Assert.Throws( delegate() { intDeque.CopyTo(new int[7], 0); } ); } #if DEBUG /// /// Tests whether the deque enumerator detects when it runs out of sync /// [Test] public void TestInvalidatedEnumeratorDetection() { Deque intDeque = createDeque(8); using(IEnumerator enumerator = intDeque.GetEnumerator()) { Assert.IsTrue(enumerator.MoveNext()); intDeque.AddFirst(12345); Assert.Throws( delegate() { enumerator.MoveNext(); } ); } } #endif /// /// Creates a deque whose first element does not coincide with a block boundary /// /// Number of items the deque will be filled with /// The newly created deque private static Deque createNonNormalizedDeque(int count) { Deque intDeque = new Deque(16); for(int item = 4; item < count; ++item) { intDeque.AddLast(item); } if(count > 3) { intDeque.AddFirst(3); } if(count > 2) { intDeque.AddFirst(2); } if(count > 1) { intDeque.AddFirst(1); } if(count > 0) { intDeque.AddFirst(0); } return intDeque; } /// Creates a deque filled with the specified number of items /// /// Number of items the deque will be filled with /// The newly created deque private static Deque createDeque(int count) { Deque intDeque = new Deque(16); for(int item = 0; item < count; ++item) { intDeque.AddLast(item); } return intDeque; } } } // namespace Nuclex.Support.Collections