1 | 1 |
|
2 | 2 |
import (
|
3 | 3 |
"context"
|
4 | |
"errors"
|
5 | 4 |
"fmt"
|
6 | 5 |
"reflect"
|
7 | 6 |
"runtime"
|
|
18 | 17 |
AsyncAssertionTypeConsistently
|
19 | 18 |
)
|
20 | 19 |
|
|
20 |
func (at AsyncAssertionType) String() string {
|
|
21 |
switch at {
|
|
22 |
case AsyncAssertionTypeEventually:
|
|
23 |
return "Eventually"
|
|
24 |
case AsyncAssertionTypeConsistently:
|
|
25 |
return "Consistently"
|
|
26 |
}
|
|
27 |
return "INVALID ASYNC ASSERTION TYPE"
|
|
28 |
}
|
|
29 |
|
21 | 30 |
type AsyncAssertion struct {
|
22 | 31 |
asyncType AsyncAssertionType
|
23 | 32 |
|
24 | 33 |
actualIsFunc bool
|
25 | |
actualValue interface{}
|
26 | |
actualFunc func() ([]reflect.Value, error)
|
|
34 |
actual interface{}
|
27 | 35 |
|
28 | 36 |
timeoutInterval time.Duration
|
29 | 37 |
pollingInterval time.Duration
|
|
42 | 50 |
g: g,
|
43 | 51 |
}
|
44 | 52 |
|
45 | |
switch actualType := reflect.TypeOf(actualInput); {
|
46 | |
case actualInput == nil || actualType.Kind() != reflect.Func:
|
47 | |
out.actualValue = actualInput
|
48 | |
case actualType.NumIn() == 0 && actualType.NumOut() > 0:
|
|
53 |
out.actual = actualInput
|
|
54 |
if actualInput != nil && reflect.TypeOf(actualInput).Kind() == reflect.Func {
|
49 | 55 |
out.actualIsFunc = true
|
50 | |
out.actualFunc = func() ([]reflect.Value, error) {
|
51 | |
return reflect.ValueOf(actualInput).Call([]reflect.Value{}), nil
|
52 | |
}
|
53 | |
case actualType.NumIn() == 1 && actualType.In(0).Implements(reflect.TypeOf((*types.Gomega)(nil)).Elem()):
|
54 | |
out.actualIsFunc = true
|
55 | |
out.actualFunc = func() (values []reflect.Value, err error) {
|
56 | |
var assertionFailure error
|
57 | |
assertionCapturingGomega := NewGomega(g.DurationBundle).ConfigureWithFailHandler(func(message string, callerSkip ...int) {
|
58 | |
skip := 0
|
59 | |
if len(callerSkip) > 0 {
|
60 | |
skip = callerSkip[0]
|
61 | |
}
|
62 | |
_, file, line, _ := runtime.Caller(skip + 1)
|
63 | |
assertionFailure = fmt.Errorf("Assertion in callback at %s:%d failed:\n%s", file, line, message)
|
64 | |
panic("stop execution")
|
65 | |
})
|
66 | |
|
67 | |
defer func() {
|
68 | |
if actualType.NumOut() == 0 {
|
69 | |
if assertionFailure == nil {
|
70 | |
values = []reflect.Value{reflect.Zero(reflect.TypeOf((*error)(nil)).Elem())}
|
71 | |
} else {
|
72 | |
values = []reflect.Value{reflect.ValueOf(assertionFailure)}
|
73 | |
}
|
74 | |
} else {
|
75 | |
err = assertionFailure
|
76 | |
}
|
77 | |
if e := recover(); e != nil && assertionFailure == nil {
|
78 | |
panic(e)
|
79 | |
}
|
80 | |
}()
|
81 | |
|
82 | |
values = reflect.ValueOf(actualInput).Call([]reflect.Value{reflect.ValueOf(assertionCapturingGomega)})
|
83 | |
return
|
84 | |
}
|
85 | |
default:
|
86 | |
msg := fmt.Sprintf("The function passed to Gomega's async assertions should either take no arguments and return values, or take a single Gomega interface that it can use to make assertions within the body of the function. When taking a Gomega interface the function can optionally return values or return nothing. The function you passed takes %d arguments and returns %d values.", actualType.NumIn(), actualType.NumOut())
|
87 | |
g.Fail(msg, offset+4)
|
88 | 56 |
}
|
89 | 57 |
|
90 | 58 |
return out
|
|
144 | 112 |
return fmt.Sprintf(optionalDescription[0].(string), optionalDescription[1:]...) + "\n"
|
145 | 113 |
}
|
146 | 114 |
|
147 | |
func (assertion *AsyncAssertion) pollActual() (interface{}, error) {
|
|
115 |
func (assertion *AsyncAssertion) processReturnValues(values []reflect.Value) (interface{}, error) {
|
|
116 |
if len(values) == 0 {
|
|
117 |
return nil, fmt.Errorf("No values were returned by the function passed to Gomega")
|
|
118 |
}
|
|
119 |
actual := values[0].Interface()
|
|
120 |
for i, extraValue := range values[1:] {
|
|
121 |
extra := extraValue.Interface()
|
|
122 |
if extra == nil {
|
|
123 |
continue
|
|
124 |
}
|
|
125 |
zero := reflect.Zero(extraValue.Type()).Interface()
|
|
126 |
if reflect.DeepEqual(extra, zero) {
|
|
127 |
continue
|
|
128 |
}
|
|
129 |
return actual, fmt.Errorf("Unexpected non-nil/non-zero argument at index %d:\n\t<%T>: %#v", i+1, extra, extra)
|
|
130 |
}
|
|
131 |
return actual, nil
|
|
132 |
}
|
|
133 |
|
|
134 |
var gomegaType = reflect.TypeOf((*types.Gomega)(nil)).Elem()
|
|
135 |
var contextType = reflect.TypeOf(new(context.Context)).Elem()
|
|
136 |
|
|
137 |
func (assertion *AsyncAssertion) invalidFunctionError(t reflect.Type) error {
|
|
138 |
return fmt.Errorf(`The function passed to %s had an invalid signature of %s. Functions passed to %s must either:
|
|
139 |
|
|
140 |
(a) have return values or
|
|
141 |
(b) take a Gomega interface as their first argument and use that Gomega instance to make assertions.
|
|
142 |
|
|
143 |
You can learn more at https://onsi.github.io/gomega/#eventually
|
|
144 |
`, assertion.asyncType, t, assertion.asyncType)
|
|
145 |
}
|
|
146 |
|
|
147 |
func (assertion *AsyncAssertion) noConfiguredContextForFunctionError(t reflect.Type) error {
|
|
148 |
return fmt.Errorf(`The function passed to %s requested a context.Context, but no context has been provided to %s. Please pass one in using %s().WithContext().
|
|
149 |
|
|
150 |
You can learn more at https://onsi.github.io/gomega/#eventually
|
|
151 |
`, assertion.asyncType, t, assertion.asyncType)
|
|
152 |
}
|
|
153 |
|
|
154 |
func (assertion *AsyncAssertion) buildActualPoller() (func() (interface{}, error), error) {
|
148 | 155 |
if !assertion.actualIsFunc {
|
149 | |
return assertion.actualValue, nil
|
150 | |
}
|
151 | |
|
152 | |
values, err := assertion.actualFunc()
|
153 | |
if err != nil {
|
154 | |
return nil, err
|
155 | |
}
|
156 | |
extras := []interface{}{nil}
|
157 | |
for _, value := range values[1:] {
|
158 | |
extras = append(extras, value.Interface())
|
159 | |
}
|
160 | |
success, message := vetActuals(extras, 0)
|
161 | |
if !success {
|
162 | |
return nil, errors.New(message)
|
163 | |
}
|
164 | |
|
165 | |
return values[0].Interface(), nil
|
|
156 |
return func() (interface{}, error) { return assertion.actual, nil }, nil
|
|
157 |
}
|
|
158 |
actualValue := reflect.ValueOf(assertion.actual)
|
|
159 |
actualType := reflect.TypeOf(assertion.actual)
|
|
160 |
numIn, numOut := actualType.NumIn(), actualType.NumOut()
|
|
161 |
|
|
162 |
if numIn == 0 && numOut == 0 {
|
|
163 |
return nil, assertion.invalidFunctionError(actualType)
|
|
164 |
} else if numIn == 0 {
|
|
165 |
return func() (interface{}, error) { return assertion.processReturnValues(actualValue.Call([]reflect.Value{})) }, nil
|
|
166 |
}
|
|
167 |
takesGomega, takesContext := actualType.In(0).Implements(gomegaType), actualType.In(0).Implements(contextType)
|
|
168 |
if takesGomega && numIn > 1 && actualType.In(1).Implements(contextType) {
|
|
169 |
takesContext = true
|
|
170 |
}
|
|
171 |
if !takesGomega && numOut == 0 {
|
|
172 |
return nil, assertion.invalidFunctionError(actualType)
|
|
173 |
}
|
|
174 |
if takesContext && assertion.ctx == nil {
|
|
175 |
return nil, assertion.noConfiguredContextForFunctionError(actualType)
|
|
176 |
}
|
|
177 |
remainingIn := numIn
|
|
178 |
if takesGomega {
|
|
179 |
remainingIn -= 1
|
|
180 |
}
|
|
181 |
if takesContext {
|
|
182 |
remainingIn -= 1
|
|
183 |
}
|
|
184 |
if remainingIn > 0 {
|
|
185 |
return nil, assertion.invalidFunctionError(actualType)
|
|
186 |
}
|
|
187 |
|
|
188 |
var assertionFailure error
|
|
189 |
inValues := []reflect.Value{}
|
|
190 |
if takesGomega {
|
|
191 |
inValues = append(inValues, reflect.ValueOf(NewGomega(assertion.g.DurationBundle).ConfigureWithFailHandler(func(message string, callerSkip ...int) {
|
|
192 |
skip := 0
|
|
193 |
if len(callerSkip) > 0 {
|
|
194 |
skip = callerSkip[0]
|
|
195 |
}
|
|
196 |
_, file, line, _ := runtime.Caller(skip + 1)
|
|
197 |
assertionFailure = fmt.Errorf("Assertion in callback at %s:%d failed:\n%s", file, line, message)
|
|
198 |
panic("stop execution")
|
|
199 |
})))
|
|
200 |
}
|
|
201 |
if takesContext {
|
|
202 |
inValues = append(inValues, reflect.ValueOf(assertion.ctx))
|
|
203 |
}
|
|
204 |
|
|
205 |
return func() (actual interface{}, err error) {
|
|
206 |
var values []reflect.Value
|
|
207 |
assertionFailure = nil
|
|
208 |
defer func() {
|
|
209 |
if numOut == 0 {
|
|
210 |
actual = assertionFailure
|
|
211 |
} else {
|
|
212 |
actual, err = assertion.processReturnValues(values)
|
|
213 |
if assertionFailure != nil {
|
|
214 |
err = assertionFailure
|
|
215 |
}
|
|
216 |
}
|
|
217 |
if e := recover(); e != nil && assertionFailure == nil {
|
|
218 |
panic(e)
|
|
219 |
}
|
|
220 |
}()
|
|
221 |
values = actualValue.Call(inValues)
|
|
222 |
return
|
|
223 |
}, nil
|
166 | 224 |
}
|
167 | 225 |
|
168 | 226 |
func (assertion *AsyncAssertion) matcherMayChange(matcher types.GomegaMatcher, value interface{}) bool {
|
|
185 | 243 |
var err error
|
186 | 244 |
mayChange := true
|
187 | 245 |
|
188 | |
value, err := assertion.pollActual()
|
|
246 |
assertion.g.THelper()
|
|
247 |
|
|
248 |
pollActual, err := assertion.buildActualPoller()
|
|
249 |
if err != nil {
|
|
250 |
assertion.g.Fail(err.Error(), 2+assertion.offset)
|
|
251 |
return false
|
|
252 |
}
|
|
253 |
|
|
254 |
value, err := pollActual()
|
189 | 255 |
if err == nil {
|
190 | 256 |
mayChange = assertion.matcherMayChange(matcher, value)
|
191 | 257 |
matches, err = matcher.Match(value)
|
192 | 258 |
}
|
193 | |
|
194 | |
assertion.g.THelper()
|
195 | 259 |
|
196 | 260 |
messageGenerator := func() string {
|
197 | 261 |
// can be called out of band by Ginkgo if the user requests a progress report
|
|
239 | 303 |
|
240 | 304 |
select {
|
241 | 305 |
case <-time.After(assertion.pollingInterval):
|
242 | |
v, e := assertion.pollActual()
|
|
306 |
v, e := pollActual()
|
243 | 307 |
lock.Lock()
|
244 | 308 |
value, err = v, e
|
245 | 309 |
lock.Unlock()
|
|
271 | 335 |
|
272 | 336 |
select {
|
273 | 337 |
case <-time.After(assertion.pollingInterval):
|
274 | |
v, e := assertion.pollActual()
|
|
338 |
v, e := pollActual()
|
275 | 339 |
lock.Lock()
|
276 | 340 |
value, err = v, e
|
277 | 341 |
lock.Unlock()
|