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