github.com/aristanetworks/goarista@v0.0.0-20240514173732-cca2755bbd44/path/map_test.go (about) 1 // Copyright (c) 2017 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 path 6 7 import ( 8 "errors" 9 "fmt" 10 "testing" 11 12 "github.com/aristanetworks/goarista/key" 13 "github.com/aristanetworks/goarista/test" 14 ) 15 16 func accumulator(counter map[int]int) VisitorFunc { 17 return func(val interface{}) error { 18 counter[val.(int)]++ 19 return nil 20 } 21 } 22 23 func TestIsEmpty(t *testing.T) { 24 m := Map{} 25 26 if !m.IsEmpty() { 27 t.Errorf("Expected IsEmpty() to return true; Got false") 28 } 29 30 nonWildcardPath := key.Path{key.New("foo")} 31 wildcardPath := key.Path{Wildcard, key.New("bar"), key.New("baz")} 32 33 m.Set(nonWildcardPath, 0) 34 if m.IsEmpty() { 35 t.Errorf("Expected IsEmpty() to return false; Got true") 36 } 37 38 m.Set(wildcardPath, 2) 39 if m.IsEmpty() { 40 t.Errorf("Expected IsEmpty() to return false; Got true") 41 } 42 43 m.Delete(nonWildcardPath) 44 if m.IsEmpty() { 45 t.Errorf("Expected IsEmpty() to return false; Got true") 46 } 47 48 m.Delete(wildcardPath) 49 if !m.IsEmpty() { 50 t.Errorf("Expected IsEmpty() to return true; Got false") 51 } 52 53 m.Set(nil, nil) 54 if m.IsEmpty() { 55 t.Errorf("Expected IsEmpty() to return false; Got true") 56 } 57 58 m.Delete(nil) 59 if !m.IsEmpty() { 60 t.Errorf("Expected IsEmpty() to return true; Got false") 61 } 62 } 63 64 func TestMapSet(t *testing.T) { 65 m := Map{} 66 a := m.Set(key.Path{key.New("foo")}, 0) 67 b := m.Set(key.Path{key.New("foo")}, 1) 68 if !a || b { 69 t.Fatal("Map.Set not working properly") 70 } 71 } 72 73 func TestMapVisit(t *testing.T) { 74 m := Map{} 75 m.Set(key.Path{key.New("foo"), key.New("bar"), key.New("baz")}, 1) 76 m.Set(key.Path{Wildcard, key.New("bar"), key.New("baz")}, 2) 77 m.Set(key.Path{Wildcard, Wildcard, key.New("baz")}, 3) 78 m.Set(key.Path{Wildcard, Wildcard, Wildcard}, 4) 79 m.Set(key.Path{key.New("foo"), Wildcard, Wildcard}, 5) 80 m.Set(key.Path{key.New("foo"), key.New("bar"), Wildcard}, 6) 81 m.Set(key.Path{key.New("foo"), Wildcard, key.New("baz")}, 7) 82 m.Set(key.Path{Wildcard, key.New("bar"), Wildcard}, 8) 83 84 m.Set(key.Path{}, 10) 85 86 m.Set(key.Path{Wildcard}, 20) 87 m.Set(key.Path{key.New("foo")}, 21) 88 89 m.Set(key.Path{key.New("zap"), key.New("zip")}, 30) 90 m.Set(key.Path{key.New("zap"), key.New("zip")}, 31) 91 92 m.Set(key.Path{key.New("zip"), Wildcard}, 40) 93 m.Set(key.Path{key.New("zip"), Wildcard}, 41) 94 95 testCases := []struct { 96 path key.Path 97 expected map[int]int 98 }{{ 99 path: key.Path{key.New("foo"), key.New("bar"), key.New("baz")}, 100 expected: map[int]int{1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1}, 101 }, { 102 path: key.Path{key.New("qux"), key.New("bar"), key.New("baz")}, 103 expected: map[int]int{2: 1, 3: 1, 4: 1, 8: 1}, 104 }, { 105 path: key.Path{key.New("foo"), key.New("qux"), key.New("baz")}, 106 expected: map[int]int{3: 1, 4: 1, 5: 1, 7: 1}, 107 }, { 108 path: key.Path{key.New("foo"), key.New("bar"), key.New("qux")}, 109 expected: map[int]int{4: 1, 5: 1, 6: 1, 8: 1}, 110 }, { 111 path: key.Path{}, 112 expected: map[int]int{10: 1}, 113 }, { 114 path: key.Path{key.New("foo")}, 115 expected: map[int]int{20: 1, 21: 1}, 116 }, { 117 path: key.Path{key.New("foo"), key.New("bar")}, 118 expected: map[int]int{}, 119 }, { 120 path: key.Path{key.New("zap"), key.New("zip")}, 121 expected: map[int]int{31: 1}, 122 }, { 123 path: key.Path{key.New("zip"), key.New("zap")}, 124 expected: map[int]int{41: 1}, 125 }} 126 127 for _, tc := range testCases { 128 result := make(map[int]int, len(tc.expected)) 129 m.Visit(tc.path, accumulator(result)) 130 if diff := test.Diff(tc.expected, result); diff != "" { 131 t.Errorf("Test case %v: %s", tc.path, diff) 132 } 133 } 134 } 135 136 func TestMapVisitError(t *testing.T) { 137 m := Map{} 138 m.Set(key.Path{key.New("foo"), key.New("bar")}, 1) 139 m.Set(key.Path{Wildcard, key.New("bar")}, 2) 140 141 errTest := errors.New("Test") 142 143 err := m.Visit(key.Path{key.New("foo"), key.New("bar")}, 144 func(v interface{}) error { return errTest }) 145 if err != errTest { 146 t.Errorf("Unexpected error. Expected: %v, Got: %v", errTest, err) 147 } 148 err = m.VisitPrefixes(key.Path{key.New("foo"), key.New("bar"), key.New("baz")}, 149 func(v interface{}) error { return errTest }) 150 if err != errTest { 151 t.Errorf("Unexpected error. Expected: %v, Got: %v", errTest, err) 152 } 153 } 154 155 func TestMapGet(t *testing.T) { 156 m := Map{} 157 m.Set(key.Path{}, 0) 158 m.Set(key.Path{key.New("foo"), key.New("bar")}, 1) 159 m.Set(key.Path{key.New("foo"), Wildcard}, 2) 160 m.Set(key.Path{Wildcard, key.New("bar")}, 3) 161 m.Set(key.Path{key.New("zap"), key.New("zip")}, 4) 162 m.Set(key.Path{key.New("baz"), key.New("qux")}, nil) 163 164 testCases := []struct { 165 path key.Path 166 v interface{} 167 ok bool 168 }{{ 169 path: nil, 170 v: 0, 171 ok: true, 172 }, { 173 path: key.Path{key.New("foo"), key.New("bar")}, 174 v: 1, 175 ok: true, 176 }, { 177 path: key.Path{key.New("foo"), Wildcard}, 178 v: 2, 179 ok: true, 180 }, { 181 path: key.Path{Wildcard, key.New("bar")}, 182 v: 3, 183 ok: true, 184 }, { 185 path: key.Path{key.New("baz"), key.New("qux")}, 186 v: nil, 187 ok: true, 188 }, { 189 path: key.Path{key.New("bar"), key.New("foo")}, 190 v: nil, 191 }, { 192 path: key.Path{key.New("zap"), Wildcard}, 193 v: nil, 194 }} 195 196 for _, tc := range testCases { 197 v, ok := m.Get(tc.path) 198 if v != tc.v || ok != tc.ok { 199 t.Errorf("Test case %v: Expected (v: %v, ok: %t), Got (v: %v, ok: %t)", 200 tc.path, tc.v, tc.ok, v, ok) 201 } 202 } 203 } 204 205 func TestMapGetLongestPrefix(t *testing.T) { 206 type testMap struct { 207 pathMap Map 208 expectedValues map[string]interface{} 209 } 210 makeMap := func(paths []string) (result testMap) { 211 result.expectedValues = make(map[string]interface{}) 212 213 nextValue := uint32(1) 214 for _, path := range paths { 215 result.pathMap.Set(FromString(path), nextValue) 216 result.expectedValues[path] = nextValue 217 nextValue++ 218 } 219 220 return 221 } 222 223 regularMap := makeMap([]string{ 224 "/", 225 "/a", 226 "/a/b", 227 "/a/b/c/d", 228 "/a/b/c/d/e", 229 "/r/s", 230 "/r/s/t", 231 "/u/v", 232 }) 233 234 noEntryAtRootMap := makeMap([]string{ 235 "/r/s", 236 "/r/s/t", 237 "/u/v", 238 }) 239 240 rootOnlyMap := makeMap([]string{"/"}) 241 242 emptyMap := makeMap(nil) 243 244 testCases := []struct { 245 name string 246 mp testMap 247 path string 248 ok bool 249 longestPath string 250 }{ 251 // The root path 252 { 253 name: "exact match, descendents, root path", 254 mp: regularMap, 255 path: "/", 256 ok: true, 257 longestPath: "/", 258 }, 259 { 260 name: "no exact match, descendents, root path", 261 mp: noEntryAtRootMap, 262 path: "/", 263 ok: false, 264 longestPath: "", 265 }, 266 { 267 name: "exact match, no descendents, root path", 268 mp: rootOnlyMap, 269 path: "/", 270 ok: true, 271 longestPath: "/", 272 }, 273 { 274 name: "no exact match, no descendents, root path", 275 mp: emptyMap, 276 path: "/", 277 ok: false, 278 longestPath: "", 279 }, 280 281 // Non-root paths when the path map has entries associated with shorter 282 // prefixes 283 { 284 name: "exact match, descendents, ancestor", 285 mp: regularMap, 286 path: "/a/b/c/d", 287 ok: true, 288 longestPath: "/a/b/c/d", 289 }, 290 { 291 name: "no exact match, descendents, ancestor", 292 mp: regularMap, 293 path: "/a/b/c", 294 ok: true, 295 longestPath: "/a/b", 296 }, 297 { 298 name: "exact match, no descendents, ancestor", 299 mp: regularMap, 300 path: "/a/b/c/d/e", 301 ok: true, 302 longestPath: "/a/b/c/d/e", 303 }, 304 // When considering divergent paths (i.e. paths p where the path map has 305 // neither an entry associated with p nor any entry associated with a 306 // descendent path of p), they may diverge from the map nodes at a node 307 // representing an entry or they may diverge from a node representing a 308 // non-entry. 309 { 310 name: "no exact match, no descendents, ancestor, stray from " + 311 "internal entry", 312 mp: regularMap, 313 path: "/a/b/f", 314 ok: true, 315 longestPath: "/a/b", 316 }, 317 { 318 name: "no exact match, no descendents, ancestor, stray two entries " + 319 "from internal entry", 320 mp: regularMap, 321 path: "/a/b/f/g", 322 ok: true, 323 longestPath: "/a/b", 324 }, 325 { 326 name: "no exact match, no descendents, ancestor, stray from leaf " + 327 "entry", 328 mp: regularMap, 329 path: "/a/b/c/d/e/f", 330 ok: true, 331 longestPath: "/a/b/c/d/e", 332 }, 333 { 334 name: "no exact match, no descendents, ancestor, stray two entries " + 335 "from leaf entry", 336 mp: regularMap, 337 path: "/a/b/c/d/e/f/g", 338 ok: true, 339 longestPath: "/a/b/c/d/e", 340 }, 341 { 342 name: "no exact match, no descendents, ancestor, stray from " + 343 "internal non-entry", 344 mp: regularMap, 345 path: "/a/b/c/f", 346 ok: true, 347 longestPath: "/a/b", 348 }, 349 { 350 name: "no exact match, no descendents, ancestor, stray two entries " + 351 "from internal non-entry", 352 mp: regularMap, 353 path: "/a/b/c/f", 354 ok: true, 355 longestPath: "/a/b", 356 }, 357 358 // Non-root paths when the path map has no entries associated with shorter 359 // prefixes except for an entry associated with the root path 360 { 361 name: "exact match, descendents, ancestor is root", 362 mp: regularMap, 363 path: "/r/s", 364 ok: true, 365 longestPath: "/r/s", 366 }, 367 { 368 name: "no exact match, descendents, ancestor is root", 369 mp: regularMap, 370 path: "/r", 371 ok: true, 372 longestPath: "/", 373 }, 374 { 375 name: "exact match, no descendents, ancestor is root", 376 mp: regularMap, 377 path: "/u/v", 378 ok: true, 379 longestPath: "/u/v", 380 }, 381 { 382 name: "no exact match, no descendents, ancestor is root", 383 mp: regularMap, 384 path: "/x/y/z", 385 ok: true, 386 longestPath: "/", 387 }, 388 389 // Non-root paths when the path map has no entries associated with shorter 390 // prefixes 391 { 392 name: "exact match, descendents, no ancestor", 393 mp: noEntryAtRootMap, 394 path: "/r/s", 395 ok: true, 396 longestPath: "/r/s", 397 }, 398 { 399 name: "no exact match, descendents, no ancestor", 400 mp: noEntryAtRootMap, 401 path: "/r", 402 ok: false, 403 longestPath: "", 404 }, 405 { 406 name: "exact match, no descendents, no ancestor", 407 mp: noEntryAtRootMap, 408 path: "/u/v", 409 ok: true, 410 longestPath: "/u/v", 411 }, 412 { 413 name: "no exact match, no descendents, no ancestor", 414 mp: noEntryAtRootMap, 415 path: "/x/y/z", 416 ok: false, 417 longestPath: "", 418 }, 419 } 420 421 for _, tc := range testCases { 422 t.Run(tc.name, func(t *testing.T) { 423 // Ensure single canonical representation of test case struct. 424 if !tc.ok && tc.longestPath != "" { 425 t.Fatalf( 426 "Test case %q expects ok == false but has a configured "+ 427 "longestPath value of %q. Please clear this.", 428 tc.name, 429 tc.longestPath, 430 ) 431 } 432 433 // Dump details of test case for easy access on failure. 434 defer func() { 435 if t.Failed() { 436 t.Logf("Failed with test case: %+v", tc) 437 } 438 }() 439 440 inputPath := FromString(tc.path) 441 442 t.Logf("Running GetLongestPrefix with %v", inputPath) 443 longestPrefix, v, ok := tc.mp.pathMap.GetLongestPrefix(inputPath) 444 445 if ok != tc.ok { 446 t.Fatalf("Unexpected ok value; expected:%v actual:%v", tc.ok, ok) 447 } 448 449 if !ok { 450 if !Equal(longestPrefix, nil) { 451 // Note: path.Equal([]key.Key{}, nil) == true. 452 t.Errorf("Unexpected non-empty longestPrefix: %v", longestPrefix) 453 } 454 if v != nil { 455 t.Errorf( 456 "Expected zero-value (nil); received unexpected value: %v", 457 v, 458 ) 459 } 460 } else { 461 expectedlongestPrefix := FromString(tc.longestPath) 462 if !Equal(longestPrefix, expectedlongestPrefix) { 463 t.Errorf( 464 "Unexpected longestPrefix; expected:%v actual:%v", 465 expectedlongestPrefix, 466 longestPrefix, 467 ) 468 } 469 470 expectedValue := tc.mp.expectedValues[tc.longestPath] 471 if v != expectedValue { 472 t.Errorf( 473 "Unexpected entry value; expected:%v actual:%v", 474 expectedValue, 475 v, 476 ) 477 } 478 } 479 }) 480 } 481 } 482 483 func countNodes(m *Map) int { 484 if m == nil { 485 return 0 486 } 487 count := 1 488 count += countNodes(m.wildcard) 489 for it := m.children.Iter(); it.Next(); { 490 count += countNodes(it.Elem()) 491 } 492 return count 493 } 494 495 func TestMapDelete(t *testing.T) { 496 m := Map{} 497 m.Set(key.Path{}, 0) 498 m.Set(key.Path{Wildcard}, 1) 499 m.Set(key.Path{key.New("foo"), key.New("bar")}, 2) 500 m.Set(key.Path{key.New("foo"), Wildcard}, 3) 501 m.Set(key.Path{key.New("foo")}, 4) 502 503 n := countNodes(&m) 504 if n != 5 { 505 t.Errorf("Initial count wrong. Expected: 5, Got: %d", n) 506 } 507 508 testCases := []struct { 509 del key.Path // key.Path to delete 510 expected bool // expected return value of Delete 511 visit key.Path // key.Path to Visit 512 before map[int]int // Expected to find items before deletion 513 after map[int]int // Expected to find items after deletion 514 count int // Count of nodes 515 }{{ 516 del: key.Path{key.New("zap")}, // A no-op Delete 517 expected: false, 518 visit: key.Path{key.New("foo"), key.New("bar")}, 519 before: map[int]int{2: 1, 3: 1}, 520 after: map[int]int{2: 1, 3: 1}, 521 count: 5, 522 }, { 523 del: key.Path{key.New("foo"), key.New("bar")}, 524 expected: true, 525 visit: key.Path{key.New("foo"), key.New("bar")}, 526 before: map[int]int{2: 1, 3: 1}, 527 after: map[int]int{3: 1}, 528 count: 4, 529 }, { 530 del: key.Path{key.New("foo")}, 531 expected: true, 532 visit: key.Path{key.New("foo")}, 533 before: map[int]int{1: 1, 4: 1}, 534 after: map[int]int{1: 1}, 535 count: 4, 536 }, { 537 del: key.Path{key.New("foo")}, 538 expected: false, 539 visit: key.Path{key.New("foo")}, 540 before: map[int]int{1: 1}, 541 after: map[int]int{1: 1}, 542 count: 4, 543 }, { 544 del: key.Path{Wildcard}, 545 expected: true, 546 visit: key.Path{key.New("foo")}, 547 before: map[int]int{1: 1}, 548 after: map[int]int{}, 549 count: 3, 550 }, { 551 del: key.Path{Wildcard}, 552 expected: false, 553 visit: key.Path{key.New("foo")}, 554 before: map[int]int{}, 555 after: map[int]int{}, 556 count: 3, 557 }, { 558 del: key.Path{key.New("foo"), Wildcard}, 559 expected: true, 560 visit: key.Path{key.New("foo"), key.New("bar")}, 561 before: map[int]int{3: 1}, 562 after: map[int]int{}, 563 count: 1, // Should have deleted "foo" and "bar" nodes 564 }, { 565 del: key.Path{}, 566 expected: true, 567 visit: key.Path{}, 568 before: map[int]int{0: 1}, 569 after: map[int]int{}, 570 count: 1, // Root node can't be deleted 571 }} 572 573 for i, tc := range testCases { 574 beforeResult := make(map[int]int, len(tc.before)) 575 m.Visit(tc.visit, accumulator(beforeResult)) 576 if diff := test.Diff(tc.before, beforeResult); diff != "" { 577 t.Errorf("Test case %d (%v): %s", i, tc.del, diff) 578 } 579 580 if got := m.Delete(tc.del); got != tc.expected { 581 t.Errorf("Test case %d (%v): Unexpected return. Expected %t, Got: %t", 582 i, tc.del, tc.expected, got) 583 } 584 585 afterResult := make(map[int]int, len(tc.after)) 586 m.Visit(tc.visit, accumulator(afterResult)) 587 if diff := test.Diff(tc.after, afterResult); diff != "" { 588 t.Errorf("Test case %d (%v): %s", i, tc.del, diff) 589 } 590 } 591 } 592 593 func TestMapVisitPrefixes(t *testing.T) { 594 m := Map{} 595 m.Set(key.Path{}, 0) 596 m.Set(key.Path{key.New("foo")}, 1) 597 m.Set(key.Path{key.New("foo"), key.New("bar")}, 2) 598 m.Set(key.Path{key.New("foo"), key.New("bar"), key.New("baz")}, 3) 599 m.Set(key.Path{key.New("foo"), key.New("bar"), key.New("baz"), key.New("quux")}, 4) 600 m.Set(key.Path{key.New("quux"), key.New("bar")}, 5) 601 m.Set(key.Path{key.New("foo"), key.New("quux")}, 6) 602 m.Set(key.Path{Wildcard}, 7) 603 m.Set(key.Path{key.New("foo"), Wildcard}, 8) 604 m.Set(key.Path{Wildcard, key.New("bar")}, 9) 605 m.Set(key.Path{Wildcard, key.New("quux")}, 10) 606 m.Set(key.Path{key.New("quux"), key.New("quux"), key.New("quux"), key.New("quux")}, 11) 607 608 testCases := []struct { 609 path key.Path 610 expected map[int]int 611 }{{ 612 path: key.Path{key.New("foo"), key.New("bar"), key.New("baz")}, 613 expected: map[int]int{0: 1, 1: 1, 2: 1, 3: 1, 7: 1, 8: 1, 9: 1}, 614 }, { 615 path: key.Path{key.New("zip"), key.New("zap")}, 616 expected: map[int]int{0: 1, 7: 1}, 617 }, { 618 path: key.Path{key.New("foo"), key.New("zap")}, 619 expected: map[int]int{0: 1, 1: 1, 8: 1, 7: 1}, 620 }, { 621 path: key.Path{key.New("quux"), key.New("quux"), key.New("quux")}, 622 expected: map[int]int{0: 1, 7: 1, 10: 1}, 623 }} 624 625 for _, tc := range testCases { 626 result := make(map[int]int, len(tc.expected)) 627 m.VisitPrefixes(tc.path, accumulator(result)) 628 if diff := test.Diff(tc.expected, result); diff != "" { 629 t.Errorf("Test case %v: %s", tc.path, diff) 630 } 631 } 632 } 633 634 func TestMapVisitPrefixed(t *testing.T) { 635 m := Map{} 636 m.Set(key.Path{}, 0) 637 m.Set(key.Path{key.New("qux")}, 1) 638 m.Set(key.Path{key.New("foo")}, 2) 639 m.Set(key.Path{key.New("foo"), key.New("qux")}, 3) 640 m.Set(key.Path{key.New("foo"), key.New("bar")}, 4) 641 m.Set(key.Path{Wildcard, key.New("bar")}, 5) 642 m.Set(key.Path{key.New("foo"), Wildcard}, 6) 643 m.Set(key.Path{key.New("qux"), key.New("foo"), key.New("bar")}, 7) 644 645 testCases := []struct { 646 in key.Path 647 out map[int]int 648 }{{ 649 in: key.Path{}, 650 out: map[int]int{0: 1, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1}, 651 }, { 652 in: key.Path{key.New("qux")}, 653 out: map[int]int{1: 1, 5: 1, 7: 1}, 654 }, { 655 in: key.Path{key.New("foo")}, 656 out: map[int]int{2: 1, 3: 1, 4: 1, 5: 1, 6: 1}, 657 }, { 658 in: key.Path{key.New("foo"), key.New("qux")}, 659 out: map[int]int{3: 1, 6: 1}, 660 }, { 661 in: key.Path{key.New("foo"), key.New("bar")}, 662 out: map[int]int{4: 1, 5: 1, 6: 1}, 663 }, { 664 in: key.Path{key.New(int64(0))}, 665 out: map[int]int{5: 1}, 666 }, { 667 in: key.Path{Wildcard}, 668 out: map[int]int{5: 1}, 669 }, { 670 in: key.Path{Wildcard, Wildcard}, 671 out: map[int]int{}, 672 }} 673 674 for _, tc := range testCases { 675 out := make(map[int]int, len(tc.out)) 676 m.VisitPrefixed(tc.in, accumulator(out)) 677 if diff := test.Diff(tc.out, out); diff != "" { 678 t.Errorf("Test case %v: %s", tc.out, diff) 679 } 680 } 681 } 682 683 func TestMapVisitChildren(t *testing.T) { 684 m := Map{} 685 m.Set(key.Path{}, 0) 686 m.Set(key.Path{key.New("foo")}, 1) 687 m.Set(key.Path{key.New("foo"), key.New("bar")}, 2) 688 m.Set(key.Path{key.New("foo"), key.New("bar"), key.New("baz")}, 3) 689 m.Set(key.Path{key.New("foo"), key.New("bar"), key.New("baz"), key.New("quux")}, 4) 690 m.Set(key.Path{key.New("quux"), key.New("bar")}, 5) 691 m.Set(key.Path{key.New("foo"), key.New("quux")}, 6) 692 m.Set(key.Path{Wildcard}, 7) 693 m.Set(key.Path{key.New("foo"), Wildcard}, 8) 694 m.Set(key.Path{Wildcard, key.New("bar")}, 9) 695 m.Set(key.Path{Wildcard, key.New("bar"), key.New("quux")}, 10) 696 m.Set(key.Path{key.New("quux"), key.New("quux"), key.New("quux"), key.New("quux")}, 11) 697 m.Set(key.Path{key.New("a"), key.New("b"), key.New("c"), key.New("d")}, 12) 698 m.Set(key.Path{key.New("a"), key.New("b")}, 13) 699 700 testCases := []struct { 701 path key.Path 702 expected map[int]int 703 }{{ 704 path: key.Path{key.New("foo"), key.New("bar"), key.New("baz")}, 705 expected: map[int]int{4: 1}, 706 }, { 707 path: key.Path{key.New("zip"), key.New("zap")}, 708 expected: map[int]int{}, 709 }, { 710 path: key.Path{key.New("foo"), key.New("bar")}, 711 expected: map[int]int{3: 1, 10: 1}, 712 }, { 713 path: key.Path{key.New("quux"), key.New("quux"), key.New("quux")}, 714 expected: map[int]int{11: 1}, 715 }, { 716 path: key.Path{key.New("a"), key.New("b")}, 717 expected: map[int]int{}, 718 }} 719 720 for _, tc := range testCases { 721 result := make(map[int]int, len(tc.expected)) 722 m.VisitChildren(tc.path, accumulator(result)) 723 if diff := test.Diff(tc.expected, result); diff != "" { 724 t.Errorf("Test case %v: %s", tc.path, diff) 725 t.Errorf("tc.expected: %#v, got %#v", tc.expected, result) 726 } 727 } 728 } 729 730 func TestMapString(t *testing.T) { 731 m := Map{} 732 m.Set(key.Path{}, 0) 733 m.Set(key.Path{key.New("foo"), key.New("bar")}, 1) 734 m.Set(key.Path{key.New("foo"), key.New("quux")}, 2) 735 m.Set(key.Path{key.New("foo"), Wildcard}, 3) 736 737 expected := `Val: 0 738 Child "foo": 739 Child "*": 740 Val: 3 741 Child "bar": 742 Val: 1 743 Child "quux": 744 Val: 2 745 ` 746 got := fmt.Sprint(&m) 747 748 if expected != got { 749 t.Errorf("Unexpected string. Expected:\n\n%s\n\nGot:\n\n%s", expected, got) 750 } 751 } 752 753 func genWords(count, wordLength int) key.Path { 754 chars := []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") 755 if count+wordLength > len(chars) { 756 panic("need more chars") 757 } 758 result := make(key.Path, count) 759 for i := 0; i < count; i++ { 760 result[i] = key.New(string(chars[i : i+wordLength])) 761 } 762 return result 763 } 764 765 func benchmarkPathMap(pathLength, pathDepth int, b *testing.B) { 766 // Push pathDepth paths, each of length pathLength 767 path := genWords(pathLength, 10) 768 words := genWords(pathDepth, 10) 769 root := &Map{} 770 m := root 771 for _, element := range path { 772 m.children = newKeyMap[any]() 773 for _, word := range words { 774 m.children.Set(word, &Map{}) 775 } 776 next, _ := m.children.Get(element) 777 m = next 778 } 779 b.ReportAllocs() 780 b.ResetTimer() 781 for i := 0; i < b.N; i++ { 782 root.Visit(path, func(v interface{}) error { return nil }) 783 } 784 } 785 786 func BenchmarkPathMap1x25(b *testing.B) { benchmarkPathMap(1, 25, b) } 787 func BenchmarkPathMap10x50(b *testing.B) { benchmarkPathMap(10, 25, b) } 788 func BenchmarkPathMap20x50(b *testing.B) { benchmarkPathMap(20, 25, b) }