https://jonleighton.name/Jon Leighton2021-03-06T00:00:00ZJon Leightonhttps://jonleighton.name/tag:jonleighton.name,2021-03-06:/2021/asynchronous-browser-tests-with-phoenix/Asynchronous browser tests with Phoenix (making our tests faster đ)2021-03-06T00:00:00Z2021-03-06T00:00:00Z<p>Recently I have been working on a project using <a href="https://elixir-lang.org/">Elixir</a> and its web framework, <a href="https://www.phoenixframework.org/">Phoenix</a>. I have enjoyed learning many new things but have thus far been too lazy to write about them. However thereâs a time for everything, so let me tell you about something interesting I did this week!</p>
<p>In our project we are using <a href="https://hexdocs.pm/wallaby/readme.html">Wallaby</a> for full-stack browser testing, but the technique discussed here is not specific to Wallaby.</p>
<p>We are also using <a href="https://hexdocs.pm/mox/Mox.html">Mox</a> to mock and stub various calls to external APIs during our tests. Unfortunately that meant we had many Wallaby tests that could not be run <a href="https://hexdocs.pm/ex_unit/ExUnit.Case.html">asynchronously</a>.</p>
<p>When Wallaby fires up a browser, the browser makes a request to the applicationâs <a href="https://hexdocs.pm/phoenix/plug.html">Plug</a> endpoint. The request is handled by a different process than the one that is running the test. If two tests are running at the same time, and they both mock out the same function, how is Mox to know which mock to use? The HTTP request could have originated from either test.</p>
<p>The easy and slow solution is to put Mox into <a href="https://hexdocs.pm/mox/Mox.html#set_mox_global/1">global mode</a>, but that means we cannot run those tests asynchronously. We did this until now, but I wanted to add a âclockâ mock to allow us to stub out the current time for time-dependent tests. This would force many more tests to become synchronous, so I wanted to find another way.</p>
<p>The better solution is to use <a href="https://hexdocs.pm/mox/Mox.html#allow/3"><code>Mox.allow/3</code></a> to share expectations and stubs between processes. The process that defines an expectation or stub is the âownerâ of that definition. We can then explicitly share the definition with another process:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">Mox</span><span class="o">.</span><span class="n">allow</span><span class="p">(</span><span class="no">MyMock</span><span class="p">,</span> <span class="n">owner_pid</span><span class="p">,</span> <span class="n">pid_to_share_with</span><span class="p">)</span>
</code></pre></div></div>
<p>This can be done in any process, because weâre passing in the relevant PIDs explicitly. This fact will prove useful later on!</p>
<p>So my problem was this: how can a test know the PID of the process that will accept the HTTP request that it is triggering? It canât. That process doesnât even exist at the moment that weâd want to know its PID.</p>
<p>I was vaguely aware that Ecto has a <a href="https://hexdocs.pm/ecto_sql/Ecto.Adapters.SQL.Sandbox.html">sandbox</a> for database connections, but had never really dug into the details to properly understand how it works. Somehow, Wallaby enables the database connection used in a test to also be used by the process receiving an HTTP request from the browser. The Phoenix generators and <a href="https://hexdocs.pm/wallaby/Wallaby.Feature.html"><code>Wallaby.Feature</code></a> set this up for us so itâs not something Iâd given a lot of thought to previously.</p>
<p>The Wallaby docs <a href="https://hexdocs.pm/wallaby/0.28.0/readme.html#phoenix">talk about</a> adding the <a href="https://hexdocs.pm/phoenix_ecto/4.1.0/Phoenix.Ecto.SQL.Sandbox.html"><code>Phoenix.Ecto.SQL.Sandbox</code></a> plug to your applicationâs endpoint in order to enable asynchronous tests. It took me a minute to realise that this module is in the <code>phoenix_ecto</code> package, and is not the same thing as <code>Ecto.Adapters.SQL.Sandbox</code>, which is in <code>ecto_sql</code> package.</p>
<p>Reading the <code>Wallaby.Feature</code> source code, I could see that for an asynchronous test it was basically doing two things when a test runs.</p>
<p>Firstly, its <a href="https://github.com/elixir-wallaby/wallaby/blob/1a66c31af05cb83c47c38355e93feee4c2d5d461/lib/wallaby/feature.ex#L174">checks out a database connection</a>, making the test process the owner of that connection:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="ss">:ok</span> <span class="o">=</span> <span class="no">Ecto</span><span class="o">.</span><span class="no">Adapters</span><span class="o">.</span><span class="no">SQL</span><span class="o">.</span><span class="no">Sandbox</span><span class="o">.</span><span class="n">checkout</span><span class="p">(</span><span class="n">repo</span><span class="p">)</span>
</code></pre></div></div>
<p>Then, it <a href="https://github.com/elixir-wallaby/wallaby/blob/1a66c31af05cb83c47c38355e93feee4c2d5d461/lib/wallaby/feature.ex#L184">calls</a> <a href="https://hexdocs.pm/phoenix_ecto/4.1.0/Phoenix.Ecto.SQL.Sandbox.html#metadata_for/2"><code>Phoenix.Ecto.SQL.Sandbox.metadata_for/2</code></a> to get some âmetadataâ:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">Phoenix</span><span class="o">.</span><span class="no">Ecto</span><span class="o">.</span><span class="no">SQL</span><span class="o">.</span><span class="no">Sandbox</span><span class="o">.</span><span class="n">metadata_for</span><span class="p">(</span><span class="n">repos</span><span class="p">,</span> <span class="n">self</span><span class="p">())</span>
</code></pre></div></div>
<p><code>self()</code> gives the PID of the current process, which is the one running the test. HmmmâŚ</p>
<p>That metadata is passed through several more layers, but eventually winds up being passed to the module which orchestrates whichever browser weâre running our test with. In our case, that was <code>Wallaby.Chrome</code>, and the metadata (which is a map at this point) is encoded to a string and <a href="https://github.com/elixir-wallaby/wallaby/blob/1a66c31af05cb83c47c38355e93feee4c2d5d461/lib/wallaby/chrome.ex#L425-L429">added to the browserâs <code>User-Agent</code> header</a> that it sends when making HTTP requests:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">user_agent</span> <span class="o">=</span>
<span class="no">Metadata</span><span class="o">.</span><span class="n">append</span><span class="p">(</span>
<span class="s2">"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"</span><span class="p">,</span>
<span class="n">opts</span><span class="p">[</span><span class="ss">:metadata</span><span class="p">]</span>
<span class="p">)</span>
</code></pre></div></div>
<p>The metadata generated by <code>Phoenix.Ecto.SQL.Sandbox.metadata_for/2</code> contains <a href="https://github.com/phoenixframework/phoenix_ecto/blob/0e6e799d571ae77f6482c2442395d1bca968eb78/lib/phoenix_ecto/sql/sandbox.ex#L157-L159">two pieces of information</a>: the Ecto repo module (which is an atom) and the pid that is passed in:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="n">metadata_for</span><span class="p">(</span><span class="n">repo_or_repos</span><span class="p">,</span> <span class="n">pid</span><span class="p">)</span> <span class="ow">when</span> <span class="n">is_pid</span><span class="p">(</span><span class="n">pid</span><span class="p">)</span> <span class="k">do</span>
<span class="p">%{</span><span class="ss">repo:</span> <span class="n">repo_or_repos</span><span class="p">,</span> <span class="ss">owner:</span> <span class="n">pid</span><span class="p">}</span>
<span class="k">end</span>
</code></pre></div></div>
<p>So, Wallaby is generating the metadata with the pid of the process running our test, and that putting that metadata into the <code>User-Agent</code> header that will be sent to the applicationâs endpoint.</p>
<p>What happens when the request hits the endpoint? Well, the <code>Phoenix.Ecto.SQL.Sandbox</code> plug <a href="https://github.com/phoenixframework/phoenix_ecto/blob/0e6e799d571ae77f6482c2442395d1bca968eb78/lib/phoenix_ecto/sql/sandbox.ex#L135-L142">picks it up here</a>:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="n">call</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="p">%{</span><span class="ss">header:</span> <span class="n">header</span><span class="p">,</span> <span class="ss">sandbox:</span> <span class="n">sandbox</span><span class="p">})</span> <span class="k">do</span>
<span class="n">_result</span> <span class="o">=</span>
<span class="n">conn</span>
<span class="o">|></span> <span class="n">extract_metadata</span><span class="p">(</span><span class="n">header</span><span class="p">)</span>
<span class="o">|></span> <span class="n">allow_sandbox_access</span><span class="p">(</span><span class="n">sandbox</span><span class="p">)</span>
<span class="n">conn</span>
<span class="k">end</span>
</code></pre></div></div>
<p>It parses out the metadata from the <code>User-Agent</code> header, and then <code>allow_sandbox_access()</code> effectively does this:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">Ecto</span><span class="o">.</span><span class="no">Adapters</span><span class="o">.</span><span class="no">SQL</span><span class="o">.</span><span class="no">Sandbox</span><span class="o">.</span><span class="n">allow</span><span class="p">(</span><span class="n">repo</span><span class="p">,</span> <span class="n">owner_pid_from_metadata</span><span class="p">,</span> <span class="n">self</span><span class="p">())</span>
</code></pre></div></div>
<p>The process receiving the HTTP request is able to obtain the pid of the process running the test that triggered it (the âownerâ), and then use <a href="https://hexdocs.pm/ecto_sql/Ecto.Adapters.SQL.Sandbox.html#allow/4"><code>Ecto.Adapters.SQL.Sandbox.allow/4</code></a> to share the ownerâs database connection with itself.</p>
<p>This is very similar to the <code>Mox.allow/3</code> call we want to make! â¨</p>
<p>I initially thought I would need to use <code>Phoenix.Ecto.SQL.Sandbox</code> as inspiration for a new, custom plug that would call <code>Mox.allow/3</code>. I could re-use the public functions of <code>Phoenix.Ecto.SQL.Sandbox</code> for some of the heavy lifting. But then I realised that <code>Phoenix.Ecto.SQL.Sandbox</code> doesnât <em>have</em> to call <code>Ecto.Adapter.SQL.Sandbox.allow()</code> â it has a <code>:sandbox</code> option that I could use to make it call an <code>allow()</code> function on any given module!</p>
<p>So I defined my own sandbox module, in <code>test/support/sandbox.ex</code>, like this:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">defmodule</span> <span class="no">MyApp</span><span class="o">.</span><span class="no">Sandbox</span> <span class="k">do</span>
<span class="k">def</span> <span class="n">allow</span><span class="p">(</span><span class="n">repo</span><span class="p">,</span> <span class="n">owner_pid</span><span class="p">,</span> <span class="n">child_pid</span><span class="p">)</span> <span class="k">do</span>
<span class="no">Ecto</span><span class="o">.</span><span class="no">Adapters</span><span class="o">.</span><span class="no">SQL</span><span class="o">.</span><span class="no">Sandbox</span><span class="o">.</span><span class="n">allow</span><span class="p">(</span><span class="n">repo</span><span class="p">,</span> <span class="n">owner_pid</span><span class="p">,</span> <span class="n">child_pid</span><span class="p">)</span>
<span class="no">Mox</span><span class="o">.</span><span class="n">allow</span><span class="p">(</span><span class="no">MyMock</span><span class="p">,</span> <span class="n">owner_pid</span><span class="p">,</span> <span class="n">child_pid</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>I then altered <code>config/test.exs</code> to change this:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">config</span> <span class="ss">:my_app</span><span class="p">,</span> <span class="ss">:sql_sandbox</span><span class="p">,</span> <span class="no">true</span>
</code></pre></div></div>
<p>To this:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">config</span> <span class="ss">:my_app</span><span class="p">,</span> <span class="ss">:sandbox</span><span class="p">,</span> <span class="no">MyApp</span><span class="o">.</span><span class="no">Sandbox</span>
</code></pre></div></div>
<p>And updated <code>lib/endpoint.ex</code>:</p>
<div class="language-elixir highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="n">sandbox</span> <span class="o">=</span> <span class="no">Application</span><span class="o">.</span><span class="n">get_env</span><span class="p">(</span><span class="ss">:my_app</span><span class="p">,</span> <span class="ss">:sandbox</span><span class="p">)</span> <span class="k">do</span>
<span class="n">plug</span> <span class="no">Phoenix</span><span class="o">.</span><span class="no">Ecto</span><span class="o">.</span><span class="no">SQL</span><span class="o">.</span><span class="no">Sandbox</span><span class="p">,</span> <span class="ss">sandbox:</span> <span class="n">sandbox</span>
<span class="k">end</span>
</code></pre></div></div>
<p>(I removed the <code>sql_</code> prefix because itâs no longer purely about the database.)</p>
<p>And voila, I was able to make all our Mox-using browser tests asychronous. This almost halved the time it took to run <code>mix test</code> on our CI! đ</p>
tag:jonleighton.name,2020-12-06:/2020/centennial-glen-canyon/Centennial Glen Canyon2020-12-06T00:00:00Z2020-12-06T00:00:00Z<figure class="image fill captioned"><img srcset="/static/_DSC7559.c4e577b216.jpg?nf_resize=fit&w=600 600w, /static/_DSC7559.c4e577b216.jpg?nf_resize=fit&w=1200 1200w, /static/_DSC7559.c4e577b216.jpg?nf_resize=fit&w=1800 1800w, /static/_DSC7559.c4e577b216.jpg?nf_resize=fit&w=2400 2400w, /static/_DSC7559.c4e577b216.jpg?nf_resize=fit&w=3000 3000w, /static/_DSC7559.c4e577b216.jpg?nf_resize=fit&w=3600 3600w, /static/_DSC7559.c4e577b216.jpg?nf_resize=fit&w=4200 4200w, /static/_DSC7559.c4e577b216.jpg?nf_resize=fit&w=4800 4800w, /static/_DSC7559.c4e577b216.jpg?nf_resize=fit&w=5400 5400w, /static/_DSC7559.c4e577b216.jpg?nf_resize=fit&w=6000 6000w" src="/static/_DSC7559.c4e577b216.jpg" loading="lazy" sizes="100vw"><figcaption><p>Centennial Glen Canyon, Blue Mountains, New South Wales</p>
</figcaption></figure>
<figure class="image fill"><img srcset="/static/_DSC7542.aa931e598c.jpg?nf_resize=fit&w=600 600w, /static/_DSC7542.aa931e598c.jpg?nf_resize=fit&w=1200 1200w, /static/_DSC7542.aa931e598c.jpg?nf_resize=fit&w=1800 1800w, /static/_DSC7542.aa931e598c.jpg?nf_resize=fit&w=2400 2400w, /static/_DSC7542.aa931e598c.jpg?nf_resize=fit&w=3000 3000w, /static/_DSC7542.aa931e598c.jpg?nf_resize=fit&w=3600 3600w, /static/_DSC7542.aa931e598c.jpg?nf_resize=fit&w=4200 4200w, /static/_DSC7542.aa931e598c.jpg?nf_resize=fit&w=4800 4800w, /static/_DSC7542.aa931e598c.jpg?nf_resize=fit&w=5400 5400w, /static/_DSC7542.aa931e598c.jpg?nf_resize=fit&w=6000 6000w" src="/static/_DSC7542.aa931e598c.jpg" loading="lazy" sizes="100vw"></figure>
tag:jonleighton.name,2020-10-03:/2020/green-grocer-circada/Green Grocer Circada2020-10-03T00:00:00Z2020-10-03T00:00:00Z<figure class="image fill captioned"><img srcset="/static/_DSC7091.518c059ad2.jpg?nf_resize=fit&w=600 600w, /static/_DSC7091.518c059ad2.jpg?nf_resize=fit&w=1200 1200w, /static/_DSC7091.518c059ad2.jpg?nf_resize=fit&w=1800 1800w, /static/_DSC7091.518c059ad2.jpg?nf_resize=fit&w=2400 2400w, /static/_DSC7091.518c059ad2.jpg?nf_resize=fit&w=3000 3000w, /static/_DSC7091.518c059ad2.jpg?nf_resize=fit&w=3600 3600w, /static/_DSC7091.518c059ad2.jpg?nf_resize=fit&w=4200 4200w, /static/_DSC7091.518c059ad2.jpg?nf_resize=fit&w=4800 4800w, /static/_DSC7091.518c059ad2.jpg?nf_resize=fit&w=5400 5400w, /static/_DSC7091.518c059ad2.jpg?nf_resize=fit&w=6000 6000w" src="/static/_DSC7091.518c059ad2.jpg" loading="lazy" sizes="100vw"><figcaption><p>A <a href="https://en.wikipedia.org/wiki/Cyclochila_australasiae">green grocer circada</a> in the Blue Mountains, New South Wales.</p>
</figcaption></figure>
<figure class="image fill captioned"><img srcset="/static/_DSC7082.b64cb130a1.jpg?nf_resize=fit&w=600 600w, /static/_DSC7082.b64cb130a1.jpg?nf_resize=fit&w=1200 1200w, /static/_DSC7082.b64cb130a1.jpg?nf_resize=fit&w=1800 1800w, /static/_DSC7082.b64cb130a1.jpg?nf_resize=fit&w=2400 2400w, /static/_DSC7082.b64cb130a1.jpg?nf_resize=fit&w=3000 3000w, /static/_DSC7082.b64cb130a1.jpg?nf_resize=fit&w=3600 3600w, /static/_DSC7082.b64cb130a1.jpg?nf_resize=fit&w=4200 4200w, /static/_DSC7082.b64cb130a1.jpg?nf_resize=fit&w=4800 4800w, /static/_DSC7082.b64cb130a1.jpg?nf_resize=fit&w=5400 5400w, /static/_DSC7082.b64cb130a1.jpg?nf_resize=fit&w=6000 6000w" src="/static/_DSC7082.b64cb130a1.jpg" loading="lazy" sizes="100vw"><figcaption><p>They spend seven years underground before emerging for their six week life as an adult!</p>
</figcaption></figure>
<figure class="image fill"><img srcset="/static/_DSC7085.84c13904a5.jpg?nf_resize=fit&w=600 600w, /static/_DSC7085.84c13904a5.jpg?nf_resize=fit&w=1200 1200w, /static/_DSC7085.84c13904a5.jpg?nf_resize=fit&w=1800 1800w, /static/_DSC7085.84c13904a5.jpg?nf_resize=fit&w=2400 2400w, /static/_DSC7085.84c13904a5.jpg?nf_resize=fit&w=3000 3000w, /static/_DSC7085.84c13904a5.jpg?nf_resize=fit&w=3600 3600w, /static/_DSC7085.84c13904a5.jpg?nf_resize=fit&w=4200 4200w, /static/_DSC7085.84c13904a5.jpg?nf_resize=fit&w=4800 4800w, /static/_DSC7085.84c13904a5.jpg?nf_resize=fit&w=5400 5400w, /static/_DSC7085.84c13904a5.jpg?nf_resize=fit&w=6000 6000w" src="/static/_DSC7085.84c13904a5.jpg" loading="lazy" sizes="100vw"></figure>
tag:jonleighton.name,2020-09-04:/2020/welcome-swallows/Welcome Swallows2020-09-04T00:00:00Z2020-09-04T00:00:00Z<figure class="image fill captioned"><img srcset="/static/_DSC6753.2c05e3922e.jpg?nf_resize=fit&w=600 600w, /static/_DSC6753.2c05e3922e.jpg?nf_resize=fit&w=1200 1200w, /static/_DSC6753.2c05e3922e.jpg?nf_resize=fit&w=1800 1800w, /static/_DSC6753.2c05e3922e.jpg?nf_resize=fit&w=2400 2400w, /static/_DSC6753.2c05e3922e.jpg?nf_resize=fit&w=3000 3000w, /static/_DSC6753.2c05e3922e.jpg?nf_resize=fit&w=3600 3600w, /static/_DSC6753.2c05e3922e.jpg?nf_resize=fit&w=4200 4200w, /static/_DSC6753.2c05e3922e.jpg?nf_resize=fit&w=4800 4800w, /static/_DSC6753.2c05e3922e.jpg?nf_resize=fit&w=5400 5400w, /static/_DSC6753.2c05e3922e.jpg?nf_resize=fit&w=6000 6000w" src="/static/_DSC6753.2c05e3922e.jpg" loading="lazy" sizes="100vw"><figcaption><p>This pair of <a href="https://en.wikipedia.org/wiki/Welcome_swallow">welcome swallows</a> are building a mud nest under the eaves of a friendâs house. Iâve seen them repeatedly perform this behaviour where one tries to land on the otherâs back, and the other flies away. Maybe a mating ritual or attempt?!</p>
</figcaption></figure>
<figure class="image fill"><img srcset="/static/_DSC6776.824e9bf737.jpg?nf_resize=fit&w=600 600w, /static/_DSC6776.824e9bf737.jpg?nf_resize=fit&w=1200 1200w, /static/_DSC6776.824e9bf737.jpg?nf_resize=fit&w=1800 1800w, /static/_DSC6776.824e9bf737.jpg?nf_resize=fit&w=2400 2400w, /static/_DSC6776.824e9bf737.jpg?nf_resize=fit&w=3000 3000w, /static/_DSC6776.824e9bf737.jpg?nf_resize=fit&w=3600 3600w, /static/_DSC6776.824e9bf737.jpg?nf_resize=fit&w=4200 4200w, /static/_DSC6776.824e9bf737.jpg?nf_resize=fit&w=4800 4800w, /static/_DSC6776.824e9bf737.jpg?nf_resize=fit&w=5400 5400w, /static/_DSC6776.824e9bf737.jpg?nf_resize=fit&w=6000 6000w" src="/static/_DSC6776.824e9bf737.jpg" loading="lazy" sizes="100vw"></figure>
tag:jonleighton.name,2020-08-31:/2020/myall-lakes/Myall Lakes2020-08-31T00:00:00Z2020-08-31T00:00:00Z<p>Weâre travelling up the New South Wales coast and stopped for a few nights at <a href="https://en.wikipedia.org/wiki/Myall_Lakes_National_Park">Myall Lakes National Park</a>. I saw lots of wildlife!</p>
<figure class="image fill captioned"><img srcset="/static/_DSC6480.49b7d86f66.jpg?nf_resize=fit&w=600 600w, /static/_DSC6480.49b7d86f66.jpg?nf_resize=fit&w=1200 1200w, /static/_DSC6480.49b7d86f66.jpg?nf_resize=fit&w=1800 1800w, /static/_DSC6480.49b7d86f66.jpg?nf_resize=fit&w=2400 2400w, /static/_DSC6480.49b7d86f66.jpg?nf_resize=fit&w=3000 3000w, /static/_DSC6480.49b7d86f66.jpg?nf_resize=fit&w=3600 3600w, /static/_DSC6480.49b7d86f66.jpg?nf_resize=fit&w=4200 4200w, /static/_DSC6480.49b7d86f66.jpg?nf_resize=fit&w=4800 4800w, /static/_DSC6480.49b7d86f66.jpg?nf_resize=fit&w=5400 5400w, /static/_DSC6480.49b7d86f66.jpg?nf_resize=fit&w=6000 6000w" src="/static/_DSC6480.49b7d86f66.jpg" loading="lazy" sizes="100vw"><figcaption><p>A goanna! Specifically, itâs a <a href="https://en.wikipedia.org/wiki/Lace_monitor">lace
monitor</a>. I gather theyâre
pretty common around here but itâs the first time Iâve seen one in the wild
and I was pretty excited about it.</p>
</figcaption></figure>
<figure class="image fill captioned"><img srcset="/static/_DSC6414.bc7e0b03f6.jpg?nf_resize=fit&w=600 600w, /static/_DSC6414.bc7e0b03f6.jpg?nf_resize=fit&w=1200 1200w, /static/_DSC6414.bc7e0b03f6.jpg?nf_resize=fit&w=1800 1800w, /static/_DSC6414.bc7e0b03f6.jpg?nf_resize=fit&w=2400 2400w, /static/_DSC6414.bc7e0b03f6.jpg?nf_resize=fit&w=3000 3000w, /static/_DSC6414.bc7e0b03f6.jpg?nf_resize=fit&w=3600 3600w, /static/_DSC6414.bc7e0b03f6.jpg?nf_resize=fit&w=4200 4200w, /static/_DSC6414.bc7e0b03f6.jpg?nf_resize=fit&w=4800 4800w, /static/_DSC6414.bc7e0b03f6.jpg?nf_resize=fit&w=5400 5400w, /static/_DSC6414.bc7e0b03f6.jpg?nf_resize=fit&w=6000 6000w" src="/static/_DSC6414.bc7e0b03f6.jpg" loading="lazy" sizes="100vw"><figcaption><p>They have really long tails! I reckon this goanna was about 1.5 metres long.</p>
</figcaption></figure>
<figure class="image fill captioned"><img srcset="/static/_DSC6247.492d6db371.jpg?nf_resize=fit&w=600 600w, /static/_DSC6247.492d6db371.jpg?nf_resize=fit&w=1200 1200w, /static/_DSC6247.492d6db371.jpg?nf_resize=fit&w=1800 1800w, /static/_DSC6247.492d6db371.jpg?nf_resize=fit&w=2400 2400w, /static/_DSC6247.492d6db371.jpg?nf_resize=fit&w=3000 3000w, /static/_DSC6247.492d6db371.jpg?nf_resize=fit&w=3600 3600w, /static/_DSC6247.492d6db371.jpg?nf_resize=fit&w=4200 4200w, /static/_DSC6247.492d6db371.jpg?nf_resize=fit&w=4800 4800w, /static/_DSC6247.492d6db371.jpg?nf_resize=fit&w=5400 5400w, /static/_DSC6247.492d6db371.jpg?nf_resize=fit&w=6000 6000w" src="/static/_DSC6247.492d6db371.jpg" loading="lazy" sizes="100vw"><figcaption><p>Myall Lake is fringed by beautiful <a href="https://en.wikipedia.org/wiki/Melaleuca_quinquenervia">broad-leaved
paperbark</a> trees.</p>
</figcaption></figure>
<figure class="image fill captioned"><img srcset="/static/_DSC6218.bed882c124.jpg?nf_resize=fit&w=600 600w, /static/_DSC6218.bed882c124.jpg?nf_resize=fit&w=1200 1200w, /static/_DSC6218.bed882c124.jpg?nf_resize=fit&w=1800 1800w, /static/_DSC6218.bed882c124.jpg?nf_resize=fit&w=2400 2400w, /static/_DSC6218.bed882c124.jpg?nf_resize=fit&w=3000 3000w, /static/_DSC6218.bed882c124.jpg?nf_resize=fit&w=3600 3600w, /static/_DSC6218.bed882c124.jpg?nf_resize=fit&w=4200 4200w, /static/_DSC6218.bed882c124.jpg?nf_resize=fit&w=4800 4800w, /static/_DSC6218.bed882c124.jpg?nf_resize=fit&w=5400 5400w, /static/_DSC6218.bed882c124.jpg?nf_resize=fit&w=6000 6000w" src="/static/_DSC6218.bed882c124.jpg" loading="lazy" sizes="100vw"><figcaption><p>A <a href="https://en.wikipedia.org/wiki/Little_pied_cormorant">little pied
cormorant</a> having a
stretch.</p>
</figcaption></figure>
<figure class="image fill captioned"><img srcset="/static/_DSC6254.e4bcb62e49.jpg?nf_resize=fit&w=600 600w, /static/_DSC6254.e4bcb62e49.jpg?nf_resize=fit&w=1200 1200w, /static/_DSC6254.e4bcb62e49.jpg?nf_resize=fit&w=1800 1800w, /static/_DSC6254.e4bcb62e49.jpg?nf_resize=fit&w=2400 2400w, /static/_DSC6254.e4bcb62e49.jpg?nf_resize=fit&w=3000 3000w, /static/_DSC6254.e4bcb62e49.jpg?nf_resize=fit&w=3600 3600w, /static/_DSC6254.e4bcb62e49.jpg?nf_resize=fit&w=4200 4200w, /static/_DSC6254.e4bcb62e49.jpg?nf_resize=fit&w=4800 4800w, /static/_DSC6254.e4bcb62e49.jpg?nf_resize=fit&w=5400 5400w, /static/_DSC6254.e4bcb62e49.jpg?nf_resize=fit&w=6000 6000w" src="/static/_DSC6254.e4bcb62e49.jpg" loading="lazy" sizes="100vw"><figcaption><p>A cool twisty paperbark!</p>
</figcaption></figure>
<figure class="image fill captioned"><img srcset="/static/_DSC6284.0822625a23.jpg?nf_resize=fit&w=600 600w, /static/_DSC6284.0822625a23.jpg?nf_resize=fit&w=1200 1200w, /static/_DSC6284.0822625a23.jpg?nf_resize=fit&w=1800 1800w, /static/_DSC6284.0822625a23.jpg?nf_resize=fit&w=2400 2400w, /static/_DSC6284.0822625a23.jpg?nf_resize=fit&w=3000 3000w, /static/_DSC6284.0822625a23.jpg?nf_resize=fit&w=3600 3600w, /static/_DSC6284.0822625a23.jpg?nf_resize=fit&w=4200 4200w, /static/_DSC6284.0822625a23.jpg?nf_resize=fit&w=4800 4800w, /static/_DSC6284.0822625a23.jpg?nf_resize=fit&w=5400 5400w, /static/_DSC6284.0822625a23.jpg?nf_resize=fit&w=6000 6000w" src="/static/_DSC6284.0822625a23.jpg" loading="lazy" sizes="100vw"><figcaption><p>A <a href="https://en.wikipedia.org/wiki/Dingo">dingo</a>. There were lots of these
about, generally trying to steal our food.</p>
</figcaption></figure>
<figure class="image fill captioned"><img srcset="/static/_DSC6652.0dc7060dd1.jpg?nf_resize=fit&w=600 600w, /static/_DSC6652.0dc7060dd1.jpg?nf_resize=fit&w=1200 1200w, /static/_DSC6652.0dc7060dd1.jpg?nf_resize=fit&w=1800 1800w, /static/_DSC6652.0dc7060dd1.jpg?nf_resize=fit&w=2400 2400w, /static/_DSC6652.0dc7060dd1.jpg?nf_resize=fit&w=3000 3000w, /static/_DSC6652.0dc7060dd1.jpg?nf_resize=fit&w=3600 3600w, /static/_DSC6652.0dc7060dd1.jpg?nf_resize=fit&w=4200 4200w, /static/_DSC6652.0dc7060dd1.jpg?nf_resize=fit&w=4800 4800w, /static/_DSC6652.0dc7060dd1.jpg?nf_resize=fit&w=5400 5400w, /static/_DSC6652.0dc7060dd1.jpg?nf_resize=fit&w=6000 6000w" src="/static/_DSC6652.0dc7060dd1.jpg" loading="lazy" sizes="100vw"><figcaption><p><a href="https://en.wikipedia.org/wiki/Australian_pelican">Australian pelicans</a>
cleaning themselves. How ridiculous/amazing are their beaks?!</p>
</figcaption></figure>
tag:jonleighton.name,2020-08-27:/2020/sooty-oystercatchers/Sooty Oystercatchers2020-08-27T00:00:00Z2020-08-27T00:00:00Z<figure class="image fill captioned"><img srcset="/static/_DSC5816.e722de9d33.jpg?nf_resize=fit&w=600 600w, /static/_DSC5816.e722de9d33.jpg?nf_resize=fit&w=1200 1200w, /static/_DSC5816.e722de9d33.jpg?nf_resize=fit&w=1800 1800w, /static/_DSC5816.e722de9d33.jpg?nf_resize=fit&w=2400 2400w, /static/_DSC5816.e722de9d33.jpg?nf_resize=fit&w=3000 3000w, /static/_DSC5816.e722de9d33.jpg?nf_resize=fit&w=3600 3600w, /static/_DSC5816.e722de9d33.jpg?nf_resize=fit&w=4200 4200w, /static/_DSC5816.e722de9d33.jpg?nf_resize=fit&w=4800 4800w, /static/_DSC5816.e722de9d33.jpg?nf_resize=fit&w=5400 5400w, /static/_DSC5816.e722de9d33.jpg?nf_resize=fit&w=6000 6000w" src="/static/_DSC5816.e722de9d33.jpg" loading="lazy" sizes="100vw"><figcaption><p>A pair of <a href="https://en.wikipedia.org/wiki/Sooty_oystercatcher">sooty oystercatchers</a>
getting dinner in Jervis Bay, New South Wales.</p>
</figcaption></figure>
<figure class="image fill"><img srcset="/static/_DSC5713.7f0bcec0c3.jpg?nf_resize=fit&w=600 600w, /static/_DSC5713.7f0bcec0c3.jpg?nf_resize=fit&w=1200 1200w, /static/_DSC5713.7f0bcec0c3.jpg?nf_resize=fit&w=1800 1800w, /static/_DSC5713.7f0bcec0c3.jpg?nf_resize=fit&w=2400 2400w, /static/_DSC5713.7f0bcec0c3.jpg?nf_resize=fit&w=3000 3000w, /static/_DSC5713.7f0bcec0c3.jpg?nf_resize=fit&w=3600 3600w, /static/_DSC5713.7f0bcec0c3.jpg?nf_resize=fit&w=4200 4200w, /static/_DSC5713.7f0bcec0c3.jpg?nf_resize=fit&w=4800 4800w, /static/_DSC5713.7f0bcec0c3.jpg?nf_resize=fit&w=5400 5400w, /static/_DSC5713.7f0bcec0c3.jpg?nf_resize=fit&w=6000 6000w" src="/static/_DSC5713.7f0bcec0c3.jpg" loading="lazy" sizes="100vw"></figure>
<figure class="image fill"><img srcset="/static/_DSC5938.d92c153edd.jpg?nf_resize=fit&w=600 600w, /static/_DSC5938.d92c153edd.jpg?nf_resize=fit&w=1200 1200w, /static/_DSC5938.d92c153edd.jpg?nf_resize=fit&w=1800 1800w, /static/_DSC5938.d92c153edd.jpg?nf_resize=fit&w=2400 2400w, /static/_DSC5938.d92c153edd.jpg?nf_resize=fit&w=3000 3000w, /static/_DSC5938.d92c153edd.jpg?nf_resize=fit&w=3600 3600w, /static/_DSC5938.d92c153edd.jpg?nf_resize=fit&w=4200 4200w, /static/_DSC5938.d92c153edd.jpg?nf_resize=fit&w=4800 4800w, /static/_DSC5938.d92c153edd.jpg?nf_resize=fit&w=5400 5400w, /static/_DSC5938.d92c153edd.jpg?nf_resize=fit&w=6000 6000w" src="/static/_DSC5938.d92c153edd.jpg" loading="lazy" sizes="100vw"></figure>
tag:jonleighton.name,2020-07-25:/2020/eastern-spinebill/The Eastern Spinebill2020-07-25T00:00:00Z2020-07-25T00:00:00Z<figure class="image fill captioned"><img srcset="/static/photo-1.b9145252d3.jpg?nf_resize=fit&w=600 600w, /static/photo-1.b9145252d3.jpg?nf_resize=fit&w=1200 1200w, /static/photo-1.b9145252d3.jpg?nf_resize=fit&w=1800 1800w, /static/photo-1.b9145252d3.jpg?nf_resize=fit&w=2400 2400w, /static/photo-1.b9145252d3.jpg?nf_resize=fit&w=3000 3000w, /static/photo-1.b9145252d3.jpg?nf_resize=fit&w=3600 3600w, /static/photo-1.b9145252d3.jpg?nf_resize=fit&w=4200 4200w, /static/photo-1.b9145252d3.jpg?nf_resize=fit&w=4800 4800w, /static/photo-1.b9145252d3.jpg?nf_resize=fit&w=5400 5400w, /static/photo-1.b9145252d3.jpg?nf_resize=fit&w=6000 6000w" src="/static/photo-1.b9145252d3.jpg" loading="lazy" sizes="100vw"><figcaption><p>Coronavirus lead to my partner and I spending 3 months living on the edge of a small town in central Victoria. One of my daily joys while there was watching <a href="https://en.wikipedia.org/wiki/Eastern_spinebill">eastern spinebills</a> foraging on a bush outside of the office window. Theyâre like Australiaâs version of the hummingbird! Itâs pretty hard to get them to sit still but I was pleased with this shot.</p>
</figcaption></figure>
tag:jonleighton.name,2020-07-16:/2020/galerina-patagonica/Galerina patagonica2020-07-16T00:00:00Z2020-07-16T00:00:00Z<figure class="image fill captioned"><img srcset="/static/photo-2.03d58ea820.jpg?nf_resize=fit&w=600 600w, /static/photo-2.03d58ea820.jpg?nf_resize=fit&w=1200 1200w, /static/photo-2.03d58ea820.jpg?nf_resize=fit&w=1800 1800w, /static/photo-2.03d58ea820.jpg?nf_resize=fit&w=2400 2400w, /static/photo-2.03d58ea820.jpg?nf_resize=fit&w=3000 3000w, /static/photo-2.03d58ea820.jpg?nf_resize=fit&w=3600 3600w, /static/photo-2.03d58ea820.jpg?nf_resize=fit&w=4200 4200w, /static/photo-2.03d58ea820.jpg?nf_resize=fit&w=4800 4800w, /static/photo-2.03d58ea820.jpg?nf_resize=fit&w=5400 5400w, /static/photo-2.03d58ea820.jpg?nf_resize=fit&w=6000 6000w" src="/static/photo-2.03d58ea820.jpg" loading="lazy" sizes="100vw"><figcaption><p>This fungus is <i><a href="https://en.wikipedia.org/wiki/Galerina_patagonica">Galerina patagonica</a></i>, which feeds on rotting wood. Many species in the <i>Galerina</i> genus contain the same deadly amatoxins that are found in the death cap mushroom (<i><a href="https://en.wikipedia.org/wiki/Amanita_phalloides">Amanita phalloides</a></i>), although Iâm not sure about this one specifically.</p>
</figcaption></figure>
tag:jonleighton.name,2020-07-15:/2020/djurite-dawn/A Djurite Dawn2020-07-15T00:00:00Z2020-07-15T00:00:00Z<figure class="image fill captioned"><img srcset="/static/photo-3.fc61014a2f.jpg?nf_resize=fit&w=600 600w, /static/photo-3.fc61014a2f.jpg?nf_resize=fit&w=1200 1200w, /static/photo-3.fc61014a2f.jpg?nf_resize=fit&w=1800 1800w, /static/photo-3.fc61014a2f.jpg?nf_resize=fit&w=2400 2400w, /static/photo-3.fc61014a2f.jpg?nf_resize=fit&w=3000 3000w, /static/photo-3.fc61014a2f.jpg?nf_resize=fit&w=3600 3600w, /static/photo-3.fc61014a2f.jpg?nf_resize=fit&w=4200 4200w, /static/photo-3.fc61014a2f.jpg?nf_resize=fit&w=4800 4800w, /static/photo-3.fc61014a2f.jpg?nf_resize=fit&w=5400 5400w, /static/photo-3.fc61014a2f.jpg?nf_resize=fit&w=6000 6000w" src="/static/photo-3.fc61014a2f.jpg" loading="lazy" sizes="100vw"><figcaption><p>I took this photo from the top of <a href="https://en.wikipedia.org/wiki/Mount_Arapiles">Djurite/Mount Arapiles</a>, on a cold but clear morning, a few days after the winter solstice. Due to COVID, that weekend was the first time Iâd been able to go climbing for several months. Iâd almost forgotten how beautiful this place is.</p>
</figcaption></figure>
tag:jonleighton.name,2020-05-29:/2020/how-to-act/How to act2020-05-29T00:00:00Z2020-05-29T00:00:00Z
<p>Responding to <a href="/articles/2020/the-other-crisis/">my recent article about the ecological crisis</a>, my friend <a href="https://www.brizk.com/">Kai</a> <a href="/articles/2020/the-other-crisis/#comment-5ecc74a5d9bee7ca022bf5a8">asked</a>:</p>
<blockquote>
<p>How would you respond to the criticism that this perspective is overly focused on the personal or local outcome?</p>
<p>As you know very well, the fight for climate action is also a fight for social justice more broadly, and especially for those that are already feeling the grim effects of climate change.</p>
</blockquote>
<p>This is a topic that <a href="https://medium.com/@kaibrach/individual-vs-collective-climate-action-85354c755d6e">Kai also wrote about</a> back in March.</p>
<p>I think whatâs often behind this sort of question is the desire to know: how can I have the biggest impact? And behind that: how can I feel OK about my participation in a system which I know is destroying the Earth? Surely by doing the thing that will have the biggest impact? Even if it doesnât work, at least I can say I tried!</p>
<p>Our culture is obsessed with quantitative reasoning. For any given problem, the default response is to figure out what to measure, find the most impactful change according to that measure, and then try to implement that change as widely as possible. This is a reductionist frame which overlooks the relationships between things and the non-linearity with which change can happen. (Look at COVID-19 â how many futurists were confidently predicting this a year ago?)</p>
<p>In Kaiâs article, he points to research showing that <a href="https://www.theguardian.com/sustainable-business/2017/jul/10/100-fossil-fuel-companies-investors-responsible-71-global-emissions-cdp-study-climate-change">over 70% of emissions to date came from just 100 oil, gas and mining companies</a>. If only we could somehow force those companies to change tack, weâd be well on our way to solving the problem.</p>
<p>The thing is, those companies arenât digging the stuff up and then just putting a match to it. Theyâre selling it to other companies and individuals who want the energy. The behaviour of those fossil fuel companies is deeply entwined with how our economy works, how we live our lives, and how we collectively value (or donât value) the world around us. Itâs a <a href="https://en.wikipedia.org/wiki/Complex_system">complex system</a> â a change to one part does not produce a predictable outcome in another.</p>
<p>I used to believe that I could force change. In a world of globalisation and Elon Musks and career ladders weâre told that we can <em>be somebody</em>, we can <em>leave a legacy</em>, we can <em>have an impact</em>. I canât quite pinpoint where in history this story first arose, but it seems to be another facet of the neoliberal individualist story which denies the connections between us all. Itâs driven by the ego, who so easily forgets that in the not too distant future our atoms will be rearranged once more.</p>
<p>Asking whether I should change myself or change the system still centres the individual making that decision. Perhaps by building strong communities a different kind of force could emerge.</p>
<p>In truth, I am not trying to <em>act</em> in a certain way, nor to tell you how to act. Action got us into this mess. When I deeply reflect on what an ecological society would look like, it is completely unrecognisable from where Iâm standing. I do not know how to get there. (I do trust that it will emerge, but I suspect I wonât be around to see that time.)</p>
<p>What I <em>have</em> done is reached a place of acceptance about that storm brewing on the horizon. I still wish to live my life in a way that feels true to my values. Sometimes that may look like changing my electricity provider or divesting my pension or getting on a bike. But I do those things because they feel like the best way to express the world I want to live in, rather than because I think it will stop the storm from coming, or because I feel guilty about being the person I am, in the time that Iâm in.</p>
<p>I donât believe thereâs one single area we can all focus on that will achieve the change that is needed. <em>Everything</em> needs to change. So do what youâre drawn to do rather than the thing youâve calculated will have the biggest impact according to some measure of the situation which undoubtedly overlooks countless things we cannot understand.</p>
<hr>
<p><em>For a much deeper exploration of these themes, I highly recommend reading Charles Eisensteinâs book, <a href="/articles/2019/climate-a-new-story/">Climate: A New Story</a>.</em></p>