common/ulid/ulid_test.go

337 lines
7.1 KiB
Go

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)
}
}