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