Skip to content

Commit 21f3090

Browse files
committed
When a polled function returns an error, keep track of the actual and report on the matcher state of the last non-errored actual
...also, improve failure reporting across the board for Eventually and Eventually(f(g Gomega)).Should(Succeed())
1 parent e2eff1f commit 21f3090

File tree

4 files changed

+315
-60
lines changed

4 files changed

+315
-60
lines changed

internal/async_assertion.go

Lines changed: 106 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package internal
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"reflect"
78
"runtime"
@@ -16,6 +17,22 @@ var errInterface = reflect.TypeOf((*error)(nil)).Elem()
1617
var gomegaType = reflect.TypeOf((*types.Gomega)(nil)).Elem()
1718
var contextType = reflect.TypeOf(new(context.Context)).Elem()
1819

20+
type formattedGomegaError interface {
21+
FormattedGomegaError() string
22+
}
23+
24+
type asyncPolledActualError struct {
25+
message string
26+
}
27+
28+
func (err *asyncPolledActualError) Error() string {
29+
return err.message
30+
}
31+
32+
func (err *asyncPolledActualError) FormattedGomegaError() string {
33+
return err.message
34+
}
35+
1936
type contextWithAttachProgressReporter interface {
2037
AttachProgressReporter(func() string) func()
2138
}
@@ -148,7 +165,9 @@ func (assertion *AsyncAssertion) buildDescription(optionalDescription ...interfa
148165

149166
func (assertion *AsyncAssertion) processReturnValues(values []reflect.Value) (interface{}, error) {
150167
if len(values) == 0 {
151-
return nil, fmt.Errorf("No values were returned by the function passed to Gomega")
168+
return nil, &asyncPolledActualError{
169+
message: fmt.Sprintf("The function passed to %s did not return any values", assertion.asyncType),
170+
}
152171
}
153172

154173
actual := values[0].Interface()
@@ -171,10 +190,12 @@ func (assertion *AsyncAssertion) processReturnValues(values []reflect.Value) (in
171190
continue
172191
}
173192
if i == len(values)-2 && extraType.Implements(errInterface) {
174-
err = fmt.Errorf("function returned error: %w", extra.(error))
193+
err = extra.(error)
175194
}
176195
if err == nil {
177-
err = fmt.Errorf("Unexpected non-nil/non-zero return value at index %d:\n\t<%T>: %#v", i+1, extra, extra)
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)),
198+
}
178199
}
179200
}
180201

@@ -253,7 +274,9 @@ func (assertion *AsyncAssertion) buildActualPoller() (func() (interface{}, error
253274
skip = callerSkip[0]
254275
}
255276
_, file, line, _ := runtime.Caller(skip + 1)
256-
assertionFailure = fmt.Errorf("Assertion in callback at %s:%d failed:\n%s", file, line, message)
277+
assertionFailure = &asyncPolledActualError{
278+
message: fmt.Sprintf("The function passed to %s failed at %s:%d with:\n%s", assertion.asyncType, file, line, message),
279+
}
257280
// we throw an asyncGomegaHaltExecutionError so that defer GinkgoRecover() can catch this error if the user makes an assertion in a goroutine
258281
panic(asyncGomegaHaltExecutionError{})
259282
})))
@@ -359,46 +382,85 @@ func (assertion *AsyncAssertion) match(matcher types.GomegaMatcher, desiredMatch
359382
timeout := assertion.afterTimeout()
360383
lock := sync.Mutex{}
361384

362-
var matches bool
363-
var err error
385+
var matches, hasLastValidActual bool
386+
var actual, lastValidActual interface{}
387+
var actualErr, matcherErr error
364388
var oracleMatcherSaysStop bool
365389

366390
assertion.g.THelper()
367391

368-
pollActual, err := assertion.buildActualPoller()
369-
if err != nil {
370-
assertion.g.Fail(err.Error(), 2+assertion.offset)
392+
pollActual, buildActualPollerErr := assertion.buildActualPoller()
393+
if buildActualPollerErr != nil {
394+
assertion.g.Fail(buildActualPollerErr.Error(), 2+assertion.offset)
371395
return false
372396
}
373397

374-
value, err := pollActual()
375-
if err == nil {
376-
oracleMatcherSaysStop = assertion.matcherSaysStopTrying(matcher, value)
377-
matches, err = assertion.pollMatcher(matcher, value)
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)
404+
}
405+
406+
renderError := func(preamble string, err error) string {
407+
message := ""
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)
413+
}
414+
} else {
415+
message = preamble + "\n" + err.Error() + "\n" + format.Object(err, 1)
416+
}
417+
return message
378418
}
379419

