<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://blog.williammanley.net/feed.xml" rel="self" type="application/atom+xml" /><link href="https://blog.williammanley.net/" rel="alternate" type="text/html" /><updated>2025-10-14T09:31:23+00:00</updated><id>https://blog.williammanley.net/feed.xml</id><title type="html">Will’s Blog</title><subtitle>The technical blog of William Manley</subtitle><author><name>William Manley</name></author><entry><title type="html">On Test-Driving HTML Templates</title><link href="https://blog.williammanley.net/2025/03/24/on-test-driving-html-templates.html" rel="alternate" type="text/html" title="On Test-Driving HTML Templates" /><published>2025-03-24T00:00:00+00:00</published><updated>2025-03-24T00:00:00+00:00</updated><id>https://blog.williammanley.net/2025/03/24/on-test-driving-html-templates</id><content type="html" xml:base="https://blog.williammanley.net/2025/03/24/on-test-driving-html-templates.html"><![CDATA[<p>I recently came across <a href="https://martinfowler.com/articles/tdd-html-templates.html">this article by Matteo Vaccari of Thoughtworks</a> via <a href="https://htmx.org/">HTMX</a> discord.  As part of developing Stb-tester’s web interface we’ve increasingly been moving to a traditional hypermedia approach<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>.  This has meant a different (and I think superior) approach to unit testing than with SPAs.</p>

<p>Some thoughts on the article based on my experience over the last few years unit testing HTML based systems:</p>

<ol>
  <li>
    <p>I definitely agree that testing the HTML output of your system is valuable. I would go further than this too - it’s better to test HTML output than to test the JSON APIs you’d typically have in an SPA architecture. Why?, because your assertions are more likely to be testing the functionality of your system that is relevant to your users.</p>

    <p>With JSON APIs you will often be sending more data than is needed to render the view, and there may be complex logic browser-side to transform the data before it becomes HTML.  With HTML over the wire the HTML that is sent is probably going to be rendered and shown to the user without much additional transformation.</p>
  </li>
  <li>
    <p>Doing the setup by injecting a specifically setup data structure into a template seems too narrow a test - you’re only testing the template in this case, but maybe in production you’ve messed up populating this data structure from the database. Ultimately the properties you’re testing in this case aren’t that useful from a system/end-user perspective.</p>

    <p>Instead we generally will set up the database in some specific state (typically using the code that will do this in production), and then generate the HTML from that.  This works well in terms of churn - you have to be judicious when changing the database schema, so the test is likely to remain valid while you refactor the innards of your implementation, and it’s likely to be testing more of the real code-paths.</p>

    <p>This approach particularly valuable in unit tests because it’s trivial to apply coverage tools (or even coverage based fuzzing) to improve confidence in the tests</p>

    <p>I still consider this unit testing, not integration testing, because it’s still deterministic, single threaded and not time-dependent (no sleeps, no waiting).</p>
  </li>
  <li>
    <p>Using CSS selectors to check specific properties of the HTML is good because it documents exactly what the properties the test is actually trying to assert. It is tedious to write though, requires maintenance in the presence of changes to the markup that wouldn’t actually impact the user, and maybe you’ll miss asserting something that’s actually important.</p>

    <p>Instead we use characterisation testing. We save the generated HTML* to disk when running the test with <code class="language-plaintext highlighter-rouge">$REGENERATE_TEST_DATA=1</code> and when running the test later we check that it hasn’t changed. This makes the tests easy to update when making changes. It also means that when reviewing a change to the code we can also see the <em>result</em> of that change in the git diff</p>
  </li>
  <li>
    <p>The article also touches on characterisation testing under the heading “Bonus level: Stringly asserted”. In our case we are also transforming the HTML before saving - by converting it to markdown using pandoc. This preserves more of the details of the HTML, without having to modify it specifically for the tests (<code class="language-plaintext highlighter-rouge">data-test-icon=</code>, etc).</p>

    <p>There’s a lot more that could be said on the subject of characterisation tests, but I’ll stop now.</p>
  </li>
  <li>
    <p>The article describes characterisation testing (“stringly asserted”) as an alternative to using CSS selectors - but I believe they are complimentary.  You can select an element with a CSS selector, but still render the whole element to text for testing.</p>
  </li>
  <li>
    <p>I’m somewhat sceptical of using a real browser in a unit test.  I’ve found keeping browser integration tests working difficult - particularly making so the browser in CI works exactly the same as the one on the dev’s PC.</p>

    <p>Secondly - and maybe this is just a matter of semantics - but in my mind a unit test should be deterministic, and probably single threaded and synchronous - such that exceptions bubble up from where they were raised, and assertions like “called_once” can be applied.  Using a browser messes with this.  Maybe it would still be possible to keep these properties by mocking out all network calls, and calling back into the test instead.  Could be complicated, but potentially powerful.</p>

    <p>With a hypermedia approach you can get pretty far without involving a browser.  To simulate a click on an <code class="language-plaintext highlighter-rouge">&lt;a href&gt;...</code> tag you can click by CSS selector, but then look up the href and get that from your backend.  Similarly for the HTMX attributes - as long as they’re kept simple enough.</p>
  </li>
</ol>

