diff --git a/go.mod b/go.mod index 11f4ba7c..57766e27 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/golang/mock v1.6.0 github.com/kardolus/httpmock v0.0.0-20181110092731-53def6cd0f87 github.com/onsi/ginkgo v1.16.5 - github.com/onsi/gomega v1.21.1 + github.com/onsi/gomega v1.22.1 github.com/tidwall/gjson v1.14.3 ) diff --git a/go.sum b/go.sum index 3c79b512..b0836e4a 100644 --- a/go.sum +++ b/go.sum @@ -89,16 +89,18 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= -github.com/onsi/ginkgo/v2 v2.1.6 h1:Fx2POJZfKRQcM1pH49qSZiYeu319wji004qX+GDovrU= github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= +github.com/onsi/ginkgo/v2 v2.3.0 h1:kUMoxMoQG3ogk/QWyKh3zibV7BKZ+xBpWil1cTylVqc= +github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= -github.com/onsi/gomega v1.21.1 h1:OB/euWYIExnPBohllTicTHmGTrMaqJ67nIu80j0/uEM= github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= +github.com/onsi/gomega v1.22.1 h1:pY8O4lBfsHKZHM/6nrxkhVPUznOlIu3quZcKP/M20KI= +github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM= github.com/paketo-buildpacks/packit v1.3.1 h1:DJAfqsDadRllr/OPYDONxJEOHbYUMWE1NIPPArq4b7w= github.com/paketo-buildpacks/packit v1.3.1/go.mod h1:v0jVFr3GNcM9JDwwuIAzYNV4Le1L728uMSNhjUybXVA= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= diff --git a/vendor/github.com/onsi/gomega/CHANGELOG.md b/vendor/github.com/onsi/gomega/CHANGELOG.md index c5f35de6..8c848ec7 100644 --- a/vendor/github.com/onsi/gomega/CHANGELOG.md +++ b/vendor/github.com/onsi/gomega/CHANGELOG.md @@ -1,4 +1,32 @@ -## v1.21.1 +## 1.22.1 + +## Fixes +- When passed a context and no explicit timeout, Eventually will only timeout when the context is cancelled [e5105cf] +- Allow StopTrying() to be wrapped [bf3cba9] + +## Maintenance +- bump to ginkgo v2.3.0 [c5d5c39] + +## 1.22.0 + +### Features + +Several improvements have been made to `Eventually` and `Consistently` in this and the most recent releases: + +- Eventually and Consistently can take a context.Context [65c01bc] + This enables integration with Ginkgo 2.3.0's interruptible nodes and node timeouts. +- Eventually and Consistently that are passed a SpecContext can provide reports when an interrupt occurs [0d063c9] +- Eventually/Consistently will forward an attached context to functions that ask for one [e2091c5] +- Eventually/Consistently supports passing arguments to functions via WithArguments() [a2dc7c3] +- Eventually and Consistently can now be stopped early with StopTrying(message) and StopTrying(message).Now() [52976bb] + +These improvements are all documented in [Gomega's docs](https://onsi.github.io/gomega/#making-asynchronous-assertions) + +## Fixes + +## Maintenance + +## 1.21.1 ### Features - Eventually and Consistently that are passed a SpecContext can provide reports when an interrupt occurs [0d063c9] diff --git a/vendor/github.com/onsi/gomega/go.mod b/vendor/github.com/onsi/gomega/go.mod index 61bab1e4..660a3ad6 100644 --- a/vendor/github.com/onsi/gomega/go.mod +++ b/vendor/github.com/onsi/gomega/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/golang/protobuf v1.5.2 github.com/google/go-cmp v0.5.8 - github.com/onsi/ginkgo/v2 v2.1.6 + github.com/onsi/ginkgo/v2 v2.3.0 golang.org/x/net v0.0.0-20220722155237-a158d28d115b gopkg.in/yaml.v3 v3.0.1 ) diff --git a/vendor/github.com/onsi/gomega/go.sum b/vendor/github.com/onsi/gomega/go.sum index 6f324027..56583dd8 100644 --- a/vendor/github.com/onsi/gomega/go.sum +++ b/vendor/github.com/onsi/gomega/go.sum @@ -4,8 +4,8 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/onsi/ginkgo/v2 v2.1.6 h1:Fx2POJZfKRQcM1pH49qSZiYeu319wji004qX+GDovrU= -github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= +github.com/onsi/ginkgo/v2 v2.3.0 h1:kUMoxMoQG3ogk/QWyKh3zibV7BKZ+xBpWil1cTylVqc= +github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0= golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= diff --git a/vendor/github.com/onsi/gomega/gomega_dsl.go b/vendor/github.com/onsi/gomega/gomega_dsl.go index 274116fd..6a9d80bb 100644 --- a/vendor/github.com/onsi/gomega/gomega_dsl.go +++ b/vendor/github.com/onsi/gomega/gomega_dsl.go @@ -22,7 +22,7 @@ import ( "github.com/onsi/gomega/types" ) -const GOMEGA_VERSION = "1.21.1" +const GOMEGA_VERSION = "1.22.1" const nilGomegaPanic = `You are trying to make an assertion, but haven't registered Gomega's fail handler. If you're using Ginkgo then you probably forgot to put your assertion in an It(). @@ -266,7 +266,7 @@ this will trigger Go's race detector as the goroutine polling via Eventually wil **Category 2: Make Eventually assertions on functions** -Eventually can be passed functions that **take no arguments** and **return at least one value**. When configured this way, Eventually will poll the function repeatedly and pass the first returned value to the matcher. +Eventually can be passed functions that **return at least one value**. When configured this way, Eventually will poll the function repeatedly and pass the first returned value to the matcher. For example: @@ -286,15 +286,27 @@ Then will pass only if and when the returned error is nil *and* the returned string satisfies the matcher. +Eventually can also accept functions that take arguments, however you must provide those arguments using .WithArguments(). For example, consider a function that takes a user-id and makes a network request to fetch a full name: + func FetchFullName(userId int) (string, error) + +You can poll this function like so: + Eventually(FetchFullName).WithArguments(1138).Should(Equal("Wookie")) + It is important to note that the function passed into Eventually is invoked *synchronously* when polled. Eventually does not (in fact, it cannot) kill the function if it takes longer to return than Eventually's configured timeout. A common practice here is to use a context. Here's an example that combines Ginkgo's spec timeout support with Eventually: It("fetches the correct count", func(ctx SpecContext) { Eventually(func() int { - return client.FetchCount(ctx) + return client.FetchCount(ctx, "/users") }, ctx).Should(BeNumerically(">=", 17)) }, SpecTimeout(time.Second)) -now, when Ginkgo cancels the context both the FetchCount client and Gomega will be informed and can exit. +you an also use Eventually().WithContext(ctx) to pass in the context. Passed-in contexts play nicely with paseed-in arguments as long as the context appears first. You can rewrite the above example as: + + It("fetches the correct count", func(ctx SpecContext) { + Eventually(client.FetchCount).WithContext(ctx).WithArguments("/users").Should(BeNumerically(">=", 17)) + }, SpecTimeout(time.Second)) + +Either way the context passd to Eventually is also passed to the underlying funciton. Now, when Ginkgo cancels the context both the FetchCount client and Gomega will be informed and can exit. **Category 3: Making assertions _in_ the function passed into Eventually** @@ -324,6 +336,17 @@ For example: will rerun the function until all assertions pass. +You can also pass additional arugments to functions that take a Gomega. The only rule is that the Gomega argument must be first. If you also want to pass the context attached to Eventually you must ensure that is the second argument. For example: + + Eventually(func(g Gomega, ctx context.Context, path string, expected ...string){ + tok, err := client.GetToken(ctx) + g.Expect(err).NotTo(HaveOccurred()) + + elements, err := client.Fetch(ctx, tok, path) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(elements).To(ConsistOf(expected)) + }).WithContext(ctx).WithArguments("/names", "Joe", "Jane", "Sam").Should(Succeed()) + Finally, in addition to passing timeouts and a context to Eventually you can be more explicit with Eventually's chaining configuration methods: Eventually(..., "1s", "2s", ctx).Should(...) @@ -382,6 +405,39 @@ func ConsistentlyWithOffset(offset int, actual interface{}, args ...interface{}) return Default.ConsistentlyWithOffset(offset, actual, args...) } +/* +StopTrying can be used to signal to Eventually and Consistently that the polled function will not change +and that they should stop trying. In the case of Eventually, if a match does not occur in this, final, iteration then a failure will result. In the case of Consistently, as long as this last iteration satisfies the match, the assertion will be considered successful. + +You can send the StopTrying signal by either returning a StopTrying("message") messages as an error from your passed-in function _or_ by calling StopTrying("message").Now() to trigger a panic and end execution. + +Here are a couple of examples. This is how you might use StopTrying() as an error to signal that Eventually should stop: + + playerIndex, numPlayers := 0, 11 + Eventually(func() (string, error) { + name := client.FetchPlayer(playerIndex) + playerIndex += 1 + if playerIndex == numPlayers { + return name, StopTrying("No more players left") + } else { + return name, nil + } + }).Should(Equal("Patrick Mahomes")) + +note that the final `name` returned alongside `StopTrying()` will be processed. + +And here's an example where `StopTrying().Now()` is called to halt execution immediately: + + Eventually(func() []string { + names, err := client.FetchAllPlayers() + if err == client.IRRECOVERABLE_ERROR { + StopTrying("Irrecoverable error occurred").Now() + } + return names + }).Should(ContainElement("Patrick Mahomes")) +*/ +var StopTrying = internal.StopTrying + // SetDefaultEventuallyTimeout sets the default timeout duration for Eventually. Eventually will repeatedly poll your condition until it succeeds, or until this timeout elapses. func SetDefaultEventuallyTimeout(t time.Duration) { Default.SetDefaultEventuallyTimeout(t) diff --git a/vendor/github.com/onsi/gomega/internal/async_assertion.go b/vendor/github.com/onsi/gomega/internal/async_assertion.go index defa8051..818a6e19 100644 --- a/vendor/github.com/onsi/gomega/internal/async_assertion.go +++ b/vendor/github.com/onsi/gomega/internal/async_assertion.go @@ -12,6 +12,50 @@ import ( "github.com/onsi/gomega/types" ) +type StopTryingError interface { + error + Now() + wasViaPanic() bool +} + +func asStopTryingError(actual interface{}) (StopTryingError, bool) { + if actual == nil { + return nil, false + } + if actualErr, ok := actual.(error); ok { + var target *stopTryingError + if errors.As(actualErr, &target) { + return target, true + } else { + return nil, false + } + } + + return nil, false +} + +type stopTryingError struct { + message string + viaPanic bool +} + +func (s *stopTryingError) Error() string { + return s.message +} + +func (s *stopTryingError) Now() { + s.viaPanic = true + panic(s) +} + +func (s *stopTryingError) wasViaPanic() bool { + return s.viaPanic +} + +var StopTrying = func(message string) StopTryingError { + return &stopTryingError{message: message} +} + type AsyncAssertionType uint const ( @@ -19,12 +63,22 @@ const ( AsyncAssertionTypeConsistently ) +func (at AsyncAssertionType) String() string { + switch at { + case AsyncAssertionTypeEventually: + return "Eventually" + case AsyncAssertionTypeConsistently: + return "Consistently" + } + return "INVALID ASYNC ASSERTION TYPE" +} + type AsyncAssertion struct { asyncType AsyncAssertionType - actualIsFunc bool - actualValue interface{} - actualFunc func() ([]reflect.Value, error) + actualIsFunc bool + actual interface{} + argsToForward []interface{} timeoutInterval time.Duration pollingInterval time.Duration @@ -43,49 +97,9 @@ func NewAsyncAssertion(asyncType AsyncAssertionType, actualInput interface{}, g g: g, } - switch actualType := reflect.TypeOf(actualInput); { - case actualInput == nil || actualType.Kind() != reflect.Func: - out.actualValue = actualInput - case actualType.NumIn() == 0 && actualType.NumOut() > 0: - out.actualIsFunc = true - out.actualFunc = func() ([]reflect.Value, error) { - return reflect.ValueOf(actualInput).Call([]reflect.Value{}), nil - } - case actualType.NumIn() == 1 && actualType.In(0).Implements(reflect.TypeOf((*types.Gomega)(nil)).Elem()): + out.actual = actualInput + if actualInput != nil && reflect.TypeOf(actualInput).Kind() == reflect.Func { out.actualIsFunc = true - out.actualFunc = func() (values []reflect.Value, err error) { - var assertionFailure error - assertionCapturingGomega := NewGomega(g.DurationBundle).ConfigureWithFailHandler(func(message string, callerSkip ...int) { - skip := 0 - if len(callerSkip) > 0 { - skip = callerSkip[0] - } - _, file, line, _ := runtime.Caller(skip + 1) - assertionFailure = fmt.Errorf("Assertion in callback at %s:%d failed:\n%s", file, line, message) - panic("stop execution") - }) - - defer func() { - if actualType.NumOut() == 0 { - if assertionFailure == nil { - values = []reflect.Value{reflect.Zero(reflect.TypeOf((*error)(nil)).Elem())} - } else { - values = []reflect.Value{reflect.ValueOf(assertionFailure)} - } - } else { - err = assertionFailure - } - if e := recover(); e != nil && assertionFailure == nil { - panic(e) - } - }() - - values = reflect.ValueOf(actualInput).Call([]reflect.Value{reflect.ValueOf(assertionCapturingGomega)}) - return - } - default: - 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()) - g.Fail(msg, offset+4) } return out @@ -121,6 +135,11 @@ func (assertion *AsyncAssertion) WithContext(ctx context.Context) types.AsyncAss return assertion } +func (assertion *AsyncAssertion) WithArguments(argsToForward ...interface{}) types.AsyncAssertion { + assertion.argsToForward = argsToForward + return assertion +} + func (assertion *AsyncAssertion) Should(matcher types.GomegaMatcher, optionalDescription ...interface{}) bool { assertion.g.THelper() vetOptionalDescription("Asynchronous assertion", optionalDescription...) @@ -145,32 +164,191 @@ func (assertion *AsyncAssertion) buildDescription(optionalDescription ...interfa return fmt.Sprintf(optionalDescription[0].(string), optionalDescription[1:]...) + "\n" } -func (assertion *AsyncAssertion) pollActual() (interface{}, error) { +func (assertion *AsyncAssertion) processReturnValues(values []reflect.Value) (interface{}, error, StopTryingError) { + var err error + var stopTrying StopTryingError + + if len(values) == 0 { + return nil, fmt.Errorf("No values were returned by the function passed to Gomega"), stopTrying + } + actual := values[0].Interface() + if stopTryingErr, ok := asStopTryingError(actual); ok { + stopTrying = stopTryingErr + } + for i, extraValue := range values[1:] { + extra := extraValue.Interface() + if extra == nil { + continue + } + if stopTryingErr, ok := asStopTryingError(extra); ok { + stopTrying = stopTryingErr + continue + } + zero := reflect.Zero(reflect.TypeOf(extra)).Interface() + if reflect.DeepEqual(extra, zero) { + continue + } + if err == nil { + err = fmt.Errorf("Unexpected non-nil/non-zero argument at index %d:\n\t<%T>: %#v", i+1, extra, extra) + } + } + return actual, err, stopTrying +} + +var gomegaType = reflect.TypeOf((*types.Gomega)(nil)).Elem() +var contextType = reflect.TypeOf(new(context.Context)).Elem() + +func (assertion *AsyncAssertion) invalidFunctionError(t reflect.Type) error { + return fmt.Errorf(`The function passed to %s had an invalid signature of %s. Functions passed to %s must either: + + (a) have return values or + (b) take a Gomega interface as their first argument and use that Gomega instance to make assertions. + +You can learn more at https://onsi.github.io/gomega/#eventually +`, assertion.asyncType, t, assertion.asyncType) +} + +func (assertion *AsyncAssertion) noConfiguredContextForFunctionError() error { + 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(). + +You can learn more at https://onsi.github.io/gomega/#eventually +`, assertion.asyncType, assertion.asyncType) +} + +func (assertion *AsyncAssertion) argumentMismatchError(t reflect.Type, numProvided int) error { + have := "have" + if numProvided == 1 { + have = "has" + } + 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. + +You can learn more at https://onsi.github.io/gomega/#eventually +`, assertion.asyncType, t, t.NumIn(), numProvided, have, assertion.asyncType) +} + +func (assertion *AsyncAssertion) buildActualPoller() (func() (interface{}, error, StopTryingError), error) { if !assertion.actualIsFunc { - return assertion.actualValue, nil + return func() (interface{}, error, StopTryingError) { return assertion.actual, nil, nil }, nil } + actualValue := reflect.ValueOf(assertion.actual) + actualType := reflect.TypeOf(assertion.actual) + numIn, numOut, isVariadic := actualType.NumIn(), actualType.NumOut(), actualType.IsVariadic() + + if numIn == 0 && numOut == 0 { + return nil, assertion.invalidFunctionError(actualType) + } else if numIn == 0 { + return func() (actual interface{}, err error, stopTrying StopTryingError) { + defer func() { + if e := recover(); e != nil { + if stopTryingErr, ok := asStopTryingError(e); ok { + stopTrying = stopTryingErr + } else { + panic(e) + } + } + }() - values, err := assertion.actualFunc() - if err != nil { - return nil, err + actual, err, stopTrying = assertion.processReturnValues(actualValue.Call([]reflect.Value{})) + return + }, nil + } + takesGomega, takesContext := actualType.In(0).Implements(gomegaType), actualType.In(0).Implements(contextType) + if takesGomega && numIn > 1 && actualType.In(1).Implements(contextType) { + takesContext = true + } + if takesContext && len(assertion.argsToForward) > 0 && reflect.TypeOf(assertion.argsToForward[0]).Implements(contextType) { + takesContext = false + } + if !takesGomega && numOut == 0 { + return nil, assertion.invalidFunctionError(actualType) } - extras := []interface{}{nil} - for _, value := range values[1:] { - extras = append(extras, value.Interface()) + if takesContext && assertion.ctx == nil { + return nil, assertion.noConfiguredContextForFunctionError() + } + + var assertionFailure error + inValues := []reflect.Value{} + if takesGomega { + inValues = append(inValues, reflect.ValueOf(NewGomega(assertion.g.DurationBundle).ConfigureWithFailHandler(func(message string, callerSkip ...int) { + skip := 0 + if len(callerSkip) > 0 { + skip = callerSkip[0] + } + _, file, line, _ := runtime.Caller(skip + 1) + assertionFailure = fmt.Errorf("Assertion in callback at %s:%d failed:\n%s", file, line, message) + panic("stop execution") + }))) + } + if takesContext { + inValues = append(inValues, reflect.ValueOf(assertion.ctx)) + } + for _, arg := range assertion.argsToForward { + inValues = append(inValues, reflect.ValueOf(arg)) + } + + if !isVariadic && numIn != len(inValues) { + return nil, assertion.argumentMismatchError(actualType, len(inValues)) + } else if isVariadic && len(inValues) < numIn-1 { + return nil, assertion.argumentMismatchError(actualType, len(inValues)) + } + + return func() (actual interface{}, err error, stopTrying StopTryingError) { + var values []reflect.Value + assertionFailure = nil + defer func() { + if numOut == 0 { + actual = assertionFailure + } else { + actual, err, stopTrying = assertion.processReturnValues(values) + if assertionFailure != nil { + err = assertionFailure + } + } + if e := recover(); e != nil { + if stopTryingErr, ok := asStopTryingError(e); ok { + stopTrying = stopTryingErr + } else if assertionFailure == nil { + panic(e) + } + } + }() + values = actualValue.Call(inValues) + return + }, nil +} + +func (assertion *AsyncAssertion) matcherSaysStopTrying(matcher types.GomegaMatcher, value interface{}) StopTryingError { + if assertion.actualIsFunc || types.MatchMayChangeInTheFuture(matcher, value) { + return nil } - success, message := vetActuals(extras, 0) - if !success { - return nil, errors.New(message) + return StopTrying("No future change is possible. Bailing out early") +} + +func (assertion *AsyncAssertion) afterTimeout() <-chan time.Time { + if assertion.timeoutInterval >= 0 { + return time.After(assertion.timeoutInterval) } - return values[0].Interface(), nil + if assertion.asyncType == AsyncAssertionTypeConsistently { + return time.After(assertion.g.DurationBundle.ConsistentlyDuration) + } else { + if assertion.ctx == nil { + return time.After(assertion.g.DurationBundle.EventuallyTimeout) + } else { + return nil + } + } } -func (assertion *AsyncAssertion) matcherMayChange(matcher types.GomegaMatcher, value interface{}) bool { - if assertion.actualIsFunc { - return true +func (assertion *AsyncAssertion) afterPolling() <-chan time.Time { + if assertion.pollingInterval >= 0 { + return time.After(assertion.pollingInterval) + } + if assertion.asyncType == AsyncAssertionTypeConsistently { + return time.After(assertion.g.DurationBundle.ConsistentlyPollingInterval) + } else { + return time.After(assertion.g.DurationBundle.EventuallyPollingInterval) } - return types.MatchMayChangeInTheFuture(matcher, value) } type contextWithAttachProgressReporter interface { @@ -179,21 +357,28 @@ type contextWithAttachProgressReporter interface { func (assertion *AsyncAssertion) match(matcher types.GomegaMatcher, desiredMatch bool, optionalDescription ...interface{}) bool { timer := time.Now() - timeout := time.After(assertion.timeoutInterval) + timeout := assertion.afterTimeout() lock := sync.Mutex{} var matches bool var err error - mayChange := true - value, err := assertion.pollActual() + assertion.g.THelper() + + pollActual, err := assertion.buildActualPoller() + if err != nil { + assertion.g.Fail(err.Error(), 2+assertion.offset) + return false + } + + value, err, stopTrying := pollActual() if err == nil { - mayChange = assertion.matcherMayChange(matcher, value) + if stopTrying == nil { + stopTrying = assertion.matcherSaysStopTrying(matcher, value) + } matches, err = matcher.Match(value) } - assertion.g.THelper() - messageGenerator := func() string { // can be called out of band by Ginkgo if the user requests a progress report lock.Lock() @@ -233,19 +418,27 @@ func (assertion *AsyncAssertion) match(matcher types.GomegaMatcher, desiredMatch return true } - if !mayChange { - fail("No future change is possible. Bailing out early") + if stopTrying != nil { + fail(stopTrying.Error() + " -") return false } select { - case <-time.After(assertion.pollingInterval): - v, e := assertion.pollActual() + case <-assertion.afterPolling(): + v, e, st := pollActual() + if st != nil && st.wasViaPanic() { + // we were told to stop trying via panic - which means we dont' have reasonable new values + // we should simply use the old values and exit now + fail(st.Error() + " -") + return false + } lock.Lock() - value, err = v, e + value, err, stopTrying = v, e, st lock.Unlock() if err == nil { - mayChange = assertion.matcherMayChange(matcher, value) + if stopTrying == nil { + stopTrying = assertion.matcherSaysStopTrying(matcher, value) + } matches, e = matcher.Match(value) lock.Lock() err = e @@ -266,18 +459,24 @@ func (assertion *AsyncAssertion) match(matcher types.GomegaMatcher, desiredMatch return false } - if !mayChange { + if stopTrying != nil { return true } select { - case <-time.After(assertion.pollingInterval): - v, e := assertion.pollActual() + case <-assertion.afterPolling(): + v, e, st := pollActual() + if st != nil && st.wasViaPanic() { + // we were told to stop trying via panic - which means we made it this far and should return successfully + return true + } lock.Lock() - value, err = v, e + value, err, stopTrying = v, e, st lock.Unlock() if err == nil { - mayChange = assertion.matcherMayChange(matcher, value) + if stopTrying == nil { + stopTrying = assertion.matcherSaysStopTrying(matcher, value) + } matches, e = matcher.Match(value) lock.Lock() err = e diff --git a/vendor/github.com/onsi/gomega/internal/gomega.go b/vendor/github.com/onsi/gomega/internal/gomega.go index 52a6b243..2d67f7f1 100644 --- a/vendor/github.com/onsi/gomega/internal/gomega.go +++ b/vendor/github.com/onsi/gomega/internal/gomega.go @@ -57,8 +57,8 @@ func (g *Gomega) Eventually(actual interface{}, intervals ...interface{}) types. } func (g *Gomega) EventuallyWithOffset(offset int, actual interface{}, args ...interface{}) types.AsyncAssertion { - timeoutInterval := g.DurationBundle.EventuallyTimeout - pollingInterval := g.DurationBundle.EventuallyPollingInterval + timeoutInterval := -time.Duration(1) + pollingInterval := -time.Duration(1) intervals := []interface{}{} var ctx context.Context for _, arg := range args { @@ -84,8 +84,8 @@ func (g *Gomega) Consistently(actual interface{}, intervals ...interface{}) type } func (g *Gomega) ConsistentlyWithOffset(offset int, actual interface{}, args ...interface{}) types.AsyncAssertion { - timeoutInterval := g.DurationBundle.ConsistentlyDuration - pollingInterval := g.DurationBundle.ConsistentlyPollingInterval + timeoutInterval := -time.Duration(1) + pollingInterval := -time.Duration(1) intervals := []interface{}{} var ctx context.Context for _, arg := range args { diff --git a/vendor/github.com/onsi/gomega/types/types.go b/vendor/github.com/onsi/gomega/types/types.go index 8f2998a5..b479e2e8 100644 --- a/vendor/github.com/onsi/gomega/types/types.go +++ b/vendor/github.com/onsi/gomega/types/types.go @@ -74,6 +74,7 @@ type AsyncAssertion interface { Within(timeout time.Duration) AsyncAssertion ProbeEvery(interval time.Duration) AsyncAssertion WithContext(ctx context.Context) AsyncAssertion + WithArguments(argsToForward ...interface{}) AsyncAssertion } // Assertions are returned by Ω and Expect and enable assertions against Gomega matchers diff --git a/vendor/modules.txt b/vendor/modules.txt index 6df9b103..868a1189 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -55,7 +55,7 @@ github.com/onsi/ginkgo/reporters/stenographer github.com/onsi/ginkgo/reporters/stenographer/support/go-colorable github.com/onsi/ginkgo/reporters/stenographer/support/go-isatty github.com/onsi/ginkgo/types -# github.com/onsi/gomega v1.21.1 +# github.com/onsi/gomega v1.22.1 github.com/onsi/gomega github.com/onsi/gomega/format github.com/onsi/gomega/internal