-
Notifications
You must be signed in to change notification settings - Fork 3.9k
/
Copy pathrand.go
147 lines (129 loc) · 4.4 KB
/
rand.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// Copyright 2014 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
package randutil
import (
crypto_rand "crypto/rand"
"encoding/binary"
"fmt"
"log" // Don't bring cockroach/util/log into this low-level package.
"math/rand"
"runtime"
"strings"
"sync"
"github.com/cockroachdb/cockroach/pkg/util/envutil"
)
// globalSeed contains a pseudo random seed that should only be used in tests.
var globalSeed int64
// rng is a random number generator used to generate seeds for test random
// number generators.
var rng *rand.Rand
// lastTestName is the function name of the last test we have seen.
var lastTestName string
// mtx protects rng and lastTestName.
var mtx sync.Mutex
// Initializes the global random seed. This value can be specified via an
// environment variable COCKROACH_RANDOM_SEED=x.
func init() {
globalSeed = envutil.EnvOrDefaultInt64("COCKROACH_RANDOM_SEED", NewPseudoSeed())
mtx.Lock()
defer mtx.Unlock()
rng = rand.New(rand.NewSource(globalSeed))
}
// NewPseudoSeed generates a seed from crypto/rand.
func NewPseudoSeed() int64 {
var seed int64
err := binary.Read(crypto_rand.Reader, binary.LittleEndian, &seed)
if err != nil {
panic(fmt.Sprintf("could not read from crypto/rand: %s", err))
}
return seed
}
// NewPseudoRand returns an instance of math/rand.Rand seeded from the
// environment variable COCKROACH_RANDOM_SEED. If that variable is not set,
// crypto/rand is used to generate a seed. The seed is also returned so we can
// easily and cheaply generate unique streams of numbers. The created object is
// not safe for concurrent access.
func NewPseudoRand() (*rand.Rand, int64) {
seed := envutil.EnvOrDefaultInt64("COCKROACH_RANDOM_SEED", NewPseudoSeed())
return rand.New(rand.NewSource(seed)), seed
}
// NewTestRand returns an instance of math/rand.Rand seeded from rng, which is
// seeded with the global seed. If the caller is a test with a different
// path-qualified name than the previous caller, rng is reseeded from the global
// seed. This rand.Rand is useful in testing to produce deterministic,
// reproducible behavior.
func NewTestRand() (*rand.Rand, int64) {
mtx.Lock()
defer mtx.Unlock()
fxn := getTestName()
if fxn != "" && lastTestName != fxn {
// Re-seed rng (the source of seeds for test random number generators) with
// the global seed so that individual tests are reproducible using the
// random seed.
lastTestName = fxn
rng = rand.New(rand.NewSource(globalSeed))
}
seed := rng.Int63()
return rand.New(rand.NewSource(seed)), seed
}
// RandIntInRange returns a value in [min, max)
func RandIntInRange(r *rand.Rand, min, max int) int {
return min + r.Intn(max-min)
}
var randLetters = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
// RandBytes returns a byte slice of the given length with random
// data.
func RandBytes(r *rand.Rand, size int) []byte {
if size <= 0 {
return nil
}
arr := make([]byte, size)
for i := 0; i < len(arr); i++ {
arr[i] = randLetters[r.Intn(len(randLetters))]
}
return arr
}
// ReadTestdataBytes reads random bytes, but then nudges them into printable
// ASCII, *reducing their randomness* to make them a little friendlier for
// humans using them as testdata.
func ReadTestdataBytes(r *rand.Rand, arr []byte) {
_, _ = r.Read(arr)
for i := range arr {
arr[i] = arr[i] & 0x7F // mask out non-ascii
if arr[i] < ' ' { // Nudge the control chars up, into the letters.
arr[i] += 'A'
}
}
}
// SeedForTests seeds the random number generator and prints the seed
// value used. This function should be called from TestMain; individual tests
// should not touch the seed of the global random number generator.
func SeedForTests() {
rand.Seed(globalSeed)
log.Printf("random seed: %v", globalSeed)
}
// getTestName returns the calling test function name, returning an empty string
// if not found. The number of calls up the call stack is limited.
func getTestName() string {
pcs := make([]uintptr, 10)
n := runtime.Callers(2, pcs)
frames := runtime.CallersFrames(pcs[:n])
for {
frame, more := frames.Next()
fxn := frame.Function
if strings.Contains(fxn, ".Test") {
return fxn
}
if !more {
break
}
}
return ""
}