www

My personal website(s)
Log | Files | Refs

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&rsquo;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&rsquo;re testing the <code>cli</code> you&rsquo;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 = &amp;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&rsquo;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&rsquo;s first dig how to create commands. In this part, the assumption here is that the
     89 command is successful, so we&rsquo;ll have <code>.Assert(t, icmd.Success)</code> for now — we&rsquo;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&rsquo;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&rsquo;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&rsquo;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&rsquo;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&rsquo;s simple to read and composable (with <code>CmdOp</code>&rsquo;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&rsquo;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&rsquo;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&rsquo;s an exit code of <code>0</code>, didn&rsquo;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&rsquo;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&rsquo;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&rsquo;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&rsquo;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>