380420
messageGenerator := func() string {
381421
// can be called out of band by Ginkgo if the user requests a progress report
382422
lock.Lock()
383423
defer lock.Unlock()
384424
message := ""
385-
if err != nil {
386-
if pollingSignalErr, ok := AsPollingSignalError(err); ok && pollingSignalErr.IsStopTrying() {
387-
message = err.Error()
388-
for _, attachment := range pollingSignalErr.Attachments {
389-
message += fmt.Sprintf("\n%s:\n", attachment.Description)
390-
message += format.Object(attachment.Object, 1)
425+
426+
if actualErr == nil {
427+
if matcherErr == nil {
428+
if desiredMatch {
429+
message += matcher.FailureMessage(actual)
430+
} else {
431+
message += matcher.NegatedFailureMessage(actual)
391432
}
392433
} else {
393-
message = "Error: " + err.Error() + "\n" + format.Object(err, 1)
434+
var fgErr formattedGomegaError
435+
if errors.As(actualErr, &fgErr) {
436+
message += fgErr.FormattedGomegaError() + "\n"
437+
} else {
438+
message += renderError(fmt.Sprintf("The matcher passed to %s returned the following error:", assertion.asyncType), matcherErr)
439+
}
394440
}
395441
} else {
396-
if desiredMatch {
397-
message = matcher.FailureMessage(value)
442+
var fgErr formattedGomegaError
443+
if errors.As(actualErr, &fgErr) {
444+
message += fgErr.FormattedGomegaError() + "\n"
398445
} else {
399-
message = matcher.NegatedFailureMessage(value)
446+
message += renderError(fmt.Sprintf("The function passed to %s returned the following error:", assertion.asyncType), actualErr)
447+
}
448+
if hasLastValidActual {
449+
message += fmt.Sprintf("\nAt one point, however, the function did return successfully. But %s failed because", assertion.asyncType)
450+
_, e := matcher.Match(lastValidActual)
451+
if e != nil {
452+
message += renderError(" the matcher returned the following error:", e)
453+
} else {
454+
message += " the matcher was not satisfied:\n"
455+
if desiredMatch {
456+
message += matcher.FailureMessage(lastValidActual)
457+
} else {
458+
message += matcher.NegatedFailureMessage(lastValidActual)
459+
}
460+
}
400461
}
401462
}
463+
402464
description := assertion.buildDescription(optionalDescription...)
403465
return fmt.Sprintf("%s%s", description, message)
404466
}
@@ -423,18 +485,20 @@ func (assertion *AsyncAssertion) match(matcher types.GomegaMatcher, desiredMatch
423485
var nextPoll <-chan time.Time = nil
424486
var isTryAgainAfterError = false
425487

426-
if pollingSignalErr, ok := AsPollingSignalError(err); ok {
427-
if pollingSignalErr.IsStopTrying() {
428-
fail("Told to stop trying")
429-
return false
430-
}
431-
if pollingSignalErr.IsTryAgainAfter() {
432-
nextPoll = time.After(pollingSignalErr.TryAgainDuration())
433-
isTryAgainAfterError = true
488+
for _, err := range []error{actualErr, matcherErr} {
489+
if pollingSignalErr, ok := AsPollingSignalError(err); ok {
490+
if pollingSignalErr.IsStopTrying() {
491+
fail("Told to stop trying")
492+
return false
493+
}
494+
if pollingSignalErr.IsTryAgainAfter() {
495+
nextPoll = time.After(pollingSignalErr.TryAgainDuration())
496+
isTryAgainAfterError = true
497+
}
434498
}
435499
}
436500

437-
if err == nil && matches == desiredMatch {
501+
if actualErr == nil && matcherErr == nil && matches == desiredMatch {
438502
if assertion.asyncType == AsyncAssertionTypeEventually {
439503
passedRepeatedlyCount += 1
440504
if passedRepeatedlyCount == assertion.mustPassRepeatedly {
@@ -465,15 +529,19 @@ func (assertion *AsyncAssertion) match(matcher types.GomegaMatcher, desiredMatch
465529

466530
select {
467531
case <-nextPoll:
468-
v, e := pollActual()
532+
a, e := pollActual()
469533
lock.Lock()
470-
value, err = v, e
534+
actual, actualErr = a, e
471535
lock.Unlock()
472-
if err == nil {
473-
oracleMatcherSaysStop = assertion.matcherSaysStopTrying(matcher, value)
474-
m, e := assertion.pollMatcher(matcher, value)
536+
if actualErr == nil {
537+
lock.Lock()
538+
lastValidActual = actual
539+
hasLastValidActual = true
540+
lock.Unlock()
541+
oracleMatcherSaysStop = assertion.matcherSaysStopTrying(matcher, actual)
542+
m, e := assertion.pollMatcher(matcher, actual)
475543
lock.Lock()
476-
matches, err = m, e
544+
matches, matcherErr = m, e
477545
lock.Unlock()
478546
}
479547
case <-contextDone:

0 commit comments

Comments
 (0)