<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Ollie's Blog]]></title><description><![CDATA[Ollie's Blog]]></description><link>https://blog.olliekennedy.co.uk</link><generator>RSS for Node</generator><lastBuildDate>Wed, 29 Apr 2026 12:39:22 GMT</lastBuildDate><atom:link href="https://blog.olliekennedy.co.uk/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[git-bisect: how to find a needle in a haystack]]></title><description><![CDATA[Imagine you've noticed a bug in your project. It may not be something that has failed any test, but it is a bug nonetheless. You've just noticed it, but how long has it been there? How many changes might have been committed since the bug was introduc...]]></description><link>https://blog.olliekennedy.co.uk/git-bisect-how-to-find-a-needle-in-a-haystack</link><guid isPermaLink="true">https://blog.olliekennedy.co.uk/git-bisect-how-to-find-a-needle-in-a-haystack</guid><category><![CDATA[Git]]></category><category><![CDATA[version control]]></category><category><![CDATA[bisect]]></category><category><![CDATA[pitest]]></category><dc:creator><![CDATA[Ollie Kennedy]]></dc:creator><pubDate>Fri, 28 Jul 2023 12:53:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/842ofHC6MaI/upload/bf21712c5d6bc9288c86d25f234f887e.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Imagine you've noticed a bug in your project. It may not be something that has failed any test, but it is a bug nonetheless. You've just noticed it, but how long has it been there? How many changes might have been committed since the bug was introduced? Debugging this is going to be a nightmare!</p>
<p>In walks <code>git bisect</code></p>
<p>It's a wicked tool for exactly the above situation. It performs a binary search for the bad commit and can very efficiently get through hundreds of commits. It can be done manually or automatically.</p>
<p>The example I used here was a problem with how we'd configured the mutation testing tool <a target="_blank" href="https://pitest.org">Pitest</a>. It resulted in an error being printed <code>Passed files have no features!</code>, but Pitest would eventually pass. Weird right?</p>
<h3 id="heading-how-to-use-it-automatically">How to use it automatically</h3>
<ol>
<li><p>Define your success command. This must be a command that exits <code>0</code> for success and <code>not 0</code> for failure. In this case, mine was:</p>
<pre><code class="lang-bash"> sh -c <span class="hljs-string">'./gradlew pitest 2&gt;&amp;1 | grep -q "Passed files have no features" &amp;&amp; exit 1 || exit 0'</span>
</code></pre>
<ul>
<li><p>Translating to English:</p>
<ul>
<li><p><code>sh -c '&lt;command&gt;'</code> - execute this shell command and exit to the current shell</p>
</li>
<li><p><code>./gradlew pitest 2&gt;&amp;1</code> - run pitest and print both stderr and stdout to the terminal</p>
</li>
<li><p><code>| grep -q "&lt;search_string&gt;"</code> - pipe the output of pitest to grep and search for the string without printing to the terminal</p>
</li>
<li><p><code>&amp;&amp; exit 1 || exit 0</code> - exit 1 if grep finds a match else exit 0</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p>Find your first good commit by choosing one WAY in the past and checking your success command exits 0 - note its revision number. Do not try to find a recent one - that's what the tool is for!</p>
</li>
<li><p>Start the bisect process with:</p>
<pre><code class="lang-bash"> git bisect start
</code></pre>
</li>
<li><p>Mark the current commit (master) as bad with:</p>
<pre><code class="lang-bash"> git bisect bad
</code></pre>
</li>
<li><p>Tell bisect which commit is known to be good with:</p>
<pre><code class="lang-bash"> git bisect good &lt;revision_number&gt;
</code></pre>
</li>
<li><p>Tell bisect to get to work with:</p>
<pre><code class="lang-bash"> git bisect run &lt;success_command&gt;
</code></pre>
<p> e.g.</p>
<pre><code class="lang-bash"> git bisect run sh -c <span class="hljs-string">'./gradlew pitest 2&gt;&amp;1 | grep -q "Passed files have no features" &amp;&amp; exit 1 || exit 0'</span>
</code></pre>
</li>
</ol>
<p>If you have defined your success command correctly, it will checkout different revisions, test them with your command, and then know whether to look for the bad commit before or after that point. It will do this until it finds the baddie!</p>
<h3 id="heading-real-world-demo">Real World Demo</h3>
<p>Here is the entire process I used to find the "Passed files have no features" issue:</p>
<pre><code class="lang-plaintext">$ git bisect start
Already on 'master'
Your branch is up to date with 'origin/master'.
status: waiting for both good and bad commits

