Repository: bxcodec/faker (go)

Issue:
support custom random sources
A nice addition would be to be able to set custom random sources.

I see several reasons for that:
1. Seed a random source to generate a stable set of fake data
2. Use an alternate random generator, such as xorshift, for faster fake data generation
3. Have full control over when concurrency-safety is desirable or when it is not

Pull request:
added random source customization
This PR adds the ability to set custom random sources at the package level.

* fixes #138 

Signed-off-by: Frederic BIDON <fredbi@yahoo.com>

Patch:
diff --git a/address.go b/address.go
index 340bb94..2ca30b5 100644
--- a/address.go
+++ b/address.go
@@ -1,7 +1,6 @@
 package faker
 
 import (
-	"math/rand"
 	"reflect"
 )
 
diff --git a/datetime.go b/datetime.go
index 448c2d6..4f321d8 100644
--- a/datetime.go
+++ b/datetime.go
@@ -2,7 +2,6 @@ package faker
 
 import (
 	"fmt"
-	"math/rand"
 	"reflect"
 	"time"
 )
diff --git a/faker.go b/faker.go
index d97bd3a..447f600 100644
--- a/faker.go
+++ b/faker.go
@@ -3,9 +3,10 @@ package faker
 // Faker is a simple fake data generator for your own struct.
 // Save your time, and Fake your data for your testing now.
 import (
+	cryptorand "crypto/rand"
 	"errors"
 	"fmt"
-	"math/rand"
+	mathrand "math/rand"
 	"reflect"
 	"regexp"
 	"strconv"
@@ -263,10 +264,16 @@ var (
 )
 
 func init() {
-	rand.Seed(time.Now().UnixNano())
+	rand = mathrand.New(NewSafeSource(mathrand.NewSource(time.Now().UnixNano())))
+	crypto = cryptorand.Reader
+}
+
+func init() {
 	findLangReg, _ = regexp.Compile("lang=[a-z]{3}")
 	findLenReg, _ = regexp.Compile(`len=\d+`)
 	findSliceLenReg, _ = regexp.Compile(`slice_len=\d+`)
+
+	randNameFlag = rand.Intn(100) // for person
 }
 
 // ResetUnique is used to forget generated unique values.
diff --git a/internet.go b/internet.go
index 799bee9..cd4f9cd 100644
--- a/internet.go
+++ b/internet.go
@@ -2,7 +2,6 @@ package faker
 
 import (
 	"fmt"
-	"math/rand"
 	"net"
 	"reflect"
 	"strings"
diff --git a/lorem.go b/lorem.go
index 8ce2342..5b3a138 100644
--- a/lorem.go
+++ b/lorem.go
@@ -2,7 +2,6 @@ package faker
 
 import (
 	"fmt"
-	"math/rand"
 	"reflect"
 	"strings"
 )
diff --git a/payment.go b/payment.go
index 5625464..1c7dfcd 100644
--- a/payment.go
+++ b/payment.go
@@ -1,7 +1,6 @@
 package faker
 
 import (
-	"math/rand"
 	"reflect"
 	"strconv"
 	"strings"
diff --git a/person.go b/person.go
index dc9ccb9..8123fa1 100644
--- a/person.go
+++ b/person.go
@@ -2,7 +2,6 @@ package faker
 
 import (
 	"fmt"
-	"math/rand"
 	"reflect"
 )
 
@@ -107,7 +106,8 @@ var lastNames = []string{
 	"Ullrich", "Upton", "Vandervort", "Veum", "Volkman", "Von", "VonRueden", "Waelchi", "Walker", "Walsh", "Walter", "Ward", "Waters", "Watsica", "Weber", "Wehner", "Weimann", "Weissnat", "Welch", "West", "White", "Wiegand", "Wilderman", "Wilkinson", "Will", "Williamson", "Willms", "Windler", "Wintheiser", "Wisoky", "Wisozk", "Witting", "Wiza", "Wolf", "Wolff", "Wuckert", "Wunsch", "Wyman",
 	"Yost", "Yundt", "Zboncak", "Zemlak", "Ziemann", "Zieme", "Zulauf",
 }
-var randNameFlag = rand.Intn(100)
+
+var randNameFlag int
 
 var genders = []string{"Male", "Female", "Prefer to skip"}
 
diff --git a/phone.go b/phone.go
index 3c20383..1f6f84f 100644
--- a/phone.go
+++ b/phone.go
@@ -2,7 +2,6 @@ package faker
 
 import (
 	"fmt"
-	"math/rand"
 	"reflect"
 	"strings"
 
diff --git a/price.go b/price.go
index 71641fa..ca3cf51 100644
--- a/price.go
+++ b/price.go
@@ -3,7 +3,6 @@ package faker
 import (
 	"fmt"
 	"math"
-	"math/rand"
 	"reflect"
 )
 
diff --git a/random_source.go b/random_source.go
new file mode 100644
index 0000000..96b7a15
--- /dev/null
+++ b/random_source.go
@@ -0,0 +1,49 @@
+package faker
+
+import (
+	"io"
+	mathrand "math/rand"
+	"sync"
+)
+
+var (
+	rand   *mathrand.Rand
+	crypto io.Reader
+)
+
+type safeSource struct {
+	mx sync.Mutex
+	mathrand.Source
+}
+
+func (s *safeSource) Int63() int64 {
+	s.mx.Lock()
+	defer s.mx.Unlock()
+
+	return s.Source.Int63()
+}
+
+// NewSafeSource wraps an unsafe rand.Source with a mutex to guard the random source
+// against concurrent access.
+func NewSafeSource(in mathrand.Source) mathrand.Source {
+	return &safeSource{
+		Source: in,
+	}
+}
+
+// SetRandomSource sets a new random source at the package level.
+//
+// To use a concurrent-safe source, you may wrap it with NewSafeSource,
+// e.g. SetRandomSource(NewSafeSource(mysource)).
+//
+// The default is the global, concurrent-safe source provided by math/rand.
+func SetRandomSource(in mathrand.Source) {
+	rand = mathrand.New(in)
+}
+
+// SetCryptoSource sets a new reader for functions using a cryptographically-safe random generator (e.g. UUID).
+//
+// The default is the global source provided by crypto/rand.
+func SetCryptoSource(in io.Reader) {
+	crypto = in
+}
diff --git a/tag_argument_extractor.go b/tag_argument_extractor.go
index 552df7f..701eda2 100644
--- a/tag_argument_extractor.go
+++ b/tag_argument_extractor.go
@@ -2,7 +2,6 @@ package faker
 
 import (
 	"fmt"
-	"math/rand"
 	"strconv"
 	"strings"
 )
diff --git a/uuid.go b/uuid.go
index 3009c4a..368919f 100644
--- a/uuid.go
+++ b/uuid.go
@@ -1,7 +1,6 @@
 package faker
 
 import (
-	"crypto/rand"
 	"fmt"
 	"io"
 	"reflect"
@@ -32,7 +31,7 @@ type UUID struct{}
 // createUUID returns a 16 byte slice with random values
 func createUUID() ([]byte, error) {
 	b := make([]byte, 16)
-	_, err := io.ReadFull(rand.Reader, b)
+	_, err := io.ReadFull(crypto, b)
 	if err != nil {
 		return b, err
 	}


Test patch:
diff --git a/faker_test.go b/faker_test.go
index 7fff340..4c47701 100644
--- a/faker_test.go
+++ b/faker_test.go
@@ -2,7 +2,7 @@ package faker
 
 import (
 	"fmt"
-	"math/rand"
+	mathrand "math/rand"
 	"reflect"
 	"strings"
 	"testing"
@@ -1066,8 +1066,7 @@ func TestExtend(t *testing.T) {
 		a := Sample{}
 		sliceLen := 10
 		err := AddProvider("myint", func(v reflect.Value) (interface{}, error) {
-			s1 := rand.NewSource(time.Now().UnixNano())
-			r1 := rand.New(s1)
+			r1 := mathrand.New(NewSafeSource(mathrand.NewSource(time.Now().UnixNano())))
 			r := make([]MyInt, sliceLen)
 			for i := range r {
 				r[i] = MyInt(r1.Intn(100))
diff --git a/random_source_test.go b/random_source_test.go
new file mode 100644
index 0000000..ed03026
--- /dev/null
+++ b/random_source_test.go
@@ -0,0 +1,25 @@
+package faker
+
+import (
+	cryptorand "crypto/rand"
+	"io"
+	mathrand "math/rand"
+	"testing"
+	"time"
+)
+
+func TestSetRandomSource(t *testing.T) {
+	SetRandomSource(NewSafeSource(mathrand.NewSource(time.Now().UnixNano())))
+
+	_ = rand.Int31n(100)
+}
+
+func TestSetCryptoSource(t *testing.T) {
+	SetCryptoSource(cryptorand.Reader)
+
+	buf := make([]byte, 10)
+	_, err := io.ReadFull(crypto, buf)
+	if err != nil {
+		t.Error("Expected Not Error, But Got: ", err)
+	}
+}