mtest->mrand: move rand functionality from mtest into its own package

This commit is contained in:
Brian Picciano 2018-07-03 00:20:00 +00:00
parent f9ec4d7bce
commit 1be0072701
11 changed files with 137 additions and 106 deletions

View File

@ -7,7 +7,7 @@ import (
"io/ioutil" "io/ioutil"
. "testing" . "testing"
"github.com/mediocregopher/mediocre-go-lib/mtest" "github.com/mediocregopher/mediocre-go-lib/mrand"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -23,16 +23,16 @@ func randBBRTest(minBodySize, maxBodySize int) bbrTest {
genWhitespace := func(n int) []byte { genWhitespace := func(n int) []byte {
ws := make([]byte, n) ws := make([]byte, n)
for i := range ws { for i := range ws {
ws[i] = whitespace[mtest.Rand.Intn(len(whitespace))] ws[i] = whitespace[mrand.Intn(len(whitespace))]
} }
return ws return ws
} }
body := mtest.RandBytes(minBodySize + mtest.Rand.Intn(maxBodySize-minBodySize)) body := mrand.Bytes(minBodySize + mrand.Intn(maxBodySize-minBodySize))
return bbrTest{ return bbrTest{
wsSuffix: genWhitespace(mtest.Rand.Intn(10)), wsSuffix: genWhitespace(mrand.Intn(10)),
body: body, body: body,
intoSize: 1 + mtest.Rand.Intn(len(body)+1), intoSize: 1 + mrand.Intn(len(body)+1),
} }
} }

View File

