go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/data/rand/mathrand/mathrand_test.go (about) 1 // Copyright 2015 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package mathrand 16 17 import ( 18 "context" 19 "math" 20 "math/rand" 21 "testing" 22 23 . "github.com/smartystreets/goconvey/convey" 24 ) 25 26 func init() { 27 rand.Seed(1) 28 } 29 30 func Test(t *testing.T) { 31 t.Parallel() 32 33 Convey("test mathrand", t, func() { 34 c := context.Background() 35 36 Convey("unset", func() { 37 // Just ensure doesn't crash. 38 So(Get(c).Int()+1 > 0, ShouldBeTrue) 39 So(WithGoRand(c, func(r *rand.Rand) error { 40 So(r.Int(), ShouldBeGreaterThanOrEqualTo, 0) 41 return nil 42 }), ShouldBeNil) 43 }) 44 45 Convey("set persistence", func() { 46 c = Set(c, rand.New(rand.NewSource(12345))) 47 r := rand.New(rand.NewSource(12345)) 48 So(Get(c).Int(), ShouldEqual, r.Int()) 49 So(Get(c).Int(), ShouldEqual, r.Int()) 50 }) 51 52 Convey("nil set", func() { 53 c = Set(c, nil) 54 // Just ensure doesn't crash. 55 So(Get(c).Int()+1 > 0, ShouldBeTrue) 56 }) 57 }) 58 59 Convey("fairness of uninitialized source", t, func() { 60 // We do some ugly stuff in Get(...) if context doesn't have math.Rand set, 61 // check that the produced RNG sequence matches the uniform distribution 62 // at least at first two moments. 63 ctx := context.Background() 64 mean, dev := calcStats(20000, func() float64 { 65 return Get(ctx).Float64() 66 }) 67 68 // For ideal uniform [0, 1) distribution it should be: 69 // Average: 0.500000 70 // Standard deviation: 0.288675 71 So(mean, ShouldBeBetween, 0.495, 0.505) 72 So(dev, ShouldBeBetween, 0.284, 0.29) 73 }) 74 } 75 76 func testConcurrentAccess(t *testing.T, r *rand.Rand) { 77 const goroutines = 16 78 const rounds = 1024 79 80 Convey(`Concurrent access does not produce a race or deadlock.`, func() { 81 c := context.Background() 82 if r != nil { 83 c = Set(c, r) 84 } 85 86 startC := make(chan struct{}) 87 doneC := make(chan struct{}, goroutines) 88 for g := 0; g < goroutines; g++ { 89 go func() { 90 defer func() { 91 doneC <- struct{}{} 92 }() 93 94 <-startC 95 for i := 0; i < rounds; i++ { 96 Int(c) 97 } 98 }() 99 } 100 101 close(startC) 102 for reap := 0; reap < goroutines; reap++ { 103 <-doneC 104 } 105 }) 106 107 } 108 109 // TestConcurrentGlobalAccess is intentionally NOT Parallel, since we want to 110 // have exclusive access to the global instance. 111 func TestConcurrentGlobalAccess(t *testing.T) { 112 Convey(`Testing concurrent global access`, t, func() { 113 testConcurrentAccess(t, nil) 114 }) 115 } 116 117 func TestConcurrentAccess(t *testing.T) { 118 t.Parallel() 119 120 Convey(`Testing concurrent non-global access`, t, func() { 121 testConcurrentAccess(t, newRand()) 122 }) 123 } 124 125 func calcStats(n int, gen func() float64) (avg float64, std float64) { 126 var m1 float64 127 var m2 float64 128 129 for i := 0; i < n; i++ { 130 x := gen() 131 m1 += x 132 m2 += x * x 133 } 134 135 avg = m1 / float64(n) 136 std = math.Sqrt(m2/float64(n) - avg*avg) 137 return 138 } 139 140 func BenchmarkStdlibDefaultSource(b *testing.B) { 141 calcStats(b.N, rand.Float64) 142 } 143 144 func BenchmarkOurDefaultSourceViaCtx(b *testing.B) { 145 ctx := context.Background() 146 calcStats(b.N, func() float64 { 147 return Get(ctx).Float64() 148 }) 149 } 150 151 func BenchmarkOurDefaultSourceViaFunc(b *testing.B) { 152 ctx := context.Background() 153 calcStats(b.N, func() float64 { 154 return Float64(ctx) 155 }) 156 } 157 158 func BenchmarkOurInitializedSourceViaCtx(b *testing.B) { 159 ctx := context.Background() 160 WithGoRand(ctx, func(r *rand.Rand) error { 161 ctx = Set(ctx, r) 162 calcStats(b.N, func() float64 { 163 return Get(ctx).Float64() 164 }) 165 return nil 166 }) 167 } 168 169 func BenchmarkOurInitializedSourceViaFunc(b *testing.B) { 170 ctx := context.Background() 171 WithGoRand(ctx, func(r *rand.Rand) error { 172 ctx = Set(ctx, r) 173 calcStats(b.N, func() float64 { 174 return Float64(ctx) 175 }) 176 return nil 177 }) 178 } 179 180 func BenchmarkGlobalSource(b *testing.B) { 181 r, _ := getGlobalRand() 182 calcStats(b.N, func() float64 { 183 return r.Float64() 184 }) 185 } 186 187 // BenchmarkStdlibDefaultSource-32 30000000 35.6 ns/op 188 // BenchmarkOurDefaultSourceViaCtx-32 20000000 77.8 ns/op 189 // BenchmarkOurDefaultSourceViaFunc-32 20000000 78.6 ns/op 190 // BenchmarkOurInitializedSourceViaCtx-32 20000000 86.8 ns/op 191 // BenchmarkOurInitializedSourceViaFunc-32 20000000 81.9 ns/op 192 // BenchmarkGlobalSource-32 30000000 43.8 ns/op