github.com/cilium/cilium@v1.16.2/pkg/container/bitlpm/unsigned_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package bitlpm 5 6 import ( 7 "fmt" 8 "math/bits" 9 "reflect" 10 "sort" 11 "strconv" 12 "strings" 13 "testing" 14 ) 15 16 type uint16Range struct { 17 start, end uint16 18 } 19 20 func (pr uint16Range) prefix() uint { 21 return prefixFromRange(pr.start, max(pr.end, pr.start)) 22 } 23 24 func prefixFromRange(start, end uint16) uint { 25 return 16 - uint(bits.TrailingZeros16(^uint16(end-start))) 26 } 27 28 func (pr uint16Range) String() string { 29 return fmt.Sprintf("%d-%d", pr.start, pr.end) 30 } 31 32 var uint16RangeMap = map[uint]uint16{ 33 0: 0b1111_1111_1111_1111, 34 1: 0b111_1111_1111_1111, 35 2: 0b11_1111_1111_1111, 36 3: 0b1_1111_1111_1111, 37 4: 0b1111_1111_1111, 38 5: 0b111_1111_1111, 39 6: 0b11_1111_1111, 40 7: 0b1_1111_1111, 41 8: 0b1111_1111, 42 9: 0b111_1111, 43 10: 0b11_1111, 44 11: 0b1_1111, 45 12: 0b1111, 46 13: 0b111, 47 14: 0b11, 48 15: 0b1, 49 16: 0, 50 } 51 52 func endFromPrefix(prefix uint, start uint16) uint16 { 53 return start + uint16RangeMap[prefix] 54 } 55 56 var ( 57 uint16Range65535 = []uint16Range{ 58 {start: 65535, end: 65535}, 59 } 60 uint16Range0_65535 = []uint16Range{ 61 {start: 0, end: 65535}, 62 } 63 uint16Range1_65534 = []uint16Range{ 64 {start: 1, end: 1}, 65 {start: 2, end: 3}, 66 {start: 4, end: 7}, 67 {start: 8, end: 15}, 68 {start: 16, end: 31}, 69 {start: 32, end: 63}, 70 {start: 64, end: 127}, 71 {start: 128, end: 255}, 72 {start: 256, end: 511}, 73 {start: 512, end: 1023}, 74 {start: 1024, end: 2047}, 75 {start: 2048, end: 4095}, 76 {start: 4096, end: 8191}, 77 {start: 8192, end: 16383}, 78 {start: 16384, end: 32767}, 79 {start: 32768, end: 49151}, 80 {start: 49152, end: 57343}, 81 {start: 57344, end: 61439}, 82 {start: 61440, end: 63487}, 83 {start: 63488, end: 64511}, 84 {start: 64512, end: 65023}, 85 {start: 65024, end: 65279}, 86 {start: 65280, end: 65407}, 87 {start: 65408, end: 65471}, 88 {start: 65472, end: 65503}, 89 {start: 65504, end: 65519}, 90 {start: 65520, end: 65527}, 91 {start: 65528, end: 65531}, 92 {start: 65532, end: 65533}, 93 {start: 65534, end: 65534}, 94 } 95 uint16Range0_1023 = []uint16Range{ 96 {start: 0, end: 1023}, 97 } 98 uint16Range1_1023 = []uint16Range{ 99 {start: 1, end: 1}, 100 {start: 2, end: 3}, 101 {start: 4, end: 7}, 102 {start: 8, end: 15}, 103 {start: 16, end: 31}, 104 {start: 32, end: 63}, 105 {start: 64, end: 127}, 106 {start: 128, end: 255}, 107 {start: 256, end: 511}, 108 {start: 512, end: 1023}, 109 } 110 uint16Range0_7 = []uint16Range{ 111 {start: 0, end: 7}, 112 } 113 uint16Range1_7 = []uint16Range{ 114 {start: 1, end: 1}, 115 {start: 2, end: 3}, 116 {start: 4, end: 7}, 117 } 118 uint16Range0_1 = []uint16Range{ 119 {start: 0, end: 1}, 120 } 121 uint16Range1_1 = []uint16Range{ 122 {start: 1, end: 1}, 123 } 124 ) 125 126 // TestUnsignedUpsert tests to see that a trie contains 127 // all the values it should after every update. 128 func TestUnsignedUpsert(t *testing.T) { 129 tests := []struct { 130 name string 131 ranges []uint16Range 132 }{ 133 { 134 ranges: uint16Range65535, 135 }, 136 { 137 name: " least entries for largest range", 138 ranges: uint16Range0_65535, 139 }, 140 { 141 name: " most entries for largest range", 142 ranges: uint16Range1_65534, 143 }, 144 { 145 ranges: uint16Range0_1023, 146 }, 147 { 148 ranges: uint16Range1_1023, 149 }, 150 { 151 ranges: uint16Range0_7, 152 }, 153 { 154 ranges: uint16Range1_7, 155 }, 156 { 157 ranges: uint16Range0_1, 158 }, 159 { 160 ranges: uint16Range1_1, 161 }, 162 } 163 for _, tt := range tests { 164 name := fmt.Sprintf("%d_%d%s", tt.ranges[0].start, 165 tt.ranges[len(tt.ranges)-1].end, tt.name) 166 // Check that the whole trie is what it should be 167 // on each update. 168 t.Run(name, func(t *testing.T) { 169 ut := NewUintTrie[uint16, string]() 170 for i, pr := range tt.ranges { 171 ut.Upsert(pr.prefix(), pr.start, fmt.Sprintf("%d-%d", pr.start, pr.end)) 172 var got []uint16Range 173 ut.ForEach(func(prefix uint, key uint16, value string) bool { 174 got = append(got, uint16Range{start: key, end: endFromPrefix(prefix, key)}) 175 return true 176 }) 177 sort.Slice(got, func(i, j int) bool { 178 return got[i].start < got[j].start 179 }) 180 if !reflect.DeepEqual(got, tt.ranges[:i+1]) { 181 t.Fatalf("When updating an unsigned trie with the key-prefix %d/%d: got %+v, but expected %+v", pr.start, pr.prefix(), got, tt.ranges[:i+1]) 182 } 183 } 184 }) 185 } 186 } 187 188 // TestUnsignedExactLookup looks up every entry expressed 189 // in a trie structure to ensure that exact lookup only returns 190 // on when keys match exactly. 191 func TestUnsignedExactLookup(t *testing.T) { 192 tests := []struct { 193 name string 194 ranges []uint16Range 195 }{ 196 { 197 ranges: uint16Range65535, 198 }, 199 { 200 name: " least entries for largest range", 201 ranges: uint16Range0_65535, 202 }, 203 { 204 name: " most entries for largest range", 205 ranges: uint16Range1_65534, 206 }, 207 { 208 ranges: uint16Range0_1023, 209 }, 210 { 211 ranges: uint16Range1_1023, 212 }, 213 { 214 ranges: uint16Range0_7, 215 }, 216 { 217 ranges: uint16Range1_7, 218 }, 219 { 220 ranges: uint16Range0_1, 221 }, 222 { 223 ranges: uint16Range1_1, 224 }, 225 } 226 for _, tt := range tests { 227 firstRange := tt.ranges[0] 228 lastRange := tt.ranges[len(tt.ranges)-1] 229 name := fmt.Sprintf("%d_%d%s", firstRange.start, lastRange.end, tt.name) 230 // Check that every valid key returns the correct 231 // entry and every invalid key returns nothing. 232 t.Run(name, func(t *testing.T) { 233 ut := NewUintTrie[uint16, string]() 234 for _, pr := range tt.ranges { 235 ut.Upsert(pr.prefix(), pr.start, fmt.Sprintf("%d-%d", pr.start, pr.end)) 236 } 237 for _, pr := range tt.ranges { 238 entry := fmt.Sprintf("%d-%d", pr.start, pr.end) 239 pref := pr.prefix() 240 // check if one-less than an exact prefix returns anything 241 if pref > 0 { 242 _, ok := ut.ExactLookup(pref-1, pr.start) 243 if ok { 244 t.Fatalf("ExactLookup returned a non-existent key-entry for prefix (%d), key (%d)", pr.prefix()-1, pr.start) 245 } 246 } 247 // check if one-more than an exact prefix returns anything 248 if pref < 16 { 249 _, ok := ut.ExactLookup(pref+1, pr.start) 250 if ok { 251 t.Fatalf("ExactLookup returned a non-existent key-entry for prefix (%d), key (%d)", pr.prefix()+1, pr.start) 252 } 253 } 254 // check if an exact lookup works 255 got, ok := ut.ExactLookup(pr.prefix(), pr.start) 256 if !ok || got != entry { 257 t.Fatalf("ExactLookup did not return the expected prefix (%d), key (%d); got %s", pr.prefix(), pr.start, got) 258 } 259 } 260 }) 261 } 262 } 263 264 // TestUnsignedLongestPrefixMatch looks up every possible value expressed 265 // in a trie structure by the most specific prefix. 266 func TestUnsignedLongestPrefixMatch(t *testing.T) { 267 tests := []struct { 268 name string 269 ranges []uint16Range 270 }{ 271 { 272 ranges: uint16Range65535, 273 }, 274 { 275 name: " least entries for largest range", 276 ranges: uint16Range0_65535, 277 }, 278 { 279 name: " most entries for largest range", 280 ranges: uint16Range1_65534, 281 }, 282 { 283 ranges: uint16Range0_1023, 284 }, 285 { 286 ranges: uint16Range1_1023, 287 }, 288 { 289 ranges: uint16Range0_7, 290 }, 291 { 292 ranges: uint16Range1_7, 293 }, 294 { 295 ranges: uint16Range0_1, 296 }, 297 { 298 ranges: uint16Range1_1, 299 }, 300 } 301 for _, tt := range tests { 302 firstRange := tt.ranges[0] 303 lastRange := tt.ranges[len(tt.ranges)-1] 304 name := fmt.Sprintf("%d_%d%s", firstRange.start, lastRange.end, tt.name) 305 // Check that every valid key returns the correct 306 // entry and every invalid key returns nothing. 307 t.Run(name, func(t *testing.T) { 308 ut := NewUintTrie[uint16, string]() 309 for _, pr := range tt.ranges { 310 ut.Upsert(pr.prefix(), pr.start, fmt.Sprintf("%d-%d", pr.start, pr.end)) 311 } 312 for _, pr := range tt.ranges { 313 entry := fmt.Sprintf("%d-%d", pr.start, pr.end) 314 start := pr.start 315 end := pr.end 316 // uint16 should be converted to uint for the 317 // purpose of the loop condition as some tests 318 // overflow uint16 causing an infinite loop. 319 for p := uint(start); p <= uint(end); p++ { 320 got, _ := ut.LongestPrefixMatch(uint16(p)) 321 if entry != got { 322 t.Fatalf("Looking up key %d, expected entry %q, but got %q", p, entry, got) 323 } 324 } 325 } 326 // look up all the missing keys. 327 start := firstRange.start 328 end := lastRange.end 329 for p := uint(0); p < uint(start); p++ { 330 got, ok := ut.LongestPrefixMatch(uint16(p)) 331 if ok { 332 t.Fatalf("Looking up key %d, expected no entry, but got %q", p, got) 333 } 334 } 335 for p := uint(end) + 1; p <= uint(65535); p++ { 336 got, ok := ut.LongestPrefixMatch(uint16(p)) 337 if ok { 338 t.Fatalf("Looking up key %d, expected no entry, but got %q", p, got) 339 } 340 } 341 }) 342 } 343 } 344 345 // TestUnsignedAncestorsRange tests looking up keys with 346 // a non-full prefix (i.e. a range of keys), by creating tries 347 // in different ranges and ensuring that the trie returns 348 // in-range queries from other known in-range lookups, and that 349 // known out-of-range lookups fail. 350 func TestUnsignedAncestorsRange(t *testing.T) { 351 ranges := [][]uint16Range{ 352 uint16Range65535, 353 uint16Range0_65535, 354 uint16Range1_65534, 355 uint16Range0_1023, 356 uint16Range1_1023, 357 uint16Range0_7, 358 uint16Range1_7, 359 uint16Range0_1, 360 uint16Range1_1, 361 } 362 // eliminate duplicate range lookups 363 rangeLookupMap := make(map[string]uint16Range) 364 for _, r := range ranges { 365 for _, pr := range r { 366 entry := fmt.Sprintf("%d-%d", pr.start, pr.end) 367 if _, ok := rangeLookupMap[entry]; !ok { 368 rangeLookupMap[entry] = pr 369 } 370 } 371 } 372 for _, r := range ranges { 373 rangeStart := r[0].start 374 rangeEnd := r[len(r)-1].end 375 name := fmt.Sprintf("%d_%d", rangeStart, rangeEnd) 376 t.Run(name, func(t *testing.T) { 377 tu := NewUintTrie[uint16, string]() 378 for _, pr := range r { 379 entry := fmt.Sprintf("%d-%d", pr.start, pr.end) 380 tu.Upsert(pr.prefix(), pr.start, entry) 381 } 382 for _, pr := range rangeLookupMap { 383 var gotEntry string 384 tu.Ancestors(pr.prefix(), pr.start, func(prefix uint, _ uint16, v string) bool { 385 gotEntry = v 386 return true 387 }) 388 if pr.start < rangeStart || pr.end > rangeEnd { 389 if gotEntry != "" { 390 t.Fatalf("Expected to get an emty entry from key-prefix %d/%d, got %q", 391 pr.start, pr.prefix(), gotEntry) 392 } 393 } else { 394 if gotEntry == "" { 395 t.Fatalf("Expected to get an in range entry from key-prefix %d/%d, but got no entry", 396 pr.start, pr.prefix()) 397 } 398 rangeS := strings.Split(gotEntry, "-") 399 start, err := strconv.ParseUint(rangeS[0], 10, 16) 400 if err != nil { 401 t.Fatalf("Error parsing start value of range entry %q", gotEntry) 402 } 403 if uint16(start) > pr.start { 404 t.Fatalf("Expected to get an in range entry from key-prefix %d/%d, but got %q", 405 pr.start, pr.prefix(), gotEntry) 406 } 407 end, err := strconv.ParseUint(rangeS[1], 10, 16) 408 if err != nil { 409 t.Fatalf("Error parsing end value of range entry %q", gotEntry) 410 } 411 if uint16(end) < pr.end { 412 t.Fatalf("Expected to get an in range entry from key-prefix %d/%d, but got %q", 413 pr.start, pr.prefix(), gotEntry) 414 } 415 } 416 } 417 }) 418 } 419 } 420 421 // TestUnsignedAncestors tests searching for all keys 422 // that match a searched-for key and prefix. 423 func TestUnsignedAncestors(t *testing.T) { 424 // Create a uint Trie that contains overlapping ranges 425 // from 0-65535. 426 tu := NewUintTrie[uint16, string]() 427 for i := uint(0); i < 16; i++ { 428 rng := uint16RangeMap[i] 429 entry := fmt.Sprintf("%d-%d", 0, rng) 430 tu.Upsert(i, rng, entry) 431 } 432 // Check to see that each range 433 // lookup returns all ranges that contain 434 // it. 435 for i := uint(0); i < 16; i++ { 436 rng := uint16RangeMap[i] 437 entry := fmt.Sprintf("%d-%d", 0, rng) 438 t.Run(entry, func(t *testing.T) { 439 expectedRes := make([]string, 0, i+1) 440 for t := uint(0); t <= i; t++ { 441 rng2 := uint16RangeMap[t] 442 expectedRes = append(expectedRes, fmt.Sprintf("%d-%d", 0, rng2)) 443 } 444 gotRes := make([]string, 0, i+1) 445 tu.Ancestors(i, rng, func(prefix uint, key uint16, v string) bool { 446 gotRes = append(gotRes, v) 447 return true 448 }) 449 if !reflect.DeepEqual(expectedRes, gotRes) { 450 t.Fatalf("Ancestors range %s, expected to get %v, but got: %v", entry, expectedRes, gotRes) 451 } 452 }) 453 } 454 } 455 456 // TestUnsignedDescendants tests searching for all keys 457 // that are match by a searched-for key and prefix. 458 func TestUnsignedDescendants(t *testing.T) { 459 // Create a uint Trie that contains overlapping ranges 460 // from 0-65535. 461 tu := NewUintTrie[uint16, string]() 462 for i := uint(0); i <= 16; i++ { 463 rng := uint16RangeMap[i] 464 entry := fmt.Sprintf("%d-%d", 0, rng) 465 tu.Upsert(i, rng, entry) 466 } 467 // Check to see that each range lookup returns 468 // all ranges that contain it. 469 for i := uint(0); i < 16; i++ { 470 rng := uint16RangeMap[i] 471 entry := fmt.Sprintf("%d-%d", 0, rng) 472 t.Run(entry, func(t *testing.T) { 473 expectedRes := make([]string, 0, 16-i) 474 for t := i; t <= 16; t++ { 475 rng2 := uint16RangeMap[t] 476 expectedRes = append(expectedRes, fmt.Sprintf("%d-%d", 0, rng2)) 477 } 478 gotRes := make([]string, 0, 16-i) 479 tu.Descendants(i, rng, func(prefix uint, key uint16, v string) bool { 480 gotRes = append(gotRes, v) 481 return true 482 }) 483 if !reflect.DeepEqual(expectedRes, gotRes) { 484 t.Fatalf("Descendants range %s, expected to get %v, but got: %v", entry, expectedRes, gotRes) 485 } 486 // It should still work even if the entry is not present 487 tu.Delete(i, rng) 488 expectedRes = expectedRes[1:] 489 gotRes = make([]string, 0, 16-i-1) 490 tu.Descendants(i, rng, func(prefix uint, key uint16, v string) bool { 491 gotRes = append(gotRes, v) 492 return true 493 }) 494 if !reflect.DeepEqual(expectedRes, gotRes) { 495 t.Fatalf("Descendants range %s, expected to get %v, but got: %v", entry, expectedRes, gotRes) 496 } 497 }) 498 } 499 } 500 501 // TestUnsignedDelete creates a trie from a set of ranges 502 // and then incrementally deletes each entry, checking 503 // that the trie contains all the values it should after 504 // each delete. It checks deleting the trie both 505 // from the bottom of a range up, and the top of the range 506 // down. 507 func TestUnsignedDelete(t *testing.T) { 508 tests := []struct { 509 name string 510 ranges []uint16Range 511 }{ 512 { 513 ranges: uint16Range65535, 514 }, 515 { 516 name: " least entries for largest range", 517 ranges: uint16Range0_65535, 518 }, 519 { 520 name: " most entries for largest range", 521 ranges: uint16Range1_65534, 522 }, 523 { 524 ranges: uint16Range0_1023, 525 }, 526 { 527 ranges: uint16Range1_1023, 528 }, 529 { 530 ranges: uint16Range0_7, 531 }, 532 { 533 ranges: uint16Range1_7, 534 }, 535 { 536 ranges: uint16Range0_1, 537 }, 538 { 539 ranges: uint16Range1_1, 540 }, 541 } 542 for _, tt := range tests { 543 name := fmt.Sprintf("%d_%d%s", tt.ranges[0].start, 544 tt.ranges[len(tt.ranges)-1].end, tt.name) 545 // Check that the whole trie is what it should be 546 // on each deletion in order. 547 t.Run(name, func(t *testing.T) { 548 ut := NewUintTrie[uint16, string]() 549 for _, pr := range tt.ranges { 550 ut.Upsert(pr.prefix(), pr.start, fmt.Sprintf("%d-%d", pr.start, pr.end)) 551 } 552 for i, pr := range tt.ranges { 553 // The "got" slice cannot be nil for the DeepEqual 554 // comparison, even if it is empty. 555 got := make([]uint16Range, 0, len(tt.ranges)-i-1) 556 ok := ut.Delete(pr.prefix(), pr.start) 557 if !ok { 558 t.Fatalf("Key-prefix %d/%d not deleted", pr.start, pr.prefix()) 559 } 560 ut.ForEach(func(prefix uint, key uint16, value string) bool { 561 got = append(got, uint16Range{start: key, end: endFromPrefix(prefix, key)}) 562 return true 563 }) 564 sort.Slice(got, func(i, j int) bool { 565 return got[i].start < got[j].start 566 }) 567 if !reflect.DeepEqual(got, tt.ranges[i+1:]) { 568 t.Fatalf("When deleting an entry from an unsigned trie with the key-prefix %d/%d: got %+v, but expected %+v", pr.start, pr.prefix(), got, tt.ranges[i+1:]) 569 } 570 } 571 }) 572 // Delete in reverse order. 573 t.Run(fmt.Sprintf("In_Reverse_%s", name), func(t *testing.T) { 574 ut := NewUintTrie[uint16, string]() 575 for _, pr := range tt.ranges { 576 ut.Upsert(pr.prefix(), pr.start, fmt.Sprintf("%d-%d", pr.start, pr.end)) 577 } 578 for i := len(tt.ranges) - 1; i >= 0; i-- { 579 pr := tt.ranges[i] 580 // The "got" slice cannot be nil for the DeepEqual 581 // comparison, even if it is empty. 582 got := make([]uint16Range, 0, i+1) 583 ok := ut.Delete(pr.prefix(), pr.start) 584 if !ok { 585 t.Fatalf("Key-prefix %d/%d not deleted", pr.start, pr.prefix()) 586 } 587 ut.ForEach(func(prefix uint, key uint16, value string) bool { 588 got = append(got, uint16Range{start: key, end: endFromPrefix(prefix, key)}) 589 return true 590 }) 591 sort.Slice(got, func(i, j int) bool { 592 return got[i].start < got[j].start 593 }) 594 if !reflect.DeepEqual(got, tt.ranges[:i]) { 595 t.Fatalf("When deleting an entry from an unsigned trie with the key-prefix %d/%d: got %+v, but expected %+v", pr.start, pr.prefix(), got, tt.ranges[:i]) 596 } 597 } 598 }) 599 } 600 } 601 602 func BenchmarkTrieUpsert(b *testing.B) { 603 tri := NewUintTrie[uint32, struct{}]() 604 emptyS := struct{}{} 605 count := uint(0) 606 b.ReportAllocs() 607 b.ResetTimer() 608 609 // mimic adding 2 octets worth of addresses 610 tri.Upsert(16, 0xffff_0000, emptyS) 611 count++ 612 for i := uint32(0); i < 255; i++ { 613 upperThree := 0xffff_0000 | i<<8 614 tri.Upsert(24, upperThree, emptyS) 615 count++ 616 for t := uint32(0); t < 255; t++ { 617 tri.Upsert(32, upperThree|t, emptyS) 618 count++ 619 } 620 } 621 b.StopTimer() 622 if tri.Len() != count { 623 b.Fatalf("expected count (%d) to agree with trie length (%d)", count, tri.Len()) 624 } 625 } 626 627 func BenchmarkMapUpdate(b *testing.B) { 628 map32 := make(map[uint32]struct{}, 256*256) 629 emptyS := struct{}{} 630 count := 0 631 b.ReportAllocs() 632 b.ResetTimer() 633 // mimic adding 2 octets worth of addresses 634 for i := uint32(0); i < 255; i++ { 635 upperOct := i << 8 636 for t := uint32(0); t < 255; t++ { 637 map32[0xffff_0000|upperOct|t] = emptyS 638 count++ 639 } 640 } 641 b.StopTimer() 642 if len(map32) != count { 643 b.Fatalf("expected count (%d) to agree with map length (%d)", count, len(map32)) 644 } 645 } 646 647 func BenchmarkTrieAncestorsRange(b *testing.B) { 648 tri := NewUintTrie[uint32, *struct{}]() 649 emptyS := &struct{}{} 650 count := uint(0) 651 // mimic adding 2 octets worth of addresses 652 tri.Upsert(16, 0xffff_0000, emptyS) 653 count++ 654 for i := uint32(0); i < 255; i++ { 655 upperOct := i << 8 656 tri.Upsert(24, 0xffff_0000|upperOct, emptyS) 657 count++ 658 for t := uint32(0); t < 255; t++ { 659 tri.Upsert(32, 0xffff_0000|upperOct|t, emptyS) 660 count++ 661 } 662 } 663 if tri.Len() != count { 664 b.Fatalf("expected count (%d) to agree with trie length (%d)", count, tri.Len()) 665 } 666 667 b.ReportAllocs() 668 b.ResetTimer() 669 var st *struct{} 670 tri.Ancestors(16, 0xffff_0000, func(_ uint, _ uint32, v *struct{}) bool { 671 st = v 672 return true 673 }) 674 if st == nil { 675 b.Fatal("expected valid lookup, but got nil") 676 } 677 for i := uint32(0); i < 255; i++ { 678 upperOct := i << 8 679 var st *struct{} 680 tri.Ancestors(24, 0xffff_0000|upperOct, func(_ uint, _ uint32, v *struct{}) bool { 681 st = v 682 return true 683 }) 684 if st == nil { 685 b.Fatal("expected valid lookup, but got nil") 686 } 687 for t := uint32(0); t < 255; t++ { 688 var st *struct{} 689 tri.Ancestors(32, 0xffff_0000|upperOct|t, func(_ uint, _ uint32, v *struct{}) bool { 690 st = v 691 return true 692 }) 693 if st == nil { 694 b.Fatal("expected valid lookup, but got nil") 695 } 696 } 697 } 698 } 699 700 func BenchmarkTrieLongestPrefixMatch(b *testing.B) { 701 tri := NewUintTrie[uint32, *struct{}]() 702 emptyS := &struct{}{} 703 count := uint(0) 704 // mimic adding 2 octets worth of addresses 705 for i := uint32(0); i < 255; i++ { 706 upperOct := i << 8 707 tri.Upsert(24, 0xffff_0000|upperOct, emptyS) 708 count++ 709 for t := uint32(0); t < 255; t++ { 710 tri.Upsert(32, 0xffff_0000|upperOct|t, emptyS) 711 count++ 712 } 713 } 714 if tri.Len() != count { 715 b.Fatalf("expected count (%d) to agree with trie length (%d)", count, tri.Len()) 716 } 717 718 b.ReportAllocs() 719 b.ResetTimer() 720 for i := uint32(0); i < 255; i++ { 721 upperOct := i << 8 722 for t := uint32(0); t < 255; t++ { 723 _, ok := tri.LongestPrefixMatch(0xffff_0000 | upperOct | t) 724 if !ok { 725 b.Fatal("expected valid lookup, but got nil") 726 } 727 } 728 } 729 } 730 731 func BenchmarkMapLookup(b *testing.B) { 732 map32 := make(map[uint32]*struct{}, 256*256) 733 emptyS := &struct{}{} 734 count := 0 735 // mimic adding 2 octets worth of addresses 736 for i := uint32(0); i < 255; i++ { 737 upperOct := i << 8 738 for t := uint32(0); t < 255; t++ { 739 map32[0xffff_0000|upperOct|t] = emptyS 740 count++ 741 } 742 } 743 if len(map32) != count { 744 b.Fatalf("expected count (%d) to agree with map length (%d)", count, len(map32)) 745 } 746 747 b.ReportAllocs() 748 b.ResetTimer() 749 for i := uint32(0); i < 255; i++ { 750 upperOct := i << 8 751 for t := uint32(0); t < 255; t++ { 752 v, ok := map32[0xffff_0000|upperOct|t] 753 if !ok || v == nil { 754 b.Fatalf("expected to get value from map lookup, got nil") 755 } 756 } 757 } 758 } 759 760 func BenchmarkTrieDelete(b *testing.B) { 761 tri := NewUintTrie[uint32, *struct{}]() 762 emptyS := &struct{}{} 763 count := uint(0) 764 // mimic adding 2 octets worth of addresses 765 for i := uint32(0); i < 255; i++ { 766 upperOct := i << 8 767 tri.Upsert(24, 0xffff_0000|upperOct, emptyS) 768 count++ 769 for t := uint32(0); t < 255; t++ { 770 tri.Upsert(32, 0xffff_0000|upperOct|t, emptyS) 771 count++ 772 } 773 } 774 if tri.Len() != count { 775 b.Fatalf("expected count (%d) to agree with trie length (%d)", count, tri.Len()) 776 } 777 778 b.ReportAllocs() 779 b.ResetTimer() 780 for i := uint32(0); i < 255; i++ { 781 upperOct := i << 8 782 if !tri.Delete(24, 0xffff_0000|upperOct) { 783 b.Fatal("expected valid delete, but got nil") 784 } 785 for t := uint32(0); t < 255; t++ { 786 if !tri.Delete(32, 0xffff_0000|upperOct|t) { 787 b.Fatal("expected valid lookup, but got nil") 788 } 789 } 790 } 791 b.StopTimer() 792 if tri.Len() != 0 { 793 b.Fatalf("expected Trie length of 0, but got %d", tri.Len()) 794 } 795 } 796 797 func BenchmarkMapDelete(b *testing.B) { 798 map32 := make(map[uint32]*struct{}, 256*256) 799 emptyS := &struct{}{} 800 count := 0 801 // mimic adding 2 octets worth of addresses 802 for i := uint32(0); i < 255; i++ { 803 upperOct := i << 8 804 for t := uint32(0); t < 255; t++ { 805 map32[0xffff_0000|upperOct|t] = emptyS 806 count++ 807 } 808 } 809 if len(map32) != count { 810 b.Fatalf("expected count (%d) to agree with map length (%d)", count, len(map32)) 811 } 812 813 b.ReportAllocs() 814 b.ResetTimer() 815 for i := uint32(0); i < 255; i++ { 816 upperOct := i << 8 817 for t := uint32(0); t < 255; t++ { 818 delete(map32, 0xffff_0000|upperOct|t) 819 } 820 } 821 b.StopTimer() 822 if len(map32) != 0 { 823 b.Fatalf("expected map length of 0, but got %d", len(map32)) 824 } 825 826 } 827 828 func mask(v, bitcnt uint8) uint8 { 829 m := ^(^uint8(0) >> bitcnt) 830 return v & m 831 } 832 833 func FuzzUint8(f *testing.F) { 834 // has the fuzzing engine generate a set of []uint8, which it interprets as 835 // a sequence of (val, prefixlen) pairs. 836 837 // Then, checks invariants 838 839 f.Add([]byte{0b1111_1111, 4}) 840 841 f.Fuzz(func(t *testing.T, sequence []byte) { 842 843 type testEntry struct { 844 k uint8 845 plen uint8 846 val uint16 // a placeholder 847 } 848 849 tree := NewUintTrie[uint8, testEntry]() 850 851 seen := map[string]testEntry{} 852 853 // Insert every item in to the tree, recording the prefix in to a hash as well 854 // so we know what we've set 855 for i := 0; i < len(sequence)-1; i += 2 { 856 k := sequence[i] 857 prefixLen := sequence[i+1] % 8 858 859 seenk := fmt.Sprintf("%#b/%d", mask(k, prefixLen), prefixLen) 860 861 seen[seenk] = testEntry{ 862 k: k, 863 plen: prefixLen, 864 val: uint16(k)<<8 + uint16(prefixLen), 865 } 866 867 tree.Upsert(uint(prefixLen), k, seen[seenk]) // may overwrite 868 869 } 870 871 if tree.Len() != uint(len(seen)) { 872 t.Errorf("unexpected length: %d (expected %d)", tree.Len(), len(seen)) 873 } 874 875 // Now, validate 876 for seenK, seenV := range seen { 877 var val testEntry 878 tree.Ancestors(uint(seenV.plen), seenV.k, func(_ uint, _ uint8, v testEntry) bool { 879 val = v 880 return true 881 }) 882 if val.val != seenV.val { 883 t.Errorf("seenKey %s: got val %#b expected %#b", seenK, val.val, seenV.val) 884 } 885 } 886 887 // Now, delete seen keys and validate 888 expectedLength := len(seen) 889 for seenK, seenV := range seen { 890 t.Logf("Deleting key %s", seenK) 891 tree.Delete(uint(seenV.plen), seenV.k) 892 expectedLength-- 893 894 if tree.Len() != uint(expectedLength) { 895 t.Errorf("unexpected length: %d (expected %d)", tree.Len(), expectedLength) 896 } 897 } 898 }) 899 }