Source file src/math/rand/default_test.go

     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  
     5  package rand_test
     6  
     7  import (
     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.
    20  func 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  		t.Run(strconv.Itoa(i), func(t *testing.T) {
    38  			t.Parallel()
    39  			cmd := testenv.Command(t, testenv.Executable(t), "-test.run=^TestDefaultRace$")
    40  			cmd = testenv.CleanCmdEnv(cmd)
    41  			cmd.Env = append(cmd.Env, fmt.Sprintf("GO_RAND_TEST_HELPER_CODE=%d", i/2))
    42  			if i%2 != 0 {
    43  				cmd.Env = append(cmd.Env, "GODEBUG=randautoseed=0")
    44  			}
    45  			out, err := cmd.CombinedOutput()
    46  			if len(out) > 0 {
    47  				t.Logf("%s", out)
    48  			}
    49  			if err != nil {
    50  				t.Error(err)
    51  			}
    52  		})
    53  	}
    54  }
    55  
    56  // doDefaultTest should be run before there have been any calls to the
    57  // top-level math/rand functions. Make sure that we can make concurrent
    58  // calls to top-level functions and to Seed without any duplicate values.
    59  // This will also give the race detector a change to report any problems.
    60  func doDefaultTest(t *testing.T, v string) {
    61  	code, err := strconv.Atoi(v)
    62  	if err != nil {
    63  		t.Fatalf("internal error: unrecognized code %q", v)
    64  	}
    65  
    66  	goroutines := runtime.GOMAXPROCS(0)
    67  	if goroutines < 4 {
    68  		goroutines = 4
    69  	}
    70  
    71  	ch := make(chan uint64, goroutines*3)
    72  	var wg sync.WaitGroup
    73  
    74  	// The various tests below should not cause race detector reports
    75  	// and should not produce duplicate results.
    76  	//
    77  	// Note: these tests can theoretically fail when using fastrand64
    78  	// in that it is possible to coincidentally get the same random
    79  	// number twice. That could happen something like 1 / 2**64 times,
    80  	// which is rare enough that it may never happen. We don't worry
    81  	// about that case.
    82  
    83  	switch code {
    84  	case 0:
    85  		// Call Seed and Uint64 concurrently.
    86  		wg.Add(goroutines)
    87  		for i := 0; i < goroutines; i++ {
    88  			go func(s int64) {
    89  				defer wg.Done()
    90  				Seed(s)
    91  			}(int64(i) + 100)
    92  		}
    93  		wg.Add(goroutines)
    94  		for i := 0; i < goroutines; i++ {
    95  			go func() {
    96  				defer wg.Done()
    97  				ch <- Uint64()
    98  			}()
    99  		}
   100  	case 1:
   101  		// Call Uint64 concurrently with no Seed.
   102  		wg.Add(goroutines)
   103  		for i := 0; i < goroutines; i++ {
   104  			go func() {
   105  				defer wg.Done()
   106  				ch <- Uint64()
   107  			}()
   108  		}
   109  	case 2:
   110  		// Start with Uint64 to pick the fast source, then call
   111  		// Seed and Uint64 concurrently.
   112  		ch <- Uint64()
   113  		wg.Add(goroutines)
   114  		for i := 0; i < goroutines; i++ {
   115  			go func(s int64) {
   116  				defer wg.Done()
   117  				Seed(s)
   118  			}(int64(i) + 100)
   119  		}
   120  		wg.Add(goroutines)
   121  		for i := 0; i < goroutines; i++ {
   122  			go func() {
   123  				defer wg.Done()
   124  				ch <- Uint64()
   125  			}()
   126  		}
   127  	default:
   128  		t.Fatalf("internal error: unrecognized code %d", code)
   129  	}
   130  
   131  	go func() {
   132  		wg.Wait()
   133  		close(ch)
   134  	}()
   135  
   136  	m := make(map[uint64]bool)
   137  	for i := range ch {
   138  		if m[i] {
   139  			t.Errorf("saw %d twice", i)
   140  		}
   141  		m[i] = true
   142  	}
   143  }
   144  

View as plain text