<?xml version="1.0" encoding="utf-8"?><rss version="2.0"><channel><title>01def.io/blog</title><link>https://01def.io/blog/view</link><description>Latest articles from 01def.io</description><item><title>I tried out Test Driven Development.</title><link>https://01def.io/blog/view/1</link><description><![CDATA[<p>I decided to switch from writing unit tests after writing implementation to using Test Driven Development. In the process, I document my first experiences.</p>
<h1>Introduction</h1>
<p>I apologize for the under-construction vibe that this site gives off. I am building it incrementally, and even I don't know how it will end up looking.</p>
<p>Recently, while writing code for this site, I decided to switch from writing unit tests after writing implementation to using Test Driven Development. So now I have unit tests that were written afterwards and unit tests that were written before implementation. And the tests look very different.</p>
<h1>First differences</h1>
<p>Here are the tests for a module I wrote earlier without TDD.</p>
<pre style="background-color:#2b303b;"><code class="language-rust"><span style="color:#c0c5ce;">    #[</span><span style="color:#bf616a;">tokio</span><span style="color:#c0c5ce;">::</span><span style="color:#bf616a;">test</span><span style="color:#c0c5ce;">]
</span><span style="color:#c0c5ce;">    async </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">test</span><span style="color:#c0c5ce;">() {
</span><span style="color:#c0c5ce;">        </span><span style="color:#96b5b4;">drop</span><span style="color:#c0c5ce;">(env_logger::try_init());
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        log::info!(&quot;</span><span style="color:#a3be8c;">Creating test database</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> pool = </span><span style="color:#d08770;">SERVER</span><span style="color:#c0c5ce;">.</span><span style="color:#96b5b4;">connect</span><span style="color:#c0c5ce;">().await;
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let mut</span><span style="color:#c0c5ce;"> mgr = mgt::Manager::new(); 
</span><span style="color:#c0c5ce;">        mgr.</span><span style="color:#96b5b4;">add</span><span style="color:#c0c5ce;">(</span><span style="color:#96b5b4;">mod_info</span><span style="color:#c0c5ce;">());
</span><span style="color:#c0c5ce;">        mgr.</span><span style="color:#96b5b4;">add</span><span style="color:#c0c5ce;">(users::mod_info());
</span><span style="color:#c0c5ce;">        mgr.</span><span style="color:#96b5b4;">migrate</span><span style="color:#c0c5ce;">(&amp;pool).await
</span><span style="color:#c0c5ce;">            .</span><span style="color:#96b5b4;">expect</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">Migration failed</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        log::info!(&quot;</span><span style="color:#a3be8c;">Creating user user1</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> user1 = users::create(&amp;pool, &quot;</span><span style="color:#a3be8c;">user1</span><span style="color:#c0c5ce;">&quot;).await
</span><span style="color:#c0c5ce;">            .</span><span style="color:#96b5b4;">expect</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">Unable to create user</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        log::info!(&quot;</span><span style="color:#a3be8c;">Creating new session</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> session = </span><span style="color:#96b5b4;">create</span><span style="color:#c0c5ce;">(&amp;pool, &amp;user1.uid, </span><span style="color:#d08770;">3</span><span style="color:#c0c5ce;">).await
</span><span style="color:#c0c5ce;">            .</span><span style="color:#96b5b4;">expect</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">Unable to create new session</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">        assert_eq!(session.uid, user1.uid);
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        log::info!(&quot;</span><span style="color:#a3be8c;">Checking for active session</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> chk_value = </span><span style="color:#96b5b4;">get</span><span style="color:#c0c5ce;">(&amp;pool, &amp;session.sessionid).await
</span><span style="color:#c0c5ce;">            .</span><span style="color:#96b5b4;">expect</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">Unable to get session via sessionid</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">        assert_eq!(chk_value, session);
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        log::info!(&quot;</span><span style="color:#a3be8c;">Testing session deletion</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">        </span><span style="color:#96b5b4;">delete</span><span style="color:#c0c5ce;">(&amp;pool, &amp;session.sessionid).await
</span><span style="color:#c0c5ce;">            .</span><span style="color:#96b5b4;">expect</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">Unable to delete session</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> chk_value = </span><span style="color:#96b5b4;">get</span><span style="color:#c0c5ce;">(&amp;pool, &amp;session.sessionid).await;
</span><span style="color:#c0c5ce;">        assert!(matches!(chk_value, Err(Error::RowNotFound)));
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        log::info!(&quot;</span><span style="color:#a3be8c;">Testing foreign key</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">        users::delete(&amp;pool, &amp;user1.uid).await
</span><span style="color:#c0c5ce;">            .</span><span style="color:#96b5b4;">expect</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">Unable to delete user</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> chk_value = </span><span style="color:#96b5b4;">create</span><span style="color:#c0c5ce;">(&amp;pool, &amp;user1.uid, </span><span style="color:#d08770;">3</span><span style="color:#c0c5ce;">).await;
</span><span style="color:#c0c5ce;">        assert!(matches!(chk_value, Err(Error::Database(_))));
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        log::info!(&quot;</span><span style="color:#a3be8c;">Testing session pruning</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> user1 = users::create(&amp;pool, &quot;</span><span style="color:#a3be8c;">user1</span><span style="color:#c0c5ce;">&quot;).await
</span><span style="color:#c0c5ce;">            .</span><span style="color:#96b5b4;">expect</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">Unable to create user</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">for </span><span style="color:#c0c5ce;">_ in </span><span style="color:#d08770;">0</span><span style="color:#c0c5ce;">..</span><span style="color:#d08770;">5 </span><span style="color:#c0c5ce;">{
</span><span style="color:#c0c5ce;">            </span><span style="color:#96b5b4;">create</span><span style="color:#c0c5ce;">(&amp;pool, &amp;user1.uid, </span><span style="color:#d08770;">3</span><span style="color:#c0c5ce;">).await
</span><span style="color:#c0c5ce;">                .</span><span style="color:#96b5b4;">expect</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">Unable to create new session</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">        }
</span><span style="color:#c0c5ce;">        log::info!(&quot;</span><span style="color:#a3be8c;">Assertions for session pruning</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> chk_value = </span><span style="color:#96b5b4;">list</span><span style="color:#c0c5ce;">(&amp;pool).await.</span><span style="color:#96b5b4;">unwrap</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">        assert_eq!(chk_value.</span><span style="color:#96b5b4;">len</span><span style="color:#c0c5ce;">(), </span><span style="color:#d08770;">4</span><span style="color:#c0c5ce;">);
</span><span style="color:#c0c5ce;">    }
</span></code></pre>
<p>It is hard to figure out in a glance what the test is checking for. It is also not obvious what the module does from just the tests. The code is written in Rust, and if you did not know Rust, the logging statements might be the only thing that tells you what the test is doing.</p>
<p>Here are the tests for the first module I wrote with TDD.</p>
<pre style="background-color:#2b303b;"><code class="language-rust"><span style="color:#c0c5ce;">    #[</span><span style="color:#bf616a;">tokio</span><span style="color:#c0c5ce;">::</span><span style="color:#bf616a;">test</span><span style="color:#c0c5ce;">]
</span><span style="color:#c0c5ce;">    async </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">test_get</span><span style="color:#c0c5ce;">() {
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let </span><span style="color:#c0c5ce;">(pool, user1, _) = </span><span style="color:#96b5b4;">init</span><span style="color:#c0c5ce;">().await;
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> form = </span><span style="color:#96b5b4;">test_blog</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> id = </span><span style="color:#96b5b4;">create</span><span style="color:#c0c5ce;">(&amp;pool, &amp;user1.uid, &amp;form).await
</span><span style="color:#c0c5ce;">            .</span><span style="color:#96b5b4;">expect</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">create</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> blog = </span><span style="color:#96b5b4;">get</span><span style="color:#c0c5ce;">(&amp;pool, id, Access::All).await
</span><span style="color:#c0c5ce;">            .</span><span style="color:#96b5b4;">expect</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">get</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">        assert_eq!(&amp;blog.title, &amp;form.title);
</span><span style="color:#c0c5ce;">        assert_eq!(&amp;blog.body, &amp;form.body);
</span><span style="color:#c0c5ce;">        assert_eq!(&amp;blog.author_name, &amp;user1.username);
</span><span style="color:#c0c5ce;">    }
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">    #[</span><span style="color:#bf616a;">tokio</span><span style="color:#c0c5ce;">::</span><span style="color:#bf616a;">test</span><span style="color:#c0c5ce;">]
</span><span style="color:#c0c5ce;">    async </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">test_get_others</span><span style="color:#c0c5ce;">() {
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let </span><span style="color:#c0c5ce;">(pool, user1, user2) = </span><span style="color:#96b5b4;">init</span><span style="color:#c0c5ce;">().await;
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> form = </span><span style="color:#96b5b4;">test_blog</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> id = </span><span style="color:#96b5b4;">create</span><span style="color:#c0c5ce;">(&amp;pool, &amp;user1.uid, &amp;form).await
</span><span style="color:#c0c5ce;">            .</span><span style="color:#96b5b4;">expect</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">create</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> result = </span><span style="color:#96b5b4;">get</span><span style="color:#c0c5ce;">(&amp;pool, id, Access::Mine(user2.uid.</span><span style="color:#96b5b4;">clone</span><span style="color:#c0c5ce;">())).await;
</span><span style="color:#c0c5ce;">        assert!(matches!(result, Err(sqlx::Error::RowNotFound)));
</span><span style="color:#c0c5ce;">    }
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">    #[</span><span style="color:#bf616a;">tokio</span><span style="color:#c0c5ce;">::</span><span style="color:#bf616a;">test</span><span style="color:#c0c5ce;">]
</span><span style="color:#c0c5ce;">    async </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">test_modify</span><span style="color:#c0c5ce;">() {
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let </span><span style="color:#c0c5ce;">(pool, user1, _) = </span><span style="color:#96b5b4;">init</span><span style="color:#c0c5ce;">().await;
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let mut</span><span style="color:#c0c5ce;"> form = </span><span style="color:#96b5b4;">test_blog</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> id = </span><span style="color:#96b5b4;">create</span><span style="color:#c0c5ce;">(&amp;pool, &amp;user1.uid, &amp;form).await
</span><span style="color:#c0c5ce;">            .</span><span style="color:#96b5b4;">expect</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">create</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        form.body = &quot;</span><span style="color:#a3be8c;">Modified body</span><span style="color:#c0c5ce;">&quot;.</span><span style="color:#96b5b4;">into</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">        </span><span style="color:#96b5b4;">modify</span><span style="color:#c0c5ce;">(&amp;pool, id, &amp;form, Access::All).await
</span><span style="color:#c0c5ce;">            .</span><span style="color:#96b5b4;">expect</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">modify</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> blog = </span><span style="color:#96b5b4;">get</span><span style="color:#c0c5ce;">(&amp;pool, </span><span style="color:#d08770;">1</span><span style="color:#c0c5ce;">, Access::All).await
</span><span style="color:#c0c5ce;">            .</span><span style="color:#96b5b4;">expect</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">get</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">        assert_eq!(&amp;blog.body, &amp;form.body);
</span><span style="color:#c0c5ce;">    }
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">    #[</span><span style="color:#bf616a;">tokio</span><span style="color:#c0c5ce;">::</span><span style="color:#bf616a;">test</span><span style="color:#c0c5ce;">]
</span><span style="color:#c0c5ce;">    async </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">test_modify_others</span><span style="color:#c0c5ce;">() {
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let </span><span style="color:#c0c5ce;">(pool, user1, user2) = </span><span style="color:#96b5b4;">init</span><span style="color:#c0c5ce;">().await;
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let mut</span><span style="color:#c0c5ce;"> form = </span><span style="color:#96b5b4;">test_blog</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> id = </span><span style="color:#96b5b4;">create</span><span style="color:#c0c5ce;">(&amp;pool, &amp;user1.uid, &amp;form).await
</span><span style="color:#c0c5ce;">            .</span><span style="color:#96b5b4;">expect</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">create</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        form.body = &quot;</span><span style="color:#a3be8c;">Modified body</span><span style="color:#c0c5ce;">&quot;.</span><span style="color:#96b5b4;">into</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> access = Access::Mine(user2.uid.</span><span style="color:#96b5b4;">clone</span><span style="color:#c0c5ce;">());
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> result = </span><span style="color:#96b5b4;">modify</span><span style="color:#c0c5ce;">(&amp;pool, id, &amp;form, access).await;
</span><span style="color:#c0c5ce;">        assert!(matches!(result, Err(sqlx::Error::RowNotFound)));
</span><span style="color:#c0c5ce;">    }
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">    #[</span><span style="color:#bf616a;">tokio</span><span style="color:#c0c5ce;">::</span><span style="color:#bf616a;">test</span><span style="color:#c0c5ce;">]
</span><span style="color:#c0c5ce;">    async </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">test_index</span><span style="color:#c0c5ce;">() {
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let </span><span style="color:#c0c5ce;">(pool, user1, _) = </span><span style="color:#96b5b4;">init</span><span style="color:#c0c5ce;">().await;
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> form = </span><span style="color:#96b5b4;">test_blog</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        </span><span style="color:#96b5b4;">create</span><span style="color:#c0c5ce;">(&amp;pool, &amp;user1.uid, &amp;form).await
</span><span style="color:#c0c5ce;">            .</span><span style="color:#96b5b4;">expect</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">create</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> idx = </span><span style="color:#96b5b4;">index</span><span style="color:#c0c5ce;">(&amp;pool, Access::All).await
</span><span style="color:#c0c5ce;">            .</span><span style="color:#96b5b4;">expect</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">index</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">        assert_eq!(idx.</span><span style="color:#96b5b4;">len</span><span style="color:#c0c5ce;">(), </span><span style="color:#d08770;">1</span><span style="color:#c0c5ce;">);
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> idx = </span><span style="color:#96b5b4;">index</span><span style="color:#c0c5ce;">(&amp;pool, Access::Mine(user1.uid.</span><span style="color:#96b5b4;">clone</span><span style="color:#c0c5ce;">())).await
</span><span style="color:#c0c5ce;">            .</span><span style="color:#96b5b4;">expect</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">index</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">        assert_eq!(idx.</span><span style="color:#96b5b4;">len</span><span style="color:#c0c5ce;">(), </span><span style="color:#d08770;">1</span><span style="color:#c0c5ce;">);
</span><span style="color:#c0c5ce;">    }
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">    #[</span><span style="color:#bf616a;">tokio</span><span style="color:#c0c5ce;">::</span><span style="color:#bf616a;">test</span><span style="color:#c0c5ce;">]
</span><span style="color:#c0c5ce;">    async </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">test_index_others</span><span style="color:#c0c5ce;">() {
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let </span><span style="color:#c0c5ce;">(pool, user1, user2) = </span><span style="color:#96b5b4;">init</span><span style="color:#c0c5ce;">().await;
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> form = </span><span style="color:#96b5b4;">test_blog</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        </span><span style="color:#96b5b4;">create</span><span style="color:#c0c5ce;">(&amp;pool, &amp;user1.uid, &amp;form).await
</span><span style="color:#c0c5ce;">            .</span><span style="color:#96b5b4;">expect</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">create</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> idx = </span><span style="color:#96b5b4;">index</span><span style="color:#c0c5ce;">(&amp;pool, Access::Mine(user2.uid.</span><span style="color:#96b5b4;">clone</span><span style="color:#c0c5ce;">())).await
</span><span style="color:#c0c5ce;">            .</span><span style="color:#96b5b4;">expect</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">index</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">        assert_eq!(idx.</span><span style="color:#96b5b4;">len</span><span style="color:#c0c5ce;">(), </span><span style="color:#d08770;">0</span><span style="color:#c0c5ce;">);
</span><span style="color:#c0c5ce;">    }
</span></code></pre>
<p>It is much more obvious that the module handles displaying and editing blog posts on this site. It is also obvious that it cannot delete blog posts yet.</p>
<p>However, if I look at the names of the tests, I see a cartesian product.</p>
<table>
<thead>
<tr>
<th>Who</th>
<th>create</th>
<th>modify</th>
<th>index</th>
</tr>
</thead>
<tbody>
<tr>
<td>Self</td>
<td>test_get</td>
<td>test_modify</td>
<td>test_index</td>
</tr>
<tr>
<td>Others</td>
<td>test_get_others</td>
<td>test_modify_others</td>
<td>test_index_others</td>
</tr>
</tbody>
</table>
<p>I do not like it. To me it looks like there is redundancy involved, and a change in the implementation might require multiple tests to be updated.</p>
<p>It is possible that this is actually good code and I haven't realized the merits yet. It is also possible that this is actually bad code and I did TDD wrong.</p>
<h1>More complex modules</h1>
<p>So far I do not have experience in using TDD for more complex code. In the past, I have written tests that involve loops or <a href="https://github.com/akashrawal/smec/blob/master/tests/test_msg.c">tree-generation algorithms</a> to check each possible case. I wonder how such tests will work with TDD involved. But then I also lack any experience in how to grade tests written with TDD. I guess only time and experience will tell.</p>
]]></description><author>akash_rawal</author><pubDate>Mon, 27 Mar 2023 17:00:45 +0000</pubDate></item><item><title>About &quot;Getting a life&quot;</title><link>https://01def.io/blog/view/about-getting-a-life</link><description><![CDATA[<h1>&quot;I have a life outside work&quot;</h1>
<p>What is this &quot;life&quot; thing are you talking about?</p>
<p>Are you one of those &quot;I like to travel&quot; people?</p>
<p>&quot;Singing&quot;?</p>
<p>&quot;Dancing&quot;?</p>
<p>Popular media?</p>
<p>No, you explored nothing. You just copied everyone else.</p>
<p>Now this is not an inherently bad choice to make, but please stop telling others to &quot;get a life&quot; or &quot;touch grass&quot;. They <strong>are</strong> having fun.
Or until you told them what they found fun is not actually fun.</p>
<p>I don't know how many tourists are 'tourists', who got gaslit into
tourism, leaving their true interests undiscovered.</p>
<p>It is good to have life outside work,
but are you actually having life outside work?</p>
<h1>The probability of &quot;I like to travel&quot;, and gaslighting</h1>
<p>Out of all people,</p>
<ul>
<li>How many people truely enjoy tourism?</li>
<li>How many people only 'like' tourism?</li>
<li>How many people don't like tourism but still travel just to get along?</li>
<li>How many just don't?</li>
</ul>
<p>It seems like basically everyone likes tourism.</p>
<p>Statistically speaking, how is this possible?
There are so many niches to dive into. How is it always
tourism? How is it always singing and dancing?</p>
<p>I once had a group of men hold me down, insisting that they wouldn't let
me go until I sing my 'favorite song'.
They genuinely had no ill will, they just couldn't accept the fact that
songs aren't my thing.</p>
<p>More than 10 people; cannot accept that someone may not like songs.</p>
<p>This probably amounts to 93.9 percent of the entire population.
How can this be possible?</p>
<h1>Tribal mechanisms</h1>
<p>Turns out, the cake is real. Life, on the other hand, is not.</p>
<p>How do most people live?</p>
<ul>
<li>Wake up</li>
<li>The usual morning routine. By usual I mean the <a href="https://world.hey.com/dhh/why-don-t-more-people-use-linux-33b75f53">sedentary one</a>.</li>
<li>Go to work.</li>
<li>Return from work.</li>
<li>Watch TV, shorts, movies, serials, stuff</li>
<li>Sleep</li>
<li>Repeat</li>
</ul>
<p>Is this a problem? No. But if you act like you are inherently superior than anyone who wants to be different, then we have a problem.</p>
<p>There are so many niches to explore, that evaluating all of them is basically
impossible in a human timescale.
Convergence on the 'one true thing' is impossible.
I think, if people truely chose what they want to do, everyone would choose
something different.</p>
<p>So why popular media is ... popular?
Why most people don't workout?
How does everyone like singing and dancing?</p>
<p>We are social creatures. We want to fit in our tribe. This means, enjoying
the same media as those around us ... hence popular media,
visiting the same places and taking pictures 'gotta catch them all' style
except for your own breath ... hence tourism, partying hard... hence
fast food and booze, and mediocre singing and dancing by those who 'love' it ...
just like everyone else.</p>
<p>If you truely love it, why aren't you good at it?</p>
<p>We don't need to be tribal.</p>
<p>We can all be different.</p>
<p>And we can all embrace each other for being different.</p>
<p>And we can stop bullying others for being different.</p>
<p>And we can all trust each other despite being different.</p>
<p>If you are different, to me it means that you are curious.
You explored around what can be done, instead of simply copying others.
You have questioned what is considered as normal, regardless of consequences.</p>
<p>And I am interested. I want to listen to you. Please tell me what makes you
different.</p>
<h1>Appendix 1</h1>
<p>Let:</p>
<blockquote>
<p>x: the fraction of people that accept that someone may not like songs.<br />
E: accepts that someone may not like songs<br />
10 random samples taken, 10 times E is false.<br />
To find: x</p>
</blockquote>
<p>Let <code data-math-style="inline">f(x)</code> : probability distribution of x
Lets assume perfect uncertainity, so <code data-math-style="inline">f(x)</code> is 1.</p>
<p>Let <code data-math-style="inline">f_1(x)</code> be probability density of x when sample 1 is false</p>
<pre><code class="language-math" data-math-style="display">\begin{align*}
f_1(x) &amp;= \frac{f(x)  (1 - x)}
         {\int\limits_{0}^{1} f(y) (1 - y) dy}