<h2 id="footnotes">Footnotes</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Traditional Hypermedia to me this means:</p>
      <ol>
        <li>Use built-in HTML functionality where possible (forms, links, etc.).</li>
        <li>HTML over the wire, not JSON</li>
        <li>JavaScript for enhancement - typically with effects scoped to HTML elements explicitly declared in markup, and preferably without dependencies.</li>
      </ol>
      <p><a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>William Manley</name></author><summary type="html"><![CDATA[I recently came across this article by Matteo Vaccari of Thoughtworks via HTMX discord. As part of developing Stb-tester’s web interface we’ve increasingly been moving to a traditional hypermedia approach1. This has meant a different (and I think superior) approach to unit testing than with SPAs. Traditional Hypermedia to me this means: &#8617;]]></summary></entry><entry><title type="html">Primer: facebook’s HTMX from 2010</title><link href="https://blog.williammanley.net/2024/02/20/primer-facebooks-htmx-from-2010.html" rel="alternate" type="text/html" title="Primer: facebook’s HTMX from 2010" /><published>2024-02-20T00:00:00+00:00</published><updated>2024-02-20T00:00:00+00:00</updated><id>https://blog.williammanley.net/2024/02/20/primer-facebooks-htmx-from-2010</id><content type="html" xml:base="https://blog.williammanley.net/2024/02/20/primer-facebooks-htmx-from-2010.html"><![CDATA[<p>I recently discovered this 2010 JSConf presentation via <a href="https://htmx.org/">HTMX</a> discord:</p>

<iframe style="width: 100%; aspect-ratio: 16 / 9" src="https://www.youtube-nocookie.com/embed/wHlyLEPtL9o?si=TCFtMn55aK0vtAlH" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe>

<h1 id="summary">Summary</h1>

<ol>
  <li>At Facebook they had loads of JavaScript and that was causing slow page loads.</li>
  <li>Loading the JavaScript async didn’t work well as the page would render, but be non interactive - which sucks from a UX perspective.</li>
  <li>They realised that the basic operation the JavaScript was performing was making an http request and swapping the new content somewhere in the DOM - so they wrote a 40 line JavaScript function to do this which they included inline in every pages’ <code class="language-plaintext highlighter-rouge">&lt;head&gt;</code><sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>. This gave them instant interactivity.</li>
  <li>They were then going through incrementally replacing as much existing JavaScript with just this library - increasing performance (~5s to ~2.5s page load) and reducing complexity and lines of code.</li>
</ol>

<h1 id="discussion">Discussion</h1>

<p>The motivation was performance. The effect was better performance - but also much less code to write and a simpler overall system.</p>

<p>I’d recommend watching the whole thing, I found it a pleasure.  The author, <a href="https://makinde.adeagbo.com/">Makinde Adeagbo</a>, is a charismatic presenter.  He covers other areas like downloading JS on-demand, native HTML controls and the tooling they used to find interactions that they could apply Primer to.</p>

<p>The actual code can be found here: <a href="https://gist.github.com/makinde/376039">https://gist.github.com/makinde/376039</a>.</p>

<p>The context now than then is very different of course. They were already returning html from the server - so it’s less of a jump to having a generic JavaScript library to do it for you. The point of comparison now would be to a react style SPA with endpoints returning JSON and HTML being generated client-side.</p>

<h2 id="why-is-this-interesting">Why is this interesting?</h2>

<ul>
  <li>The core idea is the same as HTMX, which is currently riding high in the hype cycle - but 14 years ago, and applied within a megacorp. I view the current enthusiasm about HTMX as very much a reaction to SPA misery - both as a user (slow brittle websites) and as a developer (increased complexity and lines of code).  In this case the technology was developed and used at the same time, and within the same organisation as react!</li>
  <li>
    <p>Like many YouTube videos I’m left with the question “What happened next?”.  How widely did they end up applying this?  What happened to the 300 people within facebook at the time that knew about it? It can’t have made that much of an impression on them at the time - they didn’t disperse and evangelise.  Why did react win inside facebook?  Why, why, why?</p>

    <p>Speculation:</p>

    <ul>
      <li>React allowed them to scale their features quickly by having many teams work more independently of each other?</li>
      <li>Something related to the mobile web/apps? At this time it was clear that mobile was going to be the most important client, and maybe they had some ambition for offline only use cases?</li>
      <li>Pure chance - the right people heard about react within facebook at the right time?</li>
      <li>The hypermedia approach was too limited to create the UX that facebook wanted?</li>
    </ul>
  </li>
</ul>

<h1 id="minutiae">Minutiae</h1>

<p>Less interesting asides peripheral to the main point of this blog post:</p>

<ul>
  <li>HTMX is 14kB gzipped.  IMO there’s no point in it being any smaller - unless it could be so small it could be included directly in every <code class="language-plaintext highlighter-rouge">&lt;head&gt;</code> - removing the need for round-trips.  I don’t know what a reasonable threshold would be here.  The default initial TCP receive buffer size is 128kB on my machine, so if it’s possible to fit the TLS negotiation, HTTP headers, the <code class="language-plaintext highlighter-rouge">&lt;head&gt;</code> and at least some content in that size you could display your website within 3 round-trips.</li>
  <li>
    <p>There’s a difference in emphasis WRT motivation between this and HTMX: The problem Makinde set out to solve is clear - facebook is too slow. The pleasant side effect: deleted a bunch of JS. Compare with the motivations on the HTMX website.  They’re more like capabilities phrased as questions than motivations, and they’re certainly more philosophical:</p>

    <ul>
      <li>Why should only <code class="language-plaintext highlighter-rouge">&lt;a&gt;</code> and <code class="language-plaintext highlighter-rouge">&lt;form&gt;</code> be able to make HTTP requests?</li>
      <li>Why should only click &amp; submit events trigger them?</li>
      <li>Why should only GET &amp; POST methods be available?</li>
      <li>Why should you only be able to replace the entire screen?</li>
    </ul>

    <p>Of these the 4th one is critical - the rest are absent from Primer, and arguably relatively unimportant.  However there are critical features in HTMX that Primer doesn’t implement<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup> including:</p>

    <ul>
      <li>Replacing the whole page with working back button behaviour</li>
      <li>URL bar wrangling</li>
      <li>Out-of-band swaps</li>
      <li>Loading indicators</li>
      <li>etc.</li>
    </ul>

    <p>For “Load more comments” buttons most of the above are unnecessary, for avoiding whole page loads when changing pages they are very much necessary.</p>
  </li>
  <li>Makinde also describes downloading JavaScript to be executed in response to user actions - rather than loading all possible javascript at page load time.  I haven’t touched on that here as it’s not a problem I’m interested in - but it may be relevant to you.</li>
</ul>

<h1 id="references">References</h1>

<ul>
  <li><a href="https://twitter.com/htmx_org/status/1753183384493297751">Twitter: Carson Gross on primer</a>.  There’s a bunch of interest in this tweet and the replies.</li>
  <li><a href="https://twitter.com/dan_abramov2/status/1758121064360497192">Twitter: Dan Abramov on facebook history</a></li>
  <li><a href="https://news.ycombinator.com/item?id=39444432">Discuss this on Hacker News</a></li>
  <li><a href="https://twitter.com/jordwalke/status/1753954026620940310">Twitter: @jordwalke on primer limitations</a></li>
  <li><a href="https://www.youtube.com/watch?v=BZmfCjtv6cM">Facebook Front End Tech Talk</a> - a longer presentation on Primer.  <a href="https://archive.org/details/facebook-front-end-tech-talk-8-5-2010-primerjs">Higher quality video at archive.org</a>.</li>
</ul>

<h1 id="footnotes">Footnotes:</h1>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>See also <a href="https://leanrada.com/htmz/">htmz</a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p><a href="https://news.ycombinator.com/item?id=39431985">Hacker news comment: Carson Gross on htmx vs htmz</a> <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>William Manley</name></author><summary type="html"><![CDATA[I recently discovered this 2010 JSConf presentation via HTMX discord:]]></summary></entry><entry><title type="html">Simple declarative schema migration for SQLite</title><link href="https://blog.williammanley.net/2022/04/30/simple-declarative-schema-migration-for-sqlite.html" rel="alternate" type="text/html" title="Simple declarative schema migration for SQLite" /><published>2022-04-30T00:00:00+00:00</published><updated>2022-04-30T00:00:00+00:00</updated><id>https://blog.williammanley.net/2022/04/30/simple-declarative-schema-migration-for-sqlite</id><content type="html" xml:base="https://blog.williammanley.net/2022/04/30/simple-declarative-schema-migration-for-sqlite.html"><![CDATA[<p>See my colleague <a href="https://david.rothlis.net/">David Röthlisberger</a>’s website for an article we authored together:
<a href="https://david.rothlis.net/declarative-schema-migration-for-sqlite/">Simple declarative schema migration for SQLite</a>.</p>

<p>The TL;DR: is:</p>

<ul>
  <li>We store our SQL schema in git and on application startup we modify the existing database so the schema matches what the application was expecting.</li>
  <li>In doing so we don’t need to write any migration code specific to a particular schema change.</li>
  <li>The types of migration we can perform automatically are limited, but in practice these limits are not particularly onerous.</li>
  <li>If we do need to explicitly step outside these limits (e.g. renaming a column) we can, and the auto-migration system will still help with the parts of a migration that it understands.</li>
  <li>This approach works really well with a branching/CI workflow where multiple versions of the database schema may exist in parallel.</li>
</ul>]]></content><author><name>William Manley</name></author><summary type="html"><![CDATA[See my colleague David Röthlisberger’s website for an article we authored together: Simple declarative schema migration for SQLite.]]></summary></entry><entry><title type="html">pip and cargo are not the same</title><link href="https://blog.williammanley.net/2022/02/23/pip-and-cargo-are-not-the-same.html" rel="alternate" type="text/html" title="pip and cargo are not the same" /><published>2022-02-23T00:00:00+00:00</published><updated>2022-02-23T00:00:00+00:00</updated><id>https://blog.williammanley.net/2022/02/23/pip-and-cargo-are-not-the-same</id><content type="html" xml:base="https://blog.williammanley.net/2022/02/23/pip-and-cargo-are-not-the-same.html"><![CDATA[<p>I often see Rust’s cargo package manager dismissed in online discussions by
analogy to pip and npm.  Cargo/rust doesn’t suffer from many of the problems
that pip does.  Some of these reasons are not just about cargo, they’re also
about how Rust is different to Python and about how Cargo and Rust integrate:</p>

