2019-03-23-gotest-tools-poll.html (8770B)
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <!-- Sep 03, 2024 --> 5 <meta charset="utf-8" /> 6 <meta name="viewport" content="width=device-width, initial-scale=1" /> 7 <title>Golang testing — gotest.tools poll</title> 8 <meta name="author" content="Vincent Demeester" /> 9 <meta name="generator" content="Org Mode" /> 10 <link rel='icon' type='image/x-icon' href='/images/favicon.ico'/> 11 <meta name='viewport' content='width=device-width, initial-scale=1'> 12 <link rel='stylesheet' href='/css/new.css' type='text/css'/> 13 <link rel='stylesheet' href='/css/syntax.css' type='text/css'/> 14 <link href='/index.xml' rel='alternate' type='application/rss+xml' title='Vincent Demeester' /> 15 </head> 16 <body> 17 <main id="content" class="content"> 18 <header> 19 <h1 class="title">Golang testing — gotest.tools poll</h1> 20 </header><nav id="table-of-contents" role="doc-toc"> 21 <h2>Table of Contents</h2> 22 <div id="text-table-of-contents" role="doc-toc"> 23 <ul> 24 <li><a href="#Introduction">Introduction</a></li> 25 <li><a href="#~WaitOn~"><code>WaitOn</code></a></li> 26 <li><a href="#~Check~%20and%20~Result~"><code>Check</code> and <code>Result</code></a></li> 27 <li><a href="#Conclusion">Conclusion</a></li> 28 </ul> 29 </div> 30 </nav> 31 <section id="outline-container-Introduction" class="outline-2"> 32 <h2 id="Introduction">Introduction</h2> 33 <div class="outline-text-2" id="text-Introduction"> 34 <p> 35 Let’s continue the <a href="https://gotest.tools"><code>gotest.tools</code></a> serie, this time with the <code>poll</code> package. 36 </p> 37 38 <blockquote> 39 <p> 40 Package poll provides tools for testing asynchronous code. 41 </p> 42 </blockquote> 43 44 <p> 45 When you write test, you may test a piece of code that work asynchronously, where the 46 state you’re expecting is gonna take a bit of time to be achieved. This is especially true 47 when you work on networking or file-system code. And this happens a lot when you write 48 integration (or end-to-end) test, less for unit-tests. 49 </p> 50 51 <p> 52 The package <code>poll</code> is trying to tackle those use cases. We’ll first take a look at the 53 main function, <code>WaitOn</code>, then how to write a <code>Check</code>, using the <code>Result</code> type. 54 </p> 55 </div> 56 </section> 57 <section id="outline-container-~WaitOn~" class="outline-2"> 58 <h2 id="~WaitOn~"><code>WaitOn</code></h2> 59 <div class="outline-text-2" id="text-~WaitOn~"> 60 <p> 61 Let’s look into the main <code>poll</code> function : `WaitOn`. 62 </p> 63 64 <blockquote> 65 <p> 66 WaitOn a condition or until a timeout. Poll by calling check and exit when check returns 67 a done Result. To fail a test and exit polling with an error return a error result. 68 </p> 69 </blockquote> 70 71 <p> 72 In a gist, <code>WaitOn</code> will run a <i>condition</i> function until it either times out or 73 succeed. It wait for a given time/delay between each run. 74 </p> 75 76 <div class="org-src-container"> 77 <pre class="src src-go">func WaitOn(t TestingT, check Check, pollOps ...SettingOp) { 78 // […] 79 } 80 </pre> 81 </div> 82 83 <p> 84 As any <i>testing helper</i> function, the first argument is <code>*testing.T</code> (or, in this case, 85 any thing that look like it, thanks to the <code>TestingT</code> interace). The two other arguments 86 are way more interesting : 87 </p> 88 89 <ul class="org-ul"> 90 <li>The <code>Check</code> is the condition that will run multiple times until it either timeout, or succeed.</li> 91 <li>The <code>SettingOp(s)</code> which are options to configure the function, things like the timeout, 92 or the <i>delay</i> between each run.</li> 93 </ul> 94 95 <p> 96 The settings are pretty straightforward : 97 </p> 98 99 <ul class="org-ul"> 100 <li><code>WithDelay</code> : sets the delay to wait between polls. The default delay is 100ms.</li> 101 <li><code>WithTimeout</code> : sets the timeout. The default timeout is 10s.</li> 102 </ul> 103 104 <p> 105 There is existing <code>Check</code> for common case: 106 </p> 107 108 <ul class="org-ul"> 109 <li><p> 110 <code>Connection</code> : try to open a connection to the address on the named network. 111 </p> 112 113 <div class="org-src-container"> 114 <pre class="src src-go">poll.WaitOn(t, poll.Connection("tcp", "foo.bar:55555"), poll.WithTimeout("5s")) 115 </pre> 116 </div></li> 117 118 <li><p> 119 <code>FileExists</code> : looks on filesystem and check that path exists. 120 </p> 121 122 <div class="org-src-container"> 123 <pre class="src src-go">poll.WaitOn(t, poll.FileExists("/should/be/created"), poll.WithDelay("1s")) 124 </pre> 125 </div></li> 126 </ul> 127 </div> 128 </section> 129 <section id="outline-container-~Check~%20and%20~Result~" class="outline-2"> 130 <h2 id="~Check~%20and%20~Result~"><code>Check</code> and <code>Result</code></h2> 131 <div class="outline-text-2" id="text-~Check~%20and%20~Result~"> 132 <p> 133 <code>Connection</code> and <code>FileExists</code> are the only two <i>built-in</i> <code>Check</code> provided by 134 <code>gotest.tools</code>. They are useful, but as usual, where <code>gotest.tools</code> shines is 135 extensiblity. It is really easy to define your own <code>Check</code>. 136 </p> 137 138 <div class="org-src-container"> 139 <pre class="src src-go">type Check func(t LogT) Result 140 </pre> 141 </div> 142 143 <p> 144 A <code>Check</code> is, thus, only a function that takes <code>LogT</code> — which is anything that can log 145 something, like <code>*testing.T</code> — and return a <code>Result</code>. Let’s look at this intersting 146 <code>Result</code> type. 147 </p> 148 149 <div class="org-src-container"> 150 <pre class="src src-go">type Result interface { 151 // Error indicates that the check failed and polling should stop, and the 152 // the has failed 153 Error() error 154 // Done indicates that polling should stop, and the test should proceed 155 Done() bool 156 // Message provides the most recent state when polling has not completed 157 Message() string 158 } 159 </pre> 160 </div> 161 162 <p> 163 Although it’s an interface, the <code>poll</code> package defines built-in <code>Result</code> so that it’s easy 164 to write <code>Check</code> without having to define you <code>Result</code> type. 165 </p> 166 167 <ul class="org-ul"> 168 <li><code>Continue</code> returns a Result that indicates to WaitOn that it should continue 169 polling. The message text will be used as the failure message if the timeout is reached.</li> 170 <li><code>Success</code> returns a Result where Done() returns true, which indicates to WaitOn that it 171 should stop polling and exit without an error.</li> 172 <li><code>Error</code> returns a Result that indicates to WaitOn that it should fail the test and stop 173 polling.</li> 174 </ul> 175 176 <p> 177 The basic just to write a <code>Check</code> is then : 178 </p> 179 180 <ul class="org-ul"> 181 <li>if the state is not there yet, return <code>Continue</code>,</li> 182 <li>if there is an error, unrelated to validating the state, return an <code>Error</code>,</li> 183 <li>if the state is there, return <code>Success</code>.</li> 184 </ul> 185 186 <p> 187 Let’s look at an example taken from the <code>moby/moby</code> source code. 188 </p> 189 190 <div class="org-src-container"> 191 <pre class="src src-go">poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) 192 193 func IsInState(ctx context.Context, client client.APIClient, containerID string, state ...string) func(log poll.LogT) poll.Result { 194 return func(log poll.LogT) poll.Result { 195 inspect, err := client.ContainerInspect(ctx, containerID) 196 if err != nil { 197 return poll.Error(err) 198 } 199 for _, v := range state { 200 if inspect.State.Status == v { 201 return poll.Success() 202 } 203 } 204 return poll.Continue("waiting for container to be one of (%s), currently %s", strings.Join(state, ", "), inspect.State.Status) 205 } 206 } 207 </pre> 208 </div> 209 </div> 210 </section> 211 <section id="outline-container-Conclusion" class="outline-2"> 212 <h2 id="Conclusion">Conclusion</h2> 213 <div class="outline-text-2" id="text-Conclusion"> 214 <p> 215 … that’s a wrap. The <code>poll</code> package allows to easily wait for a condition to happen in a 216 given time-frame — with sane defaults. As for most of the <code>gotest.tools</code> package, we use 217 this package heavily in <code>docker/*</code> projects too… 218 </p> 219 </div> 220 </section> 221 </main> 222 <footer id="postamble" class="status"> 223 <footer> 224 <small><a href="/" rel="history">Index</a> • <a href="/sitemap.html">Sitemap</a> • <a href="https://dl.sbr.pm/">Files</a></small><br/> 225 <small class='questions'>Questions, comments ? Please use my <a href="https://lists.sr.ht/~vdemeester/public-inbox">public inbox</a> by sending a plain-text email to <a href="mailto:~vdemeester/public-inbox@lists.sr.ht">~vdemeester/public-inbox@lists.sr.ht</a>.</small><br/> 226 <small class='copyright'> 227 Content and design by Vincent Demeester 228 (<a rel='licence' href='http://creativecommons.org/licenses/by-nc-sa/3.0/'>Some rights reserved</a>) 229 </small><br /> 230 </footer> 231 </footer> 232 </body> 233 </html>