| // 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 |
| } |
| } |