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