| /*Package poll provides tools for testing asynchronous code. |
| */ |
| package poll // import "gotest.tools/v3/poll" |
| |
| import ( |
| "fmt" |
| "strings" |
| "time" |
| |
| "gotest.tools/v3/assert/cmp" |
| "gotest.tools/v3/internal/assert" |
| ) |
| |
| // TestingT is the subset of [testing.T] used by [WaitOn] |
| type TestingT interface { |
| LogT |
| Fatalf(format string, args ...interface{}) |
| } |
| |
| // LogT is a logging interface that is passed to the [WaitOn] check function |
| type LogT interface { |
| Log(args ...interface{}) |
| Logf(format string, args ...interface{}) |
| } |
| |
| type helperT interface { |
| Helper() |
| } |
| |
| // Settings are used to configure the behaviour of [WaitOn] |
| type Settings struct { |
| // Timeout is the maximum time to wait for the condition. Defaults to 10s. |
| Timeout time.Duration |
| // Delay is the time to sleep between checking the condition. Defaults to |
| // 100ms. |
| Delay time.Duration |
| } |
| |
| func defaultConfig() *Settings { |
| return &Settings{Timeout: 10 * time.Second, Delay: 100 * time.Millisecond} |
| } |
| |
| // SettingOp is a function which accepts and modifies Settings |
| type SettingOp func(config *Settings) |
| |
| // WithDelay sets the delay to wait between polls |
| func WithDelay(delay time.Duration) SettingOp { |
| return func(config *Settings) { |
| config.Delay = delay |
| } |
| } |
| |
| // WithTimeout sets the timeout |
| func WithTimeout(timeout time.Duration) SettingOp { |
| return func(config *Settings) { |
| config.Timeout = timeout |
| } |
| } |
| |
| // Result of a check performed by [WaitOn] |
| type Result interface { |
| // Error indicates that the check failed and polling should stop, and the |
| // the has failed |
| Error() error |
| // Done indicates that polling should stop, and the test should proceed |
| Done() bool |
| // Message provides the most recent state when polling has not completed |
| Message() string |
| } |
| |
| type result struct { |
| done bool |
| message string |
| err error |
| } |
| |
| func (r result) Done() bool { |
| return r.done |
| } |
| |
| func (r result) Message() string { |
| return r.message |
| } |
| |
| func (r result) Error() error { |
| return r.err |
| } |
| |
| // Continue returns a [Result] that indicates to [WaitOn] that it should continue |
| // polling. The message text will be used as the failure message if the timeout |
| // is reached. |
| func Continue(message string, args ...interface{}) Result { |
| return result{message: fmt.Sprintf(message, args...)} |
| } |
| |
| // Success returns a [Result] where Done() returns true, which indicates to [WaitOn] |
| // that it should stop polling and exit without an error. |
| func Success() Result { |
| return result{done: true} |
| } |
| |
| // Error returns a [Result] that indicates to [WaitOn] that it should fail the test |
| // and stop polling. |
| func Error(err error) Result { |
| return result{err: err} |
| } |
| |
| // WaitOn a condition or until a timeout. Poll by calling check and exit when |
| // check returns a done Result. To fail a test and exit polling with an error |
| // return a error result. |
| func WaitOn(t TestingT, check Check, pollOps ...SettingOp) { |
| if ht, ok := t.(helperT); ok { |
| ht.Helper() |
| } |
| config := defaultConfig() |
| for _, pollOp := range pollOps { |
| pollOp(config) |
| } |
| |
| var lastMessage string |
| after := time.After(config.Timeout) |
| chResult := make(chan Result) |
| for { |
| go func() { |
| chResult <- check(t) |
| }() |
| select { |
| case <-after: |
| if lastMessage == "" { |
| lastMessage = "first check never completed" |
| } |
| t.Fatalf("timeout hit after %s: %s", config.Timeout, lastMessage) |
| case result := <-chResult: |
| switch { |
| case result.Error() != nil: |
| t.Fatalf("polling check failed: %s", result.Error()) |
| case result.Done(): |
| return |
| } |
| time.Sleep(config.Delay) |
| lastMessage = result.Message() |
| } |
| } |
| } |
| |
| // Compare values using the [cmp.Comparison]. If the comparison fails return a |
| // result which indicates to WaitOn that it should continue waiting. |
| // If the comparison is successful then [WaitOn] stops polling. |
| func Compare(compare cmp.Comparison) Result { |
| buf := new(logBuffer) |
| if assert.RunComparison(buf, assert.ArgsAtZeroIndex, compare) { |
| return Success() |
| } |
| return Continue(buf.String()) |
| } |
| |
| type logBuffer struct { |
| log [][]interface{} |
| } |
| |
| func (c *logBuffer) Log(args ...interface{}) { |
| c.log = append(c.log, args) |
| } |
| |
| func (c *logBuffer) String() string { |
| b := new(strings.Builder) |
| for _, item := range c.log { |
| b.WriteString(fmt.Sprint(item...) + " ") |
| } |
| return b.String() |
| } |