<ol>
  <li>There are only venvs with Cargo, so you can’t install crates into a location
where it will interfere with other unrelated rust programs.</li>
  <li>Rust programs are “almost statically linked”.  Typically they only dynamically
link against libc, and possibly a few more common libraries like openssl.</li>
  <li>You don’t need to worry about reproducing the build environment on the host
that will be running the executable.  Usually just copying the executable
over is sufficient (see (2)).</li>
  <li>There is a 1 to 1 relationship between cargo crate names and what gets <code class="language-plaintext highlighter-rouge">use</code>d
in the rust file. With pip your pypi package <code class="language-plaintext highlighter-rouge">moo</code> can include Python package
<code class="language-plaintext highlighter-rouge">foo</code>, or whatever else it likes.</li>
  <li>Similarly you can’t <code class="language-plaintext highlighter-rouge">use</code> something in your rust code that you haven’t asked
for in your <code class="language-plaintext highlighter-rouge">Cargo.toml</code> - transitive dependencies of your dependencies
(mostly) don’t affect you.</li>
  <li>Rust provides many tools for managing privacy, so the public interface of a
package is explicit. This makes it much harder to accidentally depend on
something the crate author considers an implementation detail, making a cargo
update much less likely to break your application than the Python equivalent.</li>
  <li>There is a culture of taking compatibility seriously in the rust ecosystem.
Crates are expected to maintain API compatibility for major versions and
<a href="https://doc.rust-lang.org/cargo/reference/semver.html">Cargo requires the same</a>.</li>
  <li>Cargo requires lockfiles, and can generate a lockfile just based on the
<code class="language-plaintext highlighter-rouge">Cargo.toml</code> and the crates.io index.  Pip has <code class="language-plaintext highlighter-rouge">pip freeze</code>, but that just
captures the packages you’ve got installed.  So it requires that the packages
be installed in the first place, and won’t include packages that are required
on other OSs for example.  <a href="https://pipenv.pypa.io/en/latest/">Pipenv</a> helps here.</li>
  <li>Rust packages tend to be more self-contained than Python ones.  Often Python
packages will be bindings to existing libraries, while Rust ones will be pure
rust.  This means that you run into issues of missing system dependencies far
less often with Cargo than Pip.</li>
  <li>Rust maintains much better backwards compatibility than Python.  Upgrading
