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().