github.com/leanovate/gopter@v0.2.9/gen.go (about) 1 package gopter 2 3 import ( 4 "fmt" 5 "reflect" 6 ) 7 8 // Gen generator of arbitrary values. 9 // Usually properties are checked by verifing a condition holds true for 10 // arbitrary input parameters generated by a Gen. 11 // 12 // IMPORTANT: Even though a generator is supposed to generate random values, it 13 // should do this in a reproducible way. Therefore a generator has to create the 14 // same result for the same GenParameters, i.e. ensure that you just use the 15 // RNG provided by GenParameters and no external one. 16 // If you just plug generators together you do not have to worry about this. 17 type Gen func(*GenParameters) *GenResult 18 19 var ( 20 // DefaultGenParams can be used as default für *GenParameters 21 DefaultGenParams = DefaultGenParameters() 22 MinGenParams = MinGenParameters() 23 ) 24 25 // Sample generate a sample value. 26 // Depending on the state of the RNG the generate might fail to provide a sample 27 func (g Gen) Sample() (interface{}, bool) { 28 return g(DefaultGenParameters()).Retrieve() 29 } 30 31 // WithLabel adds a label to a generated value. 32 // Labels are usually used for reporting for the arguments of a property check. 33 func (g Gen) WithLabel(label string) Gen { 34 return func(genParams *GenParameters) *GenResult { 35 result := g(genParams) 36 result.Labels = append(result.Labels, label) 37 return result 38 } 39 } 40 41 // SuchThat creates a derived generator by adding a sieve. 42 // f: has to be a function with one parameter (matching the generated value) returning a bool. 43 // All generated values are expected to satisfy 44 // f(value) == true. 45 // Use this care, if the sieve to to fine the generator will have many misses which results 46 // in an undecided property. 47 func (g Gen) SuchThat(f interface{}) Gen { 48 checkVal := reflect.ValueOf(f) 49 checkType := checkVal.Type() 50 51 if checkVal.Kind() != reflect.Func { 52 panic(fmt.Sprintf("Param of SuchThat has to be a func, but is %v", checkType.Kind())) 53 } 54 if checkType.NumIn() != 1 { 55 panic(fmt.Sprintf("Param of SuchThat has to be a func with one param, but is %v", checkType.NumIn())) 56 } else { 57 genResultType := g(MinGenParams).ResultType 58 if !genResultType.AssignableTo(checkType.In(0)) { 59 panic(fmt.Sprintf("Param of SuchThat has to be a func with one param assignable to %v, but is %v", genResultType, checkType.In(0))) 60 } 61 } 62 if checkType.NumOut() != 1 { 63 panic(fmt.Sprintf("Param of SuchThat has to be a func with one return value, but is %v", checkType.NumOut())) 64 } else if checkType.Out(0).Kind() != reflect.Bool { 65 panic(fmt.Sprintf("Param of SuchThat has to be a func with one return value of bool, but is %v", checkType.Out(0).Kind())) 66 } 67 sieve := func(v interface{}) bool { 68 valueOf := reflect.ValueOf(v) 69 if !valueOf.IsValid() { 70 return false 71 } 72 return checkVal.Call([]reflect.Value{valueOf})[0].Bool() 73 } 74 75 return func(genParams *GenParameters) *GenResult { 76 result := g(genParams) 77 prevSieve := result.Sieve 78 if prevSieve == nil { 79 result.Sieve = sieve 80 } else { 81 result.Sieve = func(value interface{}) bool { 82 return prevSieve(value) && sieve(value) 83 } 84 } 85 return result 86 } 87 } 88 89 // WithShrinker creates a derived generator with a specific shrinker 90 func (g Gen) WithShrinker(shrinker Shrinker) Gen { 91 return func(genParams *GenParameters) *GenResult { 92 result := g(genParams) 93 if shrinker == nil { 94 result.Shrinker = NoShrinker 95 } else { 96 result.Shrinker = shrinker 97 } 98 return result 99 } 100 } 101 102 // Map creates a derived generator by mapping all generatored values with a given function. 103 // f: has to be a function with one parameter (matching the generated value) and a single return. 104 // Note: The derived generator will not have a sieve or shrinker unless you are mapping to the same type 105 // Note: The mapping function may have a second parameter "*GenParameters" 106 // Note: The first parameter of the mapping function and its return may be a *GenResult (this makes MapResult obsolete) 107 func (g Gen) Map(f interface{}) Gen { 108 mapperVal := reflect.ValueOf(f) 109 mapperType := mapperVal.Type() 110 needsGenParameters := false 111 genResultInput := false 112 genResultOutput := false 113 114 if mapperVal.Kind() != reflect.Func { 115 panic(fmt.Sprintf("Param of Map has to be a func, but is %v", mapperType.Kind())) 116 } 117 if mapperType.NumIn() != 1 && mapperType.NumIn() != 2 { 118 panic(fmt.Sprintf("Param of Map has to be a func with one or two params, but is %v", mapperType.NumIn())) 119 } else { 120 if mapperType.NumIn() == 2 { 121 if !reflect.TypeOf(&GenParameters{}).AssignableTo(mapperType.In(1)) { 122 panic("Second parameter of mapper function has to be a *GenParameters") 123 } 124 needsGenParameters = true 125 } 126 genResultType := g(MinGenParams).ResultType 127 if reflect.TypeOf(&GenResult{}).AssignableTo(mapperType.In(0)) { 128 genResultInput = true 129 } else if !genResultType.AssignableTo(mapperType.In(0)) { 130 panic(fmt.Sprintf("Param of Map has to be a func with one param assignable to %v, but is %v", genResultType, mapperType.In(0))) 131 } 132 } 133 if mapperType.NumOut() != 1 { 134 panic(fmt.Sprintf("Param of Map has to be a func with one return value, but is %v", mapperType.NumOut())) 135 } else if reflect.TypeOf(&GenResult{}).AssignableTo(mapperType.Out(0)) { 136 genResultOutput = true 137 } 138 139 return func(genParams *GenParameters) *GenResult { 140 result := g(genParams) 141 if genResultInput { 142 var mapped reflect.Value 143 if needsGenParameters { 144 mapped = mapperVal.Call([]reflect.Value{reflect.ValueOf(result), reflect.ValueOf(genParams)})[0] 145 } else { 146 mapped = mapperVal.Call([]reflect.Value{reflect.ValueOf(result)})[0] 147 } 148 if genResultOutput { 149 return mapped.Interface().(*GenResult) 150 } 151 return &GenResult{ 152 Shrinker: NoShrinker, 153 Result: mapped.Interface(), 154 Labels: result.Labels, 155 ResultType: mapperType.Out(0), 156 } 157 } 158 value, ok := result.RetrieveAsValue() 159 if ok { 160 var mapped reflect.Value 161 shrinker := NoShrinker 162 if needsGenParameters { 163 mapped = mapperVal.Call([]reflect.Value{value, reflect.ValueOf(genParams)})[0] 164 } else { 165 mapped = mapperVal.Call([]reflect.Value{value})[0] 166 } 167 if genResultOutput { 168 return mapped.Interface().(*GenResult) 169 } 170 if mapperType.In(0) == mapperType.Out(0) { 171 shrinker = result.Shrinker 172 } 173 return &GenResult{ 174 Shrinker: shrinker, 175 Result: mapped.Interface(), 176 Labels: result.Labels, 177 ResultType: mapperType.Out(0), 178 } 179 } 180 return &GenResult{ 181 Shrinker: NoShrinker, 182 Result: nil, 183 Labels: result.Labels, 184 ResultType: mapperType.Out(0), 185 } 186 } 187 } 188 189 // FlatMap creates a derived generator by passing a generated value to a function which itself 190 // creates a generator. 191 func (g Gen) FlatMap(f func(interface{}) Gen, resultType reflect.Type) Gen { 192 return func(genParams *GenParameters) *GenResult { 193 result := g(genParams) 194 value, ok := result.Retrieve() 195 if ok { 196 return f(value)(genParams) 197 } 198 return &GenResult{ 199 Shrinker: NoShrinker, 200 Result: nil, 201 Labels: result.Labels, 202 ResultType: resultType, 203 } 204 } 205 } 206 207 // MapResult creates a derived generator by mapping the GenResult directly. 208 // Contrary to `Map` and `FlatMap` this also allow the conversion of 209 // shrinkers and sieves, but implementation is more cumbersome. 210 // Deprecation note: Map now has the same functionality 211 func (g Gen) MapResult(f func(*GenResult) *GenResult) Gen { 212 return func(genParams *GenParameters) *GenResult { 213 return f(g(genParams)) 214 } 215 } 216 217 // CombineGens creates a generators from a list of generators. 218 // The result type will be a []interface{} containing the generated values of each generators in 219 // the list. 220 // Note: The combined generator will not have a sieve or shrinker. 221 func CombineGens(gens ...Gen) Gen { 222 return func(genParams *GenParameters) *GenResult { 223 labels := []string{} 224 values := make([]interface{}, len(gens)) 225 shrinkers := make([]Shrinker, len(gens)) 226 sieves := make([]func(v interface{}) bool, len(gens)) 227 228 var ok bool 229 for i, gen := range gens { 230 result := gen(genParams) 231 labels = append(labels, result.Labels...) 232 shrinkers[i] = result.Shrinker 233 sieves[i] = result.Sieve 234 values[i], ok = result.Retrieve() 235 if !ok { 236 return &GenResult{ 237 Shrinker: NoShrinker, 238 Result: nil, 239 Labels: result.Labels, 240 ResultType: reflect.TypeOf(values), 241 } 242 } 243 } 244 return &GenResult{ 245 Shrinker: CombineShrinker(shrinkers...), 246 Result: values, 247 Labels: labels, 248 ResultType: reflect.TypeOf(values), 249 Sieve: func(v interface{}) bool { 250 values := v.([]interface{}) 251 for i, value := range values { 252 if sieves[i] != nil && !sieves[i](value) { 253 return false 254 } 255 } 256 return true 257 }, 258 } 259 } 260 }