File size: 3,414 Bytes
e36aeda | 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 | // Copyright 2023 The Go 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 rand_test
import (
"fmt"
"internal/race"
"internal/testenv"
. "math/rand"
"os"
"runtime"
"strconv"
"sync"
"testing"
)
// Test that racy access to the default functions behaves reasonably.
func TestDefaultRace(t *testing.T) {
// Skip the test in short mode, but even in short mode run
// the test if we are using the race detector, because part
// of this is to see whether the race detector reports any problems.
if testing.Short() && !race.Enabled {
t.Skip("skipping starting another executable in short mode")
}
const env = "GO_RAND_TEST_HELPER_CODE"
if v := os.Getenv(env); v != "" {
doDefaultTest(t, v)
return
}
t.Parallel()
for i := 0; i < 6; i++ {
t.Run(strconv.Itoa(i), func(t *testing.T) {
t.Parallel()
cmd := testenv.Command(t, testenv.Executable(t), "-test.run=^TestDefaultRace$")
cmd = testenv.CleanCmdEnv(cmd)
cmd.Env = append(cmd.Env, fmt.Sprintf("GO_RAND_TEST_HELPER_CODE=%d", i/2))
if i%2 != 0 {
cmd.Env = append(cmd.Env, "GODEBUG=randautoseed=0")
}
out, err := cmd.CombinedOutput()
if len(out) > 0 {
t.Logf("%s", out)
}
if err != nil {
t.Error(err)
}
})
}
}
// doDefaultTest should be run before there have been any calls to the
// top-level math/rand functions. Make sure that we can make concurrent
// calls to top-level functions and to Seed without any duplicate values.
// This will also give the race detector a change to report any problems.
func doDefaultTest(t *testing.T, v string) {
code, err := strconv.Atoi(v)
if err != nil {
t.Fatalf("internal error: unrecognized code %q", v)
}
goroutines := runtime.GOMAXPROCS(0)
if goroutines < 4 {
goroutines = 4
}
ch := make(chan uint64, goroutines*3)
var wg sync.WaitGroup
// The various tests below should not cause race detector reports
// and should not produce duplicate results.
//
// Note: these tests can theoretically fail when using fastrand64
// in that it is possible to coincidentally get the same random
// number twice. That could happen something like 1 / 2**64 times,
// which is rare enough that it may never happen. We don't worry
// about that case.
switch code {
case 0:
// Call Seed and Uint64 concurrently.
wg.Add(goroutines)
for i := 0; i < goroutines; i++ {
go func(s int64) {
defer wg.Done()
Seed(s)
}(int64(i) + 100)
}
wg.Add(goroutines)
for i := 0; i < goroutines; i++ {
go func() {
defer wg.Done()
ch <- Uint64()
}()
}
case 1:
// Call Uint64 concurrently with no Seed.
wg.Add(goroutines)
for i := 0; i < goroutines; i++ {
go func() {
defer wg.Done()
ch <- Uint64()
}()
}
case 2:
// Start with Uint64 to pick the fast source, then call
// Seed and Uint64 concurrently.
ch <- Uint64()
wg.Add(goroutines)
for i := 0; i < goroutines; i++ {
go func(s int64) {
defer wg.Done()
Seed(s)
}(int64(i) + 100)
}
wg.Add(goroutines)
for i := 0; i < goroutines; i++ {
go func() {
defer wg.Done()
ch <- Uint64()
}()
}
default:
t.Fatalf("internal error: unrecognized code %d", code)
}
go func() {
wg.Wait()
close(ch)
}()
m := make(map[uint64]bool)
for i := range ch {
if m[i] {
t.Errorf("saw %d twice", i)
}
m[i] = true
}
}
|