$ git bisect bad
status: waiting for good commit(s), bad commit known

$ git bisect good b39b356
Bisecting: 72 revisions left to test after this (roughly 6 steps)
[c343bab24e630bee0912abe923b5e09049204603] fix(US4764533): Fix running the service locally

$ git bisect run sh -c './gradlew pitest 2&gt;&amp;1 | grep -q "Passed files have no features" &amp;&amp; exit 1 || exit 0'
running  'sh' '-c' './gradlew pitest 2&gt;&amp;1 | grep -q "Passed files have no features" &amp;&amp; exit 1 || exit 0'
Bisecting: 36 revisions left to test after this (roughly 5 steps)
[4bc5b1444577128ce008a382f4c777548d7d10e0] fix(US3976007): Autogenerate WireMock stubs
running  'sh' '-c' './gradlew pitest 2&gt;&amp;1 | grep -q "Passed files have no features" &amp;&amp; exit 1 || exit 0'
Bisecting: 17 revisions left to test after this (roughly 4 steps)
[04b239b50334f63fabdcfdeb6cddae657d848e97] Update plugin com.diffplug.spotless to v6.12.1
running  'sh' '-c' './gradlew pitest 2&gt;&amp;1 | grep -q "Passed files have no features" &amp;&amp; exit 1 || exit 0'
Bisecting: 8 revisions left to test after this (roughly 3 steps)
[1904b509d8b753bcf4613dc35eb62409732be1bd] US4407184: Revert changes to cucumber reports for unit tests due to PiTest
running  'sh' '-c' './gradlew pitest 2&gt;&amp;1 | grep -q "Passed files have no features" &amp;&amp; exit 1 || exit 0'
Bisecting: 4 revisions left to test after this (roughly 2 steps)
[c894a4d0bd994d33fdeac771f1c742f61bdc4fc8] Merged after successful ./gradlew clean build
running  'sh' '-c' './gradlew pitest 2&gt;&amp;1 | grep -q "Passed files have no features" &amp;&amp; exit 1 || exit 0'
Bisecting: 2 revisions left to test after this (roughly 1 step)
[0cba5d12cd8300a26b8f518138a6063d62fedf7d] US4407184: Fix LSD database url for staging
running  'sh' '-c' './gradlew pitest 2&gt;&amp;1 | grep -q "Passed files have no features" &amp;&amp; exit 1 || exit 0'
Bisecting: 0 revisions left to test after this (roughly 1 step)
[2308a73e20f3683f4cc65491a76d880a6d80ba99] US4407184: Publish cucumber reports for unit tests
running  'sh' '-c' './gradlew pitest 2&gt;&amp;1 | grep -q "Passed files have no features" &amp;&amp; exit 1 || exit 0'
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[49c2b1be8d838eb4bd75793e84ece69f6c0b0cb6] US4407184: Update internal dependencies
running  'sh' '-c' './gradlew pitest 2&gt;&amp;1 | grep -q "Passed files have no features" &amp;&amp; exit 1 || exit 0'
2308a73e20f3683f4cc65491a76d880a6d80ba99 is the first bad commit
commit 2308a73e20f3683f4cc65491a76d880a6d80ba99
Author: John Doe &lt;john@doe.com&gt;
Date:   Mon Jan 23 12:22:30 2023 +0000

    US4407184: Publish cucumber reports for unit tests

 Jenkinsfile                                              | 2 +-
 application/src/test/resources/junit-platform.properties | 3 +++
 2 files changed, 4 insertions(+), 1 deletion(-)
