home

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

2019-03-23-gotest-tools-poll.org (5193B)


      1 #+TITLE: Golang testing — gotest.tools poll
      2 #+date: <2019-03-23 Sat>
      3 #+filetags: go testing poll
      4 
      5 #+TOC: headlines 2
      6 
      7 * Introduction
      8 Let's continue the [[https://gotest.tools][=gotest.tools=]] serie, this time with the =poll= package.
      9 
     10 #+BEGIN_QUOTE
     11 Package poll provides tools for testing asynchronous code.
     12 #+END_QUOTE
     13 
     14 When you write test, you may test a piece of code that work asynchronously, where the
     15 state you're expecting is gonna take a bit of time to be achieved. This is especially true
     16 when you work on networking or file-system code. And this happens a lot when you write
     17 integration (or end-to-end) test, less for unit-tests.
     18 
     19 The package =poll= is trying to tackle those use cases. We'll first take a look at the
     20 main function, =WaitOn=, then how to write a ~Check~, using the ~Result~ type.
     21 
     22 * ~WaitOn~
     23 
     24 Let's look into the main ~poll~ function : `WaitOn`.
     25 
     26 #+begin_quote
     27  WaitOn a condition or until a timeout. Poll by calling check and exit when check returns
     28  a done Result. To fail a test and exit polling with an error return a error result.
     29 #+end_quote
     30 
     31 In a gist, ~WaitOn~ will run a /condition/ function until it either times out or
     32 succeed. It wait for a given time/delay between each run.
     33 
     34 #+begin_src go
     35   func WaitOn(t TestingT, check Check, pollOps ...SettingOp) {
     36           // […]
     37   }
     38 #+end_src
     39 
     40 As any /testing helper/ function, the first argument is ~*testing.T~ (or, in this case,
     41 any thing that look like it, thanks to the ~TestingT~ interace). The two other arguments
     42 are way more interesting :
     43 
     44 - The ~Check~ is the condition that will run multiple times until it either timeout, or succeed.
     45 - The ~SettingOp(s)~ which are options to configure the function, things like the timeout,
     46   or the /delay/ between each run.
     47 
     48 The settings are pretty straightforward :
     49 
     50 - ~WithDelay~ : sets the delay to wait between polls. The default delay is 100ms.
     51 - ~WithTimeout~ : sets the timeout. The default timeout is 10s.
     52 
     53 There is existing ~Check~ for common case:
     54 
     55 - ~Connection~ : try to open a connection to the address on the named network.
     56 
     57   #+begin_src go
     58     poll.WaitOn(t, poll.Connection("tcp", "foo.bar:55555"), poll.WithTimeout("5s"))
     59   #+end_src
     60 
     61 - ~FileExists~ : looks on filesystem and check that path exists.
     62 
     63   #+begin_src go
     64     poll.WaitOn(t, poll.FileExists("/should/be/created"), poll.WithDelay("1s"))
     65   #+end_src
     66 
     67 
     68 * ~Check~ and ~Result~
     69 
     70 ~Connection~ and ~FileExists~ are the only two /built-in/ ~Check~ provided by
     71 ~gotest.tools~. They are useful, but as usual, where ~gotest.tools~ shines is
     72 extensiblity. It is really easy to define your own ~Check~.
     73 
     74 #+begin_src go
     75   type Check func(t LogT) Result
     76 #+end_src
     77 
     78 A ~Check~ is, thus, only a function that takes ~LogT~ — which is anything that can log
     79 something, like ~*testing.T~ — and return a ~Result~. Let's look at this intersting
     80 ~Result~ type.
     81 
     82 #+begin_src go
     83   type Result interface {
     84       // Error indicates that the check failed and polling should stop, and the
     85       // the has failed
     86       Error() error
     87       // Done indicates that polling should stop, and the test should proceed
     88       Done() bool
     89       // Message provides the most recent state when polling has not completed
     90       Message() string
     91   }
     92 #+end_src
     93 
     94 Although it's an interface, the ~poll~ package defines built-in ~Result~ so that it's easy
     95 to write ~Check~ without having to define you ~Result~ type.
     96 
     97 - ~Continue~ returns a Result that indicates to WaitOn that it should continue
     98   polling. The message text will be used as the failure message if the timeout is reached.
     99 - ~Success~ returns a Result where Done() returns true, which indicates to WaitOn that it
    100   should stop polling and exit without an error.
    101 - ~Error~ returns a Result that indicates to WaitOn that it should fail the test and stop
    102   polling.
    103 
    104 The basic just to write a ~Check~ is then :
    105 
    106 - if the state is not there yet, return ~Continue~,
    107 - if there is an error, unrelated to validating the state, return an ~Error~,
    108 - if the state is there, return ~Success~.
    109 
    110 Let's look at an example taken from the ~moby/moby~ source code.
    111 
    112 #+begin_src go
    113   poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
    114 
    115   func IsInState(ctx context.Context, client client.APIClient, containerID string, state ...string) func(log poll.LogT) poll.Result {
    116           return func(log poll.LogT) poll.Result {
    117                   inspect, err := client.ContainerInspect(ctx, containerID)
    118                   if err != nil {
    119                           return poll.Error(err)
    120                   }
    121                   for _, v := range state {
    122                           if inspect.State.Status == v {
    123                                   return poll.Success()
    124                           }
    125                   }
    126                   return poll.Continue("waiting for container to be one of (%s), currently %s", strings.Join(state, ", "), inspect.State.Status)
    127           }
    128   }
    129 #+end_src
    130 
    131 
    132 * Conclusion
    133 
    134 … that's a wrap. The =poll= package allows to easily wait for a condition to happen in a
    135 given time-frame — with sane defaults. As for most of the ~gotest.tools~ package, we use
    136 this package heavily in =docker/*= projects too…