<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://www.cornerinthemiddle.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.cornerinthemiddle.com/" rel="alternate" type="text/html" /><updated>2026-01-08T15:06:11+01:00</updated><id>https://www.cornerinthemiddle.com/feed.xml</id><title type="html">The corner in the middle</title><subtitle>A blog about technology, gaming and lifehacks. Posts about backend, architecture, agile and coaching but also about gaming and general stuff happening around my life.</subtitle><author><name>Juan Ara</name></author><entry><title type="html">Strategies to manage Technical Debt - Part 1</title><link href="https://www.cornerinthemiddle.com/software%20engineering/simple-strategies-to-manage-techdebt-part-1/" rel="alternate" type="text/html" title="Strategies to manage Technical Debt - Part 1" /><published>2020-08-31T00:00:00+02:00</published><updated>2020-08-31T00:00:00+02:00</updated><id>https://www.cornerinthemiddle.com/software%20engineering/simple-strategies-to-manage-techdebt-part-1</id><content type="html" xml:base="https://www.cornerinthemiddle.com/software%20engineering/simple-strategies-to-manage-techdebt-part-1/"><![CDATA[<blockquote>
  <p>Three article series about how to manage technical debt in a simple and fun way.</p>
</blockquote>

<div class="feature__wrapper">

  
    <div class="feature__item">
      <div class="archive__item">
        
          <div class="archive__item-teaser">
            <img src="/assets/images/posts/simple-strategies-to-manage-techdebt/alice-pasqual-Olki5QpHxts-unsplash-hdpi.jpg" alt="" />
            
          </div>
        

        <div class="archive__item-body">
          

          
            <div class="archive__item-excerpt">
              <p>Why teams struggle with technical debt. Technical debt definition adapted to modern times. Introduction to a simple framework to classify and see technical debt impact in a team.</p>

            </div>
          

          
            <p><a href="/software%20engineering/simple-strategies-to-manage-techdebt-part-1/#introduction" class="btn btn--primary">Keep reading!</a></p>
          
        </div>
      </div>
    </div>
  
    <div class="feature__item">
      <div class="archive__item">
        
          <div class="archive__item-teaser">
            <img src="/assets/images/posts/simple-strategies-to-manage-techdebt/kat-yukawa-K0E6E0a0R3A-unsplash-hdpi.jpg" alt="" />
            
          </div>
        

        <div class="archive__item-body">
          

          
            <div class="archive__item-excerpt">
              <p>We will evolve the simple framework by using industry standard definitions, like the sofware architecture <em>illities</em> and explaining by example, grouping them on predefined categories.</p>

            </div>
          

          
            <p><a href="" class="btn btn--disabled">Part 2 (coming soon!)</a></p>
          
        </div>
      </div>
    </div>
  
    <div class="feature__item">
      <div class="archive__item">
        
          <div class="archive__item-teaser">
            <img src="/assets/images/posts/simple-strategies-to-manage-techdebt/sydney-rae-geM5lzDj4Iw-unsplash-hdpi.jpg" alt="" />
            
          </div>
        

        <div class="archive__item-body">
          

          
            <div class="archive__item-excerpt">
              <p>Standardize metrics, language and communication to allow company wide alignment and high level planning. Examples of how this framework can be used across different teams.</p>

            </div>
          

          
            <p><a href="" class="btn btn--disabled">Part 3 (coming soon!)</a></p>
          
        </div>
      </div>
    </div>
  

</div>

<h1 id="introduction">Introduction</h1>

<p>Recently, I met with a team to share ideas about how they (engineering and product owners alike) could raise 
awareness of the importance of their tech-debt, but also how to take ownership of it.
For some reason, this team had heavy churn, and the engineers could not find a way to remove or tackle their pain
points during their sprints.</p>

<p>With other team members, we had already identified some items in various repositories and services, but we had 
not been able to onboard everyone on the importance of them. For example, there were some <em>code smells</em>, some 
services had tons of <em>legacy code</em> (with few tests or no documentation) and other stuff that usually is considered
technical debt.</p>

<p>The most senior or historic engineers on the team did not consider lack of documentation or tests as technical debt
(after all they were the technical experts and owners of the repositories), but the product owners were seeing some 
slowness onboarding new engineers on the team.
On the other hand, the product owners had not noticed some technical deficiencies (or if they were they thought there 
was almost no impact) as technical debt, but the engineers were not happy with some old tools, libraries and patterns
used.</p>

<p>Although when reading the above examples it might be clear a relationship exists between the pairs, it needed external 
feedback to make the whole team aware of it.
It was not a problem of communication between engineers and product owners, it was just that they were not able to align
themselves nor see the impacts of each other’s worries or the relation with parts of their code.</p>

<p>So we gathered together again, this time hands-on to try to find the best approach to the problems they were facing:</p>

<ul>
  <li>How to identify if something was really tech-debt or just a false positive</li>
  <li>How to measure the impact of each tech-debt item</li>
  <li>How to reach consensus within the team (including product owner) that a given piece of software needed some love 
:heart_eyes: :hearts: <sup>:hearts: <sup>:hearts:</sup></sup></li>
</ul>

<p>Basically we were going to <strong>observe</strong>, <strong>measure impact</strong> and <strong>raise awareness</strong>.</p>

<h1 id="the-first-iteration">The first iteration</h1>

<p>We tried to start small, with specific examples and use cases, in order to then iterate over them trying to find
generalizations.</p>

<p>So we started with a very basic question:</p>

<blockquote>
  <p>What things do we have that we believe are tech-debt right now? (aka what are our current pain points?)</p>
</blockquote>

<p>We identified some of them (and left some for the next iterations):</p>

<figure class=""><img src="/assets/images/posts/simple-strategies-to-manage-techdebt/tech-debt-1.jpg" alt="Image with boxes about identified tech debt items with the titles 
        Monoliths, Obsolete Stuff, Not Scaling, Unstable, Monitoring, 
        Not using platform tools, Testing, Documentation, and Code Smells" /><figcaption>
      This were the pain points detected with the team on the first iteration

    </figcaption></figure>

<p>We intentionally left some stuff aside as at this point we already began to discuss that some pain points were
consequence of others; for example, <em>slow onboarding</em> was a direct consequence of <em>not having good enough documentation</em>.</p>

<p>We decided to follow that line of thought (consequences and impacts) later on, instead focusing at that moment on extracting
common information from our items by grouping or classifying them.</p>

<p>In order to classify something, you need a criterion. Our criteria were not clear then but became clearer when 
we saw the impacts, much later on. At this stage we just tried to group it, and we saw some 
<em><strong>“architectural deficiencies”</strong></em>, some <em><strong>“things related to production”</strong></em> and finally other <em>stuff</em> related to 
<em><strong>“how fast and happy the team was”</strong></em> while working on certain repositories.</p>

<p>We used the initials <code class="language-plaintext highlighter-rouge">A</code>, <code class="language-plaintext highlighter-rouge">O</code> and <code class="language-plaintext highlighter-rouge">S</code> to tag our items:</p>

<figure class=""><img src="/assets/images/posts/simple-strategies-to-manage-techdebt/tech-debt-2.jpg" alt="Previous image updated with categories (Architecture, Operations and Speed)" /><figcaption>
      Grouping the different pain points into categories

    </figcaption></figure>

<p>We had reached a stage where we could simply add some tags to given tech-debt items that gave some inspiration to 
categorize them, but most importantly, we were able to extract the impact.
Effectively, how we were categorizing an item told us about its impact.</p>

<figure class=""><img src="/assets/images/posts/simple-strategies-to-manage-techdebt/tech-debt-3.jpg" alt="Previous image deriving the impact from each category:
        Company Strategy, Bugs/Production problems and Time to Market" /><figcaption>
      We could see the impact from each category easily and this drove us towards the matrix in the next part of this series.

    </figcaption></figure>

<h1 id="retrospective">Retrospective</h1>

<p>This very first iteration was so powerful! We were really happy, as this gave us great insights but also offered 
opportunities to establish some technical debt goals within the team. We could focus on specific <em>themes</em> (using the 
impact) according to the upcoming user stories and thus was a great way to align everyone, from product to engineering, 
into dealing with technical debt.</p>

<p>We did a small retrospective, and we all agreed that while it was powerful to raise awareness, we could benefit even more if 
we dug deeper on the framework we were creating.</p>

<h1 id="some-examples">Some examples</h1>

<p>Let’s say there are new features involving integrating with third parties that will consume our fancy new API, that was 
just built in a hurry for a PoC, in the next quarter. We had identified some tech debt related to speed on that part of 
the codebase, like not enough tests, documentation,  or some code that needs refactor because it was just a PoC.
We know that this will impact the time to market, so we can anticipate and just focus as a technical goal for the sprints 
to “improve the time to market for new API features” while working on the user stories. Focusing only on that goal (for 
example omitting any big architectural refactor) we could tackle our codebase at the same time we were delivering new 
value.</p>

<p>Maybe our Company is expecting a peak in our # of requests because of some sales goals (go sales, :moneybag: go!), 
and we need to focus on improving our operations stuff, like monitoring, scaling or making the jobs resilient and 
parallelizable. This way, we could introduce technical parts to the user stories (for example, the big architectural
refactor that we omitted before) that were aligned with the company goals or certain user stories.</p>

<h1 id="technical-debt">Technical Debt</h1>

<h2 id="what-is-technical-debt">What is Technical Debt</h2>

<p>How do we know that something is technical debt? Because we are afraid to work on it. Or because it hinders product 
development by not being able to achieve the speed we want. Or maybe because it breaks too much. Or it is not aligned with
the company strategies…</p>

<p>Sounds familiar? Basically it is what the second image above illustrates: Team Speed Stuff, Operations Stuff and Architecture 
Stuff. In other words:</p>

<ul>
  <li>Techdebt doesn’t allow product owners to deliver features at the speed they want: <strong>It slows development teams</strong></li>
  <li>Techdebt are also pieces of software that we cannot maintain any longer (obsolete technology, tools or lack of
experts): <strong>it is not aligned with company strategies</strong></li>
  <li>Techdebt is also present when some products break <em>too</em> often or even don’t work as expected: <strong>products already 
running in the company that have a high operational cost</strong></li>
</ul>

<p>Basically, it is a slow poison, draining resources from the company or increasing the cost to deliver new value.</p>

<p><strong>What is the formal definition?</strong></p>

<p><em>From <a href="https://en.wikipedia.org/wiki/Technical_debt" title="Wikipedia article about Technical Debt">Wikipedia</a>:</em></p>
<blockquote>
  <p>The formal  definition states that technical debt reflects the implied cost of additional rework caused by choosing an
easy (limited) solution now instead of using a better approach that would take longer. Technical debt can be compared 
to monetary debt. If technical debt is not repaid, it can accumulate ‘interest’, making it harder to implement changes 
later on. Unaddressed technical debt increases software entropy. Technical debt is not necessarily a bad thing, and 
sometimes (e.g., as a proof-of-concept) technical debt is required to move projects forward.</p>
</blockquote>

<p><em>From: <a href="https://www.martinfowler.com/bliki/TechnicalDebt.html" title="Martin Fowler definition of technical debt">Martin Fowler</a>:</em></p>
<blockquote>
  <p>Software systems are prone to the build up of cruft - deficiencies in internal quality that make it harder than it 
would ideally be to modify and extend the system further. Technical Debt is a metaphor, coined by Ward Cunningham, 
that frames how to think about dealing with this cruft, thinking of it like a financial debt. The extra effort that 
it takes to add new features is the interest paid on the debt.</p>
</blockquote>

<p>At this point we could use one of the wide known definitions of tech debt, but we decided to go with our own.</p>

<p><strong>Why would we change it?</strong></p>

<p>Simply, because software engineering has evolved <strong>a lot</strong> since the first of those definitions were introduced back 
in 1992 by <a href="https://github.com/WardCunningham/remodeling" title="Github repository from Ward Cunningham about remodeling">Ward Cunningham</a>.</p>

<p>Now we use infrastructure as a code, microservices architecture, CI/CD, agile philosophy, and evolved technology stacks, 
some of which were born just a couple of years ago and give a competitive advantage over old ones.</p>

<p>We have seen stacks rise and fall since then, and using those dead stacks was not considered technical debt at the time,
they were <em>state of the art</em>. 
We consider that anything in engineering that induces a cost for the company to grow, deliver or build new features is 
technical debt, be it new developments or things already in production.
From architecture to code but also, culture and training.</p>

<p>The above definitions define technical debt when the software is created, not when the environment changes.</p>

<p>This was our amendment to the classic technical debt definitions:</p>
<blockquote>
  <p>We do not consider technical debt only stuff made consciously, but also things made some time ago which needs
reconsideration based on <strong>current</strong> software engineering standards, tools and technology, 
but also on <strong>updated company strategy and needs</strong>.</p>
</blockquote>

<p>This updated definition helped us to revisit our projects periodically to make sure they are aligned with company
strategy but also helps us to deprecate things we are going to evolve when their stacks become obsolete.</p>

<h2 id="how-technical-debt-is-born">How technical debt is born</h2>

<p>Technical debt is not only born in the code. This is one of the biggest mistakes that startups make when thinking about 
it. This also happens in established companies. According to our definition above it can be born in different ways.</p>

<p>Let’s explore some examples:</p>

<ul>
  <li>A stakeholder meeting establishing a plan for the company to duplicate clients or traffic will spawn
a bunch of technical debt items to optimize performance, allow scalability or other evolutions that are needed to achieve 
that goal. Especially if scalability was not important for some services previously, and thus their architecture was not 
focused on that.</li>
  <li>Our company is not yet ready to use fully automated CI/CD pipelines, and we have some manual QA testing, and some
teams keep detecting in their retrospectives that there is a problem there, needing additional environments or just
evolving the pipelines and training the team to help them automate those tests.</li>
  <li>A new technology spawning in our market that gives an advantage to competitors and forces the company to switch to
it, evolving architectures or tools, or just because there is a lack of engineers working in our previous technology
stack.</li>
  <li>A merge is happening with another company, and we need to improve our ability to integrate with them, by improving our
documentation, APIs and standardization.</li>
  <li>A library we were using in the past suddenly gets a new major version and drops support and compatibility with the 
previous one. While dead (maintenance mode) projects could keep using the library, keeping it on live/active projects 
could trigger a problem in the near future.</li>
  <li>A quick bug fix that did not pass a proper code review gets forgotten in the codebase. Also, this bug fix has 
unforeseen consequences</li>
  <li>…</li>
</ul>

<p>I stumbled recently upon this blog entry <a href="https://medium.com/@elizarov/intentional-qualities-7e6a57bb87fc">Intentional Qualities</a> 
from <a href="https://www.linkedin.com/in/relizarov">Roman Elizarov</a> and would like to highlight a couple of paragraphs there:</p>

<blockquote>
  <p>See, the fact is that you cannot accidentally write a software that has a specific quality. 
You cannot accidentally write fast code. Your code cannot be secure by an accident.
Accidentally easy to learn? Never heard of it.</p>
</blockquote>

<p>[…]</p>

<blockquote>
  <p>Qualities are often lost by an accident, but are never accidentally acquired. 
It is similar to the second law of thermodynamics. In order to maintain quality you have to be very conscious, 
intentional about it. You have to constantly fight against losing it.</p>
</blockquote>

<p>I love that statement! You can start writing code that follows a target (secure, fast, easy to learn or maintain) but 
several iterations later, it only needs one small commit not focused on that <em>-ility</em>, and the code is not longer 
secure, fast or maintainable.</p>

<p>As a recap, these are five examples of technical debt appears:</p>

<ul>
  <li>At any time (dev, test, prod), detecting something that was not detected before (third party library bug, side effect…)</li>
  <li>During development, as a conscious decision or consensus in order to achieve a given goal faster (PoC or other)</li>
  <li>During development, by mistake (i.e. losing one or more of our software qualities)</li>
  <li>Because new needs / company goals turns something into not-ideal / not good enough / not aligned</li>
  <li>Because new tools / technology / patterns emerge that solves problems in a different and more efficient way</li>
</ul>

<p>We never consider our bugs technical debt items (unless they become a <a href="https://www.wired.com/story/its-not-a-bug-its-a-feature/">feature</a>),
but we might consider third party bugs, problems or missing features and other external things that we might need to invest
time into as technical debt if left unattended.</p>

<h1 id="the-second-iteration">The second iteration</h1>

<p>As a recap of our exercise above after the not-so-brief retrospective, we had already identified some tags (A, O, S) and
the impact of some technical debt items.
We had a starting point, and the feeling of being in the right path.</p>

<p>We started to look for existing standards about the tags we were using on our tech-debt items, but we were also aiming
to keep this simple enough to make it broadly used in the company.</p>

<p>I already mentioned early the Software Architecture <em>-ilities</em> (check some videos about them from <a href="http://nealford.com/">Neal Ford</a> 
or <a href="http://www.wmrichards.com/">Mark Richards</a>), so we started straight with their recommendations.
Something was going to be tech-debt and not just false positives if it was lacking any of the important <em>-ilities</em> 
for the company or team on a given point in time (important <em>-ilities</em> change overtime as company goals and tools 
evolve).</p>

<p>There are some frameworks and acronyms that try to classify software according to their <em>-ilities</em>, like <a href="https://en.wikipedia.org/wiki/ACID">ACID</a> 
for transactions, <a href="https://en.wikipedia.org/wiki/Reliability,_availability_and_serviceability">RAS</a> for operations, 
<a href="https://en.wikipedia.org/wiki/FURPS">FURPS</a> for requirements…
We were inspired by them, and we decided to group the list of <em>-ilities</em> that were important for the company into one 
or more of the categories we already had identified.</p>

<p>In the next entry of this series, we will
introduce an evolved version of this framework by further defining and grouping those <em>illities</em>, 
while in the last entry of this series we will
dive deeper by using it to help not only the teams but also the company to make better decisions, to react to 
changes and to reach its goals.</p>

<blockquote>
  <p>This post could not have been possible without some <a href="https://www.linkedin.com/in/m%C3%B3nica-bartolom%C3%A9-9433686/">awesome coworkers</a></p>
</blockquote>]]></content><author><name>Juan Ara</name></author><category term="Software Engineering" /><category term="software architecture" /><category term="enterprise architecture" /><category term="technical debt" /><summary type="html"><![CDATA[Sometimes teams struggle to identify which technical debt items are worth investing into, or how to align them to company goals. We recently had some internal sessions to discuss this topic, and we came up with a simple strategy to classify and measure technical debt across teams, while having a chance to align it with our product needs.]]></summary></entry><entry><title type="html">Feedback and Sandwiches</title><link href="https://www.cornerinthemiddle.com/personal/coaching/feedback-and-sandwiches/" rel="alternate" type="text/html" title="Feedback and Sandwiches" /><published>2020-08-03T00:00:00+02:00</published><updated>2020-08-03T00:00:00+02:00</updated><id>https://www.cornerinthemiddle.com/personal/coaching/feedback-and-sandwiches</id><content type="html" xml:base="https://www.cornerinthemiddle.com/personal/coaching/feedback-and-sandwiches/"><![CDATA[<p>Yesterday somebody shared  <a href="https://basecamp.com/guides/how-we-communicate">Basecamp’s “How we communicate”</a> guide in 
our corporate workplace. 
At the time of this post, it is <a href="https://twitter.com/jasonfried/status/1213198779517681670">half a year old</a> and I have 
seen it a couple of times during quarantine. However, this time around something caught my attention, something that 
made me think about writing this entry.</p>

<blockquote>
  <p>Great news delivered on the heels of bad news makes both bits worse. 
The bad news feels like it’s being buried, the good news feels like it’s being injected to change the mood. 
Be honest with each by giving them adequate space.</p>
</blockquote>
<figcaption>Article 25 at Basecamp's "How we communicate"</figcaption>

<p>Why did this statement trigger something in my head?</p>

<p>I am Spanish. Mediterranean, but I’ve worked in Netherlands, France, Bulgaria, San Francisco… I’ve caught
glimpses of other cultures. One of the soft skills I was able to improve while working in the US was giving and 
receiving feedback.</p>

<p>As part of a team-building exercise to build trust, we made confessions to coworkers. One of the techniques shared by 
the trainer was the <em>sandwich</em>. I’ve learned that it is a classic in American culture, and
once aware of it I was able to identify <em>the sandwich :hamburger:</em> early on during conversations:</p>

<blockquote>
  <p>Hey Juan! Can I say something personal to you? I’ve always appreciated you and your family!
You need to lose some weight because your health is important, and it’s a wonderful daughter you need to take care of!</p>
</blockquote>

<p>That happened (not literally but almost!) during lunch with an American friend that visited a year ago (I’m 
improving my health, I promise :muscle: ). 
I saw the pattern immediately and let my friend know that I caught him in the <em>sandwich</em> (we always laugh at our cultural 
differences) and he confirmed that it is in his DNA.</p>

<p>What’s funny about it is, as Basecamp code says, everything is wrong with that technique. The first slice of bread 
seems artificial, and the <em>wonderful daughter</em> seems to be there just to change the mood, burying the important thing, 
that is to lose weight or get healthier.</p>

<h3 id="feedback-canvas">Feedback Canvas</h3>

<blockquote>
  <p>When giving feedback, you should be constructive, specific and always assume good intent. 
That goes in both directions: when you receive feedback, assume that the person who is giving you the feedback is 
going beyond his comfort zone and is pro-actively trying to help you to grow further.</p>

  <p>When you give feedback to someone else, please keep in mind that you don’t know the full picture: assume that your 
colleague was acting and performing to the best of his skills and knowledge and with good intent.</p>

  <p>All feedback must be specific. That is, you should name specific examples.</p>

  <p>Sometimes feedback is just one way conversation, and the receiving part will just receive and think about it.
Other times it spawns a two-way conversation, creating actionable take-aways. 
In this latter case, it is ok to recommend steps that your colleague can act upon. 
Otherwise, let them come himself with actions and be ready to give him your opinion on them if applicable.</p>
</blockquote>

<p>This is my canvas for feedback conversations, and it is the one I use in my 1:1s when nobody provides a different
framework.</p>

<p>After the intro, I have some questions to help drive the conversation:</p>

<ul>
  <li>What are some things that you see me doing well and should continue doing?</li>
  <li>Is there anything I am not doing yet that you would want to see me start doing?</li>
  <li>Is there anything I am doing that you would want to see me stop doing?</li>
</ul>

<p>The goal of the first question is to see if my efforts are aligned with what people perceive, while the second one is 
to see what might be in my blind spot but is obvious to others.</p>

<p>Usually, the most difficult question is the last one, but it’s not sandwiched between the other ones. It’s left at the
end on purpose; it is what pushes people outside their comfort zone.</p>

<p>In my experience, people still use sandwiches to avoid bringing negative stuff into a conversation, and that is ok. I am 
often told that I go too fast or that I intimidate people because I come across as self-confident, which makes them feel
insecure or worried about their own skills or capabilities (impostor syndrome).</p>

<p>What is funny is that those things don’t appear on the third question, but instead during the first or second one.
I have found it very challenging to learn how to ask the right questions when I want to understand how I can be a better
person or coworker.</p>

