github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/uid/ksuid_test.go (about) 1 package uid 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "encoding/json" 7 "flag" 8 "fmt" 9 "math" 10 "sort" 11 "strings" 12 "testing" 13 "time" 14 ) 15 16 func TestConstructionTimestamp(t *testing.T) { 17 x := New() 18 nowTime := time.Now().Round(1 * time.Minute) 19 xTime := x.Time().Round(1 * time.Minute) 20 21 if xTime != nowTime { 22 t.Fatal(xTime, "!=", nowTime) 23 } 24 } 25 26 func TestNil(t *testing.T) { 27 if !Nil.IsNil() { 28 t.Fatal("Nil should be Nil!") 29 } 30 31 x, _ := FromBytes(make([]byte, byteLength)) 32 if !x.IsNil() { 33 t.Fatal("Zero-byte array should be Nil!") 34 } 35 } 36 37 func TestEncoding(t *testing.T) { 38 x, _ := FromBytes(make([]byte, byteLength)) 39 if !x.IsNil() { 40 t.Fatal("Zero-byte array should be Nil!") 41 } 42 43 encoded := x.String() 44 expected := strings.Repeat("0", stringEncodedLength) 45 46 if encoded != expected { 47 t.Fatal("expected", expected, "encoded", encoded) 48 } 49 } 50 51 func TestPadding(t *testing.T) { 52 b := make([]byte, byteLength) 53 for i := 0; i < byteLength; i++ { 54 b[i] = 255 55 } 56 57 x, _ := FromBytes(b) 58 xEncoded := x.String() 59 nilEncoded := Nil.String() 60 61 if len(xEncoded) != len(nilEncoded) { 62 t.Fatal("Encoding should produce equal-length strings for zero and max case") 63 } 64 } 65 66 func TestParse(t *testing.T) { 67 _, err := Parse("123") 68 if err != errStrSize { 69 t.Fatal("Expected Parsing a 3-char string to return an error") 70 } 71 72 parsed, err := Parse(strings.Repeat("0", stringEncodedLength)) 73 if err != nil { 74 t.Fatal("Unexpected error", err) 75 } 76 77 if Compare(parsed, Nil) != 0 { 78 t.Fatal("Parsing all-zeroes string should equal Nil value", 79 "expected:", Nil, 80 "actual:", parsed) 81 } 82 83 maxBytes := make([]byte, byteLength) 84 for i := 0; i < byteLength; i++ { 85 maxBytes[i] = 255 86 } 87 maxBytesKSUID, err := FromBytes(maxBytes) 88 if err != nil { 89 t.Fatal("Unexpected error", err) 90 } 91 92 maxParseKSUID, err := Parse(maxStringEncoded) 93 if err != nil { 94 t.Fatal("Unexpected error", err) 95 } 96 97 if Compare(maxBytesKSUID, maxParseKSUID) != 0 { 98 t.Fatal("String decoder broke for max string") 99 } 100 } 101 102 func TestIssue25(t *testing.T) { 103 // https://github.com/segmentio/ksuid/issues/25 104 for _, s := range []string{ 105 "aaaaaaaaaaaaaaaaaaaaaaaaaaa", 106 "aWgEPTl1tmebfsQzFP4bxwgy80!", 107 } { 108 _, err := Parse(s) 109 if err != errStrValue { 110 t.Error("invalid KSUID representations cannot be successfully parsed, got err =", err) 111 } 112 } 113 } 114 115 func TestEncodeAndDecode(t *testing.T) { 116 x := New() 117 builtFromEncodedString, err := Parse(x.String()) 118 if err != nil { 119 t.Fatal("Unexpected error", err) 120 } 121 122 if Compare(x, builtFromEncodedString) != 0 { 123 t.Fatal("Parse(X).String() != X") 124 } 125 } 126 127 func TestMarshalText(t *testing.T) { 128 id1 := New() 129 var id2 KSUID 130 131 if err := id2.UnmarshalText([]byte(id1.String())); err != nil { 132 t.Fatal(err) 133 } 134 135 if id1 != id2 { 136 t.Fatal(id1, "!=", id2) 137 } 138 139 if b, err := id2.MarshalText(); err != nil { 140 t.Fatal(err) 141 } else if s := string(b); s != id1.String() { 142 t.Fatal(s) 143 } 144 } 145 146 func TestMarshalBinary(t *testing.T) { 147 id1 := New() 148 var id2 KSUID 149 150 if err := id2.UnmarshalBinary(id1.Bytes()); err != nil { 151 t.Fatal(err) 152 } 153 154 if id1 != id2 { 155 t.Fatal(id1, "!=", id2) 156 } 157 158 if b, err := id2.MarshalBinary(); err != nil { 159 t.Fatal(err) 160 } else if bytes.Compare(b, id1.Bytes()) != 0 { 161 t.Fatal("bad binary form:", id2) 162 } 163 } 164 165 func TestMarshalJSON(t *testing.T) { 166 id1 := New() 167 var id2 KSUID 168 169 if b, err := json.Marshal(id1); err != nil { 170 t.Fatal(err) 171 } else if err := json.Unmarshal(b, &id2); err != nil { 172 t.Fatal(err) 173 } else if id1 != id2 { 174 t.Error(id1, "!=", id2) 175 } 176 } 177 178 func TestFlag(t *testing.T) { 179 id1 := New() 180 var id2 KSUID 181 182 fset := flag.NewFlagSet("test", flag.ContinueOnError) 183 fset.Var(&id2, "id", "the KSUID") 184 185 if err := fset.Parse([]string{"-id", id1.String()}); err != nil { 186 t.Fatal(err) 187 } 188 189 if id1 != id2 { 190 t.Error(id1, "!=", id2) 191 } 192 } 193 194 func TestSqlValuer(t *testing.T) { 195 id, _ := Parse(maxStringEncoded) 196 197 if v, err := id.Value(); err != nil { 198 t.Error(err) 199 } else if s, ok := v.(string); !ok { 200 t.Error("not a string value") 201 } else if s != maxStringEncoded { 202 t.Error("bad string value::", s) 203 } 204 } 205 206 func TestSqlValuerNilValue(t *testing.T) { 207 if v, err := Nil.Value(); err != nil { 208 t.Error(err) 209 } else if v != nil { 210 t.Errorf("bad nil value: %v", v) 211 } 212 } 213 214 func TestSqlScanner(t *testing.T) { 215 id1 := New() 216 id2 := New() 217 218 tests := []struct { 219 ksuid KSUID 220 value interface{} 221 }{ 222 {Nil, nil}, 223 {id1, id1.String()}, 224 {id2, id2.Bytes()}, 225 } 226 227 for _, test := range tests { 228 t.Run(fmt.Sprintf("%T", test.value), func(t *testing.T) { 229 var id KSUID 230 231 if err := id.Scan(test.value); err != nil { 232 t.Error(err) 233 } 234 235 if id != test.ksuid { 236 t.Error("bad KSUID:") 237 t.Logf("expected %v", test.ksuid) 238 t.Logf("found %v", id) 239 } 240 }) 241 } 242 } 243 244 func TestAppend(t *testing.T) { 245 for _, repr := range []string{"0pN1Own7255s7jwpwy495bAZeEa", "aWgEPTl1tmebfsQzFP4bxwgy80V"} { 246 k, _ := Parse(repr) 247 a := make([]byte, 0, stringEncodedLength) 248 249 a = append(a, "?: "...) 250 a = k.Append(a) 251 252 if s := string(a); s != "?: "+repr { 253 t.Error(s) 254 } 255 } 256 } 257 258 func TestSort(t *testing.T) { 259 ids1 := [11]KSUID{} 260 ids2 := [11]KSUID{} 261 262 for i := range ids1 { 263 ids1[i] = New() 264 } 265 266 ids2 = ids1 267 sort.Slice(ids2[:], func(i, j int) bool { 268 return Compare(ids2[i], ids2[j]) < 0 269 }) 270 271 Sort(ids1[:]) 272 273 if !IsSorted(ids1[:]) { 274 t.Error("not sorted") 275 } 276 277 if ids1 != ids2 { 278 t.Error("bad order:") 279 t.Log(ids1) 280 t.Log(ids2) 281 } 282 } 283 284 func TestPrevNext(t *testing.T) { 285 tests := []struct { 286 id KSUID 287 prev KSUID 288 next KSUID 289 }{ 290 { 291 id: Nil, 292 prev: Max, 293 next: KSUID{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, 294 }, 295 { 296 id: Max, 297 prev: KSUID{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe}, 298 next: Nil, 299 }, 300 } 301 302 for _, test := range tests { 303 t.Run(test.id.String(), func(t *testing.T) { 304 testPrevNext(t, test.id, test.prev, test.next) 305 }) 306 } 307 } 308 309 func TestGetTimestamp(t *testing.T) { 310 nowTime := time.Now() 311 x, _ := NewRandomWithTime(nowTime) 312 xTime := int64(x.Timestamp()) 313 unix := nowTime.Unix() 314 if xTime != unix-epochStamp { 315 t.Fatal(xTime, "!=", unix) 316 } 317 } 318 319 func testPrevNext(t *testing.T, id, prev, next KSUID) { 320 id1 := id.Prev() 321 id2 := id.Next() 322 323 if id1 != prev { 324 t.Error("previous id of the nil KSUID is wrong:", id1, "!=", prev) 325 } 326 327 if id2 != next { 328 t.Error("next id of the nil KSUID is wrong:", id2, "!=", next) 329 } 330 } 331 332 func BenchmarkAppend(b *testing.B) { 333 a := make([]byte, 0, stringEncodedLength) 334 k := New() 335 336 for i := 0; i != b.N; i++ { 337 k.Append(a) 338 } 339 } 340 341 func BenchmarkString(b *testing.B) { 342 k := New() 343 344 for i := 0; i != b.N; i++ { 345 _ = k.String() 346 } 347 } 348 349 func BenchmarkParse(b *testing.B) { 350 for i := 0; i != b.N; i++ { 351 Parse(maxStringEncoded) 352 } 353 } 354 355 func BenchmarkCompare(b *testing.B) { 356 k1 := New() 357 k2 := New() 358 359 for i := 0; i != b.N; i++ { 360 Compare(k1, k2) 361 } 362 } 363 364 func BenchmarkSort(b *testing.B) { 365 ids1 := [101]KSUID{} 366 ids2 := [101]KSUID{} 367 368 for i := range ids1 { 369 ids1[i] = New() 370 } 371 372 for i := 0; i != b.N; i++ { 373 ids2 = ids1 374 Sort(ids2[:]) 375 } 376 } 377 378 func BenchmarkNew(b *testing.B) { 379 b.Run("with crypto rand", func(b *testing.B) { 380 SetRand(nil) 381 for i := 0; i != b.N; i++ { 382 New() 383 } 384 }) 385 b.Run("with math rand", func(b *testing.B) { 386 SetRand(FastRander) 387 for i := 0; i != b.N; i++ { 388 New() 389 } 390 }) 391 } 392 393 func TestCmp128(t *testing.T) { 394 tests := []struct { 395 x uint128 396 y uint128 397 k int 398 }{ 399 { 400 x: makeUint128(0, 0), 401 y: makeUint128(0, 0), 402 k: 0, 403 }, 404 { 405 x: makeUint128(0, 1), 406 y: makeUint128(0, 0), 407 k: +1, 408 }, 409 { 410 x: makeUint128(0, 0), 411 y: makeUint128(0, 1), 412 k: -1, 413 }, 414 { 415 x: makeUint128(1, 0), 416 y: makeUint128(0, 1), 417 k: +1, 418 }, 419 { 420 x: makeUint128(0, 1), 421 y: makeUint128(1, 0), 422 k: -1, 423 }, 424 } 425 426 for _, test := range tests { 427 t.Run(fmt.Sprintf("cmp128(%s,%s)", test.x, test.y), func(t *testing.T) { 428 if k := cmp128(test.x, test.y); k != test.k { 429 t.Error(k, "!=", test.k) 430 } 431 }) 432 } 433 } 434 435 func TestAdd128(t *testing.T) { 436 tests := []struct { 437 x uint128 438 y uint128 439 z uint128 440 }{ 441 { 442 x: makeUint128(0, 0), 443 y: makeUint128(0, 0), 444 z: makeUint128(0, 0), 445 }, 446 { 447 x: makeUint128(0, 1), 448 y: makeUint128(0, 0), 449 z: makeUint128(0, 1), 450 }, 451 { 452 x: makeUint128(0, 0), 453 y: makeUint128(0, 1), 454 z: makeUint128(0, 1), 455 }, 456 { 457 x: makeUint128(1, 0), 458 y: makeUint128(0, 1), 459 z: makeUint128(1, 1), 460 }, 461 { 462 x: makeUint128(0, 1), 463 y: makeUint128(1, 0), 464 z: makeUint128(1, 1), 465 }, 466 { 467 x: makeUint128(0, 0xFFFFFFFFFFFFFFFF), 468 y: makeUint128(0, 1), 469 z: makeUint128(1, 0), 470 }, 471 } 472 473 for _, test := range tests { 474 t.Run(fmt.Sprintf("add128(%s,%s)", test.x, test.y), func(t *testing.T) { 475 if z := add128(test.x, test.y); z != test.z { 476 t.Error(z, "!=", test.z) 477 } 478 }) 479 } 480 } 481 482 func TestSub128(t *testing.T) { 483 tests := []struct { 484 x uint128 485 y uint128 486 z uint128 487 }{ 488 { 489 x: makeUint128(0, 0), 490 y: makeUint128(0, 0), 491 z: makeUint128(0, 0), 492 }, 493 { 494 x: makeUint128(0, 1), 495 y: makeUint128(0, 0), 496 z: makeUint128(0, 1), 497 }, 498 { 499 x: makeUint128(0, 0), 500 y: makeUint128(0, 1), 501 z: makeUint128(0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF), 502 }, 503 { 504 x: makeUint128(1, 0), 505 y: makeUint128(0, 1), 506 z: makeUint128(0, 0xFFFFFFFFFFFFFFFF), 507 }, 508 { 509 x: makeUint128(0, 1), 510 y: makeUint128(1, 0), 511 z: makeUint128(0xFFFFFFFFFFFFFFFF, 1), 512 }, 513 { 514 x: makeUint128(0, 0xFFFFFFFFFFFFFFFF), 515 y: makeUint128(0, 1), 516 z: makeUint128(0, 0xFFFFFFFFFFFFFFFE), 517 }, 518 } 519 520 for _, test := range tests { 521 t.Run(fmt.Sprintf("sub128(%s,%s)", test.x, test.y), func(t *testing.T) { 522 if z := sub128(test.x, test.y); z != test.z { 523 t.Error(z, "!=", test.z) 524 } 525 }) 526 } 527 } 528 529 func BenchmarkCmp128(b *testing.B) { 530 x := makeUint128(0, 0) 531 y := makeUint128(0, 0) 532 533 for i := 0; i != b.N; i++ { 534 cmp128(x, y) 535 } 536 } 537 538 func BenchmarkAdd128(b *testing.B) { 539 x := makeUint128(0, 0) 540 y := makeUint128(0, 0) 541 542 for i := 0; i != b.N; i++ { 543 add128(x, y) 544 } 545 } 546 547 func BenchmarkSub128(b *testing.B) { 548 x := makeUint128(0, 0) 549 y := makeUint128(0, 0) 550 551 for i := 0; i != b.N; i++ { 552 sub128(x, y) 553 } 554 } 555 556 func TestSequence(t *testing.T) { 557 seq := Sequence{Seed: New()} 558 559 if min, max := seq.Bounds(); min == max { 560 t.Error("min and max of KSUID range must differ when no ids have been generated") 561 } 562 563 for i := 0; i <= math.MaxUint16; i++ { 564 id, err := seq.Next() 565 if err != nil { 566 t.Fatal(err) 567 } 568 if j := int(binary.BigEndian.Uint16(id[len(id)-2:])); j != i { 569 t.Fatalf("expected %d but got %d in %s", i, j, id) 570 } 571 } 572 573 if _, err := seq.Next(); err == nil { 574 t.Fatal("no error returned after exhausting the id generator") 575 } 576 577 if min, max := seq.Bounds(); min != max { 578 t.Error("after all KSUIDs were generated the min and max must be equal") 579 } 580 } 581 582 func TestBase10ToBase62AndBack(t *testing.T) { 583 number := []byte{1, 2, 3, 4} 584 encoded := base2base(number, 10, 62) 585 decoded := base2base(encoded, 62, 10) 586 587 if bytes.Compare(number, decoded) != 0 { 588 t.Fatal(number, " != ", decoded) 589 } 590 } 591 592 func TestBase256ToBase62AndBack(t *testing.T) { 593 number := []byte{255, 254, 253, 251} 594 encoded := base2base(number, 256, 62) 595 decoded := base2base(encoded, 62, 256) 596 597 if bytes.Compare(number, decoded) != 0 { 598 t.Fatal(number, " != ", decoded) 599 } 600 } 601 602 func TestEncodeAndDecodeBase62(t *testing.T) { 603 helloWorld := []byte("hello world") 604 encoded := encodeBase62(helloWorld) 605 decoded := decodeBase62(encoded) 606 607 if len(encoded) < len(helloWorld) { 608 t.Fatal("length of encoded base62 string", encoded, "should be >= than raw bytes!") 609 } 610 611 if bytes.Compare(helloWorld, decoded) != 0 { 612 t.Fatal(decoded, " != ", helloWorld) 613 } 614 } 615 616 func TestLexographicOrdering(t *testing.T) { 617 unsortedStrings := make([]string, 256) 618 for i := 0; i < 256; i++ { 619 s := string(encodeBase62([]byte{0, byte(i)})) 620 unsortedStrings[i] = strings.Repeat("0", 2-len(s)) + s 621 } 622 623 if !sort.StringsAreSorted(unsortedStrings) { 624 sortedStrings := make([]string, len(unsortedStrings)) 625 for i, s := range unsortedStrings { 626 sortedStrings[i] = s 627 } 628 sort.Strings(sortedStrings) 629 630 t.Fatal("base62 encoder does not produce lexographically sorted output.", 631 "expected:", sortedStrings, 632 "actual:", unsortedStrings) 633 } 634 } 635 636 func TestBase62Value(t *testing.T) { 637 s := base62Characters 638 639 for i := range s { 640 v := int(base62Value(s[i])) 641 642 if v != i { 643 t.Error("bad value:") 644 t.Log("<<<", i) 645 t.Log(">>>", v) 646 } 647 } 648 } 649 650 func TestFastAppendEncodeBase62(t *testing.T) { 651 for i := 0; i != 1000; i++ { 652 id := New() 653 654 b0 := id[:] 655 b1 := appendEncodeBase62(nil, b0) 656 b2 := fastAppendEncodeBase62(nil, b0) 657 658 s1 := string(leftpad(b1, '0', stringEncodedLength)) 659 s2 := string(b2) 660 661 if s1 != s2 { 662 t.Error("bad base62 representation of", id) 663 t.Log("<<<", s1, len(s1)) 664 t.Log(">>>", s2, len(s2)) 665 } 666 } 667 } 668 669 func TestFastAppendDecodeBase62(t *testing.T) { 670 for i := 0; i != 1000; i++ { 671 id := New() 672 b0 := leftpad(encodeBase62(id[:]), '0', stringEncodedLength) 673 674 b1 := appendDecodeBase62(nil, []byte(string(b0))) // because it modifies the input buffer 675 b2 := fastAppendDecodeBase62(nil, b0) 676 677 if !bytes.Equal(leftpad(b1, 0, byteLength), b2) { 678 t.Error("bad binary representation of", string(b0)) 679 t.Log("<<<", b1) 680 t.Log(">>>", b2) 681 } 682 } 683 } 684 685 func BenchmarkAppendEncodeBase62(b *testing.B) { 686 a := [stringEncodedLength]byte{} 687 id := New() 688 689 for i := 0; i != b.N; i++ { 690 appendEncodeBase62(a[:0], id[:]) 691 } 692 } 693 694 func BenchmarkAppendFastEncodeBase62(b *testing.B) { 695 a := [stringEncodedLength]byte{} 696 id := New() 697 698 for i := 0; i != b.N; i++ { 699 fastAppendEncodeBase62(a[:0], id[:]) 700 } 701 } 702 703 func BenchmarkAppendDecodeBase62(b *testing.B) { 704 a := [byteLength]byte{} 705 id := []byte(New().String()) 706 707 for i := 0; i != b.N; i++ { 708 b := [stringEncodedLength]byte{} 709 copy(b[:], id) 710 appendDecodeBase62(a[:0], b[:]) 711 } 712 } 713 714 func BenchmarkAppendFastDecodeBase62(b *testing.B) { 715 a := [byteLength]byte{} 716 id := []byte(New().String()) 717 718 for i := 0; i != b.N; i++ { 719 fastAppendDecodeBase62(a[:0], id) 720 } 721 } 722 723 // The functions bellow were the initial implementation of the base conversion 724 // algorithms, they were replaced by optimized versions later on. We keep them 725 // in the test files as a reference to ensure compatibility between the generic 726 // and optimized implementations. 727 func appendBase2Base(dst []byte, src []byte, inBase int, outBase int) []byte { 728 off := len(dst) 729 bs := src[:] 730 bq := [stringEncodedLength]byte{} 731 732 for len(bs) > 0 { 733 length := len(bs) 734 quotient := bq[:0] 735 remainder := 0 736 737 for i := 0; i != length; i++ { 738 acc := int(bs[i]) + remainder*inBase 739 d := acc/outBase | 0 740 remainder = acc % outBase 741 742 if len(quotient) > 0 || d > 0 { 743 quotient = append(quotient, byte(d)) 744 } 745 } 746 747 // Appends in reverse order, the byte slice gets reversed before it's 748 // returned by the function. 749 dst = append(dst, byte(remainder)) 750 bs = quotient 751 } 752 753 reverse(dst[off:]) 754 return dst 755 } 756 757 func base2base(src []byte, inBase int, outBase int) []byte { 758 return appendBase2Base(nil, src, inBase, outBase) 759 } 760 761 func appendEncodeBase62(dst []byte, src []byte) []byte { 762 off := len(dst) 763 dst = appendBase2Base(dst, src, 256, 62) 764 for i, c := range dst[off:] { 765 dst[off+i] = base62Characters[c] 766 } 767 return dst 768 } 769 770 func encodeBase62(in []byte) []byte { 771 return appendEncodeBase62(nil, in) 772 } 773 774 func appendDecodeBase62(dst []byte, src []byte) []byte { 775 // Kind of intrusive, we modify the input buffer... it's OK here, it saves 776 // a memory allocation in Parse. 777 for i, b := range src { 778 // O(1)... technically. Has better real-world perf than a map 779 src[i] = byte(strings.IndexByte(base62Characters, b)) 780 } 781 return appendBase2Base(dst, src, 62, 256) 782 } 783 784 func decodeBase62(src []byte) []byte { 785 return appendDecodeBase62( 786 make([]byte, 0, len(src)*2), 787 append(make([]byte, 0, len(src)), src...), 788 ) 789 } 790 791 func reverse(b []byte) { 792 i := 0 793 j := len(b) - 1 794 795 for i < j { 796 b[i], b[j] = b[j], b[i] 797 i++ 798 j-- 799 } 800 } 801 802 func leftpad(b []byte, c byte, n int) []byte { 803 if n -= len(b); n > 0 { 804 for i := 0; i != n; i++ { 805 b = append(b, c) 806 } 807 808 copy(b[n:], b) 809 810 for i := 0; i != n; i++ { 811 b[i] = c 812 } 813 } 814 return b 815 } 816 817 func TestCompressedSet(t *testing.T) { 818 tests := []struct { 819 scenario string 820 function func(*testing.T) 821 }{ 822 { 823 scenario: "String", 824 function: testCompressedSetString, 825 }, 826 { 827 scenario: "GoString", 828 function: testCompressedSetGoString, 829 }, 830 { 831 scenario: "sparse", 832 function: testCompressedSetSparse, 833 }, 834 { 835 scenario: "packed", 836 function: testCompressedSetPacked, 837 }, 838 { 839 scenario: "mixed", 840 function: testCompressedSetMixed, 841 }, 842 { 843 scenario: "iterating over a nil compressed set returns no ids", 844 function: testCompressedSetNil, 845 }, 846 { 847 scenario: "concatenating multiple compressed sets is supported", 848 function: testCompressedSetConcat, 849 }, 850 { 851 scenario: "duplicate ids are appear only once in the compressed set", 852 function: testCompressedSetDuplicates, 853 }, 854 { 855 scenario: "building a compressed set with a single id repeated multiple times produces the id only once", 856 function: testCompressedSetSingle, 857 }, 858 { 859 scenario: "iterating over a compressed sequence returns the full sequence", 860 function: testCompressedSetSequence, 861 }, 862 } 863 864 for _, test := range tests { 865 t.Run(test.scenario, test.function) 866 } 867 } 868 869 func testCompressedSetString(t *testing.T) { 870 id1, _ := Parse("0uHjRkQoL2JKAQIULPdqqb5fOkk") 871 id2, _ := Parse("0uHjRvkOG5CbtoXW5oCEp3L2xBu") 872 id3, _ := Parse("0uHjSJ4Pe5606kT2XWixK6dirlo") 873 874 set := Compress(id1, id2, id3) 875 876 if s := set.String(); s != `["0uHjRkQoL2JKAQIULPdqqb5fOkk", "0uHjRvkOG5CbtoXW5oCEp3L2xBu", "0uHjSJ4Pe5606kT2XWixK6dirlo"]` { 877 t.Error(s) 878 } 879 } 880 881 func testCompressedSetGoString(t *testing.T) { 882 id1, _ := Parse("0uHjRkQoL2JKAQIULPdqqb5fOkk") 883 id2, _ := Parse("0uHjRvkOG5CbtoXW5oCEp3L2xBu") 884 id3, _ := Parse("0uHjSJ4Pe5606kT2XWixK6dirlo") 885 886 set := Compress(id1, id2, id3) 887 888 if s := set.GoString(); s != `ksuid.CompressedSet{"0uHjRkQoL2JKAQIULPdqqb5fOkk", "0uHjRvkOG5CbtoXW5oCEp3L2xBu", "0uHjSJ4Pe5606kT2XWixK6dirlo"}` { 889 t.Error(s) 890 } 891 } 892 893 func testCompressedSetSparse(t *testing.T) { 894 now := time.Now() 895 896 times := [100]time.Time{} 897 for i := range times { 898 times[i] = now.Add(time.Duration(i) * 2 * time.Second) 899 } 900 901 ksuids := [1000]KSUID{} 902 for i := range ksuids { 903 ksuids[i], _ = NewRandomWithTime(times[i%len(times)]) 904 } 905 906 set := Compress(ksuids[:]...) 907 908 for i, it := 0, set.Iter(); it.Next(); { 909 if i >= len(ksuids) { 910 t.Error("too many KSUIDs were produced by the set iterator") 911 break 912 } 913 if ksuids[i] != it.KSUID { 914 t.Errorf("bad KSUID at index %d: expected %s but found %s", i, ksuids[i], it.KSUID) 915 } 916 i++ 917 } 918 919 reportCompressionRatio(t, ksuids[:], set) 920 } 921 922 func testCompressedSetPacked(t *testing.T) { 923 sequences := [10]Sequence{} 924 for i := range sequences { 925 sequences[i] = Sequence{Seed: New()} 926 } 927 928 ksuids := [1000]KSUID{} 929 for i := range ksuids { 930 ksuids[i], _ = sequences[i%len(sequences)].Next() 931 } 932 933 set := Compress(ksuids[:]...) 934 935 for i, it := 0, set.Iter(); it.Next(); { 936 if i >= len(ksuids) { 937 t.Error("too many KSUIDs were produced by the set iterator") 938 break 939 } 940 if ksuids[i] != it.KSUID { 941 t.Errorf("bad KSUID at index %d: expected %s but found %s", i, ksuids[i], it.KSUID) 942 } 943 i++ 944 } 945 946 reportCompressionRatio(t, ksuids[:], set) 947 } 948 949 func testCompressedSetMixed(t *testing.T) { 950 now := time.Now() 951 952 times := [20]time.Time{} 953 for i := range times { 954 times[i] = now.Add(time.Duration(i) * 2 * time.Second) 955 } 956 957 sequences := [200]Sequence{} 958 for i := range sequences { 959 seed, _ := NewRandomWithTime(times[i%len(times)]) 960 sequences[i] = Sequence{Seed: seed} 961 } 962 963 ksuids := [1000]KSUID{} 964 for i := range ksuids { 965 ksuids[i], _ = sequences[i%len(sequences)].Next() 966 } 967 968 set := Compress(ksuids[:]...) 969 970 for i, it := 0, set.Iter(); it.Next(); { 971 if i >= len(ksuids) { 972 t.Error("too many KSUIDs were produced by the set iterator") 973 break 974 } 975 if ksuids[i] != it.KSUID { 976 t.Errorf("bad KSUID at index %d: expected %s but found %s", i, ksuids[i], it.KSUID) 977 } 978 i++ 979 } 980 981 reportCompressionRatio(t, ksuids[:], set) 982 } 983 984 func testCompressedSetDuplicates(t *testing.T) { 985 sequence := Sequence{Seed: New()} 986 987 ksuids := [1000]KSUID{} 988 for i := range ksuids[:10] { 989 ksuids[i], _ = sequence.Next() // exercise dedupe on the id range code path 990 } 991 for i := range ksuids[10:] { 992 ksuids[i+10] = New() 993 } 994 for i := 1; i < len(ksuids); i += 4 { 995 ksuids[i] = ksuids[i-1] // generate many dupes 996 } 997 998 miss := make(map[KSUID]struct{}) 999 uniq := make(map[KSUID]struct{}) 1000 1001 for _, id := range ksuids { 1002 miss[id] = struct{}{} 1003 } 1004 1005 set := Compress(ksuids[:]...) 1006 1007 for it := set.Iter(); it.Next(); { 1008 if _, dupe := uniq[it.KSUID]; dupe { 1009 t.Errorf("duplicate id found in compressed set: %s", it.KSUID) 1010 } 1011 uniq[it.KSUID] = struct{}{} 1012 delete(miss, it.KSUID) 1013 } 1014 1015 if len(miss) != 0 { 1016 t.Error("some ids were not found in the compressed set:") 1017 for id := range miss { 1018 t.Log(id) 1019 } 1020 } 1021 } 1022 1023 func testCompressedSetSingle(t *testing.T) { 1024 id := New() 1025 1026 set := Compress( 1027 id, id, id, id, id, id, id, id, id, id, 1028 id, id, id, id, id, id, id, id, id, id, 1029 id, id, id, id, id, id, id, id, id, id, 1030 id, id, id, id, id, id, id, id, id, id, 1031 ) 1032 1033 n := 0 1034 1035 for it := set.Iter(); it.Next(); { 1036 if n != 0 { 1037 t.Errorf("too many ids found in the compressed set: %s", it.KSUID) 1038 } else if id != it.KSUID { 1039 t.Errorf("invalid id found in the compressed set: %s != %s", it.KSUID, id) 1040 } 1041 n++ 1042 } 1043 1044 if n == 0 { 1045 t.Error("no ids were produced by the compressed set") 1046 } 1047 } 1048 1049 func testCompressedSetSequence(t *testing.T) { 1050 seq := Sequence{Seed: New()} 1051 1052 ids := make([]KSUID, 5) 1053 1054 for i := 0; i < 5; i++ { 1055 ids[i], _ = seq.Next() 1056 } 1057 1058 iter := Compress(ids...).Iter() 1059 1060 index := 0 1061 for iter.Next() { 1062 if iter.KSUID != ids[index] { 1063 t.Errorf("mismatched id at index %d: %s != %s", index, iter.KSUID, ids[index]) 1064 } 1065 index++ 1066 } 1067 1068 if index != 5 { 1069 t.Errorf("Expected 5 ids, got %d", index) 1070 } 1071 } 1072 1073 func testCompressedSetNil(t *testing.T) { 1074 set := CompressedSet(nil) 1075 1076 for it := set.Iter(); it.Next(); { 1077 t.Errorf("too many ids returned by the iterator of a nil compressed set: %s", it.KSUID) 1078 } 1079 } 1080 1081 func testCompressedSetConcat(t *testing.T) { 1082 ksuids := [100]KSUID{} 1083 1084 for i := range ksuids { 1085 ksuids[i] = New() 1086 } 1087 1088 set := CompressedSet(nil) 1089 set = AppendCompressed(set, ksuids[:42]...) 1090 set = AppendCompressed(set, ksuids[42:64]...) 1091 set = AppendCompressed(set, ksuids[64:]...) 1092 1093 for i, it := 0, set.Iter(); it.Next(); i++ { 1094 if ksuids[i] != it.KSUID { 1095 t.Errorf("invalid ID at index %d: %s != %s", i, ksuids[i], it.KSUID) 1096 } 1097 } 1098 } 1099 1100 func reportCompressionRatio(t *testing.T, ksuids []KSUID, set CompressedSet) { 1101 len1 := byteLength * len(ksuids) 1102 len2 := len(set) 1103 t.Logf("original %d B, compressed %d B (%.4g%%)", len1, len2, 100*(1-(float64(len2)/float64(len1)))) 1104 } 1105 1106 func BenchmarkCompressedSet(b *testing.B) { 1107 ksuids1 := [1000]KSUID{} 1108 ksuids2 := [1000]KSUID{} 1109 1110 for i := range ksuids1 { 1111 ksuids1[i] = New() 1112 } 1113 1114 ksuids2 = ksuids1 1115 buf := make([]byte, 0, 1024) 1116 set := Compress(ksuids2[:]...) 1117 1118 b.Run("write", func(b *testing.B) { 1119 n := 0 1120 for i := 0; i != b.N; i++ { 1121 ksuids2 = ksuids1 1122 buf = AppendCompressed(buf[:0], ksuids2[:]...) 1123 n = len(buf) 1124 } 1125 b.SetBytes(int64(n + len(ksuids2))) 1126 }) 1127 1128 b.Run("read", func(b *testing.B) { 1129 n := 0 1130 for i := 0; i != b.N; i++ { 1131 n = 0 1132 for it := set.Iter(); true; { 1133 if !it.Next() { 1134 n++ 1135 break 1136 } 1137 } 1138 } 1139 b.SetBytes(int64((n * byteLength) + len(set))) 1140 }) 1141 }