to a newer version of Rust for a dependency is very unlikely to break your
build - and if it will break anything it will break at build time, rather
than run time.  Upgrading Python often causes your code or dependencies to break
and requires you upgrade Python on your deployment target as well.  Rust
doesn’t even need to be installed on your deployment target.</li>
  <li>A rust executable can include different versions of the same crate in the
same rust executable - so you don’t need your transitive dependencies to all
agree on the same version if there are compatibility issues.</li>
</ol>

<p>I don’t have any first hand experience with npm, but I believe at least some of
the above will apply there too.</p>]]></content><author><name>William Manley</name></author><summary type="html"><![CDATA[I often see Rust’s cargo package manager dismissed in online discussions by analogy to pip and npm. Cargo/rust doesn’t suffer from many of the problems that pip does. Some of these reasons are not just about cargo, they’re also about how Rust is different to Python and about how Cargo and Rust integrate:]]></summary></entry><entry><title type="html">Neil Brown on the UNIX philosophy</title><link href="https://blog.williammanley.net/2021/12/16/the-unix-philosophy.html" rel="alternate" type="text/html" title="Neil Brown on the UNIX philosophy" /><published>2021-12-16T00:00:00+00:00</published><updated>2021-12-16T00:00:00+00:00</updated><id>https://blog.williammanley.net/2021/12/16/the-unix-philosophy</id><content type="html" xml:base="https://blog.williammanley.net/2021/12/16/the-unix-philosophy.html"><![CDATA[<p>From <a href="https://lwn.net/Articles/576078/">an LWN comment</a> by <a href="https://blog.neil.brown.name">Neil Brown</a>, kernal hacker and author of the <a href="https://lwn.net/Articles/411845/">Ghosts</a> <a href="https://lwn.net/Articles/412131/">of</a> <a href="https://lwn.net/Articles/414618/">Unix</a> <a href="https://lwn.net/Articles/416494/">Past</a> LWN article series (among others):</p>

<blockquote>
  <p>One of the big weaknesses of the “do one job and do it well” approach is that those individual tools didn’t really combine very well. sort, join, cut, paste, cat, grep, comm etc make a nice set of tools for simple text-database work, but they all have slightly different ways of identifying and selecting fields and sort orders etc. You can sort-of stick them together with pipes and shell scripts, but it is rather messy and always error prone.</p>

  <p>I remember being severly disillusioned by this in my early days. I read some article that explained how a “spell” program can be written to report the spelling errors in a file. It uses ‘tr’ to split into words, then “sort” and “uniq” to get a word list, then “comm” to find the differences. “cool” I thought. Then I looked at the actual “spell” program on my university’s Unix installation. It used a special ‘dcomm’ (or something like that) which knew about “dictionary ordering” (Which ignores case - sometimes). Suddenly the whole illusion came shattering down. Lots of separate tools only do 90% of the work. To do really complete work, you need real purpose-built tools. “do one thing and do it well” is good for prototypes, not for final products.</p>

  <p>One thing Unix never gave us was a clear big picture. It was always lots of bits that could mostly be stuck together to mostly work. I spent a good many years as a Unix sysadmin at a University and I got to see a lot of the rough edges and paper over some of them.</p>
</blockquote>

<p>I read this when it was first published and it had quite an effect on my thinking.</p>]]></content><author><name>William Manley</name></author><summary type="html"><![CDATA[From an LWN comment by Neil Brown, kernal hacker and author of the Ghosts of Unix Past LWN article series (among others):]]></summary></entry><entry><title type="html">Seen on HN: Invert shell and terminal</title><link href="https://blog.williammanley.net/2021/03/31/seen-on-hn-invert-shell-and-terminal.html" rel="alternate" type="text/html" title="Seen on HN: Invert shell and terminal" /><published>2021-03-31T00:00:00+00:00</published><updated>2021-03-31T00:00:00+00:00</updated><id>https://blog.williammanley.net/2021/03/31/seen-on-hn-invert-shell-and-terminal</id><content type="html" xml:base="https://blog.williammanley.net/2021/03/31/seen-on-hn-invert-shell-and-terminal.html"><![CDATA[<p>Here’s a <a href="https://news.ycombinator.com/item?id=26617656">comment from Ericson2314</a>
that I agree with wholeheartedly:</p>

<blockquote>
  <p>Invert the shell and terminal: every shell command (with unredirected streams)
gets it’s own pty.</p>
</blockquote>

<p>Read the whole comment for more.</p>

<p>I think this could work well - but unlike many projects intended to improve the
terminal/shell experience it could be implemented in a backward compatible way -
and as such have a chance of being widely adopted.</p>

<p>You’d need to implement a new protocol between terminal and shell.  Maybe the
shell would be in charge - allocating PTYs and multiplexing the output back to
the terminal.  Or maybe the terminal would be in charge where it would allocate
PTYs for children and send them to the shell using FD passing.  There are
advantages both ways.</p>

<p>Integral to its success though would be getting the support for this new
protocol <strong>upstream in bash</strong>.  Bash is the most widely deployed shell and the
only way to get to the point where you can log in to a new machine (or over SSH)
and expect this to “just work”.  Without that it would remain a niche and could
join the graveyard of other improved shells/terminals that approximately no-one
uses.</p>

<p>As such the protocol would have to be minimal and easily implementable in C.
Systemd’s protocols could be an inspiration.  I’ve implemented both sides of
<code class="language-plaintext highlighter-rouge">sd_notify</code> and <code class="language-plaintext highlighter-rouge">LISTEN_FDS</code> before, in more than one programming language and
it was very straightforward.</p>]]></content><author><name>William Manley</name></author><summary type="html"><![CDATA[Here’s a comment from Ericson2314 that I agree with wholeheartedly:]]></summary></entry><entry><title type="html">Merkle trees and build systems</title><link href="https://blog.williammanley.net/2020/05/28/merkle-trees-and-build-systems.html" rel="alternate" type="text/html" title="Merkle trees and build systems" /><published>2020-05-28T00:00:00+00:00</published><updated>2020-05-28T00:00:00+00:00</updated><id>https://blog.williammanley.net/2020/05/28/merkle-trees-and-build-systems</id><content type="html" xml:base="https://blog.williammanley.net/2020/05/28/merkle-trees-and-build-systems.html"><![CDATA[<p>An article written by my colleague <a href="https://david.rothlis.net/">David Röthlisberger</a> in part describing how the build system that we built at stb-tester works and the philosophy behind it:
<a href="https://lwn.net/Articles/821367/">Merkle trees and build systems</a>.</p>

