From 70da65fd5cafdc649ca22f063b46e4a593d90f0b Mon Sep 17 00:00:00 2001 From: Andrew Hoefling Date: Mon, 4 Mar 2019 16:09:03 -0500 Subject: [PATCH] Added Collection AddRange and InsertRange and added new XUnit tests to cover new APIs Added missing APIs from Collection; Added new unit tests for RemoveRange, ReplaceRange, InsertItemsRange and RemoveItemsRange Updated AddRange in Collection to add items to the end of the collection Added CollectionChanged tests for ObservableCollection for InsertRange, AddRange, RemoveRange and ReplaceRange Removing API changes as these will be mirrored from CoreCLR Updated array assertions to use Span to simplify logic Sorted order of new API methods Simplified CollectionTests Assertion to use Span instead of calling out each item in the array Added missing API ReplaceItemsRange and updated unit tests for check for overflow errors on RemoveItems Added overrides for ObservableCollection ItemsRange API to only fire CollectionChanged once per API call. Before this was firing for each item in the collection which is not the desired result. Updated unit tests to verify this logic Updated RemoveItemsRange to prevent int.MaxValue overflow errors Added int.MaxValue overflow tests for Collection and ObservableCollection when invoking RemoveItemsRange API Added additional RemoveRange unit tests to cover when Count is Less than 0 or when Count is 0 Updated RemoveRange overflow tests to check for ArgumentException for new business rules Updated ObservableCollection overflow test to assert ArgumentException Added try-finally block to ObservableCollection to certify that _skipCollectionChanged is always set to false after a collection manipulation in case an exception is thrown in a subclass. Added new test class to test the new rules on the example of a NonNullObservableCollection CollectionChanged events do not fire on RemoveRange if the count == 0 Updated ObservableCollection to only throw the 3 required events when ReplaceItemsRange is invoked. Updated OnChangedEvent methods to check for IList and cast as necessary. Added additional exception handling to reset _skipRaisingEvents bool. Added unit tests to verify event and event parameters are being passed correctly. Added Collection AddRange and InsertRange and added new XUnit tests to cover new APIs Simplified CollectionTests Assertion to use Span instead of calling out each item in the array Added additional RemoveRange unit tests to cover when Count is Less than 0 or when Count is 0 Updated ObservableCollection to only throw the 3 required events when ReplaceItemsRange is invoked. Updated OnChangedEvent methods to check for IList and cast as necessary. Added additional exception handling to reset _skipRaisingEvents bool. Added unit tests to verify event and event parameters are being passed correctly. Updated ReplaceRange event to pass in the OldItems and NewItems to match the event docs Simplified CollectionChanged 'NewItems for InsertItemsRange Updated removedItems to use an array instead of a List Added new OnCollectionChanged overloads to reduce need for additional if checks. Removed stale code Optimized ReplaceItemsRange by simplifying the itemsToReplace which is no an array instead of a List Removed (IList) cast in ReplaceItemsRange when raising CollectionChanged Removed temp array in RemoveRange which decreases memory allocation needed --- .../ObjectModel/ObservableCollection.cs | 147 ++++++- .../ObservableCollection_MethodsTest.cs | 275 ++++++++++++ ...leCollection_SkipCollectionChangedTests.cs | 118 +++++ .../tests/System.ObjectModel.Tests.csproj | 1 + src/System.Runtime/ref/System.Runtime.cs | 9 +- .../ObjectModel/CollectionTests.cs | 405 ++++++++++++++++++ 6 files changed, 950 insertions(+), 5 deletions(-) create mode 100644 src/System.ObjectModel/tests/ObservableCollection/ObservableCollection_SkipCollectionChangedTests.cs diff --git a/src/System.ObjectModel/src/System/Collections/ObjectModel/ObservableCollection.cs b/src/System.ObjectModel/src/System/Collections/ObjectModel/ObservableCollection.cs index f74a2c8c7bef..0f96dd66bc37 100644 --- a/src/System.ObjectModel/src/System/Collections/ObjectModel/ObservableCollection.cs +++ b/src/System.ObjectModel/src/System/Collections/ObjectModel/ObservableCollection.cs @@ -27,6 +27,9 @@ public class ObservableCollection : Collection, INotifyCollectionChanged, [NonSerialized] private int _blockReentrancyCount; + [NonSerialized] + private bool _skipRaisingEvents; + /// /// Initializes a new instance of ObservableCollection that is empty and has default initial capacity. /// @@ -121,11 +124,92 @@ protected override void RemoveItem(int index) base.RemoveItem(index); + if (!_skipRaisingEvents) + { + OnCountPropertyChanged(); + OnIndexerPropertyChanged(); + OnCollectionChanged(NotifyCollectionChangedAction.Remove, removedItem, index); + } + } + + /// + /// Called by base class Collection<T> when a count of items is removed from the list; + /// raises a CollectionChanged event to any listeners. + /// + protected override void RemoveItemsRange(int index, int count) + { + CheckReentrancy(); + + T[] removedItems = null; + + bool ignore = _skipRaisingEvents; + if (!ignore) + { + _skipRaisingEvents = true; + + if (count > 0) + { + removedItems = new T[count]; + for (int i = 0; i < count; i++) + { + removedItems[i] = this[index + i]; + } + } + } + + try + { + base.RemoveItemsRange(index, count); + } + finally + { + if (!ignore) + { + _skipRaisingEvents = false; + } + } + + if (count > 0 && !_skipRaisingEvents) + { + OnCountPropertyChanged(); + OnIndexerPropertyChanged(); + OnCollectionChanged(NotifyCollectionChangedAction.Remove, removedItems, index); + } + } + + /// + /// Called by base class Collection<T> when a collection of items is added to list; + /// raises a CollectionChanged event to any listeners. + /// + protected override void ReplaceItemsRange(int index, int count, IEnumerable collection) + { + CheckReentrancy(); + + _skipRaisingEvents = true; + + T[] itemsToReplace = new T[count - index]; + for (int i = index; i < count; i++) + { + itemsToReplace[i] = this[i]; + } + + try + { + base.ReplaceItemsRange(index, count, collection); + } + finally + { + _skipRaisingEvents = false; + } + + IList newItems = collection is IList list ? list : new List(collection); + OnCountPropertyChanged(); OnIndexerPropertyChanged(); - OnCollectionChanged(NotifyCollectionChangedAction.Remove, removedItem, index); + OnCollectionChanged(NotifyCollectionChangedAction.Replace, itemsToReplace, newItems, index); } + /// /// Called by base class Collection<T> when an item is added to list; /// raises a CollectionChanged event to any listeners. @@ -135,9 +219,48 @@ protected override void InsertItem(int index, T item) CheckReentrancy(); base.InsertItem(index, item); - OnCountPropertyChanged(); - OnIndexerPropertyChanged(); - OnCollectionChanged(NotifyCollectionChangedAction.Add, item, index); + if (!_skipRaisingEvents) + { + OnCountPropertyChanged(); + OnIndexerPropertyChanged(); + OnCollectionChanged(NotifyCollectionChangedAction.Add, item, index); + } + } + + /// + /// Called by base class Collection<T> when a collection of items is added to list; + /// raises a CollectionChanged event to any listeners. + /// + protected override void InsertItemsRange(int index, IEnumerable collection) + { + CheckReentrancy(); + + bool ignore = _skipRaisingEvents; + if (!ignore) + { + _skipRaisingEvents = true; + } + + try + { + base.InsertItemsRange(index, collection); + } + finally + { + if (!ignore) + { + _skipRaisingEvents = false; + } + } + + if (!_skipRaisingEvents) + { + IList newItems = collection is IList list ? list : new List(collection); + + OnCountPropertyChanged(); + OnIndexerPropertyChanged(); + OnCollectionChanged(NotifyCollectionChangedAction.Add, newItems, index); + } } /// @@ -265,6 +388,14 @@ private void OnCollectionChanged(NotifyCollectionChangedAction action, object it OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index)); } + /// + /// Helper to raise CollectionChanged event to any listeners + /// + private void OnCollectionChanged(NotifyCollectionChangedAction action, IList items, int index) + { + OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, items, index)); + } + /// /// Helper to raise CollectionChanged event to any listeners /// @@ -281,6 +412,14 @@ private void OnCollectionChanged(NotifyCollectionChangedAction action, object ol OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, newItem, oldItem, index)); } + /// + /// Helper to raise CollectionChanged event to any listeners + /// + private void OnCollectionChanged(NotifyCollectionChangedAction action, IList oldItems, IList newItems, int index) + { + OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, newItems, oldItems, index)); + } + /// /// Helper to raise CollectionChanged event with action == Reset to any listeners /// diff --git a/src/System.ObjectModel/tests/ObservableCollection/ObservableCollection_MethodsTest.cs b/src/System.ObjectModel/tests/ObservableCollection/ObservableCollection_MethodsTest.cs index 6f03a97cc190..d43045743140 100644 --- a/src/System.ObjectModel/tests/ObservableCollection/ObservableCollection_MethodsTest.cs +++ b/src/System.ObjectModel/tests/ObservableCollection/ObservableCollection_MethodsTest.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; +using System.Linq; using Xunit; namespace System.Collections.ObjectModel.Tests @@ -454,6 +455,280 @@ public static void GetEnumeratorTest() Assert.Equal(col.Count, i); e.Dispose(); } + + [Fact] + public static void InsertRange_NotifyCollectionChanged_Beginning_Test() + { + int[] dataToInsert = new int[] { 1, 2, 3, 4, 5 }; + int[] initialData = new int[] { 10, 11, 12, 13 }; + int eventCounter = 0; + ObservableCollection collection = new ObservableCollection(initialData); + collection.CollectionChanged += (o, e) => eventCounter++; + + collection.InsertRange(0, dataToInsert); + + Assert.NotNull(collection); + Assert.Equal(dataToInsert.Length + initialData.Length, collection.Count); + Assert.Equal(1, eventCounter); + + int[] collectionAssertion = collection.ToArray(); + Assert.Equal(dataToInsert, collectionAssertion.AsSpan(0, 5).ToArray()); + Assert.Equal(initialData, collectionAssertion.AsSpan(5).ToArray()); + } + + [Fact] + public static void InsertRange_NotifyCollectionChanged_Middle_Test() + { + int[] dataToInsert = new int[] { 1, 2, 3, 4, 5 }; + int[] initialData = new int[] { 10, 11, 12, 13 }; + int eventCounter = 0; + ObservableCollection collection = new ObservableCollection(initialData); + collection.CollectionChanged += (o, e) => eventCounter++; + + collection.InsertRange(2, dataToInsert); + + Assert.NotNull(collection); + Assert.Equal(dataToInsert.Length + initialData.Length, collection.Count); + Assert.Equal(1, eventCounter); + + int[] collectionAssertion = collection.ToArray(); + Assert.Equal(initialData.AsSpan(0, 2).ToArray(), collectionAssertion.AsSpan(0, 2).ToArray()); + Assert.Equal(dataToInsert, collectionAssertion.AsSpan(2, 5).ToArray()); + Assert.Equal(initialData.AsSpan(2, 2).ToArray(), collectionAssertion.AsSpan(7, 2).ToArray()); + } + + [Fact] + public static void InsertRange_NotifyCollectionChanged_End_Test() + { + int[] dataToInsert = new int[] { 1, 2, 3, 4, 5 }; + int[] initialData = new int[] { 10, 11, 12, 13 }; + int eventCounter = 0; + ObservableCollection collection = new ObservableCollection(initialData); + collection.CollectionChanged += (o, e) => eventCounter++; + + collection.InsertRange(4, dataToInsert); + + Assert.NotNull(collection); + Assert.Equal(dataToInsert.Length + initialData.Length, collection.Count); + Assert.Equal(1, eventCounter); + + int[] collectionAssertion = collection.ToArray(); + Assert.Equal(initialData, collectionAssertion.AsSpan(0, 4).ToArray()); + Assert.Equal(dataToInsert, collectionAssertion.AsSpan(4).ToArray()); + } + + [Fact] + public static void AddRange_NotifyCollectionChanged_Test() + { + int[] dataToInsert = new int[] { 1, 2, 3, 4, 5 }; + int[] initialData = new int[] { 10, 11, 12, 13 }; + int eventCounter = 0; + ObservableCollection collection = new ObservableCollection(initialData); + collection.CollectionChanged += (o, e) => eventCounter++; + + collection.AddRange(dataToInsert); + + Assert.NotNull(collection); + Assert.Equal(dataToInsert.Length + initialData.Length, collection.Count); + Assert.Equal(1, eventCounter); + + int[] collectionAssertion = collection.ToArray(); + Assert.Equal(initialData, collectionAssertion.AsSpan(0, 4).ToArray()); + Assert.Equal(dataToInsert, collectionAssertion.AsSpan(4).ToArray()); + } + + [Fact] + public static void AddRange_NotifyCollectionChanged_EventArgs_Test() + { + int[] dataToAdd = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 }; + int[] actualDataAdded = new int[0]; + ObservableCollection collection = new ObservableCollection(); + collection.CollectionChanged += (o, e) => actualDataAdded = e.NewItems.Cast().ToArray(); + + collection.AddRange(dataToAdd); + + Assert.NotNull(collection); + Assert.Equal(dataToAdd, actualDataAdded); + } + + [Fact] + public static void InsertRange_NotifyCollectionChanged_EventArgs_Test() + { + int[] dataToAdd = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 }; + int[] actualDataAdded = new int[0]; + ObservableCollection collection = new ObservableCollection(); + collection.CollectionChanged += (o, e) => actualDataAdded = e.NewItems.Cast().ToArray(); + + collection.InsertRange(0, dataToAdd); + + Assert.NotNull(collection); + Assert.Equal(dataToAdd, actualDataAdded); + } + + [Fact] + public static void InsertRange_NotifyCollectionChanged_EventArgs_Middle_Test() + { + int[] dataToAdd = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 }; + int[] actualDataAdded = new int[0]; + ObservableCollection collection = new ObservableCollection(); + for (int i = 0; i < 4; i++) + { + collection.Add(i); + } + + collection.CollectionChanged += (o, e) => actualDataAdded = e.NewItems.Cast().ToArray(); + collection.InsertRange(2, dataToAdd); + + Assert.NotNull(collection); + Assert.Equal(dataToAdd, actualDataAdded); + } + + [Fact] + public static void InsertRange_NotifyCollectionChanged_EventArgs_End_Test() + { + int[] dataToAdd = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 }; + int[] actualDataAdded = new int[0]; + ObservableCollection collection = new ObservableCollection(); + for (int i = 0; i < 4; i++) + { + collection.Add(i); + } + + collection.CollectionChanged += (o, e) => actualDataAdded = e.NewItems.Cast().ToArray(); + collection.InsertRange(4, dataToAdd); + + Assert.NotNull(collection); + Assert.Equal(dataToAdd, actualDataAdded); + } + + [Fact] + public static void RemoveRange_NotifyCollectionChanged_FirstTwo_Test() + { + int[] initialData = new int[] { 10, 11, 12, 13 }; + int itemsToRemove = 2; + int eventCounter = 0; + ObservableCollection collection = new ObservableCollection(initialData); + collection.CollectionChanged += (o, e) => eventCounter++; + + collection.RemoveRange(0, itemsToRemove); + + Assert.NotNull(collection); + Assert.Equal(initialData.Length - itemsToRemove, collection.Count); + Assert.Equal(1, eventCounter); + Assert.Equal(initialData.AsSpan(2, 2).ToArray(), collection.ToArray()); + } + + [Fact] + public static void RemoveRange_NotifyCollectionChanged_MiddleTwo_Test() + { + int[] initialData = new int[] { 10, 11, 12, 13 }; + int itemsToRemove = 2; + int eventCounter = 0; + ObservableCollection collection = new ObservableCollection(initialData); + collection.CollectionChanged += (o, e) => eventCounter++; + + collection.RemoveRange(1, itemsToRemove); + + Assert.NotNull(collection); + Assert.Equal(initialData.Length - itemsToRemove, collection.Count); + Assert.Equal(1, eventCounter); + Assert.Equal(initialData[0], collection[0]); + Assert.Equal(initialData[3], collection[1]); + } + + [Fact] + public static void RemoveRange_NotifyCollectionChanged_LastTwo_Test() + { + int[] initialData = new int[] { 10, 11, 12, 13 }; + int itemsToRemove = 2; + int eventCounter = 0; + ObservableCollection collection = new ObservableCollection(initialData); + collection.CollectionChanged += (o, e) => eventCounter++; + + collection.RemoveRange(2, itemsToRemove); + + Assert.NotNull(collection); + Assert.Equal(initialData.Length - itemsToRemove, collection.Count); + Assert.Equal(1, eventCounter); + Assert.Equal(initialData.AsSpan(0, 2).ToArray(), collection.ToArray()); + } + + [Fact] + public static void RemoveRange_NotifyCollectionChanged_IntMaxValueOverflow_Test() + { + int eventCounter = 0; + int count = 500; + ObservableCollection collection = new ObservableCollection(); + for (int i = 0; i < count; i++) + { + collection.Add(i); + } + + collection.CollectionChanged += (o, e) => eventCounter++; + + Assert.Throws(() => collection.RemoveRange(collection.Count - 2, int.MaxValue)); + } + + [Fact] + public static void RemoveRange_NotifyCollectionChanged_EventArgs_IndexOfZero_Test() + { + int[] initialData = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 }; + int[] actualDataRemoved = new int[0]; + int numberOfItemsToRemove = 4; + ObservableCollection collection = new ObservableCollection(); + foreach (int item in initialData) + { + collection.Add(item); + } + + collection.CollectionChanged += (o, e) => actualDataRemoved = e.OldItems.Cast().ToArray(); + collection.RemoveRange(0, numberOfItemsToRemove); + + Assert.NotNull(collection); + Assert.Equal(initialData.Length - numberOfItemsToRemove, collection.Count); + Assert.Equal(initialData.AsSpan(0, numberOfItemsToRemove).ToArray(), actualDataRemoved); + } + + [Fact] + public static void RemoveRange_NotifyCollectionChanged_EventArgs_IndexMiddle_Test() + { + int[] initialData = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 }; + int[] actualDataRemoved = new int[0]; + int numberOfItemsToRemove = 4; + int startIndex = 3; + ObservableCollection collection = new ObservableCollection(); + foreach (int item in initialData) + { + collection.Add(item); + } + + collection.CollectionChanged += (o, e) => actualDataRemoved = e.OldItems.Cast().ToArray(); + collection.RemoveRange(startIndex, numberOfItemsToRemove); + + Assert.NotNull(collection); + Assert.Equal(initialData.Length - numberOfItemsToRemove, collection.Count); + Assert.Equal(initialData.AsSpan(startIndex, numberOfItemsToRemove).ToArray(), actualDataRemoved); + } + + [Fact] + public static void ReplaceRange_NotifyCollectionChanged_Test() + { + int[] initialData = new int[] { 10, 11, 12, 13 }; + int[] dataToReplace = new int[] { 3, 8 }; + int eventCounter = 0; + ObservableCollection collection = new ObservableCollection(initialData); + collection.CollectionChanged += (o, e) => eventCounter++; + + collection.ReplaceRange(0, 2, dataToReplace); + + Assert.NotNull(collection); + Assert.Equal(initialData.Length, collection.Count); + Assert.Equal(1, eventCounter); + + int[] collectionAssertion = collection.ToArray(); + Assert.Equal(dataToReplace, collectionAssertion.AsSpan(0, 2).ToArray()); + Assert.Equal(initialData.AsSpan(2, 2).ToArray(), collectionAssertion.AsSpan(2, 2).ToArray()); + } } /// diff --git a/src/System.ObjectModel/tests/ObservableCollection/ObservableCollection_SkipCollectionChangedTests.cs b/src/System.ObjectModel/tests/ObservableCollection/ObservableCollection_SkipCollectionChangedTests.cs new file mode 100644 index 000000000000..b8480702ad84 --- /dev/null +++ b/src/System.ObjectModel/tests/ObservableCollection/ObservableCollection_SkipCollectionChangedTests.cs @@ -0,0 +1,118 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Xunit; + +namespace System.ObjectModel.Tests.ObservableCollection +{ + public class ObservableCollection_SkipCollectionChangedTests + { + [Fact] + public void SkipCollectionChanged_AddRange_Test() + { + int collectionChangedCounter = 0; + NonNullObservableCollection collection = new NonNullObservableCollection(); + collection.Add("1"); + collection.Add("2"); + collection.Add("3"); + collection.CollectionChanged += (s, e) => collectionChangedCounter++; + + Assert.Throws(() => collection.AddRange(new string[1])); + Assert.Equal(0, collectionChangedCounter); + + collection.Add("4"); + Assert.Equal(1, collectionChangedCounter); + } + + [Fact] + public void SkipCollectionChanged_InsertRange_Test() + { + int collectionChangedCounter = 0; + NonNullObservableCollection collection = new NonNullObservableCollection(); + collection.Add("1"); + collection.Add("2"); + collection.Add("3"); + collection.CollectionChanged += (s, e) => collectionChangedCounter++; + + Assert.Throws(() => collection.InsertRange(0, new string[1])); + Assert.Equal(0, collectionChangedCounter); + + collection.Add("4"); + Assert.Equal(1, collectionChangedCounter); + } + + [Fact] + public void SkipCollectionChanged_RemoveRange_Test() + { + int collectionChangedCounter = 0; + NonNullObservableCollection collection = new NonNullObservableCollection(); + collection.Add("1"); + collection.Add("2"); + collection.Add("3"); + collection.CollectionChanged += (s, e) => collectionChangedCounter++; + + collection.RemoveRange(0, 2); + Assert.Equal(1, collectionChangedCounter); + + collection.Add("1"); + Assert.Equal(2, collectionChangedCounter); + } + + [Fact] + public void SkipCollectionChanged_RemoveRange_NoEventsRaised_Test() + { + int collectionChangedCounter = 0; + NonNullObservableCollection collection = new NonNullObservableCollection(); + collection.Add("1"); + collection.Add("2"); + collection.Add("3"); + collection.CollectionChanged += (s, e) => collectionChangedCounter++; + + collection.RemoveRange(0, 0); + + Assert.Equal(0, collectionChangedCounter); + } + + [Fact] + public void SkipCollectionChanged_ReplaceRange_Test() + { + int collectionChangedCounter = 0; + NonNullObservableCollection collection = new NonNullObservableCollection(); + collection.Add("1"); + collection.Add("2"); + collection.Add("3"); + collection.CollectionChanged += (s, e) => collectionChangedCounter++; + + Assert.Throws(() => collection.ReplaceRange(0, 2, new string[1])); + Assert.Equal(0, collectionChangedCounter); + + collection.Add("1"); + Assert.Equal(1, collectionChangedCounter); + } + + public class NonNullObservableCollection : ObservableCollection + { + + public NonNullObservableCollection() : base() { } + public NonNullObservableCollection(List list) : base(list) { } + + protected override void InsertItem(int index, T item) + { + if (item == null) + { + throw new ArgumentNullException(); + } + + base.InsertItem(index, item); + } + + protected override void SetItem(int index, T item) + { + if (item == null) + { + } + + base.SetItem(index, item); + } + } + } +} diff --git a/src/System.ObjectModel/tests/System.ObjectModel.Tests.csproj b/src/System.ObjectModel/tests/System.ObjectModel.Tests.csproj index 7682226ac131..c9a873a29b9a 100644 --- a/src/System.ObjectModel/tests/System.ObjectModel.Tests.csproj +++ b/src/System.ObjectModel/tests/System.ObjectModel.Tests.csproj @@ -22,6 +22,7 @@ Common\System\CollectionsIDictionaryTest.cs + diff --git a/src/System.Runtime/ref/System.Runtime.cs b/src/System.Runtime/ref/System.Runtime.cs index 116238ebc6b4..46d11ff3bf46 100644 --- a/src/System.Runtime/ref/System.Runtime.cs +++ b/src/System.Runtime/ref/System.Runtime.cs @@ -4049,6 +4049,7 @@ public Collection(System.Collections.Generic.IList list) { } bool System.Collections.IList.IsReadOnly { get { throw null; } } object System.Collections.IList.this[int index] { get { throw null; } set { } } public void Add(T item) { } + public void AddRange(System.Collections.Generic.IEnumerable collection) { } public void Clear() { } protected virtual void ClearItems() { } public bool Contains(T item) { throw null; } @@ -4057,9 +4058,15 @@ public void CopyTo(T[] array, int index) { } public int IndexOf(T item) { throw null; } public void Insert(int index, T item) { } protected virtual void InsertItem(int index, T item) { } - public bool Remove(T item) { throw null; } + protected virtual void InsertItemsRange(int index, System.Collections.Generic.IEnumerable collection) { } + public void InsertRange(int index, System.Collections.Generic.IEnumerable collection) { } + public bool Remove(T item) { throw null; } public void RemoveAt(int index) { } protected virtual void RemoveItem(int index) { } + protected virtual void RemoveItemsRange(int index, int count) { } + public void RemoveRange(int index, int count) { } + protected virtual void ReplaceItemsRange(int index, int count, System.Collections.Generic.IEnumerable collection) { } + public void ReplaceRange(int index, int count, System.Collections.Generic.IEnumerable collection) { } protected virtual void SetItem(int index, T item) { } void System.Collections.ICollection.CopyTo(System.Array array, int index) { } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } diff --git a/src/System.Runtime/tests/System/Collections/ObjectModel/CollectionTests.cs b/src/System.Runtime/tests/System/Collections/ObjectModel/CollectionTests.cs index 4e1fb2f82eb0..0f7a5bca6bb6 100644 --- a/src/System.Runtime/tests/System/Collections/ObjectModel/CollectionTests.cs +++ b/src/System.Runtime/tests/System/Collections/ObjectModel/CollectionTests.cs @@ -6,6 +6,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; using Xunit; namespace System.Collections.ObjectModel.Tests @@ -325,6 +326,410 @@ public void ReadOnly_ModifyingCollection_ThrowsNotSupportedException() Assert.Throws(() => collection.RemoveAt(0)); } + [Fact] + public void Collection_AddRange_ToEmpty_Test() + { + int[] expected = new[] { 1, 2, 3, 4 }; + Collection collection = new Collection(); + + collection.AddRange(expected); + + Assert.NotNull(collection); + Assert.Equal(expected.Length, collection.Count); + Assert.Equal(expected, collection.ToArray()); + } + + [Fact] + public void Collection_AddRange_ToExisting_Test() + { + int[] initial = new int[] { 1, 2, 3, 4 }; + int[] dataToInsert = new int[] { 5, 6, 7, 8 }; + Collection collection = new Collection(); + for (int i = 0; i < initial.Length; i++) + collection.Add(initial[i]); + + collection.AddRange(dataToInsert); + + Assert.NotNull(collection); + Assert.Equal(initial.Length + dataToInsert.Length, collection.Count); + + int[] collectionAssertion = collection.ToArray(); + Assert.Equal(initial, collectionAssertion.AsSpan(0, 4).ToArray()); + Assert.Equal(dataToInsert, collectionAssertion.AsSpan(4, 4).ToArray()); + } + + [Fact] + public void Collection_AddRange_Empty_Test() + { + int[] expected = new int[0]; + Collection collection = new Collection(); + + collection.AddRange(expected); + + Assert.NotNull(collection); + Assert.Equal(expected.Length, collection.Count); + } + + [Fact] + public void Collection_AddRange_Null_Test() + { + Collection collection = new Collection(); + Exception ex = Assert.Throws(() => collection.AddRange(null)); + } + + [Fact] + public void Collection_AddRange_ReadOnly_Test() + { + int[] expected = new int[] { 1, 2, 3, 4 }; + int[] baseCollection = new int[] { 5, 6, 7, 8 }; + Collection collection = new Collection(baseCollection); + + Exception ex = Assert.Throws(() => collection.AddRange(expected)); + } + + [Fact] + public void Collection_InsertRange_Beginning_Test() + { + int[] expected = new int[] { 1, 2, 3, 4, 5 }; + int[] originalCollection = new int[] { 10, 11, 12, 13 }; + List baseCollection = new List(originalCollection); + + int expectedLength = expected.Length + originalCollection.Length; + Collection collection = new Collection(baseCollection); + + collection.InsertRange(0, expected); + + Assert.NotNull(collection); + Assert.Equal(expectedLength, collection.Count); + + int[] collectionAssertion = collection.ToArray(); + Assert.Equal(expected, collectionAssertion.AsSpan(0, 5).ToArray()); + Assert.Equal(originalCollection, collectionAssertion.AsSpan(5, 4).ToArray()); + } + + [Fact] + public void Collection_InsertRange_End_Test() + { + int[] expected = new int[] { 1, 2, 3, 4, 5 }; + int[] originalCollection = new int[] { 10, 11, 12, 13 }; + List baseCollection = new List(originalCollection); + + int expectedLength = expected.Length + originalCollection.Length; + Collection collection = new Collection(baseCollection); + + collection.InsertRange(expected.Length - 1, expected); + + Assert.NotNull(collection); + Assert.Equal(expectedLength, collection.Count); + + int[] collectionAssertion = collection.ToArray(); + Assert.Equal(originalCollection, collectionAssertion.AsSpan(0, 4).ToArray()); + Assert.Equal(expected, collectionAssertion.AsSpan(4, 5).ToArray()); + } + + [Fact] + public void Collection_InsertRange_Middle_Test() + { + int[] expected = new int[] { 1, 2, 3, 4, 5 }; + int[] originalCollection = new int[] { 10, 11, 12, 13 }; + List baseCollection = new List(originalCollection); + + int expectedLength = expected.Length + originalCollection.Length; + Collection collection = new Collection(baseCollection); + + collection.InsertRange(2, expected); + + Assert.NotNull(collection); + Assert.Equal(expectedLength, collection.Count); + + int[] collectionAssertion = collection.ToArray(); + Assert.Equal(originalCollection.AsSpan(0, 2).ToArray(), collectionAssertion.AsSpan(0, 2).ToArray()); + Assert.Equal(expected, collectionAssertion.AsSpan(2, 5).ToArray()); + Assert.Equal(originalCollection.AsSpan(2, 2).ToArray(), collectionAssertion.AsSpan(7, 2).ToArray()); + } + + [Fact] + public void Collection_InsertRange_Empty_Test() + { + List baseCollection = new List(new[] { 10, 11, 12, 13 }); + Collection collection = new Collection(baseCollection); + + collection.InsertRange(0, new int[0]); + + Assert.NotNull(collection); + Assert.Equal(baseCollection.Count, collection.Count); + Assert.Equal(baseCollection, collection.ToArray()); + } + + [Fact] + public void Collection_InsertRange_Null_Test() + { + Collection collection = new Collection(); + Exception ex = Assert.Throws(() => collection.InsertRange(0, null)); + } + + [Fact] + public void Collection_InsertRange_ReadOnly_Test() + { + int[] expected = new int[] { 1, 2, 3, 4 }; + int[] baseCollection = new int[] { 5, 6, 7, 8 }; + Collection collection = new Collection(baseCollection); + + Exception ex = Assert.Throws(() => collection.InsertRange(0, expected)); + } + + [Fact] + public void Collection_InsertRange_IndexLessThan0_Test() + { + int[] expected = new int[] { 1, 2, 3, 4 }; + Collection collection = new Collection(); + Exception ex = Assert.Throws(() => collection.InsertRange(-1, expected)); + } + + [Fact] + public void Collection_InsertRange_IndexGreaterThanCount_Test() + { + int[] expected = new int[] { 1, 2, 3, 4 }; + Collection collection = new Collection(); + Exception ex = Assert.Throws(() => collection.InsertRange(10, expected)); + } + + [Fact] + public void Collection_RemoveRange_Overflow_Test() + { + Collection collection = new Collection(); + collection.Add(1); + collection.Add(2); + collection.Add(3); + + Assert.Throws(() => collection.RemoveRange(0, 4)); + } + + [Fact] + public void Collection_RemoveRange_IntMaxValueOverflow_Test() + { + var count = 500; + Collection collection = new Collection(); + for (int i = 0; i < count; i++) + { + collection.Add(i); + } + + Assert.Throws(() => collection.RemoveRange(collection.Count - 2, int.MaxValue)); + } + + [Fact] + public void Collection_RemoveRange_CountIsZero_Test() + { + int[] expected = new int[] { 1, 2, 3, 4, 5 }; + Collection collection = new Collection(); + for (int i = 0; i < expected.Length; i++) + { + collection.Add(expected[i]); + } + + collection.RemoveRange(0, 0); + + Assert.NotNull(collection); + Assert.Equal(expected.Length, collection.Count); + Assert.Equal(expected, collection.ToArray()); + } + + [Fact] + public void Collection_RemoveRange_CountIsLessThanZero_Test() + { + Collection collection = new Collection(); + collection.Add(1); + collection.Add(2); + + Assert.Throws(() => collection.RemoveRange(0, -1)); + } + + [Fact] + public void Collection_RemoveRange_All_Test() + { + Collection collection = new Collection(); + collection.Add(1); + collection.Add(2); + collection.Add(3); + + collection.RemoveRange(0, 3); + + Assert.NotNull(collection); + Assert.Equal(0, collection.Count); + } + + [Fact] + public void Collection_RemoveRange_FirstTwoItems_Test() + { + Collection collection = new Collection(); + collection.Add(1); + collection.Add(2); + collection.Add(3); + + collection.RemoveRange(0, 2); + + Assert.NotNull(collection); + Assert.Equal(1, collection.Count); + Assert.Equal(3, collection[0]); + } + + [Fact] + public void Collection_RemoveRange_LastTwoItems_Test() + { + Collection collection = new Collection(); + collection.Add(1); + collection.Add(2); + collection.Add(3); + + collection.RemoveRange(1, 2); + + Assert.NotNull(collection); + Assert.Equal(1, collection.Count); + Assert.Equal(1, collection[0]); + } + + [Fact] + public void Collection_RemoveRange_ZeroItems_Test() + { + Collection collection = new Collection(); + collection.Add(1); + collection.Add(2); + collection.Add(3); + + collection.RemoveRange(0, 0); + + Assert.NotNull(collection); + Assert.Equal(3, collection.Count); + Assert.Equal(1, collection[0]); + Assert.Equal(2, collection[1]); + Assert.Equal(3, collection[2]); + } + + [Fact] + public void Collection_RemoveRange_IndexLessThanZero_Test() + { + Collection collection = new Collection(); + collection.Add(1); + collection.Add(2); + collection.Add(3); + + AssertExtensions.Throws("index", () => collection.RemoveRange(-1, 3)); + } + + [Fact] + public void Collection_RemoveRange_IndexGreaterThanCollection_Test() + { + Collection collection = new Collection(); + collection.Add(1); + collection.Add(2); + collection.Add(3); + + AssertExtensions.Throws("index", () => collection.RemoveRange(4, 3)); + } + + [Fact] + public void Collection_RemoveRange_ReadOnly_Test() + { + Collection collection = new Collection(new int[] { 1, 2, 3 }); + + Assert.Throws(() => collection.RemoveRange(0, 2)); + } + + [Fact] + public void Collection_ReplaceRange_FirstTwo_Test() + { + int[] initial = new int[] { 1, 2, 3, 4 }; + int[] replace = new int[] { 5, 6, 7, 8 }; + Collection collection = new Collection(); + foreach (var item in initial) + collection.Add(item); + + collection.ReplaceRange(0, 2, replace); + + Assert.NotNull(collection); + Assert.Equal(initial.Length + 2, collection.Count); + + int[] collectionAssertion = collection.ToArray(); + Assert.Equal(replace, collectionAssertion.AsSpan(0, 4).ToArray()); + Assert.Equal(initial.AsSpan(2, 2).ToArray(), collectionAssertion.AsSpan(4, 2).ToArray()); + } + + [Fact] + public void Collection_ReplaceRange_LastTwo_Test() + { + int[] initial = new int[] { 1, 2, 3, 4 }; + int[] replace = new int[] { 5, 6, 7, 8 }; + Collection collection = new Collection(); + foreach (var item in initial) + collection.Add(item); + + collection.ReplaceRange(2, 2, replace); + + Assert.NotNull(collection); + Assert.Equal(initial.Length + 2, collection.Count); + + int[] collectionAssertion = collection.ToArray(); + Assert.Equal(initial.AsSpan(0, 2).ToArray(), collectionAssertion.AsSpan(0, 2).ToArray()); + Assert.Equal(replace.AsSpan(0, 4).ToArray(), collectionAssertion.AsSpan(2, 4).ToArray()); + } + + [Fact] + public void Collection_ReplaceRange_MiddleTwo_Test() + { + int[] initial = new int[] { 1, 2, 3, 4 }; + int[] replace = new int[] { 5, 6, 7, 8 }; + Collection collection = new Collection(); + foreach (var item in initial) + collection.Add(item); + + collection.ReplaceRange(1, 2, replace); + + Assert.NotNull(collection); + Assert.Equal(initial.Length + 2, collection.Count); + + Assert.Equal(initial[0], collection[0]); + Assert.Equal(replace, collection.ToArray().AsSpan(1, 4).ToArray()); + Assert.Equal(initial[3], collection[5]); + } + + [Fact] + public void Collection_ReplaceRange_NullCollection_Test() + { + Collection collection = new Collection(); + collection.Add(1); + collection.Add(2); + + Assert.Throws(() => collection.ReplaceRange(0, 2, null)); + } + + [Fact] + public void Collection_ReplaceRange_ReadOnly_Test() + { + Collection collection = new Collection(new int[] { 1, 2, 3 }); + Assert.Throws(() => collection.ReplaceRange(0, 2, new int[] { 4, 5 })); + } + + [Fact] + public void Collection_ReplaceRange_IndexLessThanZero_Test() + { + Collection collection = new Collection(); + collection.Add(1); + collection.Add(2); + + AssertExtensions.Throws("index", () => collection.ReplaceRange(-2, 2, new int[] { 1, 2 })); + } + + [Fact] + public void Collection_ReplaceRange_IndexGreaterThanCount_Test() + { + Collection collection = new Collection(); + collection.Add(1); + collection.Add(2); + + AssertExtensions.Throws("index", () => collection.ReplaceRange(4, 2, new int[] { 1, 2 })); + } + private class TestCollection : Collection { public TestCollection()