diff --git a/ExamplesAndTests/Swordfish.NET.UnitTestV3/ConcurrentObservableSortedCollectionTests.cs b/ExamplesAndTests/Swordfish.NET.UnitTestV3/ConcurrentObservableSortedCollectionTests.cs new file mode 100644 index 0000000..bbcfe09 --- /dev/null +++ b/ExamplesAndTests/Swordfish.NET.UnitTestV3/ConcurrentObservableSortedCollectionTests.cs @@ -0,0 +1,223 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Swordfish.NET.Collections; +using Swordfish.NET.UnitTestV3.Auxiliary; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Swordfish.NET.UnitTestV3 +{ + [TestClass] + public class ConcurrentObservableSortedCollectionTests + { + private int _testCollectionCount = 10; + private int _itemsPerCollection = 100_000; + private List> _testCollections = new List>(); + private List _sortedCollection = new List(); + + public ConcurrentObservableSortedCollectionTests() + { + GenerateTestCollections(); + } + + private int TotalItems => _testCollectionCount * _itemsPerCollection; + + private bool IsSorted(IEnumerable list) + { + int previous = list.First(); + foreach (var current in list.Skip(1)) + { + if (previous > current) + { + return false; + } + previous = current; + } + return true; + } + + private void GenerateTestCollections() + { + using (var benchmark = new BenchmarkIt("Generating Test Input Collections for sorted collection")) + { + // Use a fixed seed for consistency in results + Random random = new Random(1); + + // Create 10 test sets + for (int collection = 0; collection < _testCollectionCount; ++collection) + { + List testCollection = new List(); + for (int item = 0; item < _itemsPerCollection; ++item) + { + // Ensure we have some duplicates by picking a random number + // less than half the number of items. + testCollection.Add(random.Next(_itemsPerCollection / 2)); + } + _testCollections.Add(testCollection); + } + } + + using (var benchmark = new BenchmarkIt("Generating Expected Output for sorted collection")) + { + _sortedCollection = _testCollections + .SelectMany(x => x) + .OrderBy(x => x) + .ToList(); + } + + } + + [TestMethod] + public void AddTest() + { + ConcurrentObservableSortedCollection subject = new ConcurrentObservableSortedCollection(); + using (var benchmark = new BenchmarkIt("Adding items to sorted collection")) + { + // Create test subject + // Populate test subject + _testCollections.AsParallel().ForAll(collection => + { + foreach (var item in collection) + { + subject.Add(item); + } + }); + } + // Compare test subject with expected result + Assert.AreEqual(subject.Count, _sortedCollection.Count); + bool itemsEqual = _sortedCollection + .Zip(subject, (a, b) => a == b) + .All(b => b); + Assert.IsTrue(itemsEqual); + + // Compare collectionView + var view = subject.CollectionView; + Assert.AreEqual(view.Count, _sortedCollection.Count); + bool viewItemsEqual = _sortedCollection + .Zip(view, (a, b) => a == b) + .All(b => b); + Assert.IsTrue(viewItemsEqual); + } + + [TestMethod] + public void AddRangeTest() + { + ConcurrentObservableSortedCollection subject = new ConcurrentObservableSortedCollection(); + using (var benchmark = new BenchmarkIt("Adding items to sorted collection")) + { + // Create test subject + // Populate test subject + _testCollections.AsParallel().ForAll(collection => + { + subject.AddRange(collection); + }); + } + // Compare test subject with expected result + Assert.AreEqual(subject.Count, _sortedCollection.Count); + bool itemsEqual = _sortedCollection + .Zip(subject, (a, b) => a == b) + .All(b => b); + Assert.IsTrue(itemsEqual); + + // Compare collectionView + var view = subject.CollectionView; + Assert.AreEqual(view.Count, _sortedCollection.Count); + bool viewItemsEqual = _sortedCollection + .Zip(view, (a, b) => a == b) + .All(b => b); + Assert.IsTrue(viewItemsEqual); + } + + + [TestMethod] + public void ResetTest() + { + ConcurrentObservableSortedCollection subject = new ConcurrentObservableSortedCollection(); + // Use a fixed seed for consistency in results + Random random = new Random(2); + for (int item = 0; item < _itemsPerCollection; ++item) + { + // Ensure we have some duplicates by picking a random number + // less than half the number of items. + subject.Add(random.Next(_itemsPerCollection / 2)); + } + + subject.Reset(_testCollections.SelectMany(x => x).ToList()); + + // Compare test subject with expected result + Assert.AreEqual(subject.Count, _sortedCollection.Count); + bool itemsEqual = _sortedCollection + .Zip(subject, (a, b) => a == b) + .All(b => b); + Assert.IsTrue(itemsEqual); + + // Compare collectionView + var view = subject.CollectionView; + Assert.AreEqual(view.Count, _sortedCollection.Count); + bool viewItemsEqual = _sortedCollection + .Zip(view, (a, b) => a == b) + .All(b => b); + Assert.IsTrue(viewItemsEqual); + } + + [TestMethod] + public void IndexTest() + { + // This test populates the subject with one collection, and then overwrites it with another collection + // however as the subject gets sorted on write the 2nd collection won't be equal to the subject at the + // end, all we can test for is that it is sorted + ConcurrentObservableSortedCollection subject = new ConcurrentObservableSortedCollection(); + + // Get test collections + var sourceCollection1 = _testCollections[0]; + var sourceCollection2 = _testCollections[1]; + + // Check the source collections aren't sorted + Assert.IsFalse(IsSorted(sourceCollection1)); + Assert.IsFalse(IsSorted(sourceCollection2)); + + // Check the source collections are different + bool sourceCollectionsEqual = sourceCollection1 + .Zip(sourceCollection2, (a, b) => a == b) + .All(b => b); + Assert.IsFalse(sourceCollectionsEqual); + + // Populate subject + subject.AddRange(sourceCollection1); + Assert.IsTrue(IsSorted(subject)); + + // Check the counts are all ok + Assert.AreEqual(sourceCollection1.Count, subject.Count); + Assert.AreEqual(sourceCollection2.Count, subject.Count); + int count = subject.Count; + + // Overwrite items from the second collection, checking counts all the way + for(int i=0; ix- _itemsPerCollection).OrderBy(x => x).ToList(); + for (int i = 0; i < count; ++i) + { + subject[i] = sourceCollection2Sorted[i]; + Assert.AreEqual(count, subject.Count); + } + Assert.IsTrue(IsSorted(subject)); + + bool viewItemsEqual = sourceCollection2Sorted + .Zip(subject, (a, b) => a == b) + .All(b => b); + Assert.IsTrue(viewItemsEqual); + } + + + } +} diff --git a/ExamplesAndTests/Swordfish.NET.UnitTestV3/ConcurrentObservableSortedDictionaryTests.cs b/ExamplesAndTests/Swordfish.NET.UnitTestV3/ConcurrentObservableSortedDictionaryTests.cs index 8a5111b..14b1c2c 100644 --- a/ExamplesAndTests/Swordfish.NET.UnitTestV3/ConcurrentObservableSortedDictionaryTests.cs +++ b/ExamplesAndTests/Swordfish.NET.UnitTestV3/ConcurrentObservableSortedDictionaryTests.cs @@ -7,11 +7,11 @@ namespace Swordfish.NET.UnitTestV3 { - [TestClass()] + [TestClass] public class ConcurrentObservableSortedDictionaryTests { - [TestMethod()] + [TestMethod] public void AddTest() { int _testCollectionCount = 10; @@ -236,49 +236,49 @@ public void TestManyOperations() /* - [TestMethod()] + [TestMethod] public void AddRangeTest() { Assert.Fail(); } - [TestMethod()] + [TestMethod] public void RemoveTest() { Assert.Fail(); } - [TestMethod()] + [TestMethod] public void RemoveRangeTest() { Assert.Fail(); } - [TestMethod()] + [TestMethod] public void ToStringTest() { Assert.Fail(); } - [TestMethod()] + [TestMethod] public void GetEnumeratorTest() { Assert.Fail(); } - [TestMethod()] + [TestMethod] public void ClearTest() { Assert.Fail(); } - [TestMethod()] + [TestMethod] public void ContainsTest() { Assert.Fail(); } - [TestMethod()] + [TestMethod] public void CopyToTest() { Assert.Fail(); diff --git a/ExamplesAndTests/Swordfish.NET.UnitTestV3/ConcurrentObservableSortedSetTests.cs b/ExamplesAndTests/Swordfish.NET.UnitTestV3/ConcurrentObservableSortedSetTests.cs index f3d29dc..cd28407 100644 --- a/ExamplesAndTests/Swordfish.NET.UnitTestV3/ConcurrentObservableSortedSetTests.cs +++ b/ExamplesAndTests/Swordfish.NET.UnitTestV3/ConcurrentObservableSortedSetTests.cs @@ -8,7 +8,7 @@ namespace Swordfish.NET.UnitTestV3 { - [TestClass()] + [TestClass] public class ConcurrentObservableSortedSetTests { private int _testCollectionCount = 10; @@ -88,7 +88,7 @@ private void GenerateSortedSet() } } - [TestMethod()] + [TestMethod] public void AddTest() { ConcurrentObservableSortedSet subject = new ConcurrentObservableSortedSet(); @@ -121,49 +121,49 @@ public void AddTest() } /* - [TestMethod()] + [TestMethod] public void AddRangeTest() { Assert.Fail(); } - [TestMethod()] + [TestMethod] public void RemoveTest() { Assert.Fail(); } - [TestMethod()] + [TestMethod] public void RemoveRangeTest() { Assert.Fail(); } - [TestMethod()] + [TestMethod] public void ToStringTest() { Assert.Fail(); } - [TestMethod()] + [TestMethod] public void GetEnumeratorTest() { Assert.Fail(); } - [TestMethod()] + [TestMethod] public void ClearTest() { Assert.Fail(); } - [TestMethod()] + [TestMethod] public void ContainsTest() { Assert.Fail(); } - [TestMethod()] + [TestMethod] public void CopyToTest() { Assert.Fail(); diff --git a/Swordfish.NET.CollectionsV3/BinarySorter.cs b/Swordfish.NET.CollectionsV3/BinarySorter.cs index ed5a586..b1cb8aa 100644 --- a/Swordfish.NET.CollectionsV3/BinarySorter.cs +++ b/Swordfish.NET.CollectionsV3/BinarySorter.cs @@ -16,7 +16,7 @@ namespace Swordfish.NET.Collections /// /// This class is a helper for creating binary sorted lists /// - internal class BinarySorter + internal class BinarySorter : IComparer { // ************************************************************************ @@ -58,22 +58,15 @@ public int GetInsertIndex(int count, TKey key, Func indexToKey) public int GetMatchIndex(int count, TKey key, Func indexToKey) { return BinarySearchForMatch(0, count - 1, key, indexToKey); - } - - #endregion Public Methods - - // ************************************************************************ - // Private Methods - // ************************************************************************ - #region Private Methods - + } + /// /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other. /// /// /// /// - private int Compare(TKey key1, TKey key2) + public int Compare(TKey key1, TKey key2) { // First use the comparer if it is set if (_comparer != null) @@ -99,6 +92,13 @@ private int Compare(TKey key1, TKey key2) } private bool _defaultCompareFailed = false; + #endregion Public Methods + + // ************************************************************************ + // Private Methods + // ************************************************************************ + #region Private Methods + /// /// Searches for the index of the insertion point for the key passed in such that /// the sort order is maintained. Implemented as a non-recursive method. @@ -158,8 +158,8 @@ private int BinarySearchForMatch(int low, int high, TKey key, Func in } return -1; //return low; - } - + } + #endregion Private Methods } } diff --git a/Swordfish.NET.CollectionsV3/ConcurrentObservableCollection.cs b/Swordfish.NET.CollectionsV3/ConcurrentObservableCollection.cs index b5ef9cc..4cb58ab 100644 --- a/Swordfish.NET.CollectionsV3/ConcurrentObservableCollection.cs +++ b/Swordfish.NET.CollectionsV3/ConcurrentObservableCollection.cs @@ -123,7 +123,7 @@ public void RemoveRange(IList items) => () => new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, (IList)items) ); - public void Reset(IList items) => + public virtual void Reset(IList items) => DoReadWriteNotify( () => ImmutableList.ToArray(), (oldItems) => ImmutableList.Empty.AddRange(items), diff --git a/Swordfish.NET.CollectionsV3/ConcurrentObservableSortedCollection.cs b/Swordfish.NET.CollectionsV3/ConcurrentObservableSortedCollection.cs index 593dc43..1115087 100644 --- a/Swordfish.NET.CollectionsV3/ConcurrentObservableSortedCollection.cs +++ b/Swordfish.NET.CollectionsV3/ConcurrentObservableSortedCollection.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Collections.Immutable; using System.Collections.Specialized; using System.Linq; @@ -47,6 +48,14 @@ public override void AddRange(IList items) ); } + public override void Reset(IList items) => + DoReadWriteNotify( + () => new OldAndNew(ImmutableList.ToArray(),items.OrderBy(x => x, _sorter).ToList()), + (oldAndNew) => ImmutableList.Empty.AddRange(oldAndNew.New), + (oldAndNew) => new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, (IList)oldAndNew.Old, 0), + (oldAndNew) => new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, (IList)oldAndNew.New, 0) + ); + public override void Insert(int index, T item) { Add(item); @@ -54,22 +63,28 @@ public override void Insert(int index, T item) public override void InsertRange(int index, IList items) { - base.InsertRange(index, items); + AddRange(items); } public override T this[int index] { - get - { - return base[index]; - } - + get=> base[index]; set { RemoveAt(index); Add(value); } } + + private class OldAndNew { + public OldAndNew(T[] old, List @new) + { + Old = old; + New = @new; + } + public T[] Old; + public List New; + } } }