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
148
|
// 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++ {
i := i
t.Run(strconv.Itoa(i), func(t *testing.T) {
t.Parallel()
exe, err := os.Executable()
if err != nil {
exe = os.Args[0]
}
cmd := testenv.Command(t, exe, "-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
}
}
|