@ -8,7 +8,7 @@ import (
"sync" "sync"
. "testing" . "testing"
"github.com/mediocregopher/mediocre-go-lib/mtest" "github.com/mediocregopher/mediocre-go-lib/mrand"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -45,7 +45,7 @@ func TestEncoderDecoder(t *T) {
randTestCase = func(typ Type, cancelable bool) testCase { randTestCase = func(typ Type, cancelable bool) testCase {
// if typ isn't given then use a random one // if typ isn't given then use a random one
if typ == "" { if typ == "" {
pick := mtest.Rand.Intn(5) pick := mrand.Intn(5)
switch { switch {
case pick == 0: case pick == 0:
typ = TypeStream typ = TypeStream
@ -58,24 +58,24 @@ func TestEncoderDecoder(t *T) {
tc := testCase{ tc := testCase{
typ: typ, typ: typ,
cancel: cancelable && mtest.Rand.Intn(10) == 0, cancel: cancelable && mrand.Intn(10) == 0,
} }
switch typ { switch typ {
case TypeJSONValue: case TypeJSONValue:
tc.val = map[string]interface{}{ tc.val = map[string]interface{}{
mtest.RandHex(8): mtest.RandHex(8), mrand.Hex(8): mrand.Hex(8),
mtest.RandHex(8): mtest.RandHex(8), mrand.Hex(8): mrand.Hex(8),
mtest.RandHex(8): mtest.RandHex(8), mrand.Hex(8): mrand.Hex(8),
mtest.RandHex(8): mtest.RandHex(8), mrand.Hex(8): mrand.Hex(8),
mtest.RandHex(8): mtest.RandHex(8), mrand.Hex(8): mrand.Hex(8),
} }
return tc return tc
case TypeByteBlob: case TypeByteBlob:
tc.bytes = mtest.RandBytes(mtest.Rand.Intn(256)) tc.bytes = mrand.Bytes(mrand.Intn(256))
return tc return tc
case TypeStream: case TypeStream:
for i := mtest.Rand.Intn(10); i > 0; i-- { for i := mrand.Intn(10); i > 0; i-- {
tc.stream = append(tc.stream, randTestCase("", true)) tc.stream = append(tc.stream, randTestCase("", true))
} }
return tc return tc

View File

@ -6,7 +6,7 @@ import (
"strconv" "strconv"
. "testing" . "testing"
"github.com/mediocregopher/mediocre-go-lib/mtest" "github.com/mediocregopher/mediocre-go-lib/mrand"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -188,9 +188,9 @@ func TestSourceCLI(t *T) {
} }
} }
childName := mtest.RandHex(8) childName := mrand.Hex(8)
childDashName := mtest.RandHex(4) + "-" + mtest.RandHex(4) childDashName := mrand.Hex(4) + "-" + mrand.Hex(4)
childEqName := mtest.RandHex(4) + "=" + mtest.RandHex(4) childEqName := mrand.Hex(4) + "=" + mrand.Hex(4)
var args []string var args []string
rootCfg := New() rootCfg := New()
@ -204,11 +204,11 @@ func TestSourceCLI(t *T) {
switch tests[i].name { switch tests[i].name {
case "normal": case "normal":
pv.Name = mtest.RandHex(8) pv.Name = mrand.Hex(8)
case "wDash": case "wDash":
pv.Name = mtest.RandHex(4) + "-" + mtest.RandHex(4) pv.Name = mrand.Hex(4) + "-" + mrand.Hex(4)
case "wEq": case "wEq":
pv.Name = mtest.RandHex(4) + "=" + mtest.RandHex(4) pv.Name = mrand.Hex(4) + "=" + mrand.Hex(4)
} }
pv.IsBool = tests[i].isBool pv.IsBool = tests[i].isBool
@ -242,18 +242,18 @@ func TestSourceCLI(t *T) {
var val string var val string
switch tests[i].nonBoolType { switch tests[i].nonBoolType {
case "int": case "int":
val = strconv.Itoa(mtest.Rand.Int()) val = strconv.Itoa(mrand.Int())
pv.Value = json.RawMessage(val) pv.Value = json.RawMessage(val)
case "str": case "str":
switch tests[i].nonBoolStrValue { switch tests[i].nonBoolStrValue {
case "empty": case "empty":
// ez // ez
case "normal": case "normal":
val = mtest.RandHex(8) val = mrand.Hex(8)
case "wDash": case "wDash":
val = mtest.RandHex(4) + "-" + mtest.RandHex(4) val = mrand.Hex(4) + "-" + mrand.Hex(4)
case "wEq": case "wEq":
val = mtest.RandHex(4) + "=" + mtest.RandHex(4) val = mrand.Hex(4) + "=" + mrand.Hex(4)
} }
pv.Value = json.RawMessage(`"` + val + `"`) pv.Value = json.RawMessage(`"` + val + `"`)
} }

View File

@ -3,7 +3,7 @@ package mcrypto
import ( import (
. "testing" . "testing"
"github.com/mediocregopher/mediocre-go-lib/mtest" "github.com/mediocregopher/mediocre-go-lib/mrand"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -11,7 +11,7 @@ func TestKeyPair(t *T) {
pub, priv := NewWeakKeyPair() pub, priv := NewWeakKeyPair()
// test signing/verifying // test signing/verifying
str := mtest.RandHex(512) str := mrand.Hex(512)
sig := SignString(priv, str) sig := SignString(priv, str)
assert.NoError(t, VerifyString(pub, sig, str)) assert.NoError(t, VerifyString(pub, sig, str))
} }

View File

@ -5,12 +5,12 @@ import (
"time" "time"
"github.com/ansel1/merry" "github.com/ansel1/merry"
"github.com/mediocregopher/mediocre-go-lib/mtest" "github.com/mediocregopher/mediocre-go-lib/mrand"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestSecretSignVerify(t *T) { func TestSecretSignVerify(t *T) {
secretRaw := mtest.RandBytes(16) secretRaw := mrand.Bytes(16)
secret := NewSecret(secretRaw) secret := NewSecret(secretRaw)
weakSecret := NewWeakSecret(secretRaw) weakSecret := NewWeakSecret(secretRaw)
var prevStr string var prevStr string
@ -20,7 +20,7 @@ func TestSecretSignVerify(t *T) {
secret.testNow = now secret.testNow = now
weakSecret.testNow = now weakSecret.testNow = now
thisStr := mtest.RandHex(512) thisStr := mrand.Hex(512)
thisSig := SignString(secret, thisStr) thisSig := SignString(secret, thisStr)
thisWeakSig := SignString(weakSecret, thisStr) thisWeakSig := SignString(weakSecret, thisStr)
thisSigStr, thisWeakSigStr := thisSig.String(), thisWeakSig.String() thisSigStr, thisWeakSigStr := thisSig.String(), thisWeakSig.String()

View File

@ -5,19 +5,19 @@ import (
"time" "time"
"github.com/ansel1/merry" "github.com/ansel1/merry"
"github.com/mediocregopher/mediocre-go-lib/mtest" "github.com/mediocregopher/mediocre-go-lib/mrand"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestSignerVerifier(t *T) { func TestSignerVerifier(t *T) {
secret := NewSecret(mtest.RandBytes(16)) secret := NewSecret(mrand.Bytes(16))
var prevStr string var prevStr string
var prevSig Signature var prevSig Signature
for i := 0; i < 10000; i++ { for i := 0; i < 10000; i++ {
now := time.Now().Round(0) now := time.Now().Round(0)
secret.testNow = now secret.testNow = now
thisStr := mtest.RandHex(512) thisStr := mrand.Hex(512)
thisSig := SignString(secret, thisStr) thisSig := SignString(secret, thisStr)
thisSigStr := thisSig.String() thisSigStr := thisSig.String()

View File

@ -6,7 +6,7 @@ import (
"time" "time"
"github.com/mediocregopher/mediocre-go-lib/mcfg" "github.com/mediocregopher/mediocre-go-lib/mcfg"
"github.com/mediocregopher/mediocre-go-lib/mtest" "github.com/mediocregopher/mediocre-go-lib/mrand"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -21,7 +21,7 @@ func init() {
// this requires the pubsub emulator to be running // this requires the pubsub emulator to be running
func TestPubSub(t *T) { func TestPubSub(t *T) {
topicName := "testTopic_" + mtest.RandHex(8) topicName := "testTopic_" + mrand.Hex(8)
ctx := context.Background() ctx := context.Background()
// Topic shouldn't exist yet // Topic shouldn't exist yet
@ -51,7 +51,7 @@ func TestPubSub(t *T) {
func TestBatchPubSub(t *T) { func TestBatchPubSub(t *T) {
ctx := context.Background() ctx := context.Background()
topicName := "testBatchTopic_" + mtest.RandHex(8) topicName := "testBatchTopic_" + mrand.Hex(8)
topic, err := testPS.Topic(ctx, topicName, true) topic, err := testPS.Topic(ctx, topicName, true)
require.NoError(t, err) require.NoError(t, err)

91
mrand/mrand.go Normal file
View File

@ -0,0 +1,91 @@
// Package mrand implements extensions and conveniences for using the default
// math/rand package.
package mrand
import (
"encoding/hex"
"math/rand"
"reflect"
"time"
)
// Rand extends the default rand.Rand type with extra functionality.
type Rand struct {
*rand.Rand
}
// Bytes returns n random bytes.
func (r Rand) Bytes(n int) []byte {
b := make([]byte, n)
if _, err := r.Read(b); err != nil {
panic(err)
}
return b
}
// Hex returns a random hex string which is n characters long.
func (r Rand) Hex(n int) string {
b := r.Bytes(hex.DecodedLen(n))
return hex.EncodeToString(b)
}
// Element returns a random element from the given slice.
//
// If a weighting function is given then that function is used to weight each
// element of the slice relative to the others, based on whatever metric and
// scale is desired. The weight function must be able to be called more than
// once on each element.
func (r Rand) Element(slice interface{}, weight func(i int) uint64) interface{} {
v := reflect.ValueOf(slice)
l := v.Len()
if weight == nil {
return v.Index(r.Intn(l)).Interface()
}
var totalWeight uint64
for i := 0; i < l; i++ {
totalWeight += weight(i)
}
target := r.Int63n(int64(totalWeight))
for i := 0; i < l; i++ {
w := int64(weight(i))
target -= w
if target < 0 {
return v.Index(i).Interface()
}
}
panic("should never get here, perhaps the weighting function is inconsistent?")
}
////////////////////////////////////////////////////////////////////////////////
// DefaultRand is an instance off Rand whose methods are directly exported by
// this package for convenience.
var DefaultRand = Rand{Rand: rand.New(rand.NewSource(time.Now().UnixNano()))}
// Methods off DefaultRand exported to the top level of this package.
var (
ExpFloat64 = DefaultRand.ExpFloat64
Float32 = DefaultRand.Float32
Float64 = DefaultRand.Float64
Int = DefaultRand.Int
Int31 = DefaultRand.Int31
Int31n = DefaultRand.Int31n
Int63 = DefaultRand.Int63
Int63n = DefaultRand.Int63n
Intn = DefaultRand.Intn
NormFloat64 = DefaultRand.NormFloat64
Perm = DefaultRand.Perm
Read = DefaultRand.Read
Seed = DefaultRand.Seed
Shuffle = DefaultRand.Shuffle
Uint32 = DefaultRand.Uint32
Uint64 = DefaultRand.Uint64
// extended methods
Bytes = DefaultRand.Bytes
Hex = DefaultRand.Hex
Element = DefaultRand.Element
)

View File

@ -1,4 +1,4 @@
package mtest package mrand
import ( import (
. "testing" . "testing"
@ -9,7 +9,7 @@ import (
func TestRandBytes(t *T) { func TestRandBytes(t *T) {
var prev []byte var prev []byte
for i := 0; i < 10000; i++ { for i := 0; i < 10000; i++ {
curr := RandBytes(16) curr := Bytes(16)
assert.Len(t, curr, 16) assert.Len(t, curr, 16)
assert.NotEqual(t, prev, curr) assert.NotEqual(t, prev, curr)
prev = curr prev = curr
@ -19,7 +19,7 @@ func TestRandBytes(t *T) {
func TestRandHex(t *T) { func TestRandHex(t *T) {
// RandHex is basically a wrapper of RandBytes, so we don't have to test it // RandHex is basically a wrapper of RandBytes, so we don't have to test it
// much // much
assert.Len(t, RandHex(16), 16) assert.Len(t, Hex(16), 16)
} }
func TestRandElement(t *T) { func TestRandElement(t *T) {
@ -35,7 +35,7 @@ func TestRandElement(t *T) {
iterations := 100000 iterations := 100000
for i := 0; i < iterations; i++ { for i := 0; i < iterations; i++ {
el := RandElement(slice, func(i int) uint64 { return slice[i] }).(uint64) el := Element(slice, func(i int) uint64 { return slice[i] }).(uint64)
m[el]++ m[el]++
} }

View File

@ -4,7 +4,7 @@ import (
"context" "context"
. "testing" . "testing"
"github.com/mediocregopher/mediocre-go-lib/mtest" "github.com/mediocregopher/mediocre-go-lib/mrand"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -28,14 +28,14 @@ func TestReflectClient(t *T) {
})) }))
{ // test with arg being non-pointer { // test with arg being non-pointer
in := mtest.RandHex(8) in := mrand.Hex(8)
var res resT var res resT
assert.NoError(t, client.CallRPC(ctx, &res, "foo", argT{In: in})) assert.NoError(t, client.CallRPC(ctx, &res, "foo", argT{In: in}))
assert.Equal(t, in, res.Out) assert.Equal(t, in, res.Out)
} }
{ // test with arg being pointer { // test with arg being pointer
in := mtest.RandHex(8) in := mrand.Hex(8)
var res resT var res resT
assert.NoError(t, client.CallRPC(ctx, &res, "foo", &argT{In: in})) assert.NoError(t, client.CallRPC(ctx, &res, "foo", &argT{In: in}))
assert.Equal(t, in, res.Out) assert.Equal(t, in, res.Out)
@ -51,14 +51,14 @@ func TestReflectClient(t *T) {
})) }))
{ // test with arg being non-pointer { // test with arg being non-pointer
in := mtest.RandHex(8) in := mrand.Hex(8)
var res resT var res resT
assert.NoError(t, client.CallRPC(ctx, &res, "foo", argT{In: in})) assert.NoError(t, client.CallRPC(ctx, &res, "foo", argT{In: in}))
assert.Equal(t, in, res.Out) assert.Equal(t, in, res.Out)
} }
{ // test with arg being pointer { // test with arg being pointer
in := mtest.RandHex(8) in := mrand.Hex(8)
var res resT var res resT
assert.NoError(t, client.CallRPC(ctx, &res, "foo", &argT{In: in})) assert.NoError(t, client.CallRPC(ctx, &res, "foo", &argT{In: in}))
assert.Equal(t, in, res.Out) assert.Equal(t, in, res.Out)

View File

@ -1,60 +0,0 @@
// Package mtest contains types and functions which are useful when writing
// tests
package mtest
import (
crand "crypto/rand"
"encoding/hex"
"math/rand"
"reflect"
"time"
)
// Rand is a public instance of rand.Rand, seeded with the current
// nano-timestamp
var Rand = rand.New(rand.NewSource(time.Now().UnixNano()))
// RandBytes returns n random bytes
func RandBytes(n int) []byte {
b := make([]byte, n)
if _, err := crand.Read(b); err != nil {
panic(err)
}
return b
}
// RandHex returns a random hex string which is n characters long
func RandHex(n int) string {
b := RandBytes(hex.DecodedLen(n))
return hex.EncodeToString(b)
}
// RandElement returns a random element from the given slice.
//
// If a weighting function is given then that function is used to weight each
// element of the slice relative to the others, based on whatever metric and
// scale is desired. The weight function must be able to be called more than
// once on each element.
func RandElement(slice interface{}, weight func(i int) uint64) interface{} {
v := reflect.ValueOf(slice)
l := v.Len()
if weight == nil {
return v.Index(Rand.Intn(l)).Interface()
}
var totalWeight uint64
for i := 0; i < l; i++ {
totalWeight += weight(i)
}
target := Rand.Int63n(int64(totalWeight))
for i := 0; i < l; i++ {
w := int64(weight(i))
target -= w
if target < 0 {
return v.Index(i).Interface()
}
}
panic("should never get here, perhaps the weighting function is inconsistent?")
}