<p>Of course the first two questions are important. Yet in the end, those negative points that we are often afraid of 
drawing attention to are the points that can spawn the spark of a change.</p>]]></content><author><name>Juan Ara</name></author><category term="Personal" /><category term="Coaching" /><category term="feedback" /><category term="culture" /><category term="mindset" /><summary type="html"><![CDATA[I saw something at [Basecamp's "How we communicate"](https://basecamp.com/guides/how-we-communicate) guide that made me think about the classic sandwich when delivering bad news/feedback, so I decided to share a feedback canvas that is working for me when trying to help teams to adopt a constructive feedback culture.]]></summary></entry><entry><title type="html">Learning to write addons for Elder Scrolls Online</title><link href="https://www.cornerinthemiddle.com/games/software%20engineering/writting-my-first-lua-addon-for-elder-scrolls-online/" rel="alternate" type="text/html" title="Learning to write addons for Elder Scrolls Online" /><published>2020-07-28T00:00:00+02:00</published><updated>2020-07-28T00:00:00+02:00</updated><id>https://www.cornerinthemiddle.com/games/software%20engineering/writting-my-first-lua-addon-for-elder-scrolls-online</id><content type="html" xml:base="https://www.cornerinthemiddle.com/games/software%20engineering/writting-my-first-lua-addon-for-elder-scrolls-online/"><![CDATA[<p>I love vide games, and I love programming. Often, I tend to go down the rabbit hole modding or <em>upgrading</em> a game to
better suit my needs. Since that usually involves doing two things that I love together, it proves to be the best way to
learn new stuff for me.</p>

<p>There’s a big list of <em>learning experiences</em> I had while I was modding games:</p>
<ul>
  <li>I learned map-reduce by using <a href="https://en.wikipedia.org/wiki/VGA_Planets">Vgaplanets</a> <a href="http://home.snafu.de/spock/">Mess</a>
host util, whose developer and website is still online (wow!);</li>
  <li>memory hacking with “Game Wizard 32” (for DOS), as well as memory optimization with some hacking;</li>
  <li>macroing on <a href="https://darkageofcamelot.com/">Dark Age of Camelot</a>;</li>
  <li>some 3d design stuff (reflections, sprites, animations…) by creating maps in counter-strike 0.5;</li>
  <li>UI modding on <a href="https://www.everquest2.com/">Everquest 2</a>, which I continued on <a href="https://en.wikipedia.org/wiki/Vanguard:_Saga_of_Heroes">Vanguard: Saga of Heroes</a>, 
where my <a href="http://www.vginterface.com/downloads/info37-TarodsShortCutPanel.html">Tarod’s Shortcut Panel</a>
was well-known in the community;</li>
  <li>I did the same with <a href="http://www.lotro.com/en">Lord of the Rings Online</a>;</li>
  <li><a href="https://github.com/TarodBOFH/ThrustmasterTARGETScripting">customized</a> my HOTAS for <a href="https://www.elitedangerous.com/">Elite: Dangerous</a>;</li>
  <li>learned <a href="https://unity.com/">Unity</a> modding <a href="http://www.motorsportmanager.com/">Motor Sport Manager</a>;</li>
  <li>… and many more.</li>
</ul>

<p>Now it was time for <a href="https://www.elderscrollsonline.com/en-us/home">Elder Scrolls Online</a>.</p>

<p>Elder Scrolls Online (<em>ESO</em>) uses <a href="https://www.lua.org/">Lua</a> as scripting language for UI interaction.
UI components are described in XML, but it’s the scripting part that I was interested on.
I always like <code class="language-plaintext highlighter-rouge">cmd-line</code> so I wanted to write a script that reacted to some basic <em>slash commands</em>.</p>

<h1 id="writing-the-addon">Writing the Addon</h1>

<p>This was going to be really fun as I had never written any Lua code before.</p>

<p>Let’s write our user story:</p>
<blockquote>
  <p>I, as a &lt;gamer&gt; want to &lt;know who is online on each of my guilds when I log in&gt; to the game so that &lt;I can 
interact with them&gt; without opening additional tools, menus or windows.</p>
</blockquote>

<p>As a reference site I used <a href="https://wiki.esoui.com/Main_Page">ESOUI Wiki</a> to get me started. Contrary to the 
recommendations I was not connecting to the TEST game server (mainly because I was short on disk space) so kids don’t
try this at home.</p>

<p>I created my addon folder, my addon metadata file and registered my addon with the game UI engine.</p>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">TarodGuildInfo</span> <span class="o">=</span> <span class="p">{}</span>
<span class="n">TarodGuildInfo</span><span class="p">.</span><span class="n">name</span> <span class="o">=</span> <span class="s2">"Tarod's Guild Info"</span>
<span class="n">TarodGuildInfo</span><span class="p">.</span><span class="n">id</span> <span class="o">=</span> <span class="s2">"TarodGuildInfo"</span>

<span class="k">function</span> <span class="nf">TarodGuildInfo</span><span class="p">:</span><span class="n">Initialize</span><span class="p">()</span>
<span class="k">end</span>

<span class="k">function</span> <span class="nc">TarodGuildInfo</span><span class="p">.</span><span class="nf">OnAddOnLoaded</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="n">addontName</span><span class="p">)</span> 
    <span class="k">if</span> <span class="n">addontName</span> <span class="o">==</span> <span class="n">TarodGuildInfo</span><span class="p">.</span><span class="n">id</span> <span class="k">then</span>
        <span class="n">TarodGuildInfo</span><span class="p">:</span><span class="n">Initialize</span><span class="p">()</span>
    <span class="k">end</span>
<span class="k">end</span>

<span class="n">EVENT_MANAGER</span><span class="p">:</span><span class="n">RegisterForEvent</span><span class="p">(</span><span class="n">TarodGuildInfo</span><span class="p">.</span><span class="n">id</span><span class="p">,</span> <span class="n">EVENT_ADD_ON_LOADED</span><span class="p">,</span> <span class="n">TarodGuildInfo</span><span class="p">.</span><span class="n">OnAddOnLoaded</span><span class="p">)</span>
</code></pre></div></div>

<p>I opened the game and enabled my addon… and the game didn’t crash which was good.</p>

<p>In order to reload my addon without closing the game I could have some commands, but I wanted to be transparent with the
engine, so each time I changed my code I just did a <code class="language-plaintext highlighter-rouge">/redloadui</code> on the game to see the effect.</p>

<h2 id="using-the-api-to-read-data-from-the-game">Using the API to read data from the game</h2>

<p>The game API for developers can be found at the developer forums or via <a href="https://wiki.esoui.com/API">ESO UI Wiki</a>. For
me, the best resource was <a href="https://github.com/esoui/esoui/">ESOUI’s GitHub repo</a>. I opened <a href="https://github.com/esoui/esoui/blob/master/ESOUIDocumentation.txt">API Doc</a>
and started my investigation.</p>

<p>I started to search for “guild” but there were too many results. I went for “getguild”, and I got some
interesting hits, like <a href="https://github.com/esoui/esoui/blob/6eaa93d7d29b90d6af3cb9fcc2afdc1cba67e565/ESOUIDocumentation.txt#L8781">* GetGuildInfo(<em>integer</em> <em>guildId</em>)</a>
and the surrounding functions (<code class="language-plaintext highlighter-rouge">isPlayerInGuild</code>, <code class="language-plaintext highlighter-rouge">GetNumGuilds()</code>, <code class="language-plaintext highlighter-rouge">GetGuildId(*luaindex* _guildIndex_)</code>, etc.).</p>

<p>The API is organized so that one can ask about the number of guilds the current player (our <code class="language-plaintext highlighter-rouge">&lt;gamer&gt;</code> persona in the 
user story) belongs to, and then ask the game about what’s the internal guild id for a given guild index for the player.</p>

<p>Although this can leave some inconsistencies if between one call and the next one (some temporal coupling), specially 
if the player joins or leaves a guild between calls, this was not a problem for me as I want to do this only at game
start.</p>

<h2 id="sending-data-back-to-the-ui">Sending data back to the UI</h2>

<p>I started to wonder how to write messages on the game chat (as result of my api calls), and I noticed there’s a special
<code class="language-plaintext highlighter-rouge">d(*str message)</code> function to send messages to the <code class="language-plaintext highlighter-rouge">System</code> message channel.</p>

<p>I did not want my mod to do anything 
special like using chat channels or other fancy stuff at the moment so It was fine for me. 
However, system messages are sent only to the first tab unless using <a href="https://www.esoui.com/downloads/info93-pChat.html">pChat</a> 
or other chat mods.</p>

<p>After some hacking I got a first version working, listing the online members for my guilds, as well as the guild name:</p>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">function</span> <span class="nf">TarodGuildInfo</span><span class="p">:</span><span class="n">GuildInfo</span><span class="p">()</span> 
    <span class="kd">local</span> <span class="n">guildCount</span> <span class="o">=</span> <span class="n">GetNumGuilds</span><span class="p">()</span>
    <span class="k">for</span> <span class="n">idx</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="n">guildCount</span> <span class="k">do</span>
        <span class="kd">local</span> <span class="n">guildId</span> <span class="o">=</span> <span class="n">GetGuildId</span><span class="p">(</span><span class="n">idx</span><span class="p">)</span>
        <span class="n">d</span><span class="p">(</span><span class="n">GetGuildName</span><span class="p">(</span><span class="n">guildId</span><span class="p">))</span>
        <span class="kd">local</span> <span class="n">_</span><span class="p">,</span> <span class="n">onlineMemberCount</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="n">GetGuildInfo</span><span class="p">(</span><span class="n">guildId</span><span class="p">)</span>
    
        <span class="n">d</span><span class="p">(</span><span class="s2">"There are "</span> <span class="o">..</span> <span class="n">onlineMemberCount</span> <span class="o">..</span> <span class="s2">" members online"</span><span class="p">)</span>
        <span class="kd">local</span> <span class="n">guildMemberCount</span> <span class="o">=</span> <span class="n">GetNumGuildMembers</span><span class="p">(</span><span class="n">guildId</span><span class="p">)</span>
            
        <span class="k">for</span> <span class="n">idx</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">guildMemberCount</span> <span class="k">do</span>
            <span class="kd">local</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span> <span class="p">,</span> <span class="n">_</span><span class="p">,</span><span class="n">logoff</span> <span class="o">=</span> <span class="n">GetGuildMemberInfo</span><span class="p">(</span><span class="n">guildId</span><span class="p">,</span> <span class="n">idx</span><span class="p">)</span>
            <span class="kd">local</span> <span class="n">_</span><span class="p">,</span> <span class="n">charName</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="n">GetGuildMemberCharacterInfo</span><span class="p">(</span><span class="n">guildId</span><span class="p">,</span> <span class="n">idx</span><span class="p">)</span>
            
            <span class="k">if</span> <span class="n">logoff</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">then</span>
                <span class="n">d</span><span class="p">(</span><span class="n">charName</span><span class="p">)</span>
            <span class="k">end</span>
        <span class="k">end</span>
    <span class="k">end</span> 
<span class="k">end</span>
</code></pre></div></div>

<p>The code was ugly, but I was working. I found very challenging not being able to unit test my code (mainly because this
was my very first lua interaction) but at this time just manual testing was ok for me.</p>

<h2 id="formatting-messages">Formatting messages</h2>

<p>First thing I wanted was to use the full return values from the functions above (instead of <code class="language-plaintext highlighter-rouge">_</code>) to display more 
detailed information, like player name, zone, level or champion points, etc. I wanted to add colors and proper 
formatting, so I decided to start with the number of people online in a guild and using conditional formatting.</p>

<p>Most languages support conditional formatting, and in this case this was achieved not with the default lua stdlib but by
using Zenimax Online (the developer of ESO) string format function. 
There is an <a href="https://wiki.esoui.com/How_to_format_strings_with_zo_strformat">article about it</a> so I’ll jump right to the
first formatting. I took the chance to do some refactor to have easier to follow functions.</p>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span> <span class="nf">TarodGuildInfo</span><span class="p">:</span><span class="n">Initialize</span><span class="p">()</span>
    <span class="n">TarodGuildInfo</span><span class="p">.</span><span class="n">currentPlayer</span> <span class="o">=</span> <span class="n">GetUnitName</span><span class="p">(</span><span class="s2">"player"</span><span class="p">)</span>
    <span class="n">d</span><span class="p">(</span><span class="n">zo_strformat</span><span class="p">(</span><span class="s2">"Welcome Back |cB27BFF&lt;&lt;1&gt;&gt;|r!"</span><span class="p">,</span> <span class="n">TarodGuildInfo</span><span class="p">.</span><span class="n">currentPlayer</span><span class="p">))</span>
    <span class="n">TarodGuildInfo</span><span class="p">:</span><span class="n">GuildInfo</span><span class="p">()</span>
<span class="k">end</span>

<span class="k">function</span> <span class="nf">TarodGuildInfo</span><span class="p">:</span><span class="n">GuildInfo</span><span class="p">()</span> 
    <span class="kd">local</span> <span class="n">guildCount</span> <span class="o">=</span> <span class="n">GetNumGuilds</span><span class="p">()</span>
    <span class="k">for</span> <span class="n">idx</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="n">guildCount</span> <span class="k">do</span>
        <span class="n">TarodGuildInfo</span><span class="p">:</span><span class="n">PrintGuildInfo</span><span class="p">(</span><span class="n">idx</span><span class="p">)</span>
    <span class="k">end</span> 
<span class="k">end</span>

<span class="k">function</span> <span class="nf">TarodGuildInfo</span><span class="p">:</span><span class="n">PrintGuildInfo</span><span class="p">(</span><span class="n">idx</span><span class="p">)</span>
    <span class="kd">local</span> <span class="n">guildId</span> <span class="o">=</span> <span class="n">GetGuildId</span><span class="p">(</span><span class="n">idx</span><span class="p">)</span>
    <span class="n">d</span><span class="p">(</span><span class="n">zo_strformat</span><span class="p">(</span><span class="s2">"|cFFFFFF&lt;&lt;1&gt;&gt;|r: |cFFB5F4&lt;&lt;2&gt;&gt;|r"</span><span class="p">,</span> <span class="n">GetGuildName</span><span class="p">(</span><span class="n">guildId</span><span class="p">),</span> <span class="n">GetGuildMotD</span><span class="p">(</span><span class="n">guildId</span><span class="p">)))</span>
    <span class="kd">local</span> <span class="n">_</span><span class="p">,</span> <span class="n">onlineMemberCount</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="n">GetGuildInfo</span><span class="p">(</span><span class="n">guildId</span><span class="p">)</span>
    <span class="kd">local</span> <span class="n">additionalMembers</span> <span class="o">=</span> <span class="n">onlineMemberCount</span> <span class="o">-</span> <span class="mi">1</span>

    <span class="n">d</span><span class="p">(</span><span class="n">zo_strformat</span><span class="p">(</span><span class="s2">" |cC3F0C2&lt;&lt;1[You are the only one online :(/There is only another member online:/There are $d members online:]&gt;&gt;|r"</span><span class="p">,</span> <span class="n">additionalMembers</span><span class="p">))</span>

    <span class="n">TarodGuildInfo</span><span class="p">:</span><span class="n">PrintGuildMembers</span><span class="p">(</span><span class="n">guildId</span><span class="p">)</span>
<span class="k">end</span>

<span class="k">function</span> <span class="nf">TarodGuildInfo</span><span class="p">:</span><span class="n">PrintGuildMembers</span><span class="p">(</span><span class="n">guildId</span><span class="p">)</span>
<span class="c1">-- nothing to see here.</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Here there are some things to highlight:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">TarodGuildInfo.currentPlayer = GetUnitName("player")</code> is creating a global variable on my addon namespace.</li>
  <li><code class="language-plaintext highlighter-rouge">zo_strformat("Welcome Back |cB27BFF&lt;&lt;1&gt;&gt;|r!", TarodGuildInfo.currentPlayer)</code> displays some text and then the current
character’s name in purple color (or that’s what I think since I am colorblind :smiley:)</li>
  <li><code class="language-plaintext highlighter-rouge">"|cFFFFFF&lt;&lt;1&gt;&gt;|r: |cFFB5F4&lt;&lt;2&gt;&gt;|r", name, motd</code> was displaying the guild name in white and the Message of the Day
 in some shade of grey, on the same line</li>
  <li><code class="language-plaintext highlighter-rouge">|cC3F0C2&lt;&lt;1[ a/b/There are $d members online:]&gt;&gt;|r, additionalMembers</code>: Displays <code class="language-plaintext highlighter-rouge">a</code> when <code class="language-plaintext highlighter-rouge">additionalMembers</code> is 
zero, <code class="language-plaintext highlighter-rouge">b</code> when it’s 1 and <code class="language-plaintext highlighter-rouge">There are &lt;additionalMembers&gt; members online</code> when it’s greater than 1</li>
</ul>

<h3 id="some-bugs-on-the-api">Some bugs on the API…</h3>

<p>When using this from <code class="language-plaintext highlighter-rouge">/reloadui</code> everything was working fine, but at first login my own character was not online yet so
the <code class="language-plaintext highlighter-rouge">-1</code> there was not making much sense.</p>

<p>This was really weird because the documentation said that the addons are invoked <strong>after</strong> character goes online, and 
not <em>before</em>. As usual, undocumented stuff happens…</p>

<p>After some trial and error trying to use global variables and locks, I decided that it was easier to have a function to
just iterate over the members and then perform the filtering myself:</p>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="n">TarodGuildInfo</span> <span class="o">=</span> <span class="p">{}</span>

<span class="c1">-- ...</span>

<span class="k">function</span> <span class="nf">TarodGuildInfo</span><span class="p">:</span><span class="n">CountOtherGuildMembers</span><span class="p">(</span><span class="n">guildId</span><span class="p">)</span>
    <span class="kd">local</span> <span class="n">guildMemberCount</span> <span class="o">=</span> <span class="n">GetNumGuildMembers</span><span class="p">(</span><span class="n">guildId</span><span class="p">)</span>
    <span class="kd">local</span> <span class="n">onlineCount</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="k">for</span> <span class="n">idx</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">guildMemberCount</span> <span class="k">do</span>
        <span class="kd">local</span> <span class="n">_</span><span class="p">,</span><span class="n">_</span><span class="p">,</span><span class="n">_</span><span class="p">,</span><span class="n">_</span><span class="p">,</span><span class="n">logoff</span> <span class="o">=</span> <span class="n">GetGuildMemberInfo</span><span class="p">(</span><span class="n">guildId</span><span class="p">,</span> <span class="n">idx</span><span class="p">)</span>
        <span class="kd">local</span> <span class="n">_</span><span class="p">,</span> <span class="n">charName</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="n">GetGuildMemberCharacterInfo</span><span class="p">(</span><span class="n">guildId</span><span class="p">,</span> <span class="n">idx</span><span class="p">)</span>
        <span class="c1">-- Why GetUnitName returns formatted string while charName has the localization suffixes??</span>
        <span class="k">if</span> <span class="n">logoff</span> <span class="o">==</span> <span class="mi">0</span> <span class="ow">and</span> <span class="n">zo_strformat</span><span class="p">(</span><span class="s2">"&lt;&lt;1&gt;&gt;"</span><span class="p">,</span> <span class="n">charName</span><span class="p">)</span> <span class="o">~=</span> <span class="n">TarodGuildInfo</span><span class="p">.</span><span class="n">currentPlayer</span> <span class="k">then</span>
            <span class="n">onlineCount</span> <span class="o">=</span> <span class="n">onlineCount</span> <span class="o">+</span> <span class="mi">1</span>
        <span class="k">end</span>
    <span class="k">end</span>
    <span class="k">return</span> <span class="n">onlineCount</span>
<span class="k">end</span>

<span class="c1">-- ...</span>
</code></pre></div></div>

<p>One weird thing happened was with the <code class="language-plaintext highlighter-rouge">GetUnitName</code> and <code class="language-plaintext highlighter-rouge">charName</code> returned by the <code class="language-plaintext highlighter-rouge">GetGuildMemberCharacterInfo</code>.
One of them gave the current character, but the other one had the <code class="language-plaintext highlighter-rouge">strformat</code> suffixes for the gender, and it was not 
possible compare directly.</p>

<h2 id="almost-done">Almost done</h2>

<p>I could now glue all together and have almost the final version of the script:</p>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="n">TarodGuildInfo</span> <span class="o">=</span> <span class="p">{}</span>

<span class="n">TarodGuildInfo</span><span class="p">.</span><span class="n">name</span> <span class="o">=</span> <span class="s2">"Tarod's Guild Info"</span>
<span class="n">TarodGuildInfo</span><span class="p">.</span><span class="n">id</span> <span class="o">=</span> <span class="s2">"TarodGuildInfo"</span>
<span class="n">TarodGuildInfo</span><span class="p">.</span><span class="n">currentPlayer</span> <span class="o">=</span> <span class="kc">nil</span>
<span class="n">TarodGuildInfo</span><span class="p">.</span><span class="n">maxOnline</span> <span class="o">=</span> <span class="mi">15</span>

<span class="k">function</span> <span class="nf">TarodGuildInfo</span><span class="p">:</span><span class="n">Initialize</span><span class="p">()</span>
    <span class="n">TarodGuildInfo</span><span class="p">.</span><span class="n">currentPlayer</span> <span class="o">=</span> <span class="n">GetUnitName</span><span class="p">(</span><span class="s2">"player"</span><span class="p">)</span>
    <span class="n">d</span><span class="p">(</span><span class="n">zo_strformat</span><span class="p">(</span><span class="s2">"Welcome Back |cB27BFF&lt;&lt;1&gt;&gt;|r!"</span><span class="p">,</span> <span class="n">TarodGuildInfo</span><span class="p">.</span><span class="n">currentPlayer</span><span class="p">))</span>
    <span class="n">TarodGuildInfo</span><span class="p">:</span><span class="n">GuildInfo</span><span class="p">()</span>
<span class="k">end</span>

<span class="k">function</span> <span class="nf">TarodGuildInfo</span><span class="p">:</span><span class="n">GuildInfo</span><span class="p">()</span> 
    <span class="kd">local</span> <span class="n">guildCount</span> <span class="o">=</span> <span class="n">GetNumGuilds</span><span class="p">()</span>
    <span class="k">for</span> <span class="n">idx</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="n">guildCount</span> <span class="k">do</span>
        <span class="n">TarodGuildInfo</span><span class="p">:</span><span class="n">PrintGuildInfo</span><span class="p">(</span><span class="n">idx</span><span class="p">)</span>
    <span class="k">end</span> 
<span class="k">end</span>

<span class="k">function</span> <span class="nf">TarodGuildInfo</span><span class="p">:</span><span class="n">PrintGuildInfo</span><span class="p">(</span><span class="n">idx</span><span class="p">)</span>
    <span class="kd">local</span> <span class="n">guildId</span> <span class="o">=</span> <span class="n">GetGuildId</span><span class="p">(</span><span class="n">idx</span><span class="p">)</span>
    <span class="n">d</span><span class="p">(</span><span class="n">zo_strformat</span><span class="p">(</span><span class="s2">"|cFFFFFF&lt;&lt;1&gt;&gt;|r: |cFFB5F4&lt;&lt;2&gt;&gt;|r"</span><span class="p">,</span> <span class="n">GetGuildName</span><span class="p">(</span><span class="n">guildId</span><span class="p">),</span> <span class="n">GetGuildMotD</span><span class="p">(</span><span class="n">guildId</span><span class="p">)))</span>
    <span class="kd">local</span> <span class="n">onlineMemberCount</span> <span class="o">=</span> <span class="n">TarodGuildInfo</span><span class="p">:</span><span class="n">CountOtherGuildMembers</span><span class="p">(</span><span class="n">guildId</span><span class="p">)</span>
    <span class="kd">local</span> <span class="n">additionalMembers</span> <span class="o">=</span> <span class="n">onlineMemberCount</span>

    <span class="n">d</span><span class="p">(</span><span class="n">zo_strformat</span><span class="p">(</span><span class="s2">" |cC3F0C2&lt;&lt;1[You are the only one online :(/There is only another member online:/There are $d members online:]&gt;&gt;|r"</span><span class="p">,</span> <span class="n">additionalMembers</span><span class="p">))</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">onlineMemberCount</span> <span class="o">&lt;</span> <span class="n">TarodGuildInfo</span><span class="p">.</span><span class="n">maxOnline</span> <span class="p">)</span> <span class="k">then</span> 
        <span class="n">TarodGuildInfo</span><span class="p">:</span><span class="n">PrintGuildMembers</span><span class="p">(</span><span class="n">guildId</span><span class="p">)</span>
    <span class="k">end</span>
<span class="k">end</span>

<span class="k">function</span> <span class="nf">TarodGuildInfo</span><span class="p">:</span><span class="n">CountOtherGuildMembers</span><span class="p">(</span><span class="n">guildId</span><span class="p">)</span>
    <span class="kd">local</span> <span class="n">guildMemberCount</span> <span class="o">=</span> <span class="n">GetNumGuildMembers</span><span class="p">(</span><span class="n">guildId</span><span class="p">)</span>
    <span class="kd">local</span> <span class="n">onlineCount</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="k">for</span> <span class="n">idx</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">guildMemberCount</span> <span class="k">do</span>
        <span class="kd">local</span> <span class="n">_</span><span class="p">,</span><span class="n">_</span><span class="p">,</span><span class="n">_</span><span class="p">,</span><span class="n">_</span><span class="p">,</span><span class="n">logoff</span> <span class="o">=</span> <span class="n">GetGuildMemberInfo</span><span class="p">(</span><span class="n">guildId</span><span class="p">,</span> <span class="n">idx</span><span class="p">)</span>
        <span class="kd">local</span> <span class="n">_</span><span class="p">,</span> <span class="n">charName</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="n">GetGuildMemberCharacterInfo</span><span class="p">(</span><span class="n">guildId</span><span class="p">,</span> <span class="n">idx</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">logoff</span> <span class="o">==</span> <span class="mi">0</span> <span class="ow">and</span> <span class="n">zo_strformat</span><span class="p">(</span><span class="s2">"&lt;&lt;1&gt;&gt;"</span><span class="p">,</span> <span class="n">charName</span><span class="p">)</span> <span class="o">~=</span> <span class="n">TarodGuildInfo</span><span class="p">.</span><span class="n">currentPlayer</span> <span class="k">then</span>
            <span class="n">onlineCount</span> <span class="o">=</span> <span class="n">onlineCount</span> <span class="o">+</span> <span class="mi">1</span>
        <span class="k">end</span>
    <span class="k">end</span>
    <span class="k">return</span> <span class="n">onlineCount</span>
<span class="k">end</span>

<span class="k">function</span> <span class="nf">TarodGuildInfo</span><span class="p">:</span><span class="n">PrintGuildMembers</span><span class="p">(</span><span class="n">guildId</span><span class="p">)</span>
    <span class="kd">local</span> <span class="n">guildMemberCount</span> <span class="o">=</span> <span class="n">GetNumGuildMembers</span><span class="p">(</span><span class="n">guildId</span><span class="p">)</span>
        
    <span class="k">for</span> <span class="n">idx</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">guildMemberCount</span> <span class="k">do</span>
        <span class="kd">local</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span> <span class="p">,</span> <span class="n">_</span><span class="p">,</span><span class="n">logoff</span> <span class="o">=</span> <span class="n">GetGuildMemberInfo</span><span class="p">(</span><span class="n">guildId</span><span class="p">,</span> <span class="n">idx</span><span class="p">)</span>
        <span class="kd">local</span> <span class="n">_</span><span class="p">,</span> <span class="n">charName</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="n">GetGuildMemberCharacterInfo</span><span class="p">(</span><span class="n">guildId</span><span class="p">,</span> <span class="n">idx</span><span class="p">)</span>
        
        <span class="k">if</span> <span class="n">logoff</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">then</span>
            <span class="n">d</span><span class="p">(</span><span class="n">charName</span><span class="p">)</span>
        <span class="k">end</span>
    <span class="k">end</span>
<span class="k">end</span>

<span class="k">function</span> <span class="nc">TarodGuildInfo</span><span class="p">.</span><span class="nf">OnAddOnLoaded</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="n">addontName</span><span class="p">)</span> 
    <span class="k">if</span> <span class="n">addontName</span> <span class="o">==</span> <span class="n">TarodGuildInfo</span><span class="p">.</span><span class="n">id</span> <span class="k">then</span>
        <span class="n">TarodGuildInfo</span><span class="p">:</span><span class="n">Initialize</span><span class="p">()</span>
    <span class="k">end</span>
<span class="k">end</span>

<span class="n">EVENT_MANAGER</span><span class="p">:</span><span class="n">RegisterForEvent</span><span class="p">(</span><span class="n">TarodGuildInfo</span><span class="p">.</span><span class="n">id</span><span class="p">,</span> <span class="n">EVENT_ADD_ON_LOADED</span><span class="p">,</span> <span class="n">TarodGuildInfo</span><span class="p">.</span><span class="n">OnAddOnLoaded</span><span class="p">)</span>

</code></pre></div></div>

<p>I added a global variable to not display online guild members if there were too many of them online, but the rest is
just almost gluing together the previous pieces.</p>

<h2 id="adding-clickable-links-to-the-client">Adding clickable links to the client</h2>

<p>Now it was time to improve how to add detailed info to each logged character info. Here, the main challenge was making
valid links in order to be able to interact with the players/characters by right clicking on them. As usual, there is
<a href="https://wiki.esoui.com/How_to_create_custom_links">an article about it</a> on the wiki:</p>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span> <span class="nf">TarodGuildInfo</span><span class="p">:</span><span class="n">PrintGuildMembers</span><span class="p">(</span><span class="n">guildId</span><span class="p">)</span>
    <span class="kd">local</span> <span class="n">guildMemberCount</span> <span class="o">=</span> <span class="n">GetNumGuildMembers</span><span class="p">(</span><span class="n">guildId</span><span class="p">)</span>
    
    <span class="k">for</span> <span class="n">idx</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">guildMemberCount</span> <span class="k">do</span>
        <span class="kd">local</span> <span class="n">pname</span><span class="p">,</span><span class="n">note</span><span class="p">,</span><span class="n">rank</span><span class="p">,</span><span class="n">status</span><span class="p">,</span><span class="n">logoff</span> <span class="o">=</span> <span class="n">GetGuildMemberInfo</span><span class="p">(</span><span class="n">guildId</span><span class="p">,</span> <span class="n">idx</span><span class="p">)</span>
        <span class="kd">local</span> <span class="n">hasChar</span><span class="p">,</span> <span class="n">charName</span><span class="p">,</span> <span class="n">zoneName</span><span class="p">,</span> <span class="n">classType</span><span class="p">,</span> <span class="n">alliance</span><span class="p">,</span> <span class="n">level</span><span class="p">,</span> <span class="n">cp</span><span class="p">,</span> <span class="n">zoneId</span><span class="p">,</span> <span class="n">consoleId</span> <span class="o">=</span> <span class="n">GetGuildMemberCharacterInfo</span><span class="p">(</span><span class="n">guildId</span><span class="p">,</span> <span class="n">idx</span><span class="p">)</span>
        
        <span class="c1">-- Why GetUnitName returns formatted string while charName has the localization suffixes??</span>
        <span class="k">if</span> <span class="n">logoff</span> <span class="o">==</span> <span class="mi">0</span> <span class="ow">and</span> <span class="n">zo_strformat</span><span class="p">(</span><span class="s2">"&lt;&lt;1&gt;&gt;"</span><span class="p">,</span> <span class="n">charName</span><span class="p">)</span> <span class="o">~=</span> <span class="n">TarodGuildInfo</span><span class="p">.</span><span class="n">currentPlayer</span> <span class="k">then</span>
            <span class="kd">local</span> <span class="n">gender</span> <span class="o">=</span> <span class="n">GetGenderFromNameDescriptor</span><span class="p">(</span><span class="n">charName</span><span class="p">)</span>
            <span class="kd">local</span> <span class="n">className</span> <span class="o">=</span> <span class="n">GetClassName</span><span class="p">(</span><span class="n">gender</span><span class="p">,</span> <span class="n">classType</span><span class="p">)</span>
            <span class="k">if</span> <span class="n">cp</span> <span class="o">&gt;</span> <span class="mi">810</span> <span class="k">then</span> <span class="n">cp</span> <span class="o">=</span> <span class="mi">810</span> <span class="k">end</span>
            <span class="kd">local</span> <span class="n">text</span> <span class="o">=</span> <span class="n">zo_strformat</span><span class="p">(</span><span class="s2">"   |cB27BFF|H1:character:&lt;&lt;1&gt;&gt;|h&lt;&lt;1&gt;&gt;|h|r/|c6EABCA&lt;&lt;2&gt;&gt;|r |cC3F0C2&lt;&lt;3&gt;&gt; &lt;&lt;4&gt;&gt; &lt;&lt;5[/%dcp/%dcp]&gt;&gt; in &lt;&lt;6&gt;&gt;|r"</span><span class="p">,</span> <span class="n">pname</span><span class="p">,</span> <span class="n">charName</span><span class="p">,</span> <span class="n">className</span><span class="p">,</span> <span class="n">level</span><span class="p">,</span> <span class="n">cp</span><span class="p">,</span> <span class="n">zoneName</span><span class="p">)</span>
            <span class="n">d</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
        <span class="k">end</span>
    <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Interesting stuff about the special string format:</p>
<ul>
  <li>`    `: I found no way to insert tabs, so I switched to spaces instead. After all, <a href="https://youtu.be/SsoOG6ZeyUI">who argues about tabs vs spaces?</a>
(spoiler, 
<a href="https://www.wired.com/2016/06/instagram-strikes-sizable-blow-silicon-valleys-tabs-vs-spaces-war/">he</a> 
<a href="https://www.wired.com/2016/06/instagram-strikes-sizable-blow-silicon-valleys-tabs-vs-spaces-war/">was</a> 
<a href="https://www.thewrap.com/silicon-valley-fact-check-why-richard-is-wrong-on-tabs-versus-spaces/">wrong</a>)</li>
  <li><code class="language-plaintext highlighter-rouge">cB27BFF|H1:character:&lt;&lt;1&gt;&gt;|h&lt;&lt;1&gt;&gt;|h|r/|c6EABCA&lt;&lt;2&gt;&gt;|r</code>: Color playerName with a link to interact, 
followed by current character name</li>
  <li><code class="language-plaintext highlighter-rouge">&lt;&lt;5[/%dcp/%dcp]&gt;&gt;</code> displays the champion points only if they are present (conditional formatting as above: 
empty string for zero, same string for 1 and greater than 1)</li>
</ul>

<h2 id="extra-slash_commands">Extra: <code class="language-plaintext highlighter-rouge">/slash_commands</code></h2>

<p>Finally, I wanted to register some <a href="https://wiki.esoui.com/How_to_add_a_slash_command"><code class="language-plaintext highlighter-rouge">SLASH_COMMANDS</code></a> with the game 
UIEngine:</p>

<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">SLASH_COMMANDS</span><span class="p">[</span><span class="s2">"/guildinfo"</span><span class="p">]</span> <span class="o">=</span> <span class="k">function</span> <span class="p">(</span><span class="n">extra</span><span class="p">)</span>
    <span class="kd">local</span> <span class="n">guilds</span> <span class="o">=</span> <span class="n">GetNumGuilds</span><span class="p">()</span>
    <span class="kd">local</span> <span class="n">index</span> <span class="o">=</span> <span class="nb">tonumber</span><span class="p">(</span><span class="n">extra</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">index</span> <span class="o">==</span> <span class="n">nill</span> <span class="ow">or</span> <span class="n">guilds</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">then</span> 
        <span class="n">TarodGuildInfo</span><span class="p">:</span><span class="n">GuildInfo</span><span class="p">()</span>
    <span class="k">elseif</span> <span class="n">index</span> <span class="o">&gt;=</span> <span class="mi">1</span> <span class="ow">and</span> <span class="n">index</span> <span class="o">&lt;=</span> <span class="n">guilds</span> <span class="k">then</span> 
        <span class="n">TarodGuildInfo</span><span class="p">:</span><span class="n">PrintGuildInfo</span><span class="p">(</span><span class="n">index</span><span class="p">)</span>
    <span class="k">else</span>
        <span class="n">d</span><span class="p">(</span><span class="n">zo_strformat</span><span class="p">(</span><span class="s2">"Please use |cC3F0C2/guildinfo|r |cB27BFF#num_guild|r where |cB27BFF#num_guild|r is a valid guild number between 1 and &lt;&lt;1&gt;&gt;"</span><span class="p">,</span> <span class="n">guilds</span><span class="p">))</span>
        <span class="n">d</span><span class="p">(</span><span class="s2">"You can also use plain |cC3F0C2/guildinfo|r to get the default welcome message."</span><span class="p">)</span>
    <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Although the <code class="language-plaintext highlighter-rouge">/guildinfo</code> command is really out of the scope of the initial user story, I found it useful, specially
after adding the right click feature of the player names on the previous step. I could just write <code class="language-plaintext highlighter-rouge">/guildinfo #</code> on the 
chat and interact with my guild mates without opening the guild window.</p>

<h2 id="the-result">The Result</h2>

<figure class=""><img src="/assets/images/elder-scrolls-online/chat-result.jpg" alt="Image showing in game chat with the members of two guilds online" /><figcaption>
      Final result of /guildinfo command

    </figcaption></figure>

<p>The full sourcecode for the addon can be found in my Git Hub <a href="https://github.com/TarodBOFH/TarodGuildInfo">repository</a>.</p>

<p>Happy modding!</p>]]></content><author><name>Juan Ara</name></author><category term="Games" /><category term="Software Engineering" /><category term="lua" /><category term="elder scrolls online" /><category term="ESO" /><summary type="html"><![CDATA[I always use my hobbies to learn stuff, and I decided that I wanted to learn how Elder Scrolls Online designed addons, API and UI, so I jumped straight into writing an addon for it. It's a simple one: it just lists online guild members when login in.]]></summary></entry><entry><title type="html">Customizing k8s plugins autocomplete on bash and zsh</title><link href="https://www.cornerinthemiddle.com/software%20engineering/Customizing-k8s-plugins-autocomplete-on-bash-and-zsh/" rel="alternate" type="text/html" title="Customizing k8s plugins autocomplete on bash and zsh" /><published>2020-06-15T00:00:00+02:00</published><updated>2020-06-15T00:00:00+02:00</updated><id>https://www.cornerinthemiddle.com/software%20engineering/Customizing-k8s-plugins-autocomplete-on-bash-and-zsh</id><content type="html" xml:base="https://www.cornerinthemiddle.com/software%20engineering/Customizing-k8s-plugins-autocomplete-on-bash-and-zsh/"><![CDATA[<p>I was procrastinating setting up my new mac for too much time.
Today I was configuring our k8s environment, and I decided to invest some time to put the right autocomplete plugins.</p>

<p>I’ve seen some companies writing their own k8s ctl application, in order to offer a basic cmd-line interface that
either lists contexts/services or has shortcut. Sometimes the shortcuts are needed, but most of the times those could
have been solved with a plugin, like using k8s plugins and binding them into bash / zsh autocomplete.</p>

<p>First, we have kube config files generated per team and since as architect I work across several teams, 
I was looking to the right way to include different config files on k8s in a very standard way. 
The standard way would be a single <code class="language-plaintext highlighter-rouge">.kube/config</code> file, but I went to adding each file to my <code class="language-plaintext highlighter-rouge">$KUBECONFIG</code> env variable:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">KUBECONFIG</span><span class="o">=</span><span class="nv">$KUBECONFIG</span>:<span class="k">${</span><span class="nv">HOME</span><span class="k">}</span>/.cube/some-config-file:<span class="k">${</span><span class="nv">HOME</span><span class="k">}</span>./some-other-config-file
</code></pre></div></div>

<p>Then I setup <a href="https://github.com/kubernetes-sigs/krew/">Krew</a> as per k8s recommendation:</p>

<blockquote>
  <p><a href="https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/#installing-kubectl-plugins">Installing kubectl plugins</a> You can use <a href="https://github.com/kubernetes-sigs/krew/">Krew</a> to discover and install kubectl plugins from a community-curated <a href="https://github.com/kubernetes-sigs/krew-index/">plugin index</a>.</p>
</blockquote>

<p>So installing krew was pretty straight forward (just have to add to <code class="language-plaintext highlighter-rouge">$PATH</code> the installation path), 
and I was ready to use the plugins I was most interested in. 
As we are having more than 200 namespaces (services) in several contexts (environments) it was a natural follow up to 
install <a href="https://github.com/ahmetb/kubectx">kubectx and kubens</a>.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl krew <span class="nb">install </span>ctx
kubectl krew <span class="nb">install </span>ns
</code></pre></div></div>

<p>Are we done? Not yet. We lack autocomplete in the plugins.</p>

<p>Both come with autocomplete on the standalone version (that is <code class="language-plaintext highlighter-rouge">kubectx</code> and <code class="language-plaintext highlighter-rouge">kubens</code>) but I prefer to stick to 
<code class="language-plaintext highlighter-rouge">kubectl</code> or as I have aliased with <a href="https://github.com/Bash-it/bash-it">bash-it</a>, <code class="language-plaintext highlighter-rouge">kc</code>.</p>

<p>There is a request on k8s to add autocomplete support to plugins <a href="https://github.com/kubernetes/kubernetes/issues/74178">here</a> 
but since the support needs to be <em>optional</em> and <em>universal</em> there’s still no agreement on how to implement it.</p>

<p>So let’s dive a little into how to add that support ourselves.</p>

<p><em>bash/zsh differences</em>: The basic procedure applies both zsh and bash but I am going to use bash as an example.</p>

<h3 id="getting-kubernetes-completion-on-the-shell">Getting kubernetes completion on the shell</h3>

<p>As a starting point, <code class="language-plaintext highlighter-rouge">kubectl</code> already includes <a href="https://kubernetes.io/docs/tasks/tools/install-kubectl/#enabling-shell-autocompletion">code completion</a> 
for bash and zsh. We will be using that as a reference for our code.</p>

<p>We just generate k8s completion for our shell and save in a file on our dotfiles or equivalent 
(some shells require tools like <code class="language-plaintext highlighter-rouge">bash-completion</code>; I am using <code class="language-plaintext highlighter-rouge">bash-it</code> as I said before)</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl completion bash <span class="o">&gt;</span> ~/.bash-it/custom/completion/custom.completion.bash
</code></pre></div></div>
<p>or if you are using <code class="language-plaintext highlighter-rouge">oh-my-zsh</code></p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kubectl completion zsh <span class="o">&gt;</span> ~/.oh-my-zsh/custom.completion.kube.config
</code></pre></div></div>

<p>This is the basic step but we have not configured anything yet.</p>

<p>Then we need to include the generated file in our profile. 
If using bash-it it’ll be included automagically with the above naming convention. 
If you’re following the example on <code class="language-plaintext highlighter-rouge">zsh</code> above just add the following line to your <code class="language-plaintext highlighter-rouge">.zshrc</code> customizations:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">source</span> ~/.oh-my-zsh/custom.completion.kube.config
</code></pre></div></div>

<p>What we want at this point is:</p>
<ul>
  <li>Add completion to kubectl to include ctx and ns commands</li>
  <li>After ctx complete the command with the different kube contexts</li>
  <li>After ns complete the command with the different kube namespaces (of the current context)</li>
</ul>

<p>The fastest and easier way to do it is just edit our newly generated file above. With your favorite editor, 
find the function <code class="language-plaintext highlighter-rouge">_kubectl_root_command()</code> and add our new commands at the bottom of the command list:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>_kubectl_root_command<span class="o">()</span>
<span class="o">{</span>
    <span class="nv">last_command</span><span class="o">=</span><span class="s2">"kubectl"</span>
    <span class="nv">command_aliases</span><span class="o">=()</span>
    
    <span class="nv">commands</span><span class="o">=()</span>
    commands+<span class="o">=(</span><span class="s2">"annotate"</span><span class="o">)</span>

    ...

    commands+<span class="o">=(</span><span class="s2">"wait"</span><span class="o">)</span>
    <span class="c"># add plugin commands to completion:</span>
    commands+<span class="o">=(</span><span class="s2">"ctx"</span><span class="o">)</span>
    commands+<span class="o">=(</span><span class="s2">"ns"</span><span class="o">)</span>
    
    ...

<span class="o">}</span>
</code></pre></div></div>

<p>If we run now kubectl with autocomplete, we get the right completions:</p>
<figure>
    <img src="/assets/images/posts/customizing-k8s-plugins-autocomplete-on-bash-and-zsh/eg0f6ak5kyk2g9zdqpu1.png" alt="Image showing kubectl completion for plugins" />
    <figcaption>Command completion is working</figcaption>
</figure>

<p>And they also behave as we expect when we’ve partial words:</p>
<figure>
    <img src="/assets/images/posts/customizing-k8s-plugins-autocomplete-on-bash-and-zsh/yzdbnpz9qxp57sinaso2.png" alt="Image showing partial kubectl completion for plugins" />
    <figcaption>Command completion is working for partial words</figcaption>
</figure>

<p>Great! We can cross the first item in our todo-list; now for the second one.</p>

<p>We observe that the file generated by k8s contains a bunch of <em>helper</em> functions (those that start with <code class="language-plaintext highlighter-rouge">__</code>) and some 
<em>command</em> functions (that start with <code class="language-plaintext highlighter-rouge">_</code>) There’s also a convention on the variables they use 
(like <code class="language-plaintext highlighter-rouge">last_command</code> or the <code class="language-plaintext highlighter-rouge">commands</code> list we <em>hacked</em> above). 
What we are going to do is to add our own <em>handler</em> to the <code class="language-plaintext highlighter-rouge">kubectl</code> <code class="language-plaintext highlighter-rouge">ctx</code> and <code class="language-plaintext highlighter-rouge">ns</code> commands and then we will instruct the autocomplete code to do it’s magic. To the magic part we will be using a function that k8s already had there but can help us to start <em>hacking</em>: <code class="language-plaintext highlighter-rouge">__kubectl_custom_func()</code></p>

<h3 id="define-your-own-handler">Define your own handler</h3>
<p>This is straight forward. Our command is <code class="language-plaintext highlighter-rouge">kubectl ctx</code> so we define a function called <code class="language-plaintext highlighter-rouge">_kubectl_ctx</code> 
(the magic parsing on the script and autocomplete will do the rest). 
Inside our function, we clear any variable that might have been set but could <em>break</em> some k8s commands:</p>

<p>I decided to add my functions at the bottom ot k8s’ generated file</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>_kubectl_ctx<span class="o">()</span>
<span class="o">{</span>
    <span class="nv">last_command</span><span class="o">=</span><span class="s2">"kubectl_ctx"</span>

    <span class="nv">command_aliases</span><span class="o">=()</span>

    <span class="nv">commands</span><span class="o">=()</span>

    <span class="nv">flags</span><span class="o">=()</span>
    <span class="nv">two_word_flags</span><span class="o">=()</span>
    <span class="nv">local_nonpersistent_flags</span><span class="o">=()</span>
    <span class="nv">flags_with_completion</span><span class="o">=()</span>
    <span class="nv">flags_completion</span><span class="o">=()</span>

    <span class="nv">must_have_one_flag</span><span class="o">=()</span>
    <span class="nv">must_have_one_noun</span><span class="o">=()</span>
    <span class="nv">noun_aliases</span><span class="o">=()</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Those lists and flags can handle the behavior of this command but we’re relying now on the custom function mentioned above.</p>

<p>Once we have added this, we can glue our new command to the function above.
Since we want to autocomplete with the contexts and the switch there already has a case for completing commands with 
contexts, we are almost done. We just add our new command to the right case:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>__kubectl_custom_func<span class="o">()</span> <span class="o">{</span>
    <span class="k">case</span> <span class="k">${</span><span class="nv">last_command</span><span class="k">}</span> <span class="k">in</span>

        ...

        kubectl_config_use-context <span class="p">|</span> kubectl_config_rename-context <span class="p">|</span> kubectl_ctx<span class="p">)</span>
            __kubectl_config_get_contexts
            <span class="k">return</span>
            <span class="p">;;</span>

        ...

        <span class="k">*</span><span class="p">)</span>
            <span class="p">;;</span>
    <span class="k">esac</span>
<span class="o">}</span>
</code></pre></div></div>

<p>To add the namespace completion, we just add one additional case to the switch above, right before the default:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>__kubectl_custom_func<span class="o">()</span> <span class="o">{</span>
    <span class="k">case</span> <span class="k">${</span><span class="nv">last_command</span><span class="k">}</span> <span class="k">in</span>

        ...

        kubectl_ns<span class="p">)</span>
            __kubectl_get_resource_namespace
            <span class="k">return</span>
            <span class="p">;;</span>
        <span class="k">*</span><span class="p">)</span>
            <span class="p">;;</span>
    <span class="k">esac</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Don’t forget to add the function to handle the ns command!:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>_kubectl_ns<span class="o">()</span>
<span class="o">{</span>
    <span class="nv">last_command</span><span class="o">=</span><span class="s2">"kubectl_ns"</span>

    <span class="nv">command_aliases</span><span class="o">=()</span>

    <span class="nv">commands</span><span class="o">=()</span>

    <span class="nv">flags</span><span class="o">=()</span>
    <span class="nv">two_word_flags</span><span class="o">=()</span>
    <span class="nv">local_nonpersistent_flags</span><span class="o">=()</span>
    <span class="nv">flags_with_completion</span><span class="o">=()</span>
    <span class="nv">flags_completion</span><span class="o">=()</span>

    <span class="nv">must_have_one_flag</span><span class="o">=()</span>
    <span class="nv">must_have_one_noun</span><span class="o">=()</span>
    <span class="nv">noun_aliases</span><span class="o">=()</span>
<span class="o">}</span>
</code></pre></div></div>

<p>This was really easy because we already had the <code class="language-plaintext highlighter-rouge">__kubectl_get_resource_namespace</code> (and get contexts) functions there,
but we could have written our own implementation.</p>

<p>If you want to dive deep into this subject, the generated k8s autocompletion script is full of examples.
The first iteration can be, for example, support some <code class="language-plaintext highlighter-rouge">kubectl</code> flags, like the rest of the commands;
for example, copying the available list of flags from one of them (or the root command).</p>

<p>Cheers!</p>]]></content><author><name>Juan Ara</name></author><category term="Software Engineering" /><category term="kubernetes" /><category term="k8s" /><category term="bash" /><category term="zsh" /><summary type="html"><![CDATA[In this post I'll configuring a default k8s installation to integrate with bash/zsh autocomplete, and add those autocomplete features on a couple of k8s plugins, for example by having kube-ctx and kube-ns plugins autocomplete with the contexts and namespaces currently running in our kube configurations.]]></summary></entry><entry><title type="html">Using Gradle Kotlin DSL with junit 5</title><link href="https://www.cornerinthemiddle.com/software%20engineering/using-gradle-kotlin-dsl-with-junit5/" rel="alternate" type="text/html" title="Using Gradle Kotlin DSL with junit 5" /><published>2019-08-07T00:00:00+02:00</published><updated>2019-08-07T00:00:00+02:00</updated><id>https://www.cornerinthemiddle.com/software%20engineering/using-gradle-kotlin-dsl-with-junit5</id><content type="html" xml:base="https://www.cornerinthemiddle.com/software%20engineering/using-gradle-kotlin-dsl-with-junit5/"><![CDATA[<h1 id="junit-5-and-gradle">JUnit 5 and Gradle</h1>

<p>Gradle 5+ <a href="https://gradle.org/releases/">has been out for a while now</a> and with that we finally got the ability to write 
our Gradle scripts in Kotlin.
Almost every example out there about JUnit and Gradle is still using the old 
dependencies or Groovy Gradle syntax, so let’s try to migrate a Groovy build using JUnit Platform to Kotlin DSL, 
specifically, based on examples about how to extend Gradle’s test output to add summaries at the end of the build.</p>

<h2 id="what-we-will-achieve">What we will achieve</h2>

<p>We are starting from a plain Groovy Gradle script that configures a test task to log our JUnit test status (like 
examples <a href="https://medium.com/@jonashavers/how-to-use-junit-5-with-gradle-fb7c5c3286cc">here</a> or
<a href="https://stackoverflow.com/a/36130467">here</a> )</p>

<p>In addition, we will enable spotless and checkstyle in our Kotlin project as well as configuring IntelliJ IDEA Gradle
plugin to download javadoc and sources of our dependencies.</p>

<h2 id="starting-point">Starting point</h2>

<p>So let’s see how an old Groovy <code class="language-plaintext highlighter-rouge">build.gradle</code> is like when we want to use JUnit 5:</p>

<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">test</span> <span class="o">{</span>
    <span class="n">testLogging</span> <span class="o">{</span>
        <span class="n">exceptionFormat</span> <span class="o">=</span> <span class="s1">'full'</span>
        <span class="n">events</span> <span class="s1">'FAILED'</span><span class="o">,</span> <span class="s1">'SKIPPED'</span><span class="o">,</span> <span class="s1">'PASSED'</span>
        <span class="n">showStandardStreams</span> <span class="o">=</span> <span class="kc">true</span>
        <span class="n">afterSuite</span> <span class="o">{</span> <span class="n">desc</span><span class="o">,</span> <span class="n">result</span> <span class="o">-&gt;</span>
            <span class="k">if</span> <span class="o">(!</span><span class="n">desc</span><span class="o">.</span><span class="na">parent</span><span class="o">)</span> <span class="o">{</span>
                <span class="n">println</span> <span class="s2">"\nTest result: ${result.resultType}"</span>
                <span class="n">println</span> <span class="s2">"Test summary: ${result.testCount} tests, "</span> <span class="o">+</span>
                        <span class="s2">"${result.successfulTestCount} succeeded, "</span> <span class="o">+</span>
                        <span class="s2">"${result.failedTestCount} failed, "</span> <span class="o">+</span>
                        <span class="s2">"${result.skippedTestCount} skipped"</span>
            <span class="o">}</span>
        <span class="o">}</span>        
    <span class="o">}</span>

    <span class="n">useJUnitPlatform</span> <span class="o">{}</span>
<span class="o">}</span>

<span class="n">cleanTest</span> <span class="o">{}</span>

<span class="n">configure</span><span class="o">(</span><span class="n">cleanTest</span><span class="o">)</span> <span class="o">{</span> <span class="n">group</span> <span class="o">=</span> <span class="s1">'verification'</span> <span class="o">}</span>
</code></pre></div></div>

<p>This is the very basic example we are going to find in stackoverflow or other forums.</p>

<p>We have a closure to display some test statistics, like the number of passed, failed and skipped tests.
We also instruct Gradle to use Junit Platform (Junit5) with the <code class="language-plaintext highlighter-rouge">useJUnitPlatform {}</code> instruction and we also  add the 
<code class="language-plaintext highlighter-rouge">cleanTest</code> task to the verification group (any <code class="language-plaintext highlighter-rouge">clean&lt;Task&gt;</code> in Gradle will delete the results by <code class="language-plaintext highlighter-rouge">&lt;Task&gt;</code>).</p>

<p>This last bit adds the task to our IDE Gradle tasklist in the desired group.</p>

<h2 id="migrating-to-gradle-kotlin-dsl">Migrating to Gradle Kotlin DSL</h2>

<p>As with every software engineering project, our task to migrate can be broken in smaller chunks and we are going to
start with the MVP, which is being able to build and test our project. Since we are testing, we will add also our test
framework dependencies: <code class="language-plaintext highlighter-rouge">mockk</code> and <code class="language-plaintext highlighter-rouge">assertj</code>:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">repositories</span> <span class="p">{</span>
    <span class="nf">mavenCentral</span><span class="p">()</span>
<span class="p">}</span>

