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 System.Runtime.CompilerServices;
10 using Xunit;
11 
12 namespace System.Collections.Immutable.Tests
13 {
14     public class ImmutableListBuilderTest : ImmutableListTestBase
15     {
16         [Fact]
CreateBuilder()17         public void CreateBuilder()
18         {
19             ImmutableList<string>.Builder builder = ImmutableList.CreateBuilder<string>();
20             Assert.NotNull(builder);
21         }
22 
23         [Fact]
ToBuilder()24         public void ToBuilder()
25         {
26             var builder = ImmutableList<int>.Empty.ToBuilder();
27             builder.Add(3);
28             builder.Add(5);
29             builder.Add(5);
30             Assert.Equal(3, builder.Count);
31             Assert.True(builder.Contains(3));
32             Assert.True(builder.Contains(5));
33             Assert.False(builder.Contains(7));
34 
35             var list = builder.ToImmutable();
36             Assert.Equal(builder.Count, list.Count);
37             builder.Add(8);
38             Assert.Equal(4, builder.Count);
39             Assert.Equal(3, list.Count);
40             Assert.True(builder.Contains(8));
41             Assert.False(list.Contains(8));
42         }
43 
44         [Fact]
BuilderFromList()45         public void BuilderFromList()
46         {
47             var list = ImmutableList<int>.Empty.Add(1);
48             var builder = list.ToBuilder();
49             Assert.True(builder.Contains(1));
50             builder.Add(3);
51             builder.Add(5);
52             builder.Add(5);
53             Assert.Equal(4, builder.Count);
54             Assert.True(builder.Contains(3));
55             Assert.True(builder.Contains(5));
56             Assert.False(builder.Contains(7));
57 
58             var list2 = builder.ToImmutable();
59             Assert.Equal(builder.Count, list2.Count);
60             Assert.True(list2.Contains(1));
61             builder.Add(8);
62             Assert.Equal(5, builder.Count);
63             Assert.Equal(4, list2.Count);
64             Assert.True(builder.Contains(8));
65 
66             Assert.False(list.Contains(8));
67             Assert.False(list2.Contains(8));
68         }
69 
70         [Fact]
SeveralChanges()71         public void SeveralChanges()
72         {
73             var mutable = ImmutableList<int>.Empty.ToBuilder();
74             var immutable1 = mutable.ToImmutable();
75             Assert.Same(immutable1, mutable.ToImmutable()); //, "The Immutable property getter is creating new objects without any differences.");
76 
77             mutable.Add(1);
78             var immutable2 = mutable.ToImmutable();
79             Assert.NotSame(immutable1, immutable2); //, "Mutating the collection did not reset the Immutable property.");
80             Assert.Same(immutable2, mutable.ToImmutable()); //, "The Immutable property getter is creating new objects without any differences.");
81             Assert.Equal(1, immutable2.Count);
82         }
83 
84         [Fact]
EnumerateBuilderWhileMutating()85         public void EnumerateBuilderWhileMutating()
86         {
87             var builder = ImmutableList<int>.Empty.AddRange(Enumerable.Range(1, 10)).ToBuilder();
88             Assert.Equal(Enumerable.Range(1, 10), builder);
89 
90             var enumerator = builder.GetEnumerator();
91             Assert.True(enumerator.MoveNext());
92             builder.Add(11);
93 
94             // Verify that a new enumerator will succeed.
95             Assert.Equal(Enumerable.Range(1, 11), builder);
96 
97             // Try enumerating further with the previous enumerable now that we've changed the collection.
98             Assert.Throws<InvalidOperationException>(() => enumerator.MoveNext());
99             enumerator.Reset();
100             enumerator.MoveNext(); // resetting should fix the problem.
101 
102             // Verify that by obtaining a new enumerator, we can enumerate all the contents.
103             Assert.Equal(Enumerable.Range(1, 11), builder);
104         }
105 
106         [Fact]
BuilderReusesUnchangedImmutableInstances()107         public void BuilderReusesUnchangedImmutableInstances()
108         {
109             var collection = ImmutableList<int>.Empty.Add(1);
110             var builder = collection.ToBuilder();
111             Assert.Same(collection, builder.ToImmutable()); // no changes at all.
112             builder.Add(2);
113 
114             var newImmutable = builder.ToImmutable();
115             Assert.NotSame(collection, newImmutable); // first ToImmutable with changes should be a new instance.
116             Assert.Same(newImmutable, builder.ToImmutable()); // second ToImmutable without changes should be the same instance.
117         }
118 
119         [Fact]
Insert()120         public void Insert()
121         {
122             var mutable = ImmutableList<int>.Empty.ToBuilder();
123             mutable.Insert(0, 1);
124             mutable.Insert(0, 0);
125             mutable.Insert(2, 3);
126             Assert.Equal(new[] { 0, 1, 3 }, mutable);
127 
128             AssertExtensions.Throws<ArgumentOutOfRangeException>("index", () => mutable.Insert(-1, 0));
129             AssertExtensions.Throws<ArgumentOutOfRangeException>("index", () => mutable.Insert(4, 0));
130         }
131 
132         [Fact]
InsertRange()133         public void InsertRange()
134         {
135             var mutable = ImmutableList<int>.Empty.ToBuilder();
136             mutable.InsertRange(0, new[] { 1, 4, 5 });
137             Assert.Equal(new[] { 1, 4, 5 }, mutable);
138             mutable.InsertRange(1, new[] { 2, 3 });
139             Assert.Equal(new[] { 1, 2, 3, 4, 5 }, mutable);
140             mutable.InsertRange(5, new[] { 6 });
141             Assert.Equal(new[] { 1, 2, 3, 4, 5, 6 }, mutable);
142             mutable.InsertRange(5, new int[0]);
143             Assert.Equal(new[] { 1, 2, 3, 4, 5, 6 }, mutable);
144 
145             Assert.Throws<ArgumentOutOfRangeException>(() => mutable.InsertRange(-1, new int[0]));
146             Assert.Throws<ArgumentOutOfRangeException>(() => mutable.InsertRange(mutable.Count + 1, new int[0]));
147         }
148 
149         [Fact]
AddRange()150         public void AddRange()
151         {
152             var mutable = ImmutableList<int>.Empty.ToBuilder();
153             mutable.AddRange(new[] { 1, 4, 5 });
154             Assert.Equal(new[] { 1, 4, 5 }, mutable);
155             mutable.AddRange(new[] { 2, 3 });
156             Assert.Equal(new[] { 1, 4, 5, 2, 3 }, mutable);
157             mutable.AddRange(new int[0]);
158             Assert.Equal(new[] { 1, 4, 5, 2, 3 }, mutable);
159 
160             AssertExtensions.Throws<ArgumentNullException>("items", () => mutable.AddRange(null));
161         }
162 
163         [Fact]
Remove()164         public void Remove()
165         {
166             var mutable = ImmutableList<int>.Empty.ToBuilder();
167             Assert.False(mutable.Remove(5));
168 
169             mutable.Add(1);
170             mutable.Add(2);
171             mutable.Add(3);
172             Assert.True(mutable.Remove(2));
173             Assert.Equal(new[] { 1, 3 }, mutable);
174             Assert.True(mutable.Remove(1));
175             Assert.Equal(new[] { 3 }, mutable);
176             Assert.True(mutable.Remove(3));
177             Assert.Equal(new int[0], mutable);
178 
179             Assert.False(mutable.Remove(5));
180         }
181 
182         [Fact]
RemoveAllBugTest()183         public void RemoveAllBugTest()
184         {
185             var builder = ImmutableList.CreateBuilder<int>();
186             var elemsToRemove = new[]{0, 1, 2, 3, 4, 5}.ToImmutableHashSet();
187             // NOTE: this uses Add instead of AddRange because AddRange doesn't exhibit the same issue due to a different order of tree building.  Don't change it without testing with the bug repro from issue #20609
188             foreach(var elem in new[]{0, 1, 2, 3, 4, 5, 6})
189                 builder.Add(elem);
190             builder.RemoveAll(elemsToRemove.Contains);
191             Assert.Equal(new[]{ 6 }, builder);
192         }
193 
194         [Fact]
RemoveAt()195         public void RemoveAt()
196         {
197             var mutable = ImmutableList<int>.Empty.ToBuilder();
198             mutable.Add(1);
199             mutable.Add(2);
200             mutable.Add(3);
201             mutable.RemoveAt(2);
202             Assert.Equal(new[] { 1, 2 }, mutable);
203             mutable.RemoveAt(0);
204             Assert.Equal(new[] { 2 }, mutable);
205 
206             AssertExtensions.Throws<ArgumentOutOfRangeException>("index", () => mutable.RemoveAt(1));
207 
208             mutable.RemoveAt(0);
209             Assert.Equal(new int[0], mutable);
210 
211             AssertExtensions.Throws<ArgumentOutOfRangeException>("index", () => mutable.RemoveAt(0));
212             AssertExtensions.Throws<ArgumentOutOfRangeException>("index", () => mutable.RemoveAt(-1));
213             AssertExtensions.Throws<ArgumentOutOfRangeException>("index", () => mutable.RemoveAt(1));
214         }
215 
216         [Fact]
Reverse()217         public void Reverse()
218         {
219             var mutable = ImmutableList.CreateRange(Enumerable.Range(1, 3)).ToBuilder();
220             mutable.Reverse();
221             Assert.Equal(Enumerable.Range(1, 3).Reverse(), mutable);
222         }
223 
224         [Fact]
Clear()225         public void Clear()
226         {
227             var mutable = ImmutableList.CreateRange(Enumerable.Range(1, 3)).ToBuilder();
228             mutable.Clear();
229             Assert.Equal(0, mutable.Count);
230 
231             // Do it again for good measure. :)
232             mutable.Clear();
233             Assert.Equal(0, mutable.Count);
234         }
235 
236         [Fact]
IsReadOnly()237         public void IsReadOnly()
238         {
239             ICollection<int> builder = ImmutableList.Create<int>().ToBuilder();
240             Assert.False(builder.IsReadOnly);
241         }
242 
243         [Fact]
Indexer()244         public void Indexer()
245         {
246             var mutable = ImmutableList.CreateRange(Enumerable.Range(1, 3)).ToBuilder();
247             Assert.Equal(2, mutable[1]);
248             mutable[1] = 5;
249             Assert.Equal(5, mutable[1]);
250             mutable[0] = -2;
251             mutable[2] = -3;
252             Assert.Equal(new[] { -2, 5, -3 }, mutable);
253 
254             AssertExtensions.Throws<ArgumentOutOfRangeException>("index", () => mutable[3] = 4);
255             AssertExtensions.Throws<ArgumentOutOfRangeException>("index", () => mutable[-1] = 4);
256             AssertExtensions.Throws<ArgumentOutOfRangeException>("index", () => mutable[3]);
257             AssertExtensions.Throws<ArgumentOutOfRangeException>("index", () => mutable[-1]);
258         }
259 
260         [Fact]
IndexOf()261         public void IndexOf()
262         {
263             IndexOfTests.IndexOfTest(
264                 seq => ImmutableList.CreateRange(seq).ToBuilder(),
265                 (b, v) => b.IndexOf(v),
266                 (b, v, i) => b.IndexOf(v, i),
267                 (b, v, i, c) => b.IndexOf(v, i, c),
268                 (b, v, i, c, eq) => b.IndexOf(v, i, c, eq));
269         }
270 
271         [Fact]
LastIndexOf()272         public void LastIndexOf()
273         {
274             IndexOfTests.LastIndexOfTest(
275                 seq => ImmutableList.CreateRange(seq).ToBuilder(),
276                 (b, v) => b.LastIndexOf(v),
277                 (b, v, eq) => b.LastIndexOf(v, b.Count > 0 ? b.Count - 1 : 0, b.Count, eq),
278                 (b, v, i) => b.LastIndexOf(v, i),
279                 (b, v, i, c) => b.LastIndexOf(v, i, c),
280                 (b, v, i, c, eq) => b.LastIndexOf(v, i, c, eq));
281         }
282 
283         [Fact]
GetEnumeratorExplicit()284         public void GetEnumeratorExplicit()
285         {
286             ICollection<int> builder = ImmutableList.Create<int>().ToBuilder();
287             var enumerator = builder.GetEnumerator();
288             Assert.NotNull(enumerator);
289         }
290 
291         [Fact]
IsSynchronized()292         public void IsSynchronized()
293         {
294             ICollection collection = ImmutableList.Create<int>().ToBuilder();
295             Assert.False(collection.IsSynchronized);
296         }
297 
298         [Fact]
IListMembers()299         public void IListMembers()
300         {
301             IList list = ImmutableList.Create<int>().ToBuilder();
302             Assert.False(list.IsReadOnly);
303             Assert.False(list.IsFixedSize);
304 
305             Assert.Equal(0, list.Add(5));
306             Assert.Equal(1, list.Add(8));
307             Assert.True(list.Contains(5));
308             Assert.False(list.Contains(7));
309             list.Insert(1, 6);
310             Assert.Equal(6, list[1]);
311             list.Remove(5);
312             list[0] = 9;
313             Assert.Equal(new[] { 9, 8 }, list.Cast<int>().ToArray());
314             list.Clear();
315             Assert.Equal(0, list.Count);
316         }
317 
318         [Fact]
IList_Remove_NullArgument()319         public void IList_Remove_NullArgument()
320         {
321             this.AssertIListBaseline(RemoveFunc, 1, null);
322             this.AssertIListBaseline(RemoveFunc, "item", null);
323             this.AssertIListBaseline(RemoveFunc, new int?(1), null);
324             this.AssertIListBaseline(RemoveFunc, new int?(), null);
325         }
326 
327         [Fact]
IList_Remove_ArgTypeMismatch()328         public void IList_Remove_ArgTypeMismatch()
329         {
330             this.AssertIListBaseline(RemoveFunc, "first item", new object());
331             this.AssertIListBaseline(RemoveFunc, 1, 1.0);
332 
333             this.AssertIListBaseline(RemoveFunc, new int?(1), 1);
334             this.AssertIListBaseline(RemoveFunc, new int?(1), new int?(1));
335             this.AssertIListBaseline(RemoveFunc, new int?(1), string.Empty);
336         }
337 
338         [Fact]
IList_Remove_EqualsOverride()339         public void IList_Remove_EqualsOverride()
340         {
341             this.AssertIListBaseline(RemoveFunc, new ProgrammaticEquals(v => v is string), "foo");
342             this.AssertIListBaseline(RemoveFunc, new ProgrammaticEquals(v => v is string), 3);
343         }
344 
345         [Fact]
346         [SkipOnTargetFramework(TargetFrameworkMonikers.UapAot, "Cannot do DebuggerAttribute testing on UapAot: requires internal Reflection on framework types.")]
DebuggerAttributesValid()347         public void DebuggerAttributesValid()
348         {
349             DebuggerAttributes.ValidateDebuggerDisplayReferences(ImmutableList.CreateBuilder<int>());
350             ImmutableList<string>.Builder builder = ImmutableList.CreateBuilder<string>();
351             builder.Add("One");
352             builder.Add("Two");
353             DebuggerAttributeInfo info = DebuggerAttributes.ValidateDebuggerTypeProxyProperties(builder);
354             PropertyInfo itemProperty = info.Properties.Single(pr => pr.GetCustomAttribute<DebuggerBrowsableAttribute>().State == DebuggerBrowsableState.RootHidden);
355             string[] items = itemProperty.GetValue(info.Instance) as string[];
356             Assert.Equal(builder, items);
357         }
358 
359         [Fact]
360         [SkipOnTargetFramework(TargetFrameworkMonikers.UapAot, "Cannot do DebuggerAttribute testing on UapAot: requires internal Reflection on framework types.")]
TestDebuggerAttributes_Null()361         public static void TestDebuggerAttributes_Null()
362         {
363             Type proxyType = DebuggerAttributes.GetProxyType(ImmutableList.CreateBuilder<string>());
364             TargetInvocationException tie = Assert.Throws<TargetInvocationException>(() => Activator.CreateInstance(proxyType, (object)null));
365             Assert.IsType<ArgumentNullException>(tie.InnerException);
366         }
367 
368         [Fact]
ItemRef()369         public void ItemRef()
370         {
371             var list = new[] { 1, 2, 3 }.ToImmutableList();
372             var builder = new ImmutableList<int>.Builder(list);
373 
374             ref readonly var safeRef = ref builder.ItemRef(1);
375             ref var unsafeRef = ref Unsafe.AsRef(safeRef);
376 
377             Assert.Equal(2, builder.ItemRef(1));
378 
379             unsafeRef = 4;
380 
381             Assert.Equal(4, builder.ItemRef(1));
382         }
383 
384         [Fact]
ItemRef_OutOfBounds()385         public void ItemRef_OutOfBounds()
386         {
387             var list = new[] { 1, 2, 3 }.ToImmutableList();
388             var builder = new ImmutableList<int>.Builder(list);
389 
390             Assert.Throws<ArgumentOutOfRangeException>(() => builder.ItemRef(5));
391         }
392 
GetEnumerableOf(params T[] contents)393         protected override IEnumerable<T> GetEnumerableOf<T>(params T[] contents)
394         {
395             return ImmutableList<T>.Empty.AddRange(contents).ToBuilder();
396         }
397 
RemoveAllTestHelper(ImmutableList<T> list, Predicate<T> test)398         protected override void RemoveAllTestHelper<T>(ImmutableList<T> list, Predicate<T> test)
399         {
400             var builder = list.ToBuilder();
401             var bcl = list.ToList();
402 
403             int expected = bcl.RemoveAll(test);
404             var actual = builder.RemoveAll(test);
405             Assert.Equal(expected, actual);
406             Assert.Equal<T>(bcl, builder.ToList());
407         }
408 
ReverseTestHelper(ImmutableList<T> list, int index, int count)409         protected override void ReverseTestHelper<T>(ImmutableList<T> list, int index, int count)
410         {
411             var expected = list.ToList();
412             expected.Reverse(index, count);
413             var builder = list.ToBuilder();
414             builder.Reverse(index, count);
415             Assert.Equal<T>(expected, builder.ToList());
416         }
417 
GetListQuery(ImmutableList<T> list)418         internal override IImmutableListQueries<T> GetListQuery<T>(ImmutableList<T> list)
419         {
420             return list.ToBuilder();
421         }
422 
SortTestHelper(ImmutableList<T> list)423         protected override List<T> SortTestHelper<T>(ImmutableList<T> list)
424         {
425             var builder = list.ToBuilder();
426             builder.Sort();
427             return builder.ToImmutable().ToList();
428         }
429 
SortTestHelper(ImmutableList<T> list, Comparison<T> comparison)430         protected override List<T> SortTestHelper<T>(ImmutableList<T> list, Comparison<T> comparison)
431         {
432             var builder = list.ToBuilder();
433             builder.Sort(comparison);
434             return builder.ToImmutable().ToList();
435         }
436 
SortTestHelper(ImmutableList<T> list, IComparer<T> comparer)437         protected override List<T> SortTestHelper<T>(ImmutableList<T> list, IComparer<T> comparer)
438         {
439             var builder = list.ToBuilder();
440             builder.Sort(comparer);
441             return builder.ToImmutable().ToList();
442         }
443 
SortTestHelper(ImmutableList<T> list, int index, int count, IComparer<T> comparer)444         protected override List<T> SortTestHelper<T>(ImmutableList<T> list, int index, int count, IComparer<T> comparer)
445         {
446             var builder = list.ToBuilder();
447             builder.Sort(index, count, comparer);
448             return builder.ToImmutable().ToList();
449         }
450     }
451 }
452