1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System.Collections.Generic; 6 using System.Diagnostics; 7 using System.Linq; 8 using System.Reflection; 9 using Xunit; 10 11 namespace System.Collections.Immutable.Tests 12 { 13 public class ImmutableDictionaryBuilderTest : ImmutableDictionaryBuilderTestBase 14 { 15 [Fact] CreateBuilder()16 public void CreateBuilder() 17 { 18 var builder = ImmutableDictionary.CreateBuilder<string, string>(); 19 Assert.Same(EqualityComparer<string>.Default, builder.KeyComparer); 20 Assert.Same(EqualityComparer<string>.Default, builder.ValueComparer); 21 22 builder = ImmutableDictionary.CreateBuilder<string, string>(StringComparer.Ordinal); 23 Assert.Same(StringComparer.Ordinal, builder.KeyComparer); 24 Assert.Same(EqualityComparer<string>.Default, builder.ValueComparer); 25 26 builder = ImmutableDictionary.CreateBuilder<string, string>(StringComparer.Ordinal, StringComparer.OrdinalIgnoreCase); 27 Assert.Same(StringComparer.Ordinal, builder.KeyComparer); 28 Assert.Same(StringComparer.OrdinalIgnoreCase, builder.ValueComparer); 29 } 30 31 [Fact] ToBuilder()32 public void ToBuilder() 33 { 34 var builder = ImmutableDictionary<int, string>.Empty.ToBuilder(); 35 builder.Add(3, "3"); 36 builder.Add(5, "5"); 37 Assert.Equal(2, builder.Count); 38 Assert.True(builder.ContainsKey(3)); 39 Assert.True(builder.ContainsKey(5)); 40 Assert.False(builder.ContainsKey(7)); 41 42 var set = builder.ToImmutable(); 43 Assert.Equal(builder.Count, set.Count); 44 builder.Add(8, "8"); 45 Assert.Equal(3, builder.Count); 46 Assert.Equal(2, set.Count); 47 Assert.True(builder.ContainsKey(8)); 48 Assert.False(set.ContainsKey(8)); 49 } 50 51 [Fact] BuilderFromMap()52 public void BuilderFromMap() 53 { 54 var set = ImmutableDictionary<int, string>.Empty.Add(1, "1"); 55 var builder = set.ToBuilder(); 56 Assert.True(builder.ContainsKey(1)); 57 builder.Add(3, "3"); 58 builder.Add(5, "5"); 59 Assert.Equal(3, builder.Count); 60 Assert.True(builder.ContainsKey(3)); 61 Assert.True(builder.ContainsKey(5)); 62 Assert.False(builder.ContainsKey(7)); 63 64 var set2 = builder.ToImmutable(); 65 Assert.Equal(builder.Count, set2.Count); 66 Assert.True(set2.ContainsKey(1)); 67 builder.Add(8, "8"); 68 Assert.Equal(4, builder.Count); 69 Assert.Equal(3, set2.Count); 70 Assert.True(builder.ContainsKey(8)); 71 72 Assert.False(set.ContainsKey(8)); 73 Assert.False(set2.ContainsKey(8)); 74 } 75 76 [Fact] SeveralChanges()77 public void SeveralChanges() 78 { 79 var mutable = ImmutableDictionary<int, string>.Empty.ToBuilder(); 80 var immutable1 = mutable.ToImmutable(); 81 Assert.Same(immutable1, mutable.ToImmutable()); // "The Immutable property getter is creating new objects without any differences." 82 83 mutable.Add(1, "a"); 84 var immutable2 = mutable.ToImmutable(); 85 Assert.NotSame(immutable1, immutable2); // "Mutating the collection did not reset the Immutable property." 86 Assert.Same(immutable2, mutable.ToImmutable()); // "The Immutable property getter is creating new objects without any differences." 87 Assert.Equal(1, immutable2.Count); 88 } 89 90 [Fact] EnumerateBuilderWhileMutating()91 public void EnumerateBuilderWhileMutating() 92 { 93 var builder = ImmutableDictionary<int, string>.Empty 94 .AddRange(Enumerable.Range(1, 10).Select(n => new KeyValuePair<int, string>(n, null))) 95 .ToBuilder(); 96 Assert.Equal( 97 Enumerable.Range(1, 10).Select(n => new KeyValuePair<int, string>(n, null)), 98 builder); 99 100 var enumerator = builder.GetEnumerator(); 101 Assert.True(enumerator.MoveNext()); 102 builder.Add(11, null); 103 104 // Verify that a new enumerator will succeed. 105 Assert.Equal( 106 Enumerable.Range(1, 11).Select(n => new KeyValuePair<int, string>(n, null)), 107 builder); 108 109 // Try enumerating further with the previous enumerable now that we've changed the collection. 110 Assert.Throws<InvalidOperationException>(() => enumerator.MoveNext()); 111 enumerator.Reset(); 112 enumerator.MoveNext(); // resetting should fix the problem. 113 114 // Verify that by obtaining a new enumerator, we can enumerate all the contents. 115 Assert.Equal( 116 Enumerable.Range(1, 11).Select(n => new KeyValuePair<int, string>(n, null)), 117 builder); 118 } 119 120 [Fact] BuilderReusesUnchangedImmutableInstances()121 public void BuilderReusesUnchangedImmutableInstances() 122 { 123 var collection = ImmutableDictionary<int, string>.Empty.Add(1, null); 124 var builder = collection.ToBuilder(); 125 Assert.Same(collection, builder.ToImmutable()); // no changes at all. 126 builder.Add(2, null); 127 128 var newImmutable = builder.ToImmutable(); 129 Assert.NotSame(collection, newImmutable); // first ToImmutable with changes should be a new instance. 130 Assert.Same(newImmutable, builder.ToImmutable()); // second ToImmutable without changes should be the same instance. 131 } 132 133 [Fact] AddRange()134 public void AddRange() 135 { 136 var builder = ImmutableDictionary.Create<string, int>().ToBuilder(); 137 builder.AddRange(new Dictionary<string, int> { { "a", 1 }, { "b", 2 } }); 138 Assert.Equal(2, builder.Count); 139 Assert.Equal(1, builder["a"]); 140 Assert.Equal(2, builder["b"]); 141 } 142 143 [Fact] RemoveRange()144 public void RemoveRange() 145 { 146 var builder = 147 ImmutableDictionary.Create<string, int>() 148 .AddRange(new Dictionary<string, int> { { "a", 1 }, { "b", 2 }, { "c", 3 } }) 149 .ToBuilder(); 150 Assert.Equal(3, builder.Count); 151 builder.RemoveRange(new[] { "a", "b" }); 152 Assert.Equal(1, builder.Count); 153 Assert.Equal(3, builder["c"]); 154 } 155 156 [Fact] Clear()157 public void Clear() 158 { 159 var builder = ImmutableDictionary.Create<string, int>().ToBuilder(); 160 builder.Add("five", 5); 161 Assert.Equal(1, builder.Count); 162 builder.Clear(); 163 Assert.Equal(0, builder.Count); 164 } 165 166 [Fact] ContainsValue()167 public void ContainsValue() 168 { 169 var map = ImmutableDictionary.Create<string, int>().Add("five", 5); 170 var builder = map.ToBuilder(); 171 Assert.True(builder.ContainsValue(5)); 172 Assert.False(builder.ContainsValue(4)); 173 } 174 175 [Fact] KeyComparer()176 public void KeyComparer() 177 { 178 var builder = ImmutableDictionary.Create<string, string>() 179 .Add("a", "1").Add("B", "1").ToBuilder(); 180 Assert.Same(EqualityComparer<string>.Default, builder.KeyComparer); 181 Assert.True(builder.ContainsKey("a")); 182 Assert.False(builder.ContainsKey("A")); 183 184 builder.KeyComparer = StringComparer.OrdinalIgnoreCase; 185 Assert.Same(StringComparer.OrdinalIgnoreCase, builder.KeyComparer); 186 Assert.Equal(2, builder.Count); 187 Assert.True(builder.ContainsKey("a")); 188 Assert.True(builder.ContainsKey("A")); 189 Assert.True(builder.ContainsKey("b")); 190 191 var set = builder.ToImmutable(); 192 Assert.Same(StringComparer.OrdinalIgnoreCase, set.KeyComparer); 193 Assert.True(set.ContainsKey("a")); 194 Assert.True(set.ContainsKey("A")); 195 Assert.True(set.ContainsKey("b")); 196 } 197 198 [Fact] KeyComparerCollisions()199 public void KeyComparerCollisions() 200 { 201 // First check where collisions have matching values. 202 var builder = ImmutableDictionary.Create<string, string>() 203 .Add("a", "1").Add("A", "1").ToBuilder(); 204 builder.KeyComparer = StringComparer.OrdinalIgnoreCase; 205 Assert.Equal(1, builder.Count); 206 Assert.True(builder.ContainsKey("a")); 207 208 var set = builder.ToImmutable(); 209 Assert.Same(StringComparer.OrdinalIgnoreCase, set.KeyComparer); 210 Assert.Equal(1, set.Count); 211 Assert.True(set.ContainsKey("a")); 212 213 // Now check where collisions have conflicting values. 214 builder = ImmutableDictionary.Create<string, string>() 215 .Add("a", "1").Add("A", "2").Add("b", "3").ToBuilder(); 216 AssertExtensions.Throws<ArgumentException>(null, () => builder.KeyComparer = StringComparer.OrdinalIgnoreCase); 217 218 // Force all values to be considered equal. 219 builder.ValueComparer = EverythingEqual<string>.Default; 220 Assert.Same(EverythingEqual<string>.Default, builder.ValueComparer); 221 builder.KeyComparer = StringComparer.OrdinalIgnoreCase; // should not throw because values will be seen as equal. 222 Assert.Equal(2, builder.Count); 223 Assert.True(builder.ContainsKey("a")); 224 Assert.True(builder.ContainsKey("b")); 225 } 226 227 [Fact] KeyComparerEmptyCollection()228 public void KeyComparerEmptyCollection() 229 { 230 var builder = ImmutableDictionary.Create<string, string>() 231 .Add("a", "1").Add("B", "1").ToBuilder(); 232 Assert.Same(EqualityComparer<string>.Default, builder.KeyComparer); 233 builder.KeyComparer = StringComparer.OrdinalIgnoreCase; 234 Assert.Same(StringComparer.OrdinalIgnoreCase, builder.KeyComparer); 235 var set = builder.ToImmutable(); 236 Assert.Same(StringComparer.OrdinalIgnoreCase, set.KeyComparer); 237 } 238 239 [Fact] GetValueOrDefaultOfConcreteType()240 public void GetValueOrDefaultOfConcreteType() 241 { 242 var empty = ImmutableDictionary.Create<string, int>().ToBuilder(); 243 var populated = ImmutableDictionary.Create<string, int>().Add("a", 5).ToBuilder(); 244 Assert.Equal(0, empty.GetValueOrDefault("a")); 245 Assert.Equal(1, empty.GetValueOrDefault("a", 1)); 246 Assert.Equal(5, populated.GetValueOrDefault("a")); 247 Assert.Equal(5, populated.GetValueOrDefault("a", 1)); 248 } 249 250 [Fact] 251 [SkipOnTargetFramework(TargetFrameworkMonikers.UapAot, "Cannot do DebuggerAttribute testing on UapAot: requires internal Reflection on framework types.")] DebuggerAttributesValid()252 public void DebuggerAttributesValid() 253 { 254 DebuggerAttributes.ValidateDebuggerDisplayReferences(ImmutableDictionary.CreateBuilder<string, int>()); 255 ImmutableDictionary<int, string>.Builder builder = ImmutableDictionary.CreateBuilder<int, string>(); 256 builder.Add(1, "One"); 257 builder.Add(2, "Two"); 258 DebuggerAttributeInfo info = DebuggerAttributes.ValidateDebuggerTypeProxyProperties(builder); 259 PropertyInfo itemProperty = info.Properties.Single(pr => pr.GetCustomAttribute<DebuggerBrowsableAttribute>().State == DebuggerBrowsableState.RootHidden); 260 KeyValuePair<int, string>[] items = itemProperty.GetValue(info.Instance) as KeyValuePair<int, string>[]; 261 Assert.Equal(builder, items); 262 } 263 264 [Fact] 265 [SkipOnTargetFramework(TargetFrameworkMonikers.UapAot, "Cannot do DebuggerAttribute testing on UapAot: requires internal Reflection on framework types.")] TestDebuggerAttributes_Null()266 public static void TestDebuggerAttributes_Null() 267 { 268 Type proxyType = DebuggerAttributes.GetProxyType(ImmutableHashSet.Create<string>()); 269 TargetInvocationException tie = Assert.Throws<TargetInvocationException>(() => Activator.CreateInstance(proxyType, (object)null)); 270 Assert.IsType<ArgumentNullException>(tie.InnerException); 271 } 272 GetEmptyImmutableDictionary()273 protected override IImmutableDictionary<TKey, TValue> GetEmptyImmutableDictionary<TKey, TValue>() 274 { 275 return ImmutableDictionary.Create<TKey, TValue>(); 276 } 277 Empty(StringComparer comparer)278 protected override IImmutableDictionary<string, TValue> Empty<TValue>(StringComparer comparer) 279 { 280 return ImmutableDictionary.Create<string, TValue>(comparer); 281 } 282 TryGetKeyHelper(IDictionary<TKey, TValue> dictionary, TKey equalKey, out TKey actualKey)283 protected override bool TryGetKeyHelper<TKey, TValue>(IDictionary<TKey, TValue> dictionary, TKey equalKey, out TKey actualKey) 284 { 285 return ((ImmutableDictionary<TKey, TValue>.Builder)dictionary).TryGetKey(equalKey, out actualKey); 286 } 287 GetBuilder(IImmutableDictionary<TKey, TValue> basis)288 protected override IDictionary<TKey, TValue> GetBuilder<TKey, TValue>(IImmutableDictionary<TKey, TValue> basis) 289 { 290 return ((ImmutableDictionary<TKey, TValue>)(basis ?? GetEmptyImmutableDictionary<TKey, TValue>())).ToBuilder(); 291 } 292 } 293 } 294