\\
       &amp;=   \frac{1-x} 
           {y - \frac{y^2}{2}}
         \bigg|_0^1
\\
       &amp;= \frac{1-x}{0.5}
\\
       &amp;= 2(1-x)
\end{align*}
</code></pre>
<p>so when first sample is false, x = 0 is more likely than x = 0.5
and x = 1 cannot happen.</p>
<pre><code class="language-math" data-math-style="display">\begin{align*}

f_{10}(x) &amp;= \frac{(1-x)^{10}}
               {\int\limits_{0}^{1} (1-y)^{10}dy} \\
\text{Substitute:} \\
           z             &amp;= 1-y   \\
           \frac{dz}{dy} &amp;= -1    \\
             dy          &amp;= -dz   \\
\text{After substitution:} \\
f_{10}(x) &amp;= \frac{(1-x)^{10}}
                  {\int\limits_{1}^{0} z^{10} (-dz)} \\
          &amp;= \frac{(1-x)^{10}}
                  {-\frac{z^{11}}{11}\bigg|_{1}^{0}} \\
          &amp;= \frac{(1-x)^{10}}
                  {\frac{1}{11}} \\
          &amp;= 11 (1-x)^{10}

\end{align*}
</code></pre>
<p>Let <code data-math-style="inline">w</code> be the median of the probability distribution.</p>
<pre><code class="language-math" data-math-style="display">\begin{aligned}
     \int\limits_{0}^{w} 11(1-x)^{10} dx 
       &amp;= \int\limits_{w}^{1} 11(1-x)^{10} dx \\
=&gt;   \int\limits_{0}^{w} (1-x)^{10} dx 
       &amp;= \int\limits_{w}^{1} (1-x)^{10} dx \\
\\
\text{Substitute:} \\
                 y &amp;= 1-x \\
             dy/dx &amp;= -1 =&gt; dx = -dy \\
       At x = 0: y &amp;= 1 \\
       At x = w: y &amp;= 1-w \\
       At x = 1: y &amp;= 0  \\
