Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Derive by bimap #7

Merged
merged 8 commits into from
Jul 9, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
107 changes: 107 additions & 0 deletions bi_mapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
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 {
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))
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 {
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))
for i, downVal := range downVals {
down[i] = downVal.Interface()
}

return down
}
27 changes: 27 additions & 0 deletions bi_mapper_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
122 changes: 122 additions & 0 deletions derived_gen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package gopter

import (
"fmt"
"reflect"
)

type derivedGen struct {
biMapper *BiMapper
upGens []Gen
upSieves []func(interface{}) bool
upShrinker 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: d.Shrinker,
result: nil,
Labels: result.Labels,
ResultType: d.resultType,
Sieve: d.Sieve,
}
}
}
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(down interface{}) bool {
if down == nil {
return false
}
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(down interface{}) Shrink {
downs, ok := down.([]interface{})
if !ok {
downs = []interface{}{down}
}
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
// generators mapped by a bijective function (BiMapper)
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,
upSieves: sieves,
upShrinker: CombineShrinker(shrinkers...),
resultType: resultType,
}
return derived.Generate
}
Loading