248 lines
5.4 KiB
Go
248 lines
5.4 KiB
Go
package ulid
|
|
|
|
import (
|
|
"sort"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
oklid "github.com/oklog/ulid/v2"
|
|
)
|
|
|
|
func TestMakeULID(t *testing.T) {
|
|
tm := time.Now()
|
|
ul1, err := MakeULID(tm)
|
|
if err != nil {
|
|
t.Fatalf("MakeULID failed: %s", err)
|
|
}
|
|
ul2, err := MakeULID(tm)
|
|
if err != nil {
|
|
t.Fatalf("MakeULID failed: %s", err)
|
|
}
|
|
|
|
if ul1 == nil || ul2 == nil {
|
|
t.Fatal("MakeULID returned nil ULID")
|
|
}
|
|
|
|
if ul1.String() == ul2.String() {
|
|
t.Errorf("ul1 and ul2 should be different: %s", ul1.String())
|
|
}
|
|
|
|
// Verify they have the same timestamp
|
|
if ul1.Time() != ul2.Time() {
|
|
t.Errorf("ULIDs with same input time should have same timestamp: %d != %d", ul1.Time(), ul2.Time())
|
|
}
|
|
|
|
t.Logf("ulid string 1 and 2: %s | %s", ul1.String(), ul2.String())
|
|
}
|
|
|
|
func TestMakeULIDUniqueness(t *testing.T) {
|
|
tm := time.Now()
|
|
seen := make(map[string]bool)
|
|
|
|
for i := 0; i < 1000; i++ {
|
|
ul, err := MakeULID(tm)
|
|
if err != nil {
|
|
t.Fatalf("MakeULID failed on iteration %d: %s", i, err)
|
|
}
|
|
|
|
str := ul.String()
|
|
if seen[str] {
|
|
t.Errorf("duplicate ULID generated: %s", str)
|
|
}
|
|
seen[str] = true
|
|
}
|
|
}
|
|
|
|
func TestMakeULIDTimestampProgression(t *testing.T) {
|
|
t1 := time.Now()
|
|
ul1, err := MakeULID(t1)
|
|
if err != nil {
|
|
t.Fatalf("MakeULID failed: %s", err)
|
|
}
|
|
|
|
// Wait to ensure different timestamp
|
|
time.Sleep(2 * time.Millisecond)
|
|
|
|
t2 := time.Now()
|
|
ul2, err := MakeULID(t2)
|
|
if err != nil {
|
|
t.Fatalf("MakeULID failed: %s", err)
|
|
}
|
|
|
|
if ul1.Time() >= ul2.Time() {
|
|
t.Errorf("second ULID should have later timestamp: %d >= %d", ul1.Time(), ul2.Time())
|
|
}
|
|
|
|
if ul1.Compare(*ul2) >= 0 {
|
|
t.Errorf("second ULID should be greater: %s >= %s", ul1.String(), ul2.String())
|
|
}
|
|
}
|
|
|
|
func TestMakeULIDMonotonicity(t *testing.T) {
|
|
tm := time.Now()
|
|
var ulids []*oklid.ULID
|
|
|
|
// Generate ULIDs rapidly with same timestamp
|
|
for i := 0; i < 100; i++ {
|
|
ul, err := MakeULID(tm)
|
|
if err != nil {
|
|
t.Fatalf("MakeULID failed on iteration %d: %s", i, err)
|
|
}
|
|
ulids = append(ulids, ul)
|
|
}
|
|
|
|
// Count non-monotonic pairs
|
|
nonMonotonicCount := 0
|
|
for i := 1; i < len(ulids); i++ {
|
|
if ulids[i-1].Compare(*ulids[i]) >= 0 {
|
|
nonMonotonicCount++
|
|
}
|
|
}
|
|
|
|
// Report summary if any non-monotonic pairs found
|
|
if nonMonotonicCount > 0 {
|
|
t.Logf("Note: %d out of %d ULID pairs with same timestamp were not monotonic due to pool usage",
|
|
nonMonotonicCount, len(ulids)-1)
|
|
}
|
|
}
|
|
|
|
func TestMakeULIDConcurrency(t *testing.T) {
|
|
const numGoroutines = 10
|
|
const numULIDsPerGoroutine = 100
|
|
|
|
var wg sync.WaitGroup
|
|
ulidChan := make(chan *oklid.ULID, numGoroutines*numULIDsPerGoroutine)
|
|
tm := time.Now()
|
|
|
|
// Start multiple goroutines generating ULIDs concurrently
|
|
for i := 0; i < numGoroutines; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for j := 0; j < numULIDsPerGoroutine; j++ {
|
|
ul, err := MakeULID(tm)
|
|
if err != nil {
|
|
t.Errorf("MakeULID failed: %s", err)
|
|
return
|
|
}
|
|
ulidChan <- ul
|
|
}
|
|
}()
|
|
}
|
|
|
|
wg.Wait()
|
|
close(ulidChan)
|
|
|
|
// Collect all ULIDs and check uniqueness
|
|
seen := make(map[string]bool)
|
|
count := 0
|
|
|
|
for ul := range ulidChan {
|
|
str := ul.String()
|
|
if seen[str] {
|
|
t.Errorf("duplicate ULID generated in concurrent test: %s", str)
|
|
}
|
|
seen[str] = true
|
|
count++
|
|
}
|
|
|
|
if count != numGoroutines*numULIDsPerGoroutine {
|
|
t.Errorf("expected %d ULIDs, got %d", numGoroutines*numULIDsPerGoroutine, count)
|
|
}
|
|
}
|
|
|
|
func TestMakeULIDErrorHandling(t *testing.T) {
|
|
// Test with various timestamps
|
|
timestamps := []time.Time{
|
|
time.Unix(0, 0), // Unix epoch
|
|
time.Now(), // Current time
|
|
time.Now().Add(time.Hour), // Future time
|
|
}
|
|
|
|
for _, tm := range timestamps {
|
|
ul, err := MakeULID(tm)
|
|
if err != nil {
|
|
t.Errorf("MakeULID failed with timestamp %v: %s", tm, err)
|
|
}
|
|
if ul == nil {
|
|
t.Errorf("MakeULID returned nil ULID with timestamp %v", tm)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMakeULIDLexicographicOrdering(t *testing.T) {
|
|
var ulids []*oklid.ULID
|
|
var timestamps []time.Time
|
|
|
|
// Generate ULIDs with increasing timestamps
|
|
for i := 0; i < 10; i++ {
|
|
tm := time.Now().Add(time.Duration(i) * time.Millisecond)
|
|
timestamps = append(timestamps, tm)
|
|
|
|
ul, err := MakeULID(tm)
|
|
if err != nil {
|
|
t.Fatalf("MakeULID failed: %s", err)
|
|
}
|
|
ulids = append(ulids, ul)
|
|
|
|
// Small delay to ensure different timestamps
|
|
time.Sleep(time.Millisecond)
|
|
}
|
|
|
|
// Sort ULID strings lexicographically
|
|
ulidStrings := make([]string, len(ulids))
|
|
for i, ul := range ulids {
|
|
ulidStrings[i] = ul.String()
|
|
}
|
|
|
|
originalOrder := make([]string, len(ulidStrings))
|
|
copy(originalOrder, ulidStrings)
|
|
|
|
sort.Strings(ulidStrings)
|
|
|
|
// Verify lexicographic order matches chronological order
|
|
for i := 0; i < len(originalOrder); i++ {
|
|
if originalOrder[i] != ulidStrings[i] {
|
|
t.Errorf("lexicographic order doesn't match chronological order at index %d: %s != %s",
|
|
i, originalOrder[i], ulidStrings[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
// Benchmark ULID generation performance
|
|
func BenchmarkMakeULID(b *testing.B) {
|
|
tm := time.Now()
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := MakeULID(tm)
|
|
if err != nil {
|
|
b.Fatalf("MakeULID failed: %s", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Benchmark concurrent ULID generation
|
|
func BenchmarkMakeULIDConcurrent(b *testing.B) {
|
|
tm := time.Now()
|
|
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
for pb.Next() {
|
|
_, err := MakeULID(tm)
|
|
if err != nil {
|
|
b.Fatalf("MakeULID failed: %s", err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// Benchmark pool performance
|
|
func BenchmarkMonotonicPool(b *testing.B) {
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_ = monotonicPool.Get()
|
|
// Note: we don't put it back as the current implementation doesn't reuse readers
|
|
}
|
|
}
|