<span class="nf">plugins</span> <span class="p">{</span>
    <span class="nf">kotlin</span><span class="p">(</span><span class="s">"jvm"</span><span class="p">)</span> <span class="n">version</span> <span class="s">"1.3.40"</span>
<span class="p">}</span>


<span class="nf">dependencies</span> <span class="p">{</span>
    <span class="nf">implementation</span><span class="p">(</span><span class="nf">kotlin</span><span class="p">(</span><span class="s">"stdlib-jdk8"</span><span class="p">))</span>

    <span class="nf">testImplementation</span><span class="p">(</span><span class="s">"io.mockk:mockk:1.9.3"</span><span class="p">)</span>
    <span class="nf">testImplementation</span><span class="p">(</span><span class="s">"org.assertj:assertj-core:3.11.1"</span><span class="p">)</span>
    <span class="nf">testImplementation</span><span class="p">(</span><span class="s">"org.junit.jupiter:junit-jupiter-api:5.4.2"</span><span class="p">)</span>
    <span class="nf">testImplementation</span><span class="p">(</span><span class="s">"org.junit.jupiter:junit-jupiter-params:5.4.2"</span><span class="p">)</span>

    <span class="nf">testRuntimeOnly</span><span class="p">(</span><span class="s">"org.junit.jupiter:junit-jupiter-engine:5.4.2"</span><span class="p">)</span>
<span class="p">}</span>

