Showing posts with label Kotlin. Show all posts
Showing posts with label Kotlin. Show all posts

Sunday 4 September 2022

Kotlin: there's no such thing as static, apparently

G'day:

Whilst writing today's previous article ("CFML: invokeImplicitAccessor on CFCs"), I noted that CFML does not support implicit accessor methods on static properties.

I wanted to look at how another language handles the same, and the only languages I personally know anything about that has accessors defined on the properties themselves are C# and Kotlin (that's not to say there aren't others, undoubtedly it's very prevalent; those're the two I know about). My C# is even worse than my Kotlin (if you can believe that), so I decided to check Kotlin. Plus, like, I need to know about Kotlin anyhow, so makes sense.

I was quite surprised to see that Kotlin simply doesn't support the concept of static properties. I mean like I initially went "huh? What? Why?", but figured there was a good reason. I found how Kotlin approximates the equivalent fairly quickly, but it too me longer to find out why.

During all this I tested how property accessors work in Kotlin anyhow, and here's some code for standard property accessors:

describe("property accessor tests") {
    class Person {
        var firstName = ""
            set(value) {
                field = value.replaceFirstChar(Char::titlecase)
            }
        var lastName = ""
            set(value) {
                field = value.replaceFirstChar(Char::titlecase)
            }

        val fullName
            get() = "$firstName $lastName"
    }

    it("uses getters/setters") {
        var person = Person()
        person.firstName = "jane"
        person.lastName = "roe"

        person.fullName shouldBe "Jane Roe"

    }
}

That's pretty self-explanatory, and no surprises there I think. I guess I was thrown by needing to use field rather than the name of the property. It's "explained" in Properties › Getters and setters › Backing fields, but only as a statement of fact; not why it is the way it is. I did note though, that if I had this:

var firstName = ""
    set(value) {
        firstName = value.replaceFirstChar(Char::titlecase)
    }

Then I get a stack overflow. I am guessing that assignment to firstName itself calls its setter, so we get a bit of a recursive meltdown there. I should test that, I think.

OK, so that's normal properties. What about approximating static ones? Kotlin has this concept of a "Companion objects" which one can define within a class, and - as the docs say - "Members of the companion object can be called simply by using the class name as the qualifier". I'm not gonna re-quote someone else's docs too comprehensively, so just go read them if you like.

The "static" equivalent tests here would be this lot:

class PropertiesTest : DescribeSpec ({

    // ...

    describe("static property accessor tests") {
        it("uses getters/setters") {
            StaticPerson.firstName = "connor"
            StaticPerson.lastName = "macLeod"

            StaticPerson.fullName shouldBe "Connor MacLeod"
        }
    }
})

class StaticPerson {
    companion object OnlyOne {
        var firstName = ""
            set(value) {
                field = value.replaceFirstChar(Char::titlecase)
            }
        var lastName = ""
            set(value) {
                field = value.replaceFirstChar(Char::titlecase)
            }

        val fullName
            get() = "$firstName $lastName"
    }
}

Another observation to make here is that I have not declared StaticPerson as a nested class here, like I did with the "property accessor tests", above. I tried that, but got an error: Modifier 'companion' is not applicable inside 'local class'. I could not find out why this is the case, but: so be it.

I found a good explanation of why Kotlin doesn't implement the concept of static: Why is there no static keyword in Kotlin?. It's worth reading a bunch of the answers. One of the answers refers back to the docs (Object expressions and declarations › Object declarations › Companion objects):

Note that even though the members of companion objects look like static members in other languages, at runtime those are still instance members of real objects, and can, for example, implement interfaces[…]

From that and the other reading, Kotlin does it this way as it makes things more OO than Java's approach. Seems legit, I guess.


I just tested that theory I had about property assignments within the class itself also call the property's setter, and this proves to be correct:

describe("property accessor tests") {
    class Person {
        var firstName = ""
            set(value) {
                field = value.replaceFirstChar(Char::titlecase)
            }
        var lastName = ""
            set(value) {
                field = value.replaceFirstChar(Char::titlecase)
            }

        val fullName
            get() = "$firstName $lastName"

        fun setBoth(firstName:String, lastName:String) {
            this.firstName = firstName
            this.lastName = lastName
        }
    }

    // ...
    
    it("uses setters on internal property assignments") {
        var person = Person()
        person.setBoth("sojourner", "truth")

        person.fullName shouldBe "Sojourner Truth"
    }
}

If it wasn't calling the setters on firstName and lastName, then fullName would not be capitalised.

That's enough learning for a Sunday.

Righto.

--
Adam

Saturday 3 September 2022

Kotlin: the next morning learning Kotlin stuff

G'day:

Whether you like it or not, I'm just gonna continue on from yesterday's random effort: Kotlin: an afternoon learning Kotlin stuff. Hey: no-one's making you read this stuff.


Next koans exercise: trimMargin

TBH I've never needed functionality like this - it seems very edge-case-y? - and am quite perplexed as to why this is put forward so early in the koans exercises. But… well here it is:

