package ulid import ( cryptorand "crypto/rand" "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 TestMake(t *testing.T) { // Test Make() function (uses current time) ul1, err := Make() if err != nil { t.Fatalf("Make failed: %s", err) } if ul1 == nil { t.Fatal("Make returned nil ULID") } // Sleep a bit and generate another time.Sleep(2 * time.Millisecond) ul2, err := Make() if err != nil { t.Fatalf("Make failed: %s", err) } // Should be different ULIDs if ul1.String() == ul2.String() { t.Errorf("ULIDs from Make() should be different: %s", ul1.String()) } // Second should be later (or at least not earlier) if ul1.Time() > ul2.Time() { t.Errorf("second ULID should not have earlier timestamp: %d > %d", ul1.Time(), ul2.Time()) } t.Logf("Make() ULIDs: %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 TestMakeUniqueness(t *testing.T) { seen := make(map[string]bool) for i := 0; i < 1000; i++ { ul, err := Make() if err != nil { t.Fatalf("Make 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 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 TestMakeConcurrency(t *testing.T) { const numGoroutines = 10 const numULIDsPerGoroutine = 100 var wg sync.WaitGroup ulidChan := make(chan *oklid.ULID, numGoroutines*numULIDsPerGoroutine) // 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 := Make() if err != nil { t.Errorf("Make 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 i, tm := range timestamps { ul, err := MakeULID(tm) if err != nil { t.Errorf("MakeULID failed with timestamp %d: %s", i, err) } if ul == nil { t.Errorf("MakeULID returned nil ULID with timestamp %d", i) } } } 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 Make function func BenchmarkMake(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { _, err := Make() if err != nil { b.Fatalf("Make 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 concurrent Make function func BenchmarkMakeConcurrent(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { _, err := Make() if err != nil { b.Fatalf("Make failed: %s", err) } } }) } // Benchmark random number generation func BenchmarkCryptoRand(b *testing.B) { buf := make([]byte, 10) // ULID entropy size b.ResetTimer() for i := 0; i < b.N; i++ { cryptorand.Read(buf) } }