veyron/lib/testutil: utility for pseudo-random testing
This change adds functionality that:
1) provides per test pseudo-random number generators that aim to
provide better coverage and reproducibility
2) efficient utility for generating random bytes
Change-Id: Ie7e0f1622c64a11fb787d95e30b62c9f264f110d
diff --git a/lib/testutil/init.go b/lib/testutil/init.go
index 2d42a41..2315551 100644
--- a/lib/testutil/init.go
+++ b/lib/testutil/init.go
@@ -7,13 +7,16 @@
import (
"flag"
+ "math/rand"
"os"
"runtime"
+ "strconv"
// Need to import all of the packages that could possibly
// define flags that we care about. In practice, this is the
// flags defined by the testing package, the logging library
// and any flags defined by the blackbox package below.
_ "testing"
+ "time"
// Import blackbox to ensure that it gets to define its flags.
_ "veyron/lib/testutil/blackbox"
@@ -21,12 +24,33 @@
"veyron2/vlog"
)
+const (
+ SeedEnv = "VEYRON_RNG_SEED"
+)
+
+var (
+ Rand *rand.Rand
+)
+
func init() {
if os.Getenv("GOMAXPROCS") == "" {
// Set the number of logical processors to the number of CPUs,
// if GOMAXPROCS is not set in the environment.
runtime.GOMAXPROCS(runtime.NumCPU())
}
+ // Initialize pseudo-random number generator.
+ seed := time.Now().UnixNano()
+ seedString := os.Getenv(SeedEnv)
+ if seedString != "" {
+ var err error
+ base, bitSize := 0, 64
+ seed, err = strconv.ParseInt(seedString, 0, 64)
+ if err != nil {
+ vlog.Fatalf("ParseInt(%v, %v, %v) failed: %v", seedString, base, bitSize, err)
+ }
+ }
+ vlog.Infof("Seeding pseudo-random number generator with %v", seed)
+ Rand = rand.New(rand.NewSource(seed))
// At this point all of the flags that we're going to use for
// tests must be defined.
flag.Parse()
diff --git a/lib/testutil/util.go b/lib/testutil/util.go
index d94633c..f69071f 100644
--- a/lib/testutil/util.go
+++ b/lib/testutil/util.go
@@ -8,6 +8,7 @@
"path/filepath"
"runtime"
"strconv"
+ "sync"
"time"
isecurity "veyron/runtimes/google/security"
@@ -15,16 +16,26 @@
"veyron2/security"
)
-// FormatLogLine will prepend the file and line number of the caller
-// at the specificied depth (as per runtime.Caller) to the supplied
-// format and args and return a formatted string. It is useful when
-// implementing functions that factor out error handling and reporting
-// in tests.
-func FormatLogLine(depth int, format string, args ...interface{}) string {
- _, file, line, _ := runtime.Caller(depth)
- nargs := []interface{}{filepath.Base(file), line}
- nargs = append(nargs, args...)
- return fmt.Sprintf("%s:%d: "+format, nargs...)
+var (
+ random []byte
+ randomMutex sync.Mutex
+)
+
+func generateRandomBytes(size int) []byte {
+ buffer := make([]byte, size)
+ offset := 0
+ for {
+ bits := int64(Rand.Int63())
+ for i := 0; i < 8; i++ {
+ buffer[offset] = byte(bits & 0xff)
+ size--
+ if size == 0 {
+ return buffer
+ }
+ offset++
+ bits >>= 8
+ }
+ }
}
// DepthToExternalCaller determines the number of stack frames to the first
@@ -45,41 +56,16 @@
return 1
}
-// SaveIdentityToFile saves the provided identity in Base64VOM format
-// to a randomly created temporary file, and returns the path to the file.
-// This function is meant to be used for testing purposes only, it panics
-// if there is an error. The caller must ensure that the created file
-// is removed once it is no longer needed.
-func SaveIdentityToFile(id security.PrivateID) string {
- f, err := ioutil.TempFile("", strconv.Itoa(rand.Int()))
- if err != nil {
- panic(err)
- }
- defer f.Close()
- filePath := f.Name()
-
- if err := security.SaveIdentity(f, id); err != nil {
- os.Remove(filePath)
- panic(err)
- }
- return filePath
-}
-
-// SaveACLToFile saves the provided ACL in JSON format to a randomly created
-// temporary file, and returns the path to the file. This function is meant
-// to be used for testing purposes only, it panics if there is an error. The
-// caller must ensure that the created file is removed once it is no longer needed.
-func SaveACLToFile(acl security.ACL) string {
- f, err := ioutil.TempFile("", "saved_acl")
- if err != nil {
- panic(err)
- }
- defer f.Close()
- if err := security.SaveACL(f, acl); err != nil {
- defer os.Remove(f.Name())
- panic(err)
- }
- return f.Name()
+// FormatLogLine will prepend the file and line number of the caller
+// at the specificied depth (as per runtime.Caller) to the supplied
+// format and args and return a formatted string. It is useful when
+// implementing functions that factor out error handling and reporting
+// in tests.
+func FormatLogLine(depth int, format string, args ...interface{}) string {
+ _, file, line, _ := runtime.Caller(depth)
+ nargs := []interface{}{filepath.Base(file), line}
+ nargs = append(nargs, args...)
+ return fmt.Sprintf("%s:%d: "+format, nargs...)
}
// NewBlessedIdentity creates a new identity and blesses it using the provided blesser
@@ -101,3 +87,59 @@
}
return derivedID
}
+
+// RandomBytes generates the given number of random bytes.
+func RandomBytes(size int) []byte {
+ buffer := make([]byte, size)
+ randomMutex.Lock()
+ defer randomMutex.Unlock()
+ // Generate a 10MB of random bytes since that is a value commonly
+ // used in the tests.
+ if len(random) == 0 {
+ random = generateRandomBytes(10 << 20)
+ }
+ if size > len(random) {
+ extra := generateRandomBytes(size - len(random))
+ random = append(random, extra...)
+ }
+ start := Rand.Intn(len(random) - size + 1)
+ copy(buffer, random[start:start+size])
+ return buffer
+}
+
+// SaveACLToFile saves the provided ACL in JSON format to a randomly created
+// temporary file, and returns the path to the file. This function is meant
+// to be used for testing purposes only, it panics if there is an error. The
+// caller must ensure that the created file is removed once it is no longer needed.
+func SaveACLToFile(acl security.ACL) string {
+ f, err := ioutil.TempFile("", "saved_acl")
+ if err != nil {
+ panic(err)
+ }
+ defer f.Close()
+ if err := security.SaveACL(f, acl); err != nil {
+ defer os.Remove(f.Name())
+ panic(err)
+ }
+ return f.Name()
+}
+
+// SaveIdentityToFile saves the provided identity in Base64VOM format
+// to a randomly created temporary file, and returns the path to the file.
+// This function is meant to be used for testing purposes only, it panics
+// if there is an error. The caller must ensure that the created file
+// is removed once it is no longer needed.
+func SaveIdentityToFile(id security.PrivateID) string {
+ f, err := ioutil.TempFile("", strconv.Itoa(rand.Int()))
+ if err != nil {
+ panic(err)
+ }
+ defer f.Close()
+ filePath := f.Name()
+
+ if err := security.SaveIdentity(f, id); err != nil {
+ os.Remove(filePath)
+ panic(err)
+ }
+ return filePath
+}
diff --git a/runtimes/google/ipc/results_store_test.go b/runtimes/google/ipc/results_store_test.go
index 03b2962..6ddc67a 100644
--- a/runtimes/google/ipc/results_store_test.go
+++ b/runtimes/google/ipc/results_store_test.go
@@ -1,17 +1,18 @@
package ipc
import (
- "math/rand"
"sort"
"sync"
"testing"
+
+ "veyron/lib/testutil"
)
func randomKeys() []uint64 {
- n := (rand.Intn(256*10) / 10) + 256
+ n := (testutil.Rand.Intn(256*10) / 10) + 256
k := make([]uint64, n)
for i := 0; i < n; i++ {
- k[i] = uint64(rand.Int63())
+ k[i] = uint64(testutil.Rand.Int63())
}
return k
}
diff --git a/runtimes/google/ipc/stream/vc/vc_test.go b/runtimes/google/ipc/stream/vc/vc_test.go
index 91d22ea..ae93fd8 100644
--- a/runtimes/google/ipc/stream/vc/vc_test.go
+++ b/runtimes/google/ipc/stream/vc/vc_test.go
@@ -5,17 +5,14 @@
import (
"bytes"
"io"
- "math/rand"
"net"
- "os"
"reflect"
"runtime"
- "strconv"
"strings"
"sync"
"testing"
- "time"
+ "veyron/lib/testutil"
"veyron/runtimes/google/ipc/stream/id"
"veyron/runtimes/google/ipc/stream/vc"
"veyron/runtimes/google/lib/bqueue"
@@ -26,7 +23,6 @@
"veyron2/ipc/stream"
"veyron2/naming"
"veyron2/security"
- "veyron2/vlog"
)
// Convenience alias to avoid conflicts between the package name "vc" and variables called "vc".
@@ -47,11 +43,11 @@
// ensures that the same string is read back.
func testFlowEcho(t *testing.T, flow stream.Flow, size int) {
defer flow.Close()
- wrote := randomString(size)
+ wrote := testutil.RandomBytes(size)
go func() {
buf := wrote
for len(buf) > 0 {
- limit := 1 + rand.Intn(len(buf)) // Random number in [1, n]
+ limit := 1 + testutil.Rand.Intn(len(buf)) // Random number in [1, n]
n, err := flow.Write(buf[:limit])
if n != limit || err != nil {
t.Errorf("Write returned (%d, %v) want (%d, nil)", n, err, limit)
@@ -378,63 +374,6 @@
}
}
-func init() {
- // Initialize pseudo-random number generator.
- var seed int64
- seedString := os.Getenv("VEYRON_RNG_SEED")
- if seedString == "" {
- seed = time.Since(time.Unix(0, 0)).Nanoseconds()
- } else {
- var err error
- base, bits := 10, 64
- if seed, err = strconv.ParseInt(seedString, base, bits); err != nil {
- vlog.Fatalf("ParseInt(%v, %v, %v) failed: %v", seedString, base, bits, err)
- }
- }
- rand.Seed(seed)
- vlog.VI(0).Infof("Using pseudo-random number generator seed = %v", seed)
-}
-
-var (
- randomMutex sync.Mutex
- random []byte
-)
-
-func generateBits(size int) []byte {
- buffer := make([]byte, size)
- offset := 0
- for {
- bits := int64(rand.Int63())
- for i := 0; i < 8; i++ {
- buffer[offset] = byte(bits & 0xff)
- size--
- if size == 0 {
- return buffer
- }
- offset++
- bits >>= 8
- }
- }
-}
-
-func randomString(size int) []byte {
- buffer := make([]byte, size)
- randomMutex.Lock()
- defer randomMutex.Unlock()
- // Generate a 10MB of random bytes since that is a value commonly
- // used in this test.
- if len(random) == 0 {
- random = generateBits(10 << 20)
- }
- if size > len(random) {
- extra := generateBits(size - len(random))
- random = append(random, extra...)
- }
- start := rand.Intn(len(random) - size + 1)
- copy(buffer, random[start:start+size])
- return buffer
-}
-
type endpoint naming.RoutingID
func (e endpoint) Network() string { return "test" }
diff --git a/runtimes/google/ipc/stream/vif/vif_test.go b/runtimes/google/ipc/stream/vif/vif_test.go
index 47538f1..ab377f4 100644
--- a/runtimes/google/ipc/stream/vif/vif_test.go
+++ b/runtimes/google/ipc/stream/vif/vif_test.go
@@ -6,21 +6,19 @@
import (
"bytes"
- crand "crypto/rand"
- "encoding/base64"
"fmt"
"io"
- "math/rand"
"net"
"reflect"
"runtime"
"sort"
"testing"
- _ "veyron/lib/testutil"
+ "veyron/lib/testutil"
"veyron/runtimes/google/ipc/stream/vc"
"veyron/runtimes/google/ipc/stream/vif"
iversion "veyron/runtimes/google/ipc/version"
+
"veyron2"
"veyron2/ipc/stream"
"veyron2/ipc/version"
@@ -121,7 +119,7 @@
// Fill in random strings that will be written over the Flows.
dataWritten := make([]string, nFlows)
for i := 0; i < nFlows; i++ {
- dataWritten[i] = mkRandomString(maxBytesPerFlow)
+ dataWritten[i] = string(testutil.RandomBytes(maxBytesPerFlow))
}
// write writes data to flow in randomly sized chunks.
@@ -130,7 +128,7 @@
buf := []byte(data)
// Split into a random number of Write calls.
for len(buf) > 0 {
- size := 1 + rand.Intn(len(buf)) // Random number in [1, len(buf)]
+ size := 1 + testutil.Rand.Intn(len(buf)) // Random number in [1, len(buf)]
n, err := flow.Write(buf[:size])
if err != nil {
t.Errorf("Write failed: (%d, %v)", n, err)
@@ -146,7 +144,7 @@
var buf bytes.Buffer
var tmp [1024]byte
for {
- n, err := flow.Read(tmp[:rand.Intn(len(tmp))])
+ n, err := flow.Read(tmp[:testutil.Rand.Intn(len(tmp))])
buf.Write(tmp[:n])
if err == io.EOF {
break
@@ -385,18 +383,6 @@
return iversion.Endpoint("test", "addr", naming.FixedRoutingID(rid))
}
-func mkRandomString(maxSize int) string {
- // Pick a random size.
- // base64 encodes 6-bits in each byte, so adjust the maxSize accordingly.
- size := rand.Intn(maxSize * 8 / 6)
- bytes := make([]byte, size)
- // Don't really need a cryptographically random string, but that is the easiest way to fill in bytes.
- if _, err := crand.Read(bytes); err != nil {
- panic(err)
- }
- return base64.StdEncoding.EncodeToString(bytes)
-}
-
// pipeAddr provides a more descriptive String implementation than provided by net.Pipe.
type pipeAddr struct{ name string }
diff --git a/runtimes/google/lib/deque/deque_test.go b/runtimes/google/lib/deque/deque_test.go
index f8f9f0b..f1b3600 100644
--- a/runtimes/google/lib/deque/deque_test.go
+++ b/runtimes/google/lib/deque/deque_test.go
@@ -1,8 +1,9 @@
package deque
import (
- "math/rand"
"testing"
+
+ "veyron/lib/testutil"
)
func TestBasic(t *testing.T) {
@@ -132,13 +133,13 @@
var q T
var contents []int
for i := 0; i != 1000; i++ {
- switch rand.Intn(4) {
+ switch testutil.Rand.Intn(4) {
case 0:
- i := rand.Int()
+ i := testutil.Rand.Int()
contents = append([]int{i}, contents...)
q.PushFront(i)
case 1:
- i := rand.Int()
+ i := testutil.Rand.Int()
contents = append(contents, i)
q.PushBack(i)
case 2:
diff --git a/runtimes/google/lib/functional/rb/rb_set_test.go b/runtimes/google/lib/functional/rb/rb_set_test.go
index 42172a6..77623ba 100644
--- a/runtimes/google/lib/functional/rb/rb_set_test.go
+++ b/runtimes/google/lib/functional/rb/rb_set_test.go
@@ -3,10 +3,9 @@
import (
"fmt"
"log"
- "math/rand"
"testing"
- "time"
+ "veyron/lib/testutil"
"veyron/runtimes/google/lib/functional"
)
@@ -158,22 +157,18 @@
// Randomized add and remove.
func TestRandom(t *testing.T) {
- now := time.Now().UnixNano()
- log.Printf("value used to seed rand: %v", now)
- seed := rand.NewSource(now)
- rnd := rand.New(seed)
s := NewSet(intCompare)
elements := make(map[int]struct{})
for i := 0; i != kLoopCount; i++ {
- switch rnd.Intn(2) {
+ switch testutil.Rand.Intn(2) {
case 0:
// Insertion
- x := rnd.Intn(kMaxElement)
+ x := testutil.Rand.Intn(kMaxElement)
elements[x] = struct{}{}
s = s.Put(x)
case 1:
// Deletion
- x := rnd.Intn(kMaxElement)
+ x := testutil.Rand.Intn(kMaxElement)
delete(elements, x)
s = s.Remove(x)
}
@@ -275,23 +270,19 @@
}
func TestRandomMap(t *testing.T) {
- now := time.Now().UnixNano()
- log.Printf("value used to seed rand: %v", now)
- seed := rand.NewSource(now)
- rnd := rand.New(seed)
s := NewSet(entryLessThan)
elements := make(map[int]interface{})
for i := 0; i != kLoopCount; i++ {
- switch rnd.Intn(2) {
+ switch testutil.Rand.Intn(2) {
case 0:
// Insertion
- k := rnd.Intn(kMaxElement)
- v := rnd.Int()
+ k := testutil.Rand.Intn(kMaxElement)
+ v := testutil.Rand.Int()
elements[k] = v
s = s.Put(&entry{key: k, value: v})
case 1:
// Deletion
- k := rnd.Intn(kMaxElement)
+ k := testutil.Rand.Intn(kMaxElement)
delete(elements, k)
s = s.Remove(&entry{key: k})
}
@@ -412,21 +403,19 @@
}
func BenchmarkRandom(b *testing.B) {
- seed := rand.NewSource(time.Now().UnixNano())
- rnd := rand.New(seed)
operations := makeOperations()
s := NewSet(intCompare)
for i := 0; i != b.N; i++ {
- switch operations[rnd.Intn(len(operations))] {
+ switch operations[testutil.Rand.Intn(len(operations))] {
case CONTAINS:
- s.Contains(rnd.Intn(kMaxElement))
+ s.Contains(testutil.Rand.Intn(kMaxElement))
case PUT:
- s = s.Put(rnd.Intn(kMaxElement))
+ s = s.Put(testutil.Rand.Intn(kMaxElement))
case REMOVE:
- s = s.Remove(rnd.Intn(kMaxElement))
+ s = s.Remove(testutil.Rand.Intn(kMaxElement))
case ITERATE:
s.Iter(func(it interface{}) bool { return true })
diff --git a/runtimes/google/testing/concurrency/clock_test.go b/runtimes/google/testing/concurrency/clock_test.go
index b664b71..32ae14a 100644
--- a/runtimes/google/testing/concurrency/clock_test.go
+++ b/runtimes/google/testing/concurrency/clock_test.go
@@ -1,14 +1,15 @@
package concurrency
import (
- "math/rand"
"testing"
+
+ "veyron/lib/testutil"
)
// TestClone checks the clone() method of a clock.
func TestClone(t *testing.T) {
c1 := newClock()
- c1[0] = rand.Intn(100)
+ c1[0] = testutil.Rand.Intn(100)
c2 := c1.clone()
c1[0]++
if c2[0] != c1[0]-1 {
@@ -20,7 +21,7 @@
func TestEquality(t *testing.T) {
c1, c2 := newClock(), newClock()
for i := TID(0); i < TID(10); i++ {
- c1[i] = rand.Intn(100)
+ c1[i] = testutil.Rand.Intn(100)
c2[i] = c1[i]
}
if !c1.equals(c2) {
@@ -32,7 +33,7 @@
func TestHappensBefore(t *testing.T) {
c1, c2, c3 := newClock(), newClock(), newClock()
for i := TID(0); i < TID(10); i++ {
- c1[i] = rand.Intn(100)
+ c1[i] = testutil.Rand.Intn(100)
if i%2 == 0 {
c2[i] = c1[i] + 1
c3[i] = c1[i] + 1
@@ -62,8 +63,8 @@
func TestMerge(t *testing.T) {
c1, c2 := newClock(), newClock()
for i := TID(0); i < TID(10); i++ {
- c1[i] = rand.Intn(100)
- c2[i] = rand.Intn(100)
+ c1[i] = testutil.Rand.Intn(100)
+ c2[i] = testutil.Rand.Intn(100)
}
c1.merge(c2)
for i := TID(0); i < TID(10); i++ {
diff --git a/runtimes/google/vsync/dag_test.go b/runtimes/google/vsync/dag_test.go
index 5f94836..659a586 100644
--- a/runtimes/google/vsync/dag_test.go
+++ b/runtimes/google/vsync/dag_test.go
@@ -5,12 +5,13 @@
import (
"errors"
"fmt"
- "math/rand"
"os"
"reflect"
"testing"
"time"
+ "veyron/lib/testutil"
+
"veyron2/storage"
)
@@ -1076,12 +1077,12 @@
headMap := make(map[storage.ID]storage.Version)
for i := 0; i < 10; i++ {
// Generate a random object id in [0, 1000).
- oid, err := strToObjID(fmt.Sprintf("%d", rand.Intn(1000)))
+ oid, err := strToObjID(fmt.Sprintf("%d", testutil.Rand.Intn(1000)))
if err != nil {
t.Fatal(err)
}
// Generate a random version number for this object.
- vers := storage.Version(rand.Intn(5000))
+ vers := storage.Version(testutil.Rand.Intn(5000))
// Cache this <oid,version> pair to verify with getHead().
headMap[oid] = vers
@@ -1102,10 +1103,10 @@
nodeMap := make(map[nodeKey]*dagNode)
for oid, vers := range headMap {
// Generate a random dag node for this <oid, vers>.
- l := uint64(rand.Intn(20))
- p1 := storage.Version(rand.Intn(5000))
- p2 := storage.Version(rand.Intn(5000))
- log := fmt.Sprintf("%d", rand.Intn(1000))
+ l := uint64(testutil.Rand.Intn(20))
+ p1 := storage.Version(testutil.Rand.Intn(5000))
+ p2 := storage.Version(testutil.Rand.Intn(5000))
+ log := fmt.Sprintf("%d", testutil.Rand.Intn(1000))
node := &dagNode{Level: l, Parents: []storage.Version{p1, p2}, Logrec: log}
// Cache this <oid,version, dagNode> to verify with getNode().