Jekyll2023-06-10T14:34:50+00:00https://www.mikem.xyz/feed.xmlDev NotesMike MazurDebugging a Docker Container in a Swarm2023-06-10T00:00:00+00:002023-06-10T00:00:00+00:00https://www.mikem.xyz/2023/06/10/debugging-a-docker-container-in-a-swarm<p>Let’s say you have a container that’s being launched by something which reads a <a href="https://docs.docker.com/compose/compose-file/">Docker Compose file</a>. For some reason, this container is crashlooping when it’s launched, which makes it difficult to a) read the logs in time, and b) log in to investigagte.</p>
<p>There are two Compose file attributes for the service which might come in useful:</p>
<p><a href="https://docs.docker.com/compose/compose-file/05-services/#command">command</a>: this will override the command specified in the Dockerfile. We need something which will not immediately terminate; <code class="language-plaintext highlighter-rouge">tail -f /dev/null</code> will do nicely.</p>
<p><a href="https://docs.docker.com/compose/compose-file/05-services/#healthcheck">healthcheck</a>: if the container’s Dockerfile has a healthcheck configured, it will restart when the healthcheck timeout expires. Since the container is broken, this is likely going to happen while you’re debugging, which can be annoying. Thankfully this can be disabled.</p>
<p>The service definition in the Compose file then looks like this:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">service</span><span class="pi">:</span>
<span class="na">foo</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">example/foo:latest</span>
<span class="c1"># all the other usual settings</span>
<span class="na">command</span><span class="pi">:</span> <span class="s2">"</span><span class="s">tail</span><span class="nv"> </span><span class="s">-f</span><span class="nv"> </span><span class="s">/dev/null"</span>
<span class="na">healthcheck</span><span class="pi">:</span>
<span class="na">disable</span><span class="pi">:</span> <span class="no">true</span>
</code></pre></div></div>
<p>Re-deploy the containers and debug in peace. Don’t forget to remove those lines when you’re done.</p>
<h1 id="references">References</h1>
<ul>
<li><a href="https://devopscube.com/keep-docker-container-running/">How to Keep Docker Container Running for Debugging</a> at DevopsCube</li>
<li><a href="https://docs.docker.com/compose/compose-file/">Docker Compose Specification Overview</a></li>
</ul>Mike MazurLet’s say you have a container that’s being launched by something which reads a Docker Compose file. For some reason, this container is crashlooping when it’s launched, which makes it difficult to a) read the logs in time, and b) log in to investigagte.Configuration is a Dependency2019-09-27T00:00:00+00:002019-09-27T00:00:00+00:00https://www.mikem.xyz/2019/09/27/configuration-is-a-dependency<p>Consider this Ruby code which generates a report for a subset of companies:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">CustomReport</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">output</span><span class="p">:)</span>
<span class="vi">@output</span> <span class="o">=</span> <span class="n">output</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">companies</span>
<span class="no">DB</span><span class="p">.</span><span class="nf">companies</span><span class="p">.</span><span class="nf">query</span><span class="p">(</span><span class="ss">vertical: </span><span class="s1">'airline'</span><span class="p">).</span><span class="nf">all</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">generate</span>
<span class="n">report</span> <span class="o">=</span> <span class="c1"># do something with companies</span>
<span class="no">File</span><span class="p">.</span><span class="nf">open</span><span class="p">(</span><span class="vi">@output</span><span class="p">,</span> <span class="s1">'w'</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">f</span><span class="o">|</span> <span class="n">f</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="n">report</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">CustomReportTest</span> <span class="o"><</span> <span class="no">Minitest</span><span class="o">::</span><span class="no">Test</span>
<span class="k">def</span> <span class="nf">setup</span>
<span class="vi">@airline</span> <span class="o">=</span> <span class="no">DB</span><span class="p">.</span><span class="nf">companies</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="ss">vertical: </span><span class="s1">'airline'</span><span class="p">)</span>
<span class="vi">@gallery</span> <span class="o">=</span> <span class="no">DB</span><span class="p">.</span><span class="nf">companies</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="ss">vertical: </span><span class="s1">'fine_art'</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">test_generate_report_includes_only_airlines</span>
<span class="no">CustomReport</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">output: </span><span class="s1">'/tmp/custom_report.txt'</span><span class="p">).</span><span class="nf">generate</span>
<span class="n">report</span> <span class="o">=</span> <span class="no">File</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="s1">'/tmp/custom_report.txt'</span><span class="p">)</span>
<span class="n">report_ids</span> <span class="o">=</span> <span class="n">extract_ids</span><span class="p">(</span><span class="n">report</span><span class="p">)</span>
<span class="n">expected_ids</span> <span class="o">=</span> <span class="no">Set</span><span class="p">.</span><span class="nf">new</span><span class="p">([</span><span class="vi">@airline</span><span class="p">.</span><span class="nf">id</span><span class="p">])</span>
<span class="n">assert_equal</span> <span class="n">expected_ids</span><span class="p">,</span> <span class="n">report_ids</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">extract_ids</span><span class="p">(</span><span class="n">report</span><span class="p">)</span>
<span class="c1"># return a Set of IDs found in the report</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Our task is to generate the same report for another vertical. The simplest solution would be adding a second DB query with the new vertical hardcoded. Instead, let’s make the algorithm generic to make supporting future verticals easier.</p>
<p>In our scenario, adding new verticals happens rarely and with ample lead time, so deploying code is a feasible workflow for adding more. A list of the verticals can be stored in a constant on <code class="language-plaintext highlighter-rouge">CustomReport</code> and iterated over. Adding a new report simply means adding a new element to the list:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">CustomReport</span>
<span class="no">VERTICALS</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'airline'</span><span class="p">,</span> <span class="s1">'performance'</span><span class="p">]</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">output</span><span class="p">:)</span>
<span class="vi">@output_dirname</span><span class="p">,</span> <span class="vi">@output_filename</span> <span class="o">=</span> <span class="n">parse_dir_and_filename</span><span class="p">(</span><span class="n">output</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">companies_by_vertical</span>
<span class="no">VERTICALS</span><span class="p">.</span><span class="nf">each_with_object</span><span class="p">({})</span> <span class="k">do</span> <span class="o">|</span><span class="n">v</span><span class="p">,</span> <span class="n">h</span><span class="o">|</span>
<span class="n">h</span><span class="p">[</span><span class="n">v</span><span class="p">]</span> <span class="o">=</span> <span class="no">DB</span><span class="p">.</span><span class="nf">companies</span><span class="p">.</span><span class="nf">query</span><span class="p">(</span><span class="ss">vertical: </span><span class="n">v</span><span class="p">).</span><span class="nf">all</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">generate</span>
<span class="n">companies_by_vertical</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">vertical</span><span class="p">,</span> <span class="n">companies</span><span class="o">|</span>
<span class="n">report</span> <span class="o">=</span> <span class="c1"># do something with companies</span>
<span class="n">filename</span> <span class="o">=</span> <span class="s2">"</span><span class="si">#{</span><span class="vi">@output_dirname</span><span class="si">}</span><span class="s2">/</span><span class="si">#{</span><span class="n">vertical</span><span class="si">}</span><span class="s2">_</span><span class="si">#{</span><span class="vi">@output_filename</span><span class="si">}</span><span class="s2">"</span>
<span class="no">File</span><span class="p">.</span><span class="nf">open</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="s1">'w'</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">f</span><span class="o">|</span> <span class="n">f</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="n">report</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">parse_dir_and_filename</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
<span class="c1"># return dirname, filename</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>This approach introduces some tradeoffs in testing. The test <em>could</em> check the same verticals defined in <code class="language-plaintext highlighter-rouge">CustomReport::VERTICALS</code>, but that means <em>changing the test each time a new vertical is added</em>.</p>
<p>The code and its tests are tightly coupled.</p>
<p>Decoupling would require modifying the value of <code class="language-plaintext highlighter-rouge">CustomReport::VERTICALS</code> in the test, which <a href="https://github.com/freerange/mocha/issues/13#issuecomment-509832">can be done</a> but requires some push-ups. It’s <em>an indicator that there’s probably a better way</em>.</p>
<p>Let’s use a class method instead, which is easier to stub in test:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">CustomReport</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">verticals</span>
<span class="p">[</span><span class="s1">'airline'</span><span class="p">,</span> <span class="s1">'performance'</span><span class="p">]</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">output</span><span class="p">:)</span>
<span class="vi">@output_dirname</span><span class="p">,</span> <span class="vi">@output_filename</span> <span class="o">=</span> <span class="n">parse_dir_and_filename</span><span class="p">(</span><span class="n">output</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">companies_by_vertical</span>
<span class="nb">self</span><span class="p">.</span><span class="nf">class</span><span class="p">.</span><span class="nf">verticals</span><span class="p">.</span><span class="nf">each_with_object</span><span class="p">({})</span> <span class="k">do</span> <span class="o">|</span><span class="n">v</span><span class="p">,</span> <span class="n">h</span><span class="o">|</span>
<span class="n">h</span><span class="p">[</span><span class="n">v</span><span class="p">]</span> <span class="o">=</span> <span class="no">DB</span><span class="p">.</span><span class="nf">companies</span><span class="p">.</span><span class="nf">query</span><span class="p">(</span><span class="ss">vertical: </span><span class="n">v</span><span class="p">).</span><span class="nf">all</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">generate</span>
<span class="n">companies_by_vertical</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">vertical</span><span class="p">,</span> <span class="n">companies</span><span class="o">|</span>
<span class="n">report</span> <span class="o">=</span> <span class="c1"># do something with companies</span>
<span class="n">filename</span> <span class="o">=</span> <span class="s2">"</span><span class="si">#{</span><span class="vi">@output_dirname</span><span class="si">}</span><span class="s2">/</span><span class="si">#{</span><span class="n">vertical</span><span class="si">}</span><span class="s2">_</span><span class="si">#{</span><span class="vi">@output_filename</span><span class="si">}</span><span class="s2">"</span>
<span class="no">File</span><span class="p">.</span><span class="nf">open</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="s1">'w'</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">f</span><span class="o">|</span> <span class="n">f</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="n">report</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">parse_dir_and_filename</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
<span class="c1"># return dirname, filename</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>The test can now stub <code class="language-plaintext highlighter-rouge">CustomReport.verticals</code> which achieves the decoupling we’re after. However, that <code class="language-plaintext highlighter-rouge">self.class.verticals.each_with_object</code> looks onerous. And it does rely on stubbing, which incurs some risk. I don’t quite like it.</p>
<p>The key here is that the verticals are <em>configuration</em> for <code class="language-plaintext highlighter-rouge">CustomReport</code> and should live outside the class. This configuration is a <em>dependency</em> for <code class="language-plaintext highlighter-rouge">CustomReport</code>. Let’s <em>inject</em> this dependency:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">CustomReport</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">output</span><span class="p">:,</span> <span class="n">verticals</span><span class="p">:)</span>
<span class="vi">@output_dirname</span><span class="p">,</span> <span class="vi">@output_filename</span> <span class="o">=</span> <span class="n">parse_dir_and_filename</span><span class="p">(</span><span class="n">output</span><span class="p">)</span>
<span class="vi">@verticals</span> <span class="o">=</span> <span class="n">verticals</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">companies</span>
<span class="vi">@verticals</span><span class="p">.</span><span class="nf">each_with_object</span><span class="p">({})</span> <span class="k">do</span> <span class="o">|</span><span class="n">v</span><span class="p">,</span> <span class="n">h</span><span class="o">|</span>
<span class="n">h</span><span class="p">[</span><span class="n">v</span><span class="p">]</span> <span class="o">=</span> <span class="no">DB</span><span class="p">.</span><span class="nf">companies</span><span class="p">.</span><span class="nf">query</span><span class="p">(</span><span class="ss">vertical: </span><span class="n">v</span><span class="p">).</span><span class="nf">all</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">generate</span>
<span class="n">companies_by_vertical</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">vertical</span><span class="p">,</span> <span class="n">companies</span><span class="o">|</span>
<span class="n">report</span> <span class="o">=</span> <span class="c1"># do something with companies</span>
<span class="n">filename</span> <span class="o">=</span> <span class="s2">"</span><span class="si">#{</span><span class="vi">@output_dirname</span><span class="si">}</span><span class="s2">/</span><span class="si">#{</span><span class="n">vertical</span><span class="si">}</span><span class="s2">_</span><span class="si">#{</span><span class="vi">@output_filename</span><span class="si">}</span><span class="s2">"</span>
<span class="no">File</span><span class="p">.</span><span class="nf">open</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="s1">'w'</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">f</span><span class="o">|</span> <span class="n">f</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span><span class="n">report</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">parse_dir_and_filename</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
<span class="c1"># return dirname, filename</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>The test now looks like this:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">CustomReportTest</span> <span class="o"><</span> <span class="no">Minitest</span><span class="o">::</span><span class="no">Test</span>
<span class="k">def</span> <span class="nf">setup</span>
<span class="vi">@verticals</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'airline'</span><span class="p">,</span> <span class="s1">'performance'</span><span class="p">]</span>
<span class="vi">@airline</span> <span class="o">=</span> <span class="no">DB</span><span class="p">.</span><span class="nf">companies</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="ss">vertical: </span><span class="s1">'airline'</span><span class="p">)</span>
<span class="vi">@gallery</span> <span class="o">=</span> <span class="no">DB</span><span class="p">.</span><span class="nf">companies</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="ss">vertical: </span><span class="s1">'fine_art'</span><span class="p">)</span>
<span class="vi">@theater</span> <span class="o">=</span> <span class="no">DB</span><span class="p">.</span><span class="nf">companies</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="ss">vertical: </span><span class="s1">'performance'</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">test_generate_airline_report_includes_only_airlines</span>
<span class="no">CustomReport</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span>
<span class="ss">output: </span><span class="s1">'/tmp/custom_report.txt'</span><span class="p">,</span>
<span class="ss">verticals: </span><span class="vi">@verticals</span>
<span class="p">).</span><span class="nf">generate</span>
<span class="n">report</span> <span class="o">=</span> <span class="no">File</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="s1">'/tmp/airline_custom_report.txt'</span><span class="p">)</span>
<span class="n">report_ids</span> <span class="o">=</span> <span class="n">extract_ids</span><span class="p">(</span><span class="n">report</span><span class="p">)</span>
<span class="n">expected_ids</span> <span class="o">=</span> <span class="no">Set</span><span class="p">.</span><span class="nf">new</span><span class="p">([</span><span class="vi">@airline</span><span class="p">.</span><span class="nf">id</span><span class="p">])</span>
<span class="n">assert_equal</span> <span class="n">expected_ids</span><span class="p">,</span> <span class="n">report_ids</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">test_generate_theater_report_includes_only_theaters</span>
<span class="no">CustomReport</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span>
<span class="ss">output: </span><span class="s1">'/tmp/custom_report.txt'</span><span class="p">,</span>
<span class="ss">verticals: </span><span class="vi">@verticals</span>
<span class="p">).</span><span class="nf">generate</span>
<span class="n">report</span> <span class="o">=</span> <span class="no">File</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="s1">'/tmp/performance_custom_report.txt'</span><span class="p">)</span>
<span class="n">report_ids</span> <span class="o">=</span> <span class="n">extract_ids</span><span class="p">(</span><span class="n">report</span><span class="p">)</span>
<span class="n">expected_ids</span> <span class="o">=</span> <span class="no">Set</span><span class="p">.</span><span class="nf">new</span><span class="p">([</span><span class="vi">@theater</span><span class="p">.</span><span class="nf">id</span><span class="p">])</span>
<span class="n">assert_equal</span> <span class="n">expected_ids</span><span class="p">,</span> <span class="n">report_ids</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">extract_ids</span><span class="p">(</span><span class="n">report</span><span class="p">)</span>
<span class="c1"># return a Set of IDs found in the report</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>The test generates reports for airlines and theaters, but the production code can generate reports for food trucks and toy stores. The code and its tests are completely decoupled, and adding more verticals is as straightforward as the initial solution. The <code class="language-plaintext highlighter-rouge">CustomReport</code> class can be used elsewhere in the code for different verticals if required.</p>
<p>Pay attention to those code smells. If something looks off or is difficult to test, it’s probably a nudge to a better design.</p>Mike MazurConsider this Ruby code which generates a report for a subset of companies:Hello Minitest2019-04-28T00:00:00+00:002019-04-28T00:00:00+00:00https://www.mikem.xyz/2019/04/28/hello-minitest<p>I have heard nice things about <a href="https://github.com/seattlerb/minitest">Minitest</a> in the past, but have always worked with RSpec. Working on a small CLI utility recently, I wanted to minimize external dependencies, and since Minitest is built in to Ruby<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> I thought I’d give it a shot.</p>
<p>Minitest allows you to <a href="http://docs.seattlerb.org/minitest/#label-Running+Your+Tests">run the tests directly from the command line</a>. I started with this approach, test-driving a specific piece of logic I had in my head. Later I followed <a href="https://bundler.io/v2.0/guides/creating_gem.html">Bundler’s excellent documentation for writing a gem</a> and adopted a Rakefile.</p>
<h1 id="spec-or-unit-test-notation">Spec or Unit Test Notation?</h1>
<p>I started using the spec notation at first, but noticed that the error messages refer to <a href="https://github.com/seattlerb/minitest/blob/master/design_rationale.rb">auto-generated methods</a>. Consider the following test:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">require</span> <span class="s1">'minitest/autorun'</span>
<span class="n">describe</span> <span class="s1">'Foo'</span> <span class="k">do</span>
<span class="n">it</span> <span class="s1">'fails'</span> <span class="k">do</span>
<span class="mi">1</span><span class="p">.</span><span class="nf">must_equal</span> <span class="mi">0</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Running it generates this output:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ruby foo_test.rb
Run options: --seed 64703
# Running:
F
Failure:
Foo#test_0001_fails [foo_test.rb:5]:
Expected: 0
Actual: 1
ruby foo_test.rb:4
Finished in 0.001004s, 996.0157 runs/s, 996.0157 assertions/s.
1 runs, 1 assertions, 1 failures, 0 errors, 0 skips
</code></pre></div></div>
<p>That failure description references <code class="language-plaintext highlighter-rouge">Foo#test_0001_fails</code>. I found this slightly disorienting even with a small amount of code, and it hinders navigating to the failing test by copying the method name from the error message and searching for it. I decided to switch to the <a href="https://github.com/seattlerb/minitest#unit-tests">regular unit test notation</a>:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">require</span> <span class="s1">'minitest/autorun'</span>
<span class="k">class</span> <span class="nc">Foo</span> <span class="o"><</span> <span class="no">Minitest</span><span class="o">::</span><span class="no">Test</span>
<span class="k">def</span> <span class="nf">test_fails</span>
<span class="n">assert_equal</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>It reminds me of how I first learned unit testing, through JUnit examples<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>. The error report is clearer:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Failure:
Foo#test_fails [foo_test.rb:5]:
Expected: 0
Actual: 1
</code></pre></div></div>
<p>The notation also affects the assertion syntax.</p>
<h1 id="running-specific-tests">Running Specific Tests</h1>
<p>Once there were multiple test files, the need arose to run specific test files and specific tests on their own. A web search eventually led me to <a href="https://github.com/ruby/rake/blob/1c22b490ee6cb8bd614fa8d0d6145f671466206b/lib/rake/testtask.rb">a post by Weston Ganger</a> which provides the solution.</p>
<p><a href="https://github.com/ruby/rake/blob/1c22b490ee6cb8bd614fa8d0d6145f671466206b/lib/rake/testtask.rb">How it works</a>: the <code class="language-plaintext highlighter-rouge">TEST</code> option tells <code class="language-plaintext highlighter-rouge">rake</code> which file to run, while the value of the <code class="language-plaintext highlighter-rouge">TESTOPTS</code> option is passed on to the test runner. <a href="http://docs.seattlerb.org/minitest/#label-Running+Your+Tests">The options Minitest accepts are documented</a>.</p>
<h1 id="references">References</h1>
<ul>
<li>Minitest <a href="http://docs.seattlerb.org/minitest/">documentation</a> and <a href="https://github.com/seattlerb/minitest">source code</a></li>
<li><a href="https://bundler.io/v2.0/guides/creating_gem.html"><em>How to create a Ruby gem with Bundler</em></a></li>
<li><a href="http://mattsears.com/articles/2011/12/10/minitest-quick-reference/"><em>Minitest Quick Reference</em></a> by Matt Sears</li>
<li><a href="https://devhints.io/minitest"><em>Cheatsheet for Minitest</em></a> at devhints.io</li>
<li><a href="https://github.com/ruby/rake/blob/1c22b490ee6cb8bd614fa8d0d6145f671466206b/lib/rake/testtask.rb"><em>A Few Ways To Run Your MiniTest Tests</em></a> by Weston Ganger</li>
</ul>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:1" role="doc-endnote">
<p>Minitest started shipping with Ruby 1.9. The <a href="http://docs.seattlerb.org/minitest/#label-INSTALL-3A<Paste>">docs do mention, though, that not all gem functionality works</a> with the built-in Minitest, so installing the gem might be best. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:2" role="doc-endnote">
<p>I actually first encountered unit testing in Python, but reading <a href="https://www.amazon.com/dp/0321146530">Kent Beck’s <em>Test-Driven Development: By Example</em></a> really solidified things for me. I recommend this book to anyone starting out with unit testing. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>Mike MazurI have heard nice things about Minitest in the past, but have always worked with RSpec. Working on a small CLI utility recently, I wanted to minimize external dependencies, and since Minitest is built in to Ruby1 I thought I’d give it a shot. Minitest started shipping with Ruby 1.9. The docs do mention, though, that not all gem functionality works with the built-in Minitest, so installing the gem might be best. ↩Enhance Your iOS Bug Report With a Video2016-11-30T00:00:00+00:002016-11-30T00:00:00+00:00https://www.mikem.xyz/2016/11/30/enhance-your-ios-bug-report-with-a-video<p>Your product has an iOS component and you found a bug. You’re going to file it in your team’s bug tracker so it gets fixed.</p>
<p>But it’s a bit tricky to describe all the steps necessary to reproduce the bug – what do you do? You could take screenshots at each step and include that in your writeup. That’s a bit cumbersome and takes a while. A video would be easier. Here’s how you do it:</p>
<p>On a Mac, you can <a href="http://ioshacker.com/how-to/use-quicktime-record-screen-iphone-ipad-ipod-touch-running-ios-8">record the screen of your iDevice with QuickTime</a>. Here are the steps:</p>
<ol>
<li>Connect the iDevice to your Mac with a Lightning cable, make sure the device is unlocked</li>
<li>Open QuickTime Player and select File -> New Movie Recording (or mash ⌥⌘N)</li>
<li>Next to the record button is a drop down icon, open it</li>
<li>In the menu, select your iDevice as the camera and the mic</li>
<li>Hit record and reproduce the bug on the iDevice</li>
</ol>
<p>(Check out <a href="http://ioshacker.com/how-to/use-quicktime-record-screen-iphone-ipad-ipod-touch-running-ios-8">iOSHacker</a> for more details and screenshots.)</p>
<p>The resulting movie file will probably be large. You can use QuickTime to make it smaller (File -> Export…).</p>
<p>You may also want to strip out the audio track. Now, you <em>could</em> record yourself narrating what you’re doing, or perhaps elaborating in certain steps. In that case, leave the audio, and make sure to indicate in the bug report that the video has important details in the audio (I usually have my laptop muted).</p>
<p>But if the audio isn’t important, stripping out will help decrease the file size. I couldn’t find how to do this with QuickTime, but your Mac ships with a useful command-line utility which can do the job: <a href="https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man1/avconvert.1.html"><code class="language-plaintext highlighter-rouge">avconvert</code></a>. Here’s a handy command to shrink your video and strip out the audio track:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>avconvert \
--preset PresetAppleM4V720pHD \
--source /path/to/source.mov \
--output /path/to/converted.mov \
-ot audioTrack
</code></pre></div></div>
<p>This command uses the <code class="language-plaintext highlighter-rouge">PresetAppleM4V720pHD</code> preset – you can list the available presets with <code class="language-plaintext highlighter-rouge">avconvert --listPresets</code>.</p>
<p>And you’re done! You have a nice video demonstrating the bug to go along with your report.</p>
<p><em>Edit [2018-05-11]:</em> if for some reason the <code class="language-plaintext highlighter-rouge">avconvert</code> command doesn’t work for you, here’s an <code class="language-plaintext highlighter-rouge">ffmpeg</code> invocation which achieves the same outcome:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ffmpeg \
-i /path/to/source.mov \
-codec:v libx264 \
-codec:a copy \
-crf 20 \ # ¯\_(ツ)_/¯
-an \
/path/to/converted.mov
</code></pre></div></div>
<p>These days I use the <a href="https://support.apple.com/en-sg/HT207935">screen recording feature</a> available since iOS 11 for this purpose.</p>Mike MazurYour product has an iOS component and you found a bug. You’re going to file it in your team’s bug tracker so it gets fixed.Redirecting in the Play routes File2016-08-26T00:00:00+00:002016-08-26T00:00:00+00:00https://www.mikem.xyz/2016/08/26/redirecting-in-play-routes-file<p>In a Play application I recently found myself wanting to redirect <code class="language-plaintext highlighter-rouge">GET /</code> to <code class="language-plaintext highlighter-rouge">GET /elsewhere</code>. I could have mapped <code class="language-plaintext highlighter-rouge">GET /</code> to a real controller which always performs the same redirect. But do I have to?</p>
<p>It turns out that Play has a <code class="language-plaintext highlighter-rouge">Default</code> controller<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> which has some useful methods:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">error</code> returns a 500 Internal Server Error</li>
<li><code class="language-plaintext highlighter-rouge">notFound</code> returns a 404 Not Found</li>
<li><code class="language-plaintext highlighter-rouge">redirect</code> takes a string argument and redirects there with a 303 See Other</li>
<li><code class="language-plaintext highlighter-rouge">todo</code> returns a 501 Not Implemented</li>
</ul>
<p>In my case I have this in my <code class="language-plaintext highlighter-rouge">routes</code> file:</p>
<div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">GET</span> <span class="o">/</span> <span class="nv">controllers</span><span class="o">.</span><span class="py">Default</span><span class="o">.</span><span class="py">redirect</span><span class="o">(</span><span class="n">to</span> <span class="k">=</span> <span class="s">"/elsewhere"</span><span class="o">)</span>
<span class="nc">GET</span> <span class="o">/</span><span class="n">elsewhere</span> <span class="nv">controllers</span><span class="o">.</span><span class="py">ElsewhereController</span><span class="o">.</span><span class="py">index</span>
</code></pre></div></div>
<p>Now <code class="language-plaintext highlighter-rouge">GET /</code> requests are redirected:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ curl -I localhost:9000
HTTP/1.1 303 See Other
Location: /elsewhere
Content-Length: 0
Date: Fri, 26 Aug 2016 12:21:52 GMT
</code></pre></div></div>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:1" role="doc-endnote">
<p>Check out <a href="https://www.playframework.com/documentation/2.5.6/api/scala/index.html#controllers.Default$">the API documentation</a> for more details. To see the four methods I mention, set the “Inherited” section to “Hide All” and select both “Default” options. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>Mike MazurIn a Play application I recently found myself wanting to redirect GET / to GET /elsewhere. I could have mapped GET / to a real controller which always performs the same redirect. But do I have to?Passing Arguments to an Elm Application2016-08-19T00:00:00+00:002016-08-19T00:00:00+00:00https://www.mikem.xyz/2016/08/19/passing-arguments-to-elm-app<p>You’re starting to play with Elm and have come up with an app which you’d like to start using with a backend. One approach is to have the backend serve an HTML file which loads the JavaScript containing the compiled Elm code and initialize it with <code class="language-plaintext highlighter-rouge">Elm.Main.fullscreen()</code>:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><!DOCTYPE HTML></span>
<span class="nt"><html></span>
<span class="nt"><head></span>
<span class="nt"><title></span>My Elm App<span class="nt"></title></span>
<span class="nt"><script </span><span class="na">src=</span><span class="s">"app.js"</span><span class="nt">></script></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
<span class="nt"><script </span><span class="na">type=</span><span class="s">"text/javascript"</span><span class="nt">></span>
<span class="nx">Elm</span><span class="p">.</span><span class="nx">Main</span><span class="p">.</span><span class="nx">fullscreen</span><span class="p">();</span>
<span class="nt"></script></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</code></pre></div></div>
<p>The app makes API calls to your backend to retrieve data to render, and you soon realize that it needs to hit <code class="language-plaintext highlighter-rouge">localhost</code> in development and <code class="language-plaintext highlighter-rouge">yoursite.com</code> in production. We can do this by passing data to the Elm application when it’s iniaizlied. But how?</p>
<p>In <a href="http://guide.elm-lang.org/">An Introduction to Elm</a> you’ll notice <code class="language-plaintext highlighter-rouge">beginnerProgram</code> or <code class="language-plaintext highlighter-rouge">program</code> as the entry point in the examples. The type of these two functions is <code class="language-plaintext highlighter-rouge">Program Never</code>. A <code class="language-plaintext highlighter-rouge">Program</code> value “captures all the details needed to manage your application.” <code class="language-plaintext highlighter-rouge">Program Never</code> essentially means this program takes no flags. We’ll need to use <code class="language-plaintext highlighter-rouge">programWithFlags</code> instead:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">type</span> <span class="n">alias</span> <span class="kt">Flags</span> <span class="o">=</span>
<span class="p">{</span> <span class="n">apiEndpoint</span> <span class="o">:</span> <span class="kt">String</span> <span class="p">}</span>
<span class="n">main</span> <span class="o">:</span> <span class="kt">Program</span> <span class="kt">Flags</span>
<span class="n">main</span> <span class="o">=</span>
<span class="kt">App</span><span class="o">.</span><span class="n">programWithFlags</span>
<span class="p">{</span> <span class="cm">{- assign init, view, update & subscriptions -}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>What does the program do with the flags it receives? They’re passed on to the <code class="language-plaintext highlighter-rouge">init</code> function. Change your <code class="language-plaintext highlighter-rouge">init</code> function to take flags as its argument:</p>
<div class="language-haskell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">type</span> <span class="n">alias</span> <span class="kt">Model</span> <span class="o">=</span>
<span class="p">{</span> <span class="n">apiEndpoint</span> <span class="o">:</span> <span class="kt">String</span><span class="p">,</span> <span class="o">...</span> <span class="p">}</span>
<span class="n">init</span> <span class="o">:</span> <span class="kt">Flags</span> <span class="o">-></span> <span class="p">(</span> <span class="kt">Model</span><span class="p">,</span> <span class="kt">Cmd</span> <span class="kt">Msg</span> <span class="p">)</span>
<span class="n">init</span> <span class="n">flags</span> <span class="o">=</span>
<span class="cm">{- use flags, store the apiEndpoint on the model,
or pass it to the function which retrieves
data from your backend -}</span>
</code></pre></div></div>
<p>That’s all you need to change in your Elm app. All that remains is passing the <code class="language-plaintext highlighter-rouge">apiEndpoint</code> when the app is being initialized:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt"><script </span><span class="na">type=</span><span class="s">"text/javascript"</span><span class="nt">></span>
<span class="nx">Elm</span><span class="p">.</span><span class="nx">Main</span><span class="p">.</span><span class="nx">fullscreen</span><span class="p">({</span><span class="na">apiEndpoint</span><span class="p">:</span> <span class="dl">"</span><span class="s2">localhost:4567</span><span class="dl">"</span><span class="p">});</span>
<span class="nt"></script></span>
</code></pre></div></div>
<p>Of course you’d probably use a templating engine to pass the actual endpoint at runtime.</p>
<p>And there you go. As the next step, have your backend pass the data needed to render the initial page so the browser doesn’t have to immediately make another round trip to the server for it.</p>
<p><strong>Note</strong>: this article is written for Elm 0.17.</p>
<h1 id="references">References</h1>
<ul>
<li><a href="http://package.elm-lang.org/packages/elm-lang/core/4.0.5/Platform#Program">Documentation for the <code class="language-plaintext highlighter-rouge">Program</code> type</a></li>
<li><a href="http://package.elm-lang.org/packages/elm-lang/core/4.0.5/Basics#Never">Documentation for the <code class="language-plaintext highlighter-rouge">Never</code> type</a></li>
<li><a href="http://package.elm-lang.org/packages/elm-lang/html/1.1.0/Html-App#programWithFlags">Documentation for the <code class="language-plaintext highlighter-rouge">programWithFlags</code> function</a></li>
<li><a href="https://groups.google.com/forum/#!topic/elm-discuss/E_2If1LI5OU">Mailing list thread with examples</a></li>
</ul>Mike MazurYou’re starting to play with Elm and have come up with an app which you’d like to start using with a backend. One approach is to have the backend serve an HTML file which loads the JavaScript containing the compiled Elm code and initialize it with Elm.Main.fullscreen():