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