Skip to content

Commit e03bc8c

Browse files
authored
Implemented protype of Kover Aggregation Plugin
Relates #608 PR #644
1 parent 325bd50 commit e03bc8c

34 files changed

+1377
-6
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ in this case report will be generated for current project joined with `:another:
113113

114114
**More examples of Gradle plugin applying can be found in [example folder](kover-gradle-plugin/examples)**
115115

116+
## Kover Aggregated Plugin
117+
Kover Aggregated Plugin as a prototype of Gradle Settings plugin, created to simplify the setup of multi-project builds.
118+
It is in its infancy, it is recommended to use it only for test or pet projects.
119+
120+
Refer to the [documentation](https://kotlin.github.io/kotlinx-kover/gradle-plugin/aggregated.html) for details.
121+
116122
## Kover CLI
117123
Standalone JVM application used for offline instrumentation and generation of human-readable reports.
118124

build-logic/src/main/kotlin/kotlinx/kover/conventions/kover-publishing-conventions.gradle.kts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,3 +210,8 @@ val Project.sourceSets: SourceSetContainer
210210

211211
val SourceSetContainer.main: NamedDomainObjectProvider<SourceSet>
212212
get() = named<SourceSet>("main")
213+
214+
signing {
215+
// disable signing if private key isn't passed
216+
isRequired = findProperty("libs.sign.key.private") != null
217+
}

kover-gradle-plugin/api/kover-gradle-plugin.api

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
public abstract interface class kotlinx/kover/gradle/aggregation/settings/dsl/KoverSettingsExtension {
2+
public abstract fun enableCoverage ()V
3+
public abstract fun getReports ()Lkotlinx/kover/gradle/aggregation/settings/dsl/ReportsSettings;
4+
public abstract fun reports (Lorg/gradle/api/Action;)V
5+
}
6+
7+
public abstract interface class kotlinx/kover/gradle/aggregation/settings/dsl/ReportsSettings {
8+
public abstract fun getExcludedClasses ()Lorg/gradle/api/provider/SetProperty;
9+
public abstract fun getExcludedProjects ()Lorg/gradle/api/provider/SetProperty;
10+
public abstract fun getExcludesAnnotatedBy ()Lorg/gradle/api/provider/SetProperty;
11+
public abstract fun getIncludedClasses ()Lorg/gradle/api/provider/SetProperty;
12+
public abstract fun getIncludedProjects ()Lorg/gradle/api/provider/SetProperty;
13+
public abstract fun getIncludesAnnotatedBy ()Lorg/gradle/api/provider/SetProperty;
14+
}
15+
116
public final class kotlinx/kover/gradle/plugin/KoverGradlePlugin : org/gradle/api/Plugin {
217
public fun <init> ()V
318
public synthetic fun apply (Ljava/lang/Object;)V

kover-gradle-plugin/build.gradle.kts

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ val functionalTestImplementation = "functionalTestImplementation"
4242

4343
dependencies {
4444
implementation(project(":kover-features-jvm"))
45+
implementation(project(":kover-jvm-agent"))
4546
// exclude transitive dependency on stdlib, the Gradle version should be used
4647
compileOnly(kotlin("stdlib"))
4748
compileOnly(libs.gradlePlugin.kotlin)
@@ -52,6 +53,13 @@ dependencies {
5253

5354
snapshotRelease(project(":kover-features-jvm"))
5455
snapshotRelease(project(":kover-jvm-agent"))
56+
57+
functionalTestImplementation(gradleTestKit())
58+
// dependencies only for plugin's classpath to work with Kotlin Multi-Platform and Android plugins
59+
functionalTestImplementation("org.jetbrains.kotlin:kotlin-gradle-plugin:$embeddedKotlinVersion")
60+
functionalTestImplementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:$embeddedKotlinVersion")
61+
functionalTestImplementation("org.jetbrains.kotlin:kotlin-compiler-runner:$embeddedKotlinVersion")
62+
5563
}
5664

5765
kotlin {
@@ -70,7 +78,20 @@ val functionalTest by tasks.registering(Test::class) {
7078
useJUnitPlatform()
7179

7280
dependsOn(tasks.collectRepository)
81+
82+
// While gradle testkit supports injection of the plugin classpath it doesn't allow using dependency notation
83+
// to determine the actual runtime classpath for the plugin. It uses isolation, so plugins applied by the build
84+
// script are not visible in the plugin classloader. This means optional dependencies (dependent on applied plugins -
85+
// for example kotlin multiplatform) are not visible even if they are in regular gradle use. This hack will allow
86+
// extending the classpath. It is based upon: https://docs.gradle.org/6.0/userguide/test_kit.html#sub:test-kit-classpath-injection
87+
// Create a configuration to register the dependencies against
7388
doFirst {
89+
val file = File(temporaryDir, "plugin-classpath.txt")
90+
file.writeText(sourceSets["functionalTest"].compileClasspath
91+
.filter { it.name.startsWith("stdlib") }
92+
.joinToString("\n"))
93+
systemProperties["plugin-classpath"] = file.absolutePath
94+
7495
// basic build properties
7596
setSystemPropertyFromProject("kover.test.kotlin.version")
7697

@@ -180,11 +201,6 @@ extensions.configure<Kover_publishing_conventions_gradle.KoverPublicationExtensi
180201
addPublication.set(false)
181202
}
182203

183-
signing {
184-
// disable signing if private key isn't passed
185-
isRequired = findProperty("libs.sign.key.private") != null
186-
}
187-
188204
gradlePlugin {
189205
website.set("https://github.com/Kotlin/kotlinx-kover")
190206
vcsUrl.set("https://github.com/Kotlin/kotlinx-kover.git")
@@ -199,3 +215,18 @@ gradlePlugin {
199215
}
200216
}
201217
}
218+
219+
gradlePlugin {
220+
website.set("https://github.com/Kotlin/kotlinx-kover")
221+
vcsUrl.set("https://github.com/Kotlin/kotlinx-kover.git")
222+
223+
plugins {
224+
create("KoverSettingsPlugin") {
225+
id = "org.jetbrains.kotlinx.kover.aggregation"
226+
implementationClass = "kotlinx.kover.gradle.aggregation.settings.KoverSettingsGradlePlugin"
227+
displayName = "Gradle Settings Plugin for Kotlin Code Coverage Tools"
228+
description = "Evaluate code coverage for projects written in Kotlin, applied only inside settings.gradle[.kts] files"
229+
tags.addAll("kover", "kotlin", "coverage", "settings plugin")
230+
}
231+
}
232+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Kover Aggregated Plugin
2+
3+
## Quickstart
4+
This plugin is a prototype for testing ideas related to an alternative configuration method.
5+
6+
The main difference from the existing Kover Gradle Plugin is the reactive content of the report: only those classes and tests that were compiled and executed within the same build get into the report.
7+
8+
To use the plugin, just add into a `settings.gradle.kts` file
9+
```kotlin
10+
plugins {
11+
id("org.jetbrains.kotlinx.kover.aggregation") version "0.8.1"
12+
}
13+
```
14+
**There is no need to apply Kover plugin in other places, the `org.jetbrains.kotlinx.kover` plug-in should not be applied anywhere.**
15+
16+
To measure coverage you should pass special `-Pkover` argument to Gradle CLI command and call the command to generate the corresponding report `koverHtmlReport` or `koverXmlReport`.
17+
Example, if you want to measure the coverage of the `test` task:
18+
```shell
19+
./gradlew test -Pkover koverHtmlReport
20+
```
21+
22+
Only those classes that were compiled as part of the current Gradle build are included in the report.
23+
If no compilation tasks were called in the build, the report will be empty.
24+
25+
The report covers only those tests that were run as part of the current build.
26+
If no tests were called in the assembly, then the coverage for all classes will be 0.
27+
28+
## Configuring
29+
At the moment, Kover Settings Plugin allows to configure reports minimally.
30+
31+
There are two ways to configure, using a build script in `settings.gradle.kts` and CLI
32+
33+
Acceptable settings in `settings.gradle.kts` and their equivalent in CLI
34+
```kotlin
35+
kover {
36+
// -Pkover
37+
enableCoverage()
38+
39+
reports {
40+
// -Pkover.projects.includes=:a
41+
includedProjects.add(":a")
42+
43+
// -Pkover.projects.excludes=:b
44+
excludedProjects.add(":b")
45+
46+
// -Pkover.classes.includes=classes.to.exclude.*
47+
includedClasses.add("classes.to.include.*")
48+
49+
// -Pkover.classes.excludes=classes.to.include.*
50+
excludedClasses.add("classes.to.exclude.*")
51+
52+
// -Pkover.classes.excludesAnnotated=*.Generated*
53+
excludesAnnotatedBy.add("*.Generated*")
54+
55+
// -Pkover.classes.includesAnnotated=*Included*
56+
includesAnnotatedBy.add("*Included*")
57+
}
58+
}
59+
```
60+
61+
For example, the following setting is in `settings.gradle.kts`
62+
```kotlin
63+
kover {
64+
enableCoverage()
65+
66+
reports {
67+
excludedClasses.add("org.test.MyClass*")
68+
}
69+
}
70+
```
71+
fully equivalent to Gradle CLI arguments `-Pkover -Pkover.classes.excludes=org.test.MyClass*`
72+
73+
**Any of the specified settings or DSL is preliminary and can be deleted or changed without maintaining backward compatibility**

kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/cases/ReportsCachingTests.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
*/
44
package kotlinx.kover.gradle.plugin.test.functional.cases
55

6-
import kotlinx.kover.gradle.plugin.commons.CoverageToolVendor
76
import kotlinx.kover.gradle.plugin.test.functional.framework.configurator.*
87
import kotlinx.kover.gradle.plugin.test.functional.framework.starter.*
98

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.kover.gradle.plugin.test.functional.cases
6+
7+
import kotlinx.kover.gradle.plugin.test.functional.framework.checker.CheckerContext
8+
import kotlinx.kover.gradle.plugin.test.functional.framework.starter.TemplateTest
9+
import kotlin.test.assertFalse
10+
import kotlin.test.assertTrue
11+
12+
internal class SettingsPluginTests {
13+
@TemplateTest("settings-plugin", [":tasks"])
14+
fun CheckerContext.testNoReportTasks() {
15+
taskOutput(":tasks") {
16+
assertFalse("koverXmlReport" in this)
17+
assertFalse("koverHtmlReport" in this)
18+
}
19+
}
20+
21+
@TemplateTest("settings-plugin", ["test"])
22+
fun CheckerContext.testNoInstrumentation() {
23+
checkDefaultBinReport(false)
24+
subproject("subproject") {
25+
checkDefaultBinReport(false)
26+
}
27+
}
28+
29+
@TemplateTest("settings-plugin", ["-Pkover", ":tasks", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"])
30+
fun CheckerContext.testHasReportTasks() {
31+
taskOutput(":tasks") {
32+
assertTrue("koverXmlReport" in this)
33+
assertTrue("koverHtmlReport" in this)
34+
}
35+
}
36+
37+
@TemplateTest("settings-plugin", ["-Pkover", "koverXmlReport", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"])
38+
fun CheckerContext.testNoCompilations() {
39+
xmlReport {
40+
classCounter("tests.settings.root.RootClass").assertAbsent()
41+
classCounter("tests.settings.subproject.SubprojectClass").assertAbsent()
42+
}
43+
}
44+
45+
@TemplateTest("settings-plugin", ["-Pkover", ":compileKotlin", "koverXmlReport", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"])
46+
fun CheckerContext.testCompilationOnlyForRoot() {
47+
xmlReport {
48+
classCounter("tests.settings.root.RootClass").assertFullyMissed()
49+
classCounter("tests.settings.subproject.SubprojectClass").assertAbsent()
50+
}
51+
}
52+
53+
@TemplateTest("settings-plugin", ["-Pkover", ":subproject:compileKotlin", ":test", "koverXmlReport", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"])
54+
fun CheckerContext.testRootAndOnlyCompileSubproject() {
55+
xmlReport {
56+
classCounter("tests.settings.root.RootClass").assertFullyCovered()
57+
classCounter("tests.settings.subproject.SubprojectClass").assertFullyMissed()
58+
}
59+
}
60+
61+
62+
@TemplateTest("settings-plugin", ["-Pkover", "test", "koverXmlReport", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"])
63+
fun CheckerContext.testAll() {
64+
xmlReport {
65+
classCounter("tests.settings.root.RootClass").assertFullyCovered()
66+
classCounter("tests.settings.subproject.SubprojectClass").assertFullyCovered()
67+
}
68+
}
69+
70+
@TemplateTest("settings-plugin", ["-Pkover", "test", "koverXmlReport", "-Pkover.projects.excludes=:subproject", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"])
71+
fun CheckerContext.testExcludeSubproject() {
72+
xmlReport {
73+
classCounter("tests.settings.root.RootClass").assertFullyCovered()
74+
classCounter("tests.settings.subproject.SubprojectClass").assertAbsent()
75+
}
76+
}
77+
78+
@TemplateTest("settings-plugin", ["-Pkover", "test", "koverXmlReport", "-Pkover.classes.excludes=tests.settings.subproject.*", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"])
79+
fun CheckerContext.testExcludeClasses() {
80+
xmlReport {
81+
classCounter("tests.settings.root.RootClass").assertFullyCovered()
82+
classCounter("tests.settings.subproject.SubprojectClass").assertAbsent()
83+
}
84+
}
85+
}

kover-gradle-plugin/src/functionalTest/kotlin/kotlinx/kover/gradle/plugin/test/functional/framework/writer/SettingsWriter.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ internal fun FormattedWriter.writePluginManagement(language: ScriptLanguage,
1515
call("resolutionStrategy") {
1616
call("eachPlugin") {
1717
line("if (requested.id.id == \"org.jetbrains.kotlinx.kover\") { useVersion(\"$koverVersion\") }")
18+
line("if (requested.id.id == \"org.jetbrains.kotlinx.kover.aggregation\") { useVersion(\"$koverVersion\") }")
1819
if (overrideKotlinVersion != null) {
1920
line("if (requested.id.id == \"org.jetbrains.kotlin.jvm\") useVersion(\"$overrideKotlinVersion\")")
2021
line("if (requested.id.id == \"org.jetbrains.kotlin.multiplatform\") useVersion(\"$overrideKotlinVersion\")")
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
plugins {
2+
kotlin("jvm") version ("2.0.0")
3+
}
4+
5+
dependencies {
6+
testImplementation(kotlin("test"))
7+
}
8+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
pluginManagement {
2+
repositories {
3+
gradlePluginPortal()
4+
mavenCentral()
5+
}
6+
}
7+
8+
plugins {
9+
id("org.jetbrains.kotlinx.kover.aggregation") version "SNAPSHOT"
10+
}
11+
12+
buildCache {
13+
local {
14+
directory = "$settingsDir/build-cache"
15+
}
16+
}
17+
18+
rootProject.name = "settings-plugin"
19+
20+
include(":subproject")
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package tests.settings.root
6+
7+
class RootClass {
8+
fun action() {
9+
println("It's root class")
10+
}
11+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package tests.settings.root
6+
7+
import kotlin.test.Test
8+
9+
class RootTest {
10+
@Test
11+
fun test() {
12+
RootClass().action()
13+
}
14+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
plugins {
2+
kotlin("jvm")
3+
}
4+
5+
dependencies {
6+
testImplementation(kotlin("test"))
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package tests.settings.subproject
6+
7+
class SubprojectClass {
8+
fun action() {
9+
println("It's class from the subproject")
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package tests.settings.subproject
2+
3+
import kotlin.test.Test
4+
5+
class SubprojectTest {
6+
@Test
7+
fun test() {
8+
SubprojectClass().action()
9+
}
10+
}

0 commit comments

Comments
 (0)