1package gopter 2 3import ( 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 reproducable 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. 17type Gen func(*GenParameters) *GenResult 18 19var ( 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 27func (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. 33func (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. 47func (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 90func (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 generators 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. 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) 107func (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 if needsGenParameters { 162 mapped = mapperVal.Call([]reflect.Value{value, reflect.ValueOf(genParams)})[0] 163 } else { 164 mapped = mapperVal.Call([]reflect.Value{value})[0] 165 } 166 if genResultOutput { 167 return mapped.Interface().(*GenResult) 168 } 169 return &GenResult{ 170 Shrinker: NoShrinker, 171 Result: mapped.Interface(), 172 Labels: result.Labels, 173 ResultType: mapperType.Out(0), 174 } 175 } 176 return &GenResult{ 177 Shrinker: NoShrinker, 178 Result: nil, 179 Labels: result.Labels, 180 ResultType: mapperType.Out(0), 181 } 182 } 183} 184 185// FlatMap creates a derived generator by passing a generated value to a function which itself 186// creates a generator. 187func (g Gen) FlatMap(f func(interface{}) Gen, resultType reflect.Type) Gen { 188 return func(genParams *GenParameters) *GenResult { 189 result := g(genParams) 190 value, ok := result.Retrieve() 191 if ok { 192 return f(value)(genParams) 193 } 194 return &GenResult{ 195 Shrinker: NoShrinker, 196 Result: nil, 197 Labels: result.Labels, 198 ResultType: resultType, 199 } 200 } 201} 202 203// MapResult creates a derived generator by mapping the GenResult directly. 204// Contrary to `Map` and `FlatMap` this also allow the conversion of 205// shrinkers and sieves, but implementation is more cumbersome. 206// Deprecation note: Map now has the same functionality 207func (g Gen) MapResult(f func(*GenResult) *GenResult) Gen { 208 return func(genParams *GenParameters) *GenResult { 209 return f(g(genParams)) 210 } 211} 212 213// CombineGens creates a generators from a list of generators. 214// The result type will be a []interface{} containing the generated values of each generators in 215// the list. 216// Note: The combined generator will not have a sieve or shrinker. 217func CombineGens(gens ...Gen) Gen { 218 return func(genParams *GenParameters) *GenResult { 219 labels := []string{} 220 values := make([]interface{}, len(gens)) 221 shrinkers := make([]Shrinker, len(gens)) 222 sieves := make([]func(v interface{}) bool, len(gens)) 223 224 var ok bool 225 for i, gen := range gens { 226 result := gen(genParams) 227 labels = append(labels, result.Labels...) 228 shrinkers[i] = result.Shrinker 229 sieves[i] = result.Sieve 230 values[i], ok = result.Retrieve() 231 if !ok { 232 return &GenResult{ 233 Shrinker: NoShrinker, 234 Result: nil, 235 Labels: result.Labels, 236 ResultType: reflect.TypeOf(values), 237 } 238 } 239 } 240 return &GenResult{ 241 Shrinker: CombineShrinker(shrinkers...), 242 Result: values, 243 Labels: labels, 244 ResultType: reflect.TypeOf(values), 245 Sieve: func(v interface{}) bool { 246 values := v.([]interface{}) 247 for i, value := range values { 248 if sieves[i] != nil && !sieves[i](value) { 249 return false 250 } 251 } 252 return true 253 }, 254 } 255 } 256} 257