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