github.com/leanovate/gopter@v0.2.9/prop/forall.go (about)

     1  package prop
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  
     7  	"github.com/leanovate/gopter"
     8  )
     9  
    10  var typeOfError = reflect.TypeOf((*error)(nil)).Elem()
    11  
    12  /*
    13  ForAll creates a property that requires the check condition to be true for all values, if the
    14  condition falsiies the generated values will be shrunk.
    15  
    16  "condition" has to be a function with the same number of parameters as the provided
    17  generators "gens". The function may return a simple bool (true means that the
    18  condition has passed), a string (empty string means that condition has passed),
    19  a *PropResult, or one of former combined with an error.
    20  */
    21  func ForAll(condition interface{}, gens ...gopter.Gen) gopter.Prop {
    22  	callCheck, err := checkConditionFunc(condition, len(gens))
    23  	if err != nil {
    24  		return ErrorProp(err)
    25  	}
    26  
    27  	return gopter.SaveProp(func(genParams *gopter.GenParameters) *gopter.PropResult {
    28  		genResults := make([]*gopter.GenResult, len(gens))
    29  		values := make([]reflect.Value, len(gens))
    30  		valuesFormated := make([]string, len(gens))
    31  		var ok bool
    32  		for i, gen := range gens {
    33  			result := gen(genParams)
    34  			genResults[i] = result
    35  			values[i], ok = result.RetrieveAsValue()
    36  			if !ok {
    37  				return &gopter.PropResult{
    38  					Status: gopter.PropUndecided,
    39  				}
    40  			}
    41  			valuesFormated[i] = fmt.Sprintf("%+v", values[i].Interface())
    42  		}
    43  		result := callCheck(values)
    44  		if result.Success() {
    45  			for i, genResult := range genResults {
    46  				result = result.AddArgs(gopter.NewPropArg(genResult, 0, values[i].Interface(), valuesFormated[i], values[i].Interface(), valuesFormated[i]))
    47  			}
    48  		} else {
    49  			for i, genResult := range genResults {
    50  				nextResult, nextValue := shrinkValue(genParams.MaxShrinkCount, genResult, values[i].Interface(), valuesFormated[i], result,
    51  					func(v interface{}) *gopter.PropResult {
    52  						shrunkOne := make([]reflect.Value, len(values))
    53  						copy(shrunkOne, values)
    54  						if v == nil {
    55  							shrunkOne[i] = reflect.Zero(values[i].Type())
    56  						} else {
    57  							shrunkOne[i] = reflect.ValueOf(v)
    58  						}
    59  						return callCheck(shrunkOne)
    60  					})
    61  				result = nextResult
    62  				if nextValue == nil {
    63  					values[i] = reflect.Zero(values[i].Type())
    64  				} else {
    65  					values[i] = reflect.ValueOf(nextValue)
    66  				}
    67  			}
    68  		}
    69  		return result
    70  	})
    71  }
    72  
    73  // ForAll1 legacy interface to be removed in the future
    74  func ForAll1(gen gopter.Gen, check func(v interface{}) (interface{}, error)) gopter.Prop {
    75  	checkFunc := func(v interface{}) *gopter.PropResult {
    76  		return convertResult(check(v))
    77  	}
    78  	return gopter.SaveProp(func(genParams *gopter.GenParameters) *gopter.PropResult {
    79  		genResult := gen(genParams)
    80  		value, ok := genResult.Retrieve()
    81  		if !ok {
    82  			return &gopter.PropResult{
    83  				Status: gopter.PropUndecided,
    84  			}
    85  		}
    86  		valueFormated := fmt.Sprintf("%+v", value)
    87  		result := checkFunc(value)
    88  		if result.Success() {
    89  			return result.AddArgs(gopter.NewPropArg(genResult, 0, value, valueFormated, value, valueFormated))
    90  		}
    91  
    92  		result, _ = shrinkValue(genParams.MaxShrinkCount, genResult, value, valueFormated, result, checkFunc)
    93  		return result
    94  	})
    95  }
    96  
    97  func shrinkValue(maxShrinkCount int, genResult *gopter.GenResult, origValue interface{}, orgiValueFormated string,
    98  	firstFail *gopter.PropResult, check func(interface{}) *gopter.PropResult) (*gopter.PropResult, interface{}) {
    99  	lastFail := firstFail
   100  	lastValue := origValue
   101  	lastValueFormated := orgiValueFormated
   102  
   103  	shrinks := 0
   104  	shrink := genResult.Shrinker(lastValue).Filter(genResult.Sieve)
   105  	nextResult, nextValue, nextValueFormated := firstFailure(shrink, check)
   106  	for nextResult != nil && shrinks < maxShrinkCount {
   107  		shrinks++
   108  		lastValue = nextValue
   109  		lastValueFormated = nextValueFormated
   110  		lastFail = nextResult
   111  
   112  		shrink = genResult.Shrinker(lastValue).Filter(genResult.Sieve)
   113  		nextResult, nextValue, nextValueFormated = firstFailure(shrink, check)
   114  	}
   115  
   116  	return lastFail.WithArgs(firstFail.Args).AddArgs(gopter.NewPropArg(genResult, shrinks, lastValue, lastValueFormated, origValue, orgiValueFormated)), lastValue
   117  }
   118  
   119  func firstFailure(shrink gopter.Shrink, check func(interface{}) *gopter.PropResult) (*gopter.PropResult, interface{}, string) {
   120  	value, ok := shrink()
   121  	for ok {
   122  		valueFormated := fmt.Sprintf("%+v", value)
   123  		result := check(value)
   124  		if !result.Success() {
   125  			return result, value, valueFormated
   126  		}
   127  		value, ok = shrink()
   128  	}
   129  	return nil, nil, ""
   130  }