1 #region Copyright notice and license 2 // Protocol Buffers - Google's data interchange format 3 // Copyright 2015 Google Inc. All rights reserved. 4 // 5 // Use of this source code is governed by a BSD-style 6 // license that can be found in the LICENSE file or at 7 // https://developers.google.com/open-source/licenses/bsd 8 #endregion 9 10 using System; 11 using System.Collections.Generic; 12 using Google.Protobuf.TestProtos; 13 using NUnit.Framework; 14 using System.Collections; 15 using System.Linq; 16 17 namespace Google.Protobuf.Collections 18 { 19 /// <summary> 20 /// Tests for MapField which aren't reliant on the encoded format - 21 /// tests for serialization/deserialization are part of GeneratedMessageTest. 22 /// </summary> 23 public class MapFieldTest 24 { 25 [Test] Clone_ClonesMessages()26 public void Clone_ClonesMessages() 27 { 28 var message = new ForeignMessage { C = 20 }; 29 var map = new MapField<string, ForeignMessage> { { "x", message } }; 30 var clone = map.Clone(); 31 map["x"].C = 30; 32 Assert.AreEqual(20, clone["x"].C); 33 } 34 35 [Test] NullValuesProhibited()36 public void NullValuesProhibited() 37 { 38 TestNullValues<int?>(0); 39 TestNullValues(""); 40 TestNullValues(new TestAllTypes()); 41 } 42 TestNullValues(T nonNullValue)43 private void TestNullValues<T>(T nonNullValue) 44 { 45 var map = new MapField<int, T>(); 46 var nullValue = (T) (object) null; 47 Assert.Throws<ArgumentNullException>(() => map.Add(0, nullValue)); 48 Assert.Throws<ArgumentNullException>(() => map[0] = nullValue); 49 map.Add(1, nonNullValue); 50 map[1] = nonNullValue; 51 } 52 53 [Test] Add_ForbidsNullKeys()54 public void Add_ForbidsNullKeys() 55 { 56 var map = new MapField<string, ForeignMessage>(); 57 Assert.Throws<ArgumentNullException>(() => map.Add(null, new ForeignMessage())); 58 } 59 60 [Test] Indexer_ForbidsNullKeys()61 public void Indexer_ForbidsNullKeys() 62 { 63 var map = new MapField<string, ForeignMessage>(); 64 Assert.Throws<ArgumentNullException>(() => map[null] = new ForeignMessage()); 65 } 66 67 [Test] AddPreservesInsertionOrder()68 public void AddPreservesInsertionOrder() 69 { 70 var map = new MapField<string, string>(); 71 map.Add("a", "v1"); 72 map.Add("b", "v2"); 73 map.Add("c", "v3"); 74 map.Remove("b"); 75 map.Add("d", "v4"); 76 CollectionAssert.AreEqual(new[] { "a", "c", "d" }, map.Keys); 77 CollectionAssert.AreEqual(new[] { "v1", "v3", "v4" }, map.Values); 78 } 79 80 [Test] EqualityIsOrderInsensitive()81 public void EqualityIsOrderInsensitive() 82 { 83 var map1 = new MapField<string, string>(); 84 map1.Add("a", "v1"); 85 map1.Add("b", "v2"); 86 87 var map2 = new MapField<string, string>(); 88 map2.Add("b", "v2"); 89 map2.Add("a", "v1"); 90 91 EqualityTester.AssertEquality(map1, map2); 92 } 93 94 [Test] EqualityIsKeySensitive()95 public void EqualityIsKeySensitive() 96 { 97 var map1 = new MapField<string, string>(); 98 map1.Add("first key", "v1"); 99 map1.Add("second key", "v2"); 100 101 var map2 = new MapField<string, string>(); 102 map2.Add("third key", "v1"); 103 map2.Add("fourth key", "v2"); 104 105 EqualityTester.AssertInequality(map1, map2); 106 } 107 108 [Test] Equality_Simple()109 public void Equality_Simple() 110 { 111 var map = new MapField<string, string>(); 112 EqualityTester.AssertEquality(map, map); 113 EqualityTester.AssertInequality(map, null); 114 Assert.IsFalse(map.Equals(new object())); 115 } 116 117 [Test] EqualityIsValueSensitive()118 public void EqualityIsValueSensitive() 119 { 120 // Note: Without some care, it's a little easier than one might 121 // hope to see hash collisions, but only in some environments... 122 var map1 = new MapField<string, string>(); 123 map1.Add("a", "first value"); 124 map1.Add("b", "second value"); 125 126 var map2 = new MapField<string, string>(); 127 map2.Add("a", "third value"); 128 map2.Add("b", "fourth value"); 129 130 EqualityTester.AssertInequality(map1, map2); 131 } 132 133 [Test] Add_Dictionary()134 public void Add_Dictionary() 135 { 136 var map1 = new MapField<string, string> 137 { 138 { "x", "y" }, 139 { "a", "b" } 140 }; 141 var map2 = new MapField<string, string> 142 { 143 { "before", "" }, 144 map1, 145 { "after", "" } 146 }; 147 var expected = new MapField<string, string> 148 { 149 { "before", "" }, 150 { "x", "y" }, 151 { "a", "b" }, 152 { "after", "" } 153 }; 154 Assert.AreEqual(expected, map2); 155 CollectionAssert.AreEqual(new[] { "before", "x", "a", "after" }, map2.Keys); 156 } 157 158 // General IDictionary<TKey, TValue> behavior tests 159 [Test] Add_KeyAlreadyExists()160 public void Add_KeyAlreadyExists() 161 { 162 var map = new MapField<string, string>(); 163 map.Add("foo", "bar"); 164 Assert.Throws<ArgumentException>(() => map.Add("foo", "baz")); 165 } 166 167 [Test] Add_Pair()168 public void Add_Pair() 169 { 170 var map = new MapField<string, string>(); 171 ICollection<KeyValuePair<string, string>> collection = map; 172 collection.Add(NewKeyValuePair("x", "y")); 173 Assert.AreEqual("y", map["x"]); 174 Assert.Throws<ArgumentException>(() => collection.Add(NewKeyValuePair("x", "z"))); 175 } 176 177 [Test] Contains_Pair()178 public void Contains_Pair() 179 { 180 var map = new MapField<string, string> { { "x", "y" } }; 181 ICollection<KeyValuePair<string, string>> collection = map; 182 Assert.IsTrue(collection.Contains(NewKeyValuePair("x", "y"))); 183 Assert.IsFalse(collection.Contains(NewKeyValuePair("x", "z"))); 184 Assert.IsFalse(collection.Contains(NewKeyValuePair("z", "y"))); 185 } 186 187 [Test] Remove_Key()188 public void Remove_Key() 189 { 190 var map = new MapField<string, string>(); 191 map.Add("foo", "bar"); 192 Assert.AreEqual(1, map.Count); 193 Assert.IsFalse(map.Remove("missing")); 194 Assert.AreEqual(1, map.Count); 195 Assert.IsTrue(map.Remove("foo")); 196 Assert.AreEqual(0, map.Count); 197 Assert.Throws<ArgumentNullException>(() => map.Remove(null)); 198 } 199 200 [Test] Remove_Pair()201 public void Remove_Pair() 202 { 203 var map = new MapField<string, string>(); 204 map.Add("foo", "bar"); 205 ICollection<KeyValuePair<string, string>> collection = map; 206 Assert.AreEqual(1, map.Count); 207 Assert.IsFalse(collection.Remove(NewKeyValuePair("wrong key", "bar"))); 208 Assert.AreEqual(1, map.Count); 209 Assert.IsFalse(collection.Remove(NewKeyValuePair("foo", "wrong value"))); 210 Assert.AreEqual(1, map.Count); 211 Assert.IsTrue(collection.Remove(NewKeyValuePair("foo", "bar"))); 212 Assert.AreEqual(0, map.Count); 213 Assert.Throws<ArgumentException>(() => collection.Remove(new KeyValuePair<string, string>(null, ""))); 214 } 215 216 [Test] CopyTo_Pair()217 public void CopyTo_Pair() 218 { 219 var map = new MapField<string, string>(); 220 map.Add("foo", "bar"); 221 ICollection<KeyValuePair<string, string>> collection = map; 222 KeyValuePair<string, string>[] array = new KeyValuePair<string, string>[3]; 223 collection.CopyTo(array, 1); 224 Assert.AreEqual(NewKeyValuePair("foo", "bar"), array[1]); 225 } 226 227 [Test] Clear()228 public void Clear() 229 { 230 var map = new MapField<string, string> { { "x", "y" } }; 231 Assert.AreEqual(1, map.Count); 232 map.Clear(); 233 Assert.AreEqual(0, map.Count); 234 map.Add("x", "y"); 235 Assert.AreEqual(1, map.Count); 236 } 237 238 [Test] Indexer_Get()239 public void Indexer_Get() 240 { 241 var map = new MapField<string, string> { { "x", "y" } }; 242 Assert.AreEqual("y", map["x"]); 243 Assert.Throws<KeyNotFoundException>(() => { var ignored = map["z"]; }); 244 } 245 246 [Test] Indexer_Set()247 public void Indexer_Set() 248 { 249 var map = new MapField<string, string>(); 250 map["x"] = "y"; 251 Assert.AreEqual("y", map["x"]); 252 map["x"] = "z"; // This won't throw, unlike Add. 253 Assert.AreEqual("z", map["x"]); 254 } 255 256 [Test] GetEnumerator_NonGeneric()257 public void GetEnumerator_NonGeneric() 258 { 259 IEnumerable map = new MapField<string, string> { { "x", "y" } }; 260 CollectionAssert.AreEqual(new[] { new KeyValuePair<string, string>("x", "y") }, 261 map.Cast<object>().ToList()); 262 } 263 264 // Test for the explicitly-implemented non-generic IDictionary interface 265 [Test] IDictionary_GetEnumerator()266 public void IDictionary_GetEnumerator() 267 { 268 IDictionary map = new MapField<string, string> { { "x", "y" } }; 269 var enumerator = map.GetEnumerator(); 270 271 // Commented assertions show an ideal situation - it looks like 272 // the LinkedList enumerator doesn't throw when you ask for the current entry 273 // at an inappropriate time; fixing this would be more work than it's worth. 274 // Assert.Throws<InvalidOperationException>(() => enumerator.Current.GetHashCode()); 275 Assert.IsTrue(enumerator.MoveNext()); 276 Assert.AreEqual("x", enumerator.Key); 277 Assert.AreEqual("y", enumerator.Value); 278 Assert.AreEqual(new DictionaryEntry("x", "y"), enumerator.Current); 279 Assert.AreEqual(new DictionaryEntry("x", "y"), enumerator.Entry); 280 Assert.IsFalse(enumerator.MoveNext()); 281 // Assert.Throws<InvalidOperationException>(() => enumerator.Current.GetHashCode()); 282 enumerator.Reset(); 283 // Assert.Throws<InvalidOperationException>(() => enumerator.Current.GetHashCode()); 284 Assert.IsTrue(enumerator.MoveNext()); 285 Assert.AreEqual("x", enumerator.Key); // Assume the rest are okay 286 } 287 288 [Test] IDictionary_Add()289 public void IDictionary_Add() 290 { 291 var map = new MapField<string, string> { { "x", "y" } }; 292 IDictionary dictionary = map; 293 dictionary.Add("a", "b"); 294 Assert.AreEqual("b", map["a"]); 295 Assert.Throws<ArgumentException>(() => dictionary.Add("a", "duplicate")); 296 Assert.Throws<InvalidCastException>(() => dictionary.Add(new object(), "key is bad")); 297 Assert.Throws<InvalidCastException>(() => dictionary.Add("value is bad", new object())); 298 } 299 300 [Test] IDictionary_Contains()301 public void IDictionary_Contains() 302 { 303 var map = new MapField<string, string> { { "x", "y" } }; 304 IDictionary dictionary = map; 305 306 Assert.IsFalse(dictionary.Contains("a")); 307 Assert.IsFalse(dictionary.Contains(5)); 308 // Surprising, but IDictionary.Contains is only about keys. 309 Assert.IsFalse(dictionary.Contains(new DictionaryEntry("x", "y"))); 310 Assert.IsTrue(dictionary.Contains("x")); 311 } 312 313 [Test] IDictionary_Remove()314 public void IDictionary_Remove() 315 { 316 var map = new MapField<string, string> { { "x", "y" } }; 317 IDictionary dictionary = map; 318 dictionary.Remove("a"); 319 Assert.AreEqual(1, dictionary.Count); 320 dictionary.Remove(5); 321 Assert.AreEqual(1, dictionary.Count); 322 dictionary.Remove(new DictionaryEntry("x", "y")); 323 Assert.AreEqual(1, dictionary.Count); 324 dictionary.Remove("x"); 325 Assert.AreEqual(0, dictionary.Count); 326 Assert.Throws<ArgumentNullException>(() => dictionary.Remove(null)); 327 } 328 329 [Test] IDictionary_CopyTo()330 public void IDictionary_CopyTo() 331 { 332 var map = new MapField<string, string> { { "x", "y" } }; 333 IDictionary dictionary = map; 334 var array = new DictionaryEntry[3]; 335 dictionary.CopyTo(array, 1); 336 CollectionAssert.AreEqual(new[] { default(DictionaryEntry), new DictionaryEntry("x", "y"), default(DictionaryEntry) }, 337 array); 338 var objectArray = new object[3]; 339 dictionary.CopyTo(objectArray, 1); 340 CollectionAssert.AreEqual(new object[] { null, new DictionaryEntry("x", "y"), null }, 341 objectArray); 342 } 343 344 [Test] IDictionary_IsFixedSize()345 public void IDictionary_IsFixedSize() 346 { 347 var map = new MapField<string, string> { { "x", "y" } }; 348 IDictionary dictionary = map; 349 Assert.IsFalse(dictionary.IsFixedSize); 350 } 351 352 [Test] IDictionary_Keys()353 public void IDictionary_Keys() 354 { 355 IDictionary dictionary = new MapField<string, string> { { "x", "y" } }; 356 CollectionAssert.AreEqual(new[] { "x" }, dictionary.Keys); 357 } 358 359 [Test] IDictionary_Values()360 public void IDictionary_Values() 361 { 362 IDictionary dictionary = new MapField<string, string> { { "x", "y" } }; 363 CollectionAssert.AreEqual(new[] { "y" }, dictionary.Values); 364 } 365 366 [Test] IDictionary_IsSynchronized()367 public void IDictionary_IsSynchronized() 368 { 369 IDictionary dictionary = new MapField<string, string> { { "x", "y" } }; 370 Assert.IsFalse(dictionary.IsSynchronized); 371 } 372 373 [Test] IDictionary_SyncRoot()374 public void IDictionary_SyncRoot() 375 { 376 IDictionary dictionary = new MapField<string, string> { { "x", "y" } }; 377 Assert.AreSame(dictionary, dictionary.SyncRoot); 378 } 379 380 [Test] IDictionary_Indexer_Get()381 public void IDictionary_Indexer_Get() 382 { 383 IDictionary dictionary = new MapField<string, string> { { "x", "y" } }; 384 Assert.AreEqual("y", dictionary["x"]); 385 Assert.IsNull(dictionary["a"]); 386 Assert.IsNull(dictionary[5]); 387 Assert.Throws<ArgumentNullException>(() => dictionary[null].GetHashCode()); 388 } 389 390 [Test] IDictionary_Indexer_Set()391 public void IDictionary_Indexer_Set() 392 { 393 var map = new MapField<string, string> { { "x", "y" } }; 394 IDictionary dictionary = map; 395 map["a"] = "b"; 396 Assert.AreEqual("b", map["a"]); 397 map["a"] = "c"; 398 Assert.AreEqual("c", map["a"]); 399 Assert.Throws<InvalidCastException>(() => dictionary[5] = "x"); 400 Assert.Throws<InvalidCastException>(() => dictionary["x"] = 5); 401 Assert.Throws<ArgumentNullException>(() => dictionary[null] = "z"); 402 Assert.Throws<ArgumentNullException>(() => dictionary["x"] = null); 403 } 404 405 [Test] KeysReturnsLiveView()406 public void KeysReturnsLiveView() 407 { 408 var map = new MapField<string, string>(); 409 var keys = map.Keys; 410 CollectionAssert.AreEqual(new string[0], keys); 411 map["foo"] = "bar"; 412 map["x"] = "y"; 413 CollectionAssert.AreEqual(new[] { "foo", "x" }, keys); 414 } 415 416 [Test] ValuesReturnsLiveView()417 public void ValuesReturnsLiveView() 418 { 419 var map = new MapField<string, string>(); 420 var values = map.Values; 421 CollectionAssert.AreEqual(new string[0], values); 422 map["foo"] = "bar"; 423 map["x"] = "y"; 424 CollectionAssert.AreEqual(new[] { "bar", "y" }, values); 425 } 426 427 // Just test keys - we know the implementation is the same for values 428 [Test] ViewsAreReadOnly()429 public void ViewsAreReadOnly() 430 { 431 var map = new MapField<string, string>(); 432 var keys = map.Keys; 433 Assert.IsTrue(keys.IsReadOnly); 434 Assert.Throws<NotSupportedException>(() => keys.Clear()); 435 Assert.Throws<NotSupportedException>(() => keys.Remove("a")); 436 Assert.Throws<NotSupportedException>(() => keys.Add("a")); 437 } 438 439 // Just test keys - we know the implementation is the same for values 440 [Test] ViewCopyTo()441 public void ViewCopyTo() 442 { 443 var map = new MapField<string, string> { { "foo", "bar" }, { "x", "y" } }; 444 var keys = map.Keys; 445 var array = new string[4]; 446 Assert.Throws<ArgumentException>(() => keys.CopyTo(array, 3)); 447 Assert.Throws<ArgumentOutOfRangeException>(() => keys.CopyTo(array, -1)); 448 keys.CopyTo(array, 1); 449 CollectionAssert.AreEqual(new[] { null, "foo", "x", null }, array); 450 } 451 452 // Just test keys - we know the implementation is the same for values 453 [Test] NonGenericViewCopyTo()454 public void NonGenericViewCopyTo() 455 { 456 IDictionary map = new MapField<string, string> { { "foo", "bar" }, { "x", "y" } }; 457 ICollection keys = map.Keys; 458 // Note the use of the Array type here rather than string[] 459 Array array = new string[4]; 460 Assert.Throws<ArgumentException>(() => keys.CopyTo(array, 3)); 461 Assert.Throws<ArgumentOutOfRangeException>(() => keys.CopyTo(array, -1)); 462 keys.CopyTo(array, 1); 463 CollectionAssert.AreEqual(new[] { null, "foo", "x", null }, array); 464 } 465 466 [Test] KeysContains()467 public void KeysContains() 468 { 469 var map = new MapField<string, string> { { "foo", "bar" }, { "x", "y" } }; 470 var keys = map.Keys; 471 Assert.IsTrue(keys.Contains("foo")); 472 Assert.IsFalse(keys.Contains("bar")); // It's a value! 473 Assert.IsFalse(keys.Contains("1")); 474 // Keys can't be null, so we should prevent contains check 475 Assert.Throws<ArgumentNullException>(() => keys.Contains(null)); 476 } 477 478 [Test] ValuesContains()479 public void ValuesContains() 480 { 481 var map = new MapField<string, string> { { "foo", "bar" }, { "x", "y" } }; 482 var values = map.Values; 483 Assert.IsTrue(values.Contains("bar")); 484 Assert.IsFalse(values.Contains("foo")); // It's a key! 485 Assert.IsFalse(values.Contains("1")); 486 // Values can be null, so this makes sense 487 Assert.IsFalse(values.Contains(null)); 488 } 489 490 [Test] ToString_StringToString()491 public void ToString_StringToString() 492 { 493 var map = new MapField<string, string> { { "foo", "bar" }, { "x", "y" } }; 494 Assert.AreEqual("{ \"foo\": \"bar\", \"x\": \"y\" }", map.ToString()); 495 } 496 497 [Test] ToString_UnsupportedKeyType()498 public void ToString_UnsupportedKeyType() 499 { 500 var map = new MapField<byte, string> { { 10, "foo" } }; 501 Assert.Throws<ArgumentException>(() => map.ToString()); 502 } 503 NewKeyValuePair(TKey key, TValue value)504 private static KeyValuePair<TKey, TValue> NewKeyValuePair<TKey, TValue>(TKey key, TValue value) 505 { 506 return new KeyValuePair<TKey, TValue>(key, value); 507 } 508 } 509 } 510