bisect found first bad commit
</code></pre>
<p>As you can see, git-bisect checks out a commit in the middle of the commit-range, tests it against the success command and then knows to narrow its commit-range to before or after that commit. It does this over and over until it knows exactly which commit the bug started.</p>
<p>Due to the way binary searches work, searching for a bad commit within 256 commits will only perform double the revision tests as within 8 commits. So don't spend time searching for a recent good commit, the tool will do this much quicker than you will!</p>
<p>Note you can also do this manually if your definition of success is too complicated, by marking the first good and bad commits, then manually testing the revision that bisect gives you, then going back and telling it <code>git bisect good</code> or <code>git bisect bad</code>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>git-bisect is an incredibly powerful tool that, while you won't use all the time, is a useful one to keep at the back of the toolbox for when you need it.</p>
]]></content:encoded></item><item><title><![CDATA[Enhancing Object Matching in Kotlin Component Tests: From Simple Assertions to Hamcrest Matchers]]></title><description><![CDATA[I was writing a component test in Kotlin for a scenario where we publish an event to a Kafka topic whenever we receive a request from an upstream system. It offered a good opportunity to learn how to write a custom matcher for comparing two different...]]></description><link>https://blog.olliekennedy.co.uk/enhancing-object-matching-in-kotlin-component-tests-from-simple-assertions-to-hamcrest-matchers</link><guid isPermaLink="true">https://blog.olliekennedy.co.uk/enhancing-object-matching-in-kotlin-component-tests-from-simple-assertions-to-hamcrest-matchers</guid><category><![CDATA[Kotlin]]></category><category><![CDATA[component-test]]></category><category><![CDATA[TDD (Test-driven development)]]></category><category><![CDATA[hamcrest]]></category><dc:creator><![CDATA[Ollie Kennedy]]></dc:creator><pubDate>Fri, 02 Jun 2023 16:16:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/LfUXV5gMDfs/upload/47d82bc3ff1f7881506e62e9a144be90.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I was writing a component test in Kotlin for a scenario where we publish an event to a Kafka topic whenever we receive a request from an upstream system. It offered a good opportunity to learn how to write a custom matcher for comparing two different types of objects!</p>
<p>For demonstration purposes let's imagine I'm a middleman between a customer wanting a cake and a baker. The cucumber scenario looked a bit like this:</p>
<pre><code class="lang-plaintext">Scenario: Tell the baker that the customer wants some cake
    Given the customer has a cake request
    When the request is submitted
    Then a cake requested event is published to the baker
</code></pre>
<p>At some point in my test, I wanted to make sure that certain fields in the outgoing cake requested event matched the ones received in the incoming cake request. Because I was intercepting the outgoing event using a Kafka listener, it was in JsonNode format.</p>
<p>So imagine this incoming <code>CakeRequest</code>:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">data</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CakeRequest</span></span>(
    <span class="hljs-keyword">val</span> requestId: String,
    <span class="hljs-keyword">val</span> flavour: String,
    <span class="hljs-keyword">val</span> colour: String,
    <span class="hljs-keyword">val</span> wantsSprinkles: <span class="hljs-built_in">Boolean</span>,
)
</code></pre>
<p>And an outgoing <code>JsonNode</code> that looks like this:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"flavour"</span>:<span class="hljs-string">"chocolate"</span>,
    <span class="hljs-attr">"colour"</span>:<span class="hljs-string">"red"</span>,
    <span class="hljs-attr">"wantsSprinkles"</span>:<span class="hljs-string">"true"</span>
}
</code></pre>
<p>What is the best way to make sure they have the same values (minus the <code>requestId</code>)?</p>
<h3 id="heading-simple-private-method">Simple private method</h3>
<p>I started with this simple assertion:</p>
<pre><code class="lang-kotlin">assertTrue { hasSameEssentialValues(cakeRequest, resultingEvent) }

<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">hasSameEssentialValues</span><span class="hljs-params">(cakeRequest: <span class="hljs-type">CakeRequest</span>, resultingEvent: <span class="hljs-type">JsonNode</span>)</span></span> =
        cakeRequest.flavour == resultingEvent[<span class="hljs-string">"flavour"</span>].asText()
                &amp;&amp; cakeRequest.colour == resultingEvent[<span class="hljs-string">"colour"</span>].asText()
                &amp;&amp; cakeRequest.wantsSprinkles == resultingEvent[<span class="hljs-string">"wantsSprinkles"</span>].asBoolean()
