diff options
Diffstat (limited to 'src/math/rand/default_test.go')
-rw-r--r-- | src/math/rand/default_test.go | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/src/math/rand/default_test.go b/src/math/rand/default_test.go new file mode 100644 index 0000000..19fd75d --- /dev/null +++ b/src/math/rand/default_test.go @@ -0,0 +1,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 + } +} |