diff --git a/BUILDING.md b/BUILDING.md index 54b6021e97..06dec12b15 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -278,6 +278,25 @@ call does not guarantee lint finishes first): The reports persist under each module's `build/reports/`, so re-run lint only when you want fresh lint data in the next scan. +### 5. Optional: include test coverage + +Coverage comes from [Kover](https://github.com/Kotlin/kotlinx-kover). The root +`koverXmlReportCoverage` task runs the unit-test suites of the covered modules +(`quartz`, `commons`, `quic`, `nestsClient`, `geode`, `cli`, `relayBench`, +`desktopApp`, and `amethyst`'s `fdroidDebug` variant) and merges the result +into `build/reports/kover/reportCoverage.xml`, which the scanner imports +automatically when it exists. Same recipe as lint — generate first, scan +second: + +```bash +./gradlew koverXmlReportCoverage +./gradlew sonar +``` + +For a human-readable version of the same data, run +`./gradlew koverHtmlReportCoverage` and open +`build/reports/kover/htmlCoverage/index.html`. + Every `sonar.*` entry in `local.properties` is forwarded to the scanner, so any [analysis parameter](https://docs.sonarsource.com/sonarqube-server/latest/analyzing-source-code/analysis-parameters/) can be set there. `sonar.projectKey` / `sonar.projectName` default to the root diff --git a/amethyst/build.gradle.kts b/amethyst/build.gradle.kts index e4df9eb8bf..d7d65cc0cb 100644 --- a/amethyst/build.gradle.kts +++ b/amethyst/build.gradle.kts @@ -6,6 +6,7 @@ plugins { alias(libs.plugins.jetbrainsComposeCompiler) alias(libs.plugins.serialization) alias(libs.plugins.googleKsp) + alias(libs.plugins.kotlinxKover) } fun getCurrentBranch(workingDir: java.io.File): String = diff --git a/build.gradle.kts b/build.gradle.kts index 6b87fbd049..5d6326492d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -45,6 +45,50 @@ plugins { alias(libs.plugins.androidKotlinMultiplatformLibrary) apply false alias(libs.plugins.serialization) alias(libs.plugins.googleKsp) apply false + alias(libs.plugins.kotlinxKover) +} + +// Aggregated code coverage: `./gradlew koverXmlReportCoverage` runs the unit +// test suites of the modules below and merges their coverage into +// build/reports/kover/reportCoverage.xml (JaCoCo-compatible XML, importable by +// SonarQube — see BUILDING.md). `koverHtmlReportCoverage` renders the same +// data for humans. A named variant is used instead of Kover's default total +// report because the total for an Android project unconditionally merges +// EVERY build variant — compiling all release/benchmark variants of +// :amethyst just to measure unit tests. The "coverage" variant below picks +// one representative compilation per project: the `jvm` target for KMP/JVM +// modules and `playDebug` for :amethyst — play is the primary flavor and the +// larger code surface (both `add`s are optional so the one block fits every +// project). :nappletHost stays out — it has no tests. +dependencies { + kover(project(":amethyst")) + kover(project(":quartz")) + kover(project(":commons")) + kover(project(":quic")) + kover(project(":nestsClient")) + kover(project(":geode")) + kover(project(":cli")) + kover(project(":relayBench")) + kover(project(":desktopApp")) +} + +kover { + currentProject { + createVariant("coverage") {} + } +} + +subprojects { + plugins.withId("org.jetbrains.kotlinx.kover") { + configure { + currentProject { + createVariant("coverage") { + add("jvm", optional = true) + add("playDebug", optional = true) + } + } + } + } } // Shared app version for all subprojects — read from gradle/libs.versions.toml. @@ -120,6 +164,15 @@ if (sonarEnabled) { .filter { it.startsWith("sonar.") } .forEach { System.setProperty(it, sonarProperties.getProperty(it)) } + // Import Kover's aggregated coverage (run `./gradlew koverXmlReportCoverage` + // first) unless the operator pointed the scanner somewhere else. + if (System.getProperty("sonar.coverage.jacoco.xmlReportPaths") == null) { + System.setProperty( + "sonar.coverage.jacoco.xmlReportPaths", + layout.buildDirectory.file("reports/kover/reportCoverage.xml").get().asFile.absolutePath, + ) + } + // The scanner's sonarResolver task reads AGP's generated-res-values provider // but doesn't depend on the task that produces it — wire it up in every // module that has both (today only :amethyst enables resValues, but the diff --git a/cli/build.gradle.kts b/cli/build.gradle.kts index 489e604740..7d26b235b1 100644 --- a/cli/build.gradle.kts +++ b/cli/build.gradle.kts @@ -3,6 +3,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.jetbrainsKotlinJvm) application + alias(libs.plugins.kotlinxKover) } kotlin { diff --git a/commons/build.gradle.kts b/commons/build.gradle.kts index 8182a3604b..b71358533c 100644 --- a/commons/build.gradle.kts +++ b/commons/build.gradle.kts @@ -25,6 +25,7 @@ plugins { alias(libs.plugins.jetbrainsComposeCompiler) alias(libs.plugins.composeMultiplatform) alias(libs.plugins.serialization) + alias(libs.plugins.kotlinxKover) } kotlin { diff --git a/desktopApp/build.gradle.kts b/desktopApp/build.gradle.kts index 10ca2b3d48..f49cad7ba2 100644 --- a/desktopApp/build.gradle.kts +++ b/desktopApp/build.gradle.kts @@ -6,6 +6,7 @@ plugins { alias(libs.plugins.jetbrainsKotlinJvm) alias(libs.plugins.composeMultiplatform) alias(libs.plugins.jetbrainsComposeCompiler) + alias(libs.plugins.kotlinxKover) } // RPM rejects dashes in version strings — replace with tilde (~) which RPM uses diff --git a/geode/build.gradle.kts b/geode/build.gradle.kts index ee636965c7..46e9f1cf23 100644 --- a/geode/build.gradle.kts +++ b/geode/build.gradle.kts @@ -5,6 +5,7 @@ plugins { alias(libs.plugins.serialization) application `java-test-fixtures` + alias(libs.plugins.kotlinxKover) } application { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3d768731a6..cc1cf2bad1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -35,6 +35,7 @@ kmpTorResource = "409.5.0" junit = "4.13.2" kchesslib = "1.0.5" kotlin = "2.4.0" +kover = "0.9.8" kotlinxCollectionsImmutable = "0.5.0" kotlinxCoroutinesCore = "1.11.0" kotlinxSerialization = "1.11.0" @@ -246,6 +247,7 @@ jetbrainsKotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } jetbrainsComposeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } serialization = { id = 'org.jetbrains.kotlin.plugin.serialization', version.ref = 'kotlin' } kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +kotlinxKover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } androidKotlinMultiplatformLibrary = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" } vanniktech-mavenPublish = { id = "com.vanniktech.maven.publish", version.ref = "mavenPublish" } composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" } diff --git a/nestsClient/build.gradle.kts b/nestsClient/build.gradle.kts index 3cb05ae465..bb5acd43a2 100644 --- a/nestsClient/build.gradle.kts +++ b/nestsClient/build.gradle.kts @@ -5,6 +5,7 @@ plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.androidKotlinMultiplatformLibrary) alias(libs.plugins.serialization) + alias(libs.plugins.kotlinxKover) } kotlin { diff --git a/quartz/build.gradle.kts b/quartz/build.gradle.kts index 10c0817ee8..108587341f 100644 --- a/quartz/build.gradle.kts +++ b/quartz/build.gradle.kts @@ -9,6 +9,7 @@ plugins { alias(libs.plugins.androidKotlinMultiplatformLibrary) alias(libs.plugins.serialization) alias(libs.plugins.vanniktech.mavenPublish) + alias(libs.plugins.kotlinxKover) } kotlin { diff --git a/quic/build.gradle.kts b/quic/build.gradle.kts index def6dad697..55c28f88d1 100644 --- a/quic/build.gradle.kts +++ b/quic/build.gradle.kts @@ -23,6 +23,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.androidKotlinMultiplatformLibrary) + alias(libs.plugins.kotlinxKover) } kotlin { diff --git a/relayBench/build.gradle.kts b/relayBench/build.gradle.kts index 8a02cc1e96..831f524883 100644 --- a/relayBench/build.gradle.kts +++ b/relayBench/build.gradle.kts @@ -3,6 +3,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.jetbrainsKotlinJvm) application + alias(libs.plugins.kotlinxKover) } application {