From 005951d4003282fcb5ce3644a29d2bbbc62baee0 Mon Sep 17 00:00:00 2001 From: talis Date: Wed, 20 Feb 2019 09:43:18 +0200 Subject: [PATCH 1/5] added source to enable concurrent reproducible usage --- uuid_source.go | 40 +++++++++++++++++++++++++++++++++ uuid_source_test.go | 55 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 uuid_source.go create mode 100644 uuid_source_test.go diff --git a/uuid_source.go b/uuid_source.go new file mode 100644 index 0000000..211b052 --- /dev/null +++ b/uuid_source.go @@ -0,0 +1,40 @@ +package uuid + +import ( + "io" + "crypto/rand" +) + +type UuidSource struct { + rander io.Reader +} + +func NewSource(r io.Reader) UuidSource { + var uuidSource UuidSource + uuidSource.SetRand(r) + return uuidSource + +} + +func (uuidSource *UuidSource) SetRand(r io.Reader) { + if r == nil { + uuidSource.rander = rand.Reader + return + } + uuidSource.rander = r +} + +func (uuidSource UuidSource) NewRandom() (UUID, error) { + var uuid UUID + _, err := io.ReadFull(uuidSource.rander, uuid[:]) + if err != nil { + return Nil, err + } + uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4 + uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10 + return uuid, nil +} + +func (uuidSource UuidSource) New() UUID { + return Must(uuidSource.NewRandom()) +} diff --git a/uuid_source_test.go b/uuid_source_test.go new file mode 100644 index 0000000..40bf995 --- /dev/null +++ b/uuid_source_test.go @@ -0,0 +1,55 @@ +package uuid + +import ( + "testing" + "fmt" + "strings" + "math/rand" + +) + +func TestUuidSources(t *testing.T) { + + uuidSourceA := NewSource(rand.New(rand.NewSource(34576))) + uuidSourceB := NewSource(rand.New(rand.NewSource(34576))) + + var uuidStrA, uuidStrB string + fmt.Printf("Random values: ") + for i := 0; i < 10; i++ { + uuidStrA += uuidSourceA.New().String() + "." + } + fmt.Printf("%v\n", uuidStrA) + + fmt.Printf("Random values: ") + for i := 0; i < 10; i++ { + uuidStrB += uuidSourceB.New().String() + "." + } + fmt.Printf("%v\n", uuidStrB) + + if !strings.EqualFold(uuidStrA, uuidStrB) { + t.Error("Uuid values are not reproducaible!") + } + + uuidSourceA = NewSource(rand.New(rand.NewSource(66))) + uuidSourceB = NewSource(rand.New(rand.NewSource(77))) + + + uuidStrA = "" + uuidStrB = "" + fmt.Printf("Random values: ") + for i := 0; i < 10; i++ { + uuidStrA += uuidSourceA.New().String() + "." + } + fmt.Printf("%v\n", uuidStrA) + + fmt.Printf("Random values: ") + for i := 0; i < 10; i++ { + uuidStrB += uuidSourceB.New().String() + "." + } + fmt.Printf("%v\n", uuidStrB) + + if strings.EqualFold(uuidStrA, uuidStrB) { + t.Error("Uuid values should not be reproducaible!") + } + +} \ No newline at end of file From caad4a112366c90c62a8957be036ead8040f6602 Mon Sep 17 00:00:00 2001 From: talis Date: Tue, 26 Feb 2019 09:40:33 +0200 Subject: [PATCH 2/5] simplify test --- uuid_source_test.go | 53 ++++++++++++--------------------------------- 1 file changed, 14 insertions(+), 39 deletions(-) diff --git a/uuid_source_test.go b/uuid_source_test.go index 40bf995..646e605 100644 --- a/uuid_source_test.go +++ b/uuid_source_test.go @@ -1,55 +1,30 @@ package uuid import ( - "testing" - "fmt" - "strings" "math/rand" - + "testing" + "time" ) func TestUuidSources(t *testing.T) { - - uuidSourceA := NewSource(rand.New(rand.NewSource(34576))) - uuidSourceB := NewSource(rand.New(rand.NewSource(34576))) + currentTime := time.Now().UnixNano() + uuidSourceA := NewSource(rand.New(rand.NewSource(currentTime))) + uuidSourceB := NewSource(rand.New(rand.NewSource(currentTime))) - var uuidStrA, uuidStrB string - fmt.Printf("Random values: ") for i := 0; i < 10; i++ { - uuidStrA += uuidSourceA.New().String() + "." - } - fmt.Printf("%v\n", uuidStrA) - - fmt.Printf("Random values: ") - for i := 0; i < 10; i++ { - uuidStrB += uuidSourceB.New().String() + "." - } - fmt.Printf("%v\n", uuidStrB) - - if !strings.EqualFold(uuidStrA, uuidStrB) { - t.Error("Uuid values are not reproducaible!") + if uuidSourceA.New().String() != uuidSourceB.New().String() { + t.Error("Uuid values are not reproducaible!") + } } - uuidSourceA = NewSource(rand.New(rand.NewSource(66))) - uuidSourceB = NewSource(rand.New(rand.NewSource(77))) + uuidSourceA = NewSource(rand.New(rand.NewSource(123))) + uuidSourceB = NewSource(rand.New(rand.NewSource(456))) - uuidStrA = "" - uuidStrB = "" - fmt.Printf("Random values: ") - for i := 0; i < 10; i++ { - uuidStrA += uuidSourceA.New().String() + "." - } - fmt.Printf("%v\n", uuidStrA) - - fmt.Printf("Random values: ") for i := 0; i < 10; i++ { - uuidStrB += uuidSourceB.New().String() + "." + if uuidSourceA.New().String() == uuidSourceB.New().String() { + t.Error("Uuid values should not match!") + } } - fmt.Printf("%v\n", uuidStrB) - - if strings.EqualFold(uuidStrA, uuidStrB) { - t.Error("Uuid values should not be reproducaible!") - } - + } \ No newline at end of file From 63b66ddfe0ddd512d239ead433bc6124ae02a56b Mon Sep 17 00:00:00 2001 From: talis Date: Thu, 28 Feb 2019 14:59:24 +0200 Subject: [PATCH 3/5] reused version4 NewRandom() and added better testing --- uuid_source.go | 30 ++++++++++++++++++++++-------- uuid_source_test.go | 42 ++++++++++++++++++++++++++++++++++++------ uuid_test.go | 21 +++++++++++++++++++++ version4.go | 7 ++++++- 4 files changed, 85 insertions(+), 15 deletions(-) diff --git a/uuid_source.go b/uuid_source.go index 211b052..83a569c 100644 --- a/uuid_source.go +++ b/uuid_source.go @@ -5,10 +5,18 @@ import ( "crypto/rand" ) +// A UuidSource holds a random number generator and generates UUIDs using it as its source. +// +// It is useful when a process need its own random number generator, +// e.g. when running some processes concurrently. type UuidSource struct { rander io.Reader } +// Creates a new UuidSource which holds its own random number generator. +// +// Calling NewSource with nil sets the random number generator to a default +// generator. func NewSource(r io.Reader) UuidSource { var uuidSource UuidSource uuidSource.SetRand(r) @@ -16,6 +24,12 @@ func NewSource(r io.Reader) UuidSource { } +// SetRand sets the random number generator of the UuidSource to r, which implements io.Reader. +// If r.Read returns an error when the package requests random data then +// a panic will be issued. +// +// Calling SetRand with nil sets the random number generator to a default +// generator. func (uuidSource *UuidSource) SetRand(r io.Reader) { if r == nil { uuidSource.rander = rand.Reader @@ -24,17 +38,17 @@ func (uuidSource *UuidSource) SetRand(r io.Reader) { uuidSource.rander = r } +// NewRandom returns a Random (Version 4) UUID based on the random number generator in the UuidSource. +// +// See more detailed explanation here: https://godoc.org/github.com/google/uuid#NewRandom func (uuidSource UuidSource) NewRandom() (UUID, error) { - var uuid UUID - _, err := io.ReadFull(uuidSource.rander, uuid[:]) - if err != nil { - return Nil, err - } - uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4 - uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10 - return uuid, nil + return newRandom(uuidSource.rander) } +// New creates a new random UUID based on the random number generator in the UuidSource or panics. New is equivalent to +// the expression +// +// uuid.Must(uuid.NewRandom()) func (uuidSource UuidSource) New() UUID { return Must(uuidSource.NewRandom()) } diff --git a/uuid_source_test.go b/uuid_source_test.go index 646e605..762146d 100644 --- a/uuid_source_test.go +++ b/uuid_source_test.go @@ -7,23 +7,53 @@ import ( ) func TestUuidSources(t *testing.T) { + + // Two identical sources, should give same sequence currentTime := time.Now().UnixNano() uuidSourceA := NewSource(rand.New(rand.NewSource(currentTime))) uuidSourceB := NewSource(rand.New(rand.NewSource(currentTime))) for i := 0; i < 10; i++ { - if uuidSourceA.New().String() != uuidSourceB.New().String() { - t.Error("Uuid values are not reproducaible!") + uuid1 := uuidSourceA.New() + uuid2 := uuidSourceB.New() + if uuid1 != uuid2 { + t.Errorf("expected duplicates, got %q and %q", uuid1, uuid2) } } - uuidSourceA = NewSource(rand.New(rand.NewSource(123))) - uuidSourceB = NewSource(rand.New(rand.NewSource(456))) + // Set rander with nil, each source will be random + uuidSourceA.SetRand(nil) + uuidSourceB.SetRand(nil) + + for i := 0; i < 10; i++ { + uuid1 := uuidSourceA.New() + uuid2 := uuidSourceB.New() + if uuid1 == uuid2 { + t.Errorf("unexpected duplicates, got %q", uuid1) + } + } + // Set rander to rand source with same seed, should give same sequence + uuidSourceA.SetRand(rand.New(rand.NewSource(123))) + uuidSourceB.SetRand(rand.New(rand.NewSource(123))) + + for i := 0; i < 10; i++ { + uuid1 := uuidSourceA.New() + uuid2 := uuidSourceB.New() + if uuid1 != uuid2 { + t.Errorf("expected duplicates, got %q and %q", uuid1, uuid2) + } + } + + // Set rander to rand source with different seeds, should not give same sequence + uuidSourceA.SetRand(rand.New(rand.NewSource(456))) + uuidSourceB.SetRand(rand.New(rand.NewSource(789))) for i := 0; i < 10; i++ { - if uuidSourceA.New().String() == uuidSourceB.New().String() { - t.Error("Uuid values should not match!") + uuid1 := uuidSourceA.New() + uuid2 := uuidSourceB.New() + if uuid1 == uuid2 { + t.Errorf("unexpected duplicates, got %q", uuid1) } } diff --git a/uuid_test.go b/uuid_test.go index e7876f1..ff17ff3 100644 --- a/uuid_test.go +++ b/uuid_test.go @@ -13,6 +13,8 @@ import ( "testing" "time" "unsafe" + + "math/rand" ) type test struct { @@ -479,6 +481,25 @@ func TestBadRand(t *testing.T) { } } +func TestSetRand(t *testing.T) { + SetRand(rand.New(rand.NewSource(456))) + uuid1 := New() + uuid2 := New() + + SetRand(rand.New(rand.NewSource(456))) + uuid3 := New() + uuid4 := New() + + if uuid1 != uuid3 { + t.Errorf("expected duplicates, got %q and %q", uuid1, uuid3) + } + if uuid2 != uuid4 { + t.Errorf("expected duplicates, got %q and %q", uuid2, uuid4) + } + + +} + var asString = "f47ac10b-58cc-0372-8567-0e02b2c3d479" var asBytes = []byte(asString) diff --git a/version4.go b/version4.go index 84af91c..66acd62 100644 --- a/version4.go +++ b/version4.go @@ -27,8 +27,12 @@ func New() UUID { // equivalent to the odds of creating a few tens of trillions of UUIDs in a // year and having one duplicate. func NewRandom() (UUID, error) { + return newRandom(rander) +} + +func newRandom(r io.Reader) (UUID, error) { var uuid UUID - _, err := io.ReadFull(rander, uuid[:]) + _, err := io.ReadFull(r, uuid[:]) if err != nil { return Nil, err } @@ -36,3 +40,4 @@ func NewRandom() (UUID, error) { uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10 return uuid, nil } + From e23e7efa9b1c867fef2ff373e512d36343c66dd1 Mon Sep 17 00:00:00 2001 From: talis Date: Thu, 28 Feb 2019 16:21:34 +0200 Subject: [PATCH 4/5] use strings.NewReader() instead of math/rand.Rand --- uuid_source_test.go | 20 +++++++++++--------- uuid_test.go | 8 ++++---- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/uuid_source_test.go b/uuid_source_test.go index 762146d..b634c6a 100644 --- a/uuid_source_test.go +++ b/uuid_source_test.go @@ -1,17 +1,19 @@ package uuid import ( - "math/rand" "testing" - "time" + "strings" + + regen "github.com/zach-klippenstein/goregen" ) func TestUuidSources(t *testing.T) { + myString, _ := regen.Generate("[a-zA-Z]{1000}") + // Two identical sources, should give same sequence - currentTime := time.Now().UnixNano() - uuidSourceA := NewSource(rand.New(rand.NewSource(currentTime))) - uuidSourceB := NewSource(rand.New(rand.NewSource(currentTime))) + uuidSourceA := NewSource(strings.NewReader(myString)) + uuidSourceB := NewSource(strings.NewReader(myString)) for i := 0; i < 10; i++ { uuid1 := uuidSourceA.New() @@ -34,8 +36,8 @@ func TestUuidSources(t *testing.T) { } // Set rander to rand source with same seed, should give same sequence - uuidSourceA.SetRand(rand.New(rand.NewSource(123))) - uuidSourceB.SetRand(rand.New(rand.NewSource(123))) + uuidSourceA.SetRand(strings.NewReader(myString)) + uuidSourceB.SetRand(strings.NewReader(myString)) for i := 0; i < 10; i++ { uuid1 := uuidSourceA.New() @@ -46,8 +48,8 @@ func TestUuidSources(t *testing.T) { } // Set rander to rand source with different seeds, should not give same sequence - uuidSourceA.SetRand(rand.New(rand.NewSource(456))) - uuidSourceB.SetRand(rand.New(rand.NewSource(789))) + uuidSourceA.SetRand(strings.NewReader("456" + myString)) + uuidSourceB.SetRand(strings.NewReader("myString" + myString)) for i := 0; i < 10; i++ { uuid1 := uuidSourceA.New() diff --git a/uuid_test.go b/uuid_test.go index ff17ff3..40f59cb 100644 --- a/uuid_test.go +++ b/uuid_test.go @@ -13,8 +13,6 @@ import ( "testing" "time" "unsafe" - - "math/rand" ) type test struct { @@ -482,11 +480,13 @@ func TestBadRand(t *testing.T) { } func TestSetRand(t *testing.T) { - SetRand(rand.New(rand.NewSource(456))) + myString := "805-9dd6-1a877cb526c678e71d38-7122-44c0-9b7c-04e7001cc78783ac3e82-47a3-4cc3-9951-13f3339d88088f5d685a-11f7-4078-ada9-de44ad2daeb7" + + SetRand(strings.NewReader(myString)) uuid1 := New() uuid2 := New() - SetRand(rand.New(rand.NewSource(456))) + SetRand(strings.NewReader(myString)) uuid3 := New() uuid4 := New() From f00b2048a800039aa9459130d964406d60b1a92f Mon Sep 17 00:00:00 2001 From: talis Date: Wed, 13 Mar 2019 22:16:45 +0200 Subject: [PATCH 5/5] remove uuid_source --- uuid_source.go | 54 --------------------------------------- uuid_source_test.go | 62 --------------------------------------------- uuid_test.go | 23 +++++++++++++++-- version4.go | 4 +-- 4 files changed, 23 insertions(+), 120 deletions(-) delete mode 100644 uuid_source.go delete mode 100644 uuid_source_test.go diff --git a/uuid_source.go b/uuid_source.go deleted file mode 100644 index 83a569c..0000000 --- a/uuid_source.go +++ /dev/null @@ -1,54 +0,0 @@ -package uuid - -import ( - "io" - "crypto/rand" -) - -// A UuidSource holds a random number generator and generates UUIDs using it as its source. -// -// It is useful when a process need its own random number generator, -// e.g. when running some processes concurrently. -type UuidSource struct { - rander io.Reader -} - -// Creates a new UuidSource which holds its own random number generator. -// -// Calling NewSource with nil sets the random number generator to a default -// generator. -func NewSource(r io.Reader) UuidSource { - var uuidSource UuidSource - uuidSource.SetRand(r) - return uuidSource - -} - -// SetRand sets the random number generator of the UuidSource to r, which implements io.Reader. -// If r.Read returns an error when the package requests random data then -// a panic will be issued. -// -// Calling SetRand with nil sets the random number generator to a default -// generator. -func (uuidSource *UuidSource) SetRand(r io.Reader) { - if r == nil { - uuidSource.rander = rand.Reader - return - } - uuidSource.rander = r -} - -// NewRandom returns a Random (Version 4) UUID based on the random number generator in the UuidSource. -// -// See more detailed explanation here: https://godoc.org/github.com/google/uuid#NewRandom -func (uuidSource UuidSource) NewRandom() (UUID, error) { - return newRandom(uuidSource.rander) -} - -// New creates a new random UUID based on the random number generator in the UuidSource or panics. New is equivalent to -// the expression -// -// uuid.Must(uuid.NewRandom()) -func (uuidSource UuidSource) New() UUID { - return Must(uuidSource.NewRandom()) -} diff --git a/uuid_source_test.go b/uuid_source_test.go deleted file mode 100644 index b634c6a..0000000 --- a/uuid_source_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package uuid - -import ( - "testing" - "strings" - - regen "github.com/zach-klippenstein/goregen" -) - -func TestUuidSources(t *testing.T) { - - myString, _ := regen.Generate("[a-zA-Z]{1000}") - - // Two identical sources, should give same sequence - uuidSourceA := NewSource(strings.NewReader(myString)) - uuidSourceB := NewSource(strings.NewReader(myString)) - - for i := 0; i < 10; i++ { - uuid1 := uuidSourceA.New() - uuid2 := uuidSourceB.New() - if uuid1 != uuid2 { - t.Errorf("expected duplicates, got %q and %q", uuid1, uuid2) - } - } - - // Set rander with nil, each source will be random - uuidSourceA.SetRand(nil) - uuidSourceB.SetRand(nil) - - for i := 0; i < 10; i++ { - uuid1 := uuidSourceA.New() - uuid2 := uuidSourceB.New() - if uuid1 == uuid2 { - t.Errorf("unexpected duplicates, got %q", uuid1) - } - } - - // Set rander to rand source with same seed, should give same sequence - uuidSourceA.SetRand(strings.NewReader(myString)) - uuidSourceB.SetRand(strings.NewReader(myString)) - - for i := 0; i < 10; i++ { - uuid1 := uuidSourceA.New() - uuid2 := uuidSourceB.New() - if uuid1 != uuid2 { - t.Errorf("expected duplicates, got %q and %q", uuid1, uuid2) - } - } - - // Set rander to rand source with different seeds, should not give same sequence - uuidSourceA.SetRand(strings.NewReader("456" + myString)) - uuidSourceB.SetRand(strings.NewReader("myString" + myString)) - - for i := 0; i < 10; i++ { - uuid1 := uuidSourceA.New() - uuid2 := uuidSourceB.New() - if uuid1 == uuid2 { - t.Errorf("unexpected duplicates, got %q", uuid1) - } - } - -} \ No newline at end of file diff --git a/uuid_test.go b/uuid_test.go index 40f59cb..2a43fb1 100644 --- a/uuid_test.go +++ b/uuid_test.go @@ -496,8 +496,27 @@ func TestSetRand(t *testing.T) { if uuid2 != uuid4 { t.Errorf("expected duplicates, got %q and %q", uuid2, uuid4) } - - +} + +func TestRandomFromReader(t *testing.T) { + myString := "8059ddhdle77cb52" + r := bytes.NewReader([]byte(myString)) + r2 := bytes.NewReader([]byte(myString)) + uuid1, err := NewRandomFromReader(r) + if err != nil { + t.Errorf("failed generating UUID from a reader") + } + _, err = NewRandomFromReader(r) + if err == nil { + t.Errorf("expecting an error as reader has no more bytes. Got uuid. NewRandomFromReader may not be using the provided reader") + } + uuid3, err := NewRandomFromReader(r2) + if err != nil { + t.Errorf("failed generating UUID from a reader") + } + if uuid1 != uuid3 { + t.Errorf("expected duplicates, got %q and %q", uuid1, uuid3) + } } var asString = "f47ac10b-58cc-0372-8567-0e02b2c3d479" diff --git a/version4.go b/version4.go index 66acd62..9ad1aba 100644 --- a/version4.go +++ b/version4.go @@ -27,10 +27,10 @@ func New() UUID { // equivalent to the odds of creating a few tens of trillions of UUIDs in a // year and having one duplicate. func NewRandom() (UUID, error) { - return newRandom(rander) + return NewRandomFromReader(rander) } -func newRandom(r io.Reader) (UUID, error) { +func NewRandomFromReader(r io.Reader) (UUID, error) { var uuid UUID _, err := io.ReadFull(r, uuid[:]) if err != nil {