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  }