From 8d35ce427aea30679f9e3ae0044c076f4fba326f Mon Sep 17 00:00:00 2001 From: Untoldwind Date: Fri, 24 Jun 2016 19:58:42 +0200 Subject: [PATCH 1/8] Rough layout for bijective generator derive (addresses #6) --- bi_mapper.go | 99 +++++++++++++++++++++++++++++++++++++++++++++++ bi_mapper_test.go | 27 +++++++++++++ derived_gen.go | 20 ++++++++++ 3 files changed, 146 insertions(+) create mode 100644 bi_mapper.go create mode 100644 bi_mapper_test.go create mode 100644 derived_gen.go diff --git a/bi_mapper.go b/bi_mapper.go new file mode 100644 index 0000000..326b79e --- /dev/null +++ b/bi_mapper.go @@ -0,0 +1,99 @@ +package gopter + +import ( + "fmt" + "reflect" +) + +// BiMapper is a bi-directional (or bijective) mapper of a tuple of values (up) +// to another tuple of values (down). +type BiMapper struct { + UpTypes []reflect.Type + DownTypes []reflect.Type + Downstream reflect.Value + Upstream reflect.Value +} + +// NewBiMapper creates a BiMapper of two functions `downstream` and its +// inverse `upstream`. +// That is: The return values of `downstream` must match the parameters of +// `upstream` and vice versa. +func NewBiMapper(downstream interface{}, upstream interface{}) *BiMapper { + downstreamVal := reflect.ValueOf(downstream) + if downstreamVal.Kind() != reflect.Func { + panic("downstream has to be a function") + } + upstreamVal := reflect.ValueOf(upstream) + if upstreamVal.Kind() != reflect.Func { + panic("upstream has to be a function") + } + + downstreamType := downstreamVal.Type() + upTypes := make([]reflect.Type, downstreamType.NumIn()) + for i := 0; i < len(upTypes); i++ { + upTypes[i] = downstreamType.In(i) + } + downTypes := make([]reflect.Type, downstreamType.NumOut()) + for i := 0; i < len(downTypes); i++ { + downTypes[i] = downstreamType.Out(i) + } + + upstreamType := upstreamVal.Type() + if len(upTypes) != upstreamType.NumOut() { + panic(fmt.Sprintf("upstream is expected to have %d return values", len(upTypes))) + } + for i, upType := range upTypes { + if upstreamType.Out(i) != upType { + panic(fmt.Sprintf("upstream has wrong return type %d: %v != %v", i, upstreamType.Out(i), upType)) + } + } + if len(downTypes) != upstreamType.NumIn() { + panic(fmt.Sprintf("upstream is expected to have %d parameters", len(downTypes))) + } + for i, downType := range downTypes { + if upstreamType.In(i) != downType { + panic(fmt.Sprintf("upstream has wrong parameter type %d: %v != %v", i, upstreamType.In(i), downType)) + } + } + + return &BiMapper{ + UpTypes: upTypes, + DownTypes: downTypes, + Downstream: downstreamVal, + Upstream: upstreamVal, + } +} + +func (b *BiMapper) ConvertUp(down []interface{}) []interface{} { + if len(down) != len(b.DownTypes) { + panic(fmt.Sprintf("Expected %d values != %d", len(b.DownTypes), len(down))) + } + downVals := make([]reflect.Value, len(b.DownTypes)) + for i, val := range down { + downVals[i] = reflect.ValueOf(val) + } + upVals := b.Upstream.Call(downVals) + up := make([]interface{}, len(upVals)) + for i, upVal := range upVals { + up[i] = upVal.Interface() + } + + return up +} + +func (b *BiMapper) ConvertDown(up []interface{}) []interface{} { + if len(up) != len(b.UpTypes) { + panic(fmt.Sprintf("Expected %d values != %d", len(b.UpTypes), len(up))) + } + upVals := make([]reflect.Value, len(b.UpTypes)) + for i, val := range up { + upVals[i] = reflect.ValueOf(val) + } + downVals := b.Downstream.Call(upVals) + down := make([]interface{}, len(upVals)) + for i, downVal := range downVals { + down[i] = downVal.Interface() + } + + return down +} diff --git a/bi_mapper_test.go b/bi_mapper_test.go new file mode 100644 index 0000000..5a0166c --- /dev/null +++ b/bi_mapper_test.go @@ -0,0 +1,27 @@ +package gopter_test + +import ( + "testing" + + "github.com/leanovate/gopter" +) + +func TestBiMapperParamNotMatch(t *testing.T) { + defer expectPanic(t, "upstream has wrong parameter type 0: string != int") + gopter.NewBiMapper(func(int) int { return 0 }, func(string) int { return 0 }) +} + +func TestBiMapperReturnNotMatch(t *testing.T) { + defer expectPanic(t, "upstream has wrong return type 0: string != int") + gopter.NewBiMapper(func(int) int { return 0 }, func(int) string { return "" }) +} + +func TestBiMapperInvalidDownstream(t *testing.T) { + defer expectPanic(t, "downstream has to be a function") + gopter.NewBiMapper(1, 2) +} + +func TestBiMapperInvalidUpstream(t *testing.T) { + defer expectPanic(t, "upstream has to be a function") + gopter.NewBiMapper(func(int) int { return 0 }, 2) +} diff --git a/derived_gen.go b/derived_gen.go new file mode 100644 index 0000000..a593fa7 --- /dev/null +++ b/derived_gen.go @@ -0,0 +1,20 @@ +package gopter + +type derivedGen struct { + biMapper *BiMapper + upGens []Gen +} + +func (d *derivedGen) Generate(params *GenParameters) *GenResult { + return nil +} + +// DeriveGen derives a generator with shrinkers from a sequence of other +// generators mapped by a bijective function (BiMapper) +func DeriveGen(biMapper *BiMapper, gens ...Gen) Gen { + derived := &derivedGen{ + biMapper: biMapper, + upGens: gens, + } + return derived.Generate +} From b31ca5abb061742fdeba82a4e14589b1dd5cfefa Mon Sep 17 00:00:00 2001 From: Untoldwind Date: Sat, 25 Jun 2016 10:31:07 +0200 Subject: [PATCH 2/8] Basic downstream conversion --- bi_mapper.go | 2 +- derived_gen.go | 89 +++++++++++++++++++++++++++++++++++++++++---- derived_gen_test.go | 37 +++++++++++++++++++ 3 files changed, 120 insertions(+), 8 deletions(-) create mode 100644 derived_gen_test.go diff --git a/bi_mapper.go b/bi_mapper.go index 326b79e..7494a4a 100644 --- a/bi_mapper.go +++ b/bi_mapper.go @@ -90,7 +90,7 @@ func (b *BiMapper) ConvertDown(up []interface{}) []interface{} { upVals[i] = reflect.ValueOf(val) } downVals := b.Downstream.Call(upVals) - down := make([]interface{}, len(upVals)) + down := make([]interface{}, len(downVals)) for i, downVal := range downVals { down[i] = downVal.Interface() } diff --git a/derived_gen.go b/derived_gen.go index a593fa7..015012e 100644 --- a/derived_gen.go +++ b/derived_gen.go @@ -1,20 +1,95 @@ package gopter +import ( + "fmt" + "reflect" +) + type derivedGen struct { - biMapper *BiMapper - upGens []Gen + biMapper *BiMapper + upGens []Gen + upSieves []func(interface{}) bool + upShrinkers []Shrinker + resultType reflect.Type +} + +func (d *derivedGen) Generate(genParams *GenParameters) *GenResult { + labels := []string{} + up := make([]interface{}, len(d.upGens)) + shrinkers := make([]Shrinker, len(d.upGens)) + sieves := make([]func(v interface{}) bool, len(d.upGens)) + + var ok bool + for i, gen := range d.upGens { + result := gen(genParams) + labels = append(labels, result.Labels...) + shrinkers[i] = result.Shrinker + sieves[i] = result.Sieve + up[i], ok = result.Retrieve() + if !ok { + return &GenResult{ + Shrinker: NoShrinker, + result: nil, + Labels: result.Labels, + ResultType: d.resultType, + } + } + } + down := d.biMapper.ConvertDown(up) + if len(down) == 1 { + return &GenResult{ + Shrinker: d.Shrinker, + result: down[0], + Labels: labels, + ResultType: reflect.TypeOf(down[0]), + Sieve: d.Sieve, + } + } + return &GenResult{ + Shrinker: d.Shrinker, + result: down, + Labels: labels, + ResultType: reflect.TypeOf(down), + Sieve: d.Sieve, + } +} + +func (d *derivedGen) Sieve(v interface{}) bool { + return true } -func (d *derivedGen) Generate(params *GenParameters) *GenResult { - return nil +func (d *derivedGen) Shrinker(v interface{}) Shrink { + return NoShrink } // DeriveGen derives a generator with shrinkers from a sequence of other // generators mapped by a bijective function (BiMapper) -func DeriveGen(biMapper *BiMapper, gens ...Gen) Gen { +func DeriveGen(downstream interface{}, upstream interface{}, gens ...Gen) Gen { + biMapper := NewBiMapper(downstream, upstream) + + if len(gens) != len(biMapper.UpTypes) { + panic(fmt.Sprintf("Expected %d generators != %d", len(biMapper.UpTypes), len(gens))) + } + + resultType := reflect.TypeOf([]interface{}{}) + if len(biMapper.DownTypes) == 1 { + resultType = biMapper.DownTypes[0] + } + + sieves := make([]func(interface{}) bool, len(gens)) + shrinkers := make([]Shrinker, len(gens)) + for i, gen := range gens { + result := gen(DefaultGenParameters()) + sieves[i] = result.Sieve + shrinkers[i] = result.Shrinker + } + derived := &derivedGen{ - biMapper: biMapper, - upGens: gens, + biMapper: biMapper, + upGens: gens, + upSieves: sieves, + upShrinkers: shrinkers, + resultType: resultType, } return derived.Generate } diff --git a/derived_gen_test.go b/derived_gen_test.go new file mode 100644 index 0000000..9e04892 --- /dev/null +++ b/derived_gen_test.go @@ -0,0 +1,37 @@ +package gopter_test + +import ( + "testing" + + "github.com/leanovate/gopter" + "github.com/leanovate/gopter/gen" +) + +type downStruct struct { + a int + b string + c bool +} + +func TestDeriveGenSingleDown(t *testing.T) { + gen := gopter.DeriveGen( + func(a int, b string, c bool) *downStruct { + return &downStruct{a: a, b: b, c: c} + }, + func(d *downStruct) (int, string, bool) { + return d.a, d.b, d.c + }, + gen.Int(), + gen.AnyString(), + gen.Bool(), + ) + + sample, ok := gen.Sample() + if !ok { + t.Error("Sample not ok") + } + _, ok = sample.(*downStruct) + if !ok { + t.Error("%#v is not a downStruct", sample) + } +} From e266d1eab3335f836f4274133031485d74d4ebec Mon Sep 17 00:00:00 2001 From: Untoldwind Date: Sat, 25 Jun 2016 10:38:02 +0200 Subject: [PATCH 3/8] Fix typo --- derived_gen_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/derived_gen_test.go b/derived_gen_test.go index 9e04892..a15904c 100644 --- a/derived_gen_test.go +++ b/derived_gen_test.go @@ -32,6 +32,6 @@ func TestDeriveGenSingleDown(t *testing.T) { } _, ok = sample.(*downStruct) if !ok { - t.Error("%#v is not a downStruct", sample) + t.Errorf("%#v is not a downStruct", sample) } } From 9141784aca444e6782c400a31120975bcba60cc7 Mon Sep 17 00:00:00 2001 From: Untoldwind Date: Sat, 9 Jul 2016 13:12:11 +0200 Subject: [PATCH 4/8] Add sieve mapping --- derived_gen.go | 40 ++++++++++++++++++++++++++++------------ derived_gen_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/derived_gen.go b/derived_gen.go index 015012e..80895e8 100644 --- a/derived_gen.go +++ b/derived_gen.go @@ -6,11 +6,11 @@ import ( ) type derivedGen struct { - biMapper *BiMapper - upGens []Gen - upSieves []func(interface{}) bool - upShrinkers []Shrinker - resultType reflect.Type + biMapper *BiMapper + upGens []Gen + upSieves []func(interface{}) bool + upShrinker Shrinker + resultType reflect.Type } func (d *derivedGen) Generate(genParams *GenParameters) *GenResult { @@ -32,6 +32,7 @@ func (d *derivedGen) Generate(genParams *GenParameters) *GenResult { result: nil, Labels: result.Labels, ResultType: d.resultType, + Sieve: d.Sieve, } } } @@ -54,11 +55,26 @@ func (d *derivedGen) Generate(genParams *GenParameters) *GenResult { } } -func (d *derivedGen) Sieve(v interface{}) bool { +func (d *derivedGen) Sieve(down interface{}) bool { + downs, ok := down.([]interface{}) + if !ok { + downs = []interface{}{down} + } + ups := d.biMapper.ConvertUp(downs) + for i, up := range ups { + if d.upSieves[i] != nil && !d.upSieves[i](up) { + return false + } + } return true } -func (d *derivedGen) Shrinker(v interface{}) Shrink { +func (d *derivedGen) Shrinker(down interface{}) Shrink { + downs, ok := down.([]interface{}) + if !ok { + downs = []interface{}{down} + } + d.biMapper.ConvertUp(downs) return NoShrink } @@ -85,11 +101,11 @@ func DeriveGen(downstream interface{}, upstream interface{}, gens ...Gen) Gen { } derived := &derivedGen{ - biMapper: biMapper, - upGens: gens, - upSieves: sieves, - upShrinkers: shrinkers, - resultType: resultType, + biMapper: biMapper, + upGens: gens, + upSieves: sieves, + upShrinker: CombineShrinker(shrinkers...), + resultType: resultType, } return derived.Generate } diff --git a/derived_gen_test.go b/derived_gen_test.go index a15904c..4d65559 100644 --- a/derived_gen_test.go +++ b/derived_gen_test.go @@ -35,3 +35,29 @@ func TestDeriveGenSingleDown(t *testing.T) { t.Errorf("%#v is not a downStruct", sample) } } + +func TestDeriveGenSingleDownWithSieves(t *testing.T) { + gen := gopter.DeriveGen( + func(a int, b string, c bool) *downStruct { + return &downStruct{a: a, b: b, c: c} + }, + func(d *downStruct) (int, string, bool) { + return d.a, d.b, d.c + }, + gen.Int().SuchThat(func(i int) bool { + return i%2 == 0 + }), + gen.AnyString(), + gen.Bool(), + ) + + sieve := gen(gopter.DefaultGenParameters()).Sieve + + if !sieve(&downStruct{a: 2, b: "something", c: false}) { + t.Error("Sieve did not pass even") + } + + if sieve(&downStruct{a: 3, b: "something", c: false}) { + t.Error("Sieve did pass odd") + } +} From abe5dacca21f66b45f8003867d29a0013fb65f15 Mon Sep 17 00:00:00 2001 From: Untoldwind Date: Sat, 9 Jul 2016 22:40:54 +0200 Subject: [PATCH 5/8] Map shrinkers --- derived_gen.go | 14 +++++++++++--- derived_gen_test.go | 14 ++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/derived_gen.go b/derived_gen.go index 80895e8..a333946 100644 --- a/derived_gen.go +++ b/derived_gen.go @@ -28,7 +28,7 @@ func (d *derivedGen) Generate(genParams *GenParameters) *GenResult { up[i], ok = result.Retrieve() if !ok { return &GenResult{ - Shrinker: NoShrinker, + Shrinker: d.Shrinker, result: nil, Labels: result.Labels, ResultType: d.resultType, @@ -74,8 +74,16 @@ func (d *derivedGen) Shrinker(down interface{}) Shrink { if !ok { downs = []interface{}{down} } - d.biMapper.ConvertUp(downs) - return NoShrink + ups := d.biMapper.ConvertUp(downs) + upShrink := d.upShrinker(ups) + + return upShrink.Map(func(shrinkedUps []interface{}) interface{} { + downs := d.biMapper.ConvertDown(shrinkedUps) + if len(downs) == 1 { + return downs[0] + } + return downs + }) } // DeriveGen derives a generator with shrinkers from a sequence of other diff --git a/derived_gen_test.go b/derived_gen_test.go index 4d65559..2d28716 100644 --- a/derived_gen_test.go +++ b/derived_gen_test.go @@ -34,6 +34,20 @@ func TestDeriveGenSingleDown(t *testing.T) { if !ok { t.Errorf("%#v is not a downStruct", sample) } + + shrinker := gen(gopter.DefaultGenParameters()).Shrinker + shrink := shrinker(&downStruct{a: 10, b: "abcd", c: false}) + + shrinkedStructs := make([]*downStruct, 0) + value, next := shrink() + for next { + shrinkedStruct, ok := value.(*downStruct) + if !ok { + t.Errorf("Invalid shrinked value: %#v", value) + } + shrinkedStructs = append(shrinkedStructs, shrinkedStruct) + value, next = shrink() + } } func TestDeriveGenSingleDownWithSieves(t *testing.T) { From 70de2e77fbd5a89d53b4d168016dbe7721a43d39 Mon Sep 17 00:00:00 2001 From: Untoldwind Date: Sat, 9 Jul 2016 23:19:57 +0200 Subject: [PATCH 6/8] Remove sieve for empty result --- derived_gen.go | 1 - derived_gen_test.go | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/derived_gen.go b/derived_gen.go index a333946..3160393 100644 --- a/derived_gen.go +++ b/derived_gen.go @@ -32,7 +32,6 @@ func (d *derivedGen) Generate(genParams *GenParameters) *GenResult { result: nil, Labels: result.Labels, ResultType: d.resultType, - Sieve: d.Sieve, } } } diff --git a/derived_gen_test.go b/derived_gen_test.go index 2d28716..d4b3f73 100644 --- a/derived_gen_test.go +++ b/derived_gen_test.go @@ -1,6 +1,7 @@ package gopter_test import ( + "reflect" "testing" "github.com/leanovate/gopter" @@ -48,6 +49,25 @@ func TestDeriveGenSingleDown(t *testing.T) { shrinkedStructs = append(shrinkedStructs, shrinkedStruct) value, next = shrink() } + + expected := []*downStruct{ + &downStruct{a: 0, b: "abcd", c: false}, + &downStruct{a: 5, b: "abcd", c: false}, + &downStruct{a: -5, b: "abcd", c: false}, + &downStruct{a: 8, b: "abcd", c: false}, + &downStruct{a: -8, b: "abcd", c: false}, + &downStruct{a: 9, b: "abcd", c: false}, + &downStruct{a: -9, b: "abcd", c: false}, + &downStruct{a: 10, b: "cd", c: false}, + &downStruct{a: 10, b: "ab", c: false}, + &downStruct{a: 10, b: "bcd", c: false}, + &downStruct{a: 10, b: "acd", c: false}, + &downStruct{a: 10, b: "abd", c: false}, + &downStruct{a: 10, b: "abc", c: false}, + } + if !reflect.DeepEqual(shrinkedStructs, expected) { + t.Errorf("%v does not equal %v", shrinkedStructs, expected) + } } func TestDeriveGenSingleDownWithSieves(t *testing.T) { @@ -65,7 +85,23 @@ func TestDeriveGenSingleDownWithSieves(t *testing.T) { gen.Bool(), ) - sieve := gen(gopter.DefaultGenParameters()).Sieve + parameters := gopter.DefaultGenParameters() + parameters.Rng.Seed(1234) + + hasNoValue := false + var sieve func(interface{}) bool + for i := 0; i < 100; i++ { + result := gen(parameters) + _, ok := result.Retrieve() + if !ok { + hasNoValue = true + } else { + sieve = result.Sieve + } + } + if !hasNoValue || sieve == nil { + t.Error("Sieve is not applied") + } if !sieve(&downStruct{a: 2, b: "something", c: false}) { t.Error("Sieve did not pass even") From 67ede4309cf4d75cfe5c9aed59115ce0e609be81 Mon Sep 17 00:00:00 2001 From: Untoldwind Date: Sat, 9 Jul 2016 23:26:55 +0200 Subject: [PATCH 7/8] Make it nil-save instead --- bi_mapper.go | 12 ++++++++++-- derived_gen.go | 4 ++++ derived_gen_test.go | 8 ++++---- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/bi_mapper.go b/bi_mapper.go index 7494a4a..cb627c9 100644 --- a/bi_mapper.go +++ b/bi_mapper.go @@ -70,7 +70,11 @@ func (b *BiMapper) ConvertUp(down []interface{}) []interface{} { } downVals := make([]reflect.Value, len(b.DownTypes)) for i, val := range down { - downVals[i] = reflect.ValueOf(val) + if val == nil { + downVals[i] = reflect.Zero(b.DownTypes[i]) + } else { + downVals[i] = reflect.ValueOf(val) + } } upVals := b.Upstream.Call(downVals) up := make([]interface{}, len(upVals)) @@ -87,7 +91,11 @@ func (b *BiMapper) ConvertDown(up []interface{}) []interface{} { } upVals := make([]reflect.Value, len(b.UpTypes)) for i, val := range up { - upVals[i] = reflect.ValueOf(val) + if val == nil { + upVals[i] = reflect.Zero(b.UpTypes[i]) + } else { + upVals[i] = reflect.ValueOf(val) + } } downVals := b.Downstream.Call(upVals) down := make([]interface{}, len(downVals)) diff --git a/derived_gen.go b/derived_gen.go index 3160393..2c44798 100644 --- a/derived_gen.go +++ b/derived_gen.go @@ -32,6 +32,7 @@ func (d *derivedGen) Generate(genParams *GenParameters) *GenResult { result: nil, Labels: result.Labels, ResultType: d.resultType, + Sieve: d.Sieve, } } } @@ -55,6 +56,9 @@ func (d *derivedGen) Generate(genParams *GenParameters) *GenResult { } func (d *derivedGen) Sieve(down interface{}) bool { + if down == nil { + return false + } downs, ok := down.([]interface{}) if !ok { downs = []interface{}{down} diff --git a/derived_gen_test.go b/derived_gen_test.go index d4b3f73..a0ed571 100644 --- a/derived_gen_test.go +++ b/derived_gen_test.go @@ -89,20 +89,20 @@ func TestDeriveGenSingleDownWithSieves(t *testing.T) { parameters.Rng.Seed(1234) hasNoValue := false - var sieve func(interface{}) bool for i := 0; i < 100; i++ { result := gen(parameters) _, ok := result.Retrieve() if !ok { hasNoValue = true - } else { - sieve = result.Sieve + break } } - if !hasNoValue || sieve == nil { + if !hasNoValue { t.Error("Sieve is not applied") } + sieve := gen(parameters).Sieve + if !sieve(&downStruct{a: 2, b: "something", c: false}) { t.Error("Sieve did not pass even") } From 82f8e70efaa689ad519a8233bd5a1f5bc3cfa894 Mon Sep 17 00:00:00 2001 From: Untoldwind Date: Sat, 9 Jul 2016 23:51:58 +0200 Subject: [PATCH 8/8] Test multi-value bi-mapping --- CHANGELOG.md | 2 ++ derived_gen_test.go | 73 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index efe29a9..40e7cc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ - `gopter.GenParameters` now has a `CloneWithSeed(seed int64)` function to temparary copies to create rerunable sections of code. - Added `gopter.Gen.MapResult` for power-user mappings +- Added `gopter.DeriveGen` to derive a generator and it's shrinker from a + bi-directional mapping (`gopter.BiMapper`) ### Changed - Refactored `commands` package under the hood to allow the use of mutable state. diff --git a/derived_gen_test.go b/derived_gen_test.go index a0ed571..af7f89b 100644 --- a/derived_gen_test.go +++ b/derived_gen_test.go @@ -111,3 +111,76 @@ func TestDeriveGenSingleDownWithSieves(t *testing.T) { t.Error("Sieve did pass odd") } } + +func TestDeriveGenMultiDown(t *testing.T) { + gen := gopter.DeriveGen( + func(a int, b string, c bool, d int32) (*downStruct, int64) { + return &downStruct{a: a, b: b, c: c}, int64(a) + int64(d) + }, + func(d *downStruct, diff int64) (int, string, bool, int32) { + return d.a, d.b, d.c, int32(diff - int64(d.a)) + }, + gen.Int(), + gen.AnyString(), + gen.Bool(), + gen.Int32(), + ) + + sample, ok := gen.Sample() + if !ok { + t.Error("Sample not ok") + } + values, ok := sample.([]interface{}) + if !ok || len(values) != 2 { + t.Errorf("%#v is not a slice of interface", sample) + } + _, ok = values[0].(*downStruct) + if !ok { + t.Errorf("%#v is not a downStruct", values[0]) + } + _, ok = values[1].(int64) + if !ok { + t.Errorf("%#v is not a int64", values[1]) + } + + shrinker := gen(gopter.DefaultGenParameters()).Shrinker + shrink := shrinker([]interface{}{&downStruct{a: 10, b: "abcd", c: false}, int64(20)}) + + value, next := shrink() + shrinkedValues := make([][]interface{}, 0) + for next { + shrinked, ok := value.([]interface{}) + if !ok || len(values) != 2 { + t.Errorf("%#v is not a slice of interface", sample) + } + shrinkedValues = append(shrinkedValues, shrinked) + value, next = shrink() + } + + expected := [][]interface{}{ + []interface{}{&downStruct{a: 0, b: "abcd", c: false}, int64(10)}, + []interface{}{&downStruct{a: 5, b: "abcd", c: false}, int64(15)}, + []interface{}{&downStruct{a: -5, b: "abcd", c: false}, int64(5)}, + []interface{}{&downStruct{a: 8, b: "abcd", c: false}, int64(18)}, + []interface{}{&downStruct{a: -8, b: "abcd", c: false}, int64(2)}, + []interface{}{&downStruct{a: 9, b: "abcd", c: false}, int64(19)}, + []interface{}{&downStruct{a: -9, b: "abcd", c: false}, int64(1)}, + []interface{}{&downStruct{a: 10, b: "cd", c: false}, int64(20)}, + []interface{}{&downStruct{a: 10, b: "ab", c: false}, int64(20)}, + []interface{}{&downStruct{a: 10, b: "bcd", c: false}, int64(20)}, + []interface{}{&downStruct{a: 10, b: "acd", c: false}, int64(20)}, + []interface{}{&downStruct{a: 10, b: "abd", c: false}, int64(20)}, + []interface{}{&downStruct{a: 10, b: "abc", c: false}, int64(20)}, + []interface{}{&downStruct{a: 10, b: "abcd", c: false}, int64(10)}, + []interface{}{&downStruct{a: 10, b: "abcd", c: false}, int64(15)}, + []interface{}{&downStruct{a: 10, b: "abcd", c: false}, int64(5)}, + []interface{}{&downStruct{a: 10, b: "abcd", c: false}, int64(18)}, + []interface{}{&downStruct{a: 10, b: "abcd", c: false}, int64(2)}, + []interface{}{&downStruct{a: 10, b: "abcd", c: false}, int64(19)}, + []interface{}{&downStruct{a: 10, b: "abcd", c: false}, int64(1)}, + } + + if !reflect.DeepEqual(shrinkedValues, expected) { + t.Errorf("%v does not equal %v", shrinkedValues, expected) + } +}