<blockquote>
  <p>In traditional build tools like Make, targets and dependencies are always <em>files</em>. Imagine if you could specify an entire <em>tree</em> (directory) as a dependency: You could exhaustively specify a “build root” filesystem containing the toolchain used for building some target as a dependency of that target. Similarly, a rule that creates that build root would have the tree as its <em>target</em>. Using <a href="https://en.wikipedia.org/wiki/Merkle_tree">Merkle trees</a> as first-class citizens in a build system gives great flexibility and many optimization opportunities. In this article I’ll explore this idea using <a href="https://ostree.readthedocs.io/">OSTree</a>, <a href="https://ninja-build.org/">Ninja</a>, and <a href="https://www.python.org/">Python</a>.</p>
</blockquote>]]></content><author><name>William Manley</name></author><summary type="html"><![CDATA[An article written by my colleague David Röthlisberger in part describing how the build system that we built at stb-tester works and the philosophy behind it: Merkle trees and build systems.]]></summary></entry><entry><title type="html">Unlock software freedom one by using better tools</title><link href="https://blog.williammanley.net/2020/05/25/unlock-software-freedom-one-by-using-better-tools.html" rel="alternate" type="text/html" title="Unlock software freedom one by using better tools" /><published>2020-05-25T00:00:00+00:00</published><updated>2020-05-25T00:00:00+00:00</updated><id>https://blog.williammanley.net/2020/05/25/unlock-software-freedom-one-by-using-better-tools</id><content type="html" xml:base="https://blog.williammanley.net/2020/05/25/unlock-software-freedom-one-by-using-better-tools.html"><![CDATA[<p>An idea that I’ve had burrowing into my mind for quite a few years now is that free software distros are held back by the crap build tools that are used to build software - but the lack of availability of better tools isn’t the cause of the problem.</p>

<p>The <a href="https://www.gnu.org/philosophy/free-sw.html">FSF define</a> software freedom one as:</p>

<blockquote>
  <p>The freedom to study how the program works, and change it so it does your computing as you wish (freedom 1). Access to the source code is a precondition for this.</p>
</blockquote>

<p>In practice if you want to make a modification to your system it’s a massive ball-ache.  In my opinion just getting to the point where you can find the relevant package and build it is harder than making the change in most cases.  I think it’s a real shame that we have these distros full of software with source available, but you’d never think of making small changes because there’s such a hurdle to get over, and that hurdle isn’t knowing programming, or understanding the code - it’s just the inconvenience of building and running your modified version.</p>

<p>Back in 2006 (I think) there was the project called <a href="http://one.laptop.org/">One Laptop Per Child</a>.  One of the ideas behind it was that not only was this a tool to enable children in the developing world to do things like wikipedia access, word processing, and spreadsheets, but that it was a tool that they could mould themselves.  They could change any part of the software that made up the device, safely, in such a way that they couldn’t brick the device and any changes were easily revertible.  The thing that really captured my imagination was a button on the keyboard labelled “View Source”!  Imagine that!  You’re using a piece of software and you want to know how it works, or you want it to work differently and you press the button, and there it is in the editor.  You could make a change and build and run it in one click. Perhaps with another click you could share your changes with the world.</p>

<p>Blam! Suddenly the four freedoms aren’t some abstract idea the advantages of which are reserved for professional software engineers, but they’re available in practice to a much larger audience of interested amateurs.  In particular, freedom 1, the freedom that is least convenient to exercise now:  “The freedom to study how the program works, and change it so it does your computing as you wish” was previously mostly academic to all but the tiniest fraction of users of the software, but is now much broader.</p>

<p>Included in this group of interested amateurs are children.  I can imagine myself as a child pressing that button just to see what happens? what does it look like? What happens if I…?  Suddenly it becomes play rather than study and discipline and work.</p>

<p>I don’t know how (or if) the view source button ever worked, but I find the idea behind it is exciting.</p>

<p>We can see what children (and adults) are capable of when given the right environment in their amazing Minecraft creations.  It doesn’t matter that the environment is constrained, it just matters that the initial barrier to entry is low enough.  See also the boot to BASIC BBC micro.  You turn the machine on and there you are, ready to start.  Along with similar machines it spawned an entire industry of software engineers.</p>

<hr />

<p>So if the main blocks to this are getting the source and then building the software, what is the solution?  I think Google has built (but more importantly - uses) tools that solve both these problems.</p>

<p>Internally Google<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> have a build system called Blaze (see also its open-source cousin Basel). It’s fast at building software at staggering scale.  Scale larger than any Linux distro.  How is it capable of doing this?  All build steps are reproducible - this in-turn makes them cacheable.  Secondly the tool has a complete view of the entire build-dependency graph, right back to each source-file checked into the repo. What does this mean? It means that when you change a source file, be it a C file or a header or some code-generation bit, all the dependents are built, but no more.  It means that they can be all built in parallel across a fleet of machines, so you get your results promptly.  It means that if a generated file would not be affected by that change it is not built.</p>

<p>Contrast this with a typical Linux distro, binary or source based.  The concept of packages cuts through the build graph at a level of fixed scale.  We define dependencies between packages, and within any particular package there is a build system that defines the finer grained dependencies. This <code class="language-plaintext highlighter-rouge">.so</code>, depends on this object file that depends on this source file.  Change the source of a man page in openoffice?  You rebuild the whole package including all the source files and running big fat linking steps.  Add a comment to a source file in a library?  Do the dependent packages need to be rebuilt?  That’s a decision that needs to be taken manually every time.</p>

