Skip to content

Commit

Permalink
Add Kotlin publishing and testing examples (#3589)
Browse files Browse the repository at this point in the history
Another addition for #3451: it adds `publishing` and `testing` examples
for Kotlin.

The only thing that needs to be mentioned is test filtering in Kotest:
it doesn't have `package.class.method` format to do the filtering,
instead it is using `specs` + `classes` parameters (see
[here](https://kotest.io/docs/framework/conditional/conditional-tests-with-gradle.html)).

Also `mockito-kotlin` requires JVM bytecode target to be 11.

Co-authored-by: 0xnm <0xnm@users.noreply.github.com>
Co-authored-by: Li Haoyi <haoyi.sg@gmail.com>
  • Loading branch information
3 people authored Sep 22, 2024
1 parent 4a8b61f commit c5bda0d
Show file tree
Hide file tree
Showing 18 changed files with 313 additions and 0 deletions.
34 changes: 34 additions & 0 deletions example/kotlinlib/publishing/2-publish-module/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//// SNIPPET:BUILD
package build
import mill._, kotlinlib._, publish._

object foo extends KotlinModule with PublishModule {

def mainClass = Some("foo.FooKt")

def kotlinVersion = "1.9.24"

def publishVersion = "0.0.1"

def pomSettings = PomSettings(
description = "Hello",
organization = "com.lihaoyi",
url = "https://github.com/lihaoyi/example",
licenses = Seq(License.MIT),
versionControl = VersionControl.github("lihaoyi", "example"),
developers = Seq(Developer("lihaoyi", "Li Haoyi", "https://github.com/lihaoyi"))
)
}

// This is an example `KotlinModule` with added publishing capabilities via
// `PublishModule`. This requires that you define an additional
// `publishVersion` and `pomSettings` with the relevant metadata, and provides
// the `.publishLocal` and `publishSigned` tasks for publishing locally to the
// machine or to the central maven repository

/** Usage

> mill foo.publishLocal
Publishing Artifact(com.lihaoyi,foo,0.0.1) to ivy repo...

*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package foo

fun main(args: Array<String>) = println("Hello World")
9 changes: 9 additions & 0 deletions example/kotlinlib/testing/1-test-suite/bar/src/bar/Bar.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package bar

open class Bar {

fun hello(): String = "Hello World"

}

fun main(args: Array<String>) = println(Bar().hello())
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package bar

import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldStartWith
import io.kotest.matchers.string.shouldEndWith
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

class BarTests : FunSpec({

test("hello") {
val result = Bar().hello()
result shouldStartWith "Hello"
}

test("world") {
val result = Bar().hello()
result shouldEndWith "World"
}

test("mockito") {
val mockBar = mock<Bar>()

whenever(mockBar.hello()) doReturn "Hello Mockito World"

val result = mockBar.hello()

result shouldBe "Hello Mockito World"
verify(mockBar).hello()
}
})
44 changes: 44 additions & 0 deletions example/kotlinlib/testing/1-test-suite/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//// SNIPPET:BUILD1
package build
import mill._, kotlinlib._

object foo extends KotlinModule {

def mainClass = Some("foo.FooKt")

def kotlinVersion = "1.9.24"

object test extends KotlinModuleTests {
def testFramework = "com.github.sbt.junit.jupiter.api.JupiterFramework"
def ivyDeps = Agg(
ivy"com.github.sbt.junit:jupiter-interface:0.11.4",
ivy"io.kotest:kotest-runner-junit5-jvm:5.9.1",
ivy"org.mockito.kotlin:mockito-kotlin:5.4.0"
)

// This is needed because of the "mockito-kotlin"
def kotlincOptions = super.kotlincOptions() ++ Seq("-jvm-target", "11")
}
}
// This build defines a single module with a test suite, configured to use
// "JUnit" + "Kotest" as the testing framework, along with Mockito. Test suites are themselves
// ``KotlinModule``s, nested within the enclosing module,
//// SNIPPET:BUILD2

object bar extends KotlinModule {

def mainClass = Some("bar.BarKt")

def kotlinVersion = "1.9.24"

object test extends KotlinModuleTests with TestModule.Junit5 {
def ivyDeps = super.ivyDeps() ++ Agg(
ivy"io.kotest:kotest-runner-junit5-jvm:5.9.1",
ivy"org.mockito.kotlin:mockito-kotlin:5.4.0"
)

// This is needed because of the "mockito-kotlin"
def kotlincOptions = super.kotlincOptions() ++ Seq("-jvm-target", "11")
}
}

9 changes: 9 additions & 0 deletions example/kotlinlib/testing/1-test-suite/foo/src/foo/Foo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package foo

open class Foo {

fun hello(): String = "Hello World"

}

fun main(args: Array<String>) = println(Foo().hello())
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package foo

import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldStartWith
import io.kotest.matchers.string.shouldEndWith
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

class FooTests : FunSpec({

test("hello") {
val result = Foo().hello()
result shouldStartWith "Hello"
}

test("world") {
val result = Foo().hello()
result shouldEndWith "World"
}

test("mockito") {
val mockFoo = mock<Foo>()

whenever(mockFoo.hello()) doReturn "Hello Mockito World"

val result = mockFoo.hello()

result shouldBe "Hello Mockito World"
verify(mockFoo).hello()
}
})
5 changes: 5 additions & 0 deletions example/kotlinlib/testing/2-test-deps/baz/src/baz/Baz.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package baz

object Baz {
const val VALUE = 123
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package baz

object BazTestUtils {

fun bazAssertEquals(x: Any, y: Any) {
println("Using BazTestUtils.bazAssertEquals")
if (x != y) {
throw AssertionError("Expected $y, but got $x")
}
}
}
12 changes: 12 additions & 0 deletions example/kotlinlib/testing/2-test-deps/baz/test/src/baz/BazTests.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package baz

import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import com.google.common.math.IntMath

class BazTests : FunSpec({

test("simple") {
BazTestUtils.bazAssertEquals(Baz.VALUE, IntMath.mean(122, 124))
}
})
30 changes: 30 additions & 0 deletions example/kotlinlib/testing/2-test-deps/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//// SNIPPET:BUILD
package build
import mill._, kotlinlib._

object qux extends KotlinModule {

def kotlinVersion = "1.9.24"

def moduleDeps = Seq(baz)

object test extends KotlinModuleTests with TestModule.Junit5 {
def moduleDeps = super.moduleDeps ++ Seq(baz.test)
def ivyDeps = super.ivyDeps() ++ Agg(
ivy"io.kotest:kotest-runner-junit5-jvm:5.9.1",
ivy"com.google.guava:guava:33.3.0-jre"
)
}
}

object baz extends KotlinModule {

def kotlinVersion = "1.9.24"

object test extends KotlinModuleTests with TestModule.Junit5 {
def ivyDeps = super.ivyDeps() ++ Agg(
ivy"io.kotest:kotest-runner-junit5-jvm:5.9.1",
ivy"com.google.guava:guava:33.3.0-jre"
)
}
}
5 changes: 5 additions & 0 deletions example/kotlinlib/testing/2-test-deps/qux/src/qux/Qux.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package qux

object Qux {
const val VALUE = "xyz"
}
12 changes: 12 additions & 0 deletions example/kotlinlib/testing/2-test-deps/qux/test/src/qux/QuxTests.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package qux

import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import com.google.common.base.Ascii

class QuxTests : FunSpec({

test("simple") {
baz.BazTestUtils.bazAssertEquals(Ascii.toLowerCase("XYZ"), Qux.VALUE)
}
})
21 changes: 21 additions & 0 deletions example/kotlinlib/testing/3-integration-suite/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//// SNIPPET:BUILD3
package build
import mill._, kotlinlib._
object qux extends KotlinModule {

def kotlinVersion = "1.9.24"

object test extends KotlinModuleTests with TestModule.Junit5 {
def ivyDeps = super.ivyDeps() ++ Agg(
ivy"io.kotest:kotest-runner-junit5-jvm:5.9.1"
)
}
object integration extends KotlinModuleTests with TestModule.Junit5 {
def ivyDeps = super.ivyDeps() ++ Agg(
ivy"io.kotest:kotest-runner-junit5-jvm:5.9.1"
)
}
}

// The integration suite is just another regular test module within the parent KotlinModule
// (This example also demonstrates using Junit 5 instead of Junit 4)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package qux

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

class QuxIntegrationTests : FunSpec({

test("helloworld") {
val result = Qux.hello()
result shouldBe "Hello World"
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package qux

object Qux {
fun main(args: Array<String>) = println(hello())

fun hello(): String = "Hello World"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package qux

import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldStartWith
import io.kotest.matchers.string.shouldEndWith

class QuxTests : FunSpec({

test("hello") {
val result = Qux.hello()
result shouldStartWith "Hello"
}

test("world") {
val result = Qux.hello()
result shouldEndWith "World"
}
})
12 changes: 12 additions & 0 deletions example/package.mill
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ object `package` extends RootModule with Module {
object builds extends Cross[ExampleCrossModuleKotlin](build.listIn(millSourcePath / "builds"))
object linting extends Cross[ExampleCrossModuleKotlin](build.listIn(millSourcePath / "linting"))
object module extends Cross[ExampleCrossModuleKotlin](build.listIn(millSourcePath / "module"))
object publishing extends Cross[ExampleCrossModuleKotlin](build.listIn(millSourcePath / "publishing"))
object testing extends Cross[ExampleCrossModuleKotlin](build.listIn(millSourcePath / "testing"))
}
object scalalib extends Module {
object basic extends Cross[ExampleCrossModule](build.listIn(millSourcePath / "basic"))
Expand Down Expand Up @@ -77,6 +79,16 @@ object `package` extends RootModule with Module {
.replace("bar.BarTests.escaping", "bar.BarTestsescaping")
case "4-builtin-commands" => line.replace("compile.dest/zinc", "compile.dest/kotlin.analysis.dummy")
case "5-resources" => line.replace("FooTests.simple", "FooTestssimple")
case "1-test-suite" => line
.replace("mill bar.test bar.BarTests.hello", "kotest_filter_tests='hello' kotest_filter_specs='bar.BarTests' ./mill bar.test")
.replace("FooTests.hello", "FooTestshello")
.replace("FooTests.world", "FooTestsworld")
.replace("BarTests.hello", "BarTestshello")
.replace("BarTests.world", "BarTestsworld")
.replace("compiling 1 ... source...", "Compiling 1 ... source...")
case "2-test-deps" => line
.replace("qux.QuxTests.simple", "qux.QuxTestssimple")
.replace("baz.BazTests.simple", "baz.BazTestssimple")
case _ => line
}
}
Expand Down

0 comments on commit c5bda0d

Please sign in to comment.