describe("trimMargin tests") {
    it("does what it says on the tin") {
        val testString = """
            | has | as a margin character
            | | is the default btw
        """.trimMargin()

        testString shouldBe " has | as a margin character\n | is the default btw"
    }

    it("handles a different margin") {
        val testString = """
            # has "# "
            # as a margin value
        """.trimMargin("# ")

        testString shouldBe "has \"# \"\nas a margin value"
    }
}

Note in the first one it only strips the indentation and the |, not the space after the |. There's no reason why it should, but I did wonder when writing the test.

I guess "better to have, and not need; than to need, and not have". But I hope if Kotlin has taken to the time to add this, then they've also taken the time to pretty much add every other bloody thing one might want to do to a string first. I can only suppose it's quite closely-related to trimIndent (see y/day's article for that one), so kinda makes sense (ish) to have this too. Can you tell I remain unconvinced? OK: moving on.


String interpolation / template expressions in strings

The koans exercise relating to trimMargin also touched on template expressions:

describe("template expression tests") {
    it("resolves a variable in a string literal") {
        val name = "Zachary"

        "G'day $name" shouldBe "G'day Zachary"
    }

    it("resolves a variable in a raw string") {
        val name = "Joe"

        """
            G'day $name
        """.trimIndent() shouldBe "G'day Joe"
    }

    it("can use curly braces to disambiguate where the variable name ends") {
        val prefix = "SOME_PREFIX_"
        "${prefix}REST_OF_STRING" shouldBe "SOME_PREFIX_REST_OF_STRING"
    }

    it("can take more complicated expressions") {
        val name = "Zachary"

        "G'day ${name.uppercase()}" shouldBe "G'day ZACHARY"
    }

    it("can take blocks of code provided they resolve to a string?") {
        val name = "Joe"
        val case = "lower"

        "G'day ${if (case == "upper"){name.uppercase()} else {name.lowercase()}}" shouldBe "G'day joe"
    }
}

Now I would never advocate doing what I have done in that last example; I was just checking to see if it would work.


Handling exceptions in Kotest tests

Whilst writing that "can use curly braces to disambiguate where the variable name ends" test above, I was not sure if using a "wrong" variable name in the template expression might be a runtime thing that I could catch in test. As it turns out it's checked at compile time so I could not test for it, buit it got me looking into how to expect exceptions in a test.

Unsurprisingly Kotest has good support for this (see Assertions › Exceptions).

package system.kotest

import io.kotest.assertions.throwables.shouldNotThrow
import io.kotest.assertions.throwables.shouldNotThrowAny
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.assertions.throwables.shouldThrowAny
import io.kotest.core.spec.style.DescribeSpec
import java.lang.AssertionError

class MatcherTest : DescribeSpec({

    describe("tests of shouldThrow~ variants") {

        class MySpecificException(message: String) : Exception(message)
        class MyDifferentException(message: String) : Exception(message)

        it("expects an exception") {
            shouldThrowAny {
                throw Exception("any old exception")
            }
        }

        it("expects a specific exception") {
            shouldThrow<MySpecificException> {
                throw MySpecificException("My specific exception")
            }
        }

        it("enforces a specific exception") {
            shouldThrow<AssertionError> {
                shouldThrow<MySpecificException> {
                    throw Exception("My specific exception")
                }
            }
        }

        it("expects an exception to not be thrown") {
            shouldNotThrowAny {
                // NOP
            }
        }

        it("expects a specific exception to not be thrown") {
            shouldNotThrow<MySpecificException> {
                // NOP
            }
        }

        it("expects a specific exception to not be thrown, but bubbles up any different exception") {
            shouldThrow<MyDifferentException> {
                shouldNotThrow<MySpecificException> {
                    throw MyDifferentException("My different exception")
                }
            }
        }

        it("deals with test failures when using the special handling of shouldNotThrow<Any> above") {
            shouldThrow<AssertionError> {
                shouldNotThrow<MySpecificException> {
                    throw MySpecificException("This specific exception is NOT expected, so should cause an AssertionError (which the test expects, so still passes")
                }
            }
        }
    }
})

One thing to note here is how I am using this strategy to test "failing" behaviour in assertion:

it("enforces a specific exception") {
    shouldThrow<AssertionError> {
        // code that SHOULD cause an assertion failure here
    }
}

So in this test:

it("enforces a specific exception") {
    shouldThrow<AssertionError> {
        shouldThrow<MySpecificException> {
            throw Exception("My specific exception")
        }
    }
}

I am testing that shouldThrow<MySpecificException> isn't fulfilled - the code the assertion is testing (throw Exception("My specific exception") doesn't throw a MySpecificException, even though that's what the assertion needs). So a "working" test here is that the test code does actually throw an AssertionError. Hopefully that makes sense. It looks confusing because I'm using the testing framework to test its own assertion behaviour. I'm not sure that paragraph make things clearer or just even worse. Sorry.

Anyhoo, everything works exactly how I'd expect.

Defining a class anywhere

You might have noticed this in the test code above:

class MatcherTest : DescribeSpec({

    describe("tests of shouldThrow~ variants") {

        class MySpecificException(message: String) : Exception(message)
        class MyDifferentException(message: String) : Exception(message)

        // ...

        it("expects a specific exception") {
            shouldThrow<MySpecificException> {
                throw MySpecificException("My specific exception")
            }
        }

        // ...

        it("expects a specific exception to not be thrown, but bubbles up any different exception") {
            shouldThrow<MyDifferentException> {
                shouldNotThrow<MySpecificException> {
                    throw MyDifferentException("My different exception")
                }
            }
        }

        // ...
    }
})

I'm able to define classes - seemingly - anywhere I like (see Nested and inner classes). I need a coupla specifically-typed Exception objects in my tests, so I can just define them where I need them. Before I had a second test needing that MyDifferentException instance, I had that class declaration within the it callback for the test itself. I like this.

Null safety & safe calls

This stuff is compile-time safety so I can't actually write tests for it, but I'll just repeat the koan exercise here (not sure this is a copyright violation? Maybe if I just say "fair use!!" I'll be fine ;-))

The want me to convert this (Java code):

public void sendMessageToClient(
    @Nullable Client client,
    @Nullable String message,
    @NotNull Mailer mailer
) {
    if (client == null || message == null) return;

    PersonalInfo personalInfo = client.getPersonalInfo();
    if (personalInfo == null) return;

    String email = personalInfo.getEmail();
    if (email == null) return;

    mailer.sendMessage(email, message);
}

To Kotlin code that fits into this function declaration:

fun sendMessageToClient(
        client: Client?, message: String?, mailer: Mailer
) {
    TODO()
}

class Client(val personalInfo: PersonalInfo?)
class PersonalInfo(val email: String?)
interface Mailer {
    fun sendMessage(email: String, message: String)
}

And my code to leverage the safe call operator (?.) so that it only needs the one if statement. This is reasonably familiar from CFML territory (and I notice PHP8 now has the null-safe opeator as well (?-> (yes, really)), so hopefully I don't mess-up this exercise…

Before we get to how I did mess up this exercise, I'll note this cool thing IntelliJ does. I copy and pasted the body of the Java method over the TODO() place holder, and IntelliJ came up with this:

This kinda felt like cheating, but I wanted to see what it did, and it came up with this:

fun sendMessageToClient(
        client: Client?, message: String?, mailer: Mailer
) {
    if (client == null || message == null) return

    val personalInfo: PersonalInfo = client.getPersonalInfo() ?: return

    val email = personalInfo.email ?: return

    mailer.sendMessage(email, message)
}

I felt relieved about not cheating cos this doesn't solve the "only one if statement" constraint to my liking. I mean it's only got one if, sure, but it's also got two elvis operator bail-outs, and I didn't want those: that's not in-keeping with the intent of the exercise.

More importantly, IntelliJ was as bemused by the getPersonalInfo call as I was. Where's the getPersonalInfo coming from? They provide the implementation of Client, and it's just this:

class Client(val personalInfo: PersonalInfo?)

I kinda guessed that there was some way I was unaware of to enable synthesised accessor methods on the class properties so a getPersonalInfo was available, but try as I might I could not find how to do it, so I gave up and cheated. The answer they were after was this:

fun sendMessageToClient(
        client: Client?, message: String?, mailer: Mailer
) {
    val email = client?.personalInfo?.email
    if (email != null && message != null) {
        mailer.sendMessage(email, message)
    }
}

Yeah all right. I was being too literal. There was not a way of being able to use getPersonalInfo to access personalInfo, they just expected me to access the thing directly. Looking at it now, IntelliJ's Java -> Kotlin conversion even did this for this statement:

// Java
String email = personalInfo.getEmail();

// converted to Kotlin
val email = personalInfo.email

Sigh. But I wonder why IntelliJ managed to automatically convert that one, but not the getPersonalInfo one? Interesting.

Anyway, I feel dumb now. I mean dumber than before. Grrr.

BTW I did try to just implement a getPersonalInfo method in the Client class just so I could get my example moving, and got this interesting error:

Reading that made me think "OK it's telling me there already is a getPersonalInfo method. OK so WhyTF doesn't it work then?" After cheating and whilst writing these coupla paragraphs up, I did a google and found this on Stack Overflow, in answer to "How to overcome "same JVM signature" error when implementing a Java interface?"):

You could use @JvmField for instructs the compiler not generate getter/setter…

I'm guessing the story is that under the hood Kotlin has generated explicit getters for me for my class's properties, so there's no need (or want) for me to make explicit ones. And Kotlin uses those to enable direct access to the property via client?.personalInfo. Clearly more reading needed on my part here.

I did come across the bit in the Kotlin docs about being implement implicit accessor methods for properties (Getters and setters). I'm sure this is tied into that error message I was getting too. I'm gonna have a look at that stuff… next time. Yes, sorry: there will be a "next time". Probably tomorrow.

OK. I'm gonna treat this dumb feeling I have with beer. Because that'll work.

Righto.

--
Adam

Friday 2 September 2022

Kotlin: an afternoon learning Kotlin stuff

G'day:

This is going to be another fairly stream-of-consciousness effort, much like my earlier "Kotlin / Gradle / Kotest random exploration" article. I currently have very little direction to what I am going to do, but I thought I'd follow along here for the hell of it. As with the previous one (which worked out to be publishable, in the end), I don't even know if I will publish this article yet. I'll check what I end up with… at the end.


A (brief) return to Gradle Kotlin DSL Primer

Last time I meant to work through this lot, but got all of 6% (485 words out of 7268) of the way through before getting side tracked. I was hoping for better today, but I only got to 2% before stumbling across this:

Prerequisites

Yeah. OK. That makes. build.gradle.kts is written in Kotlin, so before knowing the DSL side of things, best I do some actual kotlin language stuff. These koans sound interesting, and following through the link I get to this on that Kotlin Koans page:

You can perform the tasks right inside IntelliJ IDEA or Android Studio by installing the EduTools plugin and choosing Kotlin Koans course.

Cool! So I'm doing that. I'll spare you the installation and getting it started and stuff.

This all looks quite cool and easy to use.

I'm gonna use each question as the basis to direct some of my own exploration, and write more tests (this is me, after all), in my scratch project.


Single-expression functions

Fist things first it's showing some unfamiliar function declaration syntax:

fun start(): String = TODO()

I'm guessing if a function has a single expression and it's the function result, we use this = syntax, so am just gonna run with that theory in a test:

package koans

import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.shouldBe

class FunctionSyntaxTest : DescribeSpec({
    describe("Checking syntax variations") {
        it("returns a single statement's value") {
            fun test():String = "EXPECTED_VALUE"

            test() shouldBe "EXPECTED_VALUE"
        }
    }
})

And this passes. And it fails if I initially expect an empty string, just to keep things red-green-etc.


joinToString and named arguments / defaulted parameters

Next they introduce the joinToString Collection method, specifically to show its named arguments and defaulted parameters (I'm gonna show you my tests, not the Koans stuff as their requirements are necessarily quite focused):

describe("Tests of joinToString") {

    val numbers = listOf<String>("tahi", "rua", "toru", "wha")

    it("takes separator (defaults to comma-space), prefix, postfix named parameters") {
        numbers.joinToString(postfix = ">", prefix = "<") shouldBe "<tahi, rua, toru, wha>"
    }

    it("takes a transformer callback which modifies each element") {
        numbers.joinToString("|", "<", ">") { "\"$it\""  } shouldBe """<"tahi"|"rua"|"toru"|"wha">"""
    }
}

Delimiting strings and escaping doube-quotes

I digress here also to look at this triple-doube-quote string delimiter ("raw strings") I spotted whilst working out if there was a nicer way to escape double quotes than \":

class StringTest : DescribeSpec({
     describe("delimiter tests") {
         it("can be delimited with three quotes (and double quote is otherwise escaped with a backslash") {
             val doubleQuote = """"""" // yeah there's 7 there
             doubleQuote shouldBe "\""
         }
     }
})

Yeah OK that is a daft way of expressing a string containing a double-quote, but it's funny. A bit. Well it is to me.


Variable scope & shadowing

Another side track. I'd set that numbers value in the parent scope, and figured that'd be fine. But also wanted to check I could override a val (immutable) variable in an inner scope:

describe("Checking how scoping of val works") {
    val testVal = "set in describe"

    it("can be accessed in a callback from an outer scope") {
        testVal shouldBe "set in describe"
    }

    it("can be overridden in a callback") {
        val testVal = "set in it"

        testVal shouldBe "set in it"
    }
}

All good. IntelliJ tells me that inner variable is shadowing another one:

The highlighting is there automatically; the tip appears if I hover on the hightlight.

I also note that when I run all my tests, I actually get a warning on this during the build phase:

w: C:\src\kotlin\scratch\src\test\kotlin\language\variables\VariablesTest.kt: (16, 17): Name shadowed: testVal

I will need to find out how to suppress that. I mean other than not shadowing the variable I mean.


Back to named arguments and defaulted parameters (briefly)

The koans notes observe what a fantastic feature of Kotlin this is that instead of all this Java boilerplate:

public String foo(String name, int number, boolean toUpperCase) {
    return (toUpperCase ? name.toUpperCase() : name) + number;
}
public String foo(String name, int number) {
    return foo(name, number, false);
}
public String foo(String name, boolean toUpperCase) {
    return foo(name, 42, toUpperCase);
}
public String foo(String name) {
    return foo(name, 42);
}

One can just do it with one method in Kotlin:

fun foo(name: String, number: Int = 42, toUpperCase: Boolean = false) =
        (if (toUpperCase) name.uppercase() else name) + number

This is quite cute coming from CFML when this has been a thing for 20 years now. And it's literally 20 years! CFMX 6.0 came out in 2002. 20 years ago. God I'm old.

I mean it's still a cool feature. Glad other languages are catching up ;-)


Raw strings and intelligent indentation-handling

This sums it up:

         it("can handle indentation in the triple-quoted string") {
             val message = """    
                 this is the message
             """.trimIndent()
        
             message shouldBe "this is the message"
         }

That is clever/handy.

And here's a second test wherein I have some trailing space on one of the lines of the string:

         it("respects trailing space on trimmed lines") {
             val message = """    
                 this has four space at the end    
             """.trimIndent()

             message shouldBe "this has four space at the end    "
         }

Good.

Here are another couple of examples where initially I thought it was behaving wrong, but I've since worked out why it does things the way it does:

         it("DOES NOT seem to trim indentation if the initial delimiter is not on a line by itself") {
             val message = """first line next to delimiter
                 second line indented
             """.trimIndent()

             // message shouldBe "first line next to delimiter\nsecond line indented"
             message shouldBe "first line next to delimiter\n                 second line indented"
         }

Initially I thought the commented-out expectation was what should happen, but the docs for trimIndent clearly state:

In case if there are non-blank lines with no leading whitespace characters (no indent at all) then the common indent is 0, and therefore this function doesn't change the indentation.

And here that first line - "first line next to delimiter" - has no indentation, sothe common indent is 0, therefore: no trim. All good.

And a second one that confused me:

         it("DOES NOT respect trailing space on the first delimiter line") {
             // the line below still has four trailing spaces
             val message = """    
                 this is indented
             """.trimIndent()

             //message shouldBe "    \ntthis is indented"
             message shouldBe "this is indented"
         }

As per the previous one: the commented-out expectation is what I was initially expecting here. However I'm being a div. I had whittled that case down from the one where there was trailing spaces at the end of the line. But once I remove all the rest of the content from the line it's not trailing space, it's at the beginning of the line. IE: indentation. Which I'm asking to strip.

I really should not expect Kotlin to be so easy to find issues with as ColdFusion or Lucee. I must remember that.

Back in the "that's cool" category: as soon as I typed a multi-line raw string, IntelliJ added the .trimIndent() call. After typing the three double-quotes:

After pressing enter:


So that was a bit random. I kinda cut off quickly there cos that represents my afternoon's investigations. I'm not sure how useful this is to anyone else, but I'm gonna press "publish" anyhow. Why not. It's my blog :-p

Righto.

--
Adam

Sunday 21 August 2022

Kotlin / Gradle / Kotest random exploration

G'day:

I am currently wondering whether this one will ever see light-of-day. It's very much what it says on the tin: I've been messing with my Kotlin ecosystem again, and I've tried and suceeded with some random things, so thought I'd write them down. There's nothing groundbreaking here, I warn you. But it's all new to me.

This follows on from the previous article: "Kotlin: creating a project and getting some code (and tests) to run". This is the very next time I have sat down and opened IntelliJ and gone "OK so now what?".


What I actually sat down to do was to work out what all the shit in the build.gradle.kts file actually means, so I had a google and found Gradle Kotlin DSL Primer. This seemed like a good start. However I only got as far as a mention of "Run ./gradle tasks to get more details" and I thought "Oh yeah, I should probably know how to use the thing from the shell as well as just pushing buttons in the IDE", so dropped down to my shell and ran ./gradlew.bat tasks (note the docs said gradle not gradlew but I did not notice that at first. And the .bat bit is just cos I'm running this in Powershell not in Bash).

I googled the difference between gradle and gradlew, and found this: gradle vs. gradlew – what’s the difference?. Basically gradle runs the underlying Gradle application, whereas gradlew runs the "Gradle Wrapper" which is the app's stand-alone Gradle runner which bundles up a bunch of app-related stuff,a nd indeed will install Gradle if it's not already installed. It's just a "self-containment" sort of thing. Best to read the article to get that clear.

Anyhow, running gradlew.bat tasks output a bunch of stuff which I won't be going back to in this article (told you it was random):

Because I noticed the magic word:

test - Runs the test suite.

(You have to picture me sitting at my keyboard clapping twice to myself and voicing "yay" with more glee than I really ought to be admitting to).


Right so let's run my tests:

PS C:\src\kotlin\scratch> .\gradlew.bat test

BUILD SUCCESSFUL in 1s
3 actionable tasks: 3 up-to-date
PS C:\src\kotlin\scratch>

OK, that's not as exciting as it could be. Indeed I dunno even if it ran some tests or not. Let's red-green-refactor this: I'll break a test…

PS C:\src\kotlin\scratch> .\gradlew.bat test

> Task :test

MainTest > Tests of Main class > MainTest.outputs G'day World & its args FAILED
    io.kotest.assertions.AssertionFailedError at MainTest.kt:12

5 tests completed, 1 failed

> Task :test FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':test'.
> There were failing tests. See the report at: file:///C:/src/kotlin/scratch/build/reports/tests/test/index.html

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 4s
3 actionable tasks: 2 executed, 1 up-to-date
PS C:\src\kotlin\scratch>

Whoa. OK. I guess if the build is green: all tests pass. If not: it'll let me know.

But what's this report it speaks of:

> There were failing tests. See the report at: file:///C:/src/kotlin/scratch/build/reports/tests/test/index.html

Let's have a look at that:

I like that. And I can drill down into my failed test too:

Let's fix the test and look at the passing report:


Cool. My next random thought was "hrmph: PowerShell". I don't like PowerShell. Largely cos I've never bothered to learn to use it, either just using the old school Windows shell, or more often when I'm needing to use a shell it's in a *nix environment, and I use Bash. So let's see if I can do all this in Bash as well:

adam@DESKTOP-QV1A45Uadam@DESKTOP-QV1A45U:~$ cd /mnt/c/src/kotlin/scratch/
adam@DESKTOP-QV1A45U:/mnt/c/src/kotlin/scratch$ ./gradlew test
Starting a Gradle Daemon (subsequent builds will be faster)

> Task :test

MainTest > Tests of Main class > MainTest.outputs G'day World & its args FAILED
    io.kotest.assertions.AssertionFailedError at MainTest.kt:12

MainTest > Tests of Main class > MainTest.works OK with no args FAILED
    io.kotest.assertions.AssertionFailedError at MainTest.kt:19

5 tests completed, 2 failed

> Task :test FAILED

Oh. :-(

However checking the report and drilling down into the failures, it made sense:

io.kotest.assertions.AssertionFailedError:
(contents match, but line-breaks differ; output has been escaped to show line-breaks)
expected:<G'day World!\r\nProgram arguments: some arg, some other arg>
but was:<G'day World!\nProgram arguments: some arg, some other arg>

It's the old "Windows uses CRLF / *nix uses LF" thing, and my test has hard-coded CRLF in it:

output.trim() shouldBe "G'day World!\r\nProgram arguments: some arg, some other arg"

I'm sure there's a constant or something in Java that is the current EOL marker for the given operating system. I googled around and found this: Stack Overflow › Is there a Newline constant defined in Java like Environment.Newline in C#?, and the answer is System.lineSeparator() (integrated into my tests now):

class MainTest : DescribeSpec ({
    describe("Tests of Main class") {
        val EOL = System.lineSeparator()

        it("outputs G'day World & its args") {
            val testArgs = arrayOf("some arg", "some other arg")
            val output = SystemLambda.tapSystemOut {
                main(testArgs)
            }
            output.trim() shouldBe "G'day World!${EOL}Program arguments: some arg, some other arg"
        }
        it("works OK with no args") {
            val testArgs = arrayOf<String>()
            val output = SystemLambda.tapSystemOut {
                main(testArgs)
            }
            output.trim() shouldBe "G'day World!${EOL}Program arguments:"
        }
    }
})

Right so lets see if the tests work in both environments now. Powershell:

BUILD SUCCESSFUL in 1s

Bash:

BUILD SUCCESSFUL in 1s

Cool.


Oh I need to back up a bit. I also had to go find out how to do interpolated strings in Kotlin as well for that:

output.trim() shouldBe "G'day World!${EOL}Program arguments:"

I googled-up this page: How Does String Interpolation Work in Kotlin?, which says one just prefixes the variable name with $. As my EOL variable usage is hard-up against another string literal, I needed to (guess) that one should surround it in curly braces to make it clear where variable name stopped and string literal started. Otherwise I got this: Unresolved reference: EOLProgram.


The last thing I thought about was "but what if there was no generic solution for the test, and I had to only conditionally run a test given some criterion?" Kotest can do this: Conditional tests with enabled flags. I put together a new test to show myself this enabled flag in action:

import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.shouldBe
import org.apache.commons.lang3.SystemUtils

class KotestTest : DescribeSpec( {
    describe("Testing test conditionality") {
        it ("only runs on linux").config(enabled = SystemUtils.IS_OS_LINUX) {
            SystemUtils.IS_OS_LINUX shouldBe true
        }
    }
})

To use SystemUtils I had to include Apache Commons as a dependency in build.gradle.kts:

dependencies {
    implementation("org.apache.commons:commons-lang3:3.12.0")

    testImplementation(kotlin("test"))
    testImplementation("io.kotest:kotest-runner-junit5:5.4.2")
    testImplementation("io.kotest:kotest-assertions-core:5.4.2")
    testImplementation("com.github.stefanbirkner:system-lambda:1.2.1")
}

BTW I'm finding the package name by just googling something like "apache commons maven", and it leads me to a page like this: https://mvnrepository.com/artifact/org.apache.commons/commons-lang3/3.12.0:

That even gives me the statement to use in my build file. In hindsight I should probably only be using that as a test dependency, so I'll change that:

testImplementation("org.apache.commons:commons-lang3:3.12.0")

Also note that after discussion with SeanC in the Working Code Podcast Discord channel, I have stopped using the ranged constraints for my dependencies (eg: something like 3.12.+), but using concrete ones (eg: something like 3.12.0) like I have here. I've changed all the other ones I alerady had, too. This is just "the Java way" apparently. Duly noted.

OK so now I run the tests, and check if the conditionality works. The difference is best seen in the test report:

Bash:

Powershell:

Excellent.


At this point I had decided I had had enough, and also thought maybe I could get a blog article out of it (still not sure I have, but hey… the bar is set pretty low around here so I'm gonna press "publish" anyhow).

Just to recap, this is what I learned in this exercise:

  • I found that docs page on the Kotlin Gradle DSL which I will come back to (Gradle Kotlin DSL Primer),
  • I had the most superficial of looks at the gradlew shell runner thingey.
  • I got a better understanding of gradle vs gradlew.
  • I found out how to run the tests on same.
  • And discovered that auto-generated HTML test results page.
  • The tests will run just fine on Powershell and Bash.
  • System.lineSeparator() contains the platform-specific line ending.
  • How to do interpolated strings, eg: "G'day World!${EOL}Program arguments:".
  • How to set a a condition on a Kotest test to control whether it's ignored.
  • How to find what the dependency string and version is for the implementation/testImplementation statement is, from Maven.

There's no rocket science there, nor anything complicated. But I've only just started looking at this stuff so can't expect too much. Everything was really easy to find, and worked with a minimum of fuss, too.

Let me know if this article is too "all over the place" or it held any interest or not. I actually found it quite useful myself for formalising the stuff I had looked at, so I guess it's at least useful to me if not for anyone else.

Righto.

--
Adam


PS: Oh! I also learned how to pass args to my app when I run it. Cos like I didn't just test it, I also ran it:

adam@DESKTOP-QV1A45U:/mnt/c/src/kotlin/scratch$ ./gradlew run --args "first second third"

> Task :run
G'day World!
Program arguments: first, second, third  

PPS: the code for this is at https://github.com/adamcameron/kotlin_scratch/tree/1.1.

Tuesday 16 August 2022

Kotlin: creating a project and getting some code (and tests) to run

G'day

This whole thing is gonna be a cross between a note-to-self and a pseudo-stream-of-consciousness as I set up a new Kotlin project in IntelliJ IDEA, and demonstrate to myself I can make some code run, as well as some tests. Why am I doing this? Well because in the last eight months I've been through this exercise half a dozen times, with a month or two between each one, and every time it's been a right pain in the arse to work out what I need to do. Context: I am 100% new to Kotlin, and I am 100% new to Gradle, and I am my past has been the luxury of writing apps in scripting languages (CFML, PHP) that need close to zero messing about to get to the "G'day World" stage (and tests thereof) into production. It's easy. Any idiot can do it (ahem). So it's annoying that I need to do all this faffing about. I hasten to add I am not going as far as "getting into production" here: I don't even know what I need to put into production if I wanted to. I'm just getting to the point where code runs in the IDE.

I worked through all this yesterday evening and early this evening, and I think I know the steps now. So I've blitzed my previous project and am starting again.

New Project

I've opened IntelliJ IDEA and clicked "New Project", and completed the form on the ensuing screen:

And I click "Create".

Project files

Having done that, I get this lot:

/mnt/c/src/kotlin/scratch$ tree
.
├── build.gradle.kts
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
└── src
    ├── main
    │   ├── kotlin
    │   │   └── Main.kt
    │   └── resources
    └── test
        ├── kotlin
        └── resources

9 directories, 8 files

scratch/src/main/kotlin/Main.kt

It's created a G'day world function for me, but there's a typo in it:

fun main(args: Array<String>) {
    println("Hello World!")

    // Try adding program arguments via Run/Debug configuration.
    // Learn more about running applications: https://www.jetbrains.com/help/idea/running-applications.html.
    println("Program arguments: ${args.joinToString()}")
}

Fixed:

println("G'day World!")


scratch/gradle.properties

kotlin.code.style=official

OK. Shrug.

scratch/settings.gradle.kts

rootProject.name = "scratch"

Yup. Another shrug.

scratch/gradlew.bat / scratch/gradlew

These are shell scripts Gradle seems to create to "do stuff". It's boilerplate and I won't be touching it.

scratch/.gradle / scratch/.idea / scratch/gradle directories

More boilerplace that I don't seem to need to know about ATM. Well the .idea dir is the project config dir for Intellif IDEA, I know that.

scratch/build.gradle.kts

This is the file I seem to spend all my time in. The default contents are:

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    kotlin("jvm") version "1.7.10"
    application
}

group = "me.adamcameron"
version = "1.0-SNAPSHOT"

repositories {
    mavenCentral()
}

dependencies {
    testImplementation(kotlin("test"))
}

tasks.test {
    useJUnitPlatform()
}

tasks.withType<KotlinCompile> {
    kotlinOptions.jvmTarget = "1.8"
}

application {
    mainClass.set("MainKt")
}

That's all lovely. Other than recognising some of the words, I have NFI what's going on really. Well I superficially do, but exactly the reason it is the way it is: NFI. As far as I can tell this is the Gradle equivalent of composer.json or packages.json or some such. Except it's doing a bunch more than just package management.

Build and run

Anyway, I guess the new project wizard knows what it's doing, so I'm gonna try to build and run the project.

And in the build window I get this lot:

  21:24:31: Executing ':classes :testClasses'...

> Task :wrapper

BUILD SUCCESSFUL in 295ms
1 actionable task: 1 executed
> Task :processResources NO-SOURCE
> Task :processTestResources NO-SOURCE
> Task :compileKotlin
> Task :compileJava NO-SOURCE
> Task :classes UP-TO-DATE
> Task :compileTestKotlin NO-SOURCE
> Task :compileTestJava NO-SOURCE
> Task :testClasses UP-TO-DATE

BUILD SUCCESSFUL in 4s
1 actionable task: 1 executed
21:24:37: Execution finished ':classes :testClasses'.

OK. That seems promising in that it's not screaming at me that I've done it all wrong, so. Um. I go back to Main.kt and run it:

Result:

C:\Users\camer\.jdks\openjdk-17.0.1\bin\java.exe …
G'day World!
Program arguments: 

Process finished with exit code 0

Excellent. The default project config will run.

Installing Kotest

It would not be me if I didn't do some testing. I actually feel a bit bad running the app before having tests for it, but… small steps.

I wanna use Kotest for my testing, so I'll work out how to install that.

Distilling the info from that quickstart, I've made these changes to my build.gradle.kts:


// ...
dependencies {
    testImplementation(kotlin("test"))
    testImplementation("io.kotest:kotest-runner-junit5:5.4.2")
    testImplementation("io.kotest:kotest-assertions-core:5.4.2")
}

tasks.test {
    useJUnitPlatform()
}
tasks.withType<Test>().configureEach {
    useJUnitPlatform()
}

Note that the docs actually said to use literally $version but after having done that and it trying to use my app's version:

version = "1.0-SNAPSHOT"

I inferred that that was just unfortunate shorthand, and they mean "the version you want to install". Checking io.kotest:kotest-runner-junit5 and io.kotest:kotest-assertions-core in the Maven repo, it's 5.4.2 for each, so that's why I'm using that for the version.

That rebuilt OK.

Testing Kotest install

So some tests!

// scratch/src/test/kotlin/SystemTest.kt
import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldStartWith

class SystemTest : DescribeSpec({
    describe("Tests of kotest installation") {
        it("should return the size of a string") {
            "hello".length shouldBe 5
        }
        it("should test for the prefix of a string") {
            "world".shouldStartWith("wor")
        }
    }
})

These are just testing that Kotest will do something, and use a couple of the assertions from its assertion lib.

Unfortunately I've gone backwards and forwards with this file whilst testing what I was saying above, but I don't think I have missed any steps here. Anyways, after a rebuild I was able to tell IntelliJ to run the tests:

And the results:

I also have the Kotest plugin for IntelliJ installed, so I can run them from there too:

Testing main

And now I need to get back to the "red" part of "red-green-refactor" of my initial main method. This is a tricky one cos it doesn't return a value, it outputs to stdout (println("G'day World!") etc). I thougth it must be possible to capture stdout from a test and test it, so I sniffed around.

I came across this project on Github (can't remember how I found it): stefanbirkner / system-lambda. This was an easy install:

dependencies {
    testImplementation(kotlin("test"))
    testImplementation("io.kotest:kotest-runner-junit5:5.4.+")
    testImplementation("io.kotest:kotest-assertions-core:5.4.+")
    testImplementation("com.github.stefanbirkner:system-lambda:1.2.+")
}

(Also note how I've worked out that version number is a constraint, so I've changed the all to get the latest in the given point release, with the +).

After a rebuild I can add a quick system test in to test the installation of the module:

import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldStartWith
import com.github.stefanbirkner.systemlambda.SystemLambda.*

class SystemTest : DescribeSpec({
    describe("Tests of kotest installation") {
        it("should return the size of a string") {
            "hello".length shouldBe 5
        }
        it("should test for the prefix of a string") {
            "world".shouldStartWith("wor")
        }
    }
    describe("tests of system-lambda") {
        it("checks system-lambda is working OK") {
            var testString = "string to capture"
            val output = tapSystemOut {
                print(testString)
            }
            output shouldBe testString
        }
    }
})

It's quite pleasing how the Kotest plugin picked up the new test automatically:

And see it passing:

And now a test actually testing main:

import com.github.stefanbirkner.systemlambda.SystemLambda
import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.shouldBe

class MainTest : DescribeSpec ({
    describe("Tests of Main class") {
        it("outputs G'day World & its args") {
            val testArgs = arrayOf("some arg", "some other arg")
            val output = SystemLambda.tapSystemOut {
                main(testArgs)
            }
            output.trim() shouldBe "G'day World!\r\nProgram arguments: some arg, some other arg"
        }
    }
})

And watch it pass:

Whilst writing that, I figured I should have a test where I don't pass any args. Not least of all cos I had no idea how to make an empty typed array in Kotlin. It's dead easy as it turns out:

it("works OK with no args") {
    val testArgs = arrayOf<String>()
    val output = SystemLambda.tapSystemOut {
        main(testArgs)
    }
    output.trim() shouldBe "G'day World!\r\nProgram arguments:"
}

Done

And that's about it. I've been through this process three times in the last 24hrs now, and I'm having fewer problems. I still don't really "get" what Gradle is doing (or how), but I know enough to be getting on with some language testing now anyhow. And I'm sure the more I need to mess around with stuff, the more Gradle expertise I will pick-up.

And I'm quite pleased to have a "Kotlin" blog article. Finally. Even though it's just the travails of a noob.

All the code in the project is here: https://github.com/adamcameron/kotlin_scratch/tree/1.0. I have not yet re-cloned it and created a project from that to see if it all works A-OK. I will report back later. Time for one last beer and bed now though.

Righto.

--
Adam