1// Copyright 2023 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package rand_test
6
7import (
8	"fmt"
9	"internal/race"
10	"internal/testenv"
11	. "math/rand"
12	"os"
13	"runtime"
14	"strconv"
15	"sync"
16	"testing"
17)
18
19// Test that racy access to the default functions behaves reasonably.
20func TestDefaultRace(t *testing.T) {
21	// Skip the test in short mode, but even in short mode run
22	// the test if we are using the race detector, because part
23	// of this is to see whether the race detector reports any problems.
24	if testing.Short() && !race.Enabled {
25		t.Skip("skipping starting another executable in short mode")
26	}
27
28	const env = "GO_RAND_TEST_HELPER_CODE"
29	if v := os.Getenv(env); v != "" {
30		doDefaultTest(t, v)
31		return
32	}
33
34	t.Parallel()
35
36	for i := 0; i < 6; i++ {
37		i := i
38		t.Run(strconv.Itoa(i), func(t *testing.T) {
39			t.Parallel()
40			exe, err := os.Executable()
41			if err != nil {
42				exe = os.Args[0]
43			}
44			cmd := testenv.Command(t, exe, "-test.run=TestDefaultRace")
45			cmd = testenv.CleanCmdEnv(cmd)
46			cmd.Env = append(cmd.Env, fmt.Sprintf("GO_RAND_TEST_HELPER_CODE=%d", i/2))
47			if i%2 != 0 {
48				cmd.Env = append(cmd.Env, "GODEBUG=randautoseed=0")
49			}
50			out, err := cmd.CombinedOutput()
51			if len(out) > 0 {
52				t.Logf("%s", out)
53			}
54			if err != nil {
55				t.Error(err)
56			}
57		})
58	}
59}
60
61// doDefaultTest should be run before there have been any calls to the
62// top-level math/rand functions. Make sure that we can make concurrent
63// calls to top-level functions and to Seed without any duplicate values.
64// This will also give the race detector a change to report any problems.
65func doDefaultTest(t *testing.T, v string) {
66	code, err := strconv.Atoi(v)
67	if err != nil {
68		t.Fatalf("internal error: unrecognized code %q", v)
69	}
70
71	goroutines := runtime.GOMAXPROCS(0)
72	if goroutines < 4 {
73		goroutines = 4
74	}
75
76	ch := make(chan uint64, goroutines*3)
77	var wg sync.WaitGroup
78
79	// The various tests below should not cause race detector reports
80	// and should not produce duplicate results.
81	//
82	// Note: these tests can theoretically fail when using fastrand64
83	// in that it is possible to coincidentally get the same random
84	// number twice. That could happen something like 1 / 2**64 times,
85	// which is rare enough that it may never happen. We don't worry
86	// about that case.
87
88	switch code {
89	case 0:
90		// Call Seed and Uint64 concurrently.
91		wg.Add(goroutines)
92		for i := 0; i < goroutines; i++ {
93			go func(s int64) {
94				defer wg.Done()
95				Seed(s)
96			}(int64(i) + 100)
97		}
98		wg.Add(goroutines)
99		for i := 0; i < goroutines; i++ {
100			go func() {
101				defer wg.Done()
102				ch <- Uint64()
103			}()
104		}
105	case 1:
106		// Call Uint64 concurrently with no Seed.
107		wg.Add(goroutines)
108		for i := 0; i < goroutines; i++ {
109			go func() {
110				defer wg.Done()
111				ch <- Uint64()
112			}()
113		}
114	case 2:
115		// Start with Uint64 to pick the fast source, then call
116		// Seed and Uint64 concurrently.
117		ch <- Uint64()
118		wg.Add(goroutines)
119		for i := 0; i < goroutines; i++ {
120			go func(s int64) {
121				defer wg.Done()
122				Seed(s)
123			}(int64(i) + 100)
124		}
125		wg.Add(goroutines)
126		for i := 0; i < goroutines; i++ {
127			go func() {
128				defer wg.Done()
129				ch <- Uint64()
130			}()
131		}
132	default:
133		t.Fatalf("internal error: unrecognized code %d", code)
134	}
135
136	go func() {
137		wg.Wait()
138		close(ch)
139	}()
140
141	m := make(map[uint64]bool)
142	for i := range ch {
143		if m[i] {
144			t.Errorf("saw %d twice", i)
145		}
146		m[i] = true
147	}
148}
149