From ba50aff940dac63fa94c7992188a5c989ae94131 Mon Sep 17 00:00:00 2001 From: Adam Luzsi Date: Tue, 16 Jan 2024 01:48:27 +0100 Subject: [PATCH] add `random.Pick` to simplify the selection of random element from an enum list --- random/Factory.go | 3 ++ random/Factory_test.go | 101 +++++++++++++++++++++++------------------ random/Random.go | 9 +--- random/pick.go | 8 ++++ random/pick_test.go | 78 +++++++++++++++++++++++++++++++ 5 files changed, 149 insertions(+), 50 deletions(-) create mode 100644 random/pick.go create mode 100644 random/pick_test.go diff --git a/random/Factory.go b/random/Factory.go index 3965344..07014f1 100644 --- a/random/Factory.go +++ b/random/Factory.go @@ -24,6 +24,9 @@ type ( ) func (f *Factory) Make(rnd *Random, T any) (_T any) { + if rnd == nil { + rnd = defaultRandom + } if T == nil { return nil } diff --git a/random/Factory_test.go b/random/Factory_test.go index 9fec15c..83cb04b 100644 --- a/random/Factory_test.go +++ b/random/Factory_test.go @@ -23,7 +23,7 @@ func TestFactory(t *testing.T) { s.Describe(`.Make`, func(s *testcase.Spec) { T := testcase.Var[any]{ID: ``} - subject := func(t *testcase.T) interface{} { + act := func(t *testcase.T) interface{} { return factory.Get(t).Make(rnd.Get(t), T.Get(t)) } @@ -36,7 +36,7 @@ func TestFactory(t *testing.T) { retry.Assert(t, func(it assert.It) { var values []interface{} for i := 0; i < 12; i++ { - v := subject(t) + v := act(t) it.Must.NotContain(values, v) values = append(values, v) } @@ -58,7 +58,7 @@ func TestFactory(t *testing.T) { retry.Assert(t, func(it assert.It) { var values []interface{} for i := 0; i < 12; i++ { - ptr := subject(t) + ptr := act(t) v := reflect.ValueOf(ptr).Elem().Interface() it.Must.NotContain(values, v) values = append(values, v) @@ -70,7 +70,7 @@ func TestFactory(t *testing.T) { hasValue := func(t *testcase.T, blk func(v interface{}) bool) { retry.Assert(t, func(it assert.It) { - it.Must.True(blk(subject(t))) + it.Must.True(blk(act(t))) }) } @@ -78,7 +78,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, int(0)) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(int) + _ = act(t).(int) }) s.Then(`non zero value generated`, func(t *testcase.T) { @@ -95,7 +95,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, TYPE(0)) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(TYPE) + _ = act(t).(TYPE) }) s.Then(`non zero value generated`, func(t *testcase.T) { @@ -113,7 +113,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, int8(0)) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(int8) + _ = act(t).(int8) }) s.Then(`non zero value generated`, func(t *testcase.T) { @@ -130,7 +130,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, TYPE(0)) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(TYPE) + _ = act(t).(TYPE) }) s.Then(`non zero value generated`, func(t *testcase.T) { @@ -148,7 +148,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, int16(0)) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(int16) + _ = act(t).(int16) }) s.Then(`non zero value generated`, func(t *testcase.T) { @@ -165,7 +165,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, TYPE(0)) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(TYPE) + _ = act(t).(TYPE) }) s.Then(`non zero value generated`, func(t *testcase.T) { @@ -183,7 +183,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, int32(0)) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(int32) + _ = act(t).(int32) }) s.Then(`non zero value generated`, func(t *testcase.T) { @@ -200,7 +200,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, TYPE(0)) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(TYPE) + _ = act(t).(TYPE) }) s.Then(`non zero value generated`, func(t *testcase.T) { @@ -218,7 +218,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, int64(0)) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(int64) + _ = act(t).(int64) }) s.Then(`non zero value generated`, func(t *testcase.T) { @@ -235,7 +235,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, TYPE(0)) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(TYPE) + _ = act(t).(TYPE) }) s.Then(`non zero value generated`, func(t *testcase.T) { @@ -253,7 +253,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, uint(0)) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(uint) + _ = act(t).(uint) }) s.Then(`non zero value generated`, func(t *testcase.T) { @@ -270,7 +270,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, TYPE(0)) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(TYPE) + _ = act(t).(TYPE) }) s.Then(`non zero value generated`, func(t *testcase.T) { @@ -288,7 +288,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, uint8(0)) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(uint8) + _ = act(t).(uint8) }) s.Then(`non zero value generated`, func(t *testcase.T) { @@ -305,7 +305,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, TYPE(0)) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(TYPE) + _ = act(t).(TYPE) }) s.Then(`non zero value generated`, func(t *testcase.T) { @@ -323,7 +323,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, uint16(0)) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(uint16) + _ = act(t).(uint16) }) s.Then(`non zero value generated`, func(t *testcase.T) { @@ -340,7 +340,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, TYPE(0)) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(TYPE) + _ = act(t).(TYPE) }) s.Then(`non zero value generated`, func(t *testcase.T) { @@ -358,7 +358,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, uint32(0)) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(uint32) + _ = act(t).(uint32) }) s.Then(`non zero value generated`, func(t *testcase.T) { @@ -375,7 +375,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, TYPE(0)) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(TYPE) + _ = act(t).(TYPE) }) s.Then(`non zero value generated`, func(t *testcase.T) { @@ -393,7 +393,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, uint64(0)) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(uint64) + _ = act(t).(uint64) }) s.Then(`non zero value generated`, func(t *testcase.T) { @@ -410,7 +410,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, TYPE(0)) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(TYPE) + _ = act(t).(TYPE) }) s.Then(`non zero value generated`, func(t *testcase.T) { @@ -428,7 +428,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, float32(0)) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(float32) + _ = act(t).(float32) }) s.Then(`non zero value generated`, func(t *testcase.T) { @@ -445,7 +445,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, TYPE(0.0)) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(TYPE) + _ = act(t).(TYPE) }) s.Then(`non zero value generated`, func(t *testcase.T) { @@ -463,7 +463,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, float64(0)) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(float64) + _ = act(t).(float64) }) s.Then(`non zero value generated`, func(t *testcase.T) { @@ -480,7 +480,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, TYPE(0.0)) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(TYPE) + _ = act(t).(TYPE) }) s.Then(`non zero value generated`, func(t *testcase.T) { @@ -500,7 +500,7 @@ func TestFactory(t *testing.T) { }) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(time.Time) + _ = act(t).(time.Time) }) s.Then(`non zero value generated`, func(t *testcase.T) { @@ -517,7 +517,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, time.Duration(0)) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(time.Duration) + _ = act(t).(time.Duration) }) s.Then(`non zero value generated`, func(t *testcase.T) { @@ -534,7 +534,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, false) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(bool) // assert it's bool + _ = act(t).(bool) // assert it's bool }) s.Then(`not just false (zero) value is returned`, func(t *testcase.T) { @@ -546,7 +546,7 @@ func TestFactory(t *testing.T) { s.Then(`it generates both true and false randomly`, func(t *testcase.T) { res := make(map[bool]struct{}) for i := 0; i < 128; i++ { - v := subject(t).(bool) + v := act(t).(bool) res[v] = struct{}{} } @@ -562,7 +562,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, TYPE(false)) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(TYPE) // assert it's bool + _ = act(t).(TYPE) // assert it's bool }) s.Then(`not just false (zero) value is returned`, func(t *testcase.T) { @@ -577,7 +577,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, "") s.Then(`value type is correct`, func(t *testcase.T) { - v := subject(t).(string) + v := act(t).(string) t.Must.True(0 < len(v)) }) @@ -589,7 +589,7 @@ func TestFactory(t *testing.T) { T.LetValue(s, TYPE("")) s.Then(`value type is correct`, func(t *testcase.T) { - v := subject(t).(TYPE) + v := act(t).(TYPE) t.Must.True(0 < len(v)) }) @@ -609,14 +609,14 @@ func TestFactory(t *testing.T) { }) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(Y) + _ = act(t).(Y) }) s.Then(`each field receive generated value`, func(t *testcase.T) { var hasFoo, hasBar, hasBaz bool for i := 0; i < 128; i++ { - y := subject(t).(Y) + y := act(t).(Y) if y.Foo != 0 { hasFoo = true @@ -645,7 +645,7 @@ func TestFactory(t *testing.T) { }) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(TYPE) + _ = act(t).(TYPE) }) s.Then(`any field has zero zero value generated`, func(t *testcase.T) { @@ -662,7 +662,7 @@ func TestFactory(t *testing.T) { }) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).(map[string]int) + _ = act(t).(map[string]int) }) s.Then(`it will create populated map`, func(t *testcase.T) { @@ -696,7 +696,7 @@ func TestFactory(t *testing.T) { }) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).([]string) + _ = act(t).([]string) }) s.Then(`it will create populated map`, func(t *testcase.T) { @@ -715,7 +715,7 @@ func TestFactory(t *testing.T) { }) s.Then(`value type is correct`, func(t *testcase.T) { - _ = subject(t).([13]string) + _ = act(t).([13]string) }) s.Then(`it will create populated map`, func(t *testcase.T) { @@ -739,7 +739,7 @@ func TestFactory(t *testing.T) { }) s.Then(`a not nil channel is created`, func(t *testcase.T) { - assert.Must(t).NotNil(subject(t).(chan int)) + assert.Must(t).NotNil(act(t).(chan int)) }) }) @@ -749,7 +749,22 @@ func TestFactory(t *testing.T) { }) s.Then(`it will return a nil`, func(t *testcase.T) { - assert.Nil(t, subject(t)) + assert.Nil(t, act(t)) + }) + }) + + s.When(`rnd is nil`, func(s *testcase.Spec) { + rnd.LetValue(s, nil) + T.LetValue(s, int(42)) + + s.Then("default random will be used to make a random", func(t *testcase.T) { + var got = make(map[int]struct{}) + + t.Eventually(func(it assert.It) { + got[act(t).(int)] = struct{}{} + + it.Must.True(len(got) > 1) + }) }) }) }) diff --git a/random/Random.go b/random/Random.go index 7298324..5165f6b 100644 --- a/random/Random.go +++ b/random/Random.go @@ -13,6 +13,8 @@ import ( "go.llib.dev/testcase/internal" ) +var defaultRandom = New(CryptoSeed{}) + func New(s rand.Source) *Random { return &Random{Source: s} } @@ -69,13 +71,6 @@ func (r *Random) IntB(min, max int) int { return r.IntBetween(min, max) } -// ElementFromSlice -// -// DEPRECATED: please use SliceElement instead -func (r *Random) ElementFromSlice(slice interface{}) interface{} { - return r.SliceElement(slice) -} - // SliceElement will return a random slice element. // You need type assert the returned value to get back the original type. func (r *Random) SliceElement(slice interface{}) interface{} { diff --git a/random/pick.go b/random/pick.go new file mode 100644 index 0000000..b8e2a5d --- /dev/null +++ b/random/pick.go @@ -0,0 +1,8 @@ +package random + +func Pick[T any](rnd *Random, vs ...T) T { + if rnd == nil { + rnd = defaultRandom + } + return rnd.SliceElement(vs).(T) +} diff --git a/random/pick_test.go b/random/pick_test.go new file mode 100644 index 0000000..fc2a078 --- /dev/null +++ b/random/pick_test.go @@ -0,0 +1,78 @@ +package random_test + +import ( + "go.llib.dev/testcase" + "go.llib.dev/testcase/assert" + "go.llib.dev/testcase/let" + "go.llib.dev/testcase/random" + "math/rand" + "testing" +) + +func ExamplePick_randomValuePicking() { + // Pick randomly from the values of 1,2,3 + var _ = random.Pick(nil, 1, 2, 3) +} + +func ExamplePick_pseudoRandomValuePicking() { + // Pick pseudo randomly from the given values using the seed. + // This will make picking deterministically random when the same seed is used. + const seed = 42 + rnd := random.New(rand.NewSource(seed)) + var _ = random.Pick(rnd, "one", "two", "three") +} + +func TestPick(t *testing.T) { + s := testcase.NewSpec(t) + + var ( + rnd = testcase.Let[*random.Random](s, nil) + vs = testcase.Let(s, func(t *testcase.T) []int { + return random.Slice(t.Random.IntB(3, 5), t.Random.Int) + }) + ) + act := func(t *testcase.T) int { + return random.Pick[int](rnd.Get(t), vs.Get(t)...) + } + + thenItWillStillSelectARandomValue := func(s *testcase.Spec) { + s.Then("it will still select a random value", func(t *testcase.T) { + var exp = make(map[int]struct{}) + for _, k := range vs.Get(t) { + exp[k] = struct{}{} + } + + var got = make(map[int]struct{}) + t.Eventually(func(it assert.It) { + got[act(t)] = struct{}{} + + it.Must.ContainExactly(exp, got) + }) + }) + } + + s.When("random.Random is nil", func(s *testcase.Spec) { + rnd.LetValue(s, nil) + + thenItWillStillSelectARandomValue(s) + }) + + s.When("random.Random is supplied", func(s *testcase.Spec) { + seed := let.IntB(s, 0, 42) + mkSource := func(t *testcase.T) rand.Source { + return rand.NewSource(int64(seed.Get(t))) + } + rnd.Let(s, func(t *testcase.T) *random.Random { + return random.New(mkSource(t)) + }) + + thenItWillStillSelectARandomValue(s) + + s.Then("random pick is determinstic through controlling the seed", func(t *testcase.T) { + exp := act(t) + rnd.Get(t).Source = mkSource(t) + got := act(t) + t.Must.Equal(exp, got) + }) + }) +}