1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
|
package gopter
import (
"fmt"
"reflect"
)
// Shrink is a stream of shrunk down values.
// Once the result of a shrink is false, it is considered to be exhausted.
// Important notes for implementors:
// * Ensure that the returned stream is finite, even though shrinking will
// eventually be aborted, infinite streams may result in very slow running
// test.
// * Ensure that modifications to the returned value will not affect the
// internal state of your Shrink. If in doubt return by value not by reference
type Shrink func() (interface{}, bool)
// Filter creates a shrink filtered by a condition
func (s Shrink) Filter(condition func(interface{}) bool) Shrink {
if condition == nil {
return s
}
return func() (interface{}, bool) {
value, ok := s()
for ok && !condition(value) {
value, ok = s()
}
return value, ok
}
}
// Map creates a shrink by applying a converter to each element of a shrink.
// f: has to be a function with one parameter (matching the generated value) and a single return.
func (s Shrink) Map(f interface{}) Shrink {
mapperVal := reflect.ValueOf(f)
mapperType := mapperVal.Type()
if mapperVal.Kind() != reflect.Func {
panic(fmt.Sprintf("Param of Map has to be a func, but is %v", mapperType.Kind()))
}
if mapperType.NumIn() != 1 {
panic(fmt.Sprintf("Param of Map has to be a func with one param, but is %v", mapperType.NumIn()))
}
if mapperType.NumOut() != 1 {
panic(fmt.Sprintf("Param of Map has to be a func with one return value, but is %v", mapperType.NumOut()))
}
return func() (interface{}, bool) {
value, ok := s()
if ok {
return mapperVal.Call([]reflect.Value{reflect.ValueOf(value)})[0].Interface(), ok
}
return nil, false
}
}
// All collects all shrinks as a slice. Use with care as this might create
// large results depending on the complexity of the shrink
func (s Shrink) All() []interface{} {
result := []interface{}{}
value, ok := s()
for ok {
result = append(result, value)
value, ok = s()
}
return result
}
type concatedShrink struct {
index int
shrinks []Shrink
}
func (c *concatedShrink) Next() (interface{}, bool) {
for c.index < len(c.shrinks) {
value, ok := c.shrinks[c.index]()
if ok {
return value, ok
}
c.index++
}
return nil, false
}
// ConcatShrinks concats an array of shrinks to a single shrinks
func ConcatShrinks(shrinks ...Shrink) Shrink {
concated := &concatedShrink{
index: 0,
shrinks: shrinks,
}
return concated.Next
}
type interleaved struct {
first Shrink
second Shrink
firstExhausted bool
secondExhaused bool
state bool
}
func (i *interleaved) Next() (interface{}, bool) {
for !i.firstExhausted || !i.secondExhaused {
i.state = !i.state
if i.state && !i.firstExhausted {
value, ok := i.first()
if ok {
return value, true
}
i.firstExhausted = true
} else if !i.state && !i.secondExhaused {
value, ok := i.second()
if ok {
return value, true
}
i.secondExhaused = true
}
}
return nil, false
}
// Interleave this shrink with another
// Both shrinks are expected to produce the same result
func (s Shrink) Interleave(other Shrink) Shrink {
interleaved := &interleaved{
first: s,
second: other,
}
return interleaved.Next
}
// Shrinker creates a shrink for a given value
type Shrinker func(value interface{}) Shrink
type elementShrink struct {
original []interface{}
index int
elementShrink Shrink
}
func (e *elementShrink) Next() (interface{}, bool) {
element, ok := e.elementShrink()
if !ok {
return nil, false
}
shrunk := make([]interface{}, len(e.original))
copy(shrunk, e.original)
shrunk[e.index] = element
return shrunk, true
}
// CombineShrinker create a shrinker by combining a list of shrinkers.
// The resulting shrinker will shrink an []interface{} where each element will be shrunk by
// the corresonding shrinker in 'shrinkers'.
// This method is implicitly used by CombineGens.
func CombineShrinker(shrinkers ...Shrinker) Shrinker {
return func(v interface{}) Shrink {
values := v.([]interface{})
shrinks := make([]Shrink, 0, len(values))
for i, shrinker := range shrinkers {
if i >= len(values) {
break
}
shrink := &elementShrink{
original: values,
index: i,
elementShrink: shrinker(values[i]),
}
shrinks = append(shrinks, shrink.Next)
}
return ConcatShrinks(shrinks...)
}
}
// NoShrink is an empty shrink.
var NoShrink = Shrink(func() (interface{}, bool) {
return nil, false
})
// NoShrinker is a shrinker for NoShrink, i.e. a Shrinker that will not shrink any values.
// This is the default Shrinker if none is provided.
var NoShrinker = Shrinker(func(value interface{}) Shrink {
return NoShrink
})
|