// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Collections; using System.Collections.Generic; using System.Linq; using Assert = Microsoft.TestCommon.AssertEx; namespace System.Web.TestUtil { public class DictionaryHelper { public IEqualityComparer Comparer { get; set; } public Func> Creator { get; set; } public IList SampleKeys { get; set; } public IList SampleValues { get; set; } public bool SkipItemPropertyTest { get; set; } public bool ThrowOnKeyNotFound { get; set; } public void Execute() { ValidateProperties(); Executor executor = new Executor() { Comparer = Comparer, Creator = Creator, ThrowOnKeyNotFound = ThrowOnKeyNotFound, Values = SampleValues.ToArray() }; SeparateKeys(out executor.ExcludedKey, out executor.ConflictingKeys, out executor.NonConflictingKeys); executor.TestAdd1(); executor.TestAdd1ThrowsArgumentExceptionIfKeyAlreadyInDictionary(); executor.TestAdd2(); executor.TestClear(); executor.TestContains(); executor.TestContainsKey(); executor.TestCopyTo(); executor.TestCountProperty(); executor.TestGetEnumerator(); executor.TestGetEnumeratorGeneric(); executor.TestIsReadOnlyProperty(); if (!SkipItemPropertyTest) { executor.TestItemProperty(); } executor.TestKeysProperty(); executor.TestRemove1(); executor.TestRemove2(); executor.TestTryGetValue(); executor.TestValuesProperty(); } private void SeparateKeys(out TKey excludedKey, out TKey[] conflictingKeys, out TKey[] nonConflictingKeys) { List nonConflictingKeyList = new List(); TKey[] newConflictingKeys = null; var keyLookup = SampleKeys.ToLookup(key => key, Comparer); foreach (var entry in keyLookup) { if (entry.Count() == 1) { // not a conflict nonConflictingKeyList.AddRange(entry); } else { // conflict newConflictingKeys = entry.ToArray(); } } excludedKey = nonConflictingKeyList[nonConflictingKeyList.Count - 1]; nonConflictingKeyList.RemoveAt(nonConflictingKeyList.Count - 1); conflictingKeys = newConflictingKeys; nonConflictingKeys = nonConflictingKeyList.ToArray(); } private void ValidateProperties() { if (Creator == null) { throw new InvalidOperationException("The Creator property must not be null."); } if (SampleKeys == null || SampleKeys.Count < 4) { throw new InvalidOperationException("The SampleKeys property must contain at least 4 elements."); } if (SampleValues == null || SampleValues.Count != SampleKeys.Count) { throw new InvalidOperationException("The SampleValues property must contain as many elements as the SampleKeys property."); } HashSet keys = new HashSet(SampleKeys, Comparer); if (keys.Count != SampleKeys.Count - 1) { throw new InvalidOperationException("The SampleKeys property must contain exactly one colliding keypair using the given comparer."); } } private class Executor { public IEqualityComparer Comparer; public Func> Creator; public TKey ExcludedKey; public TKey[] ConflictingKeys; public TKey[] NonConflictingKeys; public bool ThrowOnKeyNotFound; public TValue[] Values; private IEnumerable> MakeKeyValuePairs() { return MakeKeyValuePairs(false /* includeConflictingKeys */); } private IEnumerable> MakeKeyValuePairs(bool includeConflictingKeys) { for (int i = 0; i < NonConflictingKeys.Length; i++) { TKey key = NonConflictingKeys[i]; TValue value = Values[i]; yield return new KeyValuePair(key, value); } if (includeConflictingKeys) { for (int i = 0; i < 2; i++) { TKey key = ConflictingKeys[i]; TValue value = Values[NonConflictingKeys.Length + i]; yield return new KeyValuePair(key, value); } } } public void TestAdd1() { // Arrange Dictionary controlDictionary = new Dictionary(Comparer); IDictionary testDictionary = Creator(); // Act foreach (var entry in MakeKeyValuePairs()) { controlDictionary.Add(entry.Key, entry.Value); testDictionary.Add(entry.Key, entry.Value); } // Assert VerifyDictionaryEntriesEqual(controlDictionary, testDictionary); } public void TestAdd1ThrowsArgumentExceptionIfKeyAlreadyInDictionary() { // Arrange IDictionary testDictionary = Creator(); // Act & assert var pairs = MakeKeyValuePairs(true /* includeConflictingKeys */).Skip(NonConflictingKeys.Length).ToArray(); testDictionary.Add(pairs[0].Key, pairs[1].Value); Assert.Throws( delegate { testDictionary.Add(pairs[1].Key, pairs[1].Value); }, "An item with the same key has already been added." ); } public void TestAdd2() { // Arrange Dictionary controlDictionary = new Dictionary(Comparer); IDictionary testDictionary = Creator(); // Act foreach (var entry in MakeKeyValuePairs()) { ((IDictionary)controlDictionary).Add(entry); testDictionary.Add(entry); } // Assert VerifyDictionaryEntriesEqual(controlDictionary, testDictionary); } public void TestClear() { // Arrange IDictionary testDictionary = Creator(); // Act foreach (var entry in MakeKeyValuePairs()) { testDictionary.Add(entry); } testDictionary.Clear(); // Assert Assert.Empty(testDictionary); } public void TestCountProperty() { // Arrange Dictionary controlDictionary = new Dictionary(Comparer); IDictionary testDictionary = Creator(); // Act & assert foreach (var entry in MakeKeyValuePairs()) { controlDictionary.Add(entry.Key, entry.Value); testDictionary.Add(entry.Key, entry.Value); Assert.Equal(controlDictionary.Count, testDictionary.Count); } } public void TestContains() { // Arrange IDictionary testDictionary = Creator(); // Act foreach (var entry in MakeKeyValuePairs()) { testDictionary.Add(entry); } // Assert var shouldBeFound = MakeKeyValuePairs().First(); var shouldNotBeFound = new KeyValuePair(ExcludedKey, Values[Values.Length - 1]); Assert.True(testDictionary.Contains(shouldBeFound), String.Format("Test dictionary should have contained entry for KVP '{0}'.", shouldBeFound)); Assert.False(testDictionary.Contains(shouldNotBeFound), String.Format("Test dictionary should not have contained entry for KVP '{0}'.", shouldNotBeFound)); } public void TestContainsKey() { // Arrange IDictionary testDictionary = Creator(); // Act foreach (var entry in MakeKeyValuePairs()) { testDictionary.Add(entry); } // Assert Assert.True(testDictionary.ContainsKey(NonConflictingKeys[0]), String.Format("Test dictionary should have contained entry for key '{0}'.", NonConflictingKeys[0])); Assert.False(testDictionary.ContainsKey(ExcludedKey), String.Format("Test dictionary should not have contained entry for key '{0}'.", ExcludedKey)); } public void TestCopyTo() { // Arrange IDictionary controlDictionary = new Dictionary(Comparer); IDictionary testDictionary = Creator(); foreach (var entry in MakeKeyValuePairs()) { controlDictionary.Add(entry.Key, entry.Value); testDictionary.Add(entry.Key, entry.Value); } KeyValuePair[] testKvps = new KeyValuePair[testDictionary.Count + 2]; // Act testDictionary.CopyTo(testKvps, 2); // Assert for (int i = 0; i < 2; i++) { var defaultValue = default(KeyValuePair); var entry = testKvps[i]; Assert.Equal(defaultValue, entry); } for (int i = 2; i < testKvps.Length; i++) { var entry = testKvps[i]; Assert.True(controlDictionary.Contains(entry), String.Format("The value '{0}' wasn't present in the control dictionary.", entry)); controlDictionary.Remove(entry); } Assert.Empty(controlDictionary); } public void TestGetEnumerator() { // Arrange Dictionary controlDictionary = new Dictionary(Comparer); IDictionary testDictionary = Creator(); foreach (var entry in MakeKeyValuePairs()) { controlDictionary.Add(entry.Key, entry.Value); testDictionary.Add(entry.Key, entry.Value); } IEnumerable testDictionaryAsEnumerable = (IEnumerable)testDictionary; // Act Dictionary newTestDictionary = new Dictionary(Comparer); foreach (object entry in testDictionaryAsEnumerable) { var kvp = Assert.IsType>(entry); newTestDictionary.Add(kvp.Key, kvp.Value); } // Assert VerifyDictionaryEntriesEqual(controlDictionary, newTestDictionary); } public void TestGetEnumeratorGeneric() { // Arrange Dictionary controlDictionary = new Dictionary(Comparer); IDictionary testDictionary = Creator(); foreach (var entry in MakeKeyValuePairs()) { controlDictionary.Add(entry.Key, entry.Value); testDictionary.Add(entry.Key, entry.Value); } // Act & assert VerifyDictionaryEntriesEqual(controlDictionary, testDictionary); } public void TestIsReadOnlyProperty() { // Arrange IDictionary testDictionary = Creator(); // Act & assert Assert.False(testDictionary.IsReadOnly, "Dictionary should not be read only."); } public void TestItemProperty() { // Arrange Dictionary controlDictionary = new Dictionary(Comparer); IDictionary testDictionary = Creator(); var shouldBeFound = MakeKeyValuePairs().First(); var shouldNotBeFound = new KeyValuePair(ExcludedKey, Values[Values.Length - 1]); // Act & assert foreach (var entry in MakeKeyValuePairs()) { controlDictionary.Add(entry.Key, entry.Value); testDictionary[entry.Key] = entry.Value; } VerifyDictionaryEntriesEqual(controlDictionary, testDictionary); TValue value = testDictionary[shouldBeFound.Key]; Assert.Equal(shouldBeFound.Value, value); if (ThrowOnKeyNotFound) { Assert.Throws( delegate { TValue valueNotFound = testDictionary[shouldNotBeFound.Key]; }, allowDerivedExceptions: true); } else { TValue valueNotFound = testDictionary[shouldNotBeFound.Key]; Assert.Equal(default(TValue), valueNotFound); // Should not throw } } public void TestKeysProperty() { // Arrange Dictionary controlDictionary = new Dictionary(Comparer); IDictionary testDictionary = Creator(); foreach (var entry in MakeKeyValuePairs()) { controlDictionary.Add(entry.Key, entry.Value); testDictionary.Add(entry.Key, entry.Value); } // Act HashSet controlKeys = new HashSet(controlDictionary.Keys, Comparer); HashSet testKeys = new HashSet(testDictionary.Keys, Comparer); // Assert Assert.True(controlKeys.SetEquals(testKeys), "Control dictionary and test dictionary key sets were not equal."); } public void TestRemove1() { // Arrange Dictionary controlDictionary = new Dictionary(Comparer); IDictionary testDictionary = Creator(); foreach (var entry in MakeKeyValuePairs()) { controlDictionary.Add(entry.Key, entry.Value); testDictionary.Add(entry.Key, entry.Value); } // Act bool removalSuccess = testDictionary.Remove(NonConflictingKeys[0]); bool removalFailure = testDictionary.Remove(ExcludedKey); // Assert Assert.True(removalSuccess, "Remove() should return true if the key was removed."); Assert.False(removalFailure, "Remove() should return false if the key was not removed."); controlDictionary.Remove(NonConflictingKeys[0]); VerifyDictionaryEntriesEqual(controlDictionary, testDictionary); } public void TestRemove2() { // Arrange Dictionary controlDictionary = new Dictionary(Comparer); IDictionary testDictionary = Creator(); foreach (var entry in MakeKeyValuePairs()) { ((IDictionary)controlDictionary).Add(entry); testDictionary.Add(entry); } // Act var shouldBeFound = MakeKeyValuePairs().First(); var shouldNotBeFound = new KeyValuePair(ExcludedKey, Values[Values.Length - 1]); bool removalSuccess = testDictionary.Remove(shouldBeFound); bool removalFailure = testDictionary.Remove(shouldNotBeFound); // Assert Assert.True(removalSuccess, "Remove() should return true if the key was removed."); Assert.False(removalFailure, "Remove() should return false if the key was not removed."); ((IDictionary)controlDictionary).Remove(shouldBeFound); VerifyDictionaryEntriesEqual(controlDictionary, testDictionary); } public void TestTryGetValue() { // Arrange Dictionary controlDictionary = new Dictionary(Comparer); IDictionary testDictionary = Creator(); foreach (var entry in MakeKeyValuePairs()) { controlDictionary.Add(entry.Key, entry.Value); testDictionary.Add(entry.Key, entry.Value); } var shouldBeFound = MakeKeyValuePairs().First(); var shouldNotBeFound = new KeyValuePair(ExcludedKey, Values[Values.Length - 1]); // Act TValue value1; bool returned1 = testDictionary.TryGetValue(shouldBeFound.Key, out value1); TValue value2; bool returned2 = testDictionary.TryGetValue(shouldNotBeFound.Key, out value2); // Assert Assert.True(returned1, String.Format("The entry '{0}' should have been found.", shouldBeFound)); Assert.Equal(shouldBeFound.Value, value1); Assert.False(returned2, String.Format("The entry '{0}' should not have been found.", shouldNotBeFound)); Assert.Equal(default(TValue), value2); } public void TestValuesProperty() { // Arrange Dictionary controlDictionary = new Dictionary(Comparer); IDictionary testDictionary = Creator(); foreach (var entry in MakeKeyValuePairs()) { controlDictionary.Add(entry.Key, entry.Value); testDictionary.Add(entry.Key, entry.Value); } // Act List controlValues = controlDictionary.Values.ToList(); List testValues = testDictionary.Values.ToList(); // Assert foreach (var entry in controlValues) { Assert.True(testValues.Contains(entry), String.Format("Test dictionary did not contain value '{0}'.", entry)); } foreach (var entry in testValues) { Assert.True(controlValues.Contains(entry), String.Format("Control dictionary did not contain value '{0}'.", entry)); } } private void VerifyDictionaryEntriesEqual(Dictionary controlDictionary, IDictionary testDictionary) { Assert.Equal(controlDictionary.Count, testDictionary.Count); Dictionary clonedControlDictionary = new Dictionary(controlDictionary, controlDictionary.Comparer); foreach (var entry in testDictionary) { var key = entry.Key; Assert.True(clonedControlDictionary.ContainsKey(entry.Key), String.Format("Control dictionary did not contain key '{0}'.", key)); clonedControlDictionary.Remove(key); } foreach (var entry in clonedControlDictionary) { var key = entry.Key; Assert.True(false, String.Format("Test dictionary did not contain key '{0}'.", key)); } } } } }