<p>With traditional binary distros this is made kind-of tractable by dynamic linking - allowing you to delay the final steps of constructing your system to run-time.  I think dynamic linking makes sense where runtime selection of code is genuinely needed, and IPC has too much overhead.  Examples include using the right GL library for your hardware, or adding new <a href="https://gstreamer.freedesktop.org/">GStreamer</a> elements for decoding some new video format.  It’s also not so onerous for cases where the ABI is small, clear and the library has few dependencies of its own.  It can be necessary for proprietary software, where rebuilding to fix an issue in a dependency isn’t possible . But in a lot of cases it’s there just to work around the way that software is packaged, delaying linking to run-time because rebuilding is just too onerous.</p>

<p>Google uses open-source software packages internally.  How do they deal with the fact that OSS comes as packages with their own build systems?  How do they cope with not having global visibility of the build graph? The answer is that they don’t. If you want to use an open-source dependency in your google project you need to convert the build system to Blaze.  This only has to happen once for any project.  Popular open-source libraries will already have been converted, so chances are that you can just reuse that effort.  By doing so the huge proprietary megacorp gains more of the benefits of the fact that the software is free (as in freedom) than the free-software distros can!</p>

<hr />

<p>That covers building the software, what about getting the source?  Within google every developer effectively has the entire Google codebase checked out on their machine, as a FUSE filesystem.  It’s called <a href="https://cacm.acm.org/magazines/2016/7/204032-why-google-stores-billions-of-lines-of-code-in-a-single-repository/fulltext">“Client in the Cloud”</a>.  So you want the source locally?  It’s already there. Imagine this applied to Debian.  You want to make a change to openoffice, but it’s huge and you don’t have space for the whole thing?  It’s ok, it’s already there. Microsoft are developing a system for git that can handle repos of the scale of the whole of Debian. It’s open-source, but currently Windows only.  See <a href="https://vfsforgit.org/">“VFS for Git”</a>.</p>

<hr />

<p>What would such a distro look like?  I’ve seen one example of this from a few years ago <a href="https://github.com/gittup/gittup">gittup</a>.  It consists of a git repo containing a submodule for every package.  Each package has been modified to build using the tup build system.  They can now make a change to any file and run a build and they get a new system in seconds. Check out the <a href="http://gittup.org/gittup/">website</a>, particularly the section “What gittup.org does that nobody else can”. It gives you a taste for what might be possible. The tools used in gittup wouldn’t scale to the size of Debian, on the other hand we know tools that will, or could.  But it’s not about the tools…</p>

<p>The challenge is the sheer amount of software to be packaged is tremendous.  Having a complete view of the build graph requires converting every package to the same build system. This requires many people, each with some level of expertise in their package, all pulling in the same direction, all choosing the same tooling. It’s a social problem, rather than a technical one.</p>

<p>As Antoine de Saint-Exupéry said:</p>

<blockquote>
  <p>“If you want to build a ship, don’t drum up the men to gather wood, divide the work, and give orders. Instead, teach them to yearn for the vast and endless sea.”</p>
</blockquote>

<p>So this is my attempt to generate that yearning.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Note: I’ve never worked for Google, this is just my understanding from information google has published and talking to googlers and ex-googlers. Ultimately whether what I say about how things work at google is true or not is irrelevant to the substance of this article.  This is about how the free software ecosystem and distros in particular could be different, and better.  Google is a strawman in this instance I’ve used to make the ideas more concrete. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>William Manley</name></author><summary type="html"><![CDATA[An idea that I’ve had burrowing into my mind for quite a few years now is that free software distros are held back by the crap build tools that are used to build software - but the lack of availability of better tools isn’t the cause of the problem.]]></summary></entry><entry><title type="html">Improve integration test sandboxing with systemd socket passing</title><link href="https://blog.williammanley.net/2014/01/26/Improve-integration-test-sandboxing-with-systemd-socket-passing.html" rel="alternate" type="text/html" title="Improve integration test sandboxing with systemd socket passing" /><published>2014-01-26T12:04:21+00:00</published><updated>2014-01-26T12:04:21+00:00</updated><id>https://blog.williammanley.net/2014/01/26/Improve-integration-test-sandboxing-with-systemd-socket-passing</id><content type="html" xml:base="https://blog.williammanley.net/2014/01/26/Improve-integration-test-sandboxing-with-systemd-socket-passing.html"><![CDATA[<p>TL;DR version: Improve integration test sandboxing with <a href="http://0pointer.de/public/systemd-man/sd_listen_fds.html">systemd socket passing</a>.  You can allocate a random port for your daemon and don’t need to wait for the daemon to start up to run your test.  It’s fast, robust, race-free and <strong>doesn’t depend on systemd</strong>.</p>

<p><a href="http://julien.danjou.info">Julien Danjou</a> recently wrote an excellent blog post on <a href="http://julien.danjou.info/blog/2014/db-integration-testing-strategies-python">Database integration testing strategies with Python</a>.    In it he discusses integration tests which require databases, but the advice is applicable to integration tests which require any external process.  For one of these tests he:</p>

<ol>
  <li>Chooses a port to listen on then starts the database server with the port number passed in as configuration.</li>
  <li>Waits for it to start up by grepping stdout.</li>
  <li>Runs the test.</li>
  <li>Tears the database server down.</li>
</ol>

<p>This is great advice but can be improved upon.  One weakness to this approach is if the port you’ve selected is already in use your test will fail.  This can be the case if the port you’ve chosen just happens to be in-use or if you’re running multiple tests in parallel.</p>

<p>The solution is to use a random unused port each time you run the test.  UNIX allows you to do this by asking <a href="http://linux.die.net/man/2/bind"><code class="language-plaintext highlighter-rouge">bind</code></a> (Python: <a href="http://docs.python.org/2/library/socket.html#socket.socket.bind"><code class="language-plaintext highlighter-rouge">socket.bind</code></a>) to bind to port 0.  You can then ask <a href="http://linux.die.net/man/2/getsockname"><code class="language-plaintext highlighter-rouge">getsockname()</code></a> (Python: <a href="http://docs.python.org/2/library/socket.html#socket.socket.getsockname"><code class="language-plaintext highlighter-rouge">socket.getsockname()</code></a>) to find out which port was actually used.  But <strong>you can’t know which port you’re going to bind to before you’ve bound to it</strong>.  If you choose an unused port at random then later try to bind to it another process may have beaten you to it and it may already be in use.</p>

