| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package testutil |
| |
| import ( |
| "fmt" |
| "math/rand" |
| "os" |
| "strconv" |
| "sync" |
| "time" |
| ) |
| |
| const ( |
| SeedEnv = "V23_RNG_SEED" |
| ) |
| |
| // An instance of Random initialized by the InitRandomGenerator function. |
| var ( |
| Rand *Random |
| once sync.Once |
| ) |
| |
| const randPanicMsg = "It looks like the singleton random number generator has not been initialized, please call InitRandGenerator." |
| |
| // Random is a concurrent-access friendly source of randomness. |
| type Random struct { |
| mu sync.Mutex |
| rand *rand.Rand |
| } |
| |
| // RandomInt returns a non-negative pseudo-random int. |
| func (r *Random) RandomInt() int { |
| r.mu.Lock() |
| defer r.mu.Unlock() |
| return r.rand.Int() |
| } |
| |
| // RandomIntn returns a non-negative pseudo-random int in the range [0, n). |
| func (r *Random) RandomIntn(n int) int { |
| r.mu.Lock() |
| defer r.mu.Unlock() |
| return r.rand.Intn(n) |
| } |
| |
| // RandomInt63 returns a non-negative 63-bit pseudo-random integer as an int64. |
| func (r *Random) RandomInt63() int64 { |
| r.mu.Lock() |
| defer r.mu.Unlock() |
| return r.rand.Int63() |
| } |
| |
| // RandomBytes generates the given number of random bytes. |
| func (rand *Random) RandomBytes(size int) []byte { |
| buffer := make([]byte, size) |
| randomMutex.Lock() |
| defer randomMutex.Unlock() |
| // Generate a 10MB of random bytes since that is a value commonly |
| // used in the tests. |
| if len(random) == 0 { |
| random = generateRandomBytes(rand, 10<<20) |
| } |
| if size > len(random) { |
| extra := generateRandomBytes(rand, size-len(random)) |
| random = append(random, extra...) |
| } |
| start := rand.RandomIntn(len(random) - size + 1) |
| copy(buffer, random[start:start+size]) |
| return buffer |
| } |
| |
| type loggingFunc func(format string, args ...interface{}) |
| |
| // NewRandGenerator creates a new pseudo-random number generator; the seed may |
| // be supplied by V23_RNG_SEED to allow for reproducing a previous sequence, and |
| // is printed using the supplied logging function. |
| func NewRandGenerator(logger loggingFunc) *Random { |
| seed := time.Now().UnixNano() |
| seedString := os.Getenv(SeedEnv) |
| if seedString != "" { |
| var err error |
| base, bitSize := 0, 64 |
| seed, err = strconv.ParseInt(seedString, base, bitSize) |
| if err != nil { |
| panic(fmt.Sprintf("ParseInt(%v, %v, %v) failed: %v", seedString, base, bitSize, err)) |
| } |
| } |
| logger("Seeded pseudo-random number generator with %v", seed) |
| return &Random{rand: rand.New(rand.NewSource(seed))} |
| } |
| |
| // TODO(caprita): Consider deprecating InitRandGenerator in favor of using |
| // NewRandGenerator directly. There are several drawbacks to using the global |
| // singleton Random object: |
| // |
| // - tests that do not call InitRandGenerator themselves could depend on |
| // InitRandGenerator having been called by other tests in the same package and |
| // stop working when run standalone with test --run |
| // |
| // - conversely, a test case may call InitRandGenerator without actually |
| // needing to; it's hard to figure out if some library called by a test |
| // actually uses the Random object or not |
| // |
| // - when several test cases share the same Random object, there is |
| // interference in the stream of random numbers generated for each test case |
| // if run in parallel |
| // |
| // All these issues can be trivially addressed if the Random object is created |
| // and plumbed through the call stack explicitly. |
| |
| // InitRandGenerator creates an instance of Random in the public variable Rand |
| // and prints out the seed use when creating the number number generator using |
| // the supplied logging function. |
| func InitRandGenerator(logger loggingFunc) { |
| once.Do(func() { |
| Rand = NewRandGenerator(logger) |
| }) |
| } |
| |
| var ( |
| random []byte |
| randomMutex sync.Mutex |
| ) |
| |
| func generateRandomBytes(rand *Random, size int) []byte { |
| buffer := make([]byte, size) |
| offset := 0 |
| for { |
| bits := int64(rand.RandomInt63()) |
| for i := 0; i < 8; i++ { |
| buffer[offset] = byte(bits & 0xff) |
| size-- |
| if size == 0 { |
| return buffer |
| } |
| offset++ |
| bits >>= 8 |
| } |
| } |
| } |
| |
| // RandomInt returns a non-negative pseudo-random int using the public variable Rand. |
| func RandomInt() int { |
| if Rand == nil { |
| panic(randPanicMsg) |
| } |
| return Rand.RandomInt() |
| } |
| |
| // RandomIntn returns a non-negative pseudo-random int in the range [0, n) using |
| // the public variable Rand. |
| func RandomIntn(n int) int { |
| if Rand == nil { |
| panic(randPanicMsg) |
| } |
| return Rand.RandomIntn(n) |
| } |
| |
| // RandomInt63 returns a non-negative 63-bit pseudo-random integer as an int64 |
| // using the public variable Rand. |
| func RandomInt63() int64 { |
| if Rand == nil { |
| panic(randPanicMsg) |
| } |
| return Rand.RandomInt63() |
| } |
| |
| // RandomBytes generates the given number of random bytes using |
| // the public variable Rand. |
| func RandomBytes(size int) []byte { |
| if Rand == nil { |
| panic(randPanicMsg) |
| } |
| return Rand.RandomBytes(size) |
| } |