From 96b7678c8ffe41c41e8ae7d60e28822db7aa6b6b Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Fri, 8 Apr 2016 15:12:21 -0700 Subject: [PATCH] RVD crazy --- .../Properties/AssemblyInfo.cs | 2 + .../Properties/Resources.Designer.cs | 62 + .../Resources.resx | 126 ++ .../RouteValueDictionary.cs | 568 +++++-- .../RouteValueDictionaryTests.cs | 1478 +++++++++++++++-- .../project.json | 3 +- 6 files changed, 1931 insertions(+), 308 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Routing.Abstractions/Properties/Resources.Designer.cs create mode 100644 src/Microsoft.AspNetCore.Routing.Abstractions/Resources.resx diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/Properties/AssemblyInfo.cs index 76feceeff0..b837b52475 100644 --- a/src/Microsoft.AspNetCore.Routing.Abstractions/Properties/AssemblyInfo.cs +++ b/src/Microsoft.AspNetCore.Routing.Abstractions/Properties/AssemblyInfo.cs @@ -3,9 +3,11 @@ using System.Reflection; using System.Resources; +using System.Runtime.CompilerServices; [assembly: AssemblyMetadata("Serviceable", "True")] [assembly: NeutralResourcesLanguage("en-us")] [assembly: AssemblyCompany("Microsoft Corporation.")] [assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")] [assembly: AssemblyProduct("Microsoft ASP.NET Core")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..b73bfdaea2 --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing.Abstractions/Properties/Resources.Designer.cs @@ -0,0 +1,62 @@ +// +namespace Microsoft.AspNetCore.Routing.Abstractions +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class Resources + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.AspNetCore.Routing.Abstractions.Resources", typeof(Resources).GetTypeInfo().Assembly); + + /// + /// An element with the key '{0}' already exists in the {1}. + /// + internal static string RouteValueDictionary_DuplicateKey + { + get { return GetString("RouteValueDictionary_DuplicateKey"); } + } + + /// + /// An element with the key '{0}' already exists in the {1}. + /// + internal static string FormatRouteValueDictionary_DuplicateKey(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("RouteValueDictionary_DuplicateKey"), p0, p1); + } + + /// + /// The type '{0}' defines properties '{1}' and '{2}' which differ only by casing. This is not supported by {3} which uses case-insensitive comparisons. + /// + internal static string RouteValueDictionary_DuplicatePropertyName + { + get { return GetString("RouteValueDictionary_DuplicatePropertyName"); } + } + + /// + /// The type '{0}' defines properties '{1}' and '{2}' which differ only by casing. This is not supported by {3} which uses case-insensitive comparisons. + /// + internal static string FormatRouteValueDictionary_DuplicatePropertyName(object p0, object p1, object p2, object p3) + { + return string.Format(CultureInfo.CurrentCulture, GetString("RouteValueDictionary_DuplicatePropertyName"), p0, p1, p2, p3); + } + + private static string GetString(string name, params string[] formatterNames) + { + var value = _resourceManager.GetString(name); + + System.Diagnostics.Debug.Assert(value != null); + + if (formatterNames != null) + { + for (var i = 0; i < formatterNames.Length; i++) + { + value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); + } + } + + return value; + } + } +} diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/Resources.resx b/src/Microsoft.AspNetCore.Routing.Abstractions/Resources.resx new file mode 100644 index 0000000000..40e651af14 --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing.Abstractions/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + An element with the key '{0}' already exists in the {1}. + + + The type '{0}' defines properties '{1}' and '{2}' which differ only by casing. This is not supported by {3} which uses case-insensitive comparisons. + + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Routing.Abstractions/RouteValueDictionary.cs b/src/Microsoft.AspNetCore.Routing.Abstractions/RouteValueDictionary.cs index 7e0ec2cdb8..ab1f798e95 100644 --- a/src/Microsoft.AspNetCore.Routing.Abstractions/RouteValueDictionary.cs +++ b/src/Microsoft.AspNetCore.Routing.Abstractions/RouteValueDictionary.cs @@ -3,7 +3,10 @@ using System; using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.AspNetCore.Routing.Abstractions; using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Routing @@ -13,11 +16,14 @@ namespace Microsoft.AspNetCore.Routing /// public class RouteValueDictionary : IDictionary, IReadOnlyDictionary { + internal Storage _storage; + /// /// Creates an empty . /// public RouteValueDictionary() { + _storage = EmptyStorage.Instance; } /// @@ -35,42 +41,43 @@ namespace Microsoft.AspNetCore.Routing /// public RouteValueDictionary(object values) { - var otherDictionary = values as RouteValueDictionary; - if (otherDictionary != null) + var dictionary = values as RouteValueDictionary; + if (dictionary != null) { - if (otherDictionary.InnerDictionary != null) + var listStorage = dictionary._storage as ListStorage; + if (listStorage != null) { - InnerDictionary = new Dictionary( - otherDictionary.InnerDictionary.Count, - StringComparer.OrdinalIgnoreCase); + _storage = new ListStorage(listStorage); + return; + } - foreach (var kvp in otherDictionary.InnerDictionary) - { - InnerDictionary[kvp.Key] = kvp.Value; - } + var propertyStorage = dictionary._storage as PropertyStorage; + if (propertyStorage != null) + { + // PropertyStorage is immutable so we can just copy it. + _storage = dictionary._storage; + return; + } - return; - } - else if (otherDictionary.Properties != null) - { - Properties = otherDictionary.Properties; - Value = otherDictionary.Value; - return; - } - else - { - return; - } + // If we get here, it's an EmptyStorage. + _storage = EmptyStorage.Instance; + return; } - var keyValuePairCollection = values as IEnumerable>; - if (keyValuePairCollection != null) + var keyValueEnumerable = values as IEnumerable>; + if (keyValueEnumerable != null) { - InnerDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); - - foreach (var kvp in keyValuePairCollection) + var listStorage = new ListStorage(); + _storage = listStorage; + foreach (var kvp in keyValueEnumerable) { - InnerDictionary[kvp.Key] = kvp.Value; + if (listStorage.ContainsKey(kvp.Key)) + { + var message = Resources.FormatRouteValueDictionary_DuplicateKey(kvp.Key, nameof(RouteValueDictionary)); + throw new ArgumentException(message, nameof(values)); + } + + listStorage._inner.Add(kvp); } return; @@ -78,19 +85,13 @@ namespace Microsoft.AspNetCore.Routing if (values != null) { - Properties = PropertyHelper.GetVisibleProperties(values); - Value = values; - + _storage = new PropertyStorage(values); return; } + + _storage = EmptyStorage.Instance; } - private Dictionary InnerDictionary { get; set; } - - private PropertyHelper[] Properties { get; } - - private object Value { get; } - /// public object this[string key] { @@ -113,8 +114,11 @@ namespace Microsoft.AspNetCore.Routing throw new ArgumentNullException(nameof(key)); } - EnsureWritable(); - InnerDictionary[key] = value; + if (!_storage.TrySetValue(key, value)) + { + Upgrade(); + _storage.TrySetValue(key, value); + } } } @@ -127,7 +131,7 @@ namespace Microsoft.AspNetCore.Routing public IEqualityComparer Comparer => StringComparer.OrdinalIgnoreCase; /// - public int Count => InnerDictionary?.Count ?? Properties?.Length ?? 0; + public int Count => _storage.Count; /// bool ICollection>.IsReadOnly => false; @@ -137,8 +141,16 @@ namespace Microsoft.AspNetCore.Routing { get { - EnsureWritable(); - return InnerDictionary.Keys; + Upgrade(); + + var list = ((ListStorage)_storage)._inner; + var keys = new string[list.Count]; + for (var i = 0; i < keys.Length; i++) + { + keys[i] = list[i].Key; + } + + return keys; } } @@ -146,8 +158,7 @@ namespace Microsoft.AspNetCore.Routing { get { - EnsureWritable(); - return InnerDictionary.Keys; + return Keys; } } @@ -156,8 +167,16 @@ namespace Microsoft.AspNetCore.Routing { get { - EnsureWritable(); - return InnerDictionary.Values; + Upgrade(); + + var list = ((ListStorage)_storage)._inner; + var values = new object[list.Count]; + for (var i = 0; i < values.Length; i++) + { + values[i] = list[i].Value; + } + + return values; } } @@ -165,16 +184,14 @@ namespace Microsoft.AspNetCore.Routing { get { - EnsureWritable(); - return InnerDictionary.Values; + return Values; } } /// void ICollection>.Add(KeyValuePair item) { - EnsureWritable(); - ((ICollection>)InnerDictionary).Add(item); + Add(item.Key, item.Value); } /// @@ -185,22 +202,55 @@ namespace Microsoft.AspNetCore.Routing throw new ArgumentNullException(nameof(key)); } - EnsureWritable(); - InnerDictionary.Add(key, value); + Upgrade(); + + var list = ((ListStorage)_storage)._inner; + for (var i = 0; i < list.Count; i++) + { + if (string.Equals(list[i].Key, key, StringComparison.OrdinalIgnoreCase)) + { + var message = Resources.FormatRouteValueDictionary_DuplicateKey(key, nameof(RouteValueDictionary)); + throw new ArgumentException(message, nameof(key)); + } + } + + list.Add(new KeyValuePair(key, value)); } /// public void Clear() { - EnsureWritable(); - InnerDictionary.Clear(); + if (_storage.Count == 0) + { + return; + } + + Upgrade(); + + var list = ((ListStorage)_storage)._inner; + list.Clear(); } /// bool ICollection>.Contains(KeyValuePair item) { - EnsureWritable(); - return ((ICollection>)InnerDictionary).Contains(item); + if (_storage.Count == 0) + { + return false; + } + + Upgrade(); + + var list = ((ListStorage)_storage)._inner; + for (var i = 0; i < list.Count; i++) + { + if (string.Equals(list[i].Key, item.Key, StringComparison.OrdinalIgnoreCase)) + { + return EqualityComparer.Default.Equals(list[i].Value, item.Value); + } + } + + return false; } /// @@ -211,27 +261,7 @@ namespace Microsoft.AspNetCore.Routing throw new ArgumentNullException(nameof(key)); } - if (InnerDictionary != null) - { - return InnerDictionary.ContainsKey(key); - } - else if (Properties != null) - { - for (var i = 0; i < Properties.Length; i++) - { - var property = Properties[i]; - if (Comparer.Equals(property.Name, key)) - { - return true; - } - } - - return false; - } - else - { - return false; - } + return _storage.ContainsKey(key); } /// @@ -244,8 +274,20 @@ namespace Microsoft.AspNetCore.Routing throw new ArgumentNullException(nameof(array)); } - EnsureWritable(); - ((ICollection>)InnerDictionary).CopyTo(array, arrayIndex); + if (arrayIndex < 0 || arrayIndex > array.Length || array.Length - arrayIndex < this.Count) + { + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + } + + if (_storage.Count == 0) + { + return; + } + + Upgrade(); + + var list = ((ListStorage)_storage)._inner; + list.CopyTo(array, arrayIndex); } /// @@ -269,8 +311,25 @@ namespace Microsoft.AspNetCore.Routing /// bool ICollection>.Remove(KeyValuePair item) { - EnsureWritable(); - return ((ICollection>)InnerDictionary).Remove(item); + if (_storage.Count == 0) + { + return false; + } + + Upgrade(); + + var list = ((ListStorage)_storage)._inner; + for (var i = 0; i < list.Count; i++) + { + if (string.Equals(list[i].Key, item.Key, StringComparison.OrdinalIgnoreCase) && + EqualityComparer.Default.Equals(list[i].Value, item.Value)) + { + list.RemoveAt(i); + return true; + } + } + + return false; } /// @@ -281,8 +340,24 @@ namespace Microsoft.AspNetCore.Routing throw new ArgumentNullException(nameof(key)); } - EnsureWritable(); - return InnerDictionary.Remove(key); + if (_storage.Count == 0) + { + return false; + } + + Upgrade(); + + var list = ((ListStorage)_storage)._inner; + for (var i = 0; i < list.Count; i++) + { + if (string.Equals(list[i].Key, key, StringComparison.OrdinalIgnoreCase)) + { + list.RemoveAt(i); + return true; + } + } + + return false; } /// @@ -293,56 +368,18 @@ namespace Microsoft.AspNetCore.Routing throw new ArgumentNullException(nameof(key)); } - if (InnerDictionary != null) - { - return InnerDictionary.TryGetValue(key, out value); - } - else if (Properties != null) - { - for (var i = 0; i < Properties.Length; i++) - { - var property = Properties[i]; - if (Comparer.Equals(property.Name, key)) - { - value = property.ValueGetter(Value); - return true; - } - } - - value = null; - return false; - } - else - { - value = null; - return false; - } + return _storage.TryGetValue(key, out value); } - private void EnsureWritable() + private void Upgrade() { - if (InnerDictionary == null && Properties == null) - { - InnerDictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); - } - else if (InnerDictionary == null) - { - InnerDictionary = new Dictionary(Properties.Length, StringComparer.OrdinalIgnoreCase); - - for (var i = 0; i < Properties.Length; i++) - { - var property = Properties[i]; - InnerDictionary.Add(property.Property.Name, property.ValueGetter(Value)); - } - } + _storage.Upgrade(ref _storage); } public struct Enumerator : IEnumerator> { - private readonly RouteValueDictionary _dictionary; - + private readonly Storage _storage; private int _index; - private Dictionary.Enumerator _enumerator; public Enumerator(RouteValueDictionary dictionary) { @@ -351,13 +388,10 @@ namespace Microsoft.AspNetCore.Routing throw new ArgumentNullException(); } - _dictionary = dictionary; + _storage = dictionary._storage; Current = default(KeyValuePair); _index = -1; - _enumerator = _dictionary.InnerDictionary == null ? - default(Dictionary.Enumerator) : - _dictionary.InnerDictionary.GetEnumerator(); } public KeyValuePair Current { get; private set; } @@ -370,24 +404,10 @@ namespace Microsoft.AspNetCore.Routing public bool MoveNext() { - if (_dictionary?.InnerDictionary != null) + if (++_index < _storage.Count) { - if (_enumerator.MoveNext()) - { - Current = _enumerator.Current; - return true; - } - } - else if (_dictionary?.Properties != null) - { - _index++; - if (_index < _dictionary.Properties.Length) - { - var property = _dictionary.Properties[_index]; - var value = property.ValueGetter(_dictionary.Value); - Current = new KeyValuePair(property.Name, value); - return true; - } + Current = _storage[_index]; + return true; } Current = default(KeyValuePair); @@ -398,10 +418,260 @@ namespace Microsoft.AspNetCore.Routing { Current = default(KeyValuePair); _index = -1; - _enumerator = _dictionary?.InnerDictionary == null ? - default(Dictionary.Enumerator) : - _dictionary.InnerDictionary.GetEnumerator(); } } + + // Storage and its subclasses are internal for testing. + internal abstract class Storage + { + public abstract int Count { get; } + + public abstract KeyValuePair this[int index] { get; } + + public abstract void Upgrade(ref Storage storage); + + public abstract bool TryGetValue(string key, out object value); + + public abstract bool ContainsKey(string key); + + public abstract bool TrySetValue(string key, object value); + } + + internal class ListStorage : Storage + { + internal readonly List> _inner; + + public ListStorage() + { + _inner = new List>(); + } + + public ListStorage(int capacity) + { + _inner = new List>(capacity); + } + + public ListStorage(ListStorage other) + { + // Perf: Don't call the copy constructor, that would box the enumerator. + _inner = new List>(other._inner.Capacity); + for (var i = 0; i < other._inner.Count; i++) + { + _inner.Add(other._inner[i]); + } + } + + public override int Count => _inner.Count; + + public override KeyValuePair this[int index] => _inner[index]; + + public void Clear() + { + _inner.Clear(); + } + + public override bool ContainsKey(string key) + { + for (var i = 0; i < _inner.Count; i++) + { + var kvp = _inner[i]; + if (string.Equals(key, kvp.Key, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } + + public override bool TrySetValue(string key, object value) + { + for (var i = 0; i < _inner.Count; i++) + { + var kvp = _inner[i]; + if (string.Equals(key, kvp.Key, StringComparison.OrdinalIgnoreCase)) + { + _inner[i] = new KeyValuePair(key, value); + return true; + } + } + + _inner.Add(new KeyValuePair(key, value)); + return true; + } + + public override bool TryGetValue(string key, out object value) + { + for (var i = 0; i < _inner.Count; i++) + { + var kvp = _inner[i]; + if (string.Equals(key, kvp.Key, StringComparison.OrdinalIgnoreCase)) + { + value = kvp.Value; + return true; + } + } + + value = null; + return false; + } + + public override void Upgrade(ref Storage storage) + { + // Do nothing. + } + } + + internal class PropertyStorage : Storage + { + private static readonly PropertyCache _propertyCache = new PropertyCache(); + + internal readonly object _value; + internal readonly PropertyHelper[] _properties; + + public PropertyStorage(object value) + { + Debug.Assert(value != null); + _value = value; + + // Cache the properties so we can know if we've already validated them for duplicates. + var type = _value.GetType(); + if (!_propertyCache.TryGetValue(type, out _properties)) + { + _properties = PropertyHelper.GetVisibleProperties(type); + ValidatePropertyNames(type, _properties); + _propertyCache.TryAdd(type, _properties); + } + } + + public PropertyStorage(PropertyStorage propertyStorage) + { + _value = propertyStorage._value; + _properties = propertyStorage._properties; + } + + public override int Count => _properties.Length; + + public override KeyValuePair this[int index] + { + get + { + var property = _properties[index]; + return new KeyValuePair(property.Name, property.GetValue(_value)); + } + } + + public override bool TryGetValue(string key, out object value) + { + for (var i = 0; i < _properties.Length; i++) + { + var property = _properties[i]; + if (string.Equals(key, property.Name, StringComparison.OrdinalIgnoreCase)) + { + value = property.GetValue(_value); + return true; + } + } + + value = null; + return false; + } + + public override bool ContainsKey(string key) + { + for (var i = 0; i < _properties.Length; i++) + { + var property = _properties[i]; + if (string.Equals(key, property.Name, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } + + public override bool TrySetValue(string key, object value) + { + // PropertyStorage never sets a value. + return false; + } + + public override void Upgrade(ref Storage storage) + { + storage = new ListStorage(Count); + for (var i = 0; i < _properties.Length; i++) + { + var property = _properties[i]; + storage.TrySetValue(property.Name, property.GetValue(_value)); + } + } + + private static void ValidatePropertyNames(Type type, PropertyHelper[] properties) + { + var names = new Dictionary(StringComparer.OrdinalIgnoreCase); + for (var i = 0; i < properties.Length; i++) + { + var property = properties[i]; + + PropertyHelper duplicate; + if (names.TryGetValue(property.Name, out duplicate)) + { + var message = Resources.FormatRouteValueDictionary_DuplicatePropertyName( + type.FullName, + property.Name, + duplicate.Name, + nameof(RouteValueDictionary)); + throw new InvalidOperationException(message); + } + + names.Add(property.Name, property); + } + } + } + + internal class EmptyStorage : Storage + { + public static readonly EmptyStorage Instance = new EmptyStorage(); + + private EmptyStorage() + { + } + + public override int Count => 0; + + public override KeyValuePair this[int index] + { + get + { + throw new NotImplementedException(); + } + } + + public override bool ContainsKey(string key) + { + return false; + } + + public override bool TryGetValue(string key, out object value) + { + value = null; + return false; + } + + public override bool TrySetValue(string key, object value) + { + return false; + } + + public override void Upgrade(ref Storage storage) + { + storage = new ListStorage(); + } + } + + private class PropertyCache : ConcurrentDictionary + { + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/RouteValueDictionaryTests.cs b/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/RouteValueDictionaryTests.cs index f624da6eee..593fcff191 100644 --- a/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/RouteValueDictionaryTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/RouteValueDictionaryTests.cs @@ -2,8 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Linq; using System.Collections.Generic; +using System.Linq; using Microsoft.AspNetCore.Testing; using Xunit; @@ -12,163 +12,82 @@ namespace Microsoft.AspNetCore.Routing.Tests public class RouteValueDictionaryTests { [Fact] - public void CreateEmpty_UsesOrdinalIgnoreCase() + public void DefaultCtor_UsesEmptyStorage() { // Arrange // Act var dict = new RouteValueDictionary(); // Assert - Assert.Same(StringComparer.OrdinalIgnoreCase, dict.Comparer); + Assert.Empty(dict); + Assert.IsType(dict._storage); } [Fact] - public void CreateFromDictionary_UsesOrdinalIgnoreCase() + public void CreateFromNull_UsesEmptyStorage() { // Arrange // Act - var dict = new RouteValueDictionary(new Dictionary(StringComparer.Ordinal)); + var dict = new RouteValueDictionary(null); // Assert - Assert.Same(StringComparer.OrdinalIgnoreCase, dict.Comparer); + Assert.Empty(dict); + Assert.IsType(dict._storage); } [Fact] - public void CreateFromObject_UsesOrdinalIgnoreCase() + public void CreateFromRouteValueDictionary_WithListStorage_CopiesStorage() { // Arrange - // Act - var dict = new RouteValueDictionary(new { cool = "beans" }); - - // Assert - Assert.Same(StringComparer.OrdinalIgnoreCase, dict.Comparer); - } - - [Fact] - public void CreateFromObject_CopiesPropertiesFromAnonymousType() - { - // Arrange - var obj = new { cool = "beans", awesome = 123 }; - - // Act - var dict = new RouteValueDictionary(obj); - - // Assert - Assert.Equal(2, dict.Count); - Assert.Equal("beans", dict["cool"]); - Assert.Equal(123, dict["awesome"]); - } - - [Fact] - public void CreateFromObject_CopiesPropertiesFromRegularType() - { - // Arrange - var obj = new RegularType() { CoolnessFactor = 73 }; - - // Act - var dict = new RouteValueDictionary(obj); - - // Assert - Assert.Equal(2, dict.Count); - Assert.Equal(false, dict["IsAwesome"]); - Assert.Equal(73, dict["CoolnessFactor"]); - } - - [Fact] - public void CreateFromObject_CopiesPropertiesFromRegularType_PublicOnly() - { - // Arrange - var obj = new Visibility() { IsPublic = true, ItsInternalDealWithIt = 5 }; - - // Act - var dict = new RouteValueDictionary(obj); - - // Assert - Assert.Equal(1, dict.Count); - Assert.Equal(true, dict["IsPublic"]); - } - - [Fact] - public void CreateFromObject_CopiesPropertiesFromRegularType_IgnoresStatic() - { - // Arrange - var obj = new StaticProperty(); - - // Act - var dict = new RouteValueDictionary(obj); - - // Assert - Assert.Equal(0, dict.Count); - } - - [Fact] - public void CreateFromObject_CopiesPropertiesFromRegularType_IgnoresSetOnly() - { - // Arrange - var obj = new SetterOnly() { CoolSetOnly = false }; - - // Act - var dict = new RouteValueDictionary(obj); - - // Assert - Assert.Equal(0, dict.Count); - } - - [Fact] - public void CreateFromObject_CopiesPropertiesFromRegularType_IncludesInherited() - { - // Arrange - var obj = new Derived() { TotallySweetProperty = true, DerivedProperty = false }; - - // Act - var dict = new RouteValueDictionary(obj); - - // Assert - Assert.Equal(2, dict.Count); - Assert.Equal(true, dict["TotallySweetProperty"]); - Assert.Equal(false, dict["DerivedProperty"]); - } - - [Fact] - public void CreateFromObject_CopiesPropertiesFromRegularType_WithHiddenProperty() - { - // Arrange - var obj = new DerivedHiddenProperty() { DerivedProperty = 5 }; - - // Act - var dict = new RouteValueDictionary(obj); - - // Assert - Assert.Equal(1, dict.Count); - Assert.Equal(5, dict["DerivedProperty"]); - } - - [Fact] - public void CreateFromObject_CopiesPropertiesFromRegularType_WithIndexerProperty() - { - // Arrange - var obj = new IndexerProperty(); - - // Act - var dict = new RouteValueDictionary(obj); - - // Assert - Assert.Equal(0, dict.Count); - } - - [Fact] - public void CreateFromObject_MixedCaseThrows() - { - // Arrange - var obj = new { controller = "Home", Controller = "Home" }; - - // Act & Assert - ExceptionAssert.Throws( - () => + var other = new RouteValueDictionary() { - var dictionary = new RouteValueDictionary(obj); - dictionary.Add("Hi", "There"); - }); + { "1", 1 } + }; + + // Act + var dict = new RouteValueDictionary(other); + + // Assert + Assert.Equal(other, dict); + + var storage = Assert.IsType(dict._storage); + var otherStorage = Assert.IsType(other._storage); + Assert.NotSame(otherStorage, storage); + Assert.NotSame(otherStorage._inner, storage._inner); + } + + [Fact] + public void CreateFromRouteValueDictionary_WithPropertyStorage_CopiesStorage() + { + // Arrange + var other = new RouteValueDictionary(new { key = "value" }); + + // Act + var dict = new RouteValueDictionary(other); + + // Assert + Assert.Equal(other, dict); + + var storage = Assert.IsType(dict._storage); + var otherStorage = Assert.IsType(other._storage); + Assert.Same(otherStorage, storage); + } + + [Fact] + public void CreateFromRouteValueDictionary_WithEmptyStorage_SharedInstance() + { + // Arrange + var other = new RouteValueDictionary(); + + // Act + var dict = new RouteValueDictionary(other); + + // Assert + Assert.Equal(other, dict); + + var storage = Assert.IsType(dict._storage); + var otherStorage = Assert.IsType(other._storage); + Assert.Same(otherStorage, storage); } public static IEnumerable IEnumerableKeyValuePairData @@ -192,36 +111,1279 @@ namespace Microsoft.AspNetCore.Routing.Tests [Theory] [MemberData(nameof(IEnumerableKeyValuePairData))] - public void RouteValueDictionary_CopiesValues_FromIEnumerableKeyValuePair(object values) + public void CreateFromIEnumerableKeyValuePair_CopiesValues(object values) { // Arrange & Act var dict = new RouteValueDictionary(values); // Assert - Assert.Equal(3, dict.Count); - Assert.Equal("James", dict["Name"]); - Assert.Equal(30, dict["Age"]); - var address = Assert.IsType
(dict["Address"]); - Assert.Equal("Redmond", address.City); - Assert.Equal("WA", address.State); + Assert.IsType(dict._storage); + Assert.Collection( + dict.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Equal("Address", kvp.Key); + var address = Assert.IsType
(kvp.Value); + Assert.Equal("Redmond", address.City); + Assert.Equal("WA", address.State); + }, + kvp => { Assert.Equal("Age", kvp.Key); Assert.Equal(30, kvp.Value); }, + kvp => { Assert.Equal("Name", kvp.Key); Assert.Equal("James", kvp.Value); }); } - [Theory] - [MemberData(nameof(IEnumerableKeyValuePairData))] - public void CreatedFrom_IEnumerableKeyValuePair_AllowsAddingOrModifyingValues(object values) + [Fact] + public void CreateFromIEnumerableKeyValuePair_ThrowsExceptionForDuplicateKey() { - // Arrange & Act - var routeValueDictionary = new RouteValueDictionary(values); - routeValueDictionary.Add("City", "Redmond"); + // Arrange + var values = new List>() + { + new KeyValuePair("name", "Billy"), + new KeyValuePair("Name", "Joey"), + }; + + // Act & Assert + ExceptionAssert.ThrowsArgument( + () => new RouteValueDictionary(values), + "values", + $"An element with the key 'Name' already exists in the {nameof(RouteValueDictionary)}."); + } + + [Fact] + public void CreateFromObject_CopiesPropertiesFromAnonymousType() + { + // Arrange + var obj = new { cool = "beans", awesome = 123 }; + + // Act + var dict = new RouteValueDictionary(obj); // Assert - Assert.Equal(4, routeValueDictionary.Count); - Assert.Equal("James", routeValueDictionary["Name"]); - Assert.Equal(30, routeValueDictionary["Age"]); - Assert.Equal("Redmond", routeValueDictionary["City"]); - var address = Assert.IsType
(routeValueDictionary["Address"]); - address.State = "Washington"; - Assert.Equal("Washington", ((Address)routeValueDictionary["Address"]).State); + Assert.IsType(dict._storage); + Assert.Collection( + dict.OrderBy(kvp => kvp.Key), + kvp => { Assert.Equal("awesome", kvp.Key); Assert.Equal(123, kvp.Value); }, + kvp => { Assert.Equal("cool", kvp.Key); Assert.Equal("beans", kvp.Value); }); + } + + [Fact] + public void CreateFromObject_CopiesPropertiesFromRegularType() + { + // Arrange + var obj = new RegularType() { CoolnessFactor = 73 }; + + // Act + var dict = new RouteValueDictionary(obj); + + // Assert + Assert.IsType(dict._storage); + Assert.Collection( + dict.OrderBy(kvp => kvp.Key), + kvp => { Assert.Equal("CoolnessFactor", kvp.Key); Assert.Equal(73, kvp.Value); }, + kvp => { Assert.Equal("IsAwesome", kvp.Key); Assert.Equal(false, kvp.Value); }); + } + + [Fact] + public void CreateFromObject_CopiesPropertiesFromRegularType_PublicOnly() + { + // Arrange + var obj = new Visibility() { IsPublic = true, ItsInternalDealWithIt = 5 }; + + // Act + var dict = new RouteValueDictionary(obj); + + // Assert + Assert.IsType(dict._storage); + Assert.Collection( + dict.OrderBy(kvp => kvp.Key), + kvp => { Assert.Equal("IsPublic", kvp.Key); Assert.Equal(true, kvp.Value); }); + } + + [Fact] + public void CreateFromObject_CopiesPropertiesFromRegularType_IgnoresStatic() + { + // Arrange + var obj = new StaticProperty(); + + // Act + var dict = new RouteValueDictionary(obj); + + // Assert + Assert.IsType(dict._storage); + Assert.Empty(dict); + } + + [Fact] + public void CreateFromObject_CopiesPropertiesFromRegularType_IgnoresSetOnly() + { + // Arrange + var obj = new SetterOnly() { CoolSetOnly = false }; + + // Act + var dict = new RouteValueDictionary(obj); + + // Assert + Assert.IsType(dict._storage); + Assert.Empty(dict); + } + + [Fact] + public void CreateFromObject_CopiesPropertiesFromRegularType_IncludesInherited() + { + // Arrange + var obj = new Derived() { TotallySweetProperty = true, DerivedProperty = false }; + + // Act + var dict = new RouteValueDictionary(obj); + + // Assert + Assert.IsType(dict._storage); + Assert.Collection( + dict.OrderBy(kvp => kvp.Key), + kvp => { Assert.Equal("DerivedProperty", kvp.Key); Assert.Equal(false, kvp.Value); }, + kvp => { Assert.Equal("TotallySweetProperty", kvp.Key); Assert.Equal(true, kvp.Value); }); + } + + [Fact] + public void CreateFromObject_CopiesPropertiesFromRegularType_WithHiddenProperty() + { + // Arrange + var obj = new DerivedHiddenProperty() { DerivedProperty = 5 }; + + // Act + var dict = new RouteValueDictionary(obj); + + // Assert + Assert.IsType(dict._storage); + Assert.Collection( + dict.OrderBy(kvp => kvp.Key), + kvp => { Assert.Equal("DerivedProperty", kvp.Key); Assert.Equal(5, kvp.Value); }); + } + + [Fact] + public void CreateFromObject_CopiesPropertiesFromRegularType_WithIndexerProperty() + { + // Arrange + var obj = new IndexerProperty(); + + // Act + var dict = new RouteValueDictionary(obj); + + // Assert + Assert.IsType(dict._storage); + Assert.Empty(dict); + } + + [Fact] + public void CreateFromObject_MixedCaseThrows() + { + // Arrange + var obj = new { controller = "Home", Controller = "Home" }; + + var message = + $"The type '{obj.GetType().FullName}' defines properties 'controller' and 'Controller' which differ " + + $"only by casing. This is not supported by {nameof(RouteValueDictionary)} which uses " + + $"case-insensitive comparisons."; + + // Act & Assert + var exception = Assert.Throws(() => + { + var dictionary = new RouteValueDictionary(obj); + }); + + // Ignoring case to make sure we're not testing reflection's ordering. + Assert.Equal(message, exception.Message, ignoreCase: true); + } + + // Our comparer is hardcoded to be OrdinalIgnoreCase no matter what. + [Fact] + public void Comparer_IsOrdinalIgnoreCase() + { + // Arrange + // Act + var dict = new RouteValueDictionary(); + + // Assert + Assert.Same(StringComparer.OrdinalIgnoreCase, dict.Comparer); + } + + // Our comparer is hardcoded to be IsReadOnly==false no matter what. + [Fact] + public void IsReadOnly_False() + { + // Arrange + var dict = new RouteValueDictionary(); + + // Act + var result = ((ICollection>)dict).IsReadOnly; + + // Assert + Assert.False(result); + } + + [Fact] + public void IndexGet_EmptyStorage_ReturnsNull() + { + // Arrange + var dict = new RouteValueDictionary(); + + // Act + var value = dict["key"]; + + // Assert + Assert.Null(value); + Assert.IsType(dict._storage); + } + + [Fact] + public void IndexGet_PropertyStorage_NoMatch_ReturnsNull() + { + // Arrange + var dict = new RouteValueDictionary(new { age = 30 }); + + // Act + var value = dict["key"]; + + // Assert + Assert.Null(value); + Assert.IsType(dict._storage); + } + + [Fact] + public void IndexGet_PropertyStorage_Match_ReturnsValue() + { + // Arrange + var dict = new RouteValueDictionary(new { key = "value" }); + + // Act + var value = dict["key"]; + + // Assert + Assert.Equal("value", value); + Assert.IsType(dict._storage); + } + + [Fact] + public void IndexGet_PropertyStorage_MatchIgnoreCase_ReturnsValue() + { + // Arrange + var dict = new RouteValueDictionary(new { key = "value" }); + + // Act + var value = dict["kEy"]; + + // Assert + Assert.Equal("value", value); + Assert.IsType(dict._storage); + } + + [Fact] + public void IndexGet_ListStorage_NoMatch_ReturnsNull() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "age", 30 }, + }; + + // Act + var value = dict["key"]; + + // Assert + Assert.Null(value); + Assert.IsType(dict._storage); + } + + [Fact] + public void IndexGet_ListStorage_Match_ReturnsValue() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + // Act + var value = dict["key"]; + + // Assert + Assert.Equal("value", value); + Assert.IsType(dict._storage); + } + + [Fact] + public void IndexGet_ListStorage_MatchIgnoreCase_ReturnsValue() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + // Act + var value = dict["kEy"]; + + // Assert + Assert.Equal("value", value); + Assert.IsType(dict._storage); + } + + [Fact] + public void IndexSet_EmptyStorage_UpgradesToList() + { + // Arrange + var dict = new RouteValueDictionary(); + + // Act + dict["key"] = "value"; + + // Assert + Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType(dict._storage); + } + + [Fact] + public void IndexSet_PropertyStorage_NoMatch_AddsValue() + { + // Arrange + var dict = new RouteValueDictionary(new { age = 30 }); + + // Act + dict["key"] = "value"; + + // Assert + Assert.Collection( + dict.OrderBy(kvp => kvp.Key), + kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); }, + kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType(dict._storage); + } + + [Fact] + public void IndexSet_PropertyStorage_Match_SetsValue() + { + // Arrange + var dict = new RouteValueDictionary(new { key = "value" }); + + // Act + dict["key"] = "value"; + + // Assert + Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType(dict._storage); + } + + [Fact] + public void IndexSet_PropertyStorage_MatchIgnoreCase_SetsValue() + { + // Arrange + var dict = new RouteValueDictionary(new { key = "value" }); + + // Act + dict["kEy"] = "value"; + + // Assert + Assert.Collection(dict, kvp => { Assert.Equal("kEy", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType(dict._storage); + } + + [Fact] + public void IndexSet_ListStorage_NoMatch_AddsValue() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "age", 30 }, + }; + + // Act + dict["key"] = "value"; + + // Assert + Assert.Collection( + dict.OrderBy(kvp => kvp.Key), + kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); }, + kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType(dict._storage); + } + + [Fact] + public void IndexSet_ListStorage_Match_SetsValue() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + // Act + dict["key"] = "value"; + + // Assert + Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType(dict._storage); + } + + [Fact] + public void IndexSet_ListStorage_MatchIgnoreCase_SetsValue() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + // Act + dict["key"] = "value"; + + // Assert + Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType(dict._storage); + } + + [Fact] + public void Count_EmptyStorage() + { + // Arrange + var dict = new RouteValueDictionary(); + + // Act + var count = dict.Count; + + // Assert + Assert.Equal(0, count); + Assert.IsType(dict._storage); + } + + [Fact] + public void Count_PropertyStorage() + { + // Arrange + var dict = new RouteValueDictionary(new { key = "value", }); + + // Act + var count = dict.Count; + + // Assert + Assert.Equal(1, count); + Assert.IsType(dict._storage); + } + + [Fact] + public void Count_ListStorage() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + // Act + var count = dict.Count; + + // Assert + Assert.Equal(1, count); + Assert.IsType(dict._storage); + } + + [Fact] + public void Keys_EmptyStorage() + { + // Arrange + var dict = new RouteValueDictionary(); + + // Act + var keys = dict.Keys; + + // Assert + Assert.Empty(keys); + Assert.IsType(dict._storage); + } + + [Fact] + public void Keys_PropertyStorage() + { + // Arrange + var dict = new RouteValueDictionary(new { key = "value", }); + + // Act + var keys = dict.Keys; + + // Assert + Assert.Equal(new[] { "key" }, keys); + Assert.IsType(dict._storage); + } + + [Fact] + public void Keys_ListStorage() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + // Act + var keys = dict.Keys; + + // Assert + Assert.Equal(new[] { "key" }, keys); + Assert.IsType(dict._storage); + } + + [Fact] + public void Values_EmptyStorage() + { + // Arrange + var dict = new RouteValueDictionary(); + + // Act + var values = dict.Values; + + // Assert + Assert.Empty(values); + Assert.IsType(dict._storage); + } + + [Fact] + public void Values_PropertyStorage() + { + // Arrange + var dict = new RouteValueDictionary(new { key = "value", }); + + // Act + var values = dict.Values; + + // Assert + Assert.Equal(new object[] { "value" }, values); + Assert.IsType(dict._storage); + } + + [Fact] + public void Values_ListStorage() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + // Act + var values = dict.Values; + + // Assert + Assert.Equal(new object[] { "value" }, values); + Assert.IsType(dict._storage); + } + + [Fact] + public void Add_EmptyStorage() + { + // Arrange + var dict = new RouteValueDictionary(); + + // Act + dict.Add("key", "value"); + + // Assert + Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType(dict._storage); + } + + [Fact] + public void Add_PropertyStorage() + { + // Arrange + var dict = new RouteValueDictionary(new { age = 30 }); + + // Act + dict.Add("key", "value"); + + // Assert + Assert.Collection( + dict.OrderBy(kvp => kvp.Key), + kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); }, + kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType(dict._storage); + } + + [Fact] + public void Add_ListStorage() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "age", 30 }, + }; + + // Act + dict.Add("key", "value"); + + // Assert + Assert.Collection( + dict.OrderBy(kvp => kvp.Key), + kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); }, + kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType(dict._storage); + } + + [Fact] + public void Add_DuplicateKey() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + var message = $"An element with the key 'key' already exists in the {nameof(RouteValueDictionary)}"; + + // Act & Assert + ExceptionAssert.ThrowsArgument(() => dict.Add("key", "value2"), "key", message); + + // Assert + Assert.Collection( + dict.OrderBy(kvp => kvp.Key), + kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType(dict._storage); + } + + [Fact] + public void Add_DuplicateKey_CaseInsensitive() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + var message = $"An element with the key 'kEy' already exists in the {nameof(RouteValueDictionary)}"; + + // Act & Assert + ExceptionAssert.ThrowsArgument(() => dict.Add("kEy", "value2"), "key", message); + + // Assert + Assert.Collection( + dict.OrderBy(kvp => kvp.Key), + kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType(dict._storage); + } + + [Fact] + public void Add_KeyValuePair() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "age", 30 }, + }; + + // Act + ((ICollection>)dict).Add(new KeyValuePair("key", "value")); + + // Assert + Assert.Collection( + dict.OrderBy(kvp => kvp.Key), + kvp => { Assert.Equal("age", kvp.Key); Assert.Equal(30, kvp.Value); }, + kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType(dict._storage); + } + + [Fact] + public void Clear_EmptyStorage() + { + // Arrange + var dict = new RouteValueDictionary(); + + // Act + dict.Clear(); + + // Assert + Assert.Empty(dict); + Assert.IsType(dict._storage); + } + + [Fact] + public void Clear_PropertyStorage_AlreadyEmpty() + { + // Arrange + var dict = new RouteValueDictionary(new { }); + + // Act + dict.Clear(); + + // Assert + Assert.Empty(dict); + Assert.IsType(dict._storage); + } + + [Fact] + public void Clear_PropertyStorage() + { + // Arrange + var dict = new RouteValueDictionary(new { key = "value" }); + + // Act + dict.Clear(); + + // Assert + Assert.Empty(dict); + Assert.IsType(dict._storage); + } + + [Fact] + public void Clear_ListStorage() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + // Act + dict.Clear(); + + // Assert + Assert.Empty(dict); + Assert.IsType(dict._storage); + } + + [Fact] + public void Contains_KeyValuePair_True() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + var input = new KeyValuePair("key", "value"); + + // Act + var result = ((ICollection>)dict).Contains(input); + + // Assert + Assert.True(result); + Assert.IsType(dict._storage); + } + + [Fact] + public void Contains_KeyValuePair_True_CaseInsensitive() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + var input = new KeyValuePair("KEY", "value"); + + // Act + var result = ((ICollection>)dict).Contains(input); + + // Assert + Assert.True(result); + Assert.IsType(dict._storage); + } + + [Fact] + public void Contains_KeyValuePair_False() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + var input = new KeyValuePair("other", "value"); + + // Act + var result = ((ICollection>)dict).Contains(input); + + // Assert + Assert.False(result); + Assert.IsType(dict._storage); + } + + // Value comparisons use the default equality comparer. + [Fact] + public void Contains_KeyValuePair_False_ValueComparisonIsDefault() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + var input = new KeyValuePair("key", "valUE"); + + // Act + var result = ((ICollection>)dict).Contains(input); + + // Assert + Assert.False(result); + Assert.IsType(dict._storage); + } + + [Fact] + public void ContainsKey_EmptyStorage() + { + // Arrange + var dict = new RouteValueDictionary(); + + // Act + var result = dict.ContainsKey("key"); + + // Assert + Assert.False(result); + Assert.IsType(dict._storage); + } + + [Fact] + public void ContainsKey_PropertyStorage_False() + { + // Arrange + var dict = new RouteValueDictionary(new { key = "value" }); + + // Act + var result = dict.ContainsKey("other"); + + // Assert + Assert.False(result); + Assert.IsType(dict._storage); + } + + [Fact] + public void ContainsKey_PropertyStorage_True() + { + // Arrange + var dict = new RouteValueDictionary(new { key = "value" }); + + // Act + var result = dict.ContainsKey("key"); + + // Assert + Assert.True(result); + Assert.IsType(dict._storage); + } + + [Fact] + public void ContainsKey_PropertyStorage_True_CaseInsensitive() + { + // Arrange + var dict = new RouteValueDictionary(new { key = "value" }); + + // Act + var result = dict.ContainsKey("kEy"); + + // Assert + Assert.True(result); + Assert.IsType(dict._storage); + } + + [Fact] + public void ContainsKey_ListStorage_False() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + // Act + var result = dict.ContainsKey("other"); + + // Assert + Assert.False(result); + Assert.IsType(dict._storage); + } + + [Fact] + public void ContainsKey_ListStorage_True() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + // Act + var result = dict.ContainsKey("key"); + + // Assert + Assert.True(result); + Assert.IsType(dict._storage); + } + + [Fact] + public void ContainsKey_ListStorage_True_CaseInsensitive() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + // Act + var result = dict.ContainsKey("kEy"); + + // Assert + Assert.True(result); + Assert.IsType(dict._storage); + } + + [Fact] + public void CopyTo() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + var array = new KeyValuePair[2]; + + // Act + ((ICollection>)dict).CopyTo(array, 1); + + // Assert + Assert.Equal( + new KeyValuePair[] + { + default(KeyValuePair), + new KeyValuePair("key", "value") + }, + array); + Assert.IsType(dict._storage); + } + + [Fact] + public void Remove_KeyValuePair_True() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + var input = new KeyValuePair("key", "value"); + + // Act + var result = ((ICollection>)dict).Remove(input); + + // Assert + Assert.True(result); + Assert.Empty(dict); + Assert.IsType(dict._storage); + } + + [Fact] + public void Remove_KeyValuePair_True_CaseInsensitive() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + var input = new KeyValuePair("KEY", "value"); + + // Act + var result = ((ICollection>)dict).Remove(input); + + // Assert + Assert.True(result); + Assert.Empty(dict); + Assert.IsType(dict._storage); + } + + [Fact] + public void Remove_KeyValuePair_False() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + var input = new KeyValuePair("other", "value"); + + // Act + var result = ((ICollection>)dict).Remove(input); + + // Assert + Assert.False(result); + Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType(dict._storage); + } + + // Value comparisons use the default equality comparer. + [Fact] + public void Remove_KeyValuePair_False_ValueComparisonIsDefault() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + var input = new KeyValuePair("key", "valUE"); + + // Act + var result = ((ICollection>)dict).Remove(input); + + // Assert + Assert.False(result); + Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType(dict._storage); + } + + [Fact] + public void Remove_EmptyStorage() + { + // Arrange + var dict = new RouteValueDictionary(); + + // Act + var result = dict.Remove("key"); + + // Assert + Assert.False(result); + Assert.IsType(dict._storage); + } + + [Fact] + public void Remove_PropertyStorage_Empty() + { + // Arrange + var dict = new RouteValueDictionary(new { }); + + // Act + var result = dict.Remove("other"); + + // Assert + Assert.False(result); + Assert.Empty(dict); + Assert.IsType(dict._storage); + } + + [Fact] + public void Remove_PropertyStorage_False() + { + // Arrange + var dict = new RouteValueDictionary(new { key = "value" }); + + // Act + var result = dict.Remove("other"); + + // Assert + Assert.False(result); + Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType(dict._storage); + } + + [Fact] + public void Remove_PropertyStorage_True() + { + // Arrange + var dict = new RouteValueDictionary(new { key = "value" }); + + // Act + var result = dict.Remove("key"); + + // Assert + Assert.True(result); + Assert.Empty(dict); + Assert.IsType(dict._storage); + } + + [Fact] + public void Remove_PropertyStorage_True_CaseInsensitive() + { + // Arrange + var dict = new RouteValueDictionary(new { key = "value" }); + + // Act + var result = dict.Remove("kEy"); + + // Assert + Assert.True(result); + Assert.Empty(dict); + Assert.IsType(dict._storage); + } + + [Fact] + public void Remove_ListStorage_False() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + // Act + var result = dict.Remove("other"); + + // Assert + Assert.False(result); + Assert.Collection(dict, kvp => { Assert.Equal("key", kvp.Key); Assert.Equal("value", kvp.Value); }); + Assert.IsType(dict._storage); + } + + [Fact] + public void Remove_ListStorage_True() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + // Act + var result = dict.Remove("key"); + + // Assert + Assert.True(result); + Assert.Empty(dict); + Assert.IsType(dict._storage); + } + + [Fact] + public void Remove_ListStorage_True_CaseInsensitive() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + // Act + var result = dict.Remove("kEy"); + + // Assert + Assert.True(result); + Assert.Empty(dict); + Assert.IsType(dict._storage); + } + + [Fact] + public void TryGetValue_EmptyStorage() + { + // Arrange + var dict = new RouteValueDictionary(); + + // Act + object value; + var result = dict.TryGetValue("key", out value); + + // Assert + Assert.False(result); + Assert.Null(value); + Assert.IsType(dict._storage); + } + + [Fact] + public void TryGetValue_PropertyStorage_False() + { + // Arrange + var dict = new RouteValueDictionary(new { key = "value" }); + + // Act + object value; + var result = dict.TryGetValue("other", out value); + + // Assert + Assert.False(result); + Assert.Null(value); + Assert.IsType(dict._storage); + } + + [Fact] + public void TryGetValue_PropertyStorage_True() + { + // Arrange + var dict = new RouteValueDictionary(new { key = "value" }); + + // Act + object value; + var result = dict.TryGetValue("key", out value); + + // Assert + Assert.True(result); + Assert.Equal("value", value); + Assert.IsType(dict._storage); + } + + [Fact] + public void TryGetValue_PropertyStorage_True_CaseInsensitive() + { + // Arrange + var dict = new RouteValueDictionary(new { key = "value" }); + + // Act + object value; + var result = dict.TryGetValue("kEy", out value); + + // Assert + Assert.True(result); + Assert.Equal("value", value); + Assert.IsType(dict._storage); + } + + [Fact] + public void TryGetValue_ListStorage_False() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + // Act + object value; + var result = dict.TryGetValue("other", out value); + + // Assert + Assert.False(result); + Assert.Null(value); + Assert.IsType(dict._storage); + } + + [Fact] + public void TryGetValue_ListStorage_True() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + // Act + object value; + var result = dict.TryGetValue("key", out value); + + // Assert + Assert.True(result); + Assert.Equal("value", value); + Assert.IsType(dict._storage); + } + + [Fact] + public void TryGetValue_ListStorage_True_CaseInsensitive() + { + // Arrange + var dict = new RouteValueDictionary() + { + { "key", "value" }, + }; + + // Act + object value; + var result = dict.TryGetValue("kEy", out value); + + // Assert + Assert.True(result); + Assert.Equal("value", value); + Assert.IsType(dict._storage); } private class RegularType diff --git a/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/project.json b/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/project.json index 79a926c680..50cf3b172e 100644 --- a/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/project.json +++ b/test/Microsoft.AspNetCore.Mvc.Routing.Abstractions.Tests/project.json @@ -1,6 +1,7 @@ { "compilationOptions": { - "warningsAsErrors": true + "warningsAsErrors": true, + "keyFile": "../../tools/Key.snk" }, "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1-*",