<span class="nf">test</span> <span class="p">{</span>
    <span class="nf">useJunitPlatform</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>

<p>But when we reimport the project… k-boom!</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  Line 21: test {
  
^ Expression 'test' cannot be invoked as a function. The function 'invoke()' is not found
...
</code></pre></div></div>

<p>Why is this not working? Well, accessors for tasks are not published outside the <code class="language-plaintext highlighter-rouge">tasks</code> container (or accessor) on
Gradle’s Kotlin DSL.</p>

<p>In order to solve this, we can use <code class="language-plaintext highlighter-rouge">tasks</code> and then filter by using one of the available methods:</p>

<h3 id="selecting-a-hardcoded-taskname-inside-tasks-block">Selecting a hardcoded taskname inside tasks block</h3>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">tasks</span> <span class="p">{</span>
    <span class="s">"test"</span><span class="p">(</span><span class="nc">Test</span><span class="o">::</span><span class="k">class</span><span class="p">)</span> <span class="p">{</span>
        <span class="nf">useJUnitPlatform</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Notice how the class of the tasks is specified by <code class="language-plaintext highlighter-rouge">Test::class</code>, thus allowing us to invoke <code class="language-plaintext highlighter-rouge">useJUnitPlatform()</code> method 
of the Gradle Test class. However we do not like hardcoded strings… like <code class="language-plaintext highlighter-rouge">"test"</code>. Fortunately, we can simply modify 
the behavior of a list of tasks easily:</p>

<h3 id="filtering-through-the-existing-tasks">Filtering through the existing tasks</h3>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">tasks</span><span class="p">.</span><span class="n">withType</span><span class="p">&lt;</span><span class="nc">Test</span><span class="p">&gt;</span> <span class="p">{</span>

    <span class="nf">useJUnitPlatform</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Much more readable! It will also apply to any <code class="language-plaintext highlighter-rouge">Test</code> task defined on our build.</p>

<h3 id="by-task-accessor-inside-the-task-block-requires">By task accessor inside the task block (requires)</h3>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">tasks</span> <span class="p">{</span>
    <span class="nf">test</span> <span class="p">{</span>
        <span class="nf">useJUnitPlatform</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Even better! we do not need to know the type of the class since this is a type-safe accessor and comes with full IDE 
integration like code completion and inspections.</p>

<p>There are many other ways to access the builtin (or plugin added) tasks.
Just check <a href="https://docs.gradle.org/current/userguide/kotlin_dsl.html#type-safe-accessors">Gradle documentation</a></p>

<h3 id="logging-junit-output">Logging JUnit output</h3>

<p>Since the accessors are now type-safe, and we are working with <code class="language-plaintext highlighter-rouge">Test</code> tasks, we believe can invoke some of our previous 
functionality almost by copying and pasting from our original implementation.</p>

<p>Unfortunately, the following snippet breaks our build:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">tasks</span> <span class="p">{</span>

    <span class="nf">test</span> <span class="p">{</span>
        <span class="nf">useJUnitPlatform</span><span class="p">()</span>
    
        <span class="nf">testLogging</span> <span class="p">{</span>
            <span class="n">exceptionFormat</span> <span class="p">=</span> <span class="err">'</span><span class="n">full</span><span class="err">'</span>
            <span class="n">events</span> <span class="err">'</span><span class="nc">FAILED</span><span class="err">'</span><span class="p">,</span> <span class="err">'</span><span class="nc">SKIPPED</span><span class="err">'</span><span class="p">,</span> <span class="err">'</span><span class="nc">PASSED</span><span class="err">'</span>
            <span class="n">showStandardStreams</span> <span class="p">=</span> <span class="k">true</span>
            <span class="nf">afterSuite</span> <span class="p">{</span> <span class="n">desc</span><span class="p">,</span> <span class="n">result</span> <span class="p">-&gt;</span>
                <span class="k">if</span> <span class="p">(!</span><span class="n">desc</span><span class="p">.</span><span class="n">parent</span><span class="p">)</span> <span class="p">{</span>
                    <span class="n">println</span> <span class="s">"\nTest result: ${result.resultType}"</span>
                    <span class="n">println</span> <span class="s">"Test summary: ${result.testCount} tests, "</span> <span class="p">+</span>
                    <span class="s">"${result.successfulTestCount} succeeded, "</span> <span class="p">+</span>
                            <span class="s">"${result.failedTestCount} failed, "</span> <span class="p">+</span>
                            <span class="s">"${result.skippedTestCount} skipped"</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">Unexpected tokens (use ';' to separate expressions on the same line)</code>. This refers to the events list we are filtering.</p>

<p>Since we are not using Groovy anymore, we need to convert them from hardcoded strings to the proper objects, and also
use the right assignment with <code class="language-plaintext highlighter-rouge">=</code></p>

<p>The same applies to the <code class="language-plaintext highlighter-rouge">exceptionFormat</code> so let’s also fix that. Don’t forget to import the new classes!</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nn">org.gradle.api.tasks.testing.logging.TestExceptionFormat</span>
<span class="k">import</span> <span class="nn">org.gradle.api.tasks.testing.logging.TestLogEvent</span>

<span class="nf">repositories</span> <span class="p">{</span>
    <span class="nf">mavenCentral</span><span class="p">()</span>
<span class="p">}</span>

<span class="nf">plugins</span> <span class="p">{</span>
    <span class="nf">kotlin</span><span class="p">(</span><span class="s">"jvm"</span><span class="p">)</span> <span class="n">version</span> <span class="s">"1.3.40"</span>
<span class="p">}</span>

<span class="cm">/*...*/</span>

<span class="nf">tasks</span> <span class="p">{</span>

    <span class="nf">test</span> <span class="p">{</span>
    
        <span class="nf">useJUnitPlatform</span><span class="p">()</span>
    
        <span class="nf">testLogging</span> <span class="p">{</span>
            <span class="n">exceptionFormat</span> <span class="p">=</span> <span class="nc">TestExceptionFormat</span><span class="p">.</span><span class="nc">FULL</span>
            <span class="n">events</span> <span class="p">=</span> <span class="nf">mutableSetOf</span><span class="p">(</span><span class="nc">TestLogEvent</span><span class="p">.</span><span class="nc">FAILED</span><span class="p">,</span> <span class="nc">TestLogEvent</span><span class="p">.</span><span class="nc">PASSED</span><span class="p">,</span> <span class="nc">TestLogEvent</span><span class="p">.</span><span class="nc">SKIPPED</span><span class="p">)</span>
            <span class="n">showStandardStreams</span> <span class="p">=</span> <span class="k">true</span>
            <span class="nf">afterSuite</span> <span class="p">{</span> <span class="n">desc</span><span class="p">,</span> <span class="n">result</span> <span class="p">-&gt;</span>
                <span class="k">if</span> <span class="p">(!</span><span class="n">desc</span><span class="p">.</span><span class="n">parent</span><span class="p">)</span> <span class="p">{</span>
                    <span class="n">println</span> <span class="s">"\nTest result: ${result.resultType}"</span>
                    <span class="n">println</span> <span class="s">"Test summary: ${result.testCount} tests, "</span> <span class="p">+</span>
                    <span class="s">"${result.successfulTestCount} succeeded, "</span> <span class="p">+</span>
                            <span class="s">"${result.failedTestCount} failed, "</span> <span class="p">+</span>
                            <span class="s">"${result.skippedTestCount} skipped"</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Ok, we fixed the imports but we are still facing the most difficult part so far.</p>

<p>Kotlin does not have <code class="language-plaintext highlighter-rouge">Closure</code> and our after suite block returns a pair of <code class="language-plaintext highlighter-rouge">Nothing</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Type mismatch: inferred type is (Nothing, Nothing) -&gt; TypeVariable(_L) but Closure&lt;(raw) Any!&gt;! was expected
</code></pre></div></div>

<p>At this point we might consider importing Groovy’s <code class="language-plaintext highlighter-rouge">Closure</code> but our objective was using Kotlin DSL and removing the 
Groovy dependency so there must be an alternative.</p>

<p>Digging on the internet <a href="https://github.com/gradle/kotlin-dsl/issues/836">cough, cough, over here</a> we see that we can 
just add a <code class="language-plaintext highlighter-rouge">TestListener</code> and override the <code class="language-plaintext highlighter-rouge">afterSuite</code> method (which in fact is what the closure was doing):</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*...*/</span>

<span class="nf">tasks</span> <span class="p">{</span>

    <span class="nf">test</span> <span class="p">{</span>
    
        <span class="nf">useJUnitPlatform</span><span class="p">()</span>
        
        <span class="nf">addTestListener</span><span class="p">(</span><span class="kd">object</span> <span class="err">: </span><span class="nc">TestListener</span> <span class="p">{</span>
            <span class="k">override</span> <span class="k">fun</span> <span class="nf">beforeSuite</span><span class="p">(</span><span class="n">suite</span><span class="p">:</span> <span class="nc">TestDescriptor</span><span class="p">)</span> <span class="p">{}</span>
            <span class="k">override</span> <span class="k">fun</span> <span class="nf">beforeTest</span><span class="p">(</span><span class="n">testDescriptor</span><span class="p">:</span> <span class="nc">TestDescriptor</span><span class="p">)</span> <span class="p">{}</span>
            <span class="k">override</span> <span class="k">fun</span> <span class="nf">afterTest</span><span class="p">(</span><span class="n">testDescriptor</span><span class="p">:</span> <span class="nc">TestDescriptor</span><span class="p">,</span> <span class="n">result</span><span class="p">:</span> <span class="nc">TestResult</span><span class="p">)</span> <span class="p">{}</span>
    
            <span class="k">override</span> <span class="k">fun</span> <span class="nf">afterSuite</span><span class="p">(</span><span class="n">suite</span><span class="p">:</span> <span class="nc">TestDescriptor</span><span class="p">,</span> <span class="n">result</span><span class="p">:</span> <span class="nc">TestResult</span><span class="p">)</span> <span class="p">{</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">suite</span><span class="p">.</span><span class="n">parent</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// root suite</span>
                    <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="s">"----"</span><span class="p">)</span>
                    <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="s">"Test result: ${result.resultType}"</span><span class="p">)</span>
                    <span class="n">logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="s">"Test summary: ${result.testCount} tests, "</span> <span class="p">+</span>
                        <span class="s">"${result.successfulTestCount} succeeded, "</span> <span class="p">+</span>
                        <span class="s">"${result.failedTestCount} failed, "</span> <span class="p">+</span>
                        <span class="s">"${result.skippedTestCount} skipped"</span><span class="p">)</span>
    
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">})</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>It is a pity that we don’t have an adapter with default empty implementations and we need to implement empty methods but
this also opens the doors to some evolution in our build setup.</p>

<p>First we introduced the logger object, that the Kotlin DSL for Gradle exposes so we can log events.
We have <code class="language-plaintext highlighter-rouge">debug</code>, <code class="language-plaintext highlighter-rouge">info</code> and all the familiar <code class="language-plaintext highlighter-rouge">LOG_LEVELS</code> but there is also an interesting log level called <code class="language-plaintext highlighter-rouge">LIFECYCLE</code>.</p>

<p>Since we (arguably) think that test results do not belong to <code class="language-plaintext highlighter-rouge">info</code> level but <code class="language-plaintext highlighter-rouge">lifecycle</code> instead, we will redefine 
the level we are logging test events.</p>

<p>We need to remember to properly configure the new lifecycle level we are using:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*...*/</span>

<span class="nf">tasks</span> <span class="p">{</span>

    <span class="nf">test</span> <span class="p">{</span>
    
        <span class="nf">useJUnitPlatform</span><span class="p">()</span>
    
        <span class="nf">testLogging</span> <span class="p">{</span>
            <span class="nf">lifecycle</span> <span class="p">{</span>
                <span class="n">events</span> <span class="p">=</span> <span class="nf">mutableSetOf</span><span class="p">(</span><span class="nc">TestLogEvent</span><span class="p">.</span><span class="nc">FAILED</span><span class="p">,</span> <span class="nc">TestLogEvent</span><span class="p">.</span><span class="nc">PASSED</span><span class="p">,</span> <span class="nc">TestLogEvent</span><span class="p">.</span><span class="nc">SKIPPED</span><span class="p">)</span>
                <span class="n">exceptionFormat</span> <span class="p">=</span> <span class="nc">TestExceptionFormat</span><span class="p">.</span><span class="nc">FULL</span>
                <span class="n">showExceptions</span> <span class="p">=</span> <span class="k">true</span>
                <span class="n">showCauses</span> <span class="p">=</span> <span class="k">true</span>
                <span class="n">showStackTraces</span> <span class="p">=</span> <span class="k">true</span>
                <span class="n">showStandardStreams</span> <span class="p">=</span> <span class="k">true</span>
            <span class="p">}</span>
            <span class="n">info</span><span class="p">.</span><span class="n">events</span> <span class="p">=</span> <span class="n">lifecycle</span><span class="p">.</span><span class="n">events</span>
            <span class="n">info</span><span class="p">.</span><span class="n">exceptionFormat</span> <span class="p">=</span> <span class="n">lifecycle</span><span class="p">.</span><span class="n">exceptionFormat</span>
        <span class="p">}</span>
    
        <span class="c1">// See https://github.com/gradle/kotlin-dsl/issues/836</span>
        <span class="nf">addTestListener</span><span class="p">(</span><span class="kd">object</span> <span class="err">: </span><span class="nc">TestListener</span> <span class="p">{</span>
            <span class="k">override</span> <span class="k">fun</span> <span class="nf">beforeSuite</span><span class="p">(</span><span class="n">suite</span><span class="p">:</span> <span class="nc">TestDescriptor</span><span class="p">)</span> <span class="p">{}</span>
            <span class="k">override</span> <span class="k">fun</span> <span class="nf">beforeTest</span><span class="p">(</span><span class="n">testDescriptor</span><span class="p">:</span> <span class="nc">TestDescriptor</span><span class="p">)</span> <span class="p">{}</span>
            <span class="k">override</span> <span class="k">fun</span> <span class="nf">afterTest</span><span class="p">(</span><span class="n">testDescriptor</span><span class="p">:</span> <span class="nc">TestDescriptor</span><span class="p">,</span> <span class="n">result</span><span class="p">:</span> <span class="nc">TestResult</span><span class="p">)</span> <span class="p">{}</span>
    
            <span class="k">override</span> <span class="k">fun</span> <span class="nf">afterSuite</span><span class="p">(</span><span class="n">suite</span><span class="p">:</span> <span class="nc">TestDescriptor</span><span class="p">,</span> <span class="n">result</span><span class="p">:</span> <span class="nc">TestResult</span><span class="p">)</span> <span class="p">{</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">suite</span><span class="p">.</span><span class="n">parent</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// root suite</span>
                    <span class="n">logger</span><span class="p">.</span><span class="nf">lifecycle</span><span class="p">(</span><span class="s">"----"</span><span class="p">)</span>
                    <span class="n">logger</span><span class="p">.</span><span class="nf">lifecycle</span><span class="p">(</span><span class="s">"Test result: ${result.resultType}"</span><span class="p">)</span>
                    <span class="n">logger</span><span class="p">.</span><span class="nf">lifecycle</span><span class="p">(</span><span class="s">"Test summary: ${result.testCount} tests, "</span> <span class="p">+</span>
                        <span class="s">"${result.successfulTestCount} succeeded, "</span> <span class="p">+</span>
                        <span class="s">"${result.failedTestCount} failed, "</span> <span class="p">+</span>
                        <span class="s">"${result.skippedTestCount} skipped"</span><span class="p">)</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">})</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Second, we can add some new logging details by overriding other functions. Let’s do a more detailed summary of failed 
and skipped tests:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*...*/</span>

<span class="nf">tasks</span> <span class="p">{</span>

    <span class="nf">test</span> <span class="p">{</span>
        
        <span class="cm">/*...*/</span>
                
        <span class="kd">val</span> <span class="py">failedTests</span> <span class="p">=</span> <span class="n">mutableListOf</span><span class="p">&lt;</span><span class="nc">TestDescriptor</span><span class="p">&gt;()</span>
        <span class="kd">val</span> <span class="py">skippedTests</span> <span class="p">=</span> <span class="n">mutableListOf</span><span class="p">&lt;</span><span class="nc">TestDescriptor</span><span class="p">&gt;()</span>
    
        <span class="c1">// See https://github.com/gradle/kotlin-dsl/issues/836</span>
        <span class="nf">addTestListener</span><span class="p">(</span><span class="kd">object</span> <span class="err">: </span><span class="nc">TestListener</span> <span class="p">{</span>
            <span class="k">override</span> <span class="k">fun</span> <span class="nf">beforeSuite</span><span class="p">(</span><span class="n">suite</span><span class="p">:</span> <span class="nc">TestDescriptor</span><span class="p">)</span> <span class="p">{}</span>
            <span class="k">override</span> <span class="k">fun</span> <span class="nf">beforeTest</span><span class="p">(</span><span class="n">testDescriptor</span><span class="p">:</span> <span class="nc">TestDescriptor</span><span class="p">)</span> <span class="p">{}</span>
            <span class="k">override</span> <span class="k">fun</span> <span class="nf">afterTest</span><span class="p">(</span><span class="n">testDescriptor</span><span class="p">:</span> <span class="nc">TestDescriptor</span><span class="p">,</span> <span class="n">result</span><span class="p">:</span> <span class="nc">TestResult</span><span class="p">)</span> <span class="p">{</span>
                <span class="k">when</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">resultType</span><span class="p">)</span> <span class="p">{</span>
                    <span class="nc">TestResult</span><span class="p">.</span><span class="nc">ResultType</span><span class="p">.</span><span class="nc">FAILURE</span> <span class="p">-&gt;</span> <span class="n">failedTests</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">testDescriptor</span><span class="p">)</span>
                    <span class="nc">TestResult</span><span class="p">.</span><span class="nc">ResultType</span><span class="p">.</span><span class="nc">SKIPPED</span> <span class="p">-&gt;</span> <span class="n">skippedTests</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">testDescriptor</span><span class="p">)</span>
                <span class="p">}</span>
            <span class="p">}</span>
    
            <span class="k">override</span> <span class="k">fun</span> <span class="nf">afterSuite</span><span class="p">(</span><span class="n">suite</span><span class="p">:</span> <span class="nc">TestDescriptor</span><span class="p">,</span> <span class="n">result</span><span class="p">:</span> <span class="nc">TestResult</span><span class="p">)</span> <span class="p">{</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">suite</span><span class="p">.</span><span class="n">parent</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// root suite</span>
                    <span class="n">logger</span><span class="p">.</span><span class="nf">lifecycle</span><span class="p">(</span><span class="s">"----"</span><span class="p">)</span>
                    <span class="n">logger</span><span class="p">.</span><span class="nf">lifecycle</span><span class="p">(</span><span class="s">"Test result: ${result.resultType}"</span><span class="p">)</span>
                    <span class="n">logger</span><span class="p">.</span><span class="nf">lifecycle</span><span class="p">(</span><span class="s">"Test summary: ${result.testCount} tests, "</span> <span class="p">+</span>
                        <span class="s">"${result.successfulTestCount} succeeded, "</span> <span class="p">+</span>
                        <span class="s">"${result.failedTestCount} failed, "</span> <span class="p">+</span>
                        <span class="s">"${result.skippedTestCount} skipped"</span><span class="p">)</span>
                    <span class="k">if</span> <span class="p">(</span><span class="n">failedTests</span><span class="p">.</span><span class="nf">isNotEmpty</span><span class="p">())</span> <span class="p">{</span>
                        <span class="n">logger</span><span class="p">.</span><span class="nf">lifecycle</span><span class="p">(</span><span class="s">"\tFailed Tests:"</span><span class="p">)</span>
                        <span class="n">failedTests</span><span class="p">.</span><span class="nf">forEach</span> <span class="p">{</span>
                            <span class="n">parent</span><span class="o">?.</span><span class="nf">let</span> <span class="p">{</span> <span class="n">parent</span> <span class="p">-&gt;</span>
                                <span class="n">logger</span><span class="p">.</span><span class="nf">lifecycle</span><span class="p">(</span><span class="s">"\t\t${parent.name} - ${it.name}"</span><span class="p">)</span>
                            <span class="p">}</span> <span class="o">?:</span> <span class="n">logger</span><span class="p">.</span><span class="nf">lifecycle</span><span class="p">(</span><span class="s">"\t\t${it.name}"</span><span class="p">)</span>
                        <span class="p">}</span>
                    <span class="p">}</span>
    
                    <span class="k">if</span> <span class="p">(</span><span class="n">skippedTests</span><span class="p">.</span><span class="nf">isNotEmpty</span><span class="p">())</span> <span class="p">{</span>
                        <span class="n">logger</span><span class="p">.</span><span class="nf">lifecycle</span><span class="p">(</span><span class="s">"\tSkipped Tests:"</span><span class="p">)</span>
                        <span class="n">skippedTests</span><span class="p">.</span><span class="nf">forEach</span> <span class="p">{</span>
                            <span class="n">parent</span><span class="o">?.</span><span class="nf">let</span> <span class="p">{</span> <span class="n">parent</span> <span class="p">-&gt;</span>
                                <span class="n">logger</span><span class="p">.</span><span class="nf">lifecycle</span><span class="p">(</span><span class="s">"\t\t${parent.name} - ${it.name}"</span><span class="p">)</span>
                            <span class="p">}</span> <span class="o">?:</span> <span class="n">logger</span><span class="p">.</span><span class="nf">lifecycle</span><span class="p">(</span><span class="s">"\t\t${it.name}"</span><span class="p">)</span>
                        <span class="p">}</span>
                    <span class="p">}</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">})</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>First we declare to mutableLists, one for failed and the other one for skipped tests. Then we check what kind of 
<code class="language-plaintext highlighter-rouge">TestResult</code> we are getting and add the <code class="language-plaintext highlighter-rouge">TestDescriptor</code> to the right list. One good thing from Kotlin scripting is that
we can omit the <code class="language-plaintext highlighter-rouge">else -&gt;</code> in the enum branching.</p>

<p>However, being able to use Kotlin, we can improve somehow the script with all the fancy Kotlin features.</p>

<p>For example, we can use extension functions to make the code more readable.</p>

<p>Also, since we are going to add some linter capabilities, we might want to adhere to best practices and use proper 
<code class="language-plaintext highlighter-rouge">when</code> branching and include the <code class="language-plaintext highlighter-rouge">ELSE</code> fallback.</p>

<p>How does our example <code class="language-plaintext highlighter-rouge">build.gradle.kts</code> look after all the optimizations?</p>

<ol>
  <li>We added some checkstyle, spotless and linter configurations as well as IntelliJ IDEA plugin.</li>
  <li>We setup some configuration options for the plugins above.</li>
  <li>We refactored how we log to use a functional approach and Kotlin features.</li>
  <li>Since we love testing with <code class="language-plaintext highlighter-rouge">ParameterizedTests</code>, we added that bit for JUnit too.</li>
  <li>Finally, we used the same approach than on the <code class="language-plaintext highlighter-rouge">Test</code> task customization to customize the Kotlin compile options.</li>
  <li>As a bonus, we are passing spotless / checkstyle to all the <code class="language-plaintext highlighter-rouge">Gradle.kts</code> files in the <code class="language-plaintext highlighter-rouge">KotlinGradle</code> task in the 
spotless configuration.</li>
</ol>

<p>The end result is:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nn">org.gradle.api.tasks.testing.logging.TestExceptionFormat</span>
<span class="k">import</span> <span class="nn">org.gradle.api.tasks.testing.logging.TestLogEvent</span>

<span class="nf">repositories</span> <span class="p">{</span>
    <span class="nf">mavenCentral</span><span class="p">()</span>
<span class="p">}</span>

<span class="nf">plugins</span> <span class="p">{</span>
    <span class="nf">kotlin</span><span class="p">(</span><span class="s">"jvm"</span><span class="p">)</span> <span class="n">version</span> <span class="s">"1.3.40"</span>
    <span class="nf">id</span><span class="p">(</span><span class="s">"com.diffplug.gradle.spotless"</span><span class="p">)</span> <span class="n">version</span> <span class="s">"3.23.1"</span>
    <span class="nf">id</span><span class="p">(</span><span class="s">"org.jmailen.kotlinter"</span><span class="p">)</span> <span class="n">version</span> <span class="s">"1.26.0"</span>
    <span class="n">checkstyle</span>
    <span class="n">idea</span>
<span class="p">}</span>

<span class="nf">idea</span> <span class="p">{</span>
    <span class="nf">module</span> <span class="p">{</span>
        <span class="n">isDownloadJavadoc</span> <span class="p">=</span> <span class="k">true</span>
        <span class="n">isDownloadSources</span> <span class="p">=</span> <span class="k">true</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="n">tasks</span><span class="p">.</span><span class="nf">checkstyleMain</span> <span class="p">{</span> <span class="n">group</span> <span class="p">=</span> <span class="s">"verification"</span> <span class="p">}</span>
<span class="n">tasks</span><span class="p">.</span><span class="nf">checkstyleTest</span> <span class="p">{</span> <span class="n">group</span> <span class="p">=</span> <span class="s">"verification"</span> <span class="p">}</span>

<span class="nf">spotless</span> <span class="p">{</span>
    <span class="nf">kotlin</span> <span class="p">{</span>
        <span class="nf">ktlint</span><span class="p">()</span>
    <span class="p">}</span>
    <span class="nf">kotlinGradle</span> <span class="p">{</span>
        <span class="nf">target</span><span class="p">(</span><span class="nf">fileTree</span><span class="p">(</span><span class="n">projectDir</span><span class="p">).</span><span class="nf">apply</span> <span class="p">{</span>
            <span class="nf">include</span><span class="p">(</span><span class="s">"*.gradle.kts"</span><span class="p">)</span>
        <span class="p">}</span> <span class="p">+</span> <span class="nf">fileTree</span><span class="p">(</span><span class="s">"src"</span><span class="p">).</span><span class="nf">apply</span> <span class="p">{</span>
            <span class="nf">include</span><span class="p">(</span><span class="s">"**/*.gradle.kts"</span><span class="p">)</span>
        <span class="p">})</span>
        <span class="nf">ktlint</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="nf">dependencies</span> <span class="p">{</span>
    <span class="nf">implementation</span><span class="p">(</span><span class="nf">kotlin</span><span class="p">(</span><span class="s">"stdlib-jdk8"</span><span class="p">))</span>

    <span class="nf">testImplementation</span><span class="p">(</span><span class="s">"io.mockk:mockk:1.9.3"</span><span class="p">)</span>
    <span class="nf">testImplementation</span><span class="p">(</span><span class="s">"org.assertj:assertj-core:3.11.1"</span><span class="p">)</span>
    <span class="nf">testImplementation</span><span class="p">(</span><span class="s">"org.junit.jupiter:junit-jupiter-api:5.4.2"</span><span class="p">)</span>
    <span class="nf">testImplementation</span><span class="p">(</span><span class="s">"org.junit.jupiter:junit-jupiter-params:5.4.2"</span><span class="p">)</span>

    <span class="nf">testRuntimeOnly</span><span class="p">(</span><span class="s">"org.junit.jupiter:junit-jupiter-engine:5.4.2"</span><span class="p">)</span>
<span class="p">}</span>

<span class="n">tasks</span><span class="p">.</span><span class="nf">compileKotlin</span> <span class="p">{</span>
    <span class="nf">kotlinOptions</span> <span class="p">{</span>
        <span class="n">jvmTarget</span> <span class="p">=</span> <span class="s">"1.8"</span>
        <span class="n">javaParameters</span> <span class="p">=</span> <span class="k">true</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="n">tasks</span><span class="p">.</span><span class="nf">test</span> <span class="p">{</span>
    <span class="nf">useJUnitPlatform</span><span class="p">()</span>

    <span class="nf">testLogging</span> <span class="p">{</span>
        <span class="nf">lifecycle</span> <span class="p">{</span>
            <span class="n">events</span> <span class="p">=</span> <span class="nf">mutableSetOf</span><span class="p">(</span><span class="nc">TestLogEvent</span><span class="p">.</span><span class="nc">FAILED</span><span class="p">,</span> <span class="nc">TestLogEvent</span><span class="p">.</span><span class="nc">PASSED</span><span class="p">,</span> <span class="nc">TestLogEvent</span><span class="p">.</span><span class="nc">SKIPPED</span><span class="p">)</span>
            <span class="n">exceptionFormat</span> <span class="p">=</span> <span class="nc">TestExceptionFormat</span><span class="p">.</span><span class="nc">FULL</span>
            <span class="n">showExceptions</span> <span class="p">=</span> <span class="k">true</span>
            <span class="n">showCauses</span> <span class="p">=</span> <span class="k">true</span>
            <span class="n">showStackTraces</span> <span class="p">=</span> <span class="k">true</span>
            <span class="n">showStandardStreams</span> <span class="p">=</span> <span class="k">true</span>
        <span class="p">}</span>
        <span class="n">info</span><span class="p">.</span><span class="n">events</span> <span class="p">=</span> <span class="n">lifecycle</span><span class="p">.</span><span class="n">events</span>
        <span class="n">info</span><span class="p">.</span><span class="n">exceptionFormat</span> <span class="p">=</span> <span class="n">lifecycle</span><span class="p">.</span><span class="n">exceptionFormat</span>
    <span class="p">}</span>

    <span class="kd">val</span> <span class="py">failedTests</span> <span class="p">=</span> <span class="n">mutableListOf</span><span class="p">&lt;</span><span class="nc">TestDescriptor</span><span class="p">&gt;()</span>
    <span class="kd">val</span> <span class="py">skippedTests</span> <span class="p">=</span> <span class="n">mutableListOf</span><span class="p">&lt;</span><span class="nc">TestDescriptor</span><span class="p">&gt;()</span>

    <span class="c1">// See https://github.com/gradle/kotlin-dsl/issues/836</span>
    <span class="nf">addTestListener</span><span class="p">(</span><span class="kd">object</span> <span class="err">: </span><span class="nc">TestListener</span> <span class="p">{</span>
        <span class="k">override</span> <span class="k">fun</span> <span class="nf">beforeSuite</span><span class="p">(</span><span class="n">suite</span><span class="p">:</span> <span class="nc">TestDescriptor</span><span class="p">)</span> <span class="p">{}</span>
        <span class="k">override</span> <span class="k">fun</span> <span class="nf">beforeTest</span><span class="p">(</span><span class="n">testDescriptor</span><span class="p">:</span> <span class="nc">TestDescriptor</span><span class="p">)</span> <span class="p">{}</span>
        <span class="k">override</span> <span class="k">fun</span> <span class="nf">afterTest</span><span class="p">(</span><span class="n">testDescriptor</span><span class="p">:</span> <span class="nc">TestDescriptor</span><span class="p">,</span> <span class="n">result</span><span class="p">:</span> <span class="nc">TestResult</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">when</span> <span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">resultType</span><span class="p">)</span> <span class="p">{</span>
                <span class="nc">TestResult</span><span class="p">.</span><span class="nc">ResultType</span><span class="p">.</span><span class="nc">FAILURE</span> <span class="p">-&gt;</span> <span class="n">failedTests</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">testDescriptor</span><span class="p">)</span>
                <span class="nc">TestResult</span><span class="p">.</span><span class="nc">ResultType</span><span class="p">.</span><span class="nc">SKIPPED</span> <span class="p">-&gt;</span> <span class="n">skippedTests</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">testDescriptor</span><span class="p">)</span>
                <span class="k">else</span> <span class="p">-&gt;</span> <span class="nc">Unit</span>
            <span class="p">}</span>
        <span class="p">}</span>

        <span class="k">override</span> <span class="k">fun</span> <span class="nf">afterSuite</span><span class="p">(</span><span class="n">suite</span><span class="p">:</span> <span class="nc">TestDescriptor</span><span class="p">,</span> <span class="n">result</span><span class="p">:</span> <span class="nc">TestResult</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">suite</span><span class="p">.</span><span class="n">parent</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// root suite</span>
                <span class="n">logger</span><span class="p">.</span><span class="nf">lifecycle</span><span class="p">(</span><span class="s">"----"</span><span class="p">)</span>
                <span class="n">logger</span><span class="p">.</span><span class="nf">lifecycle</span><span class="p">(</span><span class="s">"Test result: ${result.resultType}"</span><span class="p">)</span>
                <span class="n">logger</span><span class="p">.</span><span class="nf">lifecycle</span><span class="p">(</span>
                        <span class="s">"Test summary: ${result.testCount} tests, "</span> <span class="p">+</span>
                        <span class="s">"${result.successfulTestCount} succeeded, "</span> <span class="p">+</span>
                        <span class="s">"${result.failedTestCount} failed, "</span> <span class="p">+</span>
                        <span class="s">"${result.skippedTestCount} skipped"</span><span class="p">)</span>
                <span class="n">failedTests</span><span class="p">.</span><span class="nf">takeIf</span> <span class="p">{</span> <span class="n">it</span><span class="p">.</span><span class="nf">isNotEmpty</span><span class="p">()</span> <span class="p">}</span><span class="o">?.</span><span class="nf">prefixedSummary</span><span class="p">(</span><span class="s">"\tFailed Tests"</span><span class="p">)</span>
                <span class="n">skippedTests</span><span class="p">.</span><span class="nf">takeIf</span> <span class="p">{</span> <span class="n">it</span><span class="p">.</span><span class="nf">isNotEmpty</span><span class="p">()</span> <span class="p">}</span><span class="o">?.</span><span class="nf">prefixedSummary</span><span class="p">(</span><span class="s">"\tSkipped Tests:"</span><span class="p">)</span>
            <span class="p">}</span>
        <span class="p">}</span>

        <span class="k">private</span> <span class="k">infix</span> <span class="k">fun</span> <span class="nf">List</span><span class="p">&lt;</span><span class="nc">TestDescriptor</span><span class="p">&gt;.</span><span class="nf">prefixedSummary</span><span class="p">(</span><span class="n">subject</span><span class="p">:</span> <span class="nc">String</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">logger</span><span class="p">.</span><span class="nf">lifecycle</span><span class="p">(</span><span class="n">subject</span><span class="p">)</span>
                <span class="nf">forEach</span> <span class="p">{</span> <span class="n">test</span> <span class="p">-&gt;</span> <span class="n">logger</span><span class="p">.</span><span class="nf">lifecycle</span><span class="p">(</span><span class="s">"\t\t${test.displayName()}"</span><span class="p">)</span> <span class="p">}</span>
        <span class="p">}</span>

        <span class="k">private</span> <span class="k">fun</span> <span class="nc">TestDescriptor</span><span class="p">.</span><span class="nf">displayName</span><span class="p">()</span> <span class="p">=</span> <span class="n">parent</span><span class="o">?.</span><span class="nf">let</span> <span class="p">{</span> <span class="s">"${it.name} - $name"</span> <span class="p">}</span> <span class="o">?:</span> <span class="s">"$name"</span>
    <span class="p">})</span>
<span class="p">}</span>

<span class="c1">// cleanTest task doesn't have an accessor on tasks (when this blog post was written)</span>
<span class="n">tasks</span><span class="p">.</span><span class="nf">named</span><span class="p">(</span><span class="s">"cleanTest"</span><span class="p">)</span> <span class="p">{</span> <span class="n">group</span> <span class="p">=</span> <span class="s">"verification"</span> <span class="p">}</span>
</code></pre></div></div>

<h2 id="gradle-test-sample-output">Gradle <code class="language-plaintext highlighter-rouge">test</code> sample output</h2>
<p>Running <code class="language-plaintext highlighter-rouge">./gradlew clean build</code> on a sample project, we can see that our Gradle project is working, hopefully we won’t
get any checkstyle or spotless violations and all test will be green.</p>

<p>What is the output if some tests are disabled or failing?</p>

<p>See this little cropped example:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>...

&gt; Task :test

Error!
org.opentest4j.AssertionFailedError: Error!
	at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:43)
	at org.junit.jupiter.api.Assertions.fail(Assertions.java:129)
	at org.junit.jupiter.api.AssertionsKt.fail(Assertions.kt:24)
	at org.junit.jupiter.api.AssertionsKt.fail$default(Assertions.kt:23)
	at org.lastminute.blog.examples.gradlekts.UnitTest.always failing test()(UnitTest.kt:24)
    ...
    

org.lastminute.blog.examples.gradlekts.UnitTest &gt; always failing test() FAILED
    org.opentest4j.AssertionFailedError: Error!
        at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:43)
        at org.junit.jupiter.api.Assertions.fail(Assertions.java:129)
        at org.junit.jupiter.api.AssertionsKt.fail(Assertions.kt:24)
        at org.junit.jupiter.api.AssertionsKt.fail$default(Assertions.kt:23)
        at org.lastminute.blog.examples.gradlekts.UnitTest.always failing test()(UnitTest.kt:24)
org.lastminute.blog.examples.gradlekts.UnitTest &gt; disabled test() SKIPPED
org.lastminute.blog.examples.gradlekts.UnitTest &gt; always passing test() PASSED
----
Test result: FAILURE
Test summary: 3 tests, 1 succeeded, 1 failed, 1 skipped
	Failed Tests
		org.lastminute.blog.examples.gradlekts.UnitTest - always failing test()
	Skipped Tests:
		org.lastminute.blog.examples.gradlekts.UnitTest - disabled test()
1 test completed, 1 failed, 1 skipped
&gt; Task :test FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':test'.
...
</code></pre></div></div>

<h1 id="conclusion">Conclusion</h1>

<p>Gradle’s Kotlin DSL comes with a change of mindset when writing your build script.
Kotlin is a type safe language which means that we need to be more strict now on our scripts, specially when migrating
Groovy ones.</p>

<p>It is really great to be able to use Kotlin in our build scripts, since we can easily extend or implement new Tasks with
few lines of code.
Some engineers fear build scripts, specially if they are not comfortable with the language used, and
having the opportunity to use the very same language you use everyday on your build scripts lowers the entry barrier for
them.</p>

<p>Even JUnit 5 has a nice 
<a href="https://junit.org/junit5/docs/current/user-guide/#running-tests-console-launcher">console launcher</a> for building 
reports (on CI or manually), we engineers like to see a summary of what went wrong and we were missing that option with
the new JUnit Engine on Gradle. Fortunately, with this entry we have improved our initial scripts and been able not only
to summarize how the Test task was but also display which tests failed (or were skipped)</p>

<p><em>Originally published in <a href="https://technology.lastminute.com/junit5-kotlin-and-gradle-dsl"><img src="/assets/images/lastminute/LM_Full_Pink-small.jpg" alt="lastminute.com logo" />Technology Blog</a></em></p>]]></content><author><name>Juan Ara</name></author><category term="Software Engineering" /><category term="tdd" /><category term="kotlin" /><category term="gradle" /><category term="gradle5" /><category term="junit" /><category term="junit5" /><category term="kts" /><category term="lastminute.com" /><summary type="html"><![CDATA[Gradle 5 has been out for a while now and with that we finally got the ability to write our Gradle scripts in Kotlin. How can I migrate my JUnit-enabled Gradle scripts to Kotlin Gradle DSL?]]></summary></entry><entry><title type="html">Refactoring the coding interview</title><link href="https://www.cornerinthemiddle.com/software%20engineering/coaching/Refactoring-the-coding-interview/" rel="alternate" type="text/html" title="Refactoring the coding interview" /><published>2019-07-19T00:00:00+02:00</published><updated>2019-07-19T00:00:00+02:00</updated><id>https://www.cornerinthemiddle.com/software%20engineering/coaching/Refactoring-the-coding-interview</id><content type="html" xml:base="https://www.cornerinthemiddle.com/software%20engineering/coaching/Refactoring-the-coding-interview/"><![CDATA[<h1 id="being-interviewed-senior-developers-and-code-assignments">Being interviewed: Senior developers and code assignments</h1>

<p>I have switched jobs recently, and I found myself doing some interviews.
I have never been nervous nor feared interview processes as I tend to approach them with a learning attitude, 
both as interviewer and interviewee. However, I found some code assignments too demanding for some candidates.</p>

<p>I have seen a lot of bloggers on the last three months posting (or complaining) about code assignments in the interview 
process so, why am I also blogging about this?
Because I will be talking from my recent experience about what I enjoyed, disliked and loved and what I am looking for 
when I’m being interviewed.</p>

<hr />

<p>Companies need filters, because we might get thousands of applications per posting, and with some tools like Greenhouse, 
Freshworks or Lever, we can automate part of the process, where any candidate is sent directly to one of those 
screenings.
But what is the appropriate screening process for candidates?</p>

<p>Basic algorithms or programming assignments on Hackerrank or similar platforms are a good filter for entry level 
positions but only adds some time delays on more senior candidates.
Good candidates that are actively looking will take less than one month to find a new job and companies want to move
faster than their competitors on this high-demanding environment, so any delay on the process can make a company to lose
an opportunity.</p>

<h1 id="when-you-reach-some-level-of-seniority-it-is-not-selling-yourself-anymore-you-are-alsobuying"><em>When you reach some level of seniority, it is not selling yourself anymore. You are also buying.</em></h1>

<p>When I was younger I faced all my interviews trying to sell myself, to rise above the rest of the candidates.
I wanted to get a job and learn, so I almost did my research outside of the interview process.
I was the one applying to jobs, so I was the one who felt <em>scrutinized.</em></p>

<p>As I have acquired more skills and experience, I have also noticed new habits when I look for new opportunities.
I rarely browse for openings but either contact some trusted recruiters, use my contacts network or directly,
write HR managers on the companies I might be interested in. 
But, during the interviews, I am the one <em>scrutinizing</em> them.</p>

<h1 id="the-interview-process">The interview process</h1>

<p>I did that with Thoughtworks and lastminute.com last month, as well as replied to some recruiters that were contacting
me.</p>

<p><img src="/assets/images/posts/Refactoring-the-coding-interview/photo-1573019606806-9695d0a9739d-ldpi.jpg" alt="image-left" class="align-left" />
I discarded a lot of interviews when the process felt copy — pasted.
Those recruiters that took time to talk with me and see if we were aligned got me in the process,
while those with automated pipelines sending me standard emails got discarded almost immediately.
Not to talk about those taking two or more weeks to reply to an email…</p>

<p>Seniors, leaders, or managers will rarely be applying to positions but positions will be offered to them, 
so having them to go through a long process without feedback or previous information is almost instant <strong>no</strong> from them.</p>

<p>Also, there is a relation between experience and age, and that often implies that senior candidates potentially have 
families.
This, tied with sometimes very high demanding responsibilities, make difficult for the applicants to find time to do 
the coding challenge. 
Who can spend a weekend coding for an interview after fifty hours working without seeing the family?</p>

<p>We, candidates are not just selling ourselves but also buying companies.</p>

<p>We don’t get a job, we <strong>join a company, follow a leader or work with a team.</strong></p>

<h2 id="but-i-need-to-know-if-you-cancode">But I need to know if you can code!</h2>

<p>Exactly! But not only that! You need to know if I can solve problems within your team. If I can understand your code,
improve it, fix things.
And doing an assignment at home, does often not prove that.</p>

<p>I have had candidates leaving a process because the assignment was going to take much time, or they were not allowed to
use the language they felt more comfortable with,
or candidates failing a code screen because they did not know the estimated amount of time or number of questions in
advance and could not plan according.
This even happened to me on a process, doing the assignment with low bandwidth(took 15 minutes to upload a 45 minutes
typing session) when after finishing the upload a new question appeared on the wild, no prior notice… leaving my time-box
completely blown.</p>

<p>Let’s walk through types of screens / interviews and a basic list cons and pros for companies and candidates based on 
<strong><em>resources/time dedicated to the interview</em></strong>, how prone to <strong><em>keep the interest of the candidate</em></strong> the process is 
and how easy is to <strong><em>validate software engineering skills</em></strong> <strong><em>or other soft skills</em></strong>.</p>

<h3 id="the-technical-filter">The technical filter</h3>

<p>The technical filter feels like a quiz, not an interview. In this kind of filter, interviewer asks several questions to 
the candidate, no matter how comfortable he or she is feeling. 
This is a <em>company-buying-workforce</em> type of interview. 
The candidate is not usually engaged in a conversation, and it requires a senior engineer with people soft skills to do 
it right (to make it more like a conversation or a two-way interview).
Most companies have this filter earlier on the process and more than often it is the very first conversation between 
the company and the new possible hire.</p>

<p><strong>PROS</strong></p>

<ul>
  <li>It helps a lot to filter out unaligned candidates (technically).</li>
</ul>

<p><strong>CONS</strong></p>

<ul>
  <li>It requires a senior engineer to perform the interview.</li>
  <li>Candidate might feel like in an exam.
Senior candidates need to buy the company and this process does not give them much information about it.</li>
</ul>

<h3 id="the-codingscreen">The coding screen</h3>

<p>Here, the candidate is given a time constrained series of problems in a tool or service like Hackerrank.
Usually, algorithms, data-structures or database knowledge is tested but can grow as complex as the company needs.</p>

<p><strong>PROS</strong></p>

<ul>
  <li>For the company, it helps out to filter junior level engineers.</li>
  <li>Fast to give feedback to candidates.</li>
  <li>No need of own engineers to get involved in the process at this stage as recruiters themselves can direct the
candidates to the exercises.</li>
</ul>

<p><strong>CONS</strong></p>

<ul>
  <li>Senior candidates might find the problems too easy and loose interest in the job or reject to invest time without 
some calls first.</li>
  <li>The candidate might have a bad day, connection issues or just choose a bad solution with no reaction time to adapt.
This might lead to loosing qualified candidates too early on the process.</li>
</ul>

<h3 id="the-home-assignment">The home assignment</h3>

<p>Similar to the code assignment above, the home assignment just takes longer, needs some research or specific knowledge
and engages the candidates with more complete solutions.</p>

<p>I have seen this kind of filter too often lately.
I have been asked to spend a significant amount of time working on a problem, posting the code to a repository and then
engaging with part of the company’s engineering team in an offline conversation via merge requests.</p>

<p><strong>PROS</strong></p>

<ul>
  <li>Gathers a lot of information about how the candidate approaches a given problem (research, architecture,
reading existing code if a template project is given to him or her…).</li>
  <li>Involves the candidate with the team, that is, helping him or her to understand how his or her future coworkers deal
with his or her code.</li>
</ul>

<p><strong>CONS</strong></p>

<ul>
  <li>The candidate might not feel comfortable enough with the toolset (i.e. asked to work on a given framework,
library or language).</li>
  <li>It might need a significant amount of time from the candidate (I have seen assignments that took a couple of full 
time days).</li>
</ul>

<h3 id="whiteboard--masterclass">Whiteboard / master class</h3>

<p>I was once this kind of interviewer (sorry!). There is a simple problem with a simple solution that can spawn a 
conversation about performance and several advanced topics.
Usually this problem falls in the comfort zone of the interviewer who can drive the conversation wherever she / he wants.</p>

<p><strong>PROS</strong></p>

<ul>
  <li>Gets to know the candidate.</li>
  <li>It is arguably easy to drive the candidate through different concepts / skill sets.</li>
</ul>

<p><strong>CONS</strong></p>

<ul>
  <li>Requires a lot of practice from the interviewer and a senior engineer to perform the interview.</li>
  <li>Can get the interviewee nervous if the interviewer misunderstands the skill set of the candidate and goes through 
difficult questions too early.</li>
  <li>Fairly subjective and not easy to <em>standardize</em>.</li>
</ul>

<h2 id="code-interviews-re-imagined">Code interviews re-imagined</h2>

<p>Why we do refactor our code but not our habits or other processes as frequently as our code? Code interviews have been 
like above for decades now. Sure we have new tools and reports, but the interviews itself keep being the same format.</p>

<p>This is how some processes have evolved in the last years:</p>

<h3 id="the-wip-assignment">The WIP assignment</h3>

<p>Similar to the assignment above, but way simpler and with one premise in mind: once finished, if the code passes to the 
next interview / stage, it will be refactored based on new constraints in a pair-programming session (see below)</p>

<p>This what I did at lastminute.com, a simple assignment that took around one hour to complete, using my language and 
tools of preference.</p>

<p><strong>PROS</strong></p>

<ul>
  <li>Leverages the home assignment to be shorter and easy, establishing a base for the next interview.</li>
  <li>Done properly leaves space to the candidate to make the choice on language and tools of his or her preference.</li>
  <li>Can also drop candidates if the code is not <em>good enough</em>.</li>
</ul>

<p><strong>CONS</strong></p>

<ul>
  <li>Still requires some effort from the candidate.</li>
  <li>Needs to be reviewed by some engineers (hopefully won’t take as much time as the full assignment).</li>
  <li>The company needs people with some experience in the language and framework the candidate might have chosen.</li>
</ul>

<h3 id="the-pair-programming-session">The pair-programming session</h3>

<p>At Thoughtworks, you get a pair-programming session.
There is a task you need to tackle with your buddy and during the session your buddy swaps with a new buddy.
You can choose the language of your preference, and you can use some external tools if needed.</p>

<p>This is also how the second part of the WIP assignment above works on other companies (i.e. lastminute.com) and to be 
honest, felt amazing to evolve your own code as part of the pairing session.</p>

<p>Other companies use a similar approach, for example, tackling a long-standing non important bug in real code together 
with the candidate and trying to solve as part of the onsite interview day.</p>

<p><strong>PROS</strong></p>

<ul>
  <li>Engages the candidate within the team. It is a pair-programming session, so the buddy is responsible also to help 
the candidate to solve the problems they face.</li>
  <li>Identifies not only coding skills but also other skill like analytical mindset, communication skills and / or 
working under pressure.</li>
  <li>Feedback is given by two or more interviewers, not just one (on the same interview).</li>
  <li>Senior enough candidates who architect the solution themselves feel owner of the proposed solution while junior 
candidates can follow their buddy’s guidelines.</li>
</ul>

<p><strong>CONS</strong></p>

<ul>
  <li>The company need to book two engineers to interview each candidate.</li>
</ul>

<h1 id="conclusion">Conclusion</h1>

<p>If I had to summarize this post in a TLDR it would be something like</p>

<blockquote>
  <p>Senior engineers do not need an interview filter. 
Their CV or LinkedIn profile is the filter and some reading and checking from recruiters or other engineers is 
mandatory prior to invite them to the onsite interview.</p>
</blockquote>

<p>I strongly agree with that statement (after all is mine), specially after a month of being interviewed almost every week. 
I enjoyed those processes that involved me, gave me feedback and were two-way communication lanes.
They required from me the same amount of effort they were putting in the process.</p>

<p>They never asked me for some time which did not involved a two-way communication and even when I had a WIP assignment, 
it was properly communicated and short (took less than one hour).
The interviews did not feel like exams, but conversations, and we were exchanging ideas, opinions and knowledge.
This is the right way to do the code interview, working together to solve a problem.</p>

<p>As a close <a href="https://www.linkedin.com/in/osangenis/">friend</a> once said to me:</p>

<blockquote>
  <p>Be aware: seniority is different from leadership.
Companies that are looking for senior engineers might be looking also for leaders, but that is not always the case.
Make sure both you and the company you are being interviewed at are aligned.</p>

  <p><strong><em>Seniority &lt;&gt; Leadership.</em></strong></p>

  <p>If you want more than a senior position, but the company has already people leading teams, you might not find space 
to grow and feel happy.</p>

</blockquote>

<p>And being happy at work makes the difference between stressed and ownership.</p>

<p><em>Originally published in</em> <a href="https://medium.com/@juan_ara/refactoring-the-coding-interview-32515dae5bca"><i class="fab fa-fw fa-medium"></i>Medium</a></p>]]></content><author><name>Juan Ara</name></author><category term="Software Engineering" /><category term="Coaching" /><category term="feedback" /><category term="culture" /><category term="mindset" /><category term="interview" /><summary type="html"><![CDATA[I switched jobs recently, and I found myself doing some interviews. Some code assignments are too demanding for some candidates. I'll talk from my recent experience about what I enjoyed, disliked and loved, and what I am looking for when I’m being interviewed.]]></summary></entry><entry><title type="html">Kotlin Elvis operator is not a ternary</title><link href="https://www.cornerinthemiddle.com/software%20engineering/Kotlin-Elvis-operator-is-not-a-ternary/" rel="alternate" type="text/html" title="Kotlin Elvis operator is not a ternary" /><published>2018-11-11T00:00:00+01:00</published><updated>2018-11-11T00:00:00+01:00</updated><id>https://www.cornerinthemiddle.com/software%20engineering/Kotlin-Elvis-operator-is-not-a-ternary</id><content type="html" xml:base="https://www.cornerinthemiddle.com/software%20engineering/Kotlin-Elvis-operator-is-not-a-ternary/"><![CDATA[<p>Last week, I was late at work because I had to take my motorbike to the garage.
As soon as I entered the room and even before I could change my shoes, some coworkers shouted:</p>

<blockquote>
  <p>- “Hey, over here, we’ve found a bug in Kotlin!”</p>
</blockquote>

<p>Curious as I am always, I headed over their desk, and I was asked:</p>

<blockquote>
  <p>- “What do you think this statement will out?”</p>
</blockquote>

<script src="https://gist.github.com/TarodBOFH/0b5f3920f4814666e9dc48881b0833f6.js"> </script>

<figcaption>Is this a bug?</figcaption>

<p>It’s a trap! I thought. I took a few moments and said something like “<em>at first sight, without any coffee yet… false, 
but I suspect that’s wrong because “you found a bug in Kotlin”, so, what does this output?</em>”.</p>

<p>That snippet, as you can see <a href="https://play.kotlinlang.org/#eyJ2ZXJzaW9uIjoiMS4zLVJDIiwicGxhdGZvcm0iOiJqYXZhIiwiYXJncyI6IiIsIm5vbmVNYXJrZXJzIjp0cnVlLCJ0aGVtZSI6ImlkZWEiLCJmb2xkZWRCdXR0b24iOnRydWUsInJlYWRPbmx5IjpmYWxzZSwiY29kZSI6Ii8qKlxuICogWW91IGNhbiBlZGl0LCBydW4sIGFuZCBzaGFyZSB0aGlzIGNvZGUuIFxuICogcGxheS5rb3RsaW5sYW5nLm9yZyBcbiAqL1xuXG5mdW4gbWFpbigpIHtcbiAgICBwcmludGxuKG51bGwgIT0gbnVsbCA/OiBmYWxzZSlcbn0ifQ==">here</a>, simply outputs <code class="language-plaintext highlighter-rouge">true</code>.</p>

<p>I looked again, and I got it. <strong>The Elvis </strong><code class="language-plaintext highlighter-rouge">**?:**</code> <strong>operator in Kotlin, is not a ternary!</strong> That fooled me completely!</p>

<p>The Elvis operator just takes whatever is on his left and replaces with whatever lies on his right if the left side is 
null.
That means that our expression in the first example becomes simply <code class="language-plaintext highlighter-rouge">println(null != false)</code> which obviously, is <code class="language-plaintext highlighter-rouge">true</code>.</p>

<p>Why? Not because <em>“operand precedence”</em>; the <em>Elvis</em> operator refers to it’s nearest variable.
It is not precedence! It is by design. 
Think it like <code class="language-plaintext highlighter-rouge">println(null != (null ?: false))</code> which translates exactly as the above result.
Heck, we could even be super-null-safe and write <code class="language-plaintext highlighter-rouge">println(null ?: false != null ?: false)</code> or any other variation.
It’s the design of the operator.</p>

<p>What’s the proper usage then, to avoid this weird confusion?
Simply, use Elvis either on simple return (either from functions or from blocks) and for variable assignation:</p>

<script src="https://gist.github.com/TarodBOFH/26339929378181883ffa7159e566d7c1.js"> </script>

<figcaption>This makes much more sense</figcaption>

<p>One funny thing about the example above is that we cannot assign <code class="language-plaintext highlighter-rouge">null</code> to <em>notNullableGuy</em> because the compiler
complains:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// notNullableGuy = null // we can't do this because   </span>
<span class="c1">// Null can not be a value of a non-null type Boolean</span>
</code></pre></div></div>
<p>This is a common mistake for java/javascript engineers who start to play with Kotlin. So follow these simple rules:</p>

<script src="https://gist.github.com/TarodBOFH/40dd67511140e249a60d6f40b9259d1a.js"> </script>

<figcaption>You can play around with that example <a href="https://pl.kotl.in/HkmPmQUam" data-href="https://pl.kotl.in/HkmPmQUam" class="markup--anchor markup--figure-anchor" rel="noopener" target="_blank">here</a>.</figcaption>

<p>These are some outputs from the snipped above:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>assignment <span class="nb">false  
</span>fromBlock <span class="nb">false  
</span>computed null  
safe-output is <span class="nb">false  
</span>null  
null  
<span class="nb">false  
true  
true  
false</span>
</code></pre></div></div>
<hr />
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>assignment <span class="nb">false  
</span>fromBlock <span class="nb">false  
</span>computed null  
safe-output is <span class="nb">false  
</span>null  
<span class="nb">false  
true  
true  
true  
false</span>
</code></pre></div></div>

<p>To read more about the Elvis operator and Kotlin null-safety features, just deep read <a href="https://kotlinlang.org/docs/reference/null-safety.html">Kotlin language reference about null-safety.</a></p>

<p><em>Originally published in</em> <a href="https://medium.com/@juan_ara/kotlin-elvis-operator-is-not-a-ternary-8f7af0eb15af"><i class="fab fa-fw fa-medium"></i>Medium</a></p>]]></content><author><name>Juan Ara</name></author><category term="Software Engineering" /><category term="kotlin" /><category term="elvis operator" /><category term="tips" /><category term="java" /><category term="ternary" /><summary type="html"><![CDATA[Last week, I was late at work and as soon as I entered the room, some coworkers shouted: “Hey, over here, we’ve found a bug in Kotlin!”]]></summary></entry><entry><title type="html">Setting up a Windows workstation for a 2018 technology stack</title><link href="https://www.cornerinthemiddle.com/software%20engineering/Setting-up-a-windows-workstation-for-a-2018-technology-stack/" rel="alternate" type="text/html" title="Setting up a Windows workstation for a 2018 technology stack" /><published>2018-10-26T00:00:00+02:00</published><updated>2018-10-26T00:00:00+02:00</updated><id>https://www.cornerinthemiddle.com/software%20engineering/Setting-up-a-windows-workstation-for-a-2018-technology-stack</id><content type="html" xml:base="https://www.cornerinthemiddle.com/software%20engineering/Setting-up-a-windows-workstation-for-a-2018-technology-stack/"><![CDATA[<blockquote>
  <p><strong>update 2020:</strong> Although the vast majority of stuff listed here still works on 2020, there is now <a href="https://www.theverge.com/2020/5/20/21264739/microsoft-windows-package-manager-preview-download">WinGet</a>
that I’ve not personally worked with yet.</p>
</blockquote>

<blockquote>
  <p><strong>update 2019:</strong> If anyone is still reading this on 2019, after May 2019 Windows 10 Update, some PowerShell customizations 
(colours) won’t work anymore (reason is that they deprecated some PSReadLine.options values).
I´ve switched to <a href="https://github.com/lukesampson/concfg">concfg</a> to customize my shells.</p>
</blockquote>

<h1 id="background">Background</h1>

<p>In one of my previous <a href="/personal/software%20engineering/So-how-is-it-working-at-a-Silicon-Valley-startup-from-an-Spaniard-point-of-view/">stories</a> 
I told you about how I failed to adapt using a Mac laptop. 
For me, +20 years of typing on a <em>standard</em> keyboard, with its key placement was too much muscle memory. 
Other coworkers could adapt, but I was an avid gamer and using my home computer at the same time I was using a Mac for 
work turned into a no-way.
Also, I almost don’t use my mouse when working in Windows or Linux since I’ve become one with the keyboard.</p>

<p>I wanted to be able to keep gaming on my laptop while avoiding purchasing additional disk space to dual boot it, 
I just thought, why not, <em>we are living in the future</em>, and I started my Ultimate Windows Workstation™</p>

<figure>
    <img src="/assets/images/posts/Setting-up-a-windows-workstation-for-a-2018-technology-stack/lilo-y-nano.png" alt="A comic about some guys who cannot use emacs because they only have four fingers and relay on vim instead, 
         stating that as reason to use linux over MacOS" />
    <figcaption>
        After using a Mac for a while I got more understanding of the emacs hype back in the 90s. 
        Source <a href="http://en.tiraecol.net/modules/comic/comic.php?content_id=2&amp;mode=0&amp;order=0" data-href="http://en.tiraecol.net/modules/comic/comic.php?content_id=2&amp;mode=0&amp;order=0" class="markup--anchor markup--figure-anchor" rel="noopener" target="_blank">tiraecol</a>
    </figcaption>
</figure>

<p>Being a fan of RHEL and since there isn’t yet a <a href="https://docs.microsoft.com/en-us/windows/wsl/install-win10">linux subsystem for windows</a> 
based on <a href="https://www.redhat.com/rhel">RHEL</a> (CentOS, Fedora…) I opted for a standalone environment, which means not 
installing cygwin nor the linux subsystems.</p>

<h1 id="package-managers">Package Managers</h1>
<p>First I had to decide between the two major automated tools to deploy software on windows.
Being impressed by <a href="https://brew.sh/"><em>Homebrew</em></a> and after using my beloved <code class="language-plaintext highlighter-rouge">yum</code> in RHEL during several years, 
it had to be a cmd line tool. 
There are to major tools right now on Windows, each one with its own pros and cons: 
<a href="https://Chocolatey.org/"><em>Chocolatey</em></a> and <a href="https://scoop.sh/"><em>Scoop</em></a></p>

<h2 id="scoop">Scoop</h2>

<figure>
    <img src="/assets/images/posts/Setting-up-a-windows-workstation-for-a-2018-technology-stack/scoop.png" alt="scoop package manager logo / homepage" />
    <figcaption>scoop install sudo; sudo make me a&nbsp;sandwich</figcaption>
</figure>

<p>Scoop installs in the user space; that is no messing with your program files or program data folders; 
and does not register <em>almost</em> anything within your environment. 
It will add a path (usually in your <code class="language-plaintext highlighter-rouge">~home/scoop/shims</code> much like a <code class="language-plaintext highlighter-rouge">usr/local</code> works.</p>

<p>One needs to think about scoop tools like portable ones.
The tools will be added to your <code class="language-plaintext highlighter-rouge">~home/scoop</code> folder, and it can even maintain versions. 
A special symlink (yes, windows supports those since long ago with <a href="https://docs.microsoft.com/en-us/windows/desktop/fileio/hard-links-and-junctions">mklink</a> tool) 
will maintain the <em>current</em> version while keeping the rest downloaded. 
You can switch versions painlessly and install tools without any elevated prompt.</p>

<p>However, if you want to make the tools get on well together, you might run into some issues. 
For example, ssh expects your .ssh in your home but since it is running <em>kinda chrooted</em> withing scoop, the .ssh folder 
needs to be sitting on the <code class="language-plaintext highlighter-rouge">~home/scoop/ssh/?version?</code> folder. 
That means that it won’t look the default location and tools that interact with it (like the agent, and so) 
won’t play well with it. 
Then you end manually adding environment variables, paths and other obscure things and then you realize that maybe, 
you’re not using the right tool. 
You can add global installs and other kind of stuff but at that point it doesn’t start to feel right. 
It just should be like <code class="language-plaintext highlighter-rouge">yum</code> or <code class="language-plaintext highlighter-rouge">brew</code> and not having to play with the setup files like <code class="language-plaintext highlighter-rouge">make</code>.</p>

<h2 id="chocolatey">Chocolatey</h2>

<figure>
    <img src="/assets/images/posts/Setting-up-a-windows-workstation-for-a-2018-technology-stack/chocolatey.png" alt="Chocolatey logo" />
    <figcaption>With that slogan and being sweet, who’s gonna not use&nbsp;it?</figcaption>
</figure>

<p>Chocolatey defines itself as “The package manager for Windows”, and after having used it for several months, sure it is.</p>

<p>Even Microsoft itself has played with Chocolatey by implementing <a href="https://blogs.technet.microsoft.com/packagemanagement/2015/05/05/10-things-about-oneget-that-are-completely-different-than-you-think/">OneGet</a>!.
Most Chocolatey packages get converted or packed into a <a href="https://www.nuget.org/">nupkg</a> which plays nice on Windows.</p>

<p>Chocolatey uses nupkg and some PowerShell own extensions to bypass some UI wizards on most of the existing installer 
technologies, by thus bringing true unattended installs. 
You can use Chocolatey from the command line much like scoop to install any packages you find, and you can make it 
interact with the already installed ones if you want.</p>

<p>One of the things I liked from Chocolatey is the ability to maintain my software updated. 
Just running <code class="language-plaintext highlighter-rouge">choco upgrade all</code> will upgrade my software, and I can even import the software I installed by myself.</p>

<p>On the other hand, I did not like the checksums checks on all packages. 
If a given package (<em>cough drivefilestream cough</em>) uses the same URL for downloading the installer, when the installer 
checksum changes, Chocolatey will complain, and you’ll need to add the flag to skip checksums. 
If you do that, Chocolatey might report an installed version older than the really installed one. 
Minor issue, isn’t it?</p>

<p>In contrast to scoop, Chocolatey is able to install both portable and standard packages, if one is able to build a 
portable one. 
By default, uses <code class="language-plaintext highlighter-rouge">%PROGRAMDATA%</code>folder (<code class="language-plaintext highlighter-rouge">C:\ProgramData</code>), which is protected from all users write so, 
if any tool you use (like I explain below under the gradle section) needs to write to that folder you might run into 
issues with that tool. 
Before the latest versions Chocolatey installed on your root drive under the <code class="language-plaintext highlighter-rouge">c:\Chocolatey</code> folder but this has been 
discouraged. 
In fact, if it detects that it is running there, and you’re updating it, it will switch to the <code class="language-plaintext highlighter-rouge">%PROGRAMFILES%</code> 
and <code class="language-plaintext highlighter-rouge">%PROGRAMDATA%</code> folders.</p>

<p>I enjoyed it so much, so I ended purchasing a personal PRO license for myself.
I did this on the hopes that they add synchronization with manually installed software to the 
<a href="https://Chocolatey.org/pricing#compare">PRO users and not only the business ones</a> soon.
So far I have only automatic synchronization. 
This is one way sync (uninstalling from windows Chocolatey packages), but I want two-way (Chocolatey detecting manually 
installed packages also)</p>

<h2 id="chocolatey--scoop">Chocolatey || Scoop ?</h2>

<p>The answer is also a question: why not both? 
For example, I found the sudo app from Scoop to be better that Chocolatey one, so I used that one from scoop. 
Also, I maintain myself the tools that I want to keep updated myself or that have an auto-update feature. 
I can install them with a Chocolatey script and either pin <code class="language-plaintext highlighter-rouge">choco pin …</code> them or just install them manually.</p>

<h1 id="one-script-to-rule-themall">One script to rule them all</h1>

<p><img src="/assets/images/posts/Setting-up-a-windows-workstation-for-a-2018-technology-stack/the-one-ring-150x130.jpg" alt="Photo of The One ring from the lord of the rings" class="align-left" /></p>

<p>With all the improvements that Microsoft has made over the years to PowerShell I thought it was possible to pack a 
script to set up my environment.</p>

<p>I started with the basics, installing scoop and Chocolatey and documenting everything on a document, so I could 
potentially create <em>The One</em> Script.</p>

<h2 id="execution-policy">Execution Policy</h2>

<p>In order to make things work for this script, since it is going to run several unsigned scripts (at least during the 
setup), one might need to enable them on PowerShell: <code class="language-plaintext highlighter-rouge">set-executionpolicy unrestricted -s cu -f</code>. 
Don’t hesitate to restore it to the previous state if needed (write it down from <code class="language-plaintext highlighter-rouge">get-executionpolicy -s cu</code>). 
On The One Script I will probably add a first and last line to alter and restore it.</p>

<h2 id="ssh">ssh</h2>

<p>One of the must-have tools if you already don’t have it is ssh. 
The new 2018 April patch brought ssh client to our windows desktops and servers, so we don’t need complicated setups to 
get it running.</p>

<p>To check if it is enabled on your workstation, on a privileged shell just run</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-WindowsCapability</span><span class="w"> </span><span class="nt">-Online</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">where</span><span class="w"> </span><span class="nx">Name</span><span class="w"> </span><span class="o">-like</span><span class="w"> </span><span class="s1">'OpenSSH.Client*'</span><span class="w">
</span></code></pre></div></div>

<p>and if it is not installed, just install it with</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Add-WindowsCapability</span><span class="w"> </span><span class="nt">-Online</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">OpenSSH.Client~~~~0.0.1.0</span><span class="w">
</span></code></pre></div></div>

<p>You’ll have to use the exact name from the first command (or automate it with some magic):</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ssh_client_capabilitry</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="err">`</span><span class="n">Get-WindowsCapability</span><span class="w"> </span><span class="nt">-Online</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">where</span><span class="w"> </span><span class="nx">Name</span><span class="w"> </span><span class="o">-like</span><span class="w"> </span><span class="s1">'OpenSSH.Client*'</span><span class="se">`
</span><span class="nx">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$ssh_client_capability</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">  
    </span><span class="err">`</span><span class="n">Add-WindowsCapability</span><span class="w"> </span><span class="nt">-Online</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nv">$ssh_client_capability</span><span class="o">.</span><span class="nf">Name</span><span class="se">`
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Once ssh is installed, we need to generate our keys, start the agent and add the keys to agent’s list of managed keys 
so any tool can use them as needed. 
As a bonus I’ll add the key as identity file for github:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Make sure ssh agent is enabled and on autostart  </span><span class="w">
</span><span class="n">Start-Service</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">ssh-agent</span><span class="w">  
</span><span class="n">Set-Service</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">ssh-agent</span><span class="w"> </span><span class="nt">-StartupType</span><span class="w"> </span><span class="nx">Automatic</span><span class="w">

</span><span class="c"># create ssh keys  </span><span class="w">
</span><span class="n">ssh-keygen</span><span class="w"> </span><span class="nt">-t</span><span class="w"> </span><span class="nx">rsa</span><span class="w"> </span><span class="nt">-b</span><span class="w"> </span><span class="nx">4096</span><span class="w"> </span><span class="nt">-C</span><span class="w"> </span><span class="s2">"[your_email@yourdomain.com](mailto:your_email@yourdomain.com)"</span><span class="w">  
</span><span class="n">ssh-add</span><span class="w"> </span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">USERPROFILE</span><span class="nx">/.ssh/id_rsa</span><span class="w">

</span><span class="c"># edit .ssh/config for github  </span><span class="w">
</span><span class="n">Add-Content</span><span class="w"> </span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">USERPROFILE</span><span class="nx">/.ssh/config</span><span class="w"> </span><span class="s2">"Host github.com"</span><span class="w">  
</span><span class="n">Add-Content</span><span class="w"> </span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">USERPROFILE</span><span class="nx">/.ssh/config</span><span class="w"> </span><span class="s2">"    Hostname github.com"</span><span class="w">  
</span><span class="n">Add-Content</span><span class="w"> </span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">USERPROFILE</span><span class="nx">/.ssh/config</span><span class="w"> </span><span class="s2">"    User git"</span><span class="w">  
</span><span class="n">Add-Content</span><span class="w"> </span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">USERPROFILE</span><span class="nx">/.ssh/config</span><span class="w"> </span><span class="s2">"    IdentityFile ~\.ssh\id_rsa"</span><span class="w">  
</span><span class="n">Add-Content</span><span class="w"> </span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">USERPROFILE</span><span class="nx">/.ssh/config</span><span class="w"> </span><span class="s2">"    # only use IdentityFile specified above, skip default ones"</span><span class="w">  
</span><span class="n">Add-Content</span><span class="w"> </span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">USERPROFILE</span><span class="nx">/.ssh/config</span><span class="w"> </span><span class="s2">"    IdentitiesOnly yes"</span><span class="w">
</span></code></pre></div></div>

<h2 id="basic-tools">Basic Tools</h2>

<p>I’ll also install some tools using Chocolatey</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">choco</span><span class="w"> </span><span class="nx">install</span><span class="w"> </span><span class="nx">jdk8</span><span class="w">  
</span><span class="n">choco</span><span class="w"> </span><span class="nx">install</span><span class="w"> </span><span class="nx">greenshot</span><span class="w">  
</span><span class="n">choco</span><span class="w"> </span><span class="nx">install</span><span class="w"> </span><span class="nx">git.install</span><span class="w">  
</span><span class="n">choco</span><span class="w"> </span><span class="nx">install</span><span class="w"> </span><span class="nx">SkyFonts</span><span class="w">  
</span><span class="n">choco</span><span class="w"> </span><span class="nx">install</span><span class="w"> </span><span class="nx">7zip</span><span class="w"> </span><span class="c">#or choco install 7zip.install   </span><span class="w">
</span><span class="n">choco</span><span class="w"> </span><span class="nx">install</span><span class="w"> </span><span class="nx">gpg4win</span><span class="w">
</span></code></pre></div></div>

<h2 id="cmd-line-customizations">cmd-line customizations</h2>

<p>Next things to do is customize the cmd-line. 
Why? Again, why not? If you want to feel h4x0r or just love your own color scheme or fonts, it can also be automated.</p>

<p>I’ve been using <a href="https://fonts.google.com/specimen/Inconsolata">Inconsolata</a> for a while, but I’m switching to 
<a href="https://fonts.google.com/specimen/Source+Code+Pro">Source Code PRO</a>.I also love Roboto. 
All of them are available through Chocolatey. By this time you’ll still bd running <code class="language-plaintext highlighter-rouge">choco</code> in a privileged prompt so 
maybe it is time to install sudo as well. 
As I said, I prefer scoop’s sudo to Chocolatey’s one. 
Remember to remove sudo if you are running privileged:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">scoop</span><span class="w"> </span><span class="nx">install</span><span class="w"> </span><span class="nx">sudo</span><span class="w">  
</span><span class="n">sudo</span><span class="w"> </span><span class="nx">choco</span><span class="w"> </span><span class="nx">install</span><span class="w"> </span><span class="nx">Inconsolata</span><span class="w">  
</span><span class="n">sudo</span><span class="w"> </span><span class="nx">choco</span><span class="w"> </span><span class="nx">install</span><span class="w"> </span><span class="nx">RobotoFonts</span><span class="w">  
</span><span class="n">sudo</span><span class="w"> </span><span class="nx">choco</span><span class="w"> </span><span class="nx">install</span><span class="w"> </span><span class="nx">SourceCodePRO</span><span class="w">
</span></code></pre></div></div>

<p>You can make the fonts available to PowerShell console as well as to standard cmd, by editing the registry within 
PowerShell:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">cd</span><span class="w"> </span><span class="nx">HKLM:</span><span class="w">  
</span><span class="n">cd</span><span class="w"> </span><span class="s1">'.\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Console'</span><span class="w">  
</span><span class="n">Set-ItemProperty</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nx">TrueTypeFont</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">Inconsolata</span><span class="w"> </span><span class="nt">-Value</span><span class="w"> </span><span class="s1">'Inconsolata'</span><span class="w">
</span></code></pre></div></div>

<p>Just right click on the console bar and on the properties context you can change the font to use.</p>

<h3 id="further-customization-for-your-shell-console">Further customization for your shell / console</h3>

<p>We all know your shell is your <code class="language-plaintext highlighter-rouge">$HOME</code>. Most of us want a customized shell. I remember having long long time ago 
some <a href="https://en.wikipedia.org/wiki/Cron">cron</a> tasks that changed my <code class="language-plaintext highlighter-rouge">motd</code> every day, and one funny server giving 
a <code class="language-plaintext highlighter-rouge">bohf-excuse</code> after every command typed. Now I am older, some say <em>wiser</em>, and I certainly can say a little more
serious. That means no more bofh-excuses but still feeling at <code class="language-plaintext highlighter-rouge">$HOME</code> at my prompt.</p>

<p>I have tried several console replacements or wrappers for the standard shell and I must admit I have uninstalled all 
of them. 
However, I have <a href="https://ethanschoonover.com/solarized/">solarized</a> my shells and added a bunch of custom extensions 
or alias to my shell.</p>

<p>If you use <code class="language-plaintext highlighter-rouge">git</code>, knowing where you are in a repo is important and there are some extensions for PowerShell out there. 
I won’t go into deep details about how to and where customize your profile, but one must know that there are
<a href="https://blogs.technet.microsoft.com/heyscriptingguy/2012/05/21/understanding-the-six-PowerShell-profiles/">several profiles in a machine</a>. 
I set up mine to include several modules and other options:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Set-PSReadlineOption -BellStyle None</code> to remove the abusive bell on the shell (seriously Microsoft, did you 
learn anything from <a href="https://en.wikipedia.org/wiki/Office_Assistant">Clippy</a>?)</li>
  <li>Chocolatey extensions that allow for command line completion</li>
  <li><a href="https://github.com/dahlbyk/posh-git">poshgit</a> from <a href="https://github.com/dahlbyk">dahlbyk</a></li>
  <li><a href="https://github.com/matt9ucci/DockerCompletion">DockerCompletion</a> or <a href="https://github.com/samneirinck/posh-docker">posh-docker</a> 
if you are using older versions</li>
  <li>Regarding docker, I added my own module with some fancy stuff like a local permanent redis and a db docker 
containers, as well as some aliases to manage, clean and manipulate images and containers.</li>
  <li>For the solarized theme, I used <a href="https://github.com/neilpa/cmd-colors-solarized">this one</a>.</li>
</ul>

<h2 id="aws">AWS</h2>

<p>Probably you will use AWS or equivalent. 
You can set up your environment variables from shell, but I don’t think this is a good idea. 
If I ever was a h4x0r everything I write would always send the environment variables of whenever it was being ran to 
one of my compromised servers, including those secrets as keys and such. 
Any program can read the environment variables so be careful where do you set or store them.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">setx</span><span class="w"> </span><span class="nx">AWS_ACCESS_KEY_ID</span><span class="w"> </span><span class="nx">xxx</span><span class="w">  
</span><span class="n">setx</span><span class="w"> </span><span class="nx">AWS_SECRET_ACCESS_KEY</span><span class="w"> </span><span class="nx">xxx</span><span class="w">  
</span><span class="n">setx</span><span class="w"> </span><span class="nx">AWS_REGION</span><span class="w"> </span><span class="nx">us-west-1</span><span class="w">
</span></code></pre></div></div>

<h2 id="intellij-idea">IntelliJ IDEA</h2>

<p>One can install IDEA from Chocolatey (which is nice). With IntelliJ I experienced some minor issues:</p>

<ul>
  <li>When using gradle, IntelliJ uses <code class="language-plaintext highlighter-rouge">GRADLE_HOME</code> environment variable instead of <code class="language-plaintext highlighter-rouge">~\.gradle</code> folder. 
This means that on a default install of Chocolatey’s gradle it will attempt to download gradle wrapper binaries to 
a privileged location. 
Usually, you won’t be running gradle scripts as a privileged user so any build will fail. 
Though there are several options to work around this (install gradle on another location, grant all users write 
permission to the wrapper folder of gradle install dir…), I found the most obscure and bizarre one to be the best one. 
If you remove the <code class="language-plaintext highlighter-rouge">GRADLE_HOME</code> environment variable, every <code class="language-plaintext highlighter-rouge">gradle</code> invocation will just use your local <code class="language-plaintext highlighter-rouge">.gradle</code> 
folder, and you won’t have any issue. 
Even if I strongly discourage deleting from windows registry via scripts, you can delete the variable using 
<code class="language-plaintext highlighter-rouge">REG delete "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /F /V GRADLE_HOME</code> or browsing 
HKLM as above in PowerShell. 
My recommendation here is to manually delete the environment variable from the computer properties, 
as the old-fashioned way.</li>
  <li>Having a <code class="language-plaintext highlighter-rouge">~</code> on my <code class="language-plaintext highlighter-rouge">.ssh/config</code> file caused a NPE in IntelliJ IDEA on some setups (not to me), so you might need 
to replace with your full path. 
I suspect this has to do with <em>native</em> vs <em>built-in</em> ssh implementation. 
I opted for native.</li>
</ul>

<figure>
    <img src="/assets/images/posts/Setting-up-a-windows-workstation-for-a-2018-technology-stack/intellij-java-home-project-setup.png" alt="Screenshot of IntelliJ showing how to setup Project JDK to avoid NPE with some setups" />
    <figcaption>Change the Project JDK to JAVA_HOME to avoid a NPE</figcaption>
</figure>

<ul>
  <li>I am getting a NPE also if I import projects not using the <code class="language-plaintext highlighter-rouge">JAVA_HOME</code> for the Gradle JVM option in the import 
dialog. 
This does not happen on new projects, just imported ones. 
It is just a minor <a href="https://youtrack.jetbrains.com/issue/IDEA-185387">issue that will probably be fixed</a>.</li>
  <li>Modifying gradlew files remove the executable bit for the linux users, 
so if you run <code class="language-plaintext highlighter-rouge">gradle wrapper -=-gradle-version … --distribution-type all</code> will probably overwrite the permissions 
for that file. I’m pretty sure there will be a way to avoid that, but I won’t mess with it yet.</li>
</ul>

<h2 id="git">git</h2>

<p>I am using the default git installation from Chocolatey and works pretty good. 
One thing I tuned was gpg for my commits, and some flavors to my <code class="language-plaintext highlighter-rouge">.gitconfig</code> files:</p>

<h3 id="gitignore"><code class="language-plaintext highlighter-rouge">.gitignore</code></h3>

<div class="language-config highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># windows folder config  
</span><span class="n">desktop</span>.<span class="n">ini</span>
</code></pre></div></div>

<h3 id="gitconfig"><code class="language-plaintext highlighter-rouge">.gitconfig</code></h3>
<p><em>Note:</em> For some reason, using <code class="language-plaintext highlighter-rouge">~</code> on gitconfig file was not working on Windows, as neither was using 
<code class="language-plaintext highlighter-rouge">$environment_variables</code>. This does not apply to the special formatting of <code class="language-plaintext highlighter-rouge">includeIf</code> conditional config blocks.</p>
<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># some options added by gpg below or git config above  
</span><span class="nn">[alias]</span>  
    <span class="c"># the acronym stands for "subtree add"  
</span>    <span class="py">sba</span> <span class="p">=</span> <span class="s">"!f() { git subtree add --prefix $1 git@github.com:my_copmany_id/${1%/}.git master --squash; }; f"</span>  
    <span class="c"># the acronym stands for "subtree update"  
</span>    <span class="s">sbu = "!f() { git subtree pull --prefix $1 git@github.com:my_copmany_id/${1%/}.git master --squash; }; f"</span>
<span class="nn">[commit]</span>  
    <span class="py">gpgsign</span> <span class="p">=</span> <span class="s">true  </span>
<span class="nn">[core]</span>  
    <span class="py">excludesfile</span> <span class="p">=</span> <span class="s">C:</span><span class="se">\\</span><span class="s">Users</span><span class="se">\\</span><span class="s">juan</span><span class="se">\\</span><span class="s">.gitignore  </span>
    <span class="py">autocrlf</span> <span class="p">=</span> <span class="s">true</span>
<span class="nn">[url "git@github.com:"]</span>
	<span class="py">insteadOf</span> <span class="p">=</span> <span class="s">https://github.com/</span>
<span class="nn">[includeIf "gitdir:~/workspace/personal/"]</span>
    <span class="py">path</span> <span class="p">=</span> <span class="s">C:</span><span class="se">\\</span><span class="s">Users</span><span class="se">\\</span><span class="s">juan</span><span class="se">\\</span><span class="s">workspace</span><span class="se">\\</span><span class="s">personal</span><span class="se">\\</span><span class="s">.gitconfig</span>
</code></pre></div></div>

<h3 id="git-andgpg">git and gpg</h3>

<p>For gpg there is the gpg4win package on Chocolatey and to configure the keys it is not really complicated except for 
some key formats. 
The following steps were working for me:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">gpg --full-generate-key</code></li>
  <li><code class="language-plaintext highlighter-rouge">gpg --list-secret-keys — keyid-format LONG</code></li>
  <li><code class="language-plaintext highlighter-rouge">gpg --armor — export &lt;your key id from list above&gt;</code></li>
  <li>Paste on <a href="https://github.com/settings/gpg/new">Github</a></li>
  <li>run <code class="language-plaintext highlighter-rouge">git config --global user.signingkey &lt;your key id from list above&gt;</code></li>
</ul>

<p>I’m sure an avid reader can automate those steps with a single script (specially the <code class="language-plaintext highlighter-rouge">--armor-export</code> part). 
Since you need to open it to paste on Github I’ve left this steps out of The One Script.</p>

<h2 id="vpn">VPN</h2>

<p>Setting up a VPN in linux or windows, if it is not using a vendor client can be painful, specially since 
developers don’t often have a networking background. 
Though that is not my case since I was some years ago certified IBM sysadmin on RHEL and I had been doing some 
networking setups.</p>

<p>Often companies have a site-to-site VPN and a per user vpn for roadrunners. 
If that the case, there can be either a VPN client from a specific vendor (like OpenVPN, Cisco, Fortigate) or a 
standard setup like ipsec, l2tp or similar. 
I’m not going to write about specific vendor stuff (or OpenVPN) but just add some details about the ipsec standard.</p>

<h3 id="ipsec-l2tp">ipsec / l2tp</h3>

<p>In order to make an ipsec/l2tp VPN running on windows there are some tricks to keep in mind:</p>

<ul>
  <li>every change to the networking stack configuration needs a reboot. 
If you add, edit or remove a registry setting, you will probably need to reboot your computer.</li>
  <li>You can set up your standard VPN as a Windows connection with a script:</li>
</ul>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#general settings     </span><span class="w">
</span><span class="nv">$VpnName</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">connection_name</span><span class="w">  
</span><span class="nv">$gateway</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">endpoint_ip</span><span class="w">  
</span><span class="nv">$psk</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">your_secret_here</span><span class="w">
</span><span class="nx">Add-VpnConnection</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nv">$VpnName</span><span class="w"> </span><span class="nt">-ServerAddress</span><span class="w"> </span><span class="nv">$gateway</span><span class="w"> </span><span class="nt">-TunnelType</span><span class="w"> </span><span class="nx">L2tp</span><span class="w"> </span><span class="nt">-AuthenticationMethod</span><span class="w"> </span><span class="nx">Chap</span><span class="w"> </span><span class="nt">-EncryptionLevel</span><span class="w"> </span><span class="nx">Optional</span><span class="w"> </span><span class="nt">-L2tpPsk</span><span class="w"> </span><span class="nv">$psk</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="nt">-AllUserConnection</span><span class="w"> </span><span class="nt">-UseWinLogonCredential</span><span class="w"> </span><span class="bp">$false</span><span class="w"> </span><span class="nt">-SplitTunneling</span><span class="w">
</span></code></pre></div></div>

<ul>
  <li>If you are behind a NAT (and you’ll probable be) you will need to edit a special value on the registry:</li>
</ul>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Set-ItemProperty</span><span class="w"> </span><span class="nx">HKLM:\SYSTEM\CurrentControlSet\Services\PolicyAgent</span><span class="w"> </span><span class="nt">-Name</span><span class="w"> </span><span class="nx">AssumeUDPEncapsulationContextOnSendRule</span><span class="w"> </span><span class="nt">-Value</span><span class="w"> </span><span class="nx">2</span><span class="w"> </span><span class="nt">-Type</span><span class="w"> </span><span class="nx">DWord</span><span class="w">
</span></code></pre></div></div>

<ul>
  <li>As a topping, if you want to route only your internal traffic through the VPN (i.e. to route an A network 
you’ll add <code class="language-plaintext highlighter-rouge">10.0.0.0/8</code> as <code class="language-plaintext highlighter-rouge">private_subnet/mask</code>):
    <div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">add-VpnConnectionRoute</span><span class="w"> </span><span class="nt">-ConnectionName</span><span class="w"> </span><span class="s2">"connection_name"</span><span class="w"> </span><span class="nt">-DestinationPrefix</span><span class="w"> </span><span class="s2">"private_subnet/mask"</span><span class="w"> </span><span class="nt">-PassThru</span><span class="w">
</span></code></pre></div>    </div>
  </li>
  <li>If for any reason your VPN is stuck on verifying or other step, make sure you have rebooted after the registry 
tweak above. 
If not, I have found that sometimes, specially after an update, the checkbox <code class="language-plaintext highlighter-rouge">LCP Extensions</code>on the 
<code class="language-plaintext highlighter-rouge">VPN Properties -&gt; Options Tab -&gt; PPP settings</code> is disabled. 
Just re enabling it will make the VPN work again. 
You can access this setting on the adapter settings for your VPN connection in the <em>old network and sharing center</em> 
by just opening Control Panel.</li>
  <li>Though not mandatory, I always like to disable the default GW on both IPV4 and IPV6 on the VPN properties. 
I do this manually but there are some commands to manipulate this from the registry or PowerShell. 
I do this because I want only the VPN traffic to be routed through it (unless I’m at a hotel where there is some kin of 
<em>deep packet inspection</em> or protocol filters that forbids videoconferencing, streaming or gaming.</li>
</ul>

<h2 id="other-customizations">Other customizations</h2>

<p>I like having a <code class="language-plaintext highlighter-rouge">workspace</code> folder where I work.
I place it outside the annoying <code class="language-plaintext highlighter-rouge">Documents and Settings</code> by default but since some tools open by default that folder 
I make a joint there. 
Also, since I’m lazy I make the same joint on the root of my drive, so I don’t have the need to write long path names:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">mkdir</span><span class="w"> </span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">USERPROFILE</span><span class="nx">/workspace</span><span class="w">
</span><span class="n">sudo</span><span class="w"> </span><span class="nx">New-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">USERPROFILE</span><span class="nx">/Documents/workspace</span><span class="w"> </span><span class="nt">-ItemType</span><span class="w"> </span><span class="nx">SymbolicLink</span><span class="w"> </span><span class="nt">-Value</span><span class="w"> </span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">USERPROFILE</span><span class="nx">/workspace</span><span class="w">
</span><span class="n">sudo</span><span class="w"> </span><span class="nx">New-Item</span><span class="w"> </span><span class="nt">-Path</span><span class="w"> </span><span class="nx">c:/workspace</span><span class="w"> </span><span class="nt">-ItemType</span><span class="w"> </span><span class="nx">SymbolicLink</span><span class="w"> </span><span class="nt">-Value</span><span class="w"> </span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">USERPROFILE</span><span class="nx">/workspace</span><span class="w">
</span></code></pre></div></div>

<p>I also added a bunch of docker aliases but actually I keep using the long commands since I forget the name of my own 
aliases. 
However, the aliases serve as a repository of useful commands, and the only thing I need to do is to type or edit my 
aliases file from the command line like <code class="language-plaintext highlighter-rouge">vim $PROFILE</code> or similar and I have them there, waiting for me to use them.</p>

<p>Most of these <em>additional</em> customizations focus on my actual technology stack, like having a db docker image from 
our docker registry, or empty dockerized redis, or aliases and shortcuts 
(like opening <a href="https://notepad-plus-plus.org/">Notepad++</a> from the command line), or some other context switching 
commands.</p>

<p>Most of the shortcuts have been added to my profile via the aforementioned <a href="https://blogs.technet.microsoft.com/heyscriptingguy/2012/05/21/understanding-the-six-PowerShell-profiles/">different profiles in a machine</a>.
Pick your favourite and add there your shortcuts or aliases.</p>

<p><em>Originally published in</em> <a href="https://medium.com/@juan_ara/setting-up-a-windows-workstation-for-a-2018-technology-stack-bb8eadec8ed1"><i class="fab fa-fw fa-medium"></i>Medium</a></p>]]></content><author><name>Juan Ara</name></author><category term="Software Engineering" /><category term="PowerShell" /><category term="tips" /><category term="automation" /><category term="workstation" /><summary type="html"><![CDATA[I failed to adapt using a Mac laptop. +20 years of typing on a standard PC keyboard was too much, so I decided to try to create a full work station on a Windows laptop similarly to how my work Mac was onboarded: command line brew like tool and automate my .dotfiles (or their Windows equivalent).]]></summary></entry><entry><title type="html">JUnit 5 unit testing with Kotlin</title><link href="https://www.cornerinthemiddle.com/software%20engineering/JUnit-5-unit-testing-with-Kotlin/" rel="alternate" type="text/html" title="JUnit 5 unit testing with Kotlin" /><published>2018-09-28T00:00:00+02:00</published><updated>2018-09-28T00:00:00+02:00</updated><id>https://www.cornerinthemiddle.com/software%20engineering/JUnit-5-unit-testing-with-Kotlin</id><content type="html" xml:base="https://www.cornerinthemiddle.com/software%20engineering/JUnit-5-unit-testing-with-Kotlin/"><![CDATA[<blockquote>
  <p>Update 2019: Like this entry?
Try <a href="/software%20engineering/using-gradle-kotlin-dsl-with-junit5/">Using Gradle Kotlin DSL with junit 5</a></p>
</blockquote>

<h1 id="introduction">Introduction</h1>

<p>At <a href="https://returnly.com/">Returnly</a> we are using <a href="https://junit.org/junit5/docs/current/user-guide/">JUnit Jupiter</a> to 
test our back-end services.
We also use <a href="https://mockk.io/">mockk</a> and <a href="http://joel-costigliola.github.io/assertj/">assertj</a> which are great
libraries that help <strong>a lot</strong> to build readable code.</p>

<p>This article will try to cover the basics of using parameterized tests, especially by trying to keep test data separated
from the test classes.</p>

<p>We have started to move into <a href="https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests">parameterized tests</a>
by creating various set of data objects.
Sometimes just CSV sources are fine, and our product managers love them (since they can define datasets without knowing
how to code), but we, engineers, tend to prefer working with DSL or Domain Objects.</p>

<p>A <code class="language-plaintext highlighter-rouge">@ParameterizedTest</code> is a test which takes some kind of values as arguments and tests them.
We found them useful to test edge error cases and unhappy paths, for example:</p>

<script src="https://gist.github.com/c74cd71c782ac744e4e2730930121323.js"> </script>

<h1 id="gradle-setup">Gradle Setup</h1>

<p>With the new gradle 4.6+ native JUnit Platform integration, it is straightforward to run the tests within gradle:</p>

<script src="https://gist.github.com/ca13ba9a84369af2aca41ea0ccf8a910.js"> </script>

<p>I like to add the <code class="language-plaintext highlighter-rouge">cleanTest</code> task there, so I can run it within my IDE and force to re-run the tests.
This task just deletes test output.</p>

<p>Other people prefer to make the tests never <code class="language-plaintext highlighter-rouge">UP-TO-DATE</code> with <code class="language-plaintext highlighter-rouge">test.outputs.upToDateWhen {false}</code> or <code class="language-plaintext highlighter-rouge">gradle test --rerun-tasks</code>.</p>

<p>I prefer the built in <code class="language-plaintext highlighter-rouge">cleantTest</code> task.</p>

<p>On a console or standard output, the now <a href="https://junit.org/junit5/docs/current/user-guide/#running-tests-build-gradle">deprecated plugin for JUnit</a> 
displayed the test results in a tree, but with gradle’s native implementation we get only a <code class="language-plaintext highlighter-rouge">BUILD SUCCESSFUL in {$time}s</code>.</p>

<p>To see the summary we have added the <code class="language-plaintext highlighter-rouge">afterSuite</code> <a href="https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/testing/TestListener.html">TestListener</a> 
to the <code class="language-plaintext highlighter-rouge">test</code> closure as an example above.</p>

<p>The output has a nice summary:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Testing started at 16:56 ...  
16:56:17: Executing task 'test'...
&gt; Task :compileKotlin NO-SOURCE  
&gt; Task :compileJava NO-SOURCE  
&gt; Task :processResources NO-SOURCE  
&gt; Task :classes UP-TO-DATE  
&gt; Task :compileTestKotlin UP-TO-DATE  
&gt; Task :compileTestJava NO-SOURCE  
&gt; Task :processTestResources NO-SOURCE  
&gt; Task :testClasses UP-TO-DATE  
&gt; Task :test  
org.tarodbofh.medium.junit5.parametrized.ValueSourceTest &gt; amount must be positive(int)[1] PASSED  
org.tarodbofh.medium.junit5.parametrized.ValueSourceTest &gt; amount must be positive(int)[2] PASSED  
Test result: SUCCESS  
Test summary: 2 tests, 2 succeeded, 0 failed, 0 skipped
</code></pre></div></div>

<p>Following this approach, one could build the same tree listener by having a look to JUnit’s implementation <a href="https://github.com/junit-team/junit5/blob/master/junit-platform-console/src/main/java/org/junit/platform/console/tasks/TreePrintingListener.java">here</a>.</p>

<p>The dependencies to add to test are (from <a href="https://search.maven.org/">maven central</a>):</p>

<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">dependencies</span> <span class="o">{</span>  
    <span class="n">testCompile</span> <span class="s2">"org.assertj:assertj-core:3.11.1"</span>  
    <span class="n">testCompile</span> <span class="s2">"org.junit.jupiter:junit-jupiter-api:5.2.0"</span>  
    <span class="n">testCompile</span> <span class="s2">"org.junit.jupiter:junit-jupiter-params:5.2.0"</span>  
    <span class="n">testRuntime</span> <span class="s2">"org.junit.jupiter:junit-jupiter-engine:5.2.0"</span>  
<span class="o">}</span>
</code></pre></div></div>

<figure class="third text-left text-left">
  
    
      <img src="" alt="" />
    
  
    
      <img src="/assets/images/posts/JUnit-5-unit-testing-with-Kotlin/1_p2nPxZvpyV8sI6Ynh6V6EA.png" alt="Image showing the result of running parameterized tests on junit jupiter inside IntelliJ" />
    
  
    
      <img src="" alt="" />
    
  
  
    <figcaption>Parameterized tests display nice in IDEs
</figcaption>
  
</figure>

<h1 id="passing-arguments-to-parameterized-tests-in-kotlin">Passing arguments to parameterized tests in kotlin</h1>

<p>Following <a href="https://blog.philipphauer.de/best-practices-unit-testing-kotlin/">these hints</a>, We have been using a 
<code class="language-plaintext highlighter-rouge">@Nested</code> inner class to implement the error cases or unhappy paths.
Sometimes, <code class="language-plaintext highlighter-rouge">ValueSource</code> seems <em>ugly</em>, and it is not possible to add some <em>flavor</em> to it:</p>

<script src="https://gist.github.com/cff5af3b70767b2f0dd625e55f9c5252.js"> </script>

<p>Values passed to an annotation need to be a constant value, which forbids us to use ranges or other constructs there, 
for example:</p>

<script src="https://gist.github.com/5452336c6914e63c9af51351e2568aae.js"> </script>

<p>Throws a compile-time error like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Error:(16, 25) Kotlin: An annotation argument must be a compile-time constant
</code></pre></div></div>

<p>To solve that, we can use the <code class="language-plaintext highlighter-rouge">@MethodSource</code> annotation, but that forces us to:</p>

<ul>
  <li>Have a method without arguments</li>
  <li>Use <code class="language-plaintext highlighter-rouge">@TestInstance(TestInstance.Lifecycle.PER_CLASS)</code> annotation <strong>and</strong> having the method part of the test class
or producing a static method (<a href="https://junit.org/junit5/docs/current/api/org/junit/jupiter/params/provider/MethodSource.html">source</a>)</li>
</ul>

<p>This leads to some strange code or other weird constructs, specially because we want to avoid using static methods in
Kotlin with the JVMStatic annotation, like:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">get1To10</span><span class="p">()</span> <span class="p">=</span> <span class="p">(</span><span class="mi">1</span><span class="o">..</span><span class="mi">10</span><span class="p">).</span><span class="nf">toList</span><span class="p">().</span><span class="nf">toIntArray</span><span class="p">()</span>
</code></pre></div></div>

<p>As we mentioned earlier, we are using some DSL or pre-configured domain objects with know values
(i.e. <code class="language-plaintext highlighter-rouge">val valid_amounts = (1..10)</code> ).
Some people would have thought to bypass the compile-time constant error above by having something like:</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">const</span> <span class="kd">val</span> <span class="py">VALID_AMOUNTS</span> <span class="p">=</span> <span class="n">valid_amounts</span><span class="p">.</span><span class="nf">toList</span><span class="p">().</span><span class="nf">toIntArray</span><span class="p">()</span> <span class="c1">//compile error</span>
</code></pre></div></div>

<p>Unfortunately, in Kotlin, only primitives and Strings can be constant values, so often this is not an option.</p>

<p>Even though we always try to keep our tests simple, if we add the domain objects to the test class it can make it
grow, and we don’t think that mixing test data with the test methods is a good idea.</p>

<p>We are using <a href="https://kotlinlang.org/docs/reference/extensions.html">Kotlin Extensions</a> to define our test domain
objects and thus keeping our test data from our test implementation as clean as possible.</p>

<p>This means that we have a separate file with the test data (much like the old CSV files that product uses) but in 
an <em>engineerly</em> way.</p>

<p>One thing we can do, though is to use the <a href="https://junit.org/junit5/docs/current/api/org/junit/jupiter/params/provider/ArgumentsSource.html">ArgumentsSource</a>
annotation and have a <em>Factory</em> class which implements <a href="https://junit.org/junit5/docs/current/api/org/junit/jupiter/params/provider/ArgumentsProvider.html">ArgumentsProvider</a>
and its <em>arguments</em> method.</p>

<p>Although it sounds overkill, it is easy to implement thanks to reified inline functions and delegation:</p>

<script src="https://gist.github.com/c3a9e825a3c18e5038051e251ba97a53.js"> </script>

<p>Then, our test method becomes:</p>

<script src="https://gist.github.com/4c69a8eeb89e9de13c67c7cd8ea9a5cc.js"> </script>

<p>The complete source now has two files, and it is easy to extend or maintain. 
We got a nice functionality to reuse our ArgumentsProvider if we move it to one of our standard libraries.</p>

<h1 id="tldr">TLDR&gt;</h1>

<p>This is how the code looks after all the changes:</p>

<script src="https://gist.github.com/91799cdb5eff8abfeebb73f31b75eaa8.js"> </script>

<p>This is easy to extend, and we can replace the ArgumentsSource with CSVSource files once the product team have 
completed their test data.</p>

<p><em>Originally published in</em> <a href="https://medium.com/@juan_ara/unit-testing-kotlin-with-junit-5-21bdf0d1a7c2"><i class="fab fa-fw fa-medium"></i>Medium</a></p>]]></content><author><name>Juan Ara</name></author><category term="Software Engineering" /><category term="kotlin" /><category term="tips" /><category term="java" /><category term="junit jupiter" /><category term="junit" /><category term="tdd" /><category term="testing" /><summary type="html"><![CDATA[At Returnly, we are using, among others, JUnit Jupiter to test our back-end services. There is some lack of documentation and examples, so I will setup some parameterized test examples.]]></summary></entry><entry><title type="html">Mockk messing up with the JVM</title><link href="https://www.cornerinthemiddle.com/software%20engineering/Mockk-messing-up-with-the-JVM/" rel="alternate" type="text/html" title="Mockk messing up with the JVM" /><published>2018-08-31T00:00:00+02:00</published><updated>2018-08-31T00:00:00+02:00</updated><id>https://www.cornerinthemiddle.com/software%20engineering/Mockk-messing-up-with-the-JVM</id><content type="html" xml:base="https://www.cornerinthemiddle.com/software%20engineering/Mockk-messing-up-with-the-JVM/"><![CDATA[<div class="feature__wrapper">

  
    <div class="feature__item--center">
      <div class="archive__item">
        

        <div class="archive__item-body">
          

          
            <div class="archive__item-excerpt">
              <p><strong>Update:</strong> The mockk issue fas fixed on <code class="language-plaintext highlighter-rouge">mockk 1.8.7</code> release, less than 10 days after I submitted the bug <a href="https://github.com/mockk/mockk/issues/129">here</a>.</p>

            </div>
          

          
        </div>
      </div>
    </div>
  

</div>

<p>I was working in a new service; I had been pushing commits as crazy, with their appropriate tests, and I was using 
<a href="https://kotlinlang.org">kotlin</a>, 
<a href="https://spring.io/">spring</a>, 
<a href="http://www.jooq.org/">jooq</a> and 
<a href="https://site.mockito.org/">mockito</a>. 
Then, I had run into an issue that <em>felt</em> could be improved.</p>

<p>I had read about <a href="https://mockk.io/"><em>mockk</em></a> from a coworker, so I gave it a try.
I started to implement a new functionality that had <strong>nothing to do with another one</strong> and implemented the tests.
The only relation they had was a table that the new functionality was reading from the database
(not writing to, I promise!).</p>

<p>Guess what happened after that?
Some other tests started to fail.
Seriously, why fixing something opens several other bugs?
How could be possible that a completely different test suite that had nothing to do with those services started to fail?
The symptoms were really strange.
I was looking at the logs,and I could see <em>jooq</em> retrieving a row and suddenly failing to map an object because the id
field was null. As I said, that could not be possible since my DB access was readonly this time and tests were not doing
anything related with db yet.</p>

<p>This was a really weird case, since running those tests alone worked but when invoked as part of the suite, failed.
Moreover, altering the order of the tests make them work or fail, as it some dark magic was controlling the code.</p>

<p>Since JUnit 5 <a href="https://github.com/junit-team/junit5/issues/13">does not have yet a way to specify the order of the tests</a>
much like JUnit 4 had, this was hard to test and identify.
Digging deeper I found that on the native call on <code class="language-plaintext highlighter-rouge">java.lang.reflect.Executable</code>,
it’s <code class="language-plaintext highlighter-rouge">private native Parameter[] getParameters0();</code> was returning a different set of parameters after <code class="language-plaintext highlighter-rouge">mockk</code> had 
been invoked.</p>

<p>Our jooq’s <code class="language-plaintext highlighter-rouge">ParameterAwareRecordMapper</code> indeed uses <code class="language-plaintext highlighter-rouge">constructor.getParameters</code> to map from a record to a POJO and 
voila, after that call, <em>jooq</em> was unable to found any parameter from the record,
 and thus populating a POJO with all fields initialized to <code class="language-plaintext highlighter-rouge">null</code>.</p>

<p>What jooq was doing was reading the parameter names from the constructor of the POJO and looking for fields with those
names on the record.
Since the POJO was generated by jooq itself based on the DB fields everything should work fine from here.
It was not. This messed up my pipeline, my code and got me through the rabbit hole for at least a couple of hours.</p>

<p>I managed to isolate the problem in the least lines of code possible and so far
I have not found any work around nor way to reset what is happening after using <code class="language-plaintext highlighter-rouge">mockk()</code>.
I suspect that the library uses reflection to inject its own constructor but forgets to keep the parameters name.
Here is the minimal test to reproduce it (remember that JUnit 5 can’t warrant test order so run them independently if 
needed):</p>

<script src="https://gist.github.com/fd3c61fdac611fad9678f1d981419cf7.js"> </script>

<p>There are two tests, and a data class with two parameters in the constructor, <code class="language-plaintext highlighter-rouge">name</code> and <code class="language-plaintext highlighter-rouge">age</code>.
The first test shows that the class has only one constructor, but it is not the same after calling to <code class="language-plaintext highlighter-rouge">mockk()</code>; 
the second one shows that the parameter names are not present anymore.</p>

<p>One expects that just mocking an object does not mess with the loaded class by the JVM, but I got this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    15:25:12.631 [main] DEBUG io.mockk.impl.instantiation.AbstractMockFactory - Creating mockk for DataClass name=#2, moreInterfaces=[]  
    15:25:12.637 [main] DEBUG io.mockk.impl.stub.CommonClearer - Clearing [] mocks
    java.lang.AssertionError: Assertion failed
    at org.tarodbofh.medium.mockk.MockkMessingNativeJVM.testConstructorEqualityAfterClearMocks(MockkMessingNativeJVM.kt:26)
</code></pre></div></div>

<p>This means that after calling <code class="language-plaintext highlighter-rouge">mockk()</code> the constructor of the class had been altered!</p>

<p>Of course, the second test failed:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    15:25:11.563 [main] DEBUG io.mockk.impl.instantiation.AbstractMockFactory - Creating mockk for DataClass name=#1, moreInterfaces=[]  
    15:25:12.555 [main] DEBUG io.mockk.impl.stub.CommonClearer - Clearing [] mocks
    java.lang.AssertionError: [Extracted: name]   
    Expecting:  
      &lt;["arg0", "arg1"]&gt;  
    to contain exactly in any order:  
      &lt;["name", "age"]&gt;  
    elements not found:  
      &lt;["name", "age"]&gt;  
    and elements not expected:  
      &lt;["arg0", "arg1"]&gt;
    at org.tarodbofh.medium.mockk.MockkMessingNativeJVM.testConstructorGetParametersMocked(MockkMessingNativeJVM.kt:44)
</code></pre></div></div>

<p>The parameter names had changed, and that is why the other tests started to fail after I mocked an object on a Unit 
est. Don’t forget to compile with java parameters targeting Java 8 
(see <a href="https://bugs.openjdk.java.net/browse/JDK-8046108">https://bugs.openjdk.java.net/browse/JDK-8046108</a>).</p>

<p>To do that, just add this to your <code class="language-plaintext highlighter-rouge">.gradle</code> script: 
<code class="language-plaintext highlighter-rouge">[compileKotlin, compileTestKotlin]*.kotlinOptions*.javaParameters = true</code> and you’ll be using the parameters. 
Else, the first test will fail on the first assertion and not on the second one.</p>

<p>If you need help to set up gradle to test this, you can have a look at my <code class="language-plaintext highlighter-rouge">.gradle</code> file 
<a href="https://gist.github.com/TarodBOFH/5bbb1d1907f2a0e4c7ce58e0fa3560de">here</a>.</p>

<p>I learned this the hard way but in the end I opted to not mock any object I was requiring, but using constant value 
objects.</p>

<p>For example, if I am testing underage people I will have a <code class="language-plaintext highlighter-rouge">val UNDERAGE_PERSON = DataClass("underage, 1)</code> on an 
<code class="language-plaintext highlighter-rouge">TestExtensions.kt</code> file instead of using a mocked object, at least for POJOs, DTOs and other stateful constructs.
For stateless and business logic methods, I am hesitant until I inspect mockk code to see what is it doing to mess with
the JVM.</p>

<p>After a brief chat on <a href="https://gitter.im"><em>gitter</em></a> with mockk maintainer he asked to submit a
<a href="https://github.com/mockk/mockk/issues/129">bug</a> report, so I hope this gets fixed on a future release.</p>

<p>Some days later, I experienced the same bug with <a href="https://github.com/mockito/mockito">mockito</a>, 
but only when <code class="language-plaintext highlighter-rouge">@Mock</code> annotations where used instead of using <code class="language-plaintext highlighter-rouge">mock(...)</code> methods.
I was unable to reproduce it on a constant behavior as apparently this happened randomly.</p>

<p>This bug indeed seems related to this 
<a href="https://github.com/mockito/mockito/issues/695">two</a> <a href="https://github.com/mockito/mockito/issues/764">bugs</a> 
on mockito that were already resolved.</p>

<p><strong>Update:</strong> The mockk issue fas fixed on <code class="language-plaintext highlighter-rouge">mockk 1.8.7</code> release, less than 10 days after I submitted the bug.</p>

<p>That is grade A job from the maintainers. Kudos!</p>

<p><em>Originally published in</em> <a href="https://medium.com/@juan_ara/mockk-messing-up-with-the-jvm-10fbdd548a78"><i class="fab fa-fw fa-medium"></i>Medium</a></p>]]></content><author><name>Juan Ara</name></author><category term="Software Engineering" /><category term="kotlin" /><category term="mockk" /><category term="java" /><category term="tdd" /><category term="testing" /><summary type="html"><![CDATA[Ok, this is a story with a happy beginning. Or ending. Or both.]]></summary></entry></feed>