diff --git a/jstream/byte_blob_reader_test.go b/jstream/byte_blob_reader_test.go index 9480dae..b610def 100644 --- a/jstream/byte_blob_reader_test.go +++ b/jstream/byte_blob_reader_test.go @@ -7,7 +7,7 @@ import ( "io/ioutil" . "testing" - "github.com/mediocregopher/mediocre-go-lib/mtest" + "github.com/mediocregopher/mediocre-go-lib/mrand" "github.com/stretchr/testify/assert" ) @@ -23,16 +23,16 @@ func randBBRTest(minBodySize, maxBodySize int) bbrTest { genWhitespace := func(n int) []byte { ws := make([]byte, n) for i := range ws { - ws[i] = whitespace[mtest.Rand.Intn(len(whitespace))] + ws[i] = whitespace[mrand.Intn(len(whitespace))] } return ws } - body := mtest.RandBytes(minBodySize + mtest.Rand.Intn(maxBodySize-minBodySize)) + body := mrand.Bytes(minBodySize + mrand.Intn(maxBodySize-minBodySize)) return bbrTest{ - wsSuffix: genWhitespace(mtest.Rand.Intn(10)), + wsSuffix: genWhitespace(mrand.Intn(10)), body: body, - intoSize: 1 + mtest.Rand.Intn(len(body)+1), + intoSize: 1 + mrand.Intn(len(body)+1), } } diff --git a/jstream/jstream_test.go b/jstream/jstream_test.go index 3bd9480..6bb8980 100644 --- a/jstream/jstream_test.go +++ b/jstream/jstream_test.go @@ -8,7 +8,7 @@ import ( "sync" . "testing" - "github.com/mediocregopher/mediocre-go-lib/mtest" + "github.com/mediocregopher/mediocre-go-lib/mrand" "github.com/stretchr/testify/assert" ) @@ -45,7 +45,7 @@ func TestEncoderDecoder(t *T) { randTestCase = func(typ Type, cancelable bool) testCase { // if typ isn't given then use a random one if typ == "" { - pick := mtest.Rand.Intn(5) + pick := mrand.Intn(5) switch { case pick == 0: typ = TypeStream @@ -58,24 +58,24 @@ func TestEncoderDecoder(t *T) { tc := testCase{ typ: typ, - cancel: cancelable && mtest.Rand.Intn(10) == 0, + cancel: cancelable && mrand.Intn(10) == 0, } switch typ { case TypeJSONValue: tc.val = map[string]interface{}{ - mtest.RandHex(8): mtest.RandHex(8), - mtest.RandHex(8): mtest.RandHex(8), - mtest.RandHex(8): mtest.RandHex(8), - mtest.RandHex(8): mtest.RandHex(8), - mtest.RandHex(8): mtest.RandHex(8), + mrand.Hex(8): mrand.Hex(8), + mrand.Hex(8): mrand.Hex(8), + mrand.Hex(8): mrand.Hex(8), + mrand.Hex(8): mrand.Hex(8), + mrand.Hex(8): mrand.Hex(8), } return tc case TypeByteBlob: - tc.bytes = mtest.RandBytes(mtest.Rand.Intn(256)) + tc.bytes = mrand.Bytes(mrand.Intn(256)) return tc 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)) } return tc diff --git a/mcfg/cli_test.go b/mcfg/cli_test.go index 0a834a0..cf9f27d 100644 --- a/mcfg/cli_test.go +++ b/mcfg/cli_test.go @@ -6,7 +6,7 @@ import ( "strconv" . "testing" - "github.com/mediocregopher/mediocre-go-lib/mtest" + "github.com/mediocregopher/mediocre-go-lib/mrand" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -188,9 +188,9 @@ func TestSourceCLI(t *T) { } } - childName := mtest.RandHex(8) - childDashName := mtest.RandHex(4) + "-" + mtest.RandHex(4) - childEqName := mtest.RandHex(4) + "=" + mtest.RandHex(4) + childName := mrand.Hex(8) + childDashName := mrand.Hex(4) + "-" + mrand.Hex(4) + childEqName := mrand.Hex(4) + "=" + mrand.Hex(4) var args []string rootCfg := New() @@ -204,11 +204,11 @@ func TestSourceCLI(t *T) { switch tests[i].name { case "normal": - pv.Name = mtest.RandHex(8) + pv.Name = mrand.Hex(8) case "wDash": - pv.Name = mtest.RandHex(4) + "-" + mtest.RandHex(4) + pv.Name = mrand.Hex(4) + "-" + mrand.Hex(4) case "wEq": - pv.Name = mtest.RandHex(4) + "=" + mtest.RandHex(4) + pv.Name = mrand.Hex(4) + "=" + mrand.Hex(4) } pv.IsBool = tests[i].isBool @@ -242,18 +242,18 @@ func TestSourceCLI(t *T) { var val string switch tests[i].nonBoolType { case "int": - val = strconv.Itoa(mtest.Rand.Int()) + val = strconv.Itoa(mrand.Int()) pv.Value = json.RawMessage(val) case "str": switch tests[i].nonBoolStrValue { case "empty": // ez case "normal": - val = mtest.RandHex(8) + val = mrand.Hex(8) case "wDash": - val = mtest.RandHex(4) + "-" + mtest.RandHex(4) + val = mrand.Hex(4) + "-" + mrand.Hex(4) case "wEq": - val = mtest.RandHex(4) + "=" + mtest.RandHex(4) + val = mrand.Hex(4) + "=" + mrand.Hex(4) } pv.Value = json.RawMessage(`"` + val + `"`) } diff --git a/mcrypto/pair_test.go b/mcrypto/pair_test.go index e0a40d7..f7a60ea 100644 --- a/mcrypto/pair_test.go +++ b/mcrypto/pair_test.go @@ -3,7 +3,7 @@ package mcrypto import ( . "testing" - "github.com/mediocregopher/mediocre-go-lib/mtest" + "github.com/mediocregopher/mediocre-go-lib/mrand" "github.com/stretchr/testify/assert" ) @@ -11,7 +11,7 @@ func TestKeyPair(t *T) { pub, priv := NewWeakKeyPair() // test signing/verifying - str := mtest.RandHex(512) + str := mrand.Hex(512) sig := SignString(priv, str) assert.NoError(t, VerifyString(pub, sig, str)) } diff --git a/mcrypto/secret_test.go b/mcrypto/secret_test.go index 5311ef6..1880197 100644 --- a/mcrypto/secret_test.go +++ b/mcrypto/secret_test.go @@ -5,12 +5,12 @@ import ( "time" "github.com/ansel1/merry" - "github.com/mediocregopher/mediocre-go-lib/mtest" + "github.com/mediocregopher/mediocre-go-lib/mrand" "github.com/stretchr/testify/assert" ) func TestSecretSignVerify(t *T) { - secretRaw := mtest.RandBytes(16) + secretRaw := mrand.Bytes(16) secret := NewSecret(secretRaw) weakSecret := NewWeakSecret(secretRaw) var prevStr string @@ -20,7 +20,7 @@ func TestSecretSignVerify(t *T) { secret.testNow = now weakSecret.testNow = now - thisStr := mtest.RandHex(512) + thisStr := mrand.Hex(512) thisSig := SignString(secret, thisStr) thisWeakSig := SignString(weakSecret, thisStr) thisSigStr, thisWeakSigStr := thisSig.String(), thisWeakSig.String() diff --git a/mcrypto/sig_test.go b/mcrypto/sig_test.go index 18d5715..dc60ffe 100644 --- a/mcrypto/sig_test.go +++ b/mcrypto/sig_test.go @@ -5,19 +5,19 @@ import ( "time" "github.com/ansel1/merry" - "github.com/mediocregopher/mediocre-go-lib/mtest" + "github.com/mediocregopher/mediocre-go-lib/mrand" "github.com/stretchr/testify/assert" ) func TestSignerVerifier(t *T) { - secret := NewSecret(mtest.RandBytes(16)) + secret := NewSecret(mrand.Bytes(16)) var prevStr string var prevSig Signature for i := 0; i < 10000; i++ { now := time.Now().Round(0) secret.testNow = now - thisStr := mtest.RandHex(512) + thisStr := mrand.Hex(512) thisSig := SignString(secret, thisStr) thisSigStr := thisSig.String() diff --git a/mdb/ps_test.go b/mdb/ps_test.go index 60d1499..35dc288 100644 --- a/mdb/ps_test.go +++ b/mdb/ps_test.go @@ -6,7 +6,7 @@ import ( "time" "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/require" ) @@ -21,7 +21,7 @@ func init() { // this requires the pubsub emulator to be running func TestPubSub(t *T) { - topicName := "testTopic_" + mtest.RandHex(8) + topicName := "testTopic_" + mrand.Hex(8) ctx := context.Background() // Topic shouldn't exist yet @@ -51,7 +51,7 @@ func TestPubSub(t *T) { func TestBatchPubSub(t *T) { ctx := context.Background() - topicName := "testBatchTopic_" + mtest.RandHex(8) + topicName := "testBatchTopic_" + mrand.Hex(8) topic, err := testPS.Topic(ctx, topicName, true) require.NoError(t, err) diff --git a/mrand/mrand.go b/mrand/mrand.go new file mode 100644 index 0000000..efe3352 --- /dev/null +++ b/mrand/mrand.go @@ -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 +) diff --git a/mtest/mtest_test.go b/mrand/mrand_test.go similarity index 88% rename from mtest/mtest_test.go rename to mrand/mrand_test.go index ae28cb1..cb90e1f 100644 --- a/mtest/mtest_test.go +++ b/mrand/mrand_test.go @@ -1,4 +1,4 @@ -package mtest +package mrand import ( . "testing" @@ -9,7 +9,7 @@ import ( func TestRandBytes(t *T) { var prev []byte for i := 0; i < 10000; i++ { - curr := RandBytes(16) + curr := Bytes(16) assert.Len(t, curr, 16) assert.NotEqual(t, prev, curr) prev = curr @@ -19,7 +19,7 @@ func TestRandBytes(t *T) { func TestRandHex(t *T) { // RandHex is basically a wrapper of RandBytes, so we don't have to test it // much - assert.Len(t, RandHex(16), 16) + assert.Len(t, Hex(16), 16) } func TestRandElement(t *T) { @@ -35,7 +35,7 @@ func TestRandElement(t *T) { iterations := 100000 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]++ } diff --git a/mrpc/mrpc_test.go b/mrpc/mrpc_test.go index a39b778..7b86c7b 100644 --- a/mrpc/mrpc_test.go +++ b/mrpc/mrpc_test.go @@ -4,7 +4,7 @@ import ( "context" . "testing" - "github.com/mediocregopher/mediocre-go-lib/mtest" + "github.com/mediocregopher/mediocre-go-lib/mrand" "github.com/stretchr/testify/assert" ) @@ -28,14 +28,14 @@ func TestReflectClient(t *T) { })) { // test with arg being non-pointer - in := mtest.RandHex(8) + in := mrand.Hex(8) var res resT assert.NoError(t, client.CallRPC(ctx, &res, "foo", argT{In: in})) assert.Equal(t, in, res.Out) } { // test with arg being pointer - in := mtest.RandHex(8) + in := mrand.Hex(8) var res resT assert.NoError(t, client.CallRPC(ctx, &res, "foo", &argT{In: in})) assert.Equal(t, in, res.Out) @@ -51,14 +51,14 @@ func TestReflectClient(t *T) { })) { // test with arg being non-pointer - in := mtest.RandHex(8) + in := mrand.Hex(8) var res resT assert.NoError(t, client.CallRPC(ctx, &res, "foo", argT{In: in})) assert.Equal(t, in, res.Out) } { // test with arg being pointer - in := mtest.RandHex(8) + in := mrand.Hex(8) var res resT assert.NoError(t, client.CallRPC(ctx, &res, "foo", &argT{In: in})) assert.Equal(t, in, res.Out) diff --git a/mtest/mtest.go b/mtest/mtest.go deleted file mode 100644 index 03f39fa..0000000 --- a/mtest/mtest.go +++ /dev/null @@ -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?") -}