12
"github.com/onsi/gomega/format"
13
"github.com/onsi/gomega/types"
16
var errInterface = reflect.TypeOf((*error)(nil)).Elem()
17
var gomegaType = reflect.TypeOf((*types.Gomega)(nil)).Elem()
18
var contextType = reflect.TypeOf(new(context.Context)).Elem()
20
type formattedGomegaError interface {
21
FormattedGomegaError() string
24
type asyncPolledActualError struct {
28
func (err *asyncPolledActualError) Error() string {
32
func (err *asyncPolledActualError) FormattedGomegaError() string {
36
type contextWithAttachProgressReporter interface {
37
AttachProgressReporter(func() string) func()
40
type asyncGomegaHaltExecutionError struct{}
42
func (a asyncGomegaHaltExecutionError) GinkgoRecoverShouldIgnoreThisPanic() {}
43
func (a asyncGomegaHaltExecutionError) Error() string {
44
return `An assertion has failed in a goroutine. You should call
48
at the top of the goroutine that caused this panic. This will allow Ginkgo and Gomega to correctly capture and manage this panic.`
51
type AsyncAssertionType uint
54
AsyncAssertionTypeEventually AsyncAssertionType = iota
55
AsyncAssertionTypeConsistently
58
func (at AsyncAssertionType) String() string {
60
case AsyncAssertionTypeEventually:
62
case AsyncAssertionTypeConsistently:
65
return "INVALID ASYNC ASSERTION TYPE"
68
type AsyncAssertion struct {
69
asyncType AsyncAssertionType
73
argsToForward []interface{}
75
timeoutInterval time.Duration
76
pollingInterval time.Duration
77
mustPassRepeatedly int
83
func NewAsyncAssertion(asyncType AsyncAssertionType, actualInput interface{}, g *Gomega, timeoutInterval time.Duration, pollingInterval time.Duration, mustPassRepeatedly int, ctx context.Context, offset int) *AsyncAssertion {
84
out := &AsyncAssertion{
86
timeoutInterval: timeoutInterval,
87
pollingInterval: pollingInterval,
88
mustPassRepeatedly: mustPassRepeatedly,
94
out.actual = actualInput
95
if actualInput != nil && reflect.TypeOf(actualInput).Kind() == reflect.Func {
96
out.actualIsFunc = true
102
func (assertion *AsyncAssertion) WithOffset(offset int) types.AsyncAssertion {
103
assertion.offset = offset
107
func (assertion *AsyncAssertion) WithTimeout(interval time.Duration) types.AsyncAssertion {
108
assertion.timeoutInterval = interval
112
func (assertion *AsyncAssertion) WithPolling(interval time.Duration) types.AsyncAssertion {
113
assertion.pollingInterval = interval
117
func (assertion *AsyncAssertion) Within(timeout time.Duration) types.AsyncAssertion {
118
assertion.timeoutInterval = timeout
122
func (assertion *AsyncAssertion) ProbeEvery(interval time.Duration) types.AsyncAssertion {
123
assertion.pollingInterval = interval
127
func (assertion *AsyncAssertion) WithContext(ctx context.Context) types.AsyncAssertion {
132
func (assertion *AsyncAssertion) WithArguments(argsToForward ...interface{}) types.AsyncAssertion {
133
assertion.argsToForward = argsToForward
137
func (assertion *AsyncAssertion) MustPassRepeatedly(count int) types.AsyncAssertion {
138
assertion.mustPassRepeatedly = count
142
func (assertion *AsyncAssertion) Should(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
143
assertion.g.THelper()
144
vetOptionalDescription("Asynchronous assertion", optionalDescription...)
145
return assertion.match(matcher, true, optionalDescription...)
148
func (assertion *AsyncAssertion) ShouldNot(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool {
149
assertion.g.THelper()
150
vetOptionalDescription("Asynchronous assertion", optionalDescription...)
151
return assertion.match(matcher, false, optionalDescription...)
154
func (assertion *AsyncAssertion) buildDescription(optionalDescription ...interface{}) string {
155
switch len(optionalDescription) {
159
if describe, ok := optionalDescription[0].(func() string); ok {
160
return describe() + "\n"
163
return fmt.Sprintf(optionalDescription[0].(string), optionalDescription[1:]...) + "\n"
166
func (assertion *AsyncAssertion) processReturnValues(values []reflect.Value) (interface{}, error) {
167
if len(values) == 0 {
168
return nil, &asyncPolledActualError{
169
message: fmt.Sprintf("The function passed to %s did not return any values", assertion.asyncType),
173
actual := values[0].Interface()
174
if _, ok := AsPollingSignalError(actual); ok {
175
return actual, actual.(error)
179
for i, extraValue := range values[1:] {
180
extra := extraValue.Interface()
184
if _, ok := AsPollingSignalError(extra); ok {
185
return actual, extra.(error)
187
extraType := reflect.TypeOf(extra)
188
zero := reflect.Zero(extraType).Interface()
189
if reflect.DeepEqual(extra, zero) {
192
if i == len(values)-2 && extraType.Implements(errInterface) {
196
err = &asyncPolledActualError{
197
message: fmt.Sprintf("The function passed to %s had an unexpected non-nil/non-zero return value at index %d:\n%s", assertion.asyncType, i+1, format.Object(extra, 1)),
205
func (assertion *AsyncAssertion) invalidFunctionError(t reflect.Type) error {
206
return fmt.Errorf(`The function passed to %s had an invalid signature of %s. Functions passed to %s must either:
208
(a) have return values or
209
(b) take a Gomega interface as their first argument and use that Gomega instance to make assertions.
211
You can learn more at https://onsi.github.io/gomega/#eventually
212
`, assertion.asyncType, t, assertion.asyncType)
215
func (assertion *AsyncAssertion) noConfiguredContextForFunctionError() error {
216
return fmt.Errorf(`The function passed to %s requested a context.Context, but no context has been provided. Please pass one in using %s().WithContext().
218
You can learn more at https://onsi.github.io/gomega/#eventually
219
`, assertion.asyncType, assertion.asyncType)
222
func (assertion *AsyncAssertion) argumentMismatchError(t reflect.Type, numProvided int) error {
224
if numProvided == 1 {
227
return fmt.Errorf(`The function passed to %s has signature %s takes %d arguments but %d %s been provided. Please use %s().WithArguments() to pass the corect set of arguments.
229
You can learn more at https://onsi.github.io/gomega/#eventually
230
`, assertion.asyncType, t, t.NumIn(), numProvided, have, assertion.asyncType)
233
func (assertion *AsyncAssertion) invalidMustPassRepeatedlyError(reason string) error {
234
return fmt.Errorf(`Invalid use of MustPassRepeatedly with %s %s
236
You can learn more at https://onsi.github.io/gomega/#eventually
237
`, assertion.asyncType, reason)
240
func (assertion *AsyncAssertion) buildActualPoller() (func() (interface{}, error), error) {
241
if !assertion.actualIsFunc {
242
return func() (interface{}, error) { return assertion.actual, nil }, nil
244
actualValue := reflect.ValueOf(assertion.actual)
245
actualType := reflect.TypeOf(assertion.actual)
246
numIn, numOut, isVariadic := actualType.NumIn(), actualType.NumOut(), actualType.IsVariadic()
248
if numIn == 0 && numOut == 0 {
249
return nil, assertion.invalidFunctionError(actualType)
251
takesGomega, takesContext := false, false
253
takesGomega, takesContext = actualType.In(0).Implements(gomegaType), actualType.In(0).Implements(contextType)
255
if takesGomega && numIn > 1 && actualType.In(1).Implements(contextType) {
258
if takesContext && len(assertion.argsToForward) > 0 && reflect.TypeOf(assertion.argsToForward[0]).Implements(contextType) {
261
if !takesGomega && numOut == 0 {
262
return nil, assertion.invalidFunctionError(actualType)
264
if takesContext && assertion.ctx == nil {
265
return nil, assertion.noConfiguredContextForFunctionError()
268
var assertionFailure error
269
inValues := []reflect.Value{}
271
inValues = append(inValues, reflect.ValueOf(NewGomega(assertion.g.DurationBundle).ConfigureWithFailHandler(func(message string, callerSkip ...int) {
273
if len(callerSkip) > 0 {
276
_, file, line, _ := runtime.Caller(skip + 1)
277
assertionFailure = &asyncPolledActualError{
278
message: fmt.Sprintf("The function passed to %s failed at %s:%d with:\n%s", assertion.asyncType, file, line, message),
281
panic(asyncGomegaHaltExecutionError{})
285
inValues = append(inValues, reflect.ValueOf(assertion.ctx))
287
for _, arg := range assertion.argsToForward {
288
inValues = append(inValues, reflect.ValueOf(arg))
291
if !isVariadic && numIn != len(inValues) {
292
return nil, assertion.argumentMismatchError(actualType, len(inValues))
293
} else if isVariadic && len(inValues) < numIn-1 {
294
return nil, assertion.argumentMismatchError(actualType, len(inValues))
297
if assertion.mustPassRepeatedly != 1 && assertion.asyncType != AsyncAssertionTypeEventually {
298
return nil, assertion.invalidMustPassRepeatedlyError("it can only be used with Eventually")
300
if assertion.mustPassRepeatedly < 1 {
301
return nil, assertion.invalidMustPassRepeatedlyError("parameter can't be < 1")
304
return func() (actual interface{}, err error) {
305
var values []reflect.Value
306
assertionFailure = nil
308
if numOut == 0 && takesGomega {
309
actual = assertionFailure
311
actual, err = assertion.processReturnValues(values)
312
_, isAsyncError := AsPollingSignalError(err)
313
if assertionFailure != nil && !isAsyncError {
314
err = assertionFailure
317
if e := recover(); e != nil {
318
if _, isAsyncError := AsPollingSignalError(e); isAsyncError {
320
} else if assertionFailure == nil {
325
values = actualValue.Call(inValues)
330
func (assertion *AsyncAssertion) afterTimeout() <-chan time.Time {
331
if assertion.timeoutInterval >= 0 {
332
return time.After(assertion.timeoutInterval)
335
if assertion.asyncType == AsyncAssertionTypeConsistently {
336
return time.After(assertion.g.DurationBundle.ConsistentlyDuration)
338
if assertion.ctx == nil {
339
return time.After(assertion.g.DurationBundle.EventuallyTimeout)
346
func (assertion *AsyncAssertion) afterPolling() <-chan time.Time {
347
if assertion.pollingInterval >= 0 {
348
return time.After(assertion.pollingInterval)
350
if assertion.asyncType == AsyncAssertionTypeConsistently {
351
return time.After(assertion.g.DurationBundle.ConsistentlyPollingInterval)
353
return time.After(assertion.g.DurationBundle.EventuallyPollingInterval)
357
func (assertion *AsyncAssertion) matcherSaysStopTrying(matcher types.GomegaMatcher, value interface{}) bool {
358
if assertion.actualIsFunc || types.MatchMayChangeInTheFuture(matcher, value) {
364
func (assertion *AsyncAssertion) pollMatcher(matcher types.GomegaMatcher, value interface{}) (matches bool, err error) {
366
if e := recover(); e != nil {
367
if _, isAsyncError := AsPollingSignalError(e); isAsyncError {
375
matches, err = matcher.Match(value)
380
func (assertion *AsyncAssertion) match(matcher types.GomegaMatcher, desiredMatch bool, optionalDescription ...interface{}) bool {
382
timeout := assertion.afterTimeout()
385
var matches, hasLastValidActual bool
386
var actual, lastValidActual interface{}
387
var actualErr, matcherErr error
388
var oracleMatcherSaysStop bool
390
assertion.g.THelper()
392
pollActual, buildActualPollerErr := assertion.buildActualPoller()
393
if buildActualPollerErr != nil {
394
assertion.g.Fail(buildActualPollerErr.Error(), 2+assertion.offset)
398
actual, actualErr = pollActual()
399
if actualErr == nil {
400
lastValidActual = actual
401
hasLastValidActual = true
402
oracleMatcherSaysStop = assertion.matcherSaysStopTrying(matcher, actual)
403
matches, matcherErr = assertion.pollMatcher(matcher, actual)
406
renderError := func(preamble string, err error) string {
408
if pollingSignalErr, ok := AsPollingSignalError(err); ok {
409
message = err.Error()
410
for _, attachment := range pollingSignalErr.Attachments {
411
message += fmt.Sprintf("\n%s:\n", attachment.Description)
412
message += format.Object(attachment.Object, 1)
415
message = preamble + "\n" + format.Object(err, 1)
420
messageGenerator := func() string {
426
if actualErr == nil {
427
if matcherErr == nil {
428
if desiredMatch != matches {
430
message += matcher.FailureMessage(actual)
432
message += matcher.NegatedFailureMessage(actual)
435
if assertion.asyncType == AsyncAssertionTypeConsistently {
436
message += "There is no failure as the matcher passed to Consistently has not yet failed"
438
message += "There is no failure as the matcher passed to Eventually succeeded on its most recent iteration"
442
var fgErr formattedGomegaError
443
if errors.As(actualErr, &fgErr) {
444
message += fgErr.FormattedGomegaError() + "\n"
446
message += renderError(fmt.Sprintf("The matcher passed to %s returned the following error:", assertion.asyncType), matcherErr)
450
var fgErr formattedGomegaError
451
if errors.As(actualErr, &fgErr) {
452
message += fgErr.FormattedGomegaError() + "\n"
454
message += renderError(fmt.Sprintf("The function passed to %s returned the following error:", assertion.asyncType), actualErr)
456
if hasLastValidActual {
457
message += fmt.Sprintf("\nAt one point, however, the function did return successfully.\nYet, %s failed because", assertion.asyncType)
458
_, e := matcher.Match(lastValidActual)
460
message += renderError(" the matcher returned the following error:", e)
462
message += " the matcher was not satisfied:\n"
464
message += matcher.FailureMessage(lastValidActual)
466
message += matcher.NegatedFailureMessage(lastValidActual)
472
description := assertion.buildDescription(optionalDescription...)
473
return fmt.Sprintf("%s%s", description, message)
476
fail := func(preamble string) {
477
assertion.g.THelper()
478
assertion.g.Fail(fmt.Sprintf("%s after %.3fs.\n%s", preamble, time.Since(timer).Seconds(), messageGenerator()), 3+assertion.offset)
481
var contextDone <-chan struct{}
482
if assertion.ctx != nil {
483
contextDone = assertion.ctx.Done()
484
if v, ok := assertion.ctx.Value("GINKGO_SPEC_CONTEXT").(contextWithAttachProgressReporter); ok {
485
detach := v.AttachProgressReporter(messageGenerator)
491
passedRepeatedlyCount := 0
493
var nextPoll <-chan time.Time = nil
494
var isTryAgainAfterError = false
496
for _, err := range []error{actualErr, matcherErr} {
497
if pollingSignalErr, ok := AsPollingSignalError(err); ok {
498
if pollingSignalErr.IsStopTrying() {
499
fail("Told to stop trying")
502
if pollingSignalErr.IsTryAgainAfter() {
503
nextPoll = time.After(pollingSignalErr.TryAgainDuration())
504
isTryAgainAfterError = true
509
if actualErr == nil && matcherErr == nil && matches == desiredMatch {
510
if assertion.asyncType == AsyncAssertionTypeEventually {
511
passedRepeatedlyCount += 1
512
if passedRepeatedlyCount == assertion.mustPassRepeatedly {
516
} else if !isTryAgainAfterError {
517
if assertion.asyncType == AsyncAssertionTypeConsistently {
522
passedRepeatedlyCount = 0
525
if oracleMatcherSaysStop {
526
if assertion.asyncType == AsyncAssertionTypeEventually {
527
fail("No future change is possible. Bailing out early")
535
nextPoll = assertion.afterPolling()
542
actual, actualErr = a, e
544
if actualErr == nil {
546
lastValidActual = actual
547
hasLastValidActual = true
549
oracleMatcherSaysStop = assertion.matcherSaysStopTrying(matcher, actual)
550
m, e := assertion.pollMatcher(matcher, actual)
552
matches, matcherErr = m, e
556
err := context.Cause(assertion.ctx)
557
if err != nil && err != context.Canceled {
558
fail(fmt.Sprintf("Context was cancelled (cause: %s)", err))
560
fail("Context was cancelled")
564
if assertion.asyncType == AsyncAssertionTypeEventually {
568
if isTryAgainAfterError {
569
fail("Timed out while waiting on TryAgainAfter")