<p>So one way to do this would be to tell the server you’re starting to choose a random port, wait for it to be ready and then find out what port it has chosen.  Maybe this involves grepping through logs or making IPC calls.</p>

<p>I like to use a different technique: open the sockets myself and then pass them to the daemon.  This way I don’t need to wait for the daemon to start-up and don’t need to inspect it’s logs or query it.  I use the <a href="http://0pointer.de/public/systemd-man/sd_listen_fds.html">systemd socket passing protocol</a> which some daemons support anyway.  This means I open a listening socket, and then start my daemon with the environment variable <code class="language-plaintext highlighter-rouge">LISTEN_FDS=1</code> to tell it that I am passing a socket to it and that socket is fd #3†.</p>

<p>I’ve written a small utility - sd-popen.py to do this for me.  An example:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ # Show that the LISTEN_FDS environment variable is set in the child:
$ ./sd-popen.py --outfile=env.log env
LAUNCHED_PORT=32814
LAUNCHED_PID=18150
$ grep LISTEN env.log
LISTEN_PID=18150
LISTEN_FDS=1

$ # Show that the socket is open in the spawned process:
$ ./sd-popen.py sleep 50
LAUNCHED_PORT=48729
LAUNCHED_PID=18898
$ netstat -lp | grep 48729
tcp        0      0 *:48729                 *:*                     LISTEN      18898/sleep     
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">sd-popen</code> opens a socket, binds it to port 0, spawns the command passed to it with <code class="language-plaintext highlighter-rouge">LISTEN_FDS</code> and <code class="language-plaintext highlighter-rouge">LISTEN_PID</code> set and then prints the pid and port to stdout before exiting.</p>

<p>The output from <code class="language-plaintext highlighter-rouge">sd-popen</code> is compatible with shell so we can write shell scripts like:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export $(./sd-popen.py my-webserver)
wget http://localhost:$LISTEN_PORT/foobar.html
kill ${LAUNCHED_PID}
</code></pre></div></div>

<p>All without waiting or worries about port clashes.</p>

<p>This is what sd-popen.py looks like:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/python
</span><span class="kn">import</span> <span class="nn">socket</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">argparse</span>
<span class="kn">import</span> <span class="nn">subprocess</span>

<span class="k">def</span> <span class="nf">main</span><span class="p">(</span><span class="n">argv</span><span class="p">):</span>
    <span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="p">.</span><span class="n">ArgumentParser</span><span class="p">()</span>
    <span class="n">parser</span><span class="p">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">'cmd'</span><span class="p">,</span> <span class="n">help</span><span class="o">=</span><span class="s">'Command to run'</span><span class="p">)</span>
    <span class="n">parser</span><span class="p">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">'args'</span><span class="p">,</span> <span class="n">nargs</span><span class="o">=</span><span class="n">argparse</span><span class="p">.</span><span class="n">REMAINDER</span><span class="p">,</span>
                        <span class="n">help</span><span class="o">=</span><span class="s">'Command arguments'</span><span class="p">)</span>
    <span class="n">parser</span><span class="p">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">'--outfile'</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="s">'/dev/null'</span><span class="p">,</span>
                        <span class="nb">type</span><span class="o">=</span><span class="n">argparse</span><span class="p">.</span><span class="n">FileType</span><span class="p">(</span><span class="s">'w'</span><span class="p">),</span>
                        <span class="n">help</span><span class="o">=</span><span class="s">'File to redirect stdout and stderr to'</span><span class="p">)</span>
    <span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="p">.</span><span class="n">parse_args</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">:])</span>

    <span class="n">s</span> <span class="o">=</span> <span class="n">socket</span><span class="p">.</span><span class="n">socket</span><span class="p">(</span><span class="n">socket</span><span class="p">.</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">socket</span><span class="p">.</span><span class="n">SOCK_STREAM</span><span class="p">)</span>
    <span class="n">s</span><span class="p">.</span><span class="n">bind</span><span class="p">((</span><span class="s">''</span><span class="p">,</span> <span class="mi">0</span><span class="p">))</span>
    <span class="n">_</span><span class="p">,</span> <span class="n">port</span> <span class="o">=</span> <span class="n">s</span><span class="p">.</span><span class="n">getsockname</span><span class="p">()</span>
    <span class="n">s</span><span class="p">.</span><span class="n">listen</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>

    <span class="n">p</span> <span class="o">=</span> <span class="n">sd_popen</span><span class="p">([</span><span class="n">args</span><span class="p">.</span><span class="n">cmd</span><span class="p">]</span> <span class="o">+</span> <span class="n">args</span><span class="p">.</span><span class="n">args</span><span class="p">,</span> <span class="p">[</span><span class="n">s</span><span class="p">.</span><span class="n">fileno</span><span class="p">()],</span> <span class="n">stdout</span><span class="o">=</span><span class="n">args</span><span class="p">.</span><span class="n">outfile</span><span class="p">,</span>
                 <span class="n">stdin</span><span class="o">=</span><span class="nb">open</span><span class="p">(</span><span class="s">'/dev/null'</span><span class="p">,</span> <span class="s">'r'</span><span class="p">),</span> <span class="n">stderr</span><span class="o">=</span><span class="n">subprocess</span><span class="p">.</span><span class="n">STDOUT</span><span class="p">)</span>
    <span class="n">sys</span><span class="p">.</span><span class="n">stdout</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="s">'LAUNCHED_PORT=%i</span><span class="se">\n</span><span class="s">LAUNCHED_PID=%i</span><span class="se">\n</span><span class="s">'</span> <span class="o">%</span> <span class="p">(</span><span class="n">port</span><span class="p">,</span> <span class="n">p</span><span class="p">.</span><span class="n">pid</span><span class="p">))</span>
    <span class="k">return</span> <span class="mi">0</span>


