Golang testing — gotest.tools poll
Sat, 23 March, 2019 (700 Words)Let’s continue the gotest.tools
serie, this time with the poll
package.
Package poll provides tools for testing asynchronous code.
When you write test, you may test a piece of code that work asynchronously, where the state you’re expecting is gonna take a bit of time to be achieved. This is especially true when you work on networking or file-system code. And this happens a lot when you write integration (or end-to-end) test, less for unit-tests.
The package poll
is trying to tackle those use cases. We’ll first take a look at the
main function, WaitOn
, then how to write a Check
, using the Result
type.
WaitOn
Let’s look into the main poll
function : `WaitOn`.
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.
In a gist, WaitOn
will run a condition function until it either times out or
succeed. It wait for a given time/delay between each run.
func WaitOn(t TestingT, check Check, pollOps ...SettingOp) {
// […]
}
As any testing helper function, the first argument is *testing.T
(or, in this case,
any thing that look like it, thanks to the TestingT
interace). The two other arguments
are way more interesting :
- The
Check
is the condition that will run multiple times until it either timeout, or succeed. - The
SettingOp(s)
which are options to configure the function, things like the timeout, or the delay between each run.
The settings are pretty straightforward :
WithDelay
: sets the delay to wait between polls. The default delay is 100ms.WithTimeout
: sets the timeout. The default timeout is 10s.
There is existing Check
for common case:
Connection
: try to open a connection to the address on the named network.poll.WaitOn(t, poll.Connection("tcp", "foo.bar:55555"), poll.WithTimeout("5s"))
FileExists
: looks on filesystem and check that path exists.poll.WaitOn(t, poll.FileExists("/should/be/created"), poll.WithDelay("1s"))
Check
and Result
Connection
and FileExists
are the only two built-in Check
provided by
gotest.tools
. They are useful, but as usual, where gotest.tools
shines is
extensiblity. It is really easy to define your own Check
.
type Check func(t LogT) Result
A Check
is, thus, only a function that takes LogT
— which is anything that can log
something, like *testing.T
— and return a Result
. Let’s look at this intersting
Result
type.
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
}
Although it’s an interface, the poll
package defines built-in Result
so that it’s easy
to write Check
without having to define you Result
type.
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.Success
returns a Result where Done() returns true, which indicates to WaitOn that it should stop polling and exit without an error.Error
returns a Result that indicates to WaitOn that it should fail the test and stop polling.
The basic just to write a Check
is then :
- if the state is not there yet, return
Continue
, - if there is an error, unrelated to validating the state, return an
Error
, - if the state is there, return
Success
.
Let’s look at an example taken from the moby/moby
source code.
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
func IsInState(ctx context.Context, client client.APIClient, containerID string, state ...string) func(log poll.LogT) poll.Result {
return func(log poll.LogT) poll.Result {
inspect, err := client.ContainerInspect(ctx, containerID)
if err != nil {
return poll.Error(err)
}
for _, v := range state {
if inspect.State.Status == v {
return poll.Success()
}
}
return poll.Continue("waiting for container to be one of (%s), currently %s", strings.Join(state, ", "), inspect.State.Status)
}
}
Conclusion
… that’s a wrap. The poll
package allows to easily wait for a condition to happen in a
given time-frame — with sane defaults. As for most of the gotest.tools
package, we use
this package heavily in docker/*
projects too…