@@ -23,7 +23,9 @@ package uuid
2323import (
2424 "bytes"
2525 "fmt"
26+ "sync"
2627 "testing"
28+ "time"
2729)
2830
2931func TestUUID (t * testing.T ) {
@@ -224,3 +226,300 @@ func BenchmarkNewV7(b *testing.B) {
224226 _ = Must (NewV7 ())
225227 }
226228}
229+
230+ func TestConcurrentGeneration (t * testing.T ) {
231+ const numGoroutines = 100
232+ const numUUIDs = 100
233+
234+ var wg sync.WaitGroup
235+ results := make ([][]UUID , numGoroutines )
236+
237+ for i := 0 ; i < numGoroutines ; i ++ {
238+ wg .Add (1 )
239+ go func (idx int ) {
240+ defer wg .Done ()
241+ uuids := make ([]UUID , numUUIDs )
242+ for j := 0 ; j < numUUIDs ; j ++ {
243+ u , err := NewV4 ()
244+ if err != nil {
245+ t .Errorf ("NewV4() failed in goroutine %d: %v" , idx , err )
246+ return
247+ }
248+ uuids [j ] = u
249+ }
250+ results [idx ] = uuids
251+ }(i )
252+ }
253+
254+ wg .Wait ()
255+
256+ // Check all UUIDs are unique across all goroutines
257+ seen := make (map [UUID ]bool )
258+ for i , uuids := range results {
259+ if uuids == nil {
260+ continue // goroutine failed
261+ }
262+ for j , u := range uuids {
263+ if seen [u ] {
264+ t .Errorf ("Duplicate UUID found: %s (goroutine %d, index %d)" , u , i , j )
265+ }
266+ seen [u ] = true
267+ }
268+ }
269+ }
270+
271+ func TestV7ConcurrentGeneration (t * testing.T ) {
272+ const numGoroutines = 10
273+ const numUUIDs = 100
274+
275+ var wg sync.WaitGroup
276+ results := make ([][]UUID , numGoroutines )
277+
278+ for i := 0 ; i < numGoroutines ; i ++ {
279+ wg .Add (1 )
280+ go func (idx int ) {
281+ defer wg .Done ()
282+ uuids := make ([]UUID , numUUIDs )
283+ for j := 0 ; j < numUUIDs ; j ++ {
284+ u , err := NewV7 ()
285+ if err != nil {
286+ t .Errorf ("NewV7() failed in goroutine %d: %v" , idx , err )
287+ return
288+ }
289+ uuids [j ] = u
290+ }
291+ results [idx ] = uuids
292+ }(i )
293+ }
294+
295+ wg .Wait ()
296+
297+ // Check all UUIDs are unique and timestamps are reasonable
298+ seen := make (map [UUID ]bool )
299+ for i , uuids := range results {
300+ if uuids == nil {
301+ continue
302+ }
303+ for j , u := range uuids {
304+ if seen [u ] {
305+ t .Errorf ("Duplicate V7 UUID found: %s (goroutine %d, index %d)" , u , i , j )
306+ }
307+ seen [u ] = true
308+
309+ // Verify timestamp extraction works
310+ if _ , err := TimestampFromV7 (u ); err != nil {
311+ t .Errorf ("Failed to extract timestamp from V7 UUID %s: %v" , u , err )
312+ }
313+ }
314+ }
315+ }
316+
317+ func TestCustomEpochFunc (t * testing.T ) {
318+ fixedTime := time .Date (2023 , 1 , 1 , 0 , 0 , 0 , 0 , time .UTC )
319+
320+ gen := & Gen {
321+ epochFunc : func () time.Time { return fixedTime },
322+ }
323+
324+ u1 , err := gen .NewV7 ()
325+ if err != nil {
326+ t .Fatalf ("NewV7() failed: %v" , err )
327+ }
328+
329+ u2 , err := gen .NewV7 ()
330+ if err != nil {
331+ t .Fatalf ("NewV7() failed: %v" , err )
332+ }
333+
334+ ts1 , _ := TimestampFromV7 (u1 )
335+ ts2 , _ := TimestampFromV7 (u2 )
336+
337+ expectedTS := uint64 (fixedTime .UnixMilli ())
338+ if uint64 (ts1 ) != expectedTS {
339+ t .Errorf ("First V7 UUID has timestamp %d, want %d" , ts1 , expectedTS )
340+ }
341+ if uint64 (ts2 ) != expectedTS {
342+ t .Errorf ("Second V7 UUID has timestamp %d, want %d" , ts2 , expectedTS )
343+ }
344+
345+ // With same timestamp, clock sequence should increment
346+ if u1 == u2 {
347+ t .Errorf ("Two V7 UUIDs with same timestamp should be different due to clock sequence" )
348+ }
349+ }
350+
351+ func TestV7RapidGeneration (t * testing.T ) {
352+ // Generate many UUIDs rapidly in same millisecond to test clock sequence behavior
353+ uuids := make ([]UUID , 1000 )
354+ for i := 0 ; i < 1000 ; i ++ {
355+ u , err := NewV7 ()
356+ if err != nil {
357+ t .Fatalf ("NewV7() failed at iteration %d: %v" , i , err )
358+ }
359+ uuids [i ] = u
360+ }
361+
362+ // All should be unique
363+ seen := make (map [UUID ]bool )
364+ for i , u := range uuids {
365+ if seen [u ] {
366+ t .Errorf ("Duplicate V7 UUID at index %d: %s" , i , u )
367+ }
368+ seen [u ] = true
369+ }
370+
371+ // Timestamps should be monotonic (non-decreasing)
372+ var lastTS int64
373+ for i , u := range uuids {
374+ ts , err := TimestampFromV7 (u )
375+ if err != nil {
376+ t .Errorf ("Failed to extract timestamp from UUID %d: %v" , i , err )
377+ continue
378+ }
379+ if ts < lastTS {
380+ t .Errorf ("V7 timestamp went backwards at index %d: %d < %d" , i , ts , lastTS )
381+ }
382+ lastTS = ts
383+ }
384+ }
385+
386+ func TestV4Correctness (t * testing.T ) {
387+ for i := 0 ; i < 1000 ; i ++ {
388+ u , err := NewV4 ()
389+ if err != nil {
390+ t .Fatalf ("NewV4() failed: %v" , err )
391+ }
392+
393+ // Check version
394+ if u .Version () != V4 {
395+ t .Errorf ("UUID %s has version %d, want %d" , u , u .Version (), V4 )
396+ }
397+
398+ // Check variant
399+ if u .Variant () != VariantRFC4122 {
400+ t .Errorf ("UUID %s has variant %d, want %d" , u , u .Variant (), VariantRFC4122 )
401+ }
402+
403+ // Check not nil
404+ if u .IsNil () {
405+ t .Errorf ("UUID should not be nil: %s" , u )
406+ }
407+
408+ // Check string format
409+ s := u .String ()
410+ if len (s ) != 36 {
411+ t .Errorf ("UUID string %s has length %d, want 36" , s , len (s ))
412+ }
413+
414+ // Check parsing back
415+ parsed , err := FromString (s )
416+ if err != nil {
417+ t .Errorf ("Failed to parse UUID string %s: %v" , s , err )
418+ }
419+ if parsed != u {
420+ t .Errorf ("Parsed UUID %s != original %s" , parsed , u )
421+ }
422+ }
423+ }
424+
425+ func TestV7Correctness (t * testing.T ) {
426+ var lastTime int64
427+
428+ for i := 0 ; i < 100 ; i ++ {
429+ u , err := NewV7 ()
430+ if err != nil {
431+ t .Fatalf ("NewV7() failed: %v" , err )
432+ }
433+
434+ // Check version
435+ if u .Version () != V7 {
436+ t .Errorf ("UUID %s has version %d, want %d" , u , u .Version (), V7 )
437+ }
438+
439+ // Check variant
440+ if u .Variant () != VariantRFC4122 {
441+ t .Errorf ("UUID %s has variant %d, want %d" , u , u .Variant (), VariantRFC4122 )
442+ }
443+
444+ // Check timestamp extraction
445+ ts , err := TimestampFromV7 (u )
446+ if err != nil {
447+ t .Errorf ("Failed to extract timestamp from V7 UUID %s: %v" , u , err )
448+ }
449+
450+ // Check timestamp is reasonable (within last hour and future)
451+ now := time .Now ().UnixMilli ()
452+ if ts < now - 3600000 || ts > now + 1000 {
453+ t .Errorf ("V7 UUID %s has unreasonable timestamp %d (now=%d)" , u , ts , now )
454+ }
455+
456+ // Check timestamps are non-decreasing (monotonic)
457+ if ts < lastTime {
458+ t .Errorf ("V7 UUID timestamp went backwards: %d < %d" , ts , lastTime )
459+ }
460+ lastTime = ts
461+
462+ time .Sleep (1 * time .Millisecond ) // Ensure different timestamps
463+ }
464+ }
465+
466+ func TestUUIDUniqueness (t * testing.T ) {
467+ seen := make (map [UUID ]bool )
468+
469+ // Test V4 uniqueness
470+ for i := 0 ; i < 10000 ; i ++ {
471+ u , err := NewV4 ()
472+ if err != nil {
473+ t .Fatalf ("NewV4() failed: %v" , err )
474+ }
475+ if seen [u ] {
476+ t .Errorf ("Duplicate V4 UUID generated: %s" , u )
477+ }
478+ seen [u ] = true
479+ }
480+
481+ // Test V7 uniqueness
482+ for i := 0 ; i < 1000 ; i ++ {
483+ u , err := NewV7 ()
484+ if err != nil {
485+ t .Fatalf ("NewV7() failed: %v" , err )
486+ }
487+ if seen [u ] {
488+ t .Errorf ("Duplicate V7 UUID generated: %s" , u )
489+ }
490+ seen [u ] = true
491+ }
492+ }
493+
494+ func TestStringFormats (t * testing.T ) {
495+ u , err := NewV4 ()
496+ if err != nil {
497+ t .Fatalf ("NewV4() failed: %v" , err )
498+ }
499+
500+ // Test canonical format
501+ canonical := u .Format (FormatCanonical )
502+ if len (canonical ) != 36 {
503+ t .Errorf ("Canonical format has wrong length: %d" , len (canonical ))
504+ }
505+ if canonical [8 ] != '-' || canonical [13 ] != '-' || canonical [18 ] != '-' || canonical [23 ] != '-' {
506+ t .Errorf ("Canonical format missing dashes: %s" , canonical )
507+ }
508+
509+ // Test hash format
510+ hash := u .Format (FormatHash )
511+ if len (hash ) != 32 {
512+ t .Errorf ("Hash format has wrong length: %d" , len (hash ))
513+ }
514+
515+ // Test base58 format
516+ base58 := u .Format (FormatBase58 )
517+ if len (base58 ) == 0 {
518+ t .Errorf ("Base58 format is empty" )
519+ }
520+
521+ // Test that String() matches canonical
522+ if u .String () != canonical {
523+ t .Errorf ("String() != Format(FormatCanonical): %s vs %s" , u .String (), canonical )
524+ }
525+ }
0 commit comments