github.com/aristanetworks/goarista@v0.0.0-20240514173732-cca2755bbd44/key/map_test.go (about) 1 // Copyright (c) 2019 Arista Networks, Inc. 2 // Use of this source code is governed by the Apache License 2.0 3 // that can be found in the COPYING file. 4 5 package key 6 7 import ( 8 "errors" 9 "fmt" 10 "math/rand" 11 "strings" 12 "testing" 13 ) 14 15 func (m *Map) debug() string { 16 var buf strings.Builder 17 for hash, entry := range m.custom { 18 fmt.Fprintf(&buf, "%d: ", hash) 19 first := true 20 _ = entryIter(entry, func(k, v interface{}) error { 21 if !first { 22 buf.WriteString(" -> ") 23 } 24 first = false 25 fmt.Fprintf(&buf, "{%v:%v}", k, v) 26 return nil 27 }) 28 buf.WriteByte('\n') 29 } 30 return buf.String() 31 } 32 33 func TestMapEqual(t *testing.T) { 34 tests := []struct { 35 a *Map 36 b *Map 37 result bool 38 }{{ // empty 39 a: &Map{}, 40 b: &Map{normal: map[interface{}]interface{}{}, custom: map[uint64]entry{}, length: 0}, 41 result: true, 42 }, { // length check 43 a: &Map{}, 44 b: &Map{normal: map[interface{}]interface{}{}, custom: map[uint64]entry{}, length: 1}, 45 result: false, 46 }, { // map[string]interface{} 47 a: &Map{normal: map[interface{}]interface{}{"a": 1}, length: 1}, 48 b: NewMap("a", 1), 49 result: true, 50 }, { // differing keys in normal 51 a: &Map{normal: map[interface{}]interface{}{"a": "b"}, length: 1}, 52 b: NewMap("b", "b"), 53 result: false, 54 }, { // differing values in normal 55 a: &Map{normal: map[interface{}]interface{}{"a": "b"}, length: 1}, 56 b: NewMap("a", false), 57 result: false, 58 }, { // multiple entries 59 a: &Map{normal: map[interface{}]interface{}{"a": 1, "b": true}, length: 2}, 60 b: NewMap("a", 1, "b", true), 61 result: true, 62 }, { // nested maps in values 63 a: &Map{ 64 normal: map[interface{}]interface{}{"a": map[string]interface{}{"b": 3}}, 65 length: 1}, 66 b: NewMap("a", map[string]interface{}{"b": 3}), 67 result: true, 68 }, { // differing nested maps in values 69 a: &Map{ 70 normal: map[interface{}]interface{}{"a": map[string]interface{}{"b": 3}}, 71 length: 1}, 72 b: NewMap("a", map[string]interface{}{"b": 4}), 73 result: false, 74 }, { // map with map as key 75 a: NewMap(New(map[string]interface{}{"a": 123}), "b"), 76 b: NewMap(New(map[string]interface{}{"a": 123}), "b"), 77 result: true, 78 }, { 79 a: NewMap(New(map[string]interface{}{"a": 123}), "a"), 80 b: NewMap(New(map[string]interface{}{"a": 123}), "b"), 81 result: false, 82 }, { 83 a: NewMap(New(map[string]interface{}{"a": 123}), "b"), 84 b: NewMap(New(map[string]interface{}{"b": 123}), "b"), 85 result: false, 86 }, { 87 a: NewMap(New(map[string]interface{}{"a": 1, "b": 2}), "c"), 88 b: NewMap(New(map[string]interface{}{"a": 1, "b": 2}), "c"), 89 result: true, 90 }, { // maps with keys that hash to same buckets in different order 91 a: NewMap( 92 dumbHashable{dumb: "hashable1"}, 1, 93 dumbHashable{dumb: "hashable2"}, 2, 94 dumbHashable{dumb: "hashable3"}, 3), 95 b: NewMap( 96 dumbHashable{dumb: "hashable3"}, 3, 97 dumbHashable{dumb: "hashable2"}, 2, 98 dumbHashable{dumb: "hashable1"}, 1), 99 result: true, 100 }, { // maps with map as value 101 a: &Map{normal: map[interface{}]interface{}{ 102 "foo": &Map{normal: map[interface{}]interface{}{"a": 1}, length: 1}}, length: 1}, 103 b: &Map{normal: map[interface{}]interface{}{ 104 "foo": &Map{normal: map[interface{}]interface{}{"a": 1}, length: 1}}, length: 1}, 105 result: true, 106 }} 107 108 for _, tcase := range tests { 109 if tcase.a.Equal(tcase.b) != tcase.result { 110 t.Errorf("%v and %v are not equal", tcase.a, tcase.b) 111 } 112 } 113 } 114 115 type dumbHashable struct { 116 dumb interface{} 117 } 118 119 func (d dumbHashable) Equal(other interface{}) bool { 120 if o, ok := other.(dumbHashable); ok { 121 return d.dumb == o.dumb 122 } 123 return false 124 } 125 126 func (d dumbHashable) Hash() uint64 { 127 return 1234567890 128 } 129 130 func TestMapEntry(t *testing.T) { 131 m := NewMap() 132 verifyPresent := func(k, v interface{}) { 133 t.Helper() 134 if got, ok := m.Get(k); !ok || got != v { 135 t.Errorf("Get(%v): expected %v, got %v", k, v, got) 136 } 137 } 138 verifyAbsent := func(k interface{}) { 139 t.Helper() 140 if got, ok := m.Get(k); ok { 141 t.Errorf("Get(%v): expected not found, got %v", k, got) 142 } 143 } 144 145 // create entry list 1 -> 2 -> 3 146 for i := 1; i <= 3; i++ { 147 m.Set(dumbHashable{i}, 0) 148 if m.Len() != i { 149 t.Errorf("expected len %d, got %d", i, m.Len()) 150 } 151 verifyPresent(dumbHashable{i}, 0) 152 } 153 if len(m.custom) != 1 { 154 t.Errorf("expected custom map to have 1 entry list, got %d", len(m.custom)) 155 } 156 if m.Len() != 3 { 157 t.Errorf("expected len of 3, got %d", m.Len()) 158 } 159 160 // overwrite list members 161 for i := 1; i <= 3; i++ { 162 m.Set(dumbHashable{i}, i) 163 verifyPresent(dumbHashable{i}, i) 164 } 165 if m.Len() != 3 { 166 t.Errorf("expected len of 3, got %d", m.Len()) 167 } 168 t.Log(m.debug()) 169 170 // delete nonexistant member 171 m.Del(dumbHashable{4}) 172 if m.Len() != 3 { 173 t.Errorf("expected len of 3, got %d", m.Len()) 174 } 175 176 // Check that iter works 177 i := 1 178 _ = m.Iter(func(k, v interface{}) error { 179 exp := dumbHashable{i} 180 if k != exp { 181 t.Errorf("expected key %v got %v", exp, k) 182 } 183 if v != i { 184 t.Errorf("expected val %d got %v", i, v) 185 } 186 i++ 187 return nil 188 }) 189 190 // delete middle of list 191 m.Del(dumbHashable{2}) 192 verifyPresent(dumbHashable{1}, 1) 193 verifyAbsent(dumbHashable{2}) 194 verifyPresent(dumbHashable{3}, 3) 195 if m.Len() != 2 { 196 t.Errorf("expected len of 2, got %d", m.Len()) 197 } 198 199 // delete end of list 200 m.Del(dumbHashable{3}) 201 verifyPresent(dumbHashable{1}, 1) 202 verifyAbsent(dumbHashable{3}) 203 if m.Len() != 1 { 204 t.Errorf("expected len of 1, got %d", m.Len()) 205 } 206 207 m.Set(dumbHashable{2}, 2) 208 // delete head of list with next member 209 m.Del(dumbHashable{1}) 210 verifyAbsent(dumbHashable{1}) 211 verifyPresent(dumbHashable{2}, 2) 212 if m.Len() != 1 { 213 t.Errorf("expected len of 1, got %d", m.Len()) 214 } 215 216 // delete final list member 217 m.Del(dumbHashable{2}) 218 verifyAbsent(dumbHashable{2}) 219 if m.Len() != 0 { 220 t.Errorf("expected len of 0, got %d", m.Len()) 221 } 222 223 if len(m.custom) != 0 { 224 t.Errorf("expected m.custom to be empty, but got len %d", len(m.custom)) 225 } 226 } 227 228 func TestMapSetGet(t *testing.T) { 229 m := Map{} 230 tests := []struct { 231 setkey interface{} 232 getkey interface{} 233 val interface{} 234 found bool 235 }{{ 236 setkey: "a", 237 getkey: "a", 238 val: 1, 239 found: true, 240 }, { 241 setkey: "b", 242 getkey: "b", 243 val: 1, 244 found: true, 245 }, { 246 setkey: 42, 247 getkey: 42, 248 val: "foobar", 249 found: true, 250 }, { 251 setkey: dumbHashable{dumb: "hashable1"}, 252 getkey: dumbHashable{dumb: "hashable1"}, 253 val: 1, 254 found: true, 255 }, { 256 getkey: dumbHashable{dumb: "hashable2"}, 257 val: nil, 258 found: false, 259 }, { 260 setkey: dumbHashable{dumb: "hashable2"}, 261 getkey: dumbHashable{dumb: "hashable2"}, 262 val: 2, 263 found: true, 264 }, { 265 getkey: dumbHashable{dumb: "hashable42"}, 266 val: nil, 267 found: false, 268 }, { 269 setkey: New(map[string]interface{}{"a": 1}), 270 getkey: New(map[string]interface{}{"a": 1}), 271 val: "foo", 272 found: true, 273 }, { 274 getkey: New(map[string]interface{}{"a": 2}), 275 val: nil, 276 found: false, 277 }, { 278 setkey: New(map[string]interface{}{"a": 2}), 279 getkey: New(map[string]interface{}{"a": 2}), 280 val: "bar", 281 found: true, 282 }} 283 for _, tcase := range tests { 284 if tcase.setkey != nil { 285 m.Set(tcase.setkey, tcase.val) 286 } 287 val, found := m.Get(tcase.getkey) 288 if found != tcase.found { 289 t.Errorf("found is %t, but expected found %t", found, tcase.found) 290 } 291 if val != tcase.val { 292 t.Errorf("val is %v for key %v, but expected val %v", val, tcase.getkey, tcase.val) 293 } 294 } 295 296 } 297 298 func TestMapDel(t *testing.T) { 299 tests := []struct { 300 m *Map 301 del interface{} 302 exp *Map 303 }{{ 304 m: NewMap(), 305 del: "a", 306 exp: NewMap(), 307 }, { 308 m: NewMap(), 309 del: New(map[string]interface{}{"a": 1}), 310 exp: NewMap(), 311 }, { 312 m: NewMap("a", true), 313 del: "a", 314 exp: NewMap(), 315 }, { 316 m: NewMap(dumbHashable{dumb: "hashable1"}, 42), 317 del: dumbHashable{dumb: "hashable1"}, 318 exp: NewMap(), 319 }} 320 321 for _, tcase := range tests { 322 tcase.m.Del(tcase.del) 323 if !tcase.m.Equal(tcase.exp) { 324 t.Errorf("map %#v after del of element %v does not equal expected %#v", 325 tcase.m, tcase.del, tcase.exp) 326 } 327 } 328 } 329 330 func contains(elementlist []interface{}, element interface{}) bool { 331 equal := func(v interface{}) bool { return element == v } 332 if comp, ok := element.(Comparable); ok { 333 equal = func(v interface{}) bool { return comp.Equal(v) } 334 } 335 for _, el := range elementlist { 336 if equal(el) { 337 return true 338 } 339 } 340 return false 341 } 342 343 func TestMapIter(t *testing.T) { 344 tests := []struct { 345 m *Map 346 elems []interface{} 347 }{{ 348 m: NewMap(), 349 elems: []interface{}{}, 350 }, { 351 m: NewMap("a", true), 352 elems: []interface{}{"a"}, 353 }, { 354 m: NewMap(dumbHashable{dumb: "hashable1"}, 42), 355 elems: []interface{}{dumbHashable{dumb: "hashable1"}}, 356 }, { 357 m: NewMap(dumbHashable{dumb: "hashable2"}, 42, 358 dumbHashable{dumb: "hashable3"}, 42, 359 dumbHashable{dumb: "hashable4"}, 42), 360 elems: []interface{}{dumbHashable{dumb: "hashable2"}, 361 dumbHashable{dumb: "hashable3"}, dumbHashable{dumb: "hashable4"}}, 362 }, { 363 m: NewMap( 364 New(map[string]interface{}{"a": 123}), "b", 365 New(map[string]interface{}{"c": 456}), "d", 366 dumbHashable{dumb: "hashable1"}, 1, 367 dumbHashable{dumb: "hashable2"}, 2, 368 dumbHashable{dumb: "hashable3"}, 3, 369 "x", true, 370 "y", false, 371 "z", nil, 372 ), 373 elems: []interface{}{ 374 New(map[string]interface{}{"a": 123}), New(map[string]interface{}{"c": 456}), 375 dumbHashable{dumb: "hashable1"}, dumbHashable{dumb: "hashable2"}, 376 dumbHashable{dumb: "hashable3"}, "x", "y", "z"}, 377 }} 378 for _, tcase := range tests { 379 count := 0 380 iterfunc := func(k, v interface{}) error { 381 if !contains(tcase.elems, k) { 382 return fmt.Errorf("map %#v should not contain element %v", tcase.m, k) 383 } 384 count++ 385 return nil 386 } 387 if err := tcase.m.Iter(iterfunc); err != nil { 388 t.Errorf("unexpected error %v", err) 389 } 390 391 expectedcount := len(tcase.elems) 392 if count != expectedcount || tcase.m.length != expectedcount { 393 t.Errorf("found %d elements in map %#v when expected %d", count, tcase.m, expectedcount) 394 } 395 } 396 } 397 398 func TestMapIterDel(t *testing.T) { 399 // Deleting from standard go maps while iterating is safe. Since a Map contains maps, 400 // deleting from a Map while iterating is also safe. 401 m := NewMap( 402 "1", "2", 403 New("1"), "keyVal", 404 New(map[string]interface{}{"key1": "val1", "key2": 2}), "mapVal", 405 dumbHashable{dumb: "dumbkey"}, "dumbHashVal", 406 ) 407 if err := m.Iter(func(k, v interface{}) error { 408 m.Del(k) 409 if _, ok := m.Get(k); ok { 410 t.Errorf("key %v should not exist", k) 411 } 412 return nil 413 }); err != nil { 414 t.Error(err) 415 } 416 if m.Len() != 0 { 417 t.Errorf("map elements should all be deleted, but found %d elements", m.Len()) 418 } 419 } 420 421 func TestMapKeys(t *testing.T) { 422 m := NewMap( 423 "1", "2", 424 New("1"), "keyVal", 425 New(map[string]interface{}{"key1": "val1", "key2": 2}), "mapVal", 426 dumbHashable{dumb: "dumbkey"}, "dumbHashVal", 427 ) 428 if len(m.Keys()) != m.Len() { 429 t.Errorf("len(m.Keys()) %d != expected len(m) %d", len(m.Keys()), m.Len()) 430 } 431 for _, key := range m.Keys() { 432 if _, ok := m.Get(key); !ok { 433 t.Errorf("could not find key %s in map m %s", key, m) 434 } 435 } 436 } 437 438 func TestMapValues(t *testing.T) { 439 m := NewMap( 440 "1", "2", 441 New("1"), "keyVal", 442 New(map[string]interface{}{"key1": "val1", "key2": 2}), "mapVal", 443 dumbHashable{dumb: "dumbkey"}, "dumbHashVal", 444 ) 445 if len(m.Values()) != m.Len() { 446 t.Errorf("len(m.Values()) %d != expected len(m) %d", len(m.Values()), m.Len()) 447 } 448 for _, value := range m.Values() { 449 found := false 450 if err := m.Iter(func(k, v interface{}) error { 451 if v == value { 452 found = true 453 return errors.New("found") 454 } 455 return nil 456 }); err != nil { 457 if err.Error() == "found" { 458 found = true 459 } 460 } 461 if !found { 462 t.Errorf("could not find value %s in map m %s", value, m) 463 } 464 } 465 } 466 467 func TestMapString(t *testing.T) { 468 for _, tc := range []struct { 469 m *Map 470 s string 471 }{{ 472 m: NewMap(), 473 s: "key.Map[]", 474 }, { 475 m: NewMap("1", "2"), 476 s: "key.Map[1:2]", 477 }, { 478 m: NewMap( 479 "3", "4", 480 "1", "2", 481 ), 482 s: "key.Map[1:2 3:4]", 483 }, { 484 m: NewMap( 485 New(map[string]interface{}{"key1": uint32(1), "key2": uint32(2)}), "foobar", 486 New(map[string]interface{}{"key1": uint32(3), "key2": uint32(4)}), "bazquux", 487 ), 488 s: "key.Map[map[key1:1 key2:2]:foobar map[key1:3 key2:4]:bazquux]", 489 }} { 490 t.Run(tc.s, func(t *testing.T) { 491 out := tc.m.String() 492 if out != tc.s { 493 t.Errorf("expected %q got %q", tc.s, out) 494 } 495 }) 496 } 497 } 498 499 func TestMapKeyString(t *testing.T) { 500 for _, tc := range []struct { 501 m *Map 502 s string 503 }{{ 504 m: NewMap( 505 New(uint32(42)), true, 506 New("foo"), "bar", 507 New(map[string]interface{}{"hello": "world"}), "yolo", 508 New(map[string]interface{}{"key1": uint32(1), "key2": uint32(2)}), "foobar"), 509 510 s: "1_2=foobar_42=true_foo=bar_world=yolo", 511 }} { 512 t.Run(tc.s, func(t *testing.T) { 513 out := tc.m.KeyString() 514 if out != tc.s { 515 t.Errorf("expected %q got %q", tc.s, out) 516 } 517 }) 518 } 519 } 520 521 func BenchmarkMapGrow(b *testing.B) { 522 keys := make([]Key, 150) 523 for j := 0; j < len(keys); j++ { 524 keys[j] = New(map[string]interface{}{ 525 "foobar": 100, 526 "baz": j, 527 }) 528 } 529 b.Run("key.Map", func(b *testing.B) { 530 b.ReportAllocs() 531 for i := 0; i < b.N; i++ { 532 m := NewMap() 533 for j := 0; j < len(keys); j++ { 534 m.Set(keys[j], "foobar") 535 } 536 if m.Len() != len(keys) { 537 b.Fatal(m) 538 } 539 } 540 }) 541 } 542 543 func BenchmarkMapGet(b *testing.B) { 544 keys := make([]Key, 150) 545 for j := 0; j < len(keys); j++ { 546 keys[j] = New(map[string]interface{}{ 547 "foobar": 100, 548 "baz": j, 549 }) 550 } 551 keysRandomOrder := make([]Key, len(keys)) 552 copy(keysRandomOrder, keys) 553 rand.Shuffle(len(keysRandomOrder), func(i, j int) { 554 keysRandomOrder[i], keysRandomOrder[j] = keysRandomOrder[j], keysRandomOrder[i] 555 }) 556 b.Run("key.Map", func(b *testing.B) { 557 m := NewMap() 558 for j := 0; j < len(keys); j++ { 559 m.Set(keys[j], "foobar") 560 } 561 b.ReportAllocs() 562 b.ResetTimer() 563 for i := 0; i < b.N; i++ { 564 for _, k := range keysRandomOrder { 565 _, ok := m.Get(k) 566 if !ok { 567 b.Fatal("didn't find key") 568 } 569 } 570 } 571 }) 572 }