www

My personal website(s)
Log | Files | Refs

index.html (23968B)


      1 <!DOCTYPE html>
      2 
      3 <html lang="en">
      4   
      5   <head>
      6     <meta charset="utf-8">
      7     <meta http-equiv="content-type" content="text/html; charset=utf-8" />
      8 
      9     <link rel="start" href="https://vincent.demeester.fr" />
     10 
     11     <title>Vincent Demeester</title>
     12     <link rel="canonical" href="https://vincent.demeester.fr/posts/2018-09-18-gotest-tools-icmd/">
     13     <link href="https://vincent.demeester.fr/index.xml" rel="alternate" type="application/rss+xml" title="Vincent Demeester" />
     14 
     15     <link rel="openid.server" href="https://indieauth.com/openid" />
     16     <link rel="openid.delegate" href="http://vincent.demeester.fr/" />
     17     <link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
     18 
     19     <link rel="stylesheet" href="/css/screen.css" type="text/css" />
     20     <link rel="stylesheet" href="/css/sbrain.css" type="text/css" />
     21     <link rel="stylesheet" href="/css/syntax.css" type="text/css" />
     22 
     23   </head>
     24   
     25   <body lang=""/>
     26   
     27 
     28 
     29 
     30 
     31 
     32 <div id="main-container">
     33   <div id="page">
     34     <article class="post">
     35       <header>
     36         <h1 class="emphnext">Golang testing β€” gotest.tools icmd</h1><a href='https://vincent.demeester.fr/posts/2018-09-18-gotest-tools-icmd/'></a>
     37         <address class="signature">
     38           <span class="date">Tue, 18 September, 2018</span>
     39           <span class="words">(1100 Words)</span>
     40         </address>
     41 	<ul class="tag_box inline">
     42 	  
     43 	  <li class="category"><a href="/categories/#developement">developement</a></li>
     44 	  
     45 	  
     46 	  
     47 	  
     48 	  
     49 	  <li class="tag tag-testing"><a href="/tags/#testing">testing<span>11</span></a></li>
     50 	  
     51 	  
     52 	  <li class="tag tag-golang"><a href="/tags/#golang">golang<span>12</span></a></li>
     53 	  
     54 	  
     55 	  <li class="tag tag-exec"><a href="/tags/#exec">exec<span>1</span></a></li>
     56 	  
     57 	  
     58 	  <li class="tag tag-command"><a href="/tags/#command">command<span>1</span></a></li>
     59 	  
     60 	  <br/>
     61 	  
     62 	</ul>
     63       </header>
     64       
     65       
     66       
     67       
     68 
     69 <p>Let&rsquo;s continue the <a href="https://gotest.tools"><code>gotest.tools</code></a> serie, this time with the <code>icmd</code> package.</p>
     70 
     71 <blockquote>
     72 <p>Package icmd executes binaries and provides convenient assertions for testing the results.</p>
     73 </blockquote>
     74 
     75 <p>After file-system operations (seen in <a href="/posts/2018-09-14-gotest-tools-fs/"><code>fs</code></a>), another common use-case in tests is to
     76 <strong>execute a command</strong>. The reasons can be you&rsquo;re testing the <code>cli</code> you&rsquo;re currently writing
     77 or you need to setup something using a command line. A classic execution in a test might
     78 lookup like the following.</p>
     79 <div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">cmd</span> <span class="o">:=</span> <span class="nx">exec</span><span class="p">.</span><span class="nf">Command</span><span class="p">(</span><span class="s">&#34;echo&#34;</span><span class="p">,</span> <span class="s">&#34;foo&#34;</span><span class="p">)</span>
     80 <span class="nx">cmd</span><span class="p">.</span><span class="nx">Stout</span> <span class="p">=</span> <span class="o">&amp;</span><span class="nx">stdout</span>
     81 <span class="nx">cmd</span><span class="p">.</span><span class="nx">Env</span> <span class="p">=</span> <span class="nx">env</span>
     82 <span class="k">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">cmd</span><span class="p">.</span><span class="nf">Run</span><span class="p">();</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
     83 	<span class="nx">t</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
     84 <span class="p">}</span>
     85 <span class="k">if</span> <span class="nb">string</span><span class="p">(</span><span class="nx">stdout</span><span class="p">)</span> <span class="o">!=</span> <span class="s">&#34;foo&#34;</span> <span class="p">{</span>
     86 	<span class="nx">t</span><span class="p">.</span><span class="nf">Fatalf</span><span class="p">(</span><span class="s">&#34;expected: foo, got %s&#34;</span><span class="p">,</span> <span class="nb">string</span><span class="p">(</span><span class="nx">stdout</span><span class="p">))</span>
     87 <span class="p">}</span></code></pre></div>
     88 <p>The package <code>icmd</code> is there to ease your pain (as usual πŸ˜‰) β€” we used <em>the name <code>icmd</code></em>
     89 instead of <code>cmd</code> because it&rsquo;s a pretty common identifier in Go source code, thus would be
     90 really easy to <em>shadow</em> β€” and have some really weird problems going on.</p>
     91 
     92 <p>The usual <code>icmd</code> workflow is the following:</p>
     93 
     94 <ol>
     95 <li>Describe the command you want to execute using : type <code>Cmd</code>, function <code>Command</code> and
     96 <code>CmdOp</code> operators.</li>
     97 <li>Run it using : function <code>RunCmd</code> or <code>RunCommand</code> (that does 1. for you). You can also
     98 use <code>StartCmd</code> and <code>WaitOnCmd</code> if you want more control on the execution workflow.</li>
     99 <li>Check the result using the <code>Assert</code>, <code>Equal</code> or <code>Compare</code> methods attached to the
    100 <code>Result</code> struct that the command execution return.</li>
    101 </ol>
    102 
    103 <h2 id="create-and-run-a-command">Create and run a command</h2>
    104 
    105 <p>Let&rsquo;s first dig how to create commands. In this part, the assumption here is that the
    106 command is successful, so we&rsquo;ll have <code>.Assert(t, icmd.Success)</code> for now β€” we&rsquo;ll learn more
    107 about <code>Assert</code> in the next section πŸ‘Ό.</p>
    108 
    109 <p>The simplest way to create and run a command is using <code>RunCommand</code>, it has the same
    110 signature as <code>os/exec.Command</code>. A simple command execution goes as below.</p>
    111 <div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">icmd</span><span class="p">.</span><span class="nf">RunCommand</span><span class="p">(</span><span class="s">&#34;echo&#34;</span><span class="p">,</span> <span class="s">&#34;foo&#34;</span><span class="p">).</span><span class="nf">Assert</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">icmd</span><span class="p">.</span><span class="nx">Sucess</span><span class="p">)</span></code></pre></div>
    112 <p>Sometimes, you need to customize the command a bit more, like adding some environment
    113 variable. In those case, you are going to use <code>RunCmd</code>, it takes a <code>Cmd</code> and operators.
    114 Let&rsquo;s look at those functions.</p>
    115 <div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="kd">func</span> <span class="nf">RunCmd</span><span class="p">(</span><span class="nx">cmd</span> <span class="nx">Cmd</span><span class="p">,</span> <span class="nx">cmdOperators</span> <span class="o">...</span><span class="nx">CmdOp</span><span class="p">)</span> <span class="o">*</span><span class="nx">Result</span>
    116 
    117 <span class="kd">func</span> <span class="nf">Command</span><span class="p">(</span><span class="nx">command</span> <span class="kt">string</span><span class="p">,</span> <span class="nx">args</span> <span class="o">...</span><span class="kt">string</span><span class="p">)</span> <span class="nx">Cmd</span>
    118 
    119 <span class="kd">type</span> <span class="nx">Cmd</span> <span class="kd">struct</span> <span class="p">{</span>
    120 	<span class="nx">Command</span> <span class="p">[]</span><span class="kt">string</span>
    121 	<span class="nx">Timeout</span> <span class="nx">time</span><span class="p">.</span><span class="nx">Duration</span>
    122 	<span class="nx">Stdin</span>   <span class="nx">io</span><span class="p">.</span><span class="nx">Reader</span>
    123 	<span class="nx">Stdout</span>  <span class="nx">io</span><span class="p">.</span><span class="nx">Writer</span>
    124 	<span class="nx">Dir</span>     <span class="kt">string</span>
    125 	<span class="nx">Env</span>     <span class="p">[]</span><span class="kt">string</span>
    126 <span class="p">}</span></code></pre></div>
    127 <p>As we&rsquo;ve seen <a href="/posts/2017-01-01-go-testing-functionnal-builders/">multiple</a> <a href="/posts/2018-08-16-gotest-tools-assertions/">times</a> <a href="/posts/2018-09-14-gotest-tools-fs/">before</a>, it uses the <em>powerful</em> functional arguments. At the
    128 time I wrote this post, the <code>icmd</code> package doesn&rsquo;t contains too much <code>CmdOp</code>&nbsp;<sup class="footnote-ref" id="fnref:fn-1"><a href="#fn:fn-1">1</a></sup>, so I&rsquo;ll
    129 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
    130 without them.</p>
    131 <div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="c1">// With
    132 </span><span class="c1"></span><span class="nx">icmd</span><span class="p">.</span><span class="nf">RunCmd</span><span class="p">(</span><span class="nx">icmd</span><span class="p">.</span><span class="nf">Command</span><span class="p">(</span><span class="s">&#34;sh&#34;</span><span class="p">,</span> <span class="s">&#34;-c&#34;</span><span class="p">,</span> <span class="s">&#34;echo $FOO&#34;</span><span class="p">),</span>
    133 	<span class="nx">icmd</span><span class="p">.</span><span class="nf">WithEnv</span><span class="p">(</span><span class="s">&#34;FOO=bar&#34;</span><span class="p">,</span> <span class="s">&#34;BAR=baz&#34;</span><span class="p">),</span> <span class="nx">icmd</span><span class="p">.</span><span class="nf">Dir</span><span class="p">(</span><span class="s">&#34;/tmp&#34;</span><span class="p">),</span>
    134 	<span class="nx">icmd</span><span class="p">.</span><span class="nf">WithTimeout</span><span class="p">(</span><span class="mi">10</span><span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">),</span>
    135 <span class="p">).</span><span class="nf">Assert</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">icmd</span><span class="p">.</span><span class="nx">Success</span><span class="p">)</span>
    136 
    137 <span class="c1">// Without
    138 </span><span class="c1"></span><span class="nx">icmd</span><span class="p">.</span><span class="nf">RunCmd</span><span class="p">(</span><span class="nx">icmd</span><span class="p">.</span><span class="nx">Cmd</span><span class="p">{</span>
    139 	<span class="nx">Command</span><span class="p">:</span> <span class="p">[]</span><span class="kt">string</span><span class="p">{</span><span class="s">&#34;sh&#34;</span><span class="p">,</span> <span class="s">&#34;-c&#34;</span><span class="p">,</span> <span class="s">&#34;echo $FOO&#34;</span><span class="p">},</span>
    140 	<span class="nx">Env</span><span class="p">:</span> <span class="p">[]</span><span class="kt">string</span><span class="p">{</span><span class="s">&#34;FOO=bar&#34;</span><span class="p">,</span> <span class="s">&#34;BAR=baz&#34;</span><span class="p">},</span>
    141 	<span class="nx">Dir</span><span class="p">:</span> <span class="s">&#34;/tmp&#34;</span><span class="p">,</span>
    142 	<span class="nx">Timeout</span><span class="p">:</span> <span class="mi">10</span><span class="o">*</span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">,</span>
    143 <span class="p">}).</span><span class="nf">Assert</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">icmd</span><span class="p">.</span><span class="nx">Success</span><span class="p">)</span></code></pre></div>
    144 <p>As usual, the intent is clear, it&rsquo;s simple to read and composable (with <code>CmdOp</code>&rsquo;s).</p>
    145 
    146 <h2 id="assertions">Assertions</h2>
    147 
    148 <p>Let&rsquo;s dig into the assertion part of <code>icmd</code>. Running a command returns a struct
    149 <code>Result</code>. It has the following methods :</p>
    150 
    151 <ul>
    152 <li><code>Assert</code> compares the Result against the Expected struct, and fails the test if any of
    153 the expectations are not met.</li>
    154 <li><code>Compare</code> compares the result to Expected and return an error if they do not match.</li>
    155 <li><code>Equal</code> compares the result to Expected. If the result doesn&rsquo;t match expected
    156 returns a formatted failure message with the command, stdout, stderr, exit code, and any
    157 failed expectations. It returns an <code>assert.Comparison</code> struct, that can be used by other
    158 <code>gotest.tools</code>.</li>
    159 <li><code>Combined</code> returns the stdout and stderr combined into a single string.</li>
    160 <li><code>Stderr</code> returns the stderr of the process as a string.</li>
    161 <li><code>Stdout</code> returns the stdout of the process as a string.</li>
    162 </ul>
    163 
    164 <p>When you have a result, you, most likely want to do two things :</p>
    165 
    166 <ul>
    167 <li><em>assert</em> that the command succeed or failed with some specific values (exit code,
    168 stderr, stdout)</li>
    169 <li>use the output β€” most likely <code>stdout</code> but maybe <code>stderr</code> β€” in the rest of the test.</li>
    170 </ul>
    171 
    172 <p>As seen above, <em>asserting</em> the command result is using the <code>Expected</code> struct.</p>
    173 <div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="kd">type</span> <span class="nx">Expected</span> <span class="kd">struct</span> <span class="p">{</span>
    174 	<span class="nx">ExitCode</span> <span class="kt">int</span>    <span class="c1">// the exit code the command returned
    175 </span><span class="c1"></span>	<span class="nx">Timeout</span>  <span class="kt">bool</span>   <span class="c1">// did it timeout ?
    176 </span><span class="c1"></span>	<span class="nx">Error</span>    <span class="kt">string</span> <span class="c1">// error returned by the execution (os/exe)
    177 </span><span class="c1"></span>	<span class="nx">Out</span>      <span class="kt">string</span> <span class="c1">// content of stdout
    178 </span><span class="c1"></span>	<span class="nx">Err</span>      <span class="kt">string</span> <span class="c1">// content of stderr
    179 </span><span class="c1"></span><span class="p">}</span></code></pre></div>
    180 <p><code>Success</code> is a constant that defines a success β€” it&rsquo;s an exit code of <code>0</code>, didn&rsquo;t timeout,
    181 no error. There is also the <code>None</code> constant, that should be used for <code>Out</code> or <code>Err</code>, to
    182 specify that we don&rsquo;t want any content for those standard outputs.</p>
    183 <div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">icmd</span><span class="p">.</span><span class="nf">RunCmd</span><span class="p">(</span><span class="nx">icmd</span><span class="p">.</span><span class="nf">Command</span><span class="p">(</span><span class="s">&#34;cat&#34;</span><span class="p">,</span> <span class="s">&#34;/does/not/exist&#34;</span><span class="p">)).</span><span class="nf">Assert</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">icmd</span><span class="p">.</span><span class="nx">Expected</span><span class="p">{</span>
    184 	<span class="nx">ExitCode</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
    185 	<span class="nx">Err</span><span class="p">:</span>      <span class="s">&#34;cat: /does/not/exist: No such file or directory&#34;</span><span class="p">,</span>
    186 <span class="p">})</span>
    187 
    188 <span class="c1">// In case of success, we may want to do something with the result
    189 </span><span class="c1"></span><span class="nx">result</span> <span class="o">:=</span> <span class="nx">icmd</span><span class="p">.</span><span class="nf">RunCommand</span><span class="p">(</span><span class="s">&#34;cat&#34;</span><span class="p">,</span> <span class="s">&#34;/does/exist&#34;</span><span class="p">)</span>
    190 <span class="nx">result</span><span class="p">.</span><span class="nf">Assert</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">icmd</span><span class="p">.</span><span class="nx">Success</span><span class="p">)</span>
    191 <span class="c1">// Read the output line by line
    192 </span><span class="c1"></span><span class="nx">scanner</span> <span class="o">:=</span> <span class="nx">bufio</span><span class="p">.</span><span class="nf">NewScanner</span><span class="p">(</span><span class="nx">strings</span><span class="p">.</span><span class="nf">NewReader</span><span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nf">Stdout</span><span class="p">()))</span>
    193 <span class="k">for</span> <span class="nx">scanner</span><span class="p">.</span><span class="nf">Scan</span><span class="p">()</span> <span class="p">{</span>
    194 	<span class="c1">// Do something with it
    195 </span><span class="c1"></span><span class="p">}</span></code></pre></div>
    196 <p>If the <code>Result</code> doesn&rsquo;t map the <code>Expected</code>, a test failure will happen with a useful
    197 message that will contains the executed command and what differs between the result and
    198 the expectation.</p>
    199 <div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">result</span> <span class="o">:=</span> <span class="nx">icmd</span><span class="p">.</span><span class="nf">RunCommand</span><span class="p">(</span><span class="err">…</span><span class="p">)</span>
    200 <span class="nx">result</span><span class="p">.</span><span class="nf">Assert</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">icmd</span><span class="p">.</span><span class="nx">Expected</span><span class="p">{</span>
    201 		<span class="nx">ExitCode</span><span class="p">:</span> <span class="mi">101</span><span class="p">,</span>
    202 		<span class="nx">Out</span><span class="p">:</span>      <span class="s">&#34;Something else&#34;</span><span class="p">,</span>
    203 		<span class="nx">Err</span><span class="p">:</span>      <span class="nx">None</span><span class="p">,</span>
    204 <span class="p">})</span>
    205 <span class="c1">// Command:  binary arg1
    206 </span><span class="c1">// ExitCode: 99 (timeout)
    207 </span><span class="c1">// Error:    exit code 99
    208 </span><span class="c1">// Stdout:   the output
    209 </span><span class="c1">// Stderr:   the stderr
    210 </span><span class="c1">//
    211 </span><span class="c1">// Failures:
    212 </span><span class="c1">// ExitCode was 99 expected 101
    213 </span><span class="c1">// Expected command to finish, but it hit the timeout
    214 </span><span class="c1">// Expected stdout to contain &#34;Something else&#34;
    215 </span><span class="c1">// Expected stderr to contain &#34;[NOTHING]&#34;
    216 </span><span class="c1"></span><span class="err">…</span></code></pre></div>
    217 <p>Finally, we listed <code>Equal</code> above, that returns a <code>Comparison</code> struct. This means we can
    218 use it easily with the <code>assert</code> package. As written in a <a href="/posts/2018-08-16-gotest-tools-assertions/">previous post (about the <code>assert</code>
    219 package)</a>, I tend prefer to use <code>cmp.Comparison</code>. Let&rsquo;s convert the above examples using
    220 <code>assert</code>.</p>
    221 <div class="highlight"><pre class="chroma"><code class="language-go" data-lang="go"><span class="nx">result</span> <span class="o">:=</span> <span class="nx">icmd</span><span class="p">.</span><span class="nf">RunCmd</span><span class="p">(</span><span class="nx">icmd</span><span class="p">.</span><span class="nf">Command</span><span class="p">(</span><span class="s">&#34;cat&#34;</span><span class="p">,</span> <span class="s">&#34;/does/not/exist&#34;</span><span class="p">))</span>
    222 <span class="nx">assert</span><span class="p">.</span><span class="nf">Check</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">result</span><span class="p">.</span><span class="nf">Equal</span><span class="p">(</span><span class="nx">icmd</span><span class="p">.</span><span class="nx">Expected</span><span class="p">{</span>
    223 	<span class="nx">ExitCode</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
    224 	<span class="nx">Err</span><span class="p">:</span>      <span class="s">&#34;cat: /does/not/exist: No such file or directory&#34;</span><span class="p">,</span>
    225 <span class="p">}))</span>
    226 
    227 <span class="c1">// In case of success, we may want to do something with the result
    228 </span><span class="c1"></span><span class="nx">result</span> <span class="o">:=</span> <span class="nx">icmd</span><span class="p">.</span><span class="nf">RunCommand</span><span class="p">(</span><span class="s">&#34;cat&#34;</span><span class="p">,</span> <span class="s">&#34;/does/exist&#34;</span><span class="p">)</span>
    229 <span class="nx">assert</span><span class="p">.</span><span class="nf">Assert</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="nx">result</span><span class="p">.</span><span class="nf">Equal</span><span class="p">(</span><span class="nx">icmd</span><span class="p">.</span><span class="nx">Success</span><span class="p">))</span>
    230 <span class="c1">// Read the output line by line
    231 </span><span class="c1"></span><span class="nx">scanner</span> <span class="o">:=</span> <span class="nx">bufio</span><span class="p">.</span><span class="nf">NewScanner</span><span class="p">(</span><span class="nx">strings</span><span class="p">.</span><span class="nf">NewReader</span><span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nf">Stdout</span><span class="p">()))</span>
    232 <span class="k">for</span> <span class="nx">scanner</span><span class="p">.</span><span class="nf">Scan</span><span class="p">()</span> <span class="p">{</span>
    233 	<span class="c1">// Do something with it
    234 </span><span class="c1"></span><span class="p">}</span></code></pre></div>
    235 <h2 id="conclusion">Conclusion…</h2>
    236 
    237 <p>… that&rsquo;s a wrap. The <code>icmd</code> package allows to easily run command and describe what result
    238 are expected of the execution, with the least noise possible. We <strong>use this package heavily</strong>
    239 on several <code>docker/*</code> projects (the engine, the cli)…</p>
    240 <div class="footnotes">
    241 
    242 <hr />
    243 
    244 <ol>
    245 <li id="fn:fn-1">The <code>icmd</code> package is one of the oldest <code>gotest.tools</code> package, that comes from the <a href="https://github.com/docker/docker"><code>docker/docker</code></a> initially. We introduced these <code>CmdOp</code> but implementations were in <code>docker/docker</code> at first and we never really updated them.
    246  <a class="footnote-return" href="#fnref:fn-1"><sup>[return]</sup></a></li>
    247 </ol>
    248 </div>
    249 
    250       
    251     </article>
    252     <hr />
    253     <div class="prev-next">
    254       
    255       <a class="paging-link prev" href="/posts/2018-12-16-link/" title="Go testing style guide">← Previous post</a>
    256       
    257 
    258       
    259       <a class="paging-link next" href="/posts/2018-09-14-gotest-tools-fs/" title="Golang testing β€” gotest.tools fs">Next post β†’</a>
    260       
    261     </div>
    262 
    263   </div>
    264 </div>
    265 
    266 <footer>
    267   <nav>
    268     
    269     <a href="/">home</a>
    270     <span class="text-muted"> | </span>
    271     
    272     <a href="/about">about</a>
    273     <span class="text-muted"> | </span>
    274     
    275     <a href="/archive">archive</a>
    276     <span class="text-muted"> | </span>
    277     
    278     <a href="/categories">categories</a>
    279     <span class="text-muted"> | </span>
    280     
    281     <a href="/tags">tags</a>
    282     <span class="text-muted"> | </span>
    283     
    284     <a href="https://twitter.com/vdemeest">twitter</a>
    285     <span class="text-muted"> | </span>
    286     
    287     <a href="https://github.com/vdemeester">github</a>
    288     <span class="text-muted"> | </span>
    289     
    290     <a href="https://vincent.demeester.fr/index.xml">rss</a>
    291   </nav>
    292   <br/>
    293   <address>
    294     <span class="copyright">
    295       Content and design by Vincent Demeester
    296       (<a rel="licence" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Some rights reserved</a>)
    297     </span><br />
    298     <span class="engine">
    299       Powered by <a href="https://gohugo.io/">Hugo</a> and <a href="https://github.com/kaushalmodi/ox-hugo/">ox-hugo</a>
    300     </span>
    301   </address>
    302 </footer>
    303 </body>
    304