<span class="k">def</span> <span class="nf">sd_popen</span><span class="p">(</span><span class="n">args</span><span class="p">,</span> <span class="n">sockets</span><span class="p">,</span> <span class="n">preexec_fn</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="o">*</span><span class="n">aargs</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
    <span class="kn">import</span> <span class="nn">os</span><span class="p">,</span> <span class="n">subprocess</span>
    <span class="k">def</span> <span class="nf">remap_ports</span><span class="p">():</span>
        <span class="n">sockets_</span> <span class="o">=</span> <span class="n">sockets</span>
        <span class="n">os</span><span class="p">.</span><span class="n">environ</span><span class="p">[</span><span class="s">"LISTEN_PID"</span><span class="p">]</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">os</span><span class="p">.</span><span class="n">getpid</span><span class="p">())</span>
        <span class="n">os</span><span class="p">.</span><span class="n">environ</span><span class="p">[</span><span class="s">"LISTEN_FDS"</span><span class="p">]</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">sockets_</span><span class="p">))</span>
        <span class="k">for</span> <span class="n">new_fd</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">1024</span><span class="p">):</span>
            <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">sockets_</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">:</span>
                <span class="k">try</span><span class="p">:</span>
                    <span class="n">os</span><span class="p">.</span><span class="n">close</span><span class="p">(</span><span class="n">new_fd</span><span class="p">)</span>
                <span class="k">except</span><span class="p">:</span>
                    <span class="k">pass</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="n">oldfd</span> <span class="o">=</span> <span class="n">sockets_</span><span class="p">.</span><span class="n">pop</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
                <span class="k">if</span> <span class="n">new_fd</span> <span class="o">!=</span> <span class="n">oldfd</span><span class="p">:</span>
                    <span class="k">if</span> <span class="n">new_fd</span> <span class="ow">in</span> <span class="n">sockets_</span><span class="p">:</span>
                        <span class="n">replacement_fd</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">dup</span><span class="p">(</span><span class="n">new_fd</span><span class="p">)</span>
                        <span class="n">sockets_</span> <span class="o">=</span> <span class="p">[</span><span class="n">replacement_fd</span> <span class="k">if</span> <span class="n">fd</span> <span class="o">==</span> <span class="n">new_fd</span> <span class="k">else</span> <span class="n">fd</span>
                                    <span class="k">for</span> <span class="n">fd</span> <span class="ow">in</span> <span class="n">sockets_</span><span class="p">]</span>
                    <span class="n">os</span><span class="p">.</span><span class="n">dup2</span><span class="p">(</span><span class="n">oldfd</span><span class="p">,</span> <span class="n">new_fd</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">preexec_fn</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span>
            <span class="n">preexec_fn</span><span class="p">()</span>
    <span class="k">return</span> <span class="n">subprocess</span><span class="p">.</span><span class="n">Popen</span><span class="p">(</span><span class="n">args</span><span class="p">,</span> <span class="n">preexec_fn</span><span class="o">=</span><span class="n">remap_ports</span><span class="p">,</span>
                            <span class="n">close_fds</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="o">*</span><span class="n">aargs</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>

<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">'__main__'</span><span class="p">:</span>
    <span class="n">sys</span><span class="p">.</span><span class="nb">exit</span><span class="p">(</span><span class="n">main</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">))</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">sd_popen</code> is essentially an extension to Python’s <code class="language-plaintext highlighter-rouge">subprocess.Popen</code> but allows passing a list of fds to be passed to the client process.  In this example we open a socket in <code class="language-plaintext highlighter-rouge">main()</code> and pass it to the subprocess before printing the socket and subprocess details.  <code class="language-plaintext highlighter-rouge">sd_popen</code> is complicated by the <code class="language-plaintext highlighter-rouge">dup2</code> dance to rearrange the file-descriptor numbers but is itself a fairly generic function for launching programs with the socket passing protocol.</p>

<p>One thing to note: there’s nothing non-portable in the above code.  It doesn’t depend on systemd and should run fine on any unix system.  It does depend on the daemon having socket passing support but that’s <a href="http://0pointer.de/blog/projects/socket-activation.html">easy to add</a> and there’s nothing non-portable about it either.</p>

<p>I’ve used this technique for writing a <a href="https://github.com/jech/polipo/pull/8">simple test suite</a> for the <a href="http://www.pps.univ-paris-diderot.fr/~jch/software/polipo/">polipo</a> caching HTTP proxy.  The tests are written in a combination of <a href="https://github.com/wmanley/polipo/blob/b2db672cc3ceead0d32b38bd1ed536163cc26bd8/test/run-test.sh">shell</a> and <a href="https://github.com/wmanley/polipo/blob/b2db672cc3ceead0d32b38bd1ed536163cc26bd8/test/sd-launch.c">C</a>.  I also use it in my prototype <a href="https://github.com/wmanley/http-dbus-bridge/blob/master/test.sh">http-dbus-bridge</a> which is a combination of shell and Python.</p>

<p>At the end of Julien Danjou’s blog post he writes:</p>

<blockquote>
  <p>To speed up tests run, you could also run the test in parallel. It can be interesting as you’ll be able to spread the workload among a lot of different CPUs. However, note that it can require a different database for each test or a locking mechanism to be in place. It’s likely that your tests won’t be able to work altogether at the same time on only one database.</p>
</blockquote>

<p>I say - start a different database for each test using the <a href="http://0pointer.de/public/systemd-man/sd_listen_fds.html"><code class="language-plaintext highlighter-rouge">LISTEN_FDS</code>/<code class="language-plaintext highlighter-rouge">LISTEN_PID</code> socket passing protocol</a>.</p>

<p>†: fd 3 is the next one after stdin (0), stdout (1) and stderr (2).</p>]]></content><author><name>William Manley</name></author><summary type="html"><![CDATA[TL;DR version: Improve integration test sandboxing with systemd socket passing. You can allocate a random port for your daemon and don’t need to wait for the daemon to start up to run your test. It’s fast, robust, race-free and doesn’t depend on systemd.]]></summary></entry></feed>