| /*Package poll provides tools for testing asynchronous code. |
| */ |
| package poll |
| |
| import ( |
| "fmt" |
| "time" |
| ) |
| |
| // 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. Detaults to |
| // 1ms |
| Delay time.Duration |
| } |
| |
| func defaultConfig() *Settings { |
| return &Settings{Timeout: 10 * time.Second, Delay: 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 func(t LogT) Result, 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() |
| } |
| } |
| } |