\text{After substitution:} \\
\int\limits_{1}^{1-w} y^{10} dy &amp;= \int\limits_{1-w}^{1} y^{10} dy \\
=&gt; y^{11} \bigg|_{1}^{1-w} &amp;= y^{11} \bigg|_{1-w}^{0} \\
=&gt; (1-w)^{11} - 1 &amp;= -(1-w)^{11} \\
=&gt; 2(1-w)^{11} &amp;= 1 \\
=&gt; 1-w &amp;= (\frac{1}{2})^{\frac{1}{11}} \\
=&gt; w &amp;= 1 - [ (\frac{1}{2})^{\frac{1}{11}} ] \\
=&gt; w &amp;~=  0.061
\end{aligned}
</code></pre>
<p>6.1% of population accepts that someone may not like songs.
Or, 93.9% of the population cannot accept that someone may not like
songs.</p>
]]></description><author>akash_rawal</author><pubDate>Sun, 9 Apr 2023 14:40:26 +0000</pubDate></item><item><title>TDD experience part 2</title><link>https://01def.io/blog/view/2</link><description><![CDATA[<p>I have been using TDD for 3 months now. How are things coming along?</p>
<h1>Accidentally testing a private API</h1>
<p>In my <a href="/blog/view/1">last article</a>, I talked about using Test Driven Development while writing code for this site.</p>
<p>So I wanted to measure how well this blog is performing (speed and efficiency stuff, not popularity). The idea is simple,</p>
<ol>
<li>The server maintains atomic integers representing performance counters.</li>
<li>I insert code at measurement points to increment the performance counters.</li>
<li>Every 5 minutes, the server takes a snapshot of the performance counters, and reset the counters to 0.</li>
<li>The server stores the snapshot into the database</li>
</ol>
<pre style="background-color:#2b303b;"><code><span style="color:#c0c5ce;">Measurement code (runs every request)
</span><span style="color:#c0c5ce;">   |
</span><span style="color:#c0c5ce;">   |  Atomic increment  
</span><span style="color:#c0c5ce;">   V
</span><span style="color:#c0c5ce;">Performance counters
</span><span style="color:#c0c5ce;">   | 
</span><span style="color:#c0c5ce;">   |  Atomic replace with 0
</span><span style="color:#c0c5ce;">   V
</span><span style="color:#c0c5ce;">Sampler (runs every 5 minutes in an hour on wall clock, beginning at 00)
</span><span style="color:#c0c5ce;">   |
</span><span style="color:#c0c5ce;">   |  INSERT INTO pm_counters VALUES (...);
</span><span style="color:#c0c5ce;">   V
</span><span style="color:#c0c5ce;">Database
</span></code></pre>
<p>One of the stated benefits of TDD is that it helps you improve the design of your code. I did not talk about code, because the problem was trivial. I already had design in mind, so I would write the same code regardless of the method used.</p>
<p>This time, it is different. I was going to write sampler code, and I had no design in mind. The code needs to trigger at multiples of 5 minutes on a wall clock, so a simple 5 minute sleep loop isn't gonna cut it.</p>
<p>Ah yes, a clock. Abstracting one would be interesting (read: difficult). Oh I know, why don't I just tell the implementation what the time is, and the implementation should return the time when to trigger it again.</p>
<p>This is the first attempt at the tests:</p>
<pre style="background-color:#2b303b;"><code class="language-rust"><span style="color:#c0c5ce;">#[</span><span style="color:#bf616a;">cfg</span><span style="color:#c0c5ce;">(test)]
</span><span style="color:#b48ead;">mod </span><span style="color:#c0c5ce;">test {
</span><span style="color:#c0c5ce;">...
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">    #[</span><span style="color:#bf616a;">test</span><span style="color:#c0c5ce;">]
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">next_trigger_from_arbitrary</span><span style="color:#c0c5ce;">() {
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let mut</span><span style="color:#c0c5ce;"> sampler = </span><span style="color:#96b5b4;">init</span><span style="color:#c0c5ce;">(</span><span style="color:#96b5b4;">rfc3339utc</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">2000-01-01T11:59:17</span><span style="color:#c0c5ce;">&quot;)); </span><span style="color:#65737e;">//&lt; We tell it the current time
</span><span style="color:#c0c5ce;">        sampler.</span><span style="color:#96b5b4;">trigger</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">        assert_eq!(sampler.trig_time, </span><span style="color:#96b5b4;">rfc3339utc</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">2000-01-01T12:00:00</span><span style="color:#c0c5ce;">&quot;)); </span><span style="color:#65737e;">//&lt; We check the next trigger time
</span><span style="color:#c0c5ce;">        assert_eq!(sampler.storage.</span><span style="color:#96b5b4;">len</span><span style="color:#c0c5ce;">(), </span><span style="color:#d08770;">0</span><span style="color:#c0c5ce;">);
</span><span style="color:#c0c5ce;">    }
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">    #[</span><span style="color:#bf616a;">test</span><span style="color:#c0c5ce;">]
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">next_trigger_from_trigger</span><span style="color:#c0c5ce;">() {
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let mut</span><span style="color:#c0c5ce;"> sampler = </span><span style="color:#96b5b4;">init</span><span style="color:#c0c5ce;">(</span><span style="color:#96b5b4;">rfc3339utc</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">2000-01-01T12:00:00</span><span style="color:#c0c5ce;">&quot;));
</span><span style="color:#c0c5ce;">        sampler.</span><span style="color:#96b5b4;">trigger</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">        assert_eq!(sampler.trig_time, </span><span style="color:#96b5b4;">rfc3339utc</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">2000-01-01T12:05:00</span><span style="color:#c0c5ce;">&quot;));
</span><span style="color:#c0c5ce;">        assert_eq!(sampler.storage.</span><span style="color:#96b5b4;">len</span><span style="color:#c0c5ce;">(), </span><span style="color:#d08770;">1</span><span style="color:#c0c5ce;">);
</span><span style="color:#c0c5ce;">    }
</span><span style="color:#c0c5ce;">}
</span></code></pre>
<p>This is the code that passes the tests:</p>
<pre style="background-color:#2b303b;"><code class="language-rust"><span style="color:#b48ead;">pub struct </span><span style="color:#c0c5ce;">Sampler {
</span><span style="color:#c0c5ce;">    </span><span style="color:#bf616a;">trig_time</span><span style="color:#c0c5ce;">: DateTime&lt;Utc&gt;, </span><span style="color:#65737e;">//&lt; Next wakeup time
</span><span style="color:#c0c5ce;">    </span><span style="color:#bf616a;">duration_s</span><span style="color:#c0c5ce;">: </span><span style="color:#b48ead;">u32</span><span style="color:#c0c5ce;">, </span><span style="color:#65737e;">//&lt; Duration between triggering, in seconds (upto 3600)
</span><span style="color:#c0c5ce;">    </span><span style="color:#bf616a;">counters</span><span style="color:#c0c5ce;">: Arc&lt;Counters&gt;,
</span><span style="color:#c0c5ce;">    </span><span style="color:#bf616a;">storage</span><span style="color:#c0c5ce;">: Vec&lt;Row&gt;, 
</span><span style="color:#c0c5ce;">}
</span><span style="color:#c0c5ce;">
</span><span style="color:#b48ead;">impl </span><span style="color:#c0c5ce;">Sampler {
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">pub fn </span><span style="color:#8fa1b3;">trigger</span><span style="color:#c0c5ce;">(&amp;</span><span style="color:#b48ead;">mut </span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">) {
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> time = </span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">.trig_time.</span><span style="color:#96b5b4;">time</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> hour = time.</span><span style="color:#96b5b4;">hour</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> minute = time.</span><span style="color:#96b5b4;">minute</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> second = time.</span><span style="color:#96b5b4;">second</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        </span><span style="color:#65737e;">//Only retain accuracy upto seconds. 
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> round_time = </span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">.trig_time.</span><span style="color:#96b5b4;">date_naive</span><span style="color:#c0c5ce;">()
</span><span style="color:#c0c5ce;">            .</span><span style="color:#96b5b4;">and_hms_opt</span><span style="color:#c0c5ce;">(hour, minute, second)
</span><span style="color:#c0c5ce;">            .</span><span style="color:#96b5b4;">expect</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">Unable to round off sub-second part</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">        </span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">.trig_time = DateTime::from_utc(round_time, Utc);
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> seconds_in_hour = second + (minute * </span><span style="color:#d08770;">60</span><span style="color:#c0c5ce;">);
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> extra_seconds = seconds_in_hour % </span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">.duration_s; </span><span style="color:#65737e;">//5min
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">if</span><span style="color:#c0c5ce;"> extra_seconds == </span><span style="color:#d08770;">0 </span><span style="color:#c0c5ce;">{
</span><span style="color:#c0c5ce;">            </span><span style="color:#65737e;">//Sample statistics
</span><span style="color:#c0c5ce;">            </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> stats = </span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">.counters.</span><span style="color:#96b5b4;">take</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">            </span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">.storage.</span><span style="color:#96b5b4;">push</span><span style="color:#c0c5ce;">(Row {
</span><span style="color:#c0c5ce;">                end_time: </span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">.trig_time, 
</span><span style="color:#c0c5ce;">                counters: stats,
</span><span style="color:#c0c5ce;">            });
</span><span style="color:#c0c5ce;">        }
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        </span><span style="color:#65737e;">//Save the next trigger time. 
</span><span style="color:#c0c5ce;">        </span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">.trig_time = </span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">.trig_time
</span><span style="color:#c0c5ce;">            .</span><span style="color:#96b5b4;">add</span><span style="color:#c0c5ce;">(Duration::seconds((</span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">.duration_s - extra_seconds) as </span><span style="color:#b48ead;">i64</span><span style="color:#c0c5ce;">));
</span><span style="color:#c0c5ce;">    }
</span><span style="color:#c0c5ce;">}
</span></code></pre>
<p>And this is the untested function I ended up writing to drive it.</p>
<pre style="background-color:#2b303b;"><code class="language-rust"><span style="color:#b48ead;">pub</span><span style="color:#c0c5ce;"> async </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">sampler_task</span><span style="color:#c0c5ce;">(
</span><span style="color:#c0c5ce;">    </span><span style="color:#bf616a;">counters</span><span style="color:#c0c5ce;">: Arc&lt;Counters&gt;, 
</span><span style="color:#c0c5ce;">    </span><span style="color:#bf616a;">duration_s</span><span style="color:#c0c5ce;">: </span><span style="color:#b48ead;">u32</span><span style="color:#c0c5ce;">,
</span><span style="color:#c0c5ce;">    </span><span style="color:#bf616a;">retain_days</span><span style="color:#c0c5ce;">: </span><span style="color:#b48ead;">u32</span><span style="color:#c0c5ce;">,
</span><span style="color:#c0c5ce;">    </span><span style="color:#bf616a;">pool</span><span style="color:#c0c5ce;">: PgPool
</span><span style="color:#c0c5ce;">) {
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">let mut</span><span style="color:#c0c5ce;"> sampler = Sampler {
</span><span style="color:#c0c5ce;">        trig_time: Utc::now(),
</span><span style="color:#c0c5ce;">        counters,
</span><span style="color:#c0c5ce;">        duration_s,
</span><span style="color:#c0c5ce;">        storage: Vec::new(),
</span><span style="color:#c0c5ce;">    };
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">    sampler.</span><span style="color:#96b5b4;">trigger</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">loop </span><span style="color:#c0c5ce;">{
</span><span style="color:#c0c5ce;">        </span><span style="color:#65737e;">//Cleanup
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> retain_time = Utc::now() - Duration::days(retain_days as </span><span style="color:#b48ead;">i64</span><span style="color:#c0c5ce;">);
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">if let </span><span style="color:#c0c5ce;">Err(e) = model::cleanup(&amp;pool, retain_time).await {
</span><span style="color:#c0c5ce;">            log::warn!(&quot;</span><span style="color:#a3be8c;">Unable to perform cleanup: {}</span><span style="color:#c0c5ce;">&quot;, e);
</span><span style="color:#c0c5ce;">        }
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        </span><span style="color:#65737e;">//sleep till the trigger time
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> duration = </span><span style="color:#b48ead;">match </span><span style="color:#c0c5ce;">(sampler.trig_time - Utc::now()).</span><span style="color:#96b5b4;">to_std</span><span style="color:#c0c5ce;">() {
</span><span style="color:#c0c5ce;">            Ok(value) =&gt; value,
</span><span style="color:#c0c5ce;">            Err(_) =&gt; {
</span><span style="color:#c0c5ce;">                log::warn!(&quot;</span><span style="color:#a3be8c;">Lag? trigger time is {}</span><span style="color:#c0c5ce;">&quot;, sampler.trig_time);
</span><span style="color:#c0c5ce;">                sampler.trig_time = Utc::now();
</span><span style="color:#c0c5ce;">                sampler.</span><span style="color:#96b5b4;">trigger</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">                (sampler.trig_time - Utc::now()).</span><span style="color:#96b5b4;">to_std</span><span style="color:#c0c5ce;">()
</span><span style="color:#c0c5ce;">                    .</span><span style="color:#96b5b4;">expect</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">Unable to figure out sleep time</span><span style="color:#c0c5ce;">&quot;)
</span><span style="color:#c0c5ce;">            }
</span><span style="color:#c0c5ce;">        };
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        tokio::time::sleep(duration).await;
</span><span style="color:#c0c5ce;">        sampler.</span><span style="color:#96b5b4;">trigger</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        </span><span style="color:#65737e;">//Insert into database
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">for</span><span style="color:#c0c5ce;"> row in sampler.storage.</span><span style="color:#96b5b4;">drain</span><span style="color:#c0c5ce;">(</span><span style="color:#d08770;">0</span><span style="color:#c0c5ce;">..) {
</span><span style="color:#c0c5ce;">            </span><span style="color:#b48ead;">if let </span><span style="color:#c0c5ce;">Err(e) = model::insert(&amp;pool, &amp;row).await {
</span><span style="color:#c0c5ce;">                log::warn!(&quot;</span><span style="color:#a3be8c;">Unable to insert into database: {}</span><span style="color:#c0c5ce;">&quot;, e);
</span><span style="color:#c0c5ce;">            }
</span><span style="color:#c0c5ce;">        }
</span><span style="color:#c0c5ce;">    }
</span><span style="color:#c0c5ce;">}
</span></code></pre>
<p>What? Code under test is smaller than the untested driver code. I ended up only calling the untested function from outside the module; thus all the code I wrote tests against is effectively private API.</p>
<p>When I initially read about TDD, I thought TDD makes it hard to build bad designs. This is just a quick and easy easy screw-up.</p>
<p>The large, untested function bothers me additionally. Ideally, we'd have integration tests to guard code like this, but let's be real... we deal with legacy code where majority of code has dodgy unit tests, if they exist at all. We'd be lucky to have a working CI setup that can run integration tests. It would be nicer if our unit tests could test more, even if ideally it shouldn't.</p>
<p>I made a note to myself, sometime later I'll try again from scratch.</p>
<h1>Further progress</h1>
<p>Since past few months, I have been writing as much code as I can using TDD.</p>
<p>At my day job, I was urgently asked to write a small service in Go. I did not know Go at that time. Multiple interfaces I had to use were not determined at that time, and solution to the problem it needed to solve was also not known. I cannot go into the details, but imagine a farmer handed you a buffalo one morning and asked you to take it to a faraway place. As soon as the farmer left, the buffalo starts acting erratically. You try out everything you know; you ask your friends, but the buffalo just wouldn't do what you want it to do. Eventually the farmer himself arrived, but he too cannot control the buffalo. It is an absolute chaos.</p>
<p>Over one month, the service code changed so much, there was almost nothing left in common compared to my initial implementation done in the first week. But throughout the time, I was able to drastically change behavior in less than a day... which I had to do at-least a dozen times.</p>
<p>I have made a few notes,</p>
<ul>
<li>TDD makes it significantly easier to learn new programming languages. It helped me quite a bit while learning Go and enabled me to start writing production code under a short notice. I think continuing to practice TDD is worth it for this benefit alone.</li>
<li>Hardest part of TDD is starting to write a new code while also not having any reference for how its interface should look like. Conversely, modifying an existing module with existing tests is the easiest.</li>
<li>When I write a new module, initially its quality is the poorest. The more I modify the module, the easier it becomes to change. Without TDD, I have to rely on experience to ensure that code remains easy to change throughout all modifications.</li>
<li>The signs that TDD sends you about your design is not always in the form of tests being hard to write.</li>
<li>Tests written after writing code have 70-80% coverage. With TDD, code has almost 100% coverage, all without trying to chase coverage.</li>
<li>Code written with TDD is initially more complex than needed. The additional complexity is only justified when adding more functionality and then TDD takes the lead. TDD does not make sense for temporary/disposable code.</li>
<li>We do not manually call the functions that represent the test cases, so these function names are as good as comments. We can go nuts on naming the tests, and we probably should. It is possible to speed up troubleshooting by naming the tests properly, though I have yet to learn how to properly take advantage of this. This leads neatly to the next note.</li>
<li>A significant reduction in printf-debugging. The better the failing test point towards where I messed up, the less likely I am to add log statements, use a debugger, etc. Before using TDD, my tests routinely had logging statements, now they don't.</li>
<li>Stress reduction. Those moments where you put all your brains into figuring out how to write this code are all gone, now I develop each module incrementally, adding one requirement fragment at a time.</li>
</ul>
<h1>Attempt 2 at performance monitoring</h1>
<p>With some experience gained, I set out to write the performance monitoring code again.</p>
<pre style="background-color:#2b303b;"><code class="language-rust"><span style="color:#b48ead;">pub trait </span><span style="color:#c0c5ce;">Clock {
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">now</span><span style="color:#c0c5ce;">(&amp;</span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">) -&gt; DateTime&lt;Utc&gt;;
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">sleep</span><span style="color:#c0c5ce;">(&amp;</span><span style="color:#b48ead;">mut </span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">, </span><span style="color:#bf616a;">duration</span><span style="color:#c0c5ce;">: Duration) -&gt; BoxFuture&lt;&#39;_, ()&gt;;
</span><span style="color:#c0c5ce;">}
</span><span style="color:#c0c5ce;">
</span><span style="color:#b48ead;">pub trait </span><span style="color:#c0c5ce;">Sampler {
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">reset</span><span style="color:#c0c5ce;">(&amp;</span><span style="color:#b48ead;">mut </span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">) -&gt; BoxFuture&lt;&#39;_, ()&gt;;
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">sample</span><span style="color:#c0c5ce;">(&amp;</span><span style="color:#b48ead;">mut </span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">, </span><span style="color:#bf616a;">end_time</span><span style="color:#c0c5ce;">: DateTime&lt;Utc&gt;) -&gt; BoxFuture&lt;&#39;_, ()&gt;;
</span><span style="color:#c0c5ce;">}
</span><span style="color:#c0c5ce;">
</span><span style="color:#b48ead;">pub</span><span style="color:#c0c5ce;"> async </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">sampling_task</span><span style="color:#c0c5ce;">(
</span><span style="color:#c0c5ce;">    </span><span style="color:#bf616a;">settings</span><span style="color:#c0c5ce;">: Settings,
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">mut </span><span style="color:#bf616a;">clock</span><span style="color:#c0c5ce;">: impl Clock,  </span><span style="color:#65737e;">//&lt; Sleep and getting current time is handled via a trait.
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">mut </span><span style="color:#bf616a;">sampler</span><span style="color:#c0c5ce;">: impl Sampler, </span><span style="color:#65737e;">//&lt; It turns out that the code relevant for
</span><span style="color:#c0c5ce;">                               </span><span style="color:#65737e;">//&lt; timing does not actually need to handle
</span><span style="color:#c0c5ce;">                               </span><span style="color:#65737e;">//&lt; the counters directly.
</span><span style="color:#c0c5ce;">) {
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> interval = Duration::seconds(settings.interval_s as </span><span style="color:#b48ead;">i64</span><span style="color:#c0c5ce;">);
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> tolerance = Duration::seconds(settings.tolerance_s as </span><span style="color:#b48ead;">i64</span><span style="color:#c0c5ce;">);
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">let mut</span><span style="color:#c0c5ce;"> target = Utc.</span><span style="color:#96b5b4;">timestamp_opt</span><span style="color:#c0c5ce;">(</span><span style="color:#d08770;">0</span><span style="color:#c0c5ce;">, </span><span style="color:#d08770;">0</span><span style="color:#c0c5ce;">).</span><span style="color:#96b5b4;">unwrap</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">let mut</span><span style="color:#c0c5ce;"> store_row = </span><span style="color:#d08770;">false</span><span style="color:#c0c5ce;">;
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">loop </span><span style="color:#c0c5ce;">{
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> now = clock.</span><span style="color:#96b5b4;">now</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> diff = target - now;
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">if</span><span style="color:#c0c5ce;"> diff &lt; -tolerance || diff &gt; (interval + tolerance) {
</span><span style="color:#c0c5ce;">            store_row = </span><span style="color:#d08770;">false</span><span style="color:#c0c5ce;">;
</span><span style="color:#c0c5ce;">            target = </span><span style="color:#96b5b4;">next_target</span><span style="color:#c0c5ce;">(now, settings.interval_s);
</span><span style="color:#c0c5ce;">        } </span><span style="color:#b48ead;">else if</span><span style="color:#c0c5ce;"> diff &gt; tolerance {
</span><span style="color:#c0c5ce;">            clock.</span><span style="color:#96b5b4;">sleep</span><span style="color:#c0c5ce;">(diff).await;
</span><span style="color:#c0c5ce;">        } </span><span style="color:#b48ead;">else </span><span style="color:#c0c5ce;">{
</span><span style="color:#c0c5ce;">            </span><span style="color:#b48ead;">if</span><span style="color:#c0c5ce;"> store_row {
</span><span style="color:#c0c5ce;">                sampler.</span><span style="color:#96b5b4;">sample</span><span style="color:#c0c5ce;">(target).await;
</span><span style="color:#c0c5ce;">            } </span><span style="color:#b48ead;">else </span><span style="color:#c0c5ce;">{
</span><span style="color:#c0c5ce;">                sampler.</span><span style="color:#96b5b4;">reset</span><span style="color:#c0c5ce;">().await;
</span><span style="color:#c0c5ce;">            }
</span><span style="color:#c0c5ce;">            store_row = </span><span style="color:#d08770;">true</span><span style="color:#c0c5ce;">;
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">            target += interval;
</span><span style="color:#c0c5ce;">        }
</span><span style="color:#c0c5ce;">    }
</span><span style="color:#c0c5ce;">}
</span><span style="color:#c0c5ce;">
</span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">next_target</span><span style="color:#c0c5ce;">(</span><span style="color:#bf616a;">time</span><span style="color:#c0c5ce;">: DateTime&lt;Utc&gt;, </span><span style="color:#bf616a;">interval_s</span><span style="color:#c0c5ce;">: </span><span style="color:#b48ead;">u32</span><span style="color:#c0c5ce;">) -&gt; DateTime&lt;Utc&gt; {
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> hour = time.</span><span style="color:#96b5b4;">hour</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> minute = time.</span><span style="color:#96b5b4;">minute</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> second = time.</span><span style="color:#96b5b4;">second</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> seconds_in_hour = second + (minute * </span><span style="color:#d08770;">60</span><span style="color:#c0c5ce;">);
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> extra_seconds = seconds_in_hour % interval_s; </span><span style="color:#65737e;">//5min
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> sleep_time = interval_s - extra_seconds;
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">    DateTime::from_utc(
</span><span style="color:#c0c5ce;">        time.</span><span style="color:#96b5b4;">date_naive</span><span style="color:#c0c5ce;">()
</span><span style="color:#c0c5ce;">            .</span><span style="color:#96b5b4;">and_hms_opt</span><span style="color:#c0c5ce;">(hour, minute, second)
</span><span style="color:#c0c5ce;">            .</span><span style="color:#96b5b4;">expect</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">Unable to round off sub-second part</span><span style="color:#c0c5ce;">&quot;),
</span><span style="color:#c0c5ce;">        Utc
</span><span style="color:#c0c5ce;">    ) + Duration::seconds(sleep_time as </span><span style="color:#b48ead;">i64</span><span style="color:#c0c5ce;">)
</span><span style="color:#c0c5ce;">}
</span><span style="color:#c0c5ce;">
</span></code></pre>
<p>Turns out that the <code>sampler</code> does not need to handle the atomic integers directly either.</p>
<pre style="background-color:#2b303b;"><code class="language-rust"><span style="color:#b48ead;">pub trait </span><span style="color:#c0c5ce;">Delegate: &#39;static + Send {
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">reset</span><span style="color:#c0c5ce;">(&amp;</span><span style="color:#b48ead;">mut </span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">);
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">sample</span><span style="color:#c0c5ce;">(&amp;</span><span style="color:#b48ead;">mut </span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">, </span><span style="color:#bf616a;">stats</span><span style="color:#c0c5ce;">: &amp;</span><span style="color:#b48ead;">mut</span><span style="color:#c0c5ce;"> Statistics);
</span><span style="color:#c0c5ce;">}
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">...
</span><span style="color:#b48ead;">impl</span><span style="color:#c0c5ce;">&lt;ErrorHandler&gt; Sampler </span><span style="color:#b48ead;">for </span><span style="color:#c0c5ce;">SamplerImpl&lt;ErrorHandler&gt;
</span><span style="color:#b48ead;">where</span><span style="color:#c0c5ce;"> ErrorHandler: Fn(sqlx::Error) + Send {
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">reset</span><span style="color:#c0c5ce;">(&amp;</span><span style="color:#b48ead;">mut </span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">) -&gt; BoxFuture&lt;&#39;_, ()&gt; {
</span><span style="color:#c0c5ce;">        Box::pin(async </span><span style="color:#b48ead;">move </span><span style="color:#c0c5ce;">{
</span><span style="color:#c0c5ce;">            </span><span style="color:#b48ead;">for</span><span style="color:#c0c5ce;"> x in &amp;</span><span style="color:#b48ead;">mut </span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">.delegates {
</span><span style="color:#c0c5ce;">                x.</span><span style="color:#96b5b4;">reset</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">            }
</span><span style="color:#c0c5ce;">        })
</span><span style="color:#c0c5ce;">    }
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">sample</span><span style="color:#c0c5ce;">(&amp;</span><span style="color:#b48ead;">mut </span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">, </span><span style="color:#bf616a;">end_time</span><span style="color:#c0c5ce;">: DateTime&lt;Utc&gt;) -&gt; BoxFuture&lt;&#39;_, ()&gt; {
</span><span style="color:#c0c5ce;">        Box::pin(async </span><span style="color:#b48ead;">move </span><span style="color:#c0c5ce;">{
</span><span style="color:#c0c5ce;">            </span><span style="color:#b48ead;">let mut</span><span style="color:#c0c5ce;"> stats = Statistics::default();
</span><span style="color:#c0c5ce;">            </span><span style="color:#b48ead;">for</span><span style="color:#c0c5ce;"> x in &amp;</span><span style="color:#b48ead;">mut </span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">.delegates {
</span><span style="color:#c0c5ce;">                x.</span><span style="color:#96b5b4;">sample</span><span style="color:#c0c5ce;">(&amp;</span><span style="color:#b48ead;">mut</span><span style="color:#c0c5ce;"> stats);
</span><span style="color:#c0c5ce;">            }
</span><span style="color:#c0c5ce;">            </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> row = Row {
</span><span style="color:#c0c5ce;">                end_time,
</span><span style="color:#c0c5ce;">                counters: stats,
</span><span style="color:#c0c5ce;">            };
</span><span style="color:#c0c5ce;">            </span><span style="color:#b48ead;">if let </span><span style="color:#c0c5ce;">Err(e) = model::insert(&amp;</span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">.pool, &amp;row).await {
</span><span style="color:#c0c5ce;">                (</span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">.error_handler)(e);
</span><span style="color:#c0c5ce;">            }
</span><span style="color:#c0c5ce;">        })
</span><span style="color:#c0c5ce;">    }
</span><span style="color:#c0c5ce;">}
</span><span style="color:#c0c5ce;">
</span></code></pre>
<p>Such a design allowed for more sources of performance data. I could use anything that fit the <code>Delegate</code> trait. e.g I added some statistics from <code>/proc</code></p>
<pre style="background-color:#2b303b;"><code class="language-rust"><span style="color:#b48ead;">impl </span><span style="color:#c0c5ce;">Delegate </span><span style="color:#b48ead;">for </span><span style="color:#c0c5ce;">Procfs {
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">sample</span><span style="color:#c0c5ce;">(&amp;</span><span style="color:#b48ead;">mut </span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">, </span><span style="color:#bf616a;">stats</span><span style="color:#c0c5ce;">: &amp;</span><span style="color:#b48ead;">mut</span><span style="color:#c0c5ce;"> Statistics) {
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> res : Result&lt;(), String&gt; = (|| {
</span><span style="color:#c0c5ce;">            </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> proc_self_stat = procfs::process::Process::myself()
</span><span style="color:#c0c5ce;">                .</span><span style="color:#96b5b4;">map_err</span><span style="color:#c0c5ce;">(|</span><span style="color:#bf616a;">e</span><span style="color:#c0c5ce;">| format!(&quot;</span><span style="color:#a3be8c;">Cannot read /proc/self: </span><span style="color:#d08770;">{}</span><span style="color:#c0c5ce;">&quot;, e))?
</span><span style="color:#c0c5ce;">                .</span><span style="color:#96b5b4;">stat</span><span style="color:#c0c5ce;">()
</span><span style="color:#c0c5ce;">                .</span><span style="color:#96b5b4;">map_err</span><span style="color:#c0c5ce;">(|</span><span style="color:#bf616a;">e</span><span style="color:#c0c5ce;">| format!(&quot;</span><span style="color:#a3be8c;">Cannot read /proc/self/stat: </span><span style="color:#d08770;">{}</span><span style="color:#c0c5ce;">&quot;, e))?;
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">            </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> load_average = procfs::LoadAverage::new()
</span><span style="color:#c0c5ce;">                .</span><span style="color:#96b5b4;">map_err</span><span style="color:#c0c5ce;">(|</span><span style="color:#bf616a;">e</span><span style="color:#c0c5ce;">| format!(&quot;</span><span style="color:#a3be8c;">Cannot read /proc/loadavg: </span><span style="color:#d08770;">{}</span><span style="color:#c0c5ce;">&quot;, e))?;
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">            stats.rss = Some(((proc_self_stat.rss * procfs::page_size())
</span><span style="color:#c0c5ce;">                              /</span><span style="color:#d08770;">1024</span><span style="color:#c0c5ce;">) as </span><span style="color:#b48ead;">i64</span><span style="color:#c0c5ce;">);
</span><span style="color:#c0c5ce;">            stats.load_avg = Some((load_average.five * </span><span style="color:#d08770;">1000.0</span><span style="color:#c0c5ce;">) as </span><span style="color:#b48ead;">i64</span><span style="color:#c0c5ce;">);
</span><span style="color:#c0c5ce;">            stats.utime = Some((((proc_self_stat.utime - </span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">.cur_utime) * </span><span style="color:#d08770;">1000</span><span style="color:#c0c5ce;">)
</span><span style="color:#c0c5ce;">                               / procfs::ticks_per_second()) as </span><span style="color:#b48ead;">i64</span><span style="color:#c0c5ce;">);
</span><span style="color:#c0c5ce;">            stats.stime = Some((((proc_self_stat.stime - </span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">.cur_stime) * </span><span style="color:#d08770;">1000</span><span style="color:#c0c5ce;">)
</span><span style="color:#c0c5ce;">                               / procfs::ticks_per_second()) as </span><span style="color:#b48ead;">i64</span><span style="color:#c0c5ce;">);
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">            </span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">.cur_utime = proc_self_stat.utime;
</span><span style="color:#c0c5ce;">            </span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">.cur_stime = proc_self_stat.stime;
</span><span style="color:#c0c5ce;">            Ok(())
</span><span style="color:#c0c5ce;">        })();
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">if let </span><span style="color:#c0c5ce;">Err(msg) = res {
</span><span style="color:#c0c5ce;">            log::warn!(&quot;</span><span style="color:#a3be8c;">{}</span><span style="color:#c0c5ce;">&quot;, msg);
</span><span style="color:#c0c5ce;">        }
</span><span style="color:#c0c5ce;">    }
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">reset</span><span style="color:#c0c5ce;">(&amp;</span><span style="color:#b48ead;">mut </span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">) {
</span><span style="color:#c0c5ce;">        </span><span style="color:#bf616a;">self</span><span style="color:#c0c5ce;">.</span><span style="color:#96b5b4;">sample</span><span style="color:#c0c5ce;">(&amp;</span><span style="color:#b48ead;">mut </span><span style="color:#c0c5ce;">Statistics::default());
</span><span style="color:#c0c5ce;">    }
</span><span style="color:#c0c5ce;">}
</span><span style="color:#c0c5ce;">
</span></code></pre>
]]></description><author>akash_rawal</author><pubDate>Sat, 1 Apr 2023 11:59:50 +0000</pubDate></item><item><title>Introducing Disposables: Daemonless-ready Testcontainers alternative</title><link>https://01def.io/blog/view/introducing-disposables</link><description><![CDATA[<p>In my last article, I wrote about Testcontainers' dependency on Docker socket
access, and a potential way to remove it.</p>
<p>But how further can we push that method?</p>
<h1>Design</h1>
<p>The goal, in case it wasn't clear yet, is to write a Testcontainers-like
library that works well with Podman. Testcontainers is a library that starts
your test dependencies in a container and stop them after you are done
using them.</p>
<p>Starting a container is easy, stopping them is the hard part.</p>
<p>Our new library will replace the entrypoint of the container with our own,
and use that entrypoint to terminate the container from within.
I am calling the new library <code>Disposables</code>.</p>
<p>Given that we have our own entrypoint inside the container, there is a lot more
we can do than just terminate the container after use.</p>
<h1>The entrypoint can do more</h1>
<p>Testcontainers library is written in many different programming languages.
All features of Testcontainers need to be re-implemented in each programming
language. We however can take advantage of running our own entrypoint
inside the container, and add feature implementation in the entrypoint.
This way a lot of implementation only needs to be done once, instead of
re-implementing them in each programming language.</p>
<p>For example, waiting for containers to become ready is entirely done
within the entrypoint, and only readiness notification is sent back to
the test suite.</p>
<p>I have some future plans to implement some Testcontainers features, like Testcontainers modules. I think I only have to write the implementation once and only write the API for each programming language, overall I predict 'modules' will be significantly less work to support.</p>
<h1>Conclusion</h1>
<p>You can try out the library now at <a href="https://github.com/akashrawal/disposables">https://github.com/akashrawal/disposables</a>.
Right now only Java and Rust languages are available, as I am basically
only developing based on my use cases.</p>
<p>I have tested Disposables with Docker daemon configured with user namespaces and with SELinux. Where <a href="https://martijndashorst.com/blog/2021/11/15/Fedora-Docker-SELinux">Testcontainers</a> <a href="https://golang.testcontainers.org/system_requirements/using_podman/">has</a> <a href="https://github.com/testcontainers/testcontainers-go/issues/2684">issues</a>, Disposables just works, no configuration needed.</p>
<pre style="background-color:#2b303b;"><code><span style="color:#c0c5ce;">user@localhost:~/selinux-test$ export DISPOSABLES_ENGINE=docker
</span><span style="color:#c0c5ce;">user@localhost:~/selinux-test$ cargo test                                                             
</span><span style="color:#c0c5ce;">    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.02s
</span><span style="color:#c0c5ce;">     Running unittests src/main.rs (target/debug/deps/selinux_test-a03052e3d408ceef)
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">running 1 test
</span><span style="color:#c0c5ce;">test test::nginx ... ok
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.86s
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">user@localhost:~/selinux-test$ export DISPOSABLES_ENGINE=podman
</span><span style="color:#c0c5ce;">user@localhost:~/selinux-test$ cargo test
</span><span style="color:#c0c5ce;">    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.02s
</span><span style="color:#c0c5ce;">     Running unittests src/main.rs (target/debug/deps/selinux_test-a03052e3d408ceef)
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">running 1 test
</span><span style="color:#c0c5ce;">test test::nginx ... ok
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.73s
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">user@localhost:~/selinux-test$ sestatus 
</span><span style="color:#c0c5ce;">SELinux status:                 enabled
</span><span style="color:#c0c5ce;">SELinuxfs mount:                /sys/fs/selinux
</span><span style="color:#c0c5ce;">SELinux root directory:         /etc/selinux
</span><span style="color:#c0c5ce;">Loaded policy name:             targeted
</span><span style="color:#c0c5ce;">Current mode:                   enforcing
</span><span style="color:#c0c5ce;">Mode from config file:          enforcing
</span><span style="color:#c0c5ce;">Policy MLS status:              enabled
</span><span style="color:#c0c5ce;">Policy deny_unknown status:     allowed
</span><span style="color:#c0c5ce;">Memory protection checking:     actual (secure)
</span><span style="color:#c0c5ce;">Max kernel policy version:      33
</span><span style="color:#c0c5ce;">
</span></code></pre>
]]></description><author>akash_rawal</author><pubDate>Sun, 27 Oct 2024 13:32:59 +0000</pubDate></item><item><title>Have courage to change legacy code. </title><link>https://01def.io/blog/view/4</link><description><![CDATA[<p>Are you afraid to change existing code because 'if it ain't broke, don't fix it'? You might want to change your mind.</p>
<h2>Changes are inevitable.</h2>
<p>Your software works well today. Tomorrow your customer might ask for a new
feature. Security vulnerabilities may be discovered in your software or
libraries and frameworks your software uses. Your software may need to adapt
to newer standards or interoperate with other software built recently. Or your
management is asking you to increase test coverage.</p>
<p>Either way, I have seen simple changes being delayed by days or weeks, just
because the engineers don't have courage to change the code. It is fruitless
to be afraid however.</p>
<h2>There will be issues.</h2>
<p>Legacy code often does not have tests to provide a safety net. The code can
fail in ways you might not foresee. You'll uncover bugs that have laid dormant
for years. When change is necessary, be prepared for these outcomes.</p>
<h2>Recognize the patterns.</h2>
<p>Have respect for those who wrote the code. They were limited by what they
knew at that time and did their best. So there will be patterns. However bad
they are today, those patterns were the 'good practices' they knew at that time.
Learn to recognize them.</p>
<h2>Setup a fast build system.</h2>
<p>This will help you change the code easier by reducing your code/test cycle time.
If you learnt to recognize the patterns, this might be easier than you think.</p>
<h2>Prioritize</h2>
<p>Always improve the code that you changed in some way.
Focus more on improving code that is changed more frequently.
Refactoring the code that has no business reason to change is often fruitless,
so channel your energy elsewhere.</p>
<h2>Testing</h2>
<p>Use automated testing to reduce the likelihood of adding defects. Add
approval tests to avoid changing behavior of existing code. You might still
break things in brand-new ways, so be prepared.</p>
<h2>There will be payoffs.</h2>
<p>You'll be the one who can fix complex issues in the codebase.
You'll lay the groundwork for setting up automated testing that catches real
bugs. You'll enable the rest of the team to work more quickly and deliver sooner.</p>
]]></description><author>akash_rawal</author><pubDate>Fri, 27 Oct 2023 07:50:21 +0000</pubDate></item><item><title>Deno made a huge mistake</title><link>https://01def.io/blog/view/deno-made-a-huge-mistake-node</link><description><![CDATA[<p>Deno intended to be the redo of 'Javascript outside the browser',
making it simpler while getting rid of the legacy.</p>
<p>However since 2022, Deno is trying to imitate Node more and more, and
this is destroying Deno's ecosystem and undermining its own goal.</p>
<h1>Users' Perspective</h1>
<p>When Deno was announced in 2020, Deno was its own thing. Deno bet hard on ESM,
re-used web APIs and metas wherever possible, pushed for URL imports instead
of <code>node_modules</code>, supported executing typescript files without <code>tsx</code>
or <code>tsconfig.json</code> and so on.</p>
<p>&quot;If Deno implemented Node APIs and tried to imitate
Node and NPM ways of doing things, existing libraries and frameworks written
using Node will automatically work in Deno and thus adopting Deno will be
easier.&quot; I don't know who said this, but someone must have said this.</p>
<p>What has happened instead, is that Deno trying to imitate Node has
disincentivized formation of any practical ecosystem for Deno,
while the existing libraries and frameworks are unreliable when used with Deno.</p>
<p>I tried using Next.js via Deno some time back, and Next.js dev server crashed
when Turbopack is enabled. There is a
<a href="https://github.com/denoland/deno/issues/26584">workaround</a>, so for the time being
that issue is solved. But today there is another issue, type checking
(and LSP) for JSX is broken.</p>
<p>This is my experience with using Node libraries with Deno. Every hour of
work is accompanied with another hour (sometimes more)
of troubleshooting the libraries themselves.</p>
<p>I think this is the consequence of trying to imitate something you are not.
Deno is trying to be compatible with Node. but there are gaps in the
said compatibility. I think achieving compatibility with Node is hard,
and the gaps in compatibility will stay for a long time.</p>
<p>For example, at the time of writing, FileHandle.readLines is not implemented
in Deno.</p>
<pre style="background-color:#2b303b;"><code class="language-js"><span style="color:#b48ead;">import </span><span style="color:#bf616a;">fs </span><span style="color:#b48ead;">from </span><span style="color:#c0c5ce;">&#39;</span><span style="color:#a3be8c;">node:fs/promises</span><span style="color:#c0c5ce;">&#39;;
</span><span style="color:#c0c5ce;">
</span><span style="color:#b48ead;">const </span><span style="color:#bf616a;">hd </span><span style="color:#c0c5ce;">= </span><span style="color:#bf616a;">await fs</span><span style="color:#c0c5ce;">.</span><span style="color:#96b5b4;">open</span><span style="color:#c0c5ce;">(</span><span style="color:#ebcb8b;">Deno</span><span style="color:#c0c5ce;">.args[</span><span style="color:#d08770;">0</span><span style="color:#c0c5ce;">]);
</span><span style="color:#b48ead;">for await </span><span style="color:#c0c5ce;">(</span><span style="color:#bf616a;">const line </span><span style="color:#c0c5ce;">of </span><span style="color:#bf616a;">hd</span><span style="color:#c0c5ce;">.</span><span style="color:#8fa1b3;">readLines</span><span style="color:#c0c5ce;">()) {
</span><span style="color:#c0c5ce;">	console.</span><span style="color:#96b5b4;">log</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">Line: </span><span style="color:#c0c5ce;">&quot;, </span><span style="color:#bf616a;">line</span><span style="color:#c0c5ce;">);
</span><span style="color:#c0c5ce;">}
</span></code></pre>
<p>The above script crashes despite having no issues with Typescript.</p>
<pre style="background-color:#2b303b;"><code><span style="color:#c0c5ce;">$ deno check test.ts
</span><span style="color:#c0c5ce;">Check file://path/to/test.ts
</span><span style="color:#c0c5ce;">$ deno run -R test.ts input.txt
</span><span style="color:#c0c5ce;">error: Uncaught (in promise) TypeError: hd.readLines(...) is not a function or its return value is not async iterable
</span><span style="color:#c0c5ce;">for await (const line of hd.readLines()) {
</span><span style="color:#c0c5ce;">                            ^
</span><span style="color:#c0c5ce;">    at file://path/to/test.ts:4:29
</span><span style="color:#c0c5ce;">$
</span></code></pre>
<p>Using NPM libraries is also typically accompanied with a complete disregard
for Deno's security features. You just end up running deno with <code>-A</code> all the
time.</p>
<h1>Library devs' Perspective</h1>
<p>Deno 1.0 is released, and library devs are excited to join the ecosystem.
Projects like <a href="https://github.com/cmorten/deno-rollup">drollup</a>,
<a href="https://github.com/eveningkid/denodb">denodb</a>,
<a href="https://github.com/shreyascodes-tech/drizzle-deno">drizzle-deno</a> are started,</p>
<p>But then Deno announces Node and NPM compatibility and all that
momentum is gone.</p>
<p>Now, it seems like Deno's practical ecosystem is limited to first party libraries
like @std and Fresh, libraries on JSR, and a small subset of libaries on NPM
that works on Deno.</p>
<p>If you look at the situation from library or framework dev's perspective,
it all seems reasonable. Most of them are not new to Javascript; they are
much more familiar with Node than with Deno.</p>
<p>When Deno is announced, some of them might want to contribute to Deno's ecosystem.
But then Deno announces Node and NPM compatibility, and now there is not enough
incentive to develop software for Deno.
It doesn't matter that Node compatibility is spotty,
because they'd rather just go back to using Node like they're used to.
Supporting multiple runtimes is painful. If you want to understand the pain,
ask anyone who tried to ship any cross platform application written in C or C++.</p>
<h1>Deno should have promoted its own API</h1>
<p>If the competition is trying to be more like Node, Node is the winner.</p>
<p>There is a lesson to be learned here. If you are trying to replace
a legacy system, don't re-implement the same legacy system.
Instead, put the burden of backwards-compatibility on the legacy system.</p>
<p>Deno aimed to uncomplicate Javascript. (Deno's homepage literally says that.)
By trying to mimic Node, Deno has unintentionally put Node's complexity
problem at the center of the stage. And now, it cannot be removed.
Instead of being a brand new thing, Deno ended up being a less reliable
variant of Node.</p>
<p>Deno should have supported its own API on top of Node instead.
Since Deno controls its API, supporting its own API on Node would be simpler
than supporting Node APIs.
For library and framework developers, libraries made for Deno would work on
Node and there would be no need to support multiple runtimes.</p>
<p>This would have resulted in a much larger ecosystem of software made for Deno
which is more reliable and free of Node's legacy.</p>
]]></description><author>akash_rawal</author><pubDate>Sat, 11 Jan 2025 11:37:05 +0000</pubDate></item><item><title>Testing a routing protocol using network namespaces</title><link>https://01def.io/blog/view/5</link><description><![CDATA[<p>How do you test your own routing protocol without a real network?</p>
<h1>Background</h1>
<p>For a homelab, all I have is an Intel NUC acting as a singular server. It is working as great as it could, but the single server setup leaves more to be desired. Every time I take it down for maintenance, for the time the server is down, I lose access to the services running on the server, and the internet. I wish to upgrade to a cluster of servers so that I could have additional servers to pick up the slack automatically. I could set up a Pacemaker cluster to ensure that only one active server will handle my default route and all my services. But then I would be paying for <code>N</code> servers and getting the benefit of one. If I have <code>N</code> servers, I want the capacity of <code>N</code> servers, or at-least a decent fraction of <code>N</code>. After some searching I figured out that VyOS and other router software can achieve this, but I don't want the complexity of managing <code>N</code> different routers. I want <code>1</code> router. One, distributed router.</p>
<h1>Baby steps first</h1>
<h2>The goal</h2>
<p>First, let's figure out a way to reliably discover each node's immediate neighbors, and via how many ways we can connect to them.</p>
<p>Each node sends its information, like its name, public keys, etc via IPv6 link local multicast packets. I'll call these packets <code>advertisements</code>. I choose IPv6 link local because no manual IP address configuration is required. The kernel automatically assigns IPv6 link local addresses for each network interface.</p>
<p>When another node (<code>B</code>) receives the multicast packets, it knows that it can receive packets from the former node (<code>A</code>), but what about the other way round? For that, node <code>B</code> then establishes a TCP connection to node <code>A</code>. If node <code>B</code> can connect to node <code>A</code>, we can conclude that a bidirectional communication is possible between node <code>A</code> and node <code>B</code>.</p>
<p>We also need to deal with a potential race condition. What if both node <code>A</code> and node <code>B</code> connect to each other simultaneously? We need a tie breaker to decide which connection to keep. All we need to do is to assign a random number called <code>rank</code> to each connection, and simply keep the connection with highest <code>rank</code>.</p>
<p>But what if the two nodes are connected by more than one links? What if there is a switch in between? The number of TCP connections will increase quadratically, and before long we'd be eating file descriptors for breakfast. So, instead of establishing a new TCP connection for each unique advertisement, the two nodes will only maintain a single TCP connection. The two nodes share advertisements received from each other. Thus for each pair of nodes <code>A</code> and <code>B</code> node <code>A</code> has two sets of advertisements:</p>
<ul>
<li>Advertisements received from node <code>B</code> over multicast UDP</li>
<li>Node <code>A</code>'s advertisements reported as received by node <code>B</code> over TCP connection</li>
</ul>
<p>The intersection of these two sets will provide all working bidirectional communication paths between node <code>A</code> and node <code>B</code> (without routing, that is.)</p>
<h2>An example</h2>
<p>Consider the following scenario:</p>
<pre style="background-color:#2b303b;"><code><span style="color:#c0c5ce;"> -----------------                                            -----------------
</span><span style="color:#c0c5ce;"> |               |  eth0(fe80::a0)           eth0(fe80::b0)   |               |
</span><span style="color:#c0c5ce;"> |      Node     |--------------------------------------------|      Node     |
</span><span style="color:#c0c5ce;"> |               |                                            |               |
</span><span style="color:#c0c5ce;"> |       A       |  eth1(fe80::a1)           eth1(fe80::b1)   |       B       |
</span><span style="color:#c0c5ce;"> |               |--------------------------------------------|               |
</span><span style="color:#c0c5ce;"> |               |                                            |               |
</span><span style="color:#c0c5ce;"> -----------------                                            -----------------
</span><span style="color:#c0c5ce;">
</span></code></pre>
<p>There are two network interfaces attached to each node, namely <code>eth0</code> and <code>eth1</code>. The network interfaces are shown above with their respective link local addresses.</p>
<p>What happens if Node A sends an advertisement via eth0?</p>
<pre style="background-color:#2b303b;"><code><span style="color:#c0c5ce;">  ----------                                   ----------
</span><span style="color:#c0c5ce;">  | Node A |                                   | Node B |
</span><span style="color:#c0c5ce;">  ----------                                   ----------
</span><span style="color:#c0c5ce;">      |                                            |
</span><span style="color:#c0c5ce;">      | Advertisement(name: A)                     |
</span><span style="color:#c0c5ce;">      |-------------------------------------------&gt;|
</span><span style="color:#c0c5ce;">      | eth0                                  eth0 |
</span><span style="color:#c0c5ce;">      |                                            |
</span><span style="color:#c0c5ce;">      |                                TCP connect |
</span><span style="color:#c0c5ce;">      |&lt;-------------------------------------------|
</span><span style="color:#c0c5ce;">      | eth0                                  eth0 |
</span><span style="color:#c0c5ce;">      |                                            |
</span><span style="color:#c0c5ce;">
</span></code></pre>
<p>Once node <code>B</code> receives the advertisement, it establishes a TCP connection back to Node <code>A</code>. Now that TCP connection is established, both nodes <code>A</code> and <code>B</code> are aware that bidirectional communication is possible between the two nodes via <code>eth0</code> network interface.</p>
<p>Discovery of the second link (<code>eth1 -- eth1</code>) happens without creating another TCP connection.</p>
<pre style="background-color:#2b303b;"><code><span style="color:#c0c5ce;">  ----------                                                    ----------
</span><span style="color:#c0c5ce;">  | Node A |                                                    | Node B |
</span><span style="color:#c0c5ce;">  ----------                                                    ----------
</span><span style="color:#c0c5ce;">      |                                                             |
</span><span style="color:#c0c5ce;">      | Advertisement(name: A)                                      |
</span><span style="color:#c0c5ce;">      |------------------------------------------------------------&gt;|
</span><span style="color:#c0c5ce;">      | eth0                                                   eth0 |
</span><span style="color:#c0c5ce;">      |                                                             |
</span><span style="color:#c0c5ce;">      |                                                 TCP connect |
</span><span style="color:#c0c5ce;">      |&lt;------------------------------------------------------------|
</span><span style="color:#c0c5ce;">      | eth0                                                   eth0 |
</span><span style="color:#c0c5ce;">      |                                                             |
</span><span style="color:#c0c5ce;">      | Advertisement(name: A)                                      |
</span><span style="color:#c0c5ce;">      |------------------------------------------------------------&gt;|
</span><span style="color:#c0c5ce;">      | eth1                                                   eth1 |
</span><span style="color:#c0c5ce;">      |                                                             |
</span><span style="color:#c0c5ce;">      |         Heartbeat(received_adverts: [fe80::a1 -&gt; fe80::b1]) |
</span><span style="color:#c0c5ce;">      |&lt;------------------------------------------------------------|
</span><span style="color:#c0c5ce;">      | eth0                                                   eth0 |
</span><span style="color:#c0c5ce;">      |                                                             |
</span><span style="color:#c0c5ce;">      |                                      Advertisement(name: B) |
</span><span style="color:#c0c5ce;">      |&lt;------------------------------------------------------------|
</span><span style="color:#c0c5ce;">      | eth1                                                   eth1 |
</span><span style="color:#c0c5ce;">      |                                                             |
</span><span style="color:#c0c5ce;">      | Heartbeat(received_adverts: [fe80::b1 -&gt; fe80::a1])         |
</span><span style="color:#c0c5ce;">      |------------------------------------------------------------&gt;|
</span><span style="color:#c0c5ce;">      | eth0                                                   eth0 |
</span><span style="color:#c0c5ce;">      |                                                             |
</span><span style="color:#c0c5ce;">
</span></code></pre>
<p>The nodes share the received advertisements over the already established TCP connection. They aren't sent on demand, but rather as part of periodic heartbeat messages sent over the TCP connection.</p>
<h2>Testing time</h2>
<h3>Network namespaces, without root</h3>
<p>Linux has network namespaces. Basically you divide the Linux network stack into partitions; each partition behaves as a separate computer from network point of view. You can easily do this using <code>unshare --net</code> command and you'll have a shell running in a new, isolated network namespace.</p>
<pre style="background-color:#2b303b;"><code><span style="color:#c0c5ce;"># ip link show
</span><span style="color:#c0c5ce;">1: lo: &lt;LOOPBACK,UP,LOWER_UP&gt; mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
</span><span style="color:#c0c5ce;">    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
</span><span style="color:#c0c5ce;">3: eth0@if2: &lt;NO-CARRIER,BROADCAST,MULTICAST,UP&gt; mtu 1500 qdisc noqueue state LOWERLAYERDOWN mode DEFAULT group default qlen 1000
</span><span style="color:#c0c5ce;">    link/ether 2a:66:b1:f2:e8:80 brd ff:ff:ff:ff:ff:ff link-netnsid 0
</span><span style="color:#c0c5ce;"># unshare --net
</span><span style="color:#c0c5ce;"># ip link show
</span><span style="color:#c0c5ce;">1: lo: &lt;LOOPBACK&gt; mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
</span><span style="color:#c0c5ce;">    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
</span><span style="color:#c0c5ce;"># 
</span></code></pre>
<p>You can make these isolated namespaces reachable by either moving real network interfaces into them, or by creating <a href="https://www.man7.org/linux/man-pages/man4/veth.4.html">veth</a> pairs and moving one of them into the network namespace.</p>
<p>One small problem, you need to be root to <code>unshare</code> a network namespace, and you don't want to run your test suite as root.</p>
<pre style="background-color:#2b303b;"><code><span style="color:#c0c5ce;">$ unshare --net
</span><span style="color:#c0c5ce;">unshare: unshare failed: Operation not permitted
</span><span style="color:#c0c5ce;">$
</span></code></pre>
<p>Fortunately, we have user namespaces. For this article all you need to understand is that by <code>unshare</code>ing a user namespace, you can create a sandbox where your process has root access within the box, but not outside it.</p>
<pre style="background-color:#2b303b;"><code><span style="color:#c0c5ce;">$ unshare --user --map-root-user
</span><span style="color:#c0c5ce;"># ls -l .bashrc /usr/bin/bash
</span><span style="color:#c0c5ce;">-rw-r--r-- 1 root   root       933 Feb  6  2023 .bashrc
</span><span style="color:#c0c5ce;">-rwxr-xr-x 1 nobody nobody 1112880 Jan 16 16:18 /usr/bin/bash
</span><span style="color:#c0c5ce;"># 
</span></code></pre>
<p>In a nutshell, my UID has been mapped to <code>root</code> and all other UIDs have been made inaccessible. This does not give me any special privileges.</p>
<pre style="background-color:#2b303b;"><code><span style="color:#c0c5ce;"># : &gt; /usr/bin/bash
</span><span style="color:#c0c5ce;">-bash: /usr/bin/bash: Permission denied
</span></code></pre>
<p>But the important bit, can I <code>unshare</code> a network namespace now?</p>
<pre style="background-color:#2b303b;"><code><span style="color:#c0c5ce;">$ unshare --user --map-root-user
</span><span style="color:#c0c5ce;"># ls -l .bashrc /usr/bin/bash
</span><span style="color:#c0c5ce;">-rw-r--r-- 1 root   root       933 Feb  6  2023 .bashrc
</span><span style="color:#c0c5ce;">-rwxr-xr-x 1 nobody nobody 1112880 Jan 16 16:18 /usr/bin/bash
</span><span style="color:#c0c5ce;"># unshare --net
</span><span style="color:#c0c5ce;"># ip link show
</span><span style="color:#c0c5ce;">1: lo: &lt;LOOPBACK&gt; mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
</span><span style="color:#c0c5ce;">    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
</span><span style="color:#c0c5ce;"># 
</span></code></pre>
<p>Yes!</p>
<h3>Remote controlled worker process</h3>
<p>User namespaces solves the problem of emulating a network situation without any virtualization or any container runtime (like the one shown in the first figure), but how would you set them up? There is <code>nsenter</code> but it needs actual root user to work.</p>
<pre style="background-color:#2b303b;"><code><span style="color:#c0c5ce;">$ unshare --user --map-root-user --net sleep infinity &amp;
</span><span style="color:#c0c5ce;">[1] 208920
</span><span style="color:#c0c5ce;">$ nsenter --net=/proc/208920/ns/net -- ip link show
</span><span style="color:#c0c5ce;">nsenter: reassociate to namespace &#39;ns/net&#39; failed: Operation not permitted
</span><span style="color:#c0c5ce;">$ sudo nsenter --net=/proc/208920/ns/net -- ip link show
</span><span style="color:#c0c5ce;">1: lo: &lt;LOOPBACK&gt; mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
</span><span style="color:#c0c5ce;">    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
</span><span style="color:#c0c5ce;">$ 
</span></code></pre>
<p>And by the way <code>/proc/&lt;pid&gt;/ns/net</code> file represents the network namespace of a given process.</p>
<p>So I made a remote controlled worker process to execute commands from within a network namespace, which connects to the test suite listening at a unix domain socket.</p>
<p>The first worker process is run in a user namespace via <code>unshare</code>; I call it the hub process. Once the hub process connects to the test suite, it can be made to spawn additional worker processes, each running under separate network namespaces via <code>unshare</code>. All the worker processes connect to the same unix domain socket and can be controlled individually and in the right order determined by the test suite.</p>
<p>Why do I need a hub process? That is because each network namespace needs to be under a common user namespace so that network interfaces can be moved between them.</p>
<pre style="background-color:#2b303b;"><code><span style="color:#c0c5ce;">$ unshare --user --map-root-user --net sleep infinity &amp;
</span><span style="color:#c0c5ce;">[1] 211416
</span><span style="color:#c0c5ce;">$ unshare --user --map-root-user --net
</span><span style="color:#c0c5ce;"># ip link add eth0 type veth peer name temp
</span><span style="color:#c0c5ce;"># ip link set temp netns 211416 name eth0
</span><span style="color:#c0c5ce;">RTNETLINK answers: Operation not permitted
</span><span style="color:#c0c5ce;"># 
</span></code></pre>
<p>Having a hub process makes it easy to have a common user namespace.</p>
<pre style="background-color:#2b303b;"><code><span style="color:#c0c5ce;">$ unshare --user --map-root-user
</span><span style="color:#c0c5ce;"># unshare --net sleep infinity &amp;
</span><span style="color:#c0c5ce;">[1] 211458
</span><span style="color:#c0c5ce;"># ip link add eth0 type veth peer name temp
</span><span style="color:#c0c5ce;"># ip link set temp netns 211458 name eth0
</span><span style="color:#c0c5ce;"># ip link show
</span><span style="color:#c0c5ce;">1: lo: &lt;LOOPBACK&gt; mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
</span><span style="color:#c0c5ce;">    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
</span><span style="color:#c0c5ce;">3: eth0@if2: &lt;BROADCAST,MULTICAST&gt; mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
</span><span style="color:#c0c5ce;">    link/ether 66:e8:e1:8a:99:c0 brd ff:ff:ff:ff:ff:ff link-netnsid 0
</span><span style="color:#c0c5ce;">
</span></code></pre>
<p>Which means, finally I have a decent way to set up a test scenario, while keeping up the readability of test cases.</p>
<pre style="background-color:#2b303b;"><code class="language-rust"><span style="color:#c0c5ce;">#[</span><span style="color:#bf616a;">tokio</span><span style="color:#c0c5ce;">::</span><span style="color:#bf616a;">test</span><span style="color:#c0c5ce;">]
</span><span style="color:#c0c5ce;">async </span><span style="color:#b48ead;">fn </span><span style="color:#8fa1b3;">ping</span><span style="color:#c0c5ce;">() {
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">let mut</span><span style="color:#c0c5ce;"> hub = Hub::new().await.</span><span style="color:#96b5b4;">unwrap</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">let mut</span><span style="color:#c0c5ce;"> a = hub.</span><span style="color:#96b5b4;">namespace</span><span style="color:#c0c5ce;">().await.</span><span style="color:#96b5b4;">unwrap</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">    </span><span style="color:#b48ead;">let mut</span><span style="color:#c0c5ce;"> b = hub.</span><span style="color:#96b5b4;">namespace</span><span style="color:#c0c5ce;">().await.</span><span style="color:#96b5b4;">unwrap</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">    </span><span style="color:#65737e;">//Connect two namespaces
</span><span style="color:#c0c5ce;">    a.</span><span style="color:#96b5b4;">connect</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">eth0</span><span style="color:#c0c5ce;">&quot;, &quot;</span><span style="color:#a3be8c;">eth0</span><span style="color:#c0c5ce;">&quot;, &amp;</span><span style="color:#b48ead;">mut</span><span style="color:#c0c5ce;"> b).await.</span><span style="color:#96b5b4;">unwrap</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">    </span><span style="color:#65737e;">//Assign IP addresses
</span><span style="color:#c0c5ce;">    a.</span><span style="color:#96b5b4;">sh</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">ip addr add 192.168.0.1/24 dev eth0</span><span style="color:#c0c5ce;">&quot;).await.</span><span style="color:#96b5b4;">unwrap</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">    a.</span><span style="color:#96b5b4;">sh</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">ip link set eth0 up</span><span style="color:#c0c5ce;">&quot;).await.</span><span style="color:#96b5b4;">unwrap</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">    b.</span><span style="color:#96b5b4;">sh</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">ip addr add 192.168.0.2/24 dev eth0</span><span style="color:#c0c5ce;">&quot;).await.</span><span style="color:#96b5b4;">unwrap</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">    b.</span><span style="color:#96b5b4;">sh</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">ip link set eth0 up</span><span style="color:#c0c5ce;">&quot;).await.</span><span style="color:#96b5b4;">unwrap</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">    </span><span style="color:#65737e;">//Ping test
</span><span style="color:#c0c5ce;">    a.</span><span style="color:#96b5b4;">sh</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">ping -c 1 192.168.0.2</span><span style="color:#c0c5ce;">&quot;).await.</span><span style="color:#96b5b4;">unwrap</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">}
</span><span style="color:#c0c5ce;">
</span></code></pre>
<p>You can check out the code behind it at <a href="https://gitlab.com/akash_rawal/nwlab2/-/tree/master/src/worker?ref_type=heads">https://gitlab.com/akash_rawal/nwlab2/-/tree/master/src/worker?ref_type=heads</a>.</p>
<p>If you browse the rest of the repository, you can also find tests and implementation of the basic neighbor discovery algorithm that I was talking about.</p>
<h1>Conclusion</h1>
<p>My first attempt to write tests comprised of using separate code blocks to initialize each network namespace, and then make them callable from <code>main</code>. That grew ugly very quickly. I mean just look at it.</p>
<pre style="background-color:#2b303b;"><code class="language-rust"><span style="color:#b48ead;">pub fn </span><span style="color:#8fa1b3;">routine</span><span style="color:#c0c5ce;">() -&gt; Routine {
</span><span style="color:#c0c5ce;">    Routine::from(|| async </span><span style="color:#b48ead;">move </span><span style="color:#c0c5ce;">{
</span><span style="color:#c0c5ce;">        cmd::run(&amp;</span><span style="color:#b48ead;">mut </span><span style="color:#c0c5ce;">cmd::new_ns(&quot;</span><span style="color:#a3be8c;">.a</span><span style="color:#c0c5ce;">&quot;)).await.</span><span style="color:#96b5b4;">unwrap</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">    }).</span><span style="color:#96b5b4;">push</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">.a</span><span style="color:#c0c5ce;">&quot;, Routine::from(|| async </span><span style="color:#b48ead;">move </span><span style="color:#c0c5ce;">{
</span><span style="color:#c0c5ce;">        cmd::init_netns().await.</span><span style="color:#96b5b4;">unwrap</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        </span><span style="color:#65737e;">//Spawn a child in new network namespace and send it the other end
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let mut</span><span style="color:#c0c5ce;"> child = cmd::fork_ns(&quot;</span><span style="color:#a3be8c;">.c</span><span style="color:#c0c5ce;">&quot;).</span><span style="color:#96b5b4;">spawn</span><span style="color:#c0c5ce;">().</span><span style="color:#96b5b4;">unwrap</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        </span><span style="color:#65737e;">//Connect to it
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">let</span><span style="color:#c0c5ce;"> pid = child.</span><span style="color:#96b5b4;">id</span><span style="color:#c0c5ce;">().</span><span style="color:#96b5b4;">unwrap</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">        cmd::new_veth(&quot;</span><span style="color:#a3be8c;">eth0</span><span style="color:#c0c5ce;">&quot;, &quot;</span><span style="color:#a3be8c;">eth0</span><span style="color:#c0c5ce;">&quot;, pid).await.</span><span style="color:#96b5b4;">unwrap</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        </span><span style="color:#65737e;">//Assign IP address
</span><span style="color:#c0c5ce;">        cmd::bash(&quot;</span><span style="color:#a3be8c;">ip addr add 172.16.0.1/16 dev eth0</span><span style="color:#c0c5ce;">&quot;).await.</span><span style="color:#96b5b4;">unwrap</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">        cmd::bash(&quot;</span><span style="color:#a3be8c;">ip link set eth0 up</span><span style="color:#c0c5ce;">&quot;).await.</span><span style="color:#96b5b4;">unwrap</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        </span><span style="color:#65737e;">//Run echo client
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">crate</span><span style="color:#c0c5ce;">::svc::echo_client(&quot;</span><span style="color:#a3be8c;">172.16.0.2:7</span><span style="color:#c0c5ce;">&quot;).await.</span><span style="color:#96b5b4;">unwrap</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        </span><span style="color:#65737e;">//Wait for child
</span><span style="color:#c0c5ce;">        child.</span><span style="color:#96b5b4;">wait</span><span style="color:#c0c5ce;">().await.</span><span style="color:#96b5b4;">unwrap</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        log::info!(&quot;</span><span style="color:#a3be8c;">Client ended</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">    }).</span><span style="color:#96b5b4;">push</span><span style="color:#c0c5ce;">(&quot;</span><span style="color:#a3be8c;">.c</span><span style="color:#c0c5ce;">&quot;, Routine::from(|| async {
</span><span style="color:#c0c5ce;">        cmd::init_netns().await.</span><span style="color:#96b5b4;">unwrap</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">        
</span><span style="color:#c0c5ce;">        </span><span style="color:#65737e;">//Wait for the network link
</span><span style="color:#c0c5ce;">        cmd::wait_for_link(&quot;</span><span style="color:#a3be8c;">eth0</span><span style="color:#c0c5ce;">&quot;).await.</span><span style="color:#96b5b4;">unwrap</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">        log::info!(&quot;</span><span style="color:#a3be8c;">From child:</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">        cmd::bash(&quot;</span><span style="color:#a3be8c;">ip link show</span><span style="color:#c0c5ce;">&quot;).await.</span><span style="color:#96b5b4;">unwrap</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        </span><span style="color:#65737e;">//Assign IP address
</span><span style="color:#c0c5ce;">        cmd::bash(&quot;</span><span style="color:#a3be8c;">ip addr add 172.16.0.2/16 dev eth0</span><span style="color:#c0c5ce;">&quot;).await.</span><span style="color:#96b5b4;">unwrap</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">        cmd::bash(&quot;</span><span style="color:#a3be8c;">ip link set eth0 up</span><span style="color:#c0c5ce;">&quot;).await.</span><span style="color:#96b5b4;">unwrap</span><span style="color:#c0c5ce;">();
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        </span><span style="color:#65737e;">//Run echo server
</span><span style="color:#c0c5ce;">        </span><span style="color:#b48ead;">crate</span><span style="color:#c0c5ce;">::svc::echo_server_1(&quot;</span><span style="color:#a3be8c;">172.16.0.2:7</span><span style="color:#c0c5ce;">&quot;).await;
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">        log::info!(&quot;</span><span style="color:#a3be8c;">Server ended</span><span style="color:#c0c5ce;">&quot;);
</span><span style="color:#c0c5ce;">    })))
</span><span style="color:#c0c5ce;">}
</span></code></pre>
<p>It is a very similar test case, but you see that <code>cmd::wait_for_link(&quot;eth0&quot;)</code> in there? That is a busy wait that repeatedly checks for whether the given network interface exists. I am quite happy with how easily I can set up test scenarios. The neighbor discovery itself is not the star of this show; it takes ~5 seconds to converge and needs a lot more work.</p>
]]></description><author>akash_rawal</author><pubDate>Sun, 10 Mar 2024 14:23:11 +0000</pubDate></item><item><title>Testcontainers&apos; reaper problem</title><link>https://01def.io/blog/view/6</link><description><![CDATA[<p>How to run external dependencies in containers to support your unit tests and clean them up reliably, without Docker socket access or additional privileges?</p>
<h1>How it all started</h1>
<p>I was once building a service that checks a MariaDB cluster for common problems
and do some basic repair work automatically. So I needed a MariaDB instance to test my project.
For that, I used Testcontainers. Testcontainers is a library to run
external dependencies in ephemeral containers, so no need to setup
environment variables or managing test servers.
<a href="https://testcontainers.com/">Go check out their homepage.</a></p>
<p>I set up a quick test to check out how it works... and we have a problem.</p>
<pre style="background-color:#2b303b;"><code><span style="color:#c0c5ce;">2024/08/18 21:26:46 🐳 Creating container for image testcontainers/ryuk:0.7.0
</span><span style="color:#c0c5ce;">2024/08/18 21:26:46 ✅ Container created: 912f450a9bc3
</span><span style="color:#c0c5ce;">2024/08/18 21:26:46 🐳 Starting container: 912f450a9bc3
</span><span style="color:#c0c5ce;">2024/08/18 21:26:46 ✅ Container started: 912f450a9bc3
</span><span style="color:#c0c5ce;">2024/08/18 21:26:46 ⏳ Waiting for container id 912f450a9bc3 image: testcontainers/ryuk:0
</span><span style="color:#c0c5ce;">.7.0. Waiting for: &amp;{Port:8080/tcp timeout:&lt;nil&gt; PollInterval:100ms}
</span><span style="color:#c0c5ce;">2024/08/18 21:26:46 failed accessing container logs: Error response from daemon: can not 
</span><span style="color:#c0c5ce;">get logs from container which is dead or marked for removal
</span><span style="color:#c0c5ce;">--- FAIL: TestMain (0.41s)
</span></code></pre>
<p>Ended up wasting the rest of the day troubleshooting the issue, recognizing
that the issue is with <code>testcontainers/ryuk</code> container not being able to
do its work, trying some workaround, and then grudgingly disabling the
user namespace feature that I had enabled in Docker daemon.</p>
<h1>Testcontainers' implementation detail: its reaper</h1>
<p>When you start containers within your tests, it is desirable to reliably
terminate them.</p>
<p>Many programming languages offer deterministic destructors. (Or similar
features like Go's <code>defer</code> statement, or Rust's <code>Drop</code> trait)
Some test runners offer functionality to run user-defined cleanup function
at the end of each test, or at the end of all tests.</p>
<p>None of these are reliable. If the test crashes or terminates abnormally,
these containers will not be cleaned up.</p>
<p>The solution made by Testcontainers is
<a href="https://hub.docker.com/r/testcontainers/ryuk">docker.io/testcontainers/ryuk</a>.
From the dockerhub description, using it involves only 6 simple steps.</p>
<pre style="background-color:#2b303b;"><code><span style="color:#c0c5ce;">1. Start it:
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;"> $ docker run -v /var/run/docker.sock:/var/run/docker.sock -e RYUK_PORT=8080 -p 8080:8080 docker.io/testcontainers/ryuk
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">2. Connect via TCP:
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;"> $ nc localhost 8080
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">3. Send some filters:
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;"> label=testing=true&amp;health=unhealthy
</span><span style="color:#c0c5ce;"> ACK
</span><span style="color:#c0c5ce;"> label=something
</span><span style="color:#c0c5ce;"> ACK
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">4. Close the connection
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">5. Send more filters with &quot;one-off&quot; style:
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;"> printf &quot;label=something_else&quot; | nc localhost 8080
</span><span style="color:#c0c5ce;">
</span><span style="color:#c0c5ce;">6. See containers/networks/volumes deleted after 10s:
</span></code></pre>
<h1>Its weakness</h1>
<p>It requires access to the docker socket.</p>
<p>Access to the docker socket usually implies root access on the system the
docker daemon runs on.</p>
<p>This also means that <code>ryuk</code> cannot function if it is running in a user namespace
or a similarly unprivileged situation.</p>
<p>I am not a fan of tests requiring root access. If anything, I want software
development work to be as contained as I can make it. If a test goes haywire,
I don't want to waste my next few hours of my life restoring my PC from backup.
It is not even a ridiculous concern, I once encountered a buggy test case
that performed <code>rm -rf $HOME</code> because of hardcoded Windows paths which
didn't work on linux.</p>
<p>Podman is a very attractive option to docker. It requires no root access,
needs no daemons, and only setup it requires is setting up <code>/etc/subuid</code>
and <code>/etc/subgid</code>, which likely your operating system already does.</p>
<p>But testcontainers requires a docker socket, and at the time of writing,
its podman support is experimental.</p>
<p>How do we reliably cleanup dependencies running in containers reliably?</p>
<h1>Just replace the entrypoint</h1>
<p>For each dependency container, we can replace its entrypoint to include a
cleanup functionality.</p>
<p>This is what it needs to do:</p>
<ol>
<li>Fork off the prior entrypoint.</li>
<li>Open a TCP socket and wait for a connection</li>
<li>Wait for the connection to close.</li>
<li>When the connection closes, simply quit.</li>
<li>As the new entrypoint that quit is the PID 1 of the container,
the container terminates.</li>
</ol>
<p>The following was the first iteration of such an entrypoint.</p>
<pre style="background-color:#2b303b;"><code class="language-bash"><span style="color:#65737e;">#!/bin/sh 
</span><span style="color:#c0c5ce;">
</span><span style="color:#65737e;">#Run the base container&#39;s entrypoint 
</span><span style="color:#c0c5ce;">&quot;$</span><span style="color:#bf616a;">@</span><span style="color:#c0c5ce;">&quot; &amp; 
</span><span style="color:#c0c5ce;">
</span><span style="color:#65737e;">#Exit after the keep-alive connection is closed 
</span><span style="color:#96b5b4;">exec</span><span style="color:#c0c5ce;"> socat TCP-LISTEN:4,accept-timeout=15 EXEC:cat
</span></code></pre>
<p>This is what the test fixture does:</p>
<ol>
<li>Start the container with replaced entrypoint.</li>
<li>Connect to TCP port 4 and keep the connection open.</li>
<li>At the end of test suite, close the TCP connection. Or the connection
is closed by the operating system if the test crashes.</li>
</ol>
<p>I have a more robust version of the entrypoint at
<a href="https://gitlab.com/akash_rawal/selfterm/-/tree/master/test_entrypoint">https://gitlab.com/akash_rawal/selfterm/-/tree/master/test_entrypoint</a>.
In the gitlab project you can also find prebuilt images for MariaDB and
Postgres container images.</p>
<h1>Conclusion</h1>
<p>Testcontainers is an awesome project and I like the idea, but I think
the implementation for cleaning up containers is too complicated and
a bit flawed. Its reliance on the docker daemon is an ongoing problem.
But with a dash of trickery this can be turned into a false statement.
I hope we get more tooling for testing which is compatible with
user namespaces or Podman, or similarly require less privileges.</p>
<p>What else, test with safety, folks!</p>
]]></description><author>akash_rawal</author><pubDate>Sun, 18 Aug 2024 17:02:05 +0000</pubDate></item></channel></rss>