diff --git a/src/FastSerialization/SegmentedDictionary/HashHelpers.cs b/src/FastSerialization/SegmentedDictionary/HashHelpers.cs new file mode 100644 index 000000000..bb07be670 --- /dev/null +++ b/src/FastSerialization/SegmentedDictionary/HashHelpers.cs @@ -0,0 +1,115 @@ +// Tests copied from dotnet/roslyn repo. Original source code can be found here: +// https://github.com/dotnet/roslyn/blob/main/src/Dependencies/Collections/Internal/HashHelpers.cs + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Microsoft.Diagnostics.FastSerialization +{ + internal static class HashHelpers + { + // This is the maximum prime smaller than Array.MaxArrayLength + public const int MaxPrimeArrayLength = 0x7FEFFFFD; + + public const int HashPrime = 101; + + // Table of prime numbers to use as hash table sizes. + // A typical resize algorithm would pick the smallest prime number in this array + // that is larger than twice the previous capacity. + // Suppose our Hashtable currently has capacity x and enough elements are added + // such that a resize needs to occur. Resizing first computes 2x then finds the + // first prime in the table greater than 2x, i.e. if primes are ordered + // p_1, p_2, ..., p_i, ..., it finds p_n such that p_n-1 < 2x < p_n. + // Doubling is important for preserving the asymptotic complexity of the + // hashtable operations such as add. Having a prime guarantees that double + // hashing does not lead to infinite loops. IE, your hash function will be + // h1(key) + i*h2(key), 0 <= i < size. h2 and the size must be relatively prime. + // We prefer the low computation costs of higher prime numbers over the increased + // memory allocation of a fixed prime number i.e. when right sizing a HashSet. + private static readonly int[] s_primes = + { + 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, + 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, + 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, + 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, + 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369 + }; + + public static bool IsPrime(int candidate) + { + if ((candidate & 1) != 0) + { + var limit = (int)Math.Sqrt(candidate); + for (var divisor = 3; divisor <= limit; divisor += 2) + { + if ((candidate % divisor) == 0) + return false; + } + return true; + } + return candidate == 2; + } + + public static int GetPrime(int min) + { + if (min < 0) + throw new ArgumentException("Collection's capacity overflowed and went negative."); + + foreach (var prime in s_primes) + { + if (prime >= min) + return prime; + } + + // Outside of our predefined table. Compute the hard way. + for (var i = (min | 1); i < int.MaxValue; i += 2) + { + if (IsPrime(i) && ((i - 1) % HashPrime != 0)) + return i; + } + return min; + } + + // Returns size of hashtable to grow to. + public static int ExpandPrime(int oldSize) + { + var newSize = 2 * oldSize; + + // Allow the hashtables to grow to maximum possible size (~2G elements) before encountering capacity overflow. + // Note that this check works even when _items.Length overflowed thanks to the (uint) cast + if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize) + { + Debug.Assert(MaxPrimeArrayLength == GetPrime(MaxPrimeArrayLength), "Invalid MaxPrimeArrayLength"); + return MaxPrimeArrayLength; + } + + return GetPrime(newSize); + } + + /// Returns approximate reciprocal of the divisor: ceil(2**64 / divisor). + /// This should only be used on 64-bit. + public static ulong GetFastModMultiplier(uint divisor) => + ulong.MaxValue / divisor + 1; + + /// Performs a mod operation using the multiplier pre-computed with . + /// + /// PERF: This improves performance in 64-bit scenarios at the expense of performance in 32-bit scenarios. Since + /// we only build a single AnyCPU binary, we opt for improved performance in the 64-bit scenario. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint FastMod(uint value, uint divisor, ulong multiplier) + { + // We use modified Daniel Lemire's fastmod algorithm (https://github.com/dotnet/runtime/pull/406), + // which allows to avoid the long multiplication if the divisor is less than 2**31. + Debug.Assert(divisor <= int.MaxValue); + + // This is equivalent of (uint)Math.BigMul(multiplier * value, divisor, out _). This version + // is faster than BigMul currently because we only need the high bits. + var highbits = (uint)(((((multiplier * value) >> 32) + 1) * divisor) >> 32); + + Debug.Assert(highbits == value % divisor); + return highbits; + } + } +} diff --git a/src/FastSerialization/SegmentedDictionary/SegmentedDictionary.cs b/src/FastSerialization/SegmentedDictionary/SegmentedDictionary.cs new file mode 100644 index 000000000..4e2991fcf --- /dev/null +++ b/src/FastSerialization/SegmentedDictionary/SegmentedDictionary.cs @@ -0,0 +1,1346 @@ +using System.Diagnostics; +using System.Runtime.CompilerServices; +using Microsoft.Diagnostics.FastSerialization; + +namespace System.Collections.Generic +{ + /// + /// Represents a collection of keys and values. + /// + /// + /// This collection has the similar performance characteristics as , but + /// uses segmented lists to avoid allocations in the Large Object Heap. + /// + /// + /// This implementation was based on the SegmentedDictionary implementation made for dotnet/roslyn. Original source code: + /// https://github.com/dotnet/roslyn/blob/release/dev17.0/src/Dependencies/Collections/SegmentedDictionary%602.cs + /// + /// + /// The type of the keys in the dictionary. + /// The type of the values in the dictionary. + public sealed class SegmentedDictionary : IDictionary, IDictionary + { + #region Private Fields + private static Entry EntryPlaceholder = new Entry(); + + private SegmentedList _buckets = new SegmentedList(defaultSegmentSize); + private SegmentedList _entries = new SegmentedList(defaultSegmentSize); + + private const int defaultSegmentSize = 8_192; + + private int _count; + private int _freeList; + private int _freeCount; + private ulong _fastModMultiplier; + private int _version; + + private readonly IEqualityComparer _comparer; + + private KeyCollection _keys = null; + private ValueCollection _values = null; + private const int StartOfFreeList = -3; + + private enum InsertionBehavior + { + None, OverwriteExisting, ThrowOnExisting + } + + private struct Entry + { + public uint _hashCode; + /// + /// 0-based index of next entry in chain: -1 means end of chain + /// also encodes whether this entry _itself_ is part of the free list by changing sign and subtracting 3, + /// so -2 means end of free list, -3 means index 0 but on free list, -4 means index 1 but on free list, etc. + /// + public int _next; + public TKey _key; // Key of entry + public TValue _value; // Value of entry + } + + #endregion + + #region Helper Methods + + private int Initialize(int capacity) + { + var size = HashHelpers.GetPrime(capacity); + var buckets = new SegmentedList(defaultSegmentSize, size); + var entries = new SegmentedList(defaultSegmentSize, size); + + // Assign member variables after both arrays allocated to guard against corruption from OOM if second fails + _freeList = -1; + _fastModMultiplier = HashHelpers.GetFastModMultiplier((uint)buckets.Capacity); + _buckets = buckets; + _entries = entries; + + return size; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ref int GetBucket(uint hashCode) + { + var buckets = _buckets; + return ref buckets.GetElementByReference((int)HashHelpers.FastMod(hashCode, (uint)buckets.Capacity, _fastModMultiplier)); + } + + private bool FindEntry(TKey key, out Entry entry) + { + entry = EntryPlaceholder; + + if (key == null) + { + throw new ArgumentNullException("Key cannot be null."); + } + + if (_buckets.Capacity > 0) + { + Debug.Assert(_entries.Capacity > 0, "expected entries to be non-empty"); + var comparer = _comparer; + + var hashCode = (uint)comparer.GetHashCode(key); + var i = GetBucket(hashCode) - 1; // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional. + var entries = _entries; + uint collisionCount = 0; + + do + { + // Should be a while loop https://github.com/dotnet/runtime/issues/9422 + // Test in if to drop range check for following array access + if ((uint)i >= (uint)entries.Capacity) + { + return false; + } + + ref var currentEntry = ref entries.GetElementByReference(i); + if (currentEntry._hashCode == hashCode && comparer.Equals(currentEntry._key, key)) + { + entry = currentEntry; + return true; + } + + i = currentEntry._next; + + collisionCount++; + } while (collisionCount <= (uint)entries.Capacity); + + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + throw new InvalidOperationException("Dictionary does not support concurrent operations."); + } + + return false; + } + + private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior) + { + if (key == null) + { + throw new ArgumentNullException("Key cannot be null."); + } + + if (_buckets.Capacity == 0) + { + Initialize(0); + } + Debug.Assert(_buckets.Capacity > 0); + + var entries = _entries; + Debug.Assert(entries.Capacity > 0, "expected entries to be non-empty"); + + var comparer = _comparer; + var hashCode = (uint)comparer.GetHashCode(key); + + uint collisionCount = 0; + ref var bucket = ref GetBucket(hashCode); + var i = bucket - 1; // Value in _buckets is 1-based + + while (true) + { + // Should be a while loop https://github.com/dotnet/runtime/issues/9422 + // Test uint in if rather than loop condition to drop range check for following array access + if ((uint)i >= (uint)entries.Capacity) + { + break; + } + + if (entries[i]._hashCode == hashCode && comparer.Equals(entries[i]._key, key)) + { + if (behavior == InsertionBehavior.OverwriteExisting) + { + entries.GetElementByReference(i)._value = value; + return true; + } + + if (behavior == InsertionBehavior.ThrowOnExisting) + { + throw new ArgumentException($"The key with value {key} is already present in the dictionary."); + } + + return false; + } + + i = entries[i]._next; + + collisionCount++; + if (collisionCount > (uint)entries.Capacity) + { + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + throw new InvalidOperationException("Dictionary does not support concurrent operations."); + } + } + + + int index; + if (_freeCount > 0) + { + index = _freeList; + Debug.Assert((StartOfFreeList - entries[_freeList]._next) >= -1, "shouldn't overflow because `next` cannot underflow"); + _freeList = StartOfFreeList - entries[_freeList]._next; + _freeCount--; + } + else + { + var count = _count; + if (count == entries.Capacity) + { + Resize(); + bucket = ref GetBucket(hashCode); + } + index = count; + _count = count + 1; + entries = _entries; + } + + ref var entry = ref entries.GetElementByReference(index); + entry._hashCode = hashCode; + entry._next = bucket - 1; // Value in _buckets is 1-based + entry._key = key; + entry._value = value; // Value in _buckets is 1-based + bucket = index + 1; + _version++; + return true; + } + + private void Resize() + => Resize(HashHelpers.ExpandPrime(_count)); + + private void Resize(int newSize) + { + Debug.Assert(_entries.Capacity > 0, "_entries should be non-empty"); + Debug.Assert(newSize >= _entries.Capacity); + + var entries = new SegmentedList(defaultSegmentSize, newSize); + + var count = _count; + + entries.AppendFrom(_entries, 0, count); + + // Assign member variables after both arrays allocated to guard against corruption from OOM if second fails + _buckets = new SegmentedList(defaultSegmentSize, newSize); + _fastModMultiplier = HashHelpers.GetFastModMultiplier((uint)_buckets.Capacity); + for (var i = 0; i < count; i++) + { + if (entries[i]._next >= -1) + { + ref var bucket = ref GetBucket(entries[i]._hashCode); + entries.GetElementByReference(i)._next = bucket - 1; // Value in _buckets is 1-based + bucket = i + 1; + } + } + + _entries = entries; + } + + private static bool IsCompatibleKey(object key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + return key is TKey; + } + + #endregion + + #region Constructors + + public SegmentedDictionary() + : this(0, null) + { + } + + public SegmentedDictionary(int capacity) + : this(capacity, null) + { + } + + public SegmentedDictionary(IEqualityComparer comparer) + : this(0, comparer) + { + } + + public SegmentedDictionary(int capacity, IEqualityComparer comparer) + { + if (capacity < 0) + { + throw new ArgumentException(nameof(capacity)); + } + + if (capacity > 0) + { + Initialize(capacity); + } + + if (comparer != null && comparer != EqualityComparer.Default) // first check for null to avoid forcing default comparer instantiation unnecessarily + { + _comparer = comparer; + } + else + { + _comparer = EqualityComparer.Default; + } + } + + public SegmentedDictionary(IDictionary dictionary) + : this(dictionary, null) + { + } + + public SegmentedDictionary(IDictionary dictionary, IEqualityComparer comparer) + : this(dictionary != null ? dictionary.Count : 0, comparer) + { + if (dictionary == null) + { + throw new ArgumentNullException(nameof(dictionary)); + } + + // It is likely that the passed-in dictionary is SegmentedDictionary. When this is the case, + // avoid the enumerator allocation and overhead by looping through the entries array directly. + // We only do this when dictionary is SegmentedDictionary and not a subclass, to maintain + // back-compat with subclasses that may have overridden the enumerator behavior. + if (dictionary.GetType() == typeof(SegmentedDictionary)) + { + var d = (SegmentedDictionary)dictionary; + var count = d._count; + var entries = d._entries; + for (var i = 0; i < count; i++) + { + if (entries[i]._next >= -1) + { + Add(entries[i]._key, entries[i]._value); + } + } + return; + } + + foreach (var pair in dictionary) + { + Add(pair.Key, pair.Value); + } + } + + #endregion + + #region IDictionary Implementation + + public TValue this[TKey key] + { + get + { + if (FindEntry(key, out Entry entry)) + { + return entry._value; + } + + ThrowHelper.ThrowKeyNotFoundException(key); + return default; + } + set + { + var modified = TryInsert(key, value, InsertionBehavior.OverwriteExisting); + Debug.Assert(modified); + } + } + + ICollection IDictionary.Keys => Keys; + + ICollection IDictionary.Values => Values; + + public void Add(TKey key, TValue value) + { + var modified = TryInsert(key, value, InsertionBehavior.ThrowOnExisting); + Debug.Assert(modified); // If there was an existing key and the Add failed, an exception will already have been thrown. + } + + public bool ContainsKey(TKey key) + { + return FindEntry(key, out Entry entry); + } + + public bool Remove(TKey key) + { + return Remove(key, out TValue _); + } + + public bool TryGetValue(TKey key, out TValue value) + { + bool entryFound = FindEntry(key, out Entry entry); + if (entryFound) + { + value = entry._value; + return true; + } + + value = default; + return false; + } + + #endregion + + #region ICollection> Implementation + + public int Count => _count - _freeCount; + + public bool IsReadOnly => false; + + public void Add(KeyValuePair item) => + Add(item.Key, item.Value); + + public void Clear() + { + var count = _count; + if (count > 0) + { + Debug.Assert(_buckets.Capacity > 0, "_buckets should be non-empty"); + Debug.Assert(_entries.Capacity > 0, "_entries should be non-empty"); + + _buckets.Clear(); + + _count = 0; + _freeList = -1; + _freeCount = 0; + _entries.Clear(); + } + } + + public bool Contains(KeyValuePair item) + { + bool valueFound = FindEntry(item.Key, out Entry entry); + if (valueFound && EqualityComparer.Default.Equals(entry._value, item.Value)) + { + return true; + } + + return false; + } + + public void CopyTo(KeyValuePair[] array, int index) + { + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + if ((uint)index > (uint)array.Length) + { + ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException(); + } + + if (array.Length - index < Count) + { + throw new ArgumentException(ThrowHelper.CommonStrings.Arg_ArrayPlusOffTooSmall); + } + + var count = _count; + var entries = _entries; + for (var i = 0; i < count; i++) + { + if (entries[i]._next >= -1) + { + array[index++] = new KeyValuePair(entries[i]._key, entries[i]._value); + } + } + } + + public bool Remove(KeyValuePair item) + { + if (FindEntry(item.Key, out Entry entry) && EqualityComparer.Default.Equals(item.Value, entry._value)) + { + return Remove(item.Key, out TValue _); + } + + return false; + } + + #endregion + + #region IEnumerable> Implementation + + public IEnumerator> GetEnumerator() => + new Enumerator(this, Enumerator.KeyValuePair); + + #endregion + + #region IEnumerable Implementation + + IEnumerator IEnumerable.GetEnumerator() => + new Enumerator(this, Enumerator.KeyValuePair); + + #endregion + + #region IDictionary Implementation + + public object this[object key] + { + get + { + if (IsCompatibleKey(key)) + { + if (FindEntry((TKey)key, out Entry entry)) + { + return entry._value; + } + } + + return null; + } + set + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + ThrowHelper.IfNullAndNullsAreIllegalThenThrow(value, nameof(value)); + + try + { + var tempKey = (TKey)key; + try + { + this[tempKey] = (TValue)value; + } + catch (InvalidCastException) + { + ThrowHelper.ThrowWrongTypeArgumentException(value, typeof(TValue)); + } + } + catch (InvalidCastException) + { + ThrowHelper.ThrowWrongTypeArgumentException(key, typeof(TKey)); + } + } + } + + ICollection IDictionary.Keys => Keys; + + ICollection IDictionary.Values => Values; + + public bool IsFixedSize => false; + + public void Add(object key, object value) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + ThrowHelper.IfNullAndNullsAreIllegalThenThrow(value, nameof(value)); + + try + { + var tempKey = (TKey)key; + + try + { + Add(tempKey, (TValue)value); + } + catch (InvalidCastException) + { + ThrowHelper.ThrowWrongTypeArgumentException(value, typeof(TValue)); + } + } + catch (InvalidCastException) + { + ThrowHelper.ThrowWrongTypeArgumentException(key, typeof(TKey)); + } + } + + public bool Contains(object key) + { + if (IsCompatibleKey(key)) + { + return ContainsKey((TKey)key); + } + + return false; + } + + IDictionaryEnumerator IDictionary.GetEnumerator() => + new Enumerator(this, Enumerator.DictEntry); + + public void Remove(object key) + { + if (IsCompatibleKey(key)) + { + Remove((TKey)key); + } + } + + #endregion + + #region ICollection Implementation + + public object SyncRoot => this; + + public bool IsSynchronized => false; + + public void CopyTo(Array array, int index) + { + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + if (array.Rank != 1) + { + throw new ArgumentException(ThrowHelper.CommonStrings.Arg_RankMultiDimNotSupported); + } + + if (array.GetLowerBound(0) != 0) + { + throw new ArgumentException(ThrowHelper.CommonStrings.Arg_NonZeroLowerBound); + } + + if ((uint)index > (uint)array.Length) + { + ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException(); + } + + if (array.Length - index < Count) + { + throw new ArgumentException(ThrowHelper.CommonStrings.Arg_ArrayPlusOffTooSmall); + } + + if (array is KeyValuePair[] pairs) + { + CopyTo(pairs, index); + } + else if (array is DictionaryEntry[] dictEntryArray) + { + var entries = _entries; + for (var i = 0; i < _count; i++) + { + if (entries[i]._next >= -1) + { + dictEntryArray[index++] = new DictionaryEntry(entries[i]._key, entries[i]._value); + } + } + } + else + { + var objects = array as object[]; + if (objects == null) + { + throw new ArgumentException(ThrowHelper.CommonStrings.Argument_InvalidArrayType); + } + + try + { + var count = _count; + var entries = _entries; + for (var i = 0; i < count; i++) + { + if (entries[i]._next >= -1) + { + objects[index++] = new KeyValuePair(entries[i]._key, entries[i]._value); + } + } + } + catch (ArrayTypeMismatchException) + { + throw new ArgumentException(ThrowHelper.CommonStrings.Argument_InvalidArrayType); + } + } + } + + #endregion + + #region Public Properties + + public IEqualityComparer Comparer + { + get + { + return _comparer ?? EqualityComparer.Default; + } + } + + public KeyCollection Keys + { + get + { + if (_keys == null) + { + _keys = new KeyCollection(this); + } + + return _keys; + } + } + + public ValueCollection Values + { + get + { + if (_values == null) + { + _values = new ValueCollection(this); + } + + return _values; + } + } + + #endregion + + #region Public Methods + + public bool TryAdd(TKey key, TValue value) => + TryInsert(key, value, InsertionBehavior.None); + + public bool Remove(TKey key, out TValue value) + { + // If perfomarnce becomes an issue, you can copy this implementation over to the other Remove method overloads. + + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (_buckets.Capacity > 0) + { + Debug.Assert(_entries.Capacity > 0, "entries should be non-empty"); + uint collisionCount = 0; + var hashCode = (uint)(_comparer?.GetHashCode(key) ?? key.GetHashCode()); + ref var bucket = ref GetBucket(hashCode); + var entries = _entries; + var last = -1; + var i = bucket - 1; // Value in buckets is 1-based + while (i >= 0) + { + ref var entry = ref entries.GetElementByReference(i); + + if (entry._hashCode == hashCode && (_comparer?.Equals(entry._key, key) ?? EqualityComparer.Default.Equals(entry._key, key))) + { + if (last < 0) + { + bucket = entry._next + 1; // Value in buckets is 1-based + } + else + { + entries.GetElementByReference(last)._next = entry._next; + } + + value = entry._value; + + Debug.Assert((StartOfFreeList - _freeList) < 0, "shouldn't underflow because max hashtable length is MaxPrimeArrayLength = 0x7FEFFFFD(2146435069) _freelist underflow threshold 2147483646"); + entry._next = StartOfFreeList - _freeList; + + entry._key = default; + entry._value = default; + + _freeList = i; + _freeCount++; + return true; + } + + last = i; + i = entry._next; + + collisionCount++; + if (collisionCount > (uint)entries.Capacity) + { + // The chain of entries forms a loop; which means a concurrent update has happened. + // Break out of the loop and throw, rather than looping forever. + throw new InvalidOperationException(ThrowHelper.CommonStrings.InvalidOperation_ConcurrentOperationsNotSupported); + } + } + } + + value = default; + return false; + } + + public int EnsureCapacity(int capacity) + { + if (capacity < 0) + { + throw new ArgumentOutOfRangeException(nameof(capacity)); + } + + var currentCapacity = _entries.Capacity; + if (currentCapacity >= capacity) + { + return currentCapacity; + } + + _version++; + + if (_buckets.Capacity == 0) + { + return Initialize(capacity); + } + + var newSize = HashHelpers.GetPrime(capacity); + Resize(newSize); + return newSize; + } + + public bool ContainsValue(TValue value) + { + var entries = _entries; + if (value == null) + { + for (var i = 0; i < _count; i++) + { + if (entries[i]._next >= -1 && entries[i]._value == null) + { + return true; + } + } + } + else + { + // Object type: Shared Generic, EqualityComparer.Default won't devirtualize + // https://github.com/dotnet/runtime/issues/10050 + // So cache in a local rather than get EqualityComparer per loop iteration + var defaultComparer = EqualityComparer.Default; + for (var i = 0; i < _count; i++) + { + if (entries[i]._next >= -1 && defaultComparer.Equals(entries[i]._value, value)) + { + return true; + } + } + } + + return false; + } + + #endregion + + public struct Enumerator : IEnumerator>, IDictionaryEnumerator + { + private readonly SegmentedDictionary _dictionary; + private readonly int _version; + private int _index; + private KeyValuePair _current; + private readonly int _getEnumeratorRetType; // What should Enumerator.Current return? + + internal const int DictEntry = 1; + internal const int KeyValuePair = 2; + + internal Enumerator(SegmentedDictionary dictionary, int getEnumeratorRetType) + { + _dictionary = dictionary; + _version = dictionary._version; + _index = 0; + _getEnumeratorRetType = getEnumeratorRetType; + _current = default; + } + + public bool MoveNext() + { + if (_version != _dictionary._version) + { + throw new InvalidOperationException(ThrowHelper.CommonStrings.InvalidOperation_EnumFailedVersion); + } + + // Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends. + // dictionary.count+1 could be negative if dictionary.count is int.MaxValue + while ((uint)_index < (uint)_dictionary._count) + { + ref var entry = ref _dictionary._entries.GetElementByReference(_index++); + + if (entry._next >= -1) + { + _current = new KeyValuePair(entry._key, entry._value); + return true; + } + } + + _index = _dictionary._count + 1; + _current = default; + return false; + } + + public KeyValuePair Current => _current; + + public void Dispose() + { + } + + object IEnumerator.Current + { + get + { + if (_index == 0 || (_index == _dictionary._count + 1)) + { + throw new InvalidOperationException(ThrowHelper.CommonStrings.InvalidOperation_EnumOpCantHappen); + } + + if (_getEnumeratorRetType == DictEntry) + { + return new DictionaryEntry(_current.Key, _current.Value); + } + + return new KeyValuePair(_current.Key, _current.Value); + } + } + + void IEnumerator.Reset() + { + if (_version != _dictionary._version) + { + throw new InvalidOperationException(ThrowHelper.CommonStrings.InvalidOperation_EnumFailedVersion); + } + + _index = 0; + _current = default; + } + + DictionaryEntry IDictionaryEnumerator.Entry + { + get + { + if (_index == 0 || (_index == _dictionary._count + 1)) + { + throw new InvalidOperationException(ThrowHelper.CommonStrings.InvalidOperation_EnumOpCantHappen); + } + + return new DictionaryEntry(_current.Key, _current.Value); + } + } + + object IDictionaryEnumerator.Key + { + get + { + if (_index == 0 || (_index == _dictionary._count + 1)) + { + throw new InvalidOperationException(ThrowHelper.CommonStrings.InvalidOperation_EnumOpCantHappen); + } + + return _current.Key; + } + } + + object IDictionaryEnumerator.Value + { + get + { + if (_index == 0 || (_index == _dictionary._count + 1)) + { + throw new InvalidOperationException(ThrowHelper.CommonStrings.InvalidOperation_EnumOpCantHappen); + } + + return _current.Value; + } + } + } + + public sealed class KeyCollection : ICollection, ICollection, IReadOnlyCollection + { + private readonly SegmentedDictionary _dictionary; + + public KeyCollection(SegmentedDictionary dictionary) + { + if (dictionary == null) + { + throw new ArgumentNullException(nameof(dictionary)); + } + + _dictionary = dictionary; + } + + public Enumerator GetEnumerator() + => new Enumerator(_dictionary); + + public void CopyTo(TKey[] array, int index) + { + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + if (index < 0 || index > array.Length) + { + ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException(); + } + + if (array.Length - index < _dictionary.Count) + { + throw new ArgumentException(ThrowHelper.CommonStrings.Arg_ArrayPlusOffTooSmall); + } + + var count = _dictionary._count; + var entries = _dictionary._entries; + for (var i = 0; i < count; i++) + { + if (entries[i]._next >= -1) + array[index++] = entries[i]._key; + } + } + + public int Count => _dictionary.Count; + + bool ICollection.IsReadOnly => true; + + void ICollection.Add(TKey item) + => throw new NotSupportedException(); + + void ICollection.Clear() + => throw new NotSupportedException(); + + public bool Contains(TKey item) + => _dictionary.ContainsKey(item); + + bool ICollection.Remove(TKey item) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + => new Enumerator(_dictionary); + + IEnumerator IEnumerable.GetEnumerator() + => new Enumerator(_dictionary); + + void ICollection.CopyTo(Array array, int index) + { + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + if (array.Rank != 1) + { + throw new ArgumentException(ThrowHelper.CommonStrings.Arg_RankMultiDimNotSupported); + } + + if (array.GetLowerBound(0) != 0) + { + throw new ArgumentException(ThrowHelper.CommonStrings.Arg_NonZeroLowerBound); + } + + if ((uint)index > (uint)array.Length) + { + ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException(); + } + + if (array.Length - index < _dictionary.Count) + { + throw new ArgumentException(ThrowHelper.CommonStrings.Arg_ArrayPlusOffTooSmall); + } + + if (array is TKey[] keys) + { + CopyTo(keys, index); + } + else + { + var objects = array as object[]; + if (objects == null) + { + throw new ArgumentException(ThrowHelper.CommonStrings.Argument_InvalidArrayType); + } + + var count = _dictionary._count; + var entries = _dictionary._entries; + try + { + for (var i = 0; i < count; i++) + { + if (entries[i]._next >= -1) + objects[index++] = entries[i]._key; + } + } + catch (ArrayTypeMismatchException) + { + throw new ArgumentException(ThrowHelper.CommonStrings.Argument_InvalidArrayType); + } + } + } + + bool ICollection.IsSynchronized => false; + + object ICollection.SyncRoot => ((ICollection)_dictionary).SyncRoot; + + public struct Enumerator : IEnumerator, IEnumerator + { + private readonly SegmentedDictionary _dictionary; + private int _index; + private readonly int _version; + private TKey _currentKey; + + internal Enumerator(SegmentedDictionary dictionary) + { + _dictionary = dictionary; + _version = dictionary._version; + _index = 0; + _currentKey = default; + } + + public void Dispose() + { + } + + public bool MoveNext() + { + if (_version != _dictionary._version) + { + throw new InvalidOperationException(ThrowHelper.CommonStrings.InvalidOperation_EnumFailedVersion); + } + + while ((uint)_index < (uint)_dictionary._count) + { + ref var entry = ref _dictionary._entries.GetElementByReference(_index++); + + if (entry._next >= -1) + { + _currentKey = entry._key; + return true; + } + } + + _index = _dictionary._count + 1; + _currentKey = default; + return false; + } + + public TKey Current => _currentKey; + + object IEnumerator.Current + { + get + { + if (_index == 0 || (_index == _dictionary._count + 1)) + { + throw new InvalidOperationException(ThrowHelper.CommonStrings.InvalidOperation_EnumOpCantHappen); + } + + return _currentKey; + } + } + + void IEnumerator.Reset() + { + if (_version != _dictionary._version) + { + throw new InvalidOperationException(ThrowHelper.CommonStrings.InvalidOperation_EnumFailedVersion); + } + + _index = 0; + _currentKey = default; + } + } + } + + public sealed class ValueCollection : ICollection, ICollection, IReadOnlyCollection + { + private readonly SegmentedDictionary _dictionary; + + public ValueCollection(SegmentedDictionary dictionary) + { + if (dictionary == null) + { + throw new ArgumentNullException(nameof(dictionary)); + } + + _dictionary = dictionary; + } + + public Enumerator GetEnumerator() + => new Enumerator(_dictionary); + + public void CopyTo(TValue[] array, int index) + { + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + if ((uint)index > array.Length) + { + ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException(); + } + + if (array.Length - index < _dictionary.Count) + { + throw new ArgumentException(ThrowHelper.CommonStrings.Arg_ArrayPlusOffTooSmall); + } + + var count = _dictionary._count; + var entries = _dictionary._entries; + for (var i = 0; i < count; i++) + { + if (entries[i]._next >= -1) + array[index++] = entries[i]._value; + } + } + + public int Count => _dictionary.Count; + + bool ICollection.IsReadOnly => true; + + void ICollection.Add(TValue item) + => throw new NotSupportedException(); + + bool ICollection.Remove(TValue item) + => throw new NotSupportedException(); + + void ICollection.Clear() + => throw new NotSupportedException(); + + public bool Contains(TValue item) + => _dictionary.ContainsValue(item); + + IEnumerator IEnumerable.GetEnumerator() + => new Enumerator(_dictionary); + + IEnumerator IEnumerable.GetEnumerator() + => new Enumerator(_dictionary); + + void ICollection.CopyTo(Array array, int index) + { + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + if (array.Rank != 1) + { + throw new ArgumentException(ThrowHelper.CommonStrings.Arg_RankMultiDimNotSupported); + } + + if (array.GetLowerBound(0) != 0) + { + throw new ArgumentException(ThrowHelper.CommonStrings.Arg_NonZeroLowerBound); + } + + if ((uint)index > (uint)array.Length) + { + ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException(); + } + + if (array.Length - index < _dictionary.Count) + { + throw new ArgumentException(ThrowHelper.CommonStrings.Arg_ArrayPlusOffTooSmall); + } + + if (array is TValue[] values) + { + CopyTo(values, index); + } + else + { + var objects = array as object[]; + if (objects == null) + { + throw new ArgumentException(ThrowHelper.CommonStrings.Argument_InvalidArrayType); + } + + var count = _dictionary._count; + var entries = _dictionary._entries; + try + { + for (var i = 0; i < count; i++) + { + if (entries[i]._next >= -1) + objects[index++] = entries[i]._value; + } + } + catch (ArrayTypeMismatchException) + { + throw new ArgumentException(ThrowHelper.CommonStrings.Argument_InvalidArrayType); + } + } + } + + bool ICollection.IsSynchronized => false; + + object ICollection.SyncRoot => ((ICollection)_dictionary).SyncRoot; + + public struct Enumerator : IEnumerator, IEnumerator + { + private readonly SegmentedDictionary _dictionary; + private int _index; + private readonly int _version; + private TValue _currentValue; + + internal Enumerator(SegmentedDictionary dictionary) + { + _dictionary = dictionary; + _version = dictionary._version; + _index = 0; + _currentValue = default; + } + + public void Dispose() + { + } + + public bool MoveNext() + { + if (_version != _dictionary._version) + { + throw new InvalidOperationException(ThrowHelper.CommonStrings.InvalidOperation_EnumFailedVersion); + } + + while ((uint)_index < (uint)_dictionary._count) + { + ref var entry = ref _dictionary._entries.GetElementByReference(_index++); + + if (entry._next >= -1) + { + _currentValue = entry._value; + return true; + } + } + _index = _dictionary._count + 1; + _currentValue = default; + return false; + } + + public TValue Current => _currentValue; + + object IEnumerator.Current + { + get + { + if (_index == 0 || (_index == _dictionary._count + 1)) + { + throw new InvalidOperationException(ThrowHelper.CommonStrings.InvalidOperation_EnumOpCantHappen); + } + + return _currentValue; + } + } + + void IEnumerator.Reset() + { + if (_version != _dictionary._version) + { + throw new InvalidOperationException(ThrowHelper.CommonStrings.InvalidOperation_EnumFailedVersion); + } + + _index = 0; + _currentValue = default; + } + } + } + } +} diff --git a/src/FastSerialization/SegmentedDictionary/ThrowHelper.cs b/src/FastSerialization/SegmentedDictionary/ThrowHelper.cs new file mode 100644 index 000000000..72594c0c4 --- /dev/null +++ b/src/FastSerialization/SegmentedDictionary/ThrowHelper.cs @@ -0,0 +1,63 @@ +// Tests copied from dotnet/roslyn repo. Original source code can be found here: +// https://github.com/dotnet/roslyn/blob/main/src/Dependencies/Collections/Internal/ThrowHelper.cs + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Microsoft.Diagnostics.FastSerialization +{ + /// + /// Utility class for exception throwing for the SegmentedDictionary. + /// + internal static class ThrowHelper + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void IfNullAndNullsAreIllegalThenThrow(object value, string argName) + { + // Note that default(T) is not equal to null for value types except when T is Nullable. + if (!(default(T) == null) && value == null) + throw new ArgumentNullException(argName); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void ThrowKeyNotFoundException(T key) + { + throw new KeyNotFoundException($"The given key '{key}' was not present in the dictionary."); + } + + internal static void ThrowIndexArgumentOutOfRange_NeedNonNegNumException() + { + throw GetArgumentOutOfRangeException("index", + CommonStrings.ArgumentOutOfRange_NeedNonNegNum); + } + + internal static void ThrowWrongTypeArgumentException(T value, Type targetType) + { + throw GetWrongTypeArgumentException(value, targetType); + } + + private static ArgumentOutOfRangeException GetArgumentOutOfRangeException(string argument, string message) + { + return new ArgumentOutOfRangeException(argument, message); + } + + private static ArgumentException GetWrongTypeArgumentException(object value, Type targetType) + { + return new ArgumentException($"The value '{value}' is not of type '{targetType}' and cannot be used in this generic collection.", + nameof(value)); + } + + internal static class CommonStrings + { + public static readonly string Arg_ArrayPlusOffTooSmall = "Destination array is not long enough to copy all the items in the collection. Check array index and length."; + public static readonly string ArgumentOutOfRange_NeedNonNegNum = "Non-negative number required."; + public static readonly string Arg_RankMultiDimNotSupported = "Only single dimensional arrays are supported for the requested action."; + public static readonly string Arg_NonZeroLowerBound = "The lower bound of target array must be zero."; + public static readonly string Argument_InvalidArrayType = "Target array type is not compatible with the type of items in the collection."; + public static readonly string InvalidOperation_ConcurrentOperationsNotSupported = "Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection's state is no longer correct."; + public static readonly string InvalidOperation_EnumFailedVersion = "Collection was modified; enumeration operation may not execute."; + public static readonly string InvalidOperation_EnumOpCantHappen = "Enumeration has either not started or has already finished."; + } + } +} diff --git a/src/FastSerialization/SegmentedList.cs b/src/FastSerialization/SegmentedList.cs index f78b80c51..cb422e0fb 100644 --- a/src/FastSerialization/SegmentedList.cs +++ b/src/FastSerialization/SegmentedList.cs @@ -93,6 +93,8 @@ public int Count } } + public int Capacity => this.capacity; + /// /// Copy to Array /// @@ -149,6 +151,9 @@ public T this[int index] } } + public ref T GetElementByReference(int index) => + ref this.items[index >> this.segmentShift][index & this.offsetMask]; + /// /// Necessary if the list is being used as an array since it creates the segments lazily. /// diff --git a/src/MemoryGraph/MemoryGraph.cs b/src/MemoryGraph/MemoryGraph.cs index 7e5ee2e95..447ec1ec7 100644 --- a/src/MemoryGraph/MemoryGraph.cs +++ b/src/MemoryGraph/MemoryGraph.cs @@ -10,7 +10,17 @@ public class MemoryGraph : Graph, IFastSerializable public MemoryGraph(int expectedSize) : base(expectedSize) { - m_addressToNodeIndex = new Dictionary(expectedSize); + // If we have too many addresses we will reach the Dictionary's internal array's size limit and throw. + // Therefore use a new implementation of it that is similar in performance but that can handle the extra load. + if (expectedSize > 200_000) + { + m_addressToNodeIndex = new SegmentedDictionary(expectedSize); + } + else + { + m_addressToNodeIndex = new Dictionary(expectedSize); + } + m_nodeAddresses = new SegmentedList
(SegmentSize, expectedSize); } @@ -111,7 +121,7 @@ public bool IsInGraph(Address objectAddress) /// THis table maps the ID that CLRProfiler uses (an address), to the NodeIndex we have assigned to it. /// It is only needed while the file is being read in. /// - protected Dictionary m_addressToNodeIndex; // This field is only used during construction + protected IDictionary m_addressToNodeIndex; // This field is only used during construction #endregion #region private diff --git a/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/AssertExtensions.cs b/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/AssertExtensions.cs new file mode 100644 index 000000000..59bbc2404 --- /dev/null +++ b/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/AssertExtensions.cs @@ -0,0 +1,21 @@ +// Tests copied from dotnet/runtime repo. Original source code can be found here: +// https://github.com/dotnet/runtime/blob/main/src/libraries/Common/tests/TestUtilities/System/AssertExtensions.cs + +using System; +using Xunit; + +namespace PerfView.Collections.Tests +{ + internal static class AssertExtensions + { + public static T Throws(string expectedParamName, Action action) + where T : ArgumentException + { + T exception = Assert.Throws(action); + + Assert.Equal(expectedParamName, exception.ParamName); + + return exception; + } + } +} diff --git a/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/CollectionAsserts.cs b/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/CollectionAsserts.cs new file mode 100644 index 000000000..1ad7a1555 --- /dev/null +++ b/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/CollectionAsserts.cs @@ -0,0 +1,31 @@ +// Tests copied from dotnet/runtime repo. Original source code can be found here: +// https://github.com/dotnet/runtime/blob/main/src/libraries/Common/tests/System/Collections/CollectionAsserts.cs + +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace PerfView.Collections.Tests +{ + internal static class CollectionAsserts + { + public static void EqualUnordered(ICollection expected, ICollection actual) + { + Assert.Equal(expected == null, actual == null); + if (expected == null) + { + return; + } + + // Lookups are an aggregated collections (enumerable contents), but ordered. + ILookup e = expected.Cast().ToLookup(key => key); + ILookup a = actual.Cast().ToLookup(key => key); + + // Dictionaries can't handle null keys, which is a possibility + Assert.Equal(e.Where(kv => kv.Key != null).ToDictionary(g => g.Key, g => g.Count()), a.Where(kv => kv.Key != null).ToDictionary(g => g.Key, g => g.Count())); + + // Get count of null keys. Returns an empty sequence (and thus a 0 count) if no null key + Assert.Equal(e[null].Count(), a[null].Count()); + } + } +} \ No newline at end of file diff --git a/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/DictionaryExtensions.cs b/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/DictionaryExtensions.cs new file mode 100644 index 000000000..db8759dc6 --- /dev/null +++ b/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/DictionaryExtensions.cs @@ -0,0 +1,21 @@ +// Tests copied from dotnet/runtime repo. Original source code can be found here: +// https://github.com/dotnet/runtime/blob/main/src/libraries/Common/tests/System/Collections/DictionaryExtensions.cs + +using System.Collections.Generic; + +namespace PerfView.Collections.Tests +{ + internal static class DictionaryExtensions + { + public static bool TryAdd(this IDictionary dict, TKey key, TValue value) + { + if (!dict.ContainsKey(key)) + { + dict.Add(key, value); + return true; + } + + return false; + } + } +} diff --git a/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/ICollection.Generic.Tests.cs b/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/ICollection.Generic.Tests.cs new file mode 100644 index 000000000..2161d92cc --- /dev/null +++ b/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/ICollection.Generic.Tests.cs @@ -0,0 +1,646 @@ +// Tests copied from dotnet/runtime repo. Original source code can be found here: +// https://github.com/dotnet/runtime/blob/main/src/libraries/Common/tests/System/Collections/ICollection.Generic.Tests.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace PerfView.Collections.Tests +{ + /// + /// Contains tests that ensure the correctness of any class that implements the generic + /// ICollection interface + /// + public abstract class ICollection_Generic_Tests : IEnumerable_Generic_Tests + { + #region ICollection Helper Methods + + /// + /// Creates an instance of an ICollection{T} that can be used for testing. + /// + /// An instance of an ICollection{T} that can be used for testing. + protected abstract ICollection GenericICollectionFactory(); + + /// + /// Creates an instance of an ICollection{T} that can be used for testing. + /// + /// The number of unique items that the returned ICollection{T} contains. + /// An instance of an ICollection{T} that can be used for testing. + protected virtual ICollection GenericICollectionFactory(int count) + { + ICollection collection = GenericICollectionFactory(); + AddToCollection(collection, count); + return collection; + } + + protected virtual bool DuplicateValuesAllowed => true; + protected virtual bool DefaultValueWhenNotAllowed_Throws => true; + protected virtual bool IsReadOnly => false; + protected virtual bool IsReadOnly_ValidityValue => IsReadOnly; + protected virtual bool AddRemoveClear_ThrowsNotSupported => false; + protected virtual bool DefaultValueAllowed => true; + protected virtual IEnumerable InvalidValues => Array.Empty(); + + protected virtual void AddToCollection(ICollection collection, int numberOfItemsToAdd) + { + int seed = 9600; + IEqualityComparer comparer = GetIEqualityComparer(); + while (collection.Count < numberOfItemsToAdd) + { + T toAdd = CreateT(seed++); + while (collection.Contains(toAdd, comparer) || InvalidValues.Contains(toAdd, comparer)) + toAdd = CreateT(seed++); + collection.Add(toAdd); + } + } + + protected virtual Type ICollection_Generic_CopyTo_IndexLargerThanArrayCount_ThrowType => typeof(ArgumentException); + + #endregion + + #region IEnumerable Helper Methods + + protected override IEnumerable GenericIEnumerableFactory(int count) + { + return GenericICollectionFactory(count); + } + + /// + /// Returns a set of ModifyEnumerable delegates that modify the enumerable passed to them. + /// + protected override IEnumerable GetModifyEnumerables(ModifyOperation operations) + { + if (!AddRemoveClear_ThrowsNotSupported && (operations & ModifyOperation.Add) == ModifyOperation.Add) + { + yield return (IEnumerable enumerable) => + { + var casted = (ICollection)enumerable; + casted.Add(CreateT(2344)); + return true; + }; + } + if (!AddRemoveClear_ThrowsNotSupported && (operations & ModifyOperation.Remove) == ModifyOperation.Remove) + { + yield return (IEnumerable enumerable) => + { + var casted = (ICollection)enumerable; + if (casted.Count() > 0) + { + casted.Remove(casted.ElementAt(0)); + return true; + } + return false; + }; + } + if (!AddRemoveClear_ThrowsNotSupported && (operations & ModifyOperation.Clear) == ModifyOperation.Clear) + { + yield return (IEnumerable enumerable) => + { + var casted = (ICollection)enumerable; + if (casted.Count() > 0) + { + casted.Clear(); + return true; + } + return false; + }; + } + } + + #endregion + + #region IsReadOnly + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_IsReadOnly_Validity(int count) + { + ICollection collection = GenericICollectionFactory(count); + Assert.Equal(IsReadOnly_ValidityValue, collection.IsReadOnly); + } + + #endregion + + #region Count + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Count_Validity(int count) + { + ICollection collection = GenericICollectionFactory(count); + Assert.Equal(count, collection.Count); + } + + #endregion + + #region Add + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public virtual void ICollection_Generic_Add_DefaultValue(int count) + { + if (DefaultValueAllowed && !IsReadOnly && !AddRemoveClear_ThrowsNotSupported) + { + ICollection collection = GenericICollectionFactory(count); + collection.Add(default(T)); + Assert.Equal(count + 1, collection.Count); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Add_InvalidValueToMiddleOfCollection(int count) + { + if (!IsReadOnly && !AddRemoveClear_ThrowsNotSupported) + { + Assert.All(InvalidValues, invalidValue => + { + ICollection collection = GenericICollectionFactory(count); + collection.Add(invalidValue); + for (int i = 0; i < count; i++) + collection.Add(CreateT(i)); + Assert.Equal(count * 2, collection.Count); + }); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Add_InvalidValueToBeginningOfCollection(int count) + { + if (!IsReadOnly && !AddRemoveClear_ThrowsNotSupported) + { + Assert.All(InvalidValues, invalidValue => + { + ICollection collection = GenericICollectionFactory(0); + collection.Add(invalidValue); + for (int i = 0; i < count; i++) + collection.Add(CreateT(i)); + Assert.Equal(count, collection.Count); + }); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Add_InvalidValueToEndOfCollection(int count) + { + if (!IsReadOnly && !AddRemoveClear_ThrowsNotSupported) + { + Assert.All(InvalidValues, invalidValue => + { + ICollection collection = GenericICollectionFactory(count); + collection.Add(invalidValue); + Assert.Equal(count, collection.Count); + }); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Add_DuplicateValue(int count) + { + if (!IsReadOnly && !AddRemoveClear_ThrowsNotSupported && DuplicateValuesAllowed) + { + ICollection collection = GenericICollectionFactory(count); + T duplicateValue = CreateT(700); + collection.Add(duplicateValue); + collection.Add(duplicateValue); + Assert.Equal(count + 2, collection.Count); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Add_AfterCallingClear(int count) + { + if (!IsReadOnly && !AddRemoveClear_ThrowsNotSupported) + { + ICollection collection = GenericICollectionFactory(count); + collection.Clear(); + AddToCollection(collection, 5); + Assert.Equal(5, collection.Count); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Add_AfterRemovingAnyValue(int count) + { + if (!IsReadOnly && !AddRemoveClear_ThrowsNotSupported) + { + int seed = 840; + ICollection collection = GenericICollectionFactory(count); + List items = collection.ToList(); + T toAdd = CreateT(seed++); + while (collection.Contains(toAdd)) + toAdd = CreateT(seed++); + collection.Add(toAdd); + collection.Remove(toAdd); + + toAdd = CreateT(seed++); + while (collection.Contains(toAdd)) + toAdd = CreateT(seed++); + + collection.Add(toAdd); + items.Add(toAdd); + CollectionAsserts.EqualUnordered(items, collection); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Add_AfterRemovingAllItems(int count) + { + if (!IsReadOnly && !AddRemoveClear_ThrowsNotSupported) + { + ICollection collection = GenericICollectionFactory(count); + List itemsToRemove = collection.ToList(); + for (int i = 0; i < count; i++) + collection.Remove(collection.ElementAt(0)); + collection.Add(CreateT(254)); + Assert.Equal(1, collection.Count); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Add_ToReadOnlyCollection(int count) + { + if (IsReadOnly || AddRemoveClear_ThrowsNotSupported) + { + ICollection collection = GenericICollectionFactory(count); + Assert.Throws(() => collection.Add(CreateT(0))); + Assert.Equal(count, collection.Count); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Add_AfterRemoving(int count) + { + if (!IsReadOnly && !AddRemoveClear_ThrowsNotSupported) + { + int seed = 840; + ICollection collection = GenericICollectionFactory(count); + T toAdd = CreateT(seed++); + while (collection.Contains(toAdd)) + toAdd = CreateT(seed++); + collection.Add(toAdd); + collection.Remove(toAdd); + collection.Add(toAdd); + } + } + + #endregion + + #region Clear + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Clear(int count) + { + ICollection collection = GenericICollectionFactory(count); + if (IsReadOnly || AddRemoveClear_ThrowsNotSupported) + { + Assert.Throws(() => collection.Clear()); + Assert.Equal(count, collection.Count); + } + else + { + collection.Clear(); + Assert.Equal(0, collection.Count); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Clear_Repeatedly(int count) + { + ICollection collection = GenericICollectionFactory(count); + if (IsReadOnly || AddRemoveClear_ThrowsNotSupported) + { + Assert.Throws(() => collection.Clear()); + Assert.Throws(() => collection.Clear()); + Assert.Throws(() => collection.Clear()); + Assert.Equal(count, collection.Count); + } + else + { + collection.Clear(); + collection.Clear(); + collection.Clear(); + Assert.Equal(0, collection.Count); + } + } + + #endregion + + #region Contains + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Contains_ValidValueOnCollectionNotContainingThatValue(int count) + { + ICollection collection = GenericICollectionFactory(count); + int seed = 4315; + T item = CreateT(seed++); + while (collection.Contains(item)) + item = CreateT(seed++); + Assert.False(collection.Contains(item)); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Contains_ValidValueOnCollectionContainingThatValue(int count) + { + ICollection collection = GenericICollectionFactory(count); + foreach (T item in collection) + Assert.True(collection.Contains(item)); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Contains_DefaultValueOnCollectionNotContainingDefaultValue(int count) + { + ICollection collection = GenericICollectionFactory(count); + if (DefaultValueAllowed) + Assert.False(collection.Contains(default(T))); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public virtual void ICollection_Generic_Contains_DefaultValueOnCollectionContainingDefaultValue(int count) + { + if (DefaultValueAllowed && !IsReadOnly && !AddRemoveClear_ThrowsNotSupported) + { + ICollection collection = GenericICollectionFactory(count); + collection.Add(default(T)); + Assert.True(collection.Contains(default(T))); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Contains_ValidValueThatExistsTwiceInTheCollection(int count) + { + if (DuplicateValuesAllowed && !IsReadOnly && !AddRemoveClear_ThrowsNotSupported) + { + ICollection collection = GenericICollectionFactory(count); + T item = CreateT(12); + collection.Add(item); + collection.Add(item); + Assert.Equal(count + 2, collection.Count); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Contains_InvalidValue_ThrowsArgumentException(int count) + { + ICollection collection = GenericICollectionFactory(count); + Assert.All(InvalidValues, invalidValue => + Assert.Throws(() => collection.Contains(invalidValue)) + ); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public virtual void ICollection_Generic_Contains_DefaultValueWhenNotAllowed(int count) + { + if (!DefaultValueAllowed && !IsReadOnly) + { + ICollection collection = GenericICollectionFactory(count); + if (DefaultValueWhenNotAllowed_Throws) + AssertExtensions.Throws("item", () => collection.Contains(default(T))); + else + Assert.False(collection.Contains(default(T))); + } + } + + #endregion + + #region CopyTo + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_CopyTo_NullArray_ThrowsArgumentNullException(int count) + { + ICollection collection = GenericICollectionFactory(count); + Assert.Throws(() => collection.CopyTo(null, 0)); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_CopyTo_NegativeIndex_ThrowsArgumentOutOfRangeException(int count) + { + ICollection collection = GenericICollectionFactory(count); + T[] array = new T[count]; + Assert.Throws(() => collection.CopyTo(array, -1)); + Assert.Throws(() => collection.CopyTo(array, int.MinValue)); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_CopyTo_IndexEqualToArrayCount_ThrowsArgumentException(int count) + { + ICollection collection = GenericICollectionFactory(count); + T[] array = new T[count]; + if (count > 0) + Assert.Throws(() => collection.CopyTo(array, count)); + else + collection.CopyTo(array, count); // does nothing since the array is empty + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_CopyTo_IndexLargerThanArrayCount_ThrowsAnyArgumentException(int count) + { + ICollection collection = GenericICollectionFactory(count); + T[] array = new T[count]; + Assert.Throws(ICollection_Generic_CopyTo_IndexLargerThanArrayCount_ThrowType, () => collection.CopyTo(array, count + 1)); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_CopyTo_NotEnoughSpaceInOffsettedArray_ThrowsArgumentException(int count) + { + if (count > 0) // Want the T array to have at least 1 element + { + ICollection collection = GenericICollectionFactory(count); + T[] array = new T[count]; + Assert.Throws(() => collection.CopyTo(array, 1)); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_CopyTo_ExactlyEnoughSpaceInArray(int count) + { + ICollection collection = GenericICollectionFactory(count); + T[] array = new T[count]; + collection.CopyTo(array, 0); + Assert.True(Enumerable.SequenceEqual(collection, array)); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_CopyTo_ArrayIsLargerThanCollection(int count) + { + ICollection collection = GenericICollectionFactory(count); + T[] array = new T[count * 3 / 2]; + collection.CopyTo(array, 0); + Assert.True(Enumerable.SequenceEqual(collection, array.Take(count))); + } + + #endregion + + #region Remove + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Remove_OnReadOnlyCollection_ThrowsNotSupportedException(int count) + { + if (IsReadOnly || AddRemoveClear_ThrowsNotSupported) + { + ICollection collection = GenericICollectionFactory(count); + Assert.Throws(() => collection.Remove(CreateT(34543))); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Remove_DefaultValueNotContainedInCollection(int count) + { + if (!IsReadOnly && !AddRemoveClear_ThrowsNotSupported && DefaultValueAllowed && !Enumerable.Contains(InvalidValues, default(T))) + { + int seed = count * 21; + ICollection collection = GenericICollectionFactory(count); + T value = default(T); + while (collection.Contains(value)) + { + collection.Remove(value); + count--; + } + Assert.False(collection.Remove(value)); + Assert.Equal(count, collection.Count); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Remove_NonDefaultValueNotContainedInCollection(int count) + { + if (!IsReadOnly && !AddRemoveClear_ThrowsNotSupported) + { + int seed = count * 251; + ICollection collection = GenericICollectionFactory(count); + T value = CreateT(seed++); + while (collection.Contains(value) || Enumerable.Contains(InvalidValues, value)) + value = CreateT(seed++); + Assert.False(collection.Remove(value)); + Assert.Equal(count, collection.Count); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public virtual void ICollection_Generic_Remove_DefaultValueContainedInCollection(int count) + { + if (!IsReadOnly && !AddRemoveClear_ThrowsNotSupported && DefaultValueAllowed && !Enumerable.Contains(InvalidValues, default(T))) + { + int seed = count * 21; + ICollection collection = GenericICollectionFactory(count); + T value = default(T); + if (!collection.Contains(value)) + { + collection.Add(value); + count++; + } + Assert.True(collection.Remove(value)); + Assert.Equal(count - 1, collection.Count); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Remove_NonDefaultValueContainedInCollection(int count) + { + if (!IsReadOnly && !AddRemoveClear_ThrowsNotSupported) + { + int seed = count * 251; + ICollection collection = GenericICollectionFactory(count); + T value = CreateT(seed++); + if (!collection.Contains(value)) + { + collection.Add(value); + count++; + } + Assert.True(collection.Remove(value)); + Assert.Equal(count - 1, collection.Count); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Remove_ValueThatExistsTwiceInCollection(int count) + { + if (!IsReadOnly && !AddRemoveClear_ThrowsNotSupported && DuplicateValuesAllowed) + { + int seed = count * 90; + ICollection collection = GenericICollectionFactory(count); + T value = CreateT(seed++); + collection.Add(value); + collection.Add(value); + count += 2; + Assert.True(collection.Remove(value)); + Assert.True(collection.Contains(value)); + Assert.Equal(count - 1, collection.Count); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Remove_EveryValue(int count) + { + if (!IsReadOnly && !AddRemoveClear_ThrowsNotSupported) + { + ICollection collection = GenericICollectionFactory(count); + Assert.All(collection.ToList(), value => + { + Assert.True(collection.Remove(value)); + }); + Assert.Empty(collection); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Remove_InvalidValue_ThrowsArgumentException(int count) + { + ICollection collection = GenericICollectionFactory(count); + Assert.All(InvalidValues, value => + { + Assert.Throws(() => collection.Remove(value)); + }); + Assert.Equal(count, collection.Count); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Remove_DefaultValueWhenNotAllowed(int count) + { + if (!DefaultValueAllowed && !IsReadOnly && !AddRemoveClear_ThrowsNotSupported) + { + ICollection collection = GenericICollectionFactory(count); + if (DefaultValueWhenNotAllowed_Throws) + Assert.Throws(() => collection.Remove(default(T))); + else + Assert.False(collection.Remove(default(T))); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/IDictionary.Generic.Tests.cs b/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/IDictionary.Generic.Tests.cs new file mode 100644 index 000000000..127f5284b --- /dev/null +++ b/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/IDictionary.Generic.Tests.cs @@ -0,0 +1,970 @@ +// Tests copied from dotnet/runtime repo. Original source code can be found here: +// https://github.com/dotnet/runtime/blob/main/src/libraries/Common/tests/System/Collections/IDictionary.Generic.Tests.cs + +using System; +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace PerfView.Collections.Tests +{ + /// + /// Contains tests that ensure the correctness of any class that implements the generic + /// IDictionary interface + /// + public abstract partial class IDictionary_Generic_Tests : ICollection_Generic_Tests> + { + public sealed class EqualityComparerConstantHashCode : IEqualityComparer + { + private readonly IEqualityComparer _comparer; + + public EqualityComparerConstantHashCode(IEqualityComparer comparer) => _comparer = comparer; + + public bool Equals(T x, T y) => _comparer.Equals(x, y); + + public int GetHashCode(T obj) => 42; + } + + #region IDictionary Helper Methods + + /// + /// Creates an instance of an IDictionary{TKey, TValue} that can be used for testing. + /// + /// An instance of an IDictionary{TKey, TValue} that can be used for testing. + protected abstract IDictionary GenericIDictionaryFactory(); + + /// + /// Creates an instance of an IDictionary{TKey, TValue} that can be used for testing, with a specific comparer. + /// + /// The comparer to use with the dictionary. + /// An instance of an IDictionary{TKey, TValue} that can be used for testing, or null if the tested type doesn't support an equality comparer. + protected virtual IDictionary GenericIDictionaryFactory(IEqualityComparer comparer) => null; + + /// + /// Creates an instance of an IDictionary{TKey, TValue} that can be used for testing. + /// + /// The number of items that the returned IDictionary{TKey, TValue} contains. + /// An instance of an IDictionary{TKey, TValue} that can be used for testing. + protected virtual IDictionary GenericIDictionaryFactory(int count) + { + IDictionary collection = GenericIDictionaryFactory(); + AddToCollection(collection, count); + return collection; + } + + /// + /// To be implemented in the concrete Dictionary test classes. Creates an instance of TKey that + /// is dependent only on the seed passed as input and will return the same key on repeated + /// calls with the same seed. + /// + protected abstract TKey CreateTKey(int seed); + + /// + /// To be implemented in the concrete Dictionary test classes. Creates an instance of TValue that + /// is dependent only on the seed passed as input and will return the same value on repeated + /// calls with the same seed. + /// + protected abstract TValue CreateTValue(int seed); + + /// + /// Helper method to get a key that doesn't already exist within the dictionary given + /// + protected TKey GetNewKey(IDictionary dictionary) + { + int seed = 840; + TKey missingKey = CreateTKey(seed++); + while (dictionary.ContainsKey(missingKey) || missingKey.Equals(default(TKey))) + missingKey = CreateTKey(seed++); + return missingKey; + } + + /// + /// For a Dictionary, the key comparer is primarily important rather than the KeyValuePair. For this + /// reason, we rely only on the KeyComparer methods instead of the GetIComparer methods. + /// + public virtual IEqualityComparer GetKeyIEqualityComparer() + { + return EqualityComparer.Default; + } + + /// + /// For a Dictionary, the key comparer is primarily important rather than the KeyValuePair. For this + /// reason, we rely only on the KeyComparer methods instead of the GetIComparer methods. + /// + public virtual IComparer GetKeyIComparer() + { + return Comparer.Default; + } + + /// + /// Class to provide an indirection around a Key comparer. Allows us to use a key comparer as a KeyValuePair comparer + /// by only looking at the key of a KeyValuePair. + /// + public class KVPComparer : IEqualityComparer>, IComparer> + { + private IComparer _comparer; + private IEqualityComparer _equalityComparer; + + public KVPComparer(IComparer comparer, IEqualityComparer eq) + { + _comparer = comparer; + _equalityComparer = eq; + } + + public int Compare(KeyValuePair x, KeyValuePair y) + { + return _comparer.Compare(x.Key, y.Key); + } + + public bool Equals(KeyValuePair x, KeyValuePair y) + { + return _equalityComparer.Equals(x.Key, y.Key); + } + + public int GetHashCode(KeyValuePair obj) + { + return _equalityComparer.GetHashCode(obj.Key); + } + } + + #endregion + + #region ICollection> Helper Methods + + protected override ICollection> GenericICollectionFactory() + { + return GenericIDictionaryFactory(); + } + + protected override ICollection> GenericICollectionFactory(int count) + { + return GenericIDictionaryFactory(count); + } + + protected override bool DefaultValueAllowed => false; + + protected override bool DuplicateValuesAllowed => false; + + protected override void AddToCollection(ICollection> collection, int numberOfItemsToAdd) + { + Assert.False(IsReadOnly); + int seed = 12353; + IDictionary casted = (IDictionary)collection; + int initialCount = casted.Count; + while ((casted.Count - initialCount) < numberOfItemsToAdd) + { + KeyValuePair toAdd = CreateT(seed++); + while (casted.ContainsKey(toAdd.Key) || Enumerable.Contains(InvalidValues, toAdd)) + toAdd = CreateT(seed++); + collection.Add(toAdd); + } + } + + protected override IEqualityComparer> GetIEqualityComparer() + { + return new KVPComparer(GetKeyIComparer(), GetKeyIEqualityComparer()); + } + + protected override IComparer> GetIComparer() + { + return new KVPComparer(GetKeyIComparer(), GetKeyIEqualityComparer()); + } + + /// + /// Returns a set of ModifyEnumerable delegates that modify the enumerable passed to them. + /// + protected override IEnumerable GetModifyEnumerables(ModifyOperation operations) + { + if ((operations & ModifyOperation.Add) == ModifyOperation.Add) + { + yield return (IEnumerable> enumerable) => + { + IDictionary casted = ((IDictionary)enumerable); + casted.Add(CreateTKey(12), CreateTValue(5123)); + return true; + }; + } + if ((operations & ModifyOperation.Insert) == ModifyOperation.Insert) + { + yield return (IEnumerable> enumerable) => + { + IDictionary casted = ((IDictionary)enumerable); + casted[CreateTKey(541)] = CreateTValue(12); + return true; + }; + } + if ((operations & ModifyOperation.Overwrite) == ModifyOperation.Overwrite) + { + yield return (IEnumerable> enumerable) => + { + IDictionary casted = ((IDictionary)enumerable); + if (casted.Count() > 0) + { + var keys = casted.Keys.GetEnumerator(); + keys.MoveNext(); + casted[keys.Current] = CreateTValue(12); + return true; + } + return false; + }; + } + if ((operations & ModifyOperation.Remove) == ModifyOperation.Remove) + { + yield return (IEnumerable> enumerable) => + { + IDictionary casted = ((IDictionary)enumerable); + if (casted.Count() > 0) + { + var keys = casted.Keys.GetEnumerator(); + keys.MoveNext(); + casted.Remove(keys.Current); + return true; + } + return false; + }; + } + if ((operations & ModifyOperation.Clear) == ModifyOperation.Clear) + { + yield return (IEnumerable> enumerable) => + { + IDictionary casted = ((IDictionary)enumerable); + if (casted.Count() > 0) + { + casted.Clear(); + return true; + } + return false; + }; + } + //throw new InvalidOperationException(string.Format("{0:G}", operations)); + } + + /// + /// Used in IDictionary_Generic_Values_Enumeration_ParentDictionaryModifiedInvalidates and + /// IDictionary_Generic_Keys_Enumeration_ParentDictionaryModifiedInvalidates. + /// Some collections (e.g. ConcurrentDictionary) do not throw an InvalidOperationException + /// when enumerating the Keys or Values property and the parent is modified. + /// + protected virtual bool IDictionary_Generic_Keys_Values_Enumeration_ThrowsInvalidOperation_WhenParentModified => true; + + /// + /// Used in IDictionary_Generic_Values_ModifyingTheDictionaryUpdatesTheCollection and + /// IDictionary_Generic_Keys_ModifyingTheDictionaryUpdatesTheCollection. + /// Some collections (e.g ConcurrentDictionary) use iterators in the Keys and Values properties, + /// and do not respond to updates in the base collection. + /// + protected virtual bool IDictionary_Generic_Keys_Values_ModifyingTheDictionaryUpdatesTheCollection => true; + + /// + /// Used in IDictionary_Generic_Keys_Enumeration_Reset and IDictionary_Generic_Values_Enumeration_Reset. + /// Typically, the support for Reset in enumerators for the Keys and Values depend on the support for it + /// in the parent dictionary. However, some collections (e.g. ConcurrentDictionary) don't. + /// + protected virtual bool IDictionary_Generic_Keys_Values_Enumeration_ResetImplemented => ResetImplemented; + + #endregion + + #region Item Getter + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_ItemGet_DefaultKey(int count) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + if (!DefaultValueAllowed) + { + Assert.Throws(() => dictionary[default(TKey)]); + } + else + { + TValue value = CreateTValue(3452); + dictionary[default(TKey)] = value; + Assert.Equal(value, dictionary[default(TKey)]); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_ItemGet_MissingNonDefaultKey_ThrowsKeyNotFoundException(int count) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + TKey missingKey = GetNewKey(dictionary); + Assert.Throws(() => dictionary[missingKey]); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_ItemGet_MissingDefaultKey_ThrowsKeyNotFoundException(int count) + { + if (DefaultValueAllowed) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + TKey missingKey = default(TKey); + while (dictionary.ContainsKey(missingKey)) + dictionary.Remove(missingKey); + Assert.Throws(() => dictionary[missingKey]); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_ItemGet_PresentKeyReturnsCorrectValue(int count) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + foreach (KeyValuePair pair in dictionary) + { + Assert.Equal(pair.Value, dictionary[pair.Key]); + } + } + + #endregion + + #region Item Setter + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_ItemSet_DefaultKey(int count) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + if (!DefaultValueAllowed) + { + Assert.Throws(() => dictionary[default(TKey)] = CreateTValue(3)); + } + else + { + TValue value = CreateTValue(3452); + dictionary[default(TKey)] = value; + Assert.Equal(value, dictionary[default(TKey)]); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_ItemSet_OnReadOnlyDictionary_ThrowsNotSupportedException(int count) + { + if (IsReadOnly) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + TKey missingKey = GetNewKey(dictionary); + Assert.Throws(() => dictionary[missingKey] = CreateTValue(5312)); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_ItemSet_AddsNewValueWhenNotPresent(int count) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + TKey missingKey = GetNewKey(dictionary); + dictionary[missingKey] = CreateTValue(543); + Assert.Equal(count + 1, dictionary.Count); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_ItemSet_ReplacesExistingValueWhenPresent(int count) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + TKey existingKey = GetNewKey(dictionary); + dictionary.Add(existingKey, CreateTValue(5342)); + TValue newValue = CreateTValue(1234); + dictionary[existingKey] = newValue; + Assert.Equal(count + 1, dictionary.Count); + Assert.Equal(newValue, dictionary[existingKey]); + } + + #endregion + + #region Keys + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_Keys_ContainsAllCorrectKeys(int count) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + IEnumerable expected = dictionary.Select((pair) => pair.Key); + Assert.True(expected.SequenceEqual(dictionary.Keys)); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_Keys_ModifyingTheDictionaryUpdatesTheCollection(int count) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + ICollection keys = dictionary.Keys; + int previousCount = keys.Count; + if (count > 0) + Assert.NotEmpty(keys); + dictionary.Clear(); + if (IDictionary_Generic_Keys_Values_ModifyingTheDictionaryUpdatesTheCollection) + { + Assert.Empty(keys); + } + else + { + Assert.Equal(previousCount, keys.Count); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_Keys_Enumeration_ParentDictionaryModifiedInvalidates(int count) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + ICollection keys = dictionary.Keys; + IEnumerator keysEnum = keys.GetEnumerator(); + dictionary.Add(GetNewKey(dictionary), CreateTValue(3432)); + if (IDictionary_Generic_Keys_Values_Enumeration_ThrowsInvalidOperation_WhenParentModified) + { + Assert.Throws(() => keysEnum.MoveNext()); + Assert.Throws(() => keysEnum.Reset()); + } + else + { + keysEnum.MoveNext(); + keysEnum.Reset(); + } + var cur = keysEnum.Current; + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_Keys_IsReadOnly(int count) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + ICollection keys = dictionary.Keys; + Assert.True(keys.IsReadOnly); + Assert.Throws(() => keys.Add(CreateTKey(11))); + Assert.Throws(() => keys.Clear()); + Assert.Throws(() => keys.Remove(CreateTKey(11))); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_Keys_Enumeration_Reset(int count) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + ICollection keys = dictionary.Keys; + var enumerator = keys.GetEnumerator(); + if (IDictionary_Generic_Keys_Values_Enumeration_ResetImplemented) + enumerator.Reset(); + else + Assert.Throws(() => enumerator.Reset()); + } + + #endregion + + #region Values + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_Values_ContainsAllCorrectValues(int count) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + IEnumerable expected = dictionary.Select((pair) => pair.Value); + Assert.True(expected.SequenceEqual(dictionary.Values)); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_Values_IncludeDuplicatesMultipleTimes(int count) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + int seed = 431; + foreach (KeyValuePair pair in dictionary.ToList()) + { + TKey missingKey = CreateTKey(seed++); + while (dictionary.ContainsKey(missingKey)) + missingKey = CreateTKey(seed++); + dictionary.Add(missingKey, pair.Value); + } + Assert.Equal(count * 2, dictionary.Values.Count); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_Values_ModifyingTheDictionaryUpdatesTheCollection(int count) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + ICollection values = dictionary.Values; + int previousCount = values.Count; + if (count > 0) + Assert.NotEmpty(values); + dictionary.Clear(); + if (IDictionary_Generic_Keys_Values_ModifyingTheDictionaryUpdatesTheCollection) + { + Assert.Empty(values); + } + else + { + Assert.Equal(previousCount, values.Count); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_Values_Enumeration_ParentDictionaryModifiedInvalidates(int count) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + ICollection values = dictionary.Values; + IEnumerator valuesEnum = values.GetEnumerator(); + dictionary.Add(GetNewKey(dictionary), CreateTValue(3432)); + if (IDictionary_Generic_Keys_Values_Enumeration_ThrowsInvalidOperation_WhenParentModified) + { + Assert.Throws(() => valuesEnum.MoveNext()); + Assert.Throws(() => valuesEnum.Reset()); + } + else + { + valuesEnum.MoveNext(); + valuesEnum.Reset(); + } + var cur = valuesEnum.Current; + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_Values_IsReadOnly(int count) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + ICollection values = dictionary.Values; + Assert.True(values.IsReadOnly); + Assert.Throws(() => values.Add(CreateTValue(11))); + Assert.Throws(() => values.Clear()); + Assert.Throws(() => values.Remove(CreateTValue(11))); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_Values_Enumeration_Reset(int count) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + ICollection values = dictionary.Values; + var enumerator = values.GetEnumerator(); + if (IDictionary_Generic_Keys_Values_Enumeration_ResetImplemented) + enumerator.Reset(); + else + Assert.Throws(() => enumerator.Reset()); + } + + #endregion + + #region Add(TKey, TValue) + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_Add_OnReadOnlyDictionary_ThrowsNotSupportedException(int count) + { + if (IsReadOnly) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + Assert.Throws(() => dictionary.Add(CreateTKey(0), CreateTValue(0))); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_Add_DefaultKey_DefaultValue(int count) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + TKey missingKey = default(TKey); + TValue value = default(TValue); + if (DefaultValueAllowed && !IsReadOnly) + { + dictionary.Add(missingKey, value); + Assert.Equal(count + 1, dictionary.Count); + Assert.Equal(value, dictionary[missingKey]); + } + else if (!IsReadOnly) + { + Assert.Throws(() => dictionary.Add(missingKey, value)); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_Add_DefaultKey_NonDefaultValue(int count) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + TKey missingKey = default(TKey); + TValue value = CreateTValue(1456); + if (DefaultValueAllowed && !IsReadOnly) + { + dictionary.Add(missingKey, value); + Assert.Equal(count + 1, dictionary.Count); + Assert.Equal(value, dictionary[missingKey]); + } + else if (!IsReadOnly) + { + Assert.Throws(() => dictionary.Add(missingKey, value)); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_Add_NonDefaultKey_DefaultValue(int count) + { + if (!IsReadOnly) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + TKey missingKey = GetNewKey(dictionary); + TValue value = default(TValue); + dictionary.Add(missingKey, value); + Assert.Equal(count + 1, dictionary.Count); + Assert.Equal(value, dictionary[missingKey]); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_Add_NonDefaultKey_NonDefaultValue(int count) + { + if (!IsReadOnly) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + TKey missingKey = GetNewKey(dictionary); + TValue value = CreateTValue(1342); + dictionary.Add(missingKey, value); + Assert.Equal(count + 1, dictionary.Count); + Assert.Equal(value, dictionary[missingKey]); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_Add_DuplicateValue(int count) + { + if (!IsReadOnly) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + int seed = 321; + TValue duplicate = CreateTValue(seed++); + while (dictionary.Values.Contains(duplicate)) + duplicate = CreateTValue(seed++); + dictionary.Add(GetNewKey(dictionary), duplicate); + dictionary.Add(GetNewKey(dictionary), duplicate); + Assert.Equal(2, dictionary.Values.Count((value) => value.Equals(duplicate))); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_Add_DuplicateKey(int count) + { + if (!IsReadOnly) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + TKey missingKey = GetNewKey(dictionary); + dictionary.Add(missingKey, CreateTValue(34251)); + Assert.Throws(() => dictionary.Add(missingKey, CreateTValue(134))); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_Add_DistinctValuesWithHashCollisions(int count) + { + if (!IsReadOnly) + { + IDictionary dictionary = GenericIDictionaryFactory(new EqualityComparerConstantHashCode(EqualityComparer.Default)); + if (dictionary != null) + { + AddToCollection(dictionary, count); + Assert.Equal(count, dictionary.Count); + } + } + } + + #endregion + + #region ContainsKey + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_ContainsKey_ValidKeyNotContainedInDictionary(int count) + { + if (!IsReadOnly) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + TKey missingKey = GetNewKey(dictionary); + Assert.False(dictionary.ContainsKey(missingKey)); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_ContainsKey_ValidKeyContainedInDictionary(int count) + { + if (!IsReadOnly) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + TKey missingKey = GetNewKey(dictionary); + dictionary.Add(missingKey, CreateTValue(34251)); + Assert.True(dictionary.ContainsKey(missingKey)); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_ContainsKey_DefaultKeyNotContainedInDictionary(int count) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + if (DefaultValueAllowed) + { + // returns false + TKey missingKey = default(TKey); + while (dictionary.ContainsKey(missingKey)) + dictionary.Remove(missingKey); + Assert.False(dictionary.ContainsKey(missingKey)); + } + else + { + // throws ArgumentNullException + Assert.Throws(() => dictionary.ContainsKey(default(TKey))); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_ContainsKey_DefaultKeyContainedInDictionary(int count) + { + if (DefaultValueAllowed && !IsReadOnly) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + TKey missingKey = default(TKey); + if (!dictionary.ContainsKey(missingKey)) + dictionary.Add(missingKey, CreateTValue(5341)); + Assert.True(dictionary.ContainsKey(missingKey)); + } + } + + #endregion + + #region Remove(TKey) + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_RemoveKey_OnReadOnlyDictionary_ThrowsNotSupportedException(int count) + { + if (IsReadOnly) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + Assert.Throws(() => dictionary.Remove(CreateTKey(0))); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_RemoveKey_EveryKey(int count) + { + if (!IsReadOnly) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + + Assert.All(dictionary.ToList(), pair => + { + Assert.True(dictionary.Remove(pair.Key)); + }); + Assert.Empty(dictionary); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_RemoveKey_ValidKeyNotContainedInDictionary(int count) + { + if (!IsReadOnly) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + TKey missingKey = GetNewKey(dictionary); + Assert.False(dictionary.Remove(missingKey)); + Assert.Equal(count, dictionary.Count); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_RemoveKey_ValidKeyContainedInDictionary(int count) + { + if (!IsReadOnly) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + TKey missingKey = GetNewKey(dictionary); + dictionary.Add(missingKey, CreateTValue(34251)); + Assert.True(dictionary.Remove(missingKey)); + Assert.Equal(count, dictionary.Count); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_RemoveKey_DefaultKeyNotContainedInDictionary(int count) + { + if (!IsReadOnly) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + if (DefaultValueAllowed) + { + TKey missingKey = default(TKey); + while (dictionary.ContainsKey(missingKey)) + dictionary.Remove(missingKey); + Assert.False(dictionary.Remove(missingKey)); + } + else + { + Assert.Throws(() => dictionary.Remove(default(TKey))); + } + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_RemoveKey_DefaultKeyContainedInDictionary(int count) + { + if (DefaultValueAllowed && !IsReadOnly) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + TKey missingKey = default(TKey); + dictionary.TryAdd(missingKey, CreateTValue(5341)); + Assert.True(dictionary.Remove(missingKey)); + } + } + + #endregion + + #region TryGetValue + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_TryGetValue_ValidKeyNotContainedInDictionary(int count) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + TKey missingKey = GetNewKey(dictionary); + TValue value = CreateTValue(5123); + TValue outValue; + Assert.False(dictionary.TryGetValue(missingKey, out outValue)); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_TryGetValue_ValidKeyContainedInDictionary(int count) + { + if (!IsReadOnly) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + TKey missingKey = GetNewKey(dictionary); + TValue value = CreateTValue(5123); + TValue outValue; + dictionary.TryAdd(missingKey, value); + Assert.True(dictionary.TryGetValue(missingKey, out outValue)); + Assert.Equal(value, outValue); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_TryGetValue_DefaultKeyNotContainedInDictionary(int count) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + TValue outValue; + if (DefaultValueAllowed) + { + TKey missingKey = default(TKey); + while (dictionary.ContainsKey(missingKey)) + dictionary.Remove(missingKey); + Assert.False(dictionary.TryGetValue(missingKey, out outValue)); + } + else + { + Assert.Throws(() => dictionary.TryGetValue(default(TKey), out outValue)); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IDictionary_Generic_TryGetValue_DefaultKeyContainedInDictionary(int count) + { + if (DefaultValueAllowed && !IsReadOnly) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + TKey missingKey = default(TKey); + TValue value = CreateTValue(5123); + TValue outValue; + dictionary.TryAdd(missingKey, value); + Assert.True(dictionary.TryGetValue(missingKey, out outValue)); + Assert.Equal(value, outValue); + } + } + + #endregion + + #region ICollection> + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Contains_ValidPresentKeyWithDefaultValue(int count) + { + if (!IsReadOnly) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + TKey missingKey = GetNewKey(dictionary); + dictionary.Add(missingKey, default(TValue)); + Assert.True(dictionary.Contains(new KeyValuePair(missingKey, default(TValue)))); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Remove_ValidPresentKeyWithDifferentValue(int count) + { + if (!IsReadOnly) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + TKey missingKey = GetNewKey(dictionary); + TValue present = CreateTValue(324); + TValue missing = CreateTValue(5612); + while (present.Equals(missing)) + missing = CreateTValue(5612); + dictionary.Add(missingKey, present); + Assert.False(dictionary.Remove(new KeyValuePair(missingKey, missing))); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void ICollection_Generic_Contains_ValidPresentKeyWithDifferentValue(int count) + { + if (!IsReadOnly) + { + IDictionary dictionary = GenericIDictionaryFactory(count); + TKey missingKey = GetNewKey(dictionary); + TValue present = CreateTValue(324); + TValue missing = CreateTValue(5612); + while (present.Equals(missing)) + missing = CreateTValue(5612); + dictionary.Add(missingKey, present); + Assert.False(dictionary.Contains(new KeyValuePair(missingKey, missing))); + } + } + + #endregion + + #region ICollection + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public override void ICollection_Generic_Contains_DefaultValueWhenNotAllowed(int count) + { + ICollection> collection = GenericIDictionaryFactory(count); + if (!DefaultValueAllowed && !IsReadOnly) + { + if (DefaultValueWhenNotAllowed_Throws) + Assert.Throws(() => collection.Contains(default(KeyValuePair))); + else + Assert.False(collection.Remove(default(KeyValuePair))); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/IEnumerable.Generic.Tests.cs b/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/IEnumerable.Generic.Tests.cs new file mode 100644 index 000000000..f08e7981e --- /dev/null +++ b/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/IEnumerable.Generic.Tests.cs @@ -0,0 +1,894 @@ +// Tests copied from dotnet/runtime repo. Original source code can be found here: +// https://github.com/dotnet/runtime/blob/main/src/libraries/Common/tests/System/Collections/IEnumerable.Generic.Tests.cs + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace PerfView.Collections.Tests +{ + /// + /// Contains tests that ensure the correctness of any class that implements the generic + /// IEnumerable interface. + /// + public abstract partial class IEnumerable_Generic_Tests : TestBase + { + #region IEnumerable Helper Methods + + /// + /// Creates an instance of an IEnumerable{T} that can be used for testing. + /// + /// The number of unique items that the returned IEnumerable{T} contains. + /// An instance of an IEnumerable{T} that can be used for testing. + protected abstract IEnumerable GenericIEnumerableFactory(int count); + + /// + /// Modifies the given IEnumerable such that any enumerators for that IEnumerable will be + /// invalidated. + /// + /// An IEnumerable to modify + /// true if the enumerable was successfully modified. Else false. + protected delegate bool ModifyEnumerable(IEnumerable enumerable); + + /// + /// To be implemented in the concrete collections test classes. Returns a set of ModifyEnumerable delegates + /// that modify the enumerable passed to them. + /// + protected abstract IEnumerable GetModifyEnumerables(ModifyOperation operations); + + protected virtual ModifyOperation ModifyEnumeratorThrows => ModifyOperation.Add | ModifyOperation.Insert | ModifyOperation.Overwrite | ModifyOperation.Remove | ModifyOperation.Clear; + + protected virtual ModifyOperation ModifyEnumeratorAllowed => ModifyOperation.None; + + /// + /// The Reset method is provided for COM interoperability. It does not necessarily need to be + /// implemented; instead, the implementer can simply throw a NotSupportedException. + /// + /// If Reset is not implemented, this property must return False. The default value is true. + /// + protected virtual bool ResetImplemented => true; + + /// + /// When calling Current of the enumerator before the first MoveNext, after the end of the collection, + /// or after modification of the enumeration, the resulting behavior is undefined. Tests are included + /// to cover two behavioral scenarios: + /// - Throwing an InvalidOperationException + /// - Returning an undefined value. + /// + /// If this property is set to true, the tests ensure that the exception is thrown. The default value is + /// false. + /// + protected virtual bool Enumerator_Current_UndefinedOperation_Throws => false; + + /// + /// When calling MoveNext or Reset after modification of the enumeration, the resulting behavior is + /// undefined. Tests are included to cover two behavioral scenarios: + /// - Throwing an InvalidOperationException + /// - Execute MoveNext or Reset. + /// + /// If this property is set to true, the tests ensure that the exception is thrown. The default value is + /// true. + /// + protected virtual bool Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException => true; + + /// + /// Specifies whether this IEnumerable follows some sort of ordering pattern. + /// + protected virtual EnumerableOrder Order => EnumerableOrder.Sequential; + + /// + /// An enum to allow specification of the order of the Enumerable. Used in validation for enumerables. + /// + protected enum EnumerableOrder + { + Unspecified, + Sequential + } + + #endregion + + #region Validation + + private void RepeatTest( + Action, T[], int> testCode, + int iters = 3) + { + IEnumerable enumerable = GenericIEnumerableFactory(32); + T[] items = enumerable.ToArray(); + IEnumerator enumerator = enumerable.GetEnumerator(); + for (var i = 0; i < iters; i++) + { + testCode(enumerator, items, i); + if (!ResetImplemented) + { + enumerator = enumerable.GetEnumerator(); + } + else + { + enumerator.Reset(); + } + } + } + + private void RepeatTest( + Action, T[]> testCode, + int iters = 3) + { + RepeatTest((e, i, it) => testCode(e, i), iters); + } + + private void VerifyModifiedEnumerator( + IEnumerator enumerator, + object expectedCurrent, + bool expectCurrentThrow, + bool atEnd) + { + if (expectCurrentThrow) + { + Assert.Throws( + () => enumerator.Current); + } + else + { + object current = enumerator.Current; + for (var i = 0; i < 3; i++) + { + Assert.Equal(expectedCurrent, current); + current = enumerator.Current; + } + } + + Assert.Throws( + () => enumerator.MoveNext()); + + if (!!ResetImplemented) + { + Assert.Throws( + () => enumerator.Reset()); + } + } + + private void VerifyEnumerator( + IEnumerator enumerator, + T[] expectedItems) + { + VerifyEnumerator( + enumerator, + expectedItems, + 0, + expectedItems.Length, + true, + true); + } + + private void VerifyEnumerator( + IEnumerator enumerator, + T[] expectedItems, + int startIndex, + int count, + bool validateStart, + bool validateEnd) + { + bool needToMatchAllExpectedItems = count - startIndex == expectedItems.Length; + if (validateStart) + { + for (var i = 0; i < 3; i++) + { + if (Enumerator_Current_UndefinedOperation_Throws) + { + Assert.Throws(() => enumerator.Current); + } + else + { + var cur = enumerator.Current; + } + } + } + + int iterations; + if (Order == EnumerableOrder.Unspecified) + { + var itemsVisited = + new BitArray( + needToMatchAllExpectedItems + ? count + : expectedItems.Length, + false); + for (iterations = 0; + iterations < count && enumerator.MoveNext(); + iterations++) + { + object currentItem = enumerator.Current; + var itemFound = false; + for (var i = 0; i < itemsVisited.Length; ++i) + { + if (!itemsVisited[i] + && Equals( + currentItem, + expectedItems[ + i + + (needToMatchAllExpectedItems + ? startIndex + : 0)])) + { + itemsVisited[i] = true; + itemFound = true; + break; + } + } + Assert.True(itemFound, "itemFound"); + + for (var i = 0; i < 3; i++) + { + object tempItem = enumerator.Current; + Assert.Equal(currentItem, tempItem); + } + } + if (needToMatchAllExpectedItems) + { + for (var i = 0; i < itemsVisited.Length; i++) + { + Assert.True(itemsVisited[i]); + } + } + else + { + var visitedItemCount = 0; + for (var i = 0; i < itemsVisited.Length; i++) + { + if (itemsVisited[i]) + { + ++visitedItemCount; + } + } + Assert.Equal(count, visitedItemCount); + } + } + else if (Order == EnumerableOrder.Sequential) + { + for (iterations = 0; + iterations < count && enumerator.MoveNext(); + iterations++) + { + object currentItem = enumerator.Current; + Assert.Equal(expectedItems[iterations], currentItem); + for (var i = 0; i < 3; i++) + { + object tempItem = enumerator.Current; + Assert.Equal(currentItem, tempItem); + } + } + } + else + { + throw new ArgumentException( + "EnumerableOrder is invalid."); + } + Assert.Equal(count, iterations); + + if (validateEnd) + { + for (var i = 0; i < 3; i++) + { + Assert.False(enumerator.MoveNext(), "enumerator.MoveNext() returned true past the expected end."); + + if (Enumerator_Current_UndefinedOperation_Throws) + { + Assert.Throws(() => enumerator.Current); + } + else + { + var cur = enumerator.Current; + } + } + } + } + + #endregion + + #region GetEnumerator() + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IEnumerable_Generic_GetEnumerator_NoExceptionsWhileGetting(int count) + { + IEnumerable enumerable = GenericIEnumerableFactory(count); + enumerable.GetEnumerator().Dispose(); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IEnumerable_Generic_GetEnumerator_ReturnsUniqueEnumerator(int count) + { + //Tests that the enumerators returned by GetEnumerator operate independently of one another + IEnumerable enumerable = GenericIEnumerableFactory(count); + int iterations = 0; + foreach (T item in enumerable) + foreach (T item2 in enumerable) + foreach (T item3 in enumerable) + iterations++; + Assert.Equal(count * count * count, iterations); + } + + #endregion + + #region Enumerator.MoveNext + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IEnumerable_Generic_Enumerator_MoveNext_FromStartToFinish(int count) + { + int iterations = 0; + using (IEnumerator enumerator = GenericIEnumerableFactory(count).GetEnumerator()) + { + while (enumerator.MoveNext()) + iterations++; + Assert.Equal(count, iterations); + } + } + + /// + /// For most collections, all calls to MoveNext after disposal of an enumerator will return false. + /// Some collections (SortedList), however, treat a call to dispose as if it were a call to Reset. Since the docs + /// specify neither of these as being strictly correct, we leave the method virtual. + /// + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public virtual void Enumerator_MoveNext_AfterDisposal(int count) + { + IEnumerator enumerator = GenericIEnumerableFactory(count).GetEnumerator(); + for (int i = 0; i < count; i++) + enumerator.MoveNext(); + enumerator.Dispose(); + Assert.False(enumerator.MoveNext()); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IEnumerable_Generic_Enumerator_MoveNext_AfterEndOfCollection(int count) + { + using (IEnumerator enumerator = GenericIEnumerableFactory(count).GetEnumerator()) + { + for (int i = 0; i < count; i++) + enumerator.MoveNext(); + Assert.False(enumerator.MoveNext()); + Assert.False(enumerator.MoveNext()); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IEnumerable_Generic_Enumerator_MoveNext_ModifiedBeforeEnumeration_ThrowsInvalidOperationException(int count) + { + Assert.All(GetModifyEnumerables(ModifyEnumeratorThrows), ModifyEnumerable => + { + IEnumerable enumerable = GenericIEnumerableFactory(count); + using (IEnumerator enumerator = enumerable.GetEnumerator()) + { + if (ModifyEnumerable(enumerable)) + { + if (Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + { + Assert.Throws(() => enumerator.MoveNext()); + } + else + { + enumerator.MoveNext(); + } + } + } + }); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IEnumerable_Generic_Enumerator_MoveNext_ModifiedBeforeEnumeration_Succeeds(int count) + { + Assert.All(GetModifyEnumerables(ModifyEnumeratorAllowed), ModifyEnumerable => + { + IEnumerable enumerable = GenericIEnumerableFactory(count); + using (IEnumerator enumerator = enumerable.GetEnumerator()) + { + if (ModifyEnumerable(enumerable)) + { + if (Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + { + enumerator.MoveNext(); + } + } + } + }); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IEnumerable_Generic_Enumerator_MoveNext_ModifiedDuringEnumeration_ThrowsInvalidOperationException(int count) + { + Assert.All(GetModifyEnumerables(ModifyEnumeratorThrows), ModifyEnumerable => + { + IEnumerable enumerable = GenericIEnumerableFactory(count); + using (IEnumerator enumerator = enumerable.GetEnumerator()) + { + for (int i = 0; i < count / 2; i++) + enumerator.MoveNext(); + if (ModifyEnumerable(enumerable)) + { + if (Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + { + Assert.Throws(() => enumerator.MoveNext()); + } + else + { + enumerator.MoveNext(); + } + } + } + }); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IEnumerable_Generic_Enumerator_MoveNext_ModifiedDuringEnumeration_Succeeds(int count) + { + Assert.All(GetModifyEnumerables(ModifyEnumeratorAllowed), ModifyEnumerable => + { + IEnumerable enumerable = GenericIEnumerableFactory(count); + using (IEnumerator enumerator = enumerable.GetEnumerator()) + { + for (int i = 0; i < count / 2; i++) + enumerator.MoveNext(); + if (ModifyEnumerable(enumerable)) + { + enumerator.MoveNext(); + } + } + }); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IEnumerable_Generic_Enumerator_MoveNext_ModifiedAfterEnumeration_ThrowsInvalidOperationException(int count) + { + Assert.All(GetModifyEnumerables(ModifyEnumeratorThrows), ModifyEnumerable => + { + IEnumerable enumerable = GenericIEnumerableFactory(count); + using (IEnumerator enumerator = enumerable.GetEnumerator()) + { + while (enumerator.MoveNext()) ; + if (ModifyEnumerable(enumerable)) + { + if (Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + { + Assert.Throws(() => enumerator.MoveNext()); + } + else + { + enumerator.MoveNext(); + } + } + } + }); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IEnumerable_Generic_Enumerator_MoveNext_ModifiedAfterEnumeration_Succeeds(int count) + { + Assert.All(GetModifyEnumerables(ModifyEnumeratorAllowed), ModifyEnumerable => + { + IEnumerable enumerable = GenericIEnumerableFactory(count); + using (IEnumerator enumerator = enumerable.GetEnumerator()) + { + while (enumerator.MoveNext()) + ; + if (ModifyEnumerable(enumerable)) + { + enumerator.MoveNext(); + } + } + }); + } + + [Fact] + public void IEnumerable_Generic_Enumerator_MoveNextHitsAllItems() + { + RepeatTest( + (enumerator, items) => + { + var iterations = 0; + while (enumerator.MoveNext()) + { + iterations++; + } + Assert.Equal(items.Length, iterations); + }); + } + + [Fact] + public void IEnumerable_Generic_Enumerator_MoveNextFalseAfterEndOfCollection() + { + RepeatTest( + (enumerator, items) => + { + while (enumerator.MoveNext()) + { + } + + Assert.False(enumerator.MoveNext()); + }); + } + + #endregion + + #region Enumerator.Current + + [Fact] + public void IEnumerable_Generic_Enumerator_Current() + { + // Verify that current returns proper result. + RepeatTest( + (enumerator, items, iteration) => + { + if (iteration == 1) + { + VerifyEnumerator( + enumerator, + items, + 0, + items.Length / 2, + true, + false); + } + else + { + VerifyEnumerator(enumerator, items); + } + }); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IEnumerable_Generic_Enumerator_Current_ReturnsSameValueOnRepeatedCalls(int count) + { + using (IEnumerator enumerator = GenericIEnumerableFactory(count).GetEnumerator()) + { + while (enumerator.MoveNext()) + { + T current = enumerator.Current; + Assert.Equal(current, enumerator.Current); + Assert.Equal(current, enumerator.Current); + Assert.Equal(current, enumerator.Current); + } + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IEnumerable_Generic_Enumerator_Current_ReturnsSameObjectsOnDifferentEnumerators(int count) + { + // Ensures that the elements returned from enumeration are exactly the same collection of + // elements returned from a previous enumeration + IEnumerable enumerable = GenericIEnumerableFactory(count); + Dictionary firstValues = new Dictionary(count); + Dictionary secondValues = new Dictionary(count); + foreach (T item in enumerable) + firstValues[item] = firstValues.ContainsKey(item) ? firstValues[item]++ : 1; + foreach (T item in enumerable) + secondValues[item] = secondValues.ContainsKey(item) ? secondValues[item]++ : 1; + Assert.Equal(firstValues.Count, secondValues.Count); + foreach (T key in firstValues.Keys) + Assert.Equal(firstValues[key], secondValues[key]); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IEnumerable_Generic_Enumerator_Current_BeforeFirstMoveNext_UndefinedBehavior(int count) + { + T current; + IEnumerable enumerable = GenericIEnumerableFactory(count); + using (IEnumerator enumerator = enumerable.GetEnumerator()) + { + if (Enumerator_Current_UndefinedOperation_Throws) + Assert.Throws(() => enumerator.Current); + else + current = enumerator.Current; + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IEnumerable_Generic_Enumerator_Current_AfterEndOfEnumerable_UndefinedBehavior(int count) + { + T current; + IEnumerable enumerable = GenericIEnumerableFactory(count); + using (IEnumerator enumerator = enumerable.GetEnumerator()) + { + while (enumerator.MoveNext()) ; + if (Enumerator_Current_UndefinedOperation_Throws) + Assert.Throws(() => enumerator.Current); + else + current = enumerator.Current; + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IEnumerable_Generic_Enumerator_Current_ModifiedDuringEnumeration_UndefinedBehavior(int count) + { + Assert.All(GetModifyEnumerables(ModifyEnumeratorThrows), ModifyEnumerable => + { + T current; + IEnumerable enumerable = GenericIEnumerableFactory(count); + using (IEnumerator enumerator = enumerable.GetEnumerator()) + { + if (ModifyEnumerable(enumerable)) + { + if (Enumerator_Current_UndefinedOperation_Throws) + Assert.Throws(() => enumerator.Current); + else + current = enumerator.Current; + } + } + }); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IEnumerable_Generic_Enumerator_Current_ModifiedDuringEnumeration_Succeeds(int count) + { + Assert.All(GetModifyEnumerables(ModifyEnumeratorAllowed), ModifyEnumerable => + { + T current; + IEnumerable enumerable = GenericIEnumerableFactory(count); + using (IEnumerator enumerator = enumerable.GetEnumerator()) + { + if (ModifyEnumerable(enumerable)) + { + current = enumerator.Current; + } + } + }); + } + + #endregion + + #region Enumerator.Reset + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IEnumerable_Generic_Enumerator_Reset_BeforeIteration_Support(int count) + { + using (IEnumerator enumerator = GenericIEnumerableFactory(count).GetEnumerator()) + { + if (ResetImplemented) + enumerator.Reset(); + else + Assert.Throws(() => enumerator.Reset()); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IEnumerable_Generic_Enumerator_Reset_ModifiedBeforeEnumeration_ThrowsInvalidOperationException(int count) + { + Assert.All(GetModifyEnumerables(ModifyEnumeratorThrows), ModifyEnumerable => + { + IEnumerable enumerable = GenericIEnumerableFactory(count); + using (IEnumerator enumerator = enumerable.GetEnumerator()) + { + if (ModifyEnumerable(enumerable)) + { + if (Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + { + Assert.Throws(() => enumerator.Reset()); + } + else + { + enumerator.Reset(); + } + } + } + }); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IEnumerable_Generic_Enumerator_Reset_ModifiedBeforeEnumeration_Succeeds(int count) + { + Assert.All(GetModifyEnumerables(ModifyEnumeratorAllowed), ModifyEnumerable => + { + IEnumerable enumerable = GenericIEnumerableFactory(count); + using (IEnumerator enumerator = enumerable.GetEnumerator()) + { + if (ModifyEnumerable(enumerable)) + { + enumerator.Reset(); + } + } + }); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IEnumerable_Generic_Enumerator_Reset_ModifiedDuringEnumeration_ThrowsInvalidOperationException(int count) + { + Assert.All(GetModifyEnumerables(ModifyEnumeratorThrows), ModifyEnumerable => + { + IEnumerable enumerable = GenericIEnumerableFactory(count); + using (IEnumerator enumerator = enumerable.GetEnumerator()) + { + for (int i = 0; i < count / 2; i++) + enumerator.MoveNext(); + if (ModifyEnumerable(enumerable)) + { + if (Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + { + Assert.Throws(() => enumerator.Reset()); + } + else + { + enumerator.Reset(); + } + } + } + }); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IEnumerable_Generic_Enumerator_Reset_ModifiedDuringEnumeration_Succeeds(int count) + { + Assert.All(GetModifyEnumerables(ModifyEnumeratorAllowed), ModifyEnumerable => + { + IEnumerable enumerable = GenericIEnumerableFactory(count); + using (IEnumerator enumerator = enumerable.GetEnumerator()) + { + for (int i = 0; i < count / 2; i++) + enumerator.MoveNext(); + if (ModifyEnumerable(enumerable)) + { + enumerator.Reset(); + } + } + }); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IEnumerable_Generic_Enumerator_Reset_ModifiedAfterEnumeration_ThrowsInvalidOperationException(int count) + { + Assert.All(GetModifyEnumerables(ModifyEnumeratorThrows), ModifyEnumerable => + { + IEnumerable enumerable = GenericIEnumerableFactory(count); + using (IEnumerator enumerator = enumerable.GetEnumerator()) + { + while (enumerator.MoveNext()) ; + if (ModifyEnumerable(enumerable)) + { + if (Enumerator_ModifiedDuringEnumeration_ThrowsInvalidOperationException) + { + Assert.Throws(() => enumerator.Reset()); + } + else + { + enumerator.Reset(); + } + } + } + }); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void IEnumerable_Generic_Enumerator_Reset_ModifiedAfterEnumeration_Succeeds(int count) + { + Assert.All(GetModifyEnumerables(ModifyEnumeratorAllowed), ModifyEnumerable => + { + IEnumerable enumerable = GenericIEnumerableFactory(count); + using (IEnumerator enumerator = enumerable.GetEnumerator()) + { + while (enumerator.MoveNext()) + ; + if (ModifyEnumerable(enumerable)) + { + enumerator.Reset(); + } + } + }); + } + + [Fact] + public void IEnumerable_Generic_Enumerator_Reset() + { + if (!ResetImplemented) + { + RepeatTest( + (enumerator, items) => + { + Assert.Throws( + () => enumerator.Reset()); + }); + RepeatTest( + (enumerator, items, iter) => + { + if (iter == 1) + { + VerifyEnumerator( + enumerator, + items, + 0, + items.Length / 2, + true, + false); + for (var i = 0; i < 3; i++) + { + Assert.Throws( + () => enumerator.Reset()); + } + VerifyEnumerator( + enumerator, + items, + items.Length / 2, + items.Length - (items.Length / 2), + false, + true); + } + else if (iter == 2) + { + VerifyEnumerator(enumerator, items); + for (var i = 0; i < 3; i++) + { + Assert.Throws( + () => enumerator.Reset()); + } + VerifyEnumerator( + enumerator, + items, + 0, + 0, + false, + true); + } + else + { + VerifyEnumerator(enumerator, items); + } + }); + } + else + { + RepeatTest( + (enumerator, items, iter) => + { + if (iter == 1) + { + VerifyEnumerator( + enumerator, + items, + 0, + items.Length / 2, + true, + false); + enumerator.Reset(); + enumerator.Reset(); + } + else if (iter == 3) + { + VerifyEnumerator(enumerator, items); + enumerator.Reset(); + enumerator.Reset(); + } + else + { + VerifyEnumerator(enumerator, items); + } + }, + 5); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/RandomExtensions.cs b/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/RandomExtensions.cs new file mode 100644 index 000000000..253d2250b --- /dev/null +++ b/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/RandomExtensions.cs @@ -0,0 +1,22 @@ +// Tests copied from dotnet/runtime repo. Original source code can be found here: +// https://github.com/dotnet/runtime/blob/main/src/libraries/Common/tests/System/RandomExtensions.cs + +using System; + +namespace PerfView.Collections.Tests +{ + internal static class RandomExtensions + { + public static void Shuffle(this Random random, T[] array) + { + int n = array.Length; + while (n > 1) + { + int k = random.Next(n--); + T temp = array[n]; + array[n] = array[k]; + array[k] = temp; + } + } + } +} diff --git a/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/SegmentedDictionary.Generic.Tests.cs b/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/SegmentedDictionary.Generic.Tests.cs new file mode 100644 index 000000000..d71040c9b --- /dev/null +++ b/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/SegmentedDictionary.Generic.Tests.cs @@ -0,0 +1,368 @@ +// Tests copied from dotnet/runtime repo. Original source code can be found here: +// https://github.com/dotnet/runtime/blob/main/src/libraries/System.Collections/tests/Generic/Dictionary/Dictionary.Generic.cs + +using System; +using System.Collections.Generic; +using Xunit; + +namespace PerfView.Collections.Tests +{ + /// + /// Contains tests that ensure the correctness of the Dictionary class. + /// + public abstract class SegmentedDictionary_Generic_Tests : IDictionary_Generic_Tests + { + protected override ModifyOperation ModifyEnumeratorThrows => ModifyOperation.Add | ModifyOperation.Insert; + + protected override ModifyOperation ModifyEnumeratorAllowed => ModifyOperation.Overwrite | ModifyOperation.Remove | ModifyOperation.Clear; + + #region IDictionary GenericIDictionaryFactory() => new SegmentedDictionary(); + + protected override IDictionary GenericIDictionaryFactory(IEqualityComparer comparer) => new SegmentedDictionary(comparer); + + protected override Type ICollection_Generic_CopyTo_IndexLargerThanArrayCount_ThrowType => typeof(ArgumentOutOfRangeException); + + #endregion + + #region Constructors + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void Dictionary_Generic_Constructor_IDictionary(int count) + { + IDictionary source = GenericIDictionaryFactory(count); + IDictionary copied = new SegmentedDictionary(source); + Assert.Equal(source, copied); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void Dictionary_Generic_Constructor_IDictionary_IEqualityComparer(int count) + { + IEqualityComparer comparer = GetKeyIEqualityComparer(); + IDictionary source = GenericIDictionaryFactory(count); + SegmentedDictionary copied = new SegmentedDictionary(source, comparer); + Assert.Equal(source, copied); + Assert.Equal(comparer, copied.Comparer); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void Dictionary_Generic_Constructor_IEqualityComparer(int count) + { + IEqualityComparer comparer = GetKeyIEqualityComparer(); + IDictionary source = GenericIDictionaryFactory(count); + SegmentedDictionary copied = new SegmentedDictionary(source, comparer); + Assert.Equal(source, copied); + Assert.Equal(comparer, copied.Comparer); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void Dictionary_Generic_Constructor_int(int count) + { + IDictionary dictionary = new SegmentedDictionary(count); + Assert.Equal(0, dictionary.Count); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void Dictionary_Generic_Constructor_int_IEqualityComparer(int count) + { + IEqualityComparer comparer = GetKeyIEqualityComparer(); + SegmentedDictionary dictionary = new SegmentedDictionary(count, comparer); + Assert.Empty(dictionary); + Assert.Equal(comparer, dictionary.Comparer); + } + + #endregion + + #region ContainsValue + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void Dictionary_Generic_ContainsValue_NotPresent(int count) + { + SegmentedDictionary dictionary = (SegmentedDictionary)GenericIDictionaryFactory(count); + int seed = 4315; + TValue notPresent = CreateTValue(seed++); + while (dictionary.Values.Contains(notPresent)) + notPresent = CreateTValue(seed++); + Assert.False(dictionary.ContainsValue(notPresent)); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void Dictionary_Generic_ContainsValue_Present(int count) + { + SegmentedDictionary dictionary = (SegmentedDictionary)GenericIDictionaryFactory(count); + int seed = 4315; + KeyValuePair notPresent = CreateT(seed++); + while (dictionary.Contains(notPresent)) + notPresent = CreateT(seed++); + dictionary.Add(notPresent.Key, notPresent.Value); + Assert.True(dictionary.ContainsValue(notPresent.Value)); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void Dictionary_Generic_ContainsValue_DefaultValueNotPresent(int count) + { + SegmentedDictionary dictionary = (SegmentedDictionary)GenericIDictionaryFactory(count); + Assert.False(dictionary.ContainsValue(default(TValue))); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void Dictionary_Generic_ContainsValue_DefaultValuePresent(int count) + { + SegmentedDictionary dictionary = (SegmentedDictionary)GenericIDictionaryFactory(count); + int seed = 4315; + TKey notPresent = CreateTKey(seed++); + while (dictionary.ContainsKey(notPresent)) + notPresent = CreateTKey(seed++); + dictionary.Add(notPresent, default(TValue)); + Assert.True(dictionary.ContainsValue(default(TValue))); + } + + #endregion + + #region Remove(TKey) + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void Dictionary_Generic_RemoveKey_ValidKeyNotContainedInDictionary(int count) + { + SegmentedDictionary dictionary = (SegmentedDictionary)GenericIDictionaryFactory(count); + TValue value; + TKey missingKey = GetNewKey(dictionary); + + Assert.False(dictionary.Remove(missingKey, out value)); + Assert.Equal(count, dictionary.Count); + Assert.Equal(default(TValue), value); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void Dictionary_Generic_RemoveKey_ValidKeyContainedInDictionary(int count) + { + SegmentedDictionary dictionary = (SegmentedDictionary)GenericIDictionaryFactory(count); + TKey missingKey = GetNewKey(dictionary); + TValue outValue; + TValue inValue = CreateTValue(count); + + dictionary.Add(missingKey, inValue); + Assert.True(dictionary.Remove(missingKey, out outValue)); + Assert.Equal(count, dictionary.Count); + Assert.Equal(inValue, outValue); + Assert.False(dictionary.TryGetValue(missingKey, out outValue)); + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void Dictionary_Generic_RemoveKey_DefaultKeyNotContainedInDictionary(int count) + { + SegmentedDictionary dictionary = (SegmentedDictionary)GenericIDictionaryFactory(count); + TValue outValue; + + if (DefaultValueAllowed) + { + TKey missingKey = default(TKey); + while (dictionary.ContainsKey(missingKey)) + dictionary.Remove(missingKey); + Assert.False(dictionary.Remove(missingKey, out outValue)); + Assert.Equal(default(TValue), outValue); + } + else + { + TValue initValue = CreateTValue(count); + outValue = initValue; + Assert.Throws(() => dictionary.Remove(default(TKey), out outValue)); + Assert.Equal(initValue, outValue); + } + } + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void Dictionary_Generic_RemoveKey_DefaultKeyContainedInDictionary(int count) + { + if (DefaultValueAllowed) + { + SegmentedDictionary dictionary = (SegmentedDictionary)(GenericIDictionaryFactory(count)); + TKey missingKey = default(TKey); + TValue value; + + dictionary.TryAdd(missingKey, default(TValue)); + Assert.True(dictionary.Remove(missingKey, out value)); + } + } + + [Fact] + public void Dictionary_Generic_Remove_RemoveFirstEnumerationContinues() + { + SegmentedDictionary dict = (SegmentedDictionary)GenericIDictionaryFactory(3); + using (var enumerator = dict.GetEnumerator()) + { + enumerator.MoveNext(); + TKey key = enumerator.Current.Key; + enumerator.MoveNext(); + dict.Remove(key); + Assert.True(enumerator.MoveNext()); + Assert.False(enumerator.MoveNext()); + } + } + + [Fact] + public void Dictionary_Generic_Remove_RemoveCurrentEnumerationContinues() + { + SegmentedDictionary dict = (SegmentedDictionary)GenericIDictionaryFactory(3); + using (var enumerator = dict.GetEnumerator()) + { + enumerator.MoveNext(); + enumerator.MoveNext(); + dict.Remove(enumerator.Current.Key); + Assert.True(enumerator.MoveNext()); + Assert.False(enumerator.MoveNext()); + } + } + + [Fact] + public void Dictionary_Generic_Remove_RemoveLastEnumerationFinishes() + { + SegmentedDictionary dict = (SegmentedDictionary)GenericIDictionaryFactory(3); + TKey key = default; + using (var enumerator = dict.GetEnumerator()) + { + while (enumerator.MoveNext()) + { + key = enumerator.Current.Key; + } + } + using (var enumerator = dict.GetEnumerator()) + { + enumerator.MoveNext(); + enumerator.MoveNext(); + dict.Remove(key); + Assert.False(enumerator.MoveNext()); + } + } + + #endregion + + #region EnsureCapacity + + [Theory] + [MemberData(nameof(ValidCollectionSizes))] + public void EnsureCapacity_Generic_RequestingLargerCapacity_DoesInvalidateEnumeration(int count) + { + var dictionary = (SegmentedDictionary)(GenericIDictionaryFactory(count)); + var capacity = dictionary.EnsureCapacity(0); + var enumerator = dictionary.GetEnumerator(); + + dictionary.EnsureCapacity(capacity + 1); // Verify EnsureCapacity does invalidate enumeration + + Assert.Throws(() => enumerator.MoveNext()); + } + + [Fact] + public void EnsureCapacity_Generic_NegativeCapacityRequested_Throws() + { + var dictionary = new SegmentedDictionary(); + AssertExtensions.Throws("capacity", () => dictionary.EnsureCapacity(-1)); + } + + [Fact] + public void EnsureCapacity_Generic_DictionaryNotInitialized_RequestedZero_ReturnsZero() + { + var dictionary = new SegmentedDictionary(); + Assert.Equal(0, dictionary.EnsureCapacity(0)); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(4)] + public void EnsureCapacity_Generic_DictionaryNotInitialized_RequestedNonZero_CapacityIsSetToAtLeastTheRequested(int requestedCapacity) + { + var dictionary = new SegmentedDictionary(); + Assert.InRange(dictionary.EnsureCapacity(requestedCapacity), requestedCapacity, int.MaxValue); + } + + [Theory] + [InlineData(3)] + [InlineData(7)] + public void EnsureCapacity_Generic_RequestedCapacitySmallerThanCurrent_CapacityUnchanged(int currentCapacity) + { + SegmentedDictionary dictionary; + + // assert capacity remains the same when ensuring a capacity smaller or equal than existing + for (int i = 0; i <= currentCapacity; i++) + { + dictionary = new SegmentedDictionary(currentCapacity); + Assert.True(dictionary.EnsureCapacity(i) > currentCapacity); + } + } + + [Theory] + [InlineData(7)] + public void EnsureCapacity_Generic_ExistingCapacityRequested_SameValueReturned(int capacity) + { + var dictionary = new SegmentedDictionary(capacity); + Assert.True(dictionary.EnsureCapacity(capacity) > capacity); + + dictionary = (SegmentedDictionary)GenericIDictionaryFactory(capacity); + Assert.True(dictionary.EnsureCapacity(capacity) > capacity); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(4)] + public void EnsureCapacity_Generic_EnsureCapacityCalledTwice_ReturnsSameValue(int count) + { + var dictionary = (SegmentedDictionary)GenericIDictionaryFactory(count); + int capacity = dictionary.EnsureCapacity(0); + Assert.Equal(capacity, dictionary.EnsureCapacity(0)); + + dictionary = (SegmentedDictionary)GenericIDictionaryFactory(count); + capacity = dictionary.EnsureCapacity(count); + Assert.True(dictionary.EnsureCapacity(count) >= capacity); + + dictionary = (SegmentedDictionary)GenericIDictionaryFactory(count); + capacity = dictionary.EnsureCapacity(count + 1); + Assert.True(dictionary.EnsureCapacity(count + 1) >= capacity); + } + + [Theory] + [InlineData(1)] + [InlineData(5)] + [InlineData(7)] + public void EnsureCapacity_Generic_DictionaryNotEmpty_RequestedSmallerThanCount_ReturnsAtLeastSizeOfCount(int count) + { + var dictionary = (SegmentedDictionary)GenericIDictionaryFactory(count); + Assert.InRange(dictionary.EnsureCapacity(count - 1), count, int.MaxValue); + } + + [Theory] + [InlineData(7)] + [InlineData(20)] + public void EnsureCapacity_Generic_DictionaryNotEmpty_SetsToAtLeastTheRequested(int count) + { + var dictionary = (SegmentedDictionary)GenericIDictionaryFactory(count); + + // get current capacity + int currentCapacity = dictionary.EnsureCapacity(0); + + // assert we can update to a larger capacity + int newCapacity = dictionary.EnsureCapacity(currentCapacity * 2); + Assert.InRange(newCapacity, currentCapacity * 2, int.MaxValue); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/SegmentedDictionaryTests.cs b/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/SegmentedDictionaryTests.cs new file mode 100644 index 000000000..8a1518479 --- /dev/null +++ b/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/SegmentedDictionaryTests.cs @@ -0,0 +1,146 @@ +// Tests copied from dotnet/runtime repo. Original source code can be found here: +// https://github.com/dotnet/runtime/blob/main/src/libraries/System.Collections/tests/Generic/Dictionary/Dictionary.Tests.cs + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace PerfView.Collections.Tests +{ + public partial class SegmentedDictionary_Generic_Tests_string_string : SegmentedDictionary_Generic_Tests + { + protected override KeyValuePair CreateT(int seed) + { + return new KeyValuePair(CreateTKey(seed), CreateTKey(seed + 500)); + } + + protected override string CreateTKey(int seed) + { + int stringLength = seed % 10 + 5; + Random rand = new Random(seed); + byte[] bytes1 = new byte[stringLength]; + rand.NextBytes(bytes1); + return Convert.ToBase64String(bytes1); + } + + protected override string CreateTValue(int seed) => CreateTKey(seed); + } + + [Serializable] + public struct SimpleInt : IStructuralComparable, IStructuralEquatable, IComparable, IComparable + { + private int _val; + public SimpleInt(int t) + { + _val = t; + } + public int Val + { + get { return _val; } + set { _val = value; } + } + + public int CompareTo(SimpleInt other) + { + return other.Val - _val; + } + + public int CompareTo(object obj) + { + if (obj.GetType() == typeof(SimpleInt)) + { + return ((SimpleInt)obj).Val - _val; + } + return -1; + } + + public int CompareTo(object other, IComparer comparer) + { + if (other.GetType() == typeof(SimpleInt)) + return ((SimpleInt)other).Val - _val; + return -1; + } + + public bool Equals(object other, IEqualityComparer comparer) + { + if (other.GetType() == typeof(SimpleInt)) + return ((SimpleInt)other).Val == _val; + return false; + } + + public int GetHashCode(IEqualityComparer comparer) + { + return comparer.GetHashCode(_val); + } + } + + [Serializable] + public class WrapStructural_SimpleInt : IEqualityComparer, IComparer + { + public int Compare(SimpleInt x, SimpleInt y) + { + return StructuralComparisons.StructuralComparer.Compare(x, y); + } + + public bool Equals(SimpleInt x, SimpleInt y) + { + return StructuralComparisons.StructuralEqualityComparer.Equals(x, y); + } + + public int GetHashCode(SimpleInt obj) + { + return StructuralComparisons.StructuralEqualityComparer.GetHashCode(obj); + } + } + + public class SegmentedDictionary_Generic_Tests_int_int : SegmentedDictionary_Generic_Tests + { + protected override bool DefaultValueAllowed => true; + + protected override KeyValuePair CreateT(int seed) + { + Random rand = new Random(seed); + return new KeyValuePair(rand.Next(), rand.Next()); + } + + protected override int CreateTKey(int seed) + { + Random rand = new Random(seed); + return rand.Next(); + } + + protected override int CreateTValue(int seed) => CreateTKey(seed); + } + + public class SegmentedDictionary_Generic_Tests_SimpleInt_int_With_Comparer_WrapStructural_SimpleInt : SegmentedDictionary_Generic_Tests + { + protected override bool DefaultValueAllowed { get { return true; } } + + public override IEqualityComparer GetKeyIEqualityComparer() + { + return new WrapStructural_SimpleInt(); + } + + public override IComparer GetKeyIComparer() + { + return new WrapStructural_SimpleInt(); + } + + protected override SimpleInt CreateTKey(int seed) + { + Random rand = new Random(seed); + return new SimpleInt(rand.Next()); + } + + protected override int CreateTValue(int seed) + { + Random rand = new Random(seed); + return rand.Next(); + } + + protected override KeyValuePair CreateT(int seed) + { + return new KeyValuePair(CreateTKey(seed), CreateTValue(seed)); + } + } +} \ No newline at end of file diff --git a/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/TestBase.Generic.cs b/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/TestBase.Generic.cs new file mode 100644 index 000000000..e2b60c4dc --- /dev/null +++ b/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/TestBase.Generic.cs @@ -0,0 +1,227 @@ +// Tests copied from dotnet/runtime repo. Original source code can be found here: +// https://github.com/dotnet/runtime/blob/main/src/libraries/Common/tests/System/Collections/TestBase.Generic.cs + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Xunit; + +namespace PerfView.Collections.Tests +{ + /// + /// Provides a base set of generic operations that are used by all other generic testing interfaces. + /// + public abstract class TestBase : TestBase + { + #region Helper Methods + + /// + /// To be implemented in the concrete collections test classes. Creates an instance of T that + /// is dependent only on the seed passed as input and will return the same value on repeated + /// calls with the same seed. + /// + protected abstract T CreateT(int seed); + + /// + /// The EqualityComparer that can be used in the overriding class when creating test enumerables + /// or test collections. Default if not overridden is the default comparator. + /// + protected virtual IEqualityComparer GetIEqualityComparer() => EqualityComparer.Default; + + /// + /// The Comparer that can be used in the overriding class when creating test enumerables + /// or test collections. Default if not overridden is the default comparator. + protected virtual IComparer GetIComparer() => Comparer.Default; + + /// + /// Helper function to create a Queue fulfilling the given specific parameters. The function will + /// create an Queue and then add values + /// to it until it is full. It will begin by adding the desired number of matching, + /// followed by random (deterministic) elements until the desired count is reached. + /// + protected IEnumerable CreateQueue(IEnumerable enumerableToMatchTo, int count, int numberOfMatchingElements, int numberOfDuplicateElements) + { + Queue queue = new Queue(count); + int seed = 528; + int duplicateAdded = 0; + List match = null; + + // Enqueue Matching elements + if (enumerableToMatchTo != null) + { + match = enumerableToMatchTo.ToList(); + for (int i = 0; i < numberOfMatchingElements; i++) + { + queue.Enqueue(match[i]); + while (duplicateAdded++ < numberOfDuplicateElements) + queue.Enqueue(match[i]); + } + } + + // Enqueue elements to reach the desired count + while (queue.Count < count) + { + T toEnqueue = CreateT(seed++); + while (queue.Contains(toEnqueue) || (match != null && match.Contains(toEnqueue))) // Don't want any unexpectedly duplicate values + toEnqueue = CreateT(seed++); + queue.Enqueue(toEnqueue); + while (duplicateAdded++ < numberOfDuplicateElements) + queue.Enqueue(toEnqueue); + } + + // Validate that the Enumerable fits the guidelines as expected + Debug.Assert(queue.Count == count); + if (match != null) + { + int actualMatchingCount = 0; + foreach (T lookingFor in match) + actualMatchingCount += queue.Contains(lookingFor) ? 1 : 0; + Assert.Equal(numberOfMatchingElements, actualMatchingCount); + } + + return queue; + } + + /// + /// Helper function to create an List fulfilling the given specific parameters. The function will + /// create an List and then add values + /// to it until it is full. It will begin by adding the desired number of matching, + /// followed by random (deterministic) elements until the desired count is reached. + /// + protected IEnumerable CreateList(IEnumerable enumerableToMatchTo, int count, int numberOfMatchingElements, int numberOfDuplicateElements) + { + List list = new List(count); + int seed = 528; + int duplicateAdded = 0; + List match = null; + + // Add Matching elements + if (enumerableToMatchTo != null) + { + match = enumerableToMatchTo.ToList(); + for (int i = 0; i < numberOfMatchingElements; i++) + { + list.Add(match[i]); + while (duplicateAdded++ < numberOfDuplicateElements) + list.Add(match[i]); + } + } + + // Add elements to reach the desired count + while (list.Count < count) + { + T toAdd = CreateT(seed++); + while (list.Contains(toAdd) || (match != null && match.Contains(toAdd))) // Don't want any unexpectedly duplicate values + toAdd = CreateT(seed++); + list.Add(toAdd); + while (duplicateAdded++ < numberOfDuplicateElements) + list.Add(toAdd); + } + + // Validate that the Enumerable fits the guidelines as expected + Debug.Assert(list.Count == count); + if (match != null) + { + int actualMatchingCount = 0; + foreach (T lookingFor in match) + actualMatchingCount += list.Contains(lookingFor) ? 1 : 0; + Assert.Equal(numberOfMatchingElements, actualMatchingCount); + } + + return list; + } + + /// + /// Helper function to create an HashSet fulfilling the given specific parameters. The function will + /// create an HashSet using the Comparer constructor and then add values + /// to it until it is full. It will begin by adding the desired number of matching, + /// followed by random (deterministic) elements until the desired count is reached. + /// + protected IEnumerable CreateHashSet(IEnumerable enumerableToMatchTo, int count, int numberOfMatchingElements) + { + HashSet set = new HashSet(GetIEqualityComparer()); + int seed = 528; + List match = null; + + // Add Matching elements + if (enumerableToMatchTo != null) + { + match = enumerableToMatchTo.ToList(); + for (int i = 0; i < numberOfMatchingElements; i++) + set.Add(match[i]); + } + + // Add elements to reach the desired count + while (set.Count < count) + { + T toAdd = CreateT(seed++); + while (set.Contains(toAdd) || (match != null && match.Contains(toAdd, GetIEqualityComparer()))) // Don't want any unexpectedly duplicate values + toAdd = CreateT(seed++); + set.Add(toAdd); + } + + // Validate that the Enumerable fits the guidelines as expected + Debug.Assert(set.Count == count); + if (match != null) + { + int actualMatchingCount = 0; + foreach (T lookingFor in match) + actualMatchingCount += set.Contains(lookingFor) ? 1 : 0; + Assert.Equal(numberOfMatchingElements, actualMatchingCount); + } + + return set; + } + + /// + /// Helper function to create an SortedSet fulfilling the given specific parameters. The function will + /// create an SortedSet using the Comparer constructor and then add values + /// to it until it is full. It will begin by adding the desired number of matching, + /// followed by random (deterministic) elements until the desired count is reached. + /// + protected IEnumerable CreateSortedSet(IEnumerable enumerableToMatchTo, int count, int numberOfMatchingElements) + { + SortedSet set = new SortedSet(GetIComparer()); + int seed = 528; + List match = null; + + // Add Matching elements + if (enumerableToMatchTo != null) + { + match = enumerableToMatchTo.ToList(); + for (int i = 0; i < numberOfMatchingElements; i++) + set.Add(match[i]); + } + + // Add elements to reach the desired count + while (set.Count < count) + { + T toAdd = CreateT(seed++); + while (set.Contains(toAdd) || (match != null && match.Contains(toAdd, GetIEqualityComparer()))) // Don't want any unexpectedly duplicate values + toAdd = CreateT(seed++); + set.Add(toAdd); + } + + // Validate that the Enumerable fits the guidelines as expected + Debug.Assert(set.Count == count); + if (match != null) + { + int actualMatchingCount = 0; + foreach (T lookingFor in match) + actualMatchingCount += set.Contains(lookingFor) ? 1 : 0; + Assert.Equal(numberOfMatchingElements, actualMatchingCount); + } + + return set; + } + + protected IEnumerable CreateLazyEnumerable(IEnumerable enumerableToMatchTo, int count, int numberOfMatchingElements, int numberOfDuplicateElements) + { + IEnumerable list = CreateList(enumerableToMatchTo, count, numberOfMatchingElements, numberOfDuplicateElements); + return list.Select(item => item); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/TestBase.NoGeneric.cs b/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/TestBase.NoGeneric.cs new file mode 100644 index 000000000..992a2a1b3 --- /dev/null +++ b/src/TraceEvent/TraceEvent.Tests/SegmentedDictionary/TestBase.NoGeneric.cs @@ -0,0 +1,45 @@ +// Tests copied from dotnet/runtime repo. Original source code can be found here: +// https://github.com/dotnet/runtime/blob/main/src/libraries/Common/tests/System/Collections/TestBase.NonGeneric.cs + +using System; +using System.Collections.Generic; + +namespace PerfView.Collections.Tests +{ + /// + /// Provides a base set of nongeneric operations that are used by all other testing interfaces. + /// + public abstract class TestBase + { + #region Helper Methods + + public static IEnumerable ValidCollectionSizes() + { + yield return new object[] { 0 }; + yield return new object[] { 1 }; + yield return new object[] { 75 }; + } + + public enum EnumerableType + { + HashSet, + SortedSet, + List, + Queue, + Lazy, + }; + + [Flags] + public enum ModifyOperation + { + None = 0, + Add = 1, + Insert = 2, + Overwrite = 4, + Remove = 8, + Clear = 16 + } + + #endregion + } +} \ No newline at end of file