type FailureError … func (f FailureError) Error() string { … } func (f FailureError) Backtrace() string { … } func (f FailureError) Is(target error) bool { … } var ErrFailure … func expect(tCtx TContext, actual interface{ … } func expectNoError(tCtx TContext, err error, explain ...interface{ … } func buildDescription(explain ...interface{ … } // Eventually wraps [gomega.Eventually] such that a failure will be reported via // TContext.Fatal. // // In contrast to [gomega.Eventually], the parameter is strongly typed. It must // accept a TContext as first argument and return one value, the one which is // then checked with the matcher. // // In contrast to direct usage of [gomega.Eventually], make additional // assertions inside the callback is okay as long as they use the TContext that // is passed in. For example, errors can be checked with ExpectNoError: // // cb := func(func(tCtx ktesting.TContext) int { // value, err := doSomething(...) // tCtx.ExpectNoError(err, "something failed") // assert(tCtx, 42, value, "the answer") // return value // } // tCtx.Eventually(cb).Should(gomega.Equal(42), "should be the answer to everything") // // If there is no value, then an error can be returned: // // cb := func(func(tCtx ktesting.TContext) error { // err := doSomething(...) // return err // } // tCtx.Eventually(cb).Should(gomega.Succeed(), "foobar should succeed") // // The default Gomega poll interval and timeout are used. Setting a specific // timeout may be useful: // // tCtx.Eventually(cb).Timeout(5 * time.Second).Should(gomega.Succeed(), "foobar should succeed") // // Canceling the context in the callback only affects code in the callback. The // context passed to Eventually is not getting canceled. To abort polling // immediately because the expected condition is known to not be reached // anymore, use [gomega.StopTrying]: // // cb := func(func(tCtx ktesting.TContext) int { // value, err := doSomething(...) // if errors.Is(err, SomeFinalErr) { // // This message completely replaces the normal // // failure message and thus should include all // // relevant information. // // // // github.com/onsi/gomega/format is a good way // // to format arbitrary data. It uses indention // // and falls back to YAML for Kubernetes API // // structs for readability. // gomega.StopTrying("permanent failure, last value:\n%s", format.Object(value, 1 /* indent one level */)). // Wrap(err).Now() // } // ktesting.ExpectNoError(tCtx, err, "something failed") // return value // } // tCtx.Eventually(cb).Should(gomega.Equal(42), "should be the answer to everything") // // To poll again after some specific timeout, use [gomega.TryAgainAfter]. This is // particularly useful in [Consistently] to ignore some intermittent error. // // cb := func(func(tCtx ktesting.TContext) int { // value, err := doSomething(...) // var intermittentErr SomeIntermittentError // if errors.As(err, &intermittentErr) { // gomega.TryAgainAfter(intermittentErr.RetryPeriod).Wrap(err).Now() // } // ktesting.ExpectNoError(tCtx, err, "something failed") // return value // } // tCtx.Eventually(cb).Should(gomega.Equal(42), "should be the answer to everything") func Eventually[T any](tCtx TContext, cb func(TContext) T) gomega.AsyncAssertion { … } // Consistently wraps [gomega.Consistently] the same way as [Eventually] wraps // [gomega.Eventually]. func Consistently[T any](tCtx TContext, cb func(TContext) T) gomega.AsyncAssertion { … }