tstest: add a helper for reproducable random seeds

We recently ran across a case of flaky tests that were seeding the
math/rand package and using that to generate data. The tests were
flaking on specific random values that were difficult to reproduce. If
the test had been using a helper like this, the flaky cases could have
been reliably reproduced.

Signed-off-by: James Tucker <james@tailscale.com>
raggi/testseed
James Tucker 2023-05-10 15:27:31 -07:00
parent cb2fd5be92
commit eca5b33a78
No known key found for this signature in database
2 changed files with 49 additions and 1 deletions

View File

@ -6,6 +6,10 @@ package tstest
import (
"context"
"math/rand"
"os"
"strconv"
"sync"
"testing"
"time"
@ -46,3 +50,38 @@ func WaitFor(maxWait time.Duration, try func() error) error {
}
return err
}
var (
seed int64
seedOnce sync.Once
)
// GetSeed gets the current global random test seed, by default this is based on
// the current time, but can be fixed to a particular value using the
// TS_TEST_SEED environment variable.
func GetSeed(t testing.TB) int64 {
t.Helper()
seedOnce.Do(func() {
if s := os.Getenv("TS_TEST_SEED"); s != "" {
var err error
seed, err = strconv.ParseInt(s, 10, 64)
if err != nil {
t.Fatalf("invalid TS_TEST_SEED: %v", err)
}
} else {
seed = time.Now().UnixNano()
}
})
return seed
}
// SeedRand seeds the standard library global rand with the current test seed.
func SeedRand(t testing.TB) {
t.Helper()
// Seed is called every time, as other tests may execute code that reseeds
// the global rand.
rand.Seed(GetSeed(t))
t.Logf("TS_TEST_SEED=%d", seed)
}

View File

@ -3,7 +3,9 @@
package tstest
import "testing"
import (
"testing"
)
func TestReplace(t *testing.T) {
before := "before"
@ -22,3 +24,10 @@ func TestReplace(t *testing.T) {
t.Errorf("before = %q; want %q", before, "before")
}
}
func TestGetSeed(t *testing.T) {
t.Setenv("TS_TEST_SEED", "1234")
if got, want := GetSeed(t), int64(1234); got != want {
t.Errorf("GetSeed = %v; want %v", got, want)
}
}