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

     1  package gopter
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  )
     7  
     8  // Shrink is a stream of shrunk down values.
     9  // Once the result of a shrink is false, it is considered to be exhausted.
    10  // Important notes for implementors:
    11  //   * Ensure that the returned stream is finite, even though shrinking will
    12  //     eventually be aborted, infinite streams may result in very slow running
    13  //     test.
    14  //   * Ensure that modifications to the returned value will not affect the
    15  //     internal state of your Shrink. If in doubt return by value not by reference
    16  type Shrink func() (interface{}, bool)
    17  
    18  // Filter creates a shrink filtered by a condition
    19  func (s Shrink) Filter(condition func(interface{}) bool) Shrink {
    20  	if condition == nil {
    21  		return s
    22  	}
    23  	return func() (interface{}, bool) {
    24  		value, ok := s()
    25  		for ok && !condition(value) {
    26  			value, ok = s()
    27  		}
    28  		return value, ok
    29  	}
    30  }
    31  
    32  // Map creates a shrink by applying a converter to each element of a shrink.
    33  // f: has to be a function with one parameter (matching the generated value) and a single return.
    34  func (s Shrink) Map(f interface{}) Shrink {
    35  	mapperVal := reflect.ValueOf(f)
    36  	mapperType := mapperVal.Type()
    37  
    38  	if mapperVal.Kind() != reflect.Func {
    39  		panic(fmt.Sprintf("Param of Map has to be a func, but is %v", mapperType.Kind()))
    40  	}
    41  	if mapperType.NumIn() != 1 {
    42  		panic(fmt.Sprintf("Param of Map has to be a func with one param, but is %v", mapperType.NumIn()))
    43  	}
    44  	if mapperType.NumOut() != 1 {
    45  		panic(fmt.Sprintf("Param of Map has to be a func with one return value, but is %v", mapperType.NumOut()))
    46  	}
    47  
    48  	return func() (interface{}, bool) {
    49  		value, ok := s()
    50  		if ok {
    51  			return mapperVal.Call([]reflect.Value{reflect.ValueOf(value)})[0].Interface(), ok
    52  		}
    53  		return nil, false
    54  	}
    55  }
    56  
    57  // All collects all shrinks as a slice. Use with care as this might create
    58  // large results depending on the complexity of the shrink
    59  func (s Shrink) All() []interface{} {
    60  	result := []interface{}{}
    61  	value, ok := s()
    62  	for ok {
    63  		result = append(result, value)
    64  		value, ok = s()
    65  	}
    66  	return result
    67  }
    68  
    69  type concatedShrink struct {
    70  	index   int
    71  	shrinks []Shrink
    72  }
    73  
    74  func (c *concatedShrink) Next() (interface{}, bool) {
    75  	for c.index < len(c.shrinks) {
    76  		value, ok := c.shrinks[c.index]()
    77  		if ok {
    78  			return value, ok
    79  		}
    80  		c.index++
    81  	}
    82  	return nil, false
    83  }
    84  
    85  // ConcatShrinks concats an array of shrinks to a single shrinks
    86  func ConcatShrinks(shrinks ...Shrink) Shrink {
    87  	concated := &concatedShrink{
    88  		index:   0,
    89  		shrinks: shrinks,
    90  	}
    91  	return concated.Next
    92  }
    93  
    94  type interleaved struct {
    95  	first          Shrink
    96  	second         Shrink
    97  	firstExhausted bool
    98  	secondExhaused bool
    99  	state          bool
   100  }
   101  
   102  func (i *interleaved) Next() (interface{}, bool) {
   103  	for !i.firstExhausted || !i.secondExhaused {
   104  		i.state = !i.state
   105  		if i.state && !i.firstExhausted {
   106  			value, ok := i.first()
   107  			if ok {
   108  				return value, true
   109  			}
   110  			i.firstExhausted = true
   111  		} else if !i.state && !i.secondExhaused {
   112  			value, ok := i.second()
   113  			if ok {
   114  				return value, true
   115  			}
   116  			i.secondExhaused = true
   117  		}
   118  	}
   119  	return nil, false
   120  }
   121  
   122  // Interleave this shrink with another
   123  // Both shrinks are expected to produce the same result
   124  func (s Shrink) Interleave(other Shrink) Shrink {
   125  	interleaved := &interleaved{
   126  		first:  s,
   127  		second: other,
   128  	}
   129  	return interleaved.Next
   130  }
   131  
   132  // Shrinker creates a shrink for a given value
   133  type Shrinker func(value interface{}) Shrink
   134  
   135  type elementShrink struct {
   136  	original      []interface{}
   137  	index         int
   138  	elementShrink Shrink
   139  }
   140  
   141  func (e *elementShrink) Next() (interface{}, bool) {
   142  	element, ok := e.elementShrink()
   143  	if !ok {
   144  		return nil, false
   145  	}
   146  	shrunk := make([]interface{}, len(e.original))
   147  	copy(shrunk, e.original)
   148  	shrunk[e.index] = element
   149  
   150  	return shrunk, true
   151  }
   152  
   153  // CombineShrinker create a shrinker by combining a list of shrinkers.
   154  // The resulting shrinker will shrink an []interface{} where each element will be shrunk by
   155  // the corresonding shrinker in 'shrinkers'.
   156  // This method is implicitly used by CombineGens.
   157  func CombineShrinker(shrinkers ...Shrinker) Shrinker {
   158  	return func(v interface{}) Shrink {
   159  		values := v.([]interface{})
   160  		shrinks := make([]Shrink, 0, len(values))
   161  		for i, shrinker := range shrinkers {
   162  			if i >= len(values) {
   163  				break
   164  			}
   165  			shrink := &elementShrink{
   166  				original:      values,
   167  				index:         i,
   168  				elementShrink: shrinker(values[i]),
   169  			}
   170  			shrinks = append(shrinks, shrink.Next)
   171  		}
   172  		return ConcatShrinks(shrinks...)
   173  	}
   174  }
   175  
   176  // NoShrink is an empty shrink.
   177  var NoShrink = Shrink(func() (interface{}, bool) {
   178  	return nil, false
   179  })
   180  
   181  // NoShrinker is a shrinker for NoShrink, i.e. a Shrinker that will not shrink any values.
   182  // This is the default Shrinker if none is provided.
   183  var NoShrinker = Shrinker(func(value interface{}) Shrink {
   184  	return NoShrink
   185  })