2018-09-18-gotest-tools-icmd.html (12388B)
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 icmd</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 icmd</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="#Create%20and%20run%20a%20command">Create and run a command</a></li> 26 <li><a href="#Assertions">Assertions</a></li> 27 <li><a href="#Conclusion%E2%80%A6">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>icmd</code> package. 36 </p> 37 38 <blockquote> 39 <p> 40 Package icmd executes binaries and provides convenient assertions for testing the results. 41 </p> 42 </blockquote> 43 44 <p> 45 After file-system operations (seen in <a href="file:///posts/2018-09-14-gotest-tools-fs/"><code>fs</code></a>), another common use-case in tests is to 46 <b>execute a command</b>. The reasons can be you’re testing the <code>cli</code> you’re currently writing 47 or you need to setup something using a command line. A classic execution in a test might 48 lookup like the following. 49 </p> 50 51 <div class="org-src-container"> 52 <pre class="src src-go">cmd := exec.Command("echo", "foo") 53 cmd.Stout = &stdout 54 cmd.Env = env 55 if err := cmd.Run(); err != nil { 56 t.Fatal(err) 57 } 58 if string(stdout) != "foo" { 59 t.Fatalf("expected: foo, got %s", string(stdout)) 60 } 61 </pre> 62 </div> 63 64 <p> 65 The package <code>icmd</code> is there to ease your pain (as usual 😉) — we used <i>the name <code>icmd</code></i> 66 instead of <code>cmd</code> because it’s a pretty common identifier in Go source code, thus would be 67 really easy to <i>shadow</i> — and have some really weird problems going on. 68 </p> 69 70 <p> 71 The usual <code>icmd</code> workflow is the following: 72 </p> 73 74 <ol class="org-ol"> 75 <li>Describe the command you want to execute using : type <code>Cmd</code>, function <code>Command</code> and 76 <code>CmdOp</code> operators.</li> 77 <li>Run it using : function <code>RunCmd</code> or <code>RunCommand</code> (that does 1. for you). You can also 78 use <code>StartCmd</code> and <code>WaitOnCmd</code> if you want more control on the execution workflow.</li> 79 <li>Check the result using the <code>Assert</code>, <code>Equal</code> or <code>Compare</code> methods attached to the 80 <code>Result</code> struct that the command execution return.</li> 81 </ol> 82 </div> 83 </section> 84 <section id="outline-container-Create%20and%20run%20a%20command" class="outline-2"> 85 <h2 id="Create%20and%20run%20a%20command">Create and run a command</h2> 86 <div class="outline-text-2" id="text-Create%20and%20run%20a%20command"> 87 <p> 88 Let’s first dig how to create commands. In this part, the assumption here is that the 89 command is successful, so we’ll have <code>.Assert(t, icmd.Success)</code> for now — we’ll learn more 90 about <code>Assert</code> in the next section 👼. 91 </p> 92 93 <p> 94 The simplest way to create and run a command is using <code>RunCommand</code>, it has the same 95 signature as <code>os/exec.Command</code>. A simple command execution goes as below. 96 </p> 97 98 <div class="org-src-container"> 99 <pre class="src src-go">icmd.RunCommand("echo", "foo").Assert(t, icmd.Sucess) 100 </pre> 101 </div> 102 103 <p> 104 Sometimes, you need to customize the command a bit more, like adding some environment 105 variable. In those case, you are going to use <code>RunCmd</code>, it takes a <code>Cmd</code> and operators. 106 Let’s look at those functions. 107 </p> 108 109 <div class="org-src-container"> 110 <pre class="src src-go">func RunCmd(cmd Cmd, cmdOperators ...CmdOp) *Result 111 112 func Command(command string, args ...string) Cmd 113 114 type Cmd struct { 115 Command []string 116 Timeout time.Duration 117 Stdin io.Reader 118 Stdout io.Writer 119 Dir string 120 Env []string 121 } 122 </pre> 123 </div> 124 125 <p> 126 As we’ve seen <a href="file:///posts/2017-01-01-go-testing-functionnal-builders/">multiple</a> <a href="file:///posts/2018-08-16-gotest-tools-assertions/">times</a> <a href="file:///posts/2018-09-14-gotest-tools-fs/">before</a>, it uses the <i>powerful</i> functional arguments. At the 127 time I wrote this post, the <code>icmd</code> package doesn’t contains too much <code>CmdOp</code> <sup><a id="fnr.1" class="footref" href="#fn.1" role="doc-backlink">1</a></sup>, so I’ll 128 propose two version for each example : one with <code>CmdOpt</code> present in <a href="https://github.com/gotestyourself/gotest.tools/pull/122">this PR</a> and one 129 without them. 130 </p> 131 132 <div class="org-src-container"> 133 <pre class="src src-go">// With 134 icmd.RunCmd(icmd.Command("sh", "-c", "echo $FOO"), 135 icmd.WithEnv("FOO=bar", "BAR=baz"), icmd.Dir("/tmp"), 136 icmd.WithTimeout(10*time.Second), 137 ).Assert(t, icmd.Success) 138 139 // Without 140 icmd.RunCmd(icmd.Cmd{ 141 Command: []string{"sh", "-c", "echo $FOO"}, 142 Env: []string{"FOO=bar", "BAR=baz"}, 143 Dir: "/tmp", 144 Timeout: 10*time.Second, 145 }).Assert(t, icmd.Success) 146 </pre> 147 </div> 148 149 <p> 150 As usual, the intent is clear, it’s simple to read and composable (with <code>CmdOp</code>’s). 151 </p> 152 </div> 153 </section> 154 <section id="outline-container-Assertions" class="outline-2"> 155 <h2 id="Assertions">Assertions</h2> 156 <div class="outline-text-2" id="text-Assertions"> 157 <p> 158 Let’s dig into the assertion part of <code>icmd</code>. Running a command returns a struct 159 <code>Result</code>. It has the following methods : 160 </p> 161 162 <ul class="org-ul"> 163 <li><code>Assert</code> compares the Result against the Expected struct, and fails the test if any of 164 the expectations are not met.</li> 165 <li><code>Compare</code> compares the result to Expected and return an error if they do not match.</li> 166 <li><code>Equal</code> compares the result to Expected. If the result doesn’t match expected 167 returns a formatted failure message with the command, stdout, stderr, exit code, and any 168 failed expectations. It returns an <code>assert.Comparison</code> struct, that can be used by other 169 <code>gotest.tools</code>.</li> 170 <li><code>Combined</code> returns the stdout and stderr combined into a single string.</li> 171 <li><code>Stderr</code> returns the stderr of the process as a string.</li> 172 <li><code>Stdout</code> returns the stdout of the process as a string.</li> 173 </ul> 174 175 <p> 176 When you have a result, you, most likely want to do two things : 177 </p> 178 179 <ul class="org-ul"> 180 <li><i>assert</i> that the command succeed or failed with some specific values (exit code, 181 stderr, stdout)</li> 182 <li>use the output — most likely <code>stdout</code> but maybe <code>stderr</code> — in the rest of the test.</li> 183 </ul> 184 185 <p> 186 As seen above, <i>asserting</i> the command result is using the <code>Expected</code> struct. 187 </p> 188 189 <div class="org-src-container"> 190 <pre class="src src-go">type Expected struct { 191 ExitCode int // the exit code the command returned 192 Timeout bool // did it timeout ? 193 Error string // error returned by the execution (os/exe) 194 Out string // content of stdout 195 Err string // content of stderr 196 } 197 </pre> 198 </div> 199 200 <p> 201 <code>Success</code> is a constant that defines a success — it’s an exit code of <code>0</code>, didn’t timeout, 202 no error. There is also the <code>None</code> constant, that should be used for <code>Out</code> or <code>Err</code>, to 203 specify that we don’t want any content for those standard outputs. 204 </p> 205 206 <div class="org-src-container"> 207 <pre class="src src-go">icmd.RunCmd(icmd.Command("cat", "/does/not/exist")).Assert(t, icmd.Expected{ 208 ExitCode: 1, 209 Err: "cat: /does/not/exist: No such file or directory", 210 }) 211 212 // In case of success, we may want to do something with the result 213 result := icmd.RunCommand("cat", "/does/exist") 214 result.Assert(t, icmd.Success) 215 // Read the output line by line 216 scanner := bufio.NewScanner(strings.NewReader(result.Stdout())) 217 for scanner.Scan() { 218 // Do something with it 219 } 220 </pre> 221 </div> 222 223 <p> 224 If the <code>Result</code> doesn’t map the <code>Expected</code>, a test failure will happen with a useful 225 message that will contains the executed command and what differs between the result and 226 the expectation. 227 </p> 228 229 <div class="org-src-container"> 230 <pre class="src src-go">result := icmd.RunCommand(…) 231 result.Assert(t, icmd.Expected{ 232 ExitCode: 101, 233 Out: "Something else", 234 Err: None, 235 }) 236 // Command: binary arg1 237 // ExitCode: 99 (timeout) 238 // Error: exit code 99 239 // Stdout: the output 240 // Stderr: the stderr 241 // 242 // Failures: 243 // ExitCode was 99 expected 101 244 // Expected command to finish, but it hit the timeout 245 // Expected stdout to contain "Something else" 246 // Expected stderr to contain "[NOTHING]" 247 … 248 </pre> 249 </div> 250 251 <p> 252 Finally, we listed <code>Equal</code> above, that returns a <code>Comparison</code> struct. This means we can 253 use it easily with the <code>assert</code> package. As written in a <a href="file:///posts/2018-08-16-gotest-tools-assertions/">previous post (about the <code>assert</code> 254 package)</a>, I tend prefer to use <code>cmp.Comparison</code>. Let’s convert the above examples using 255 <code>assert</code>. 256 </p> 257 258 <div class="org-src-container"> 259 <pre class="src src-go">result := icmd.RunCmd(icmd.Command("cat", "/does/not/exist")) 260 assert.Check(t, result.Equal(icmd.Expected{ 261 ExitCode: 1, 262 Err: "cat: /does/not/exist: No such file or directory", 263 })) 264 265 // In case of success, we may want to do something with the result 266 result := icmd.RunCommand("cat", "/does/exist") 267 assert.Assert(t, result.Equal(icmd.Success)) 268 // Read the output line by line 269 scanner := bufio.NewScanner(strings.NewReader(result.Stdout())) 270 for scanner.Scan() { 271 // Do something with it 272 } 273 </pre> 274 </div> 275 </div> 276 </section> 277 <section id="outline-container-Conclusion%E2%80%A6" class="outline-2"> 278 <h2 id="Conclusion%E2%80%A6">Conclusion…</h2> 279 <div class="outline-text-2" id="text-Conclusion%E2%80%A6"> 280 <p> 281 … that’s a wrap. The <code>icmd</code> package allows to easily run command and describe what result 282 are expected of the execution, with the least noise possible. We <b>use this package heavily</b> 283 on several <code>docker/*</code> projects (the engine, the cli)… 284 </p> 285 </div> 286 </section> 287 <div id="footnotes"> 288 <h2 class="footnotes">Footnotes: </h2> 289 <div id="text-footnotes"> 290 291 <div class="footdef"><sup><a id="fn.1" class="footnum" href="#fnr.1" role="doc-backlink">1</a></sup> <div class="footpara" role="doc-footnote"><p class="footpara"> 292 The <code>icmd</code> package is one of the oldest <code>gotest.tools</code> package, that comes from the 293 <a href="https://github.com/docker/docker"><code>docker/docker</code></a> initially. We introduced these <code>CmdOp</code> but implementations were in 294 <code>docker/docker</code> at first and we never really updated them. 295 </p></div></div> 296 297 298 </div> 299 </div></main> 300 <footer id="postamble" class="status"> 301 <footer> 302 <small><a href="/" rel="history">Index</a> • <a href="/sitemap.html">Sitemap</a> • <a href="https://dl.sbr.pm/">Files</a></small><br/> 303 <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/> 304 <small class='copyright'> 305 Content and design by Vincent Demeester 306 (<a rel='licence' href='http://creativecommons.org/licenses/by-nc-sa/3.0/'>Some rights reserved</a>) 307 </small><br /> 308 </footer> 309 </footer> 310 </body> 311 </html>