blob: 8e216404712651bbc19da6494f3db1a6c69d64ff [file] [log] [blame]
// 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)
}