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