Chethan N
3 min readFeb 29, 2024

--

Konsist adoption with a custom Baseline definition

We randomly stumbled upon Konsist and were excited about the possibilities it offered. If you haven't checked it out, please visit their GitHub page. This article explains how we could leverage Konsist to quickly enforce the rules we always intended and also how we arrived at a way of checking these rules only for the new code.

At a high level, Konsist is a powerful static code analyzer tailored for Kotlin, focused on ensuring codebase consistency and adherence to coding conventions. It integrates seamlessly with prominent testing frameworks like JUnit4, JUnit5, and Kotest, Konsist allows developers to embed consistency checks within unit tests.

We were able to define our project-specific rules in our Android project easily and enforce them. We use Jetpack Compose for our UI, so we could define rules for our UI as well (Composables are Kotlin functions).

There are two ways to include Konsist in a Kotlin project (a dedicated module for defining Konsist test or by adding konsistTest sourceset for each module). We went with a dedicated module (by the name konsistTest) at the root level of the project. This mode of integration is easier, doesn’t change any of the existing modules' code base and was required to achieve custom baseline capabilities easily (explained later).

Some of the rules we defined were:

Asserting that our internal design system UI elements are used instead of the default platform-provided ones. We have our counterparts for Text and Button elements defined in our design system, we could easily add a rule that no file imports androidx.compose.material.Text or androidx.compose.material.Button to make sure design system elements are used

And, to assert all our Composables are in ui package, we could add a rule like

@Test
fun `assert — All Compose functions should be inside the ui package`() {
Konsist
.scopeFromProject()
.functions()
.withAnnotationOf(Composable::class)
.assertTrue {
it
.resideInPackage(“..ui..”)
}
}

One can get creative and add any custom rules that your project needs to follow. These are just a couple of the simplest rules to give what kind of custom rules we can define. Check https://docs.konsist.lemonappdev.com/inspiration/snippets for more examples

So far so good. The difficulties arose when we wanted to automate these checks in our CI system and check if the new code passed the tests. One of the key limitations of Konsist is, unlike most linters it doesn’t support baseline right now. This meant that we couldn’t follow the Clean as You Code principle (Clean as You Code is an approach to code quality that eliminates many of the challenges that come with traditional methodologies. As a developer, you focus on maintaining high standards and taking responsibility specifically in the new code you're working on). This is necessary for any project as it's not possible to fix all the existing violations of the rules at once. We wanted to make sure the new code going in follows the rules. We also wanted a way to bypass this check in case of exceptional situations.

We were able to overcome this limitation by quickly writing some scripts which analysed the output generated by Kosist tests. We wrote a small python script to generate a baseline and packaged it in one of the directories in our code base. Then, for each MR, we run Konsist tests in our CI system and check that there are no new violations for any of our rules. If there is any new violation of any of the rules, then the job would fail and prevent the new MR from getting merged.

Below is the complete code for generating baseline and checking if the baseline is breached. generate_baseline generates a baseline and saves the output in konsist_baseline.txt, check_if_baseline_is_breached exits with non 0 exit code in case of any branches in baseline numbers or in case a baseline for any file is not present. We use this to make sure that the new code doesn’t introduce any new violations.

We use gitlab pipeline for our CI needs, there we could write a simple job with the below check.

    - ./gradlew konsistTest:<UTTaskName> || exit_code=$?
- if [ ${exit_code} -ne 0 ]; then python3 konsistAnalysis.py ; else echo "No violations"; fi

Basically, we don’t fail the job even if konsistTest:<UTTaskName> fails, we run our analysis on the output of the above task and check if the baseline is breached, we fail the pipeline only on baseline breach.
Also, on any new rule addition, we run the baseline and update the baseline file. In case of exceptional situations, we update the Baseline to accommodate unavoidable cases, the Baseline file is thoroughly reviewed in that case.

With these simple changes, we could enforce some of the code consistency checks we intended to do.

--

--