</code></pre>
<p>While this worked fine, there were a few downsides:</p>
<ul>
<li><p>The assertion didn't read fantastically</p>
</li>
<li><p>It polluted my test file with ugly private methods</p>
</li>
<li><p>Most importantly, the truth about how to match fields between a CakeRequest and a JsonNode was being mixed in with everything else.</p>
</li>
</ul>
<h3 id="heading-in-steps-a-hamcrest-matcher">In steps a Hamcrest matcher!</h3>
<p>By the power of Hamcrest, I created an assertion that looks like this:</p>
<pre><code class="lang-kotlin">assertThat(resultingEvent, hasSameEssentialValuesAs(cakeRequest))
</code></pre>
<p>And a matcher in a separate file that looked like this:</p>
<pre><code class="lang-kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">hasSameEssentialValuesAs</span><span class="hljs-params">(cakeRequest: <span class="hljs-type">CakeRequest</span>)</span></span> = CakeRequestMatcher(cakeRequest)

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CakeRequestMatcher</span></span>(<span class="hljs-keyword">private</span> <span class="hljs-keyword">val</span> cakeRequest: CakeRequest) : BaseMatcher&lt;JsonNode&gt;() {

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">describeTo</span><span class="hljs-params">(description: <span class="hljs-type">Description</span>)</span></span> {
        description.appendText(<span class="hljs-string">"The important fields must match"</span>)
    }

    <span class="hljs-keyword">override</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">matches</span><span class="hljs-params">(resultingEvent: <span class="hljs-type">Any</span>?)</span></span>: <span class="hljs-built_in">Boolean</span> {
        <span class="hljs-keyword">if</span> (resultingEvent !<span class="hljs-keyword">is</span> JsonNode)
            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>

        <span class="hljs-keyword">return</span> cakeRequest.flavour == resultingEvent[<span class="hljs-string">"flavour"</span>].asText()
                &amp;&amp; cakeRequest.colour == resultingEvent[<span class="hljs-string">"colour"</span>].asText()
                &amp;&amp; cakeRequest.wantsSprinkles == resultingEvent[<span class="hljs-string">"wantsSprinkles"</span>].asBoolean()
    }
}
</code></pre>
<p>This makes the assertion read brilliantly and follows the single responsibility principle much better.</p>
<p>But there are some downsides:</p>
<ul>
<li><p>The mandatory implementation <code>describeTo()</code> - not useful in my situation</p>
</li>
<li><p><code>matches()</code> takes the <code>Any?</code> type as its input, requiring manual type-checking</p>
</li>
<li><p>The whole thing is complex - it will be harder to read and maintain</p>
</li>
</ul>
<h3 id="heading-final-takeaways-reflections-and-next-steps">Final Takeaways: Reflections and Next Steps</h3>
<p>While this was a good exercise where I learned a lot about matchers (and ended up writing this article), in my specific use case I ultimately decided to check the fields in a simple package-level function in its own file. This kept the responsibility in the correct place and made the whole thing less complex.</p>
<p>It means when someone else (or me in 6 months) reads this test, they'll take less time to understand it, and less time to modify it. Which surely is the ultimate goal when writing code, right?</p>
<h3 id="heading-update">Update:</h3>
<p>5 minutes after I published this article I sent the link to my esteemed <a target="_blank" href="https://dzone.com/users/4762175/lukaszgryzbon.html">work colleague</a> who I worked with on this problem, and he replied straight away with a solution that blows mine out of the water. It solves the problem of readability and allows you to abstract the comparison logic away from both the test and the CakeRequest object:</p>
<pre><code class="lang-kotlin">assertTrue { cakeRequest.hasSameEssentialValuesAs(resultingEvent) }
</code></pre>
<p>Kotlin has extension functions, duh!!</p>
]]></content:encoded></item><item><title><![CDATA[Technical Debt and Me]]></title><description><![CDATA[Technical Debt has got to be one of the most misused terms in software development and I myself had it wrong for a very long time. This is the evolution I've been through, and where I currently sit with it.
Where it started
Before we get to the real ...]]></description><link>https://blog.olliekennedy.co.uk/technical-debt-and-me</link><guid isPermaLink="true">https://blog.olliekennedy.co.uk/technical-debt-and-me</guid><category><![CDATA[agile]]></category><category><![CDATA[technical-debt]]></category><category><![CDATA[legacy]]></category><category><![CDATA[lean]]></category><category><![CDATA[ways-of-working]]></category><dc:creator><![CDATA[Ollie Kennedy]]></dc:creator><pubDate>Fri, 12 May 2023 16:31:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/RJQE64NmC_o/upload/189d293de4d173a93313f82a1692a6f8.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Technical Debt has got to be one of the most misused terms in software development and I myself had it wrong for a very long time. This is the evolution I've been through, and where I currently sit with it.</p>
<h3 id="heading-where-it-started">Where it started</h3>
<p>Before we get to the real origins, let me take you through how it originated in my head. I've subscribed to a few definitions over time!</p>
<p>I first heard the term technical debt when I was a junior developer, interacting with teams who were maintaining lots of legacy code. Due to this, there was a growing initiative from middle managers for their teams to <em>reduce their technical debt.</em> This brings us to...</p>
<h3 id="heading-definition-1-legacy-technology">Definition 1: Legacy Technology</h3>
<p>Technical debt is synonymous with legacy tech. If you have lots of legacy tech that is expensive to maintain and hard to change, then you must reduce that technical debt.</p>
<p>This definition was easy to learn since it was a pure definition of a tangible thing rather than a concept. And it was all I ever knew until I was told otherwise!</p>
<h4 id="heading-pros-of-this-definition">Pros of this definition</h4>
<ul>
<li><em>Reducing technical debt</em> sounds much more appealing than <em>dealing with legacy technology</em> so it might speed up decisions to deal with problems</li>
</ul>
<h4 id="heading-cons-of-this-definition">Cons of this definition</h4>
<ul>
<li>It is reactive. It offers no insight into how problems are caused in the first place</li>
</ul>
<h4 id="heading-what-triggered-me-to-rethink-my-definition">What triggered me to rethink my definition</h4>
<ul>
<li>I got hot on the fact that everything we write as developers is someday going to have to be modified or deleted and in assuming that technical debt is a thing that we must get rid of, I concluded very confidently that...</li>
</ul>
<h3 id="heading-definition-2-everything-is-technical-debt">Definition 2: Everything is Technical Debt</h3>
<p>Every line of code will one day die, and it will cost effort to kill it. So in assuming everything we write is technical debt, we can be mindful of the cost of creating and deploying.</p>
<p>While this is quite reductive, let's explore it:</p>
<h4 id="heading-pros-of-this-definition-1">Pros of this definition</h4>
<ul>
<li><p>If we adopted this mindset, then we might be more sparing in the code we write. Think of the "overproduction" or "extra processing" <a target="_blank" href="https://theleanway.net/The-8-Wastes-of-Lean">lean wastes</a>.</p>
</li>
<li><p>It will reduce your ego! No matter how unbelievably beautiful the code you write today is, one day it will be as decrepit as something written in the context of the 1990s might be today. As you write your code, you might now consider how easy it would be to delete it.</p>
</li>
</ul>
<h4 id="heading-cons-of-this-definition-1">Cons of this definition</h4>
<ul>
<li><p>It might cause people to be nihilistic. What's the point in trying to do a good job if it's eventually going to be deleted anyway?</p>
</li>
<li><p>The opportunity cost of not leaning into the metaphor</p>
</li>
</ul>
<h4 id="heading-what-triggered-me-to-rethink-my-definition-1">What triggered me to rethink my definition</h4>
<ul>
<li>I realised it had absolutely nothing to do with the idea of debt. Why was this term first coined? Someone must have had a metaphor in mind</li>
</ul>
<h3 id="heading-definition-3-getting-back-to-the-metaphor">Definition 3: Getting back to the metaphor</h3>
<h4 id="heading-financial-debt">Financial Debt</h4>
<p>What is financial debt? It is borrowing money now, with the requirement to pay it back later, often with interest.</p>
<p>It can be bad; unconsciously racking up credit card debt so you can get a fresh wardrobe ready for the summer, with no plan to pay it back might get you into a lot of trouble.</p>
<p>But taking out a business loan to buy some equipment so you can start your new manufacturing business might pay itself back multiple times over.</p>
<p>The difference between these two cases? Only one of them uses the debt to invest in something that will make more money in the future, so you can pay back the debt, and end up with more money than if you hadn't taken out the debt in the first place.</p>
<p>The same applies to the metaphor of technical debt.</p>
<h4 id="heading-technical-debt">Technical Debt</h4>
<p>If you unconsciously go faster than your constraints should allow you when developing a new application, then something will have to give. The code might be substandard and hard to maintain. And if you've done this unconsciously, then you might never repay the technical debt by bringing the code up to standard.</p>
<p>On the other hand, if you take shortcuts in your coding to deliver something in a short time frame, you may get buy-in from the business resulting in them investing more in your idea.</p>
<p>BUT! You must be conscious that one day, that debt will have to be repaid, or else interest will accrue. Every day that goes by, more workarounds will have to be made, and something that should take 1 hour to do, ends up taking 3, all due to the substandard code that was written in the first place and never dealt with.</p>
<p>With every decision to move faster than your constraints allow, be it financial or ability, you must make a plan to pay that back as well as the interest that goes along with it.</p>
<p>Definition 3 is the one I've settled on organically and feel comfortable with in my head. It has the benefit of being quite close to the real idea too.</p>
<h3 id="heading-the-actual-origins-of-technical-debt">The actual origins of Technical Debt</h3>
<p>Ward Cunningham coined the term around 1992 but his most in-depth exploration of the concept is in a video he released almost 20 years after. Rather than for me to try and fail in paraphrasing his ideas, I'll point you to where you can <a target="_blank" href="https://www.youtube.com/watch?v=pqeJFYwnkjE">watch</a> or <a target="_blank" href="http://wiki.c2.com/?WardExplainsDebtMetaphor">read</a> about his ideas directly.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>In my experience, <em>Definition 1: Legacy Technology</em> is by far the most widespread definition people subscribe to, but it misses out on the main point.</p>
<p><em>Definition 2: Everything is Technical Debt</em> might have some ideas to extrude, but it shouldn't be associated with the term technical debt. It waters down the real idea unnecessarily.</p>
<p>I'm settled on Definition 3 for now, and given it was the original one, something revolutionary would have to come along to dethrone it.</p>
<p>Reading the actual idea behind technical debt by watching Ward's video straight away would have got me to my answer quicker.</p>
<p><strong>But you know what?</strong> I learned a lot from my discovery along the way and each of the definitions contributed to my thoughts on developing software.</p>
]]></content:encoded></item><item><title><![CDATA[hello world]]></title><description><![CDATA[This is some content
This is some bold content
this_block = 'some code'


That was a horizontal line ^
This is a list:

list item 1

list item 2



This is a blockquote

This is a link to Google
This is an image:

This is a mermaid diagram:
flowchart...]]></description><link>https://blog.olliekennedy.co.uk/hello-world</link><guid isPermaLink="true">https://blog.olliekennedy.co.uk/hello-world</guid><category><![CDATA[Hello World]]></category><dc:creator><![CDATA[Ollie Kennedy]]></dc:creator><pubDate>Thu, 11 May 2023 10:23:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/nXt5HtLmlgE/upload/a5bc7f8d5c0bd6b8bba121750117d12e.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This is some content</p>
<p><strong>This is some bold content</strong></p>
<pre><code class="lang-python">this_block = <span class="hljs-string">'some code'</span>
</code></pre>
<hr />
<p>That was a horizontal line ^</p>
<p>This is a list:</p>
<ul>
<li><p>list item 1</p>
</li>
<li><p>list item 2</p>
</li>
</ul>
<blockquote>
<p>This is a blockquote</p>
</blockquote>
<p>This is a link to <a target="_blank" href="https://www.google.com">Google</a></p>
<p>This is an image:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1611902473383/CDyAuTy75.png?auto=compress" alt="Hashnode Brand Resources - Logos and other media files" /></p>
<p>This is a mermaid diagram:</p>
<pre><code class="lang-plaintext">flowchart LR
    START -- Journey --&gt; FINISH
</code></pre>
<p>...well that didn't work, did it?</p>
<p>This is an emoji :joy:</p>
<p>ah man...</p>
<p>reached the limitation!</p>
]]></content:encoded></item></channel></rss>