Skip to content

Commit

Permalink
Added ability to specify inclusion filter for source sets
Browse files Browse the repository at this point in the history
Resolves #714
PR #726
  • Loading branch information
shanshin authored Jan 8, 2025
1 parent 8e47a67 commit 3f6745d
Show file tree
Hide file tree
Showing 17 changed files with 292 additions and 6 deletions.
1 change: 1 addition & 0 deletions kover-gradle-plugin/api/kover-gradle-plugin.api
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ public abstract interface class kotlinx/kover/gradle/plugin/dsl/KoverVariantCrea
public abstract interface class kotlinx/kover/gradle/plugin/dsl/KoverVariantSources {
public abstract fun getExcludeJava ()Lorg/gradle/api/provider/Property;
public abstract fun getExcludedSourceSets ()Lorg/gradle/api/provider/SetProperty;
public abstract fun getIncludedSourceSets ()Lorg/gradle/api/provider/SetProperty;
}

public abstract interface class kotlinx/kover/gradle/plugin/dsl/KoverVerificationRulesConfig {
Expand Down
33 changes: 31 additions & 2 deletions kover-gradle-plugin/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -1120,9 +1120,9 @@ By specifying an empty filter `filters { }`, you can completely disable report f

### Exclusion of JVM source sets

It is possible to exclude from all reports the code declared in certain source sets.
Code declarations from `test` source set are excluded by default.

As a side effect, the generation of Kover reports ceases to depend on the compilation tasks of these source sets.
It is possible to exclude from all reports the code declared in certain source sets.

```kotlin
kover {
Expand All @@ -1134,6 +1134,35 @@ kover {
}
```

In addition, it is possible to exclude classes from all source sets that are not specified.

```kotlin
kover {
currentProject {
sources {
includedSourceSets.addAll("main")
}
}
}
```

This way, only classes from the `main` source set will be included in the report, but not from others.

These filters can be combined, the `excludedSourceSets` filter has priority.
```kotlin
kover {
currentProject {
sources {
includedSourceSets.addAll("main", "extra")
excludedSourceSets.addAll("main")
}
}
}
```
In this case, only classes from the `extra` source set will be included in the report.

As a side effect of the source set exclusion, the generation of Kover reports ceases to depend on the compilation tasks of excluded source sets.

### Exclusion of test tasks
If some task does not test the coverage of application classes,
or it is necessary to exclude its coverage from reports, then specify this task in the excluded list.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* Copyright 2017-2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.kover.gradle.plugin.test.functional.cases

import kotlinx.kover.gradle.plugin.test.functional.framework.checker.createCheckerContext
import kotlinx.kover.gradle.plugin.test.functional.framework.runner.buildFromTemplate
import kotlinx.kover.gradle.plugin.test.functional.framework.runner.runWithParams
import org.junit.jupiter.api.Test
import kotlin.test.assertTrue

internal class SourceSetsTests {

@Test
fun testExclude() {
val template = buildFromTemplate("sourcesets-multi")

template.kover {
currentProject {
sources.excludedSourceSets.addAll("main", "foo")
}
}

val gradleBuild = template.generate()
val buildResult = gradleBuild.runWithParams(":koverXmlReport")

gradleBuild.createCheckerContext(buildResult).xmlReport {
assertTrue(buildResult.isSuccessful, "Build should be successful")
classCounter("kotlinx.kover.examples.sourcesets.MainClass").assertAbsent()
classCounter("kotlinx.kover.examples.sourcesets.FooClass").assertAbsent()
classCounter("kotlinx.kover.examples.sourcesets.ExtraClass").assertPresent()
classCounter("kotlinx.kover.examples.sourcesets.TestClasses").assertAbsent()
}
}

@Test
fun testInclude() {
val template = buildFromTemplate("sourcesets-multi")

template.kover {
currentProject {
sources.includedSourceSets.addAll("extra")
}
}

val gradleBuild = template.generate()
val buildResult = gradleBuild.runWithParams(":koverXmlReport")

gradleBuild.createCheckerContext(buildResult).xmlReport {
assertTrue(buildResult.isSuccessful, "Build should be successful")
classCounter("kotlinx.kover.examples.sourcesets.MainClass").assertAbsent()
classCounter("kotlinx.kover.examples.sourcesets.FooClass").assertAbsent()
classCounter("kotlinx.kover.examples.sourcesets.ExtraClass").assertPresent()
classCounter("kotlinx.kover.examples.sourcesets.TestClasses").assertAbsent()
}
}

@Test
fun testInclude2() {
val template = buildFromTemplate("sourcesets-multi")

template.kover {
currentProject {
sources.includedSourceSets.addAll("extra", "foo")
}
}

val gradleBuild = template.generate()
val buildResult = gradleBuild.runWithParams(":koverXmlReport")

gradleBuild.createCheckerContext(buildResult).xmlReport {
assertTrue(buildResult.isSuccessful, "Build should be successful")
classCounter("kotlinx.kover.examples.sourcesets.MainClass").assertAbsent()
classCounter("kotlinx.kover.examples.sourcesets.FooClass").assertPresent()
classCounter("kotlinx.kover.examples.sourcesets.ExtraClass").assertPresent()
classCounter("kotlinx.kover.examples.sourcesets.TestClasses").assertAbsent()
}
}

@Test
fun testMixed() {
val template = buildFromTemplate("sourcesets-multi")

template.kover {
currentProject {
sources {
includedSourceSets.addAll("extra", "foo")
excludedSourceSets.add("foo")
}
}
}

val gradleBuild = template.generate()
val buildResult = gradleBuild.runWithParams(":koverXmlReport")

gradleBuild.createCheckerContext(buildResult).xmlReport {
assertTrue(buildResult.isSuccessful, "Build should be successful")
classCounter("kotlinx.kover.examples.sourcesets.MainClass").assertAbsent()
classCounter("kotlinx.kover.examples.sourcesets.FooClass").assertAbsent()
classCounter("kotlinx.kover.examples.sourcesets.ExtraClass").assertPresent()
classCounter("kotlinx.kover.examples.sourcesets.TestClasses").assertAbsent()
}
}

@Test
fun testTestSourceSet() {
val template = buildFromTemplate("sourcesets-multi")

template.kover {
currentProject {
sources {
includedSourceSets.addAll("test")
}
}
}

val gradleBuild = template.generate()
val buildResult = gradleBuild.runWithParams(":koverXmlReport")

gradleBuild.createCheckerContext(buildResult).xmlReport {
assertTrue(buildResult.isSuccessful, "Build should be successful")
classCounter("kotlinx.kover.examples.sourcesets.MainClass").assertAbsent()
classCounter("kotlinx.kover.examples.sourcesets.FooClass").assertAbsent()
classCounter("kotlinx.kover.examples.sourcesets.ExtraClass").assertAbsent()
classCounter("kotlinx.kover.examples.sourcesets.TestClasses").assertPresent()
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,10 @@ private class CounterImpl(
assertNull(values, "Counter '$symbol' with type '$type' isn't absent")
}

override fun assertPresent() {
assertNotNull(values, "Counter for '$symbol' with type '$type' isn't present in report")
}

override fun assertFullyMissed() {
assertNotNull(values, "Counter '$symbol' with type '$type' isn't fully missed because it absent")
assertTrue(values.missed > 0, "Counter '$symbol' with type '$type' isn't fully missed")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ internal interface ProjectAnalysisData {
internal interface Counter {
fun assertAbsent()

fun assertPresent()

fun assertFullyMissed()

fun assertCovered()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@

package kotlinx.kover.gradle.plugin.test.functional.framework.runner

import kotlinx.kover.gradle.plugin.dsl.KoverProjectExtension
import kotlinx.kover.gradle.plugin.test.functional.framework.common.BuildSlice
import kotlinx.kover.gradle.plugin.test.functional.framework.common.ScriptLanguage
import kotlinx.kover.gradle.plugin.test.functional.framework.common.isDebugEnabled
import kotlinx.kover.gradle.plugin.test.functional.framework.common.logInfo
import kotlinx.kover.gradle.plugin.test.functional.framework.common.uri
import kotlinx.kover.gradle.plugin.test.functional.framework.configurator.ProjectScope
import kotlinx.kover.gradle.plugin.test.functional.framework.mirroring.printGradleDsl
import kotlinx.kover.gradle.plugin.test.functional.framework.starter.*
import kotlinx.kover.gradle.plugin.test.functional.framework.starter.buildSrc
import kotlinx.kover.gradle.plugin.test.functional.framework.starter.patchSettingsFile
Expand All @@ -31,6 +36,8 @@ internal interface BuildSource {

fun from(rootProjectDir: File)

fun kover(config: KoverProjectExtension.(ProjectScope) -> Unit)

fun generate(): GradleBuild
}

Expand All @@ -54,6 +61,8 @@ private class BuildSourceImpl(val snapshotRepos: List<String>, val koverVersion:

private var copy: Boolean = false

private val koverBlocks: MutableList<(ScriptLanguage, String) -> String> = mutableListOf()

override var buildName: String = "default"

override var buildType: String = "default"
Expand All @@ -70,6 +79,12 @@ private class BuildSourceImpl(val snapshotRepos: List<String>, val koverVersion:
copy = false
}

override fun kover(config: KoverProjectExtension.(ProjectScope) -> Unit) {
koverBlocks += { language, gradle ->
printGradleDsl<KoverProjectExtension, ProjectScope>(language, gradle, "kover", config)
}
}

override fun generate(): GradleBuild {
val actualDir = dir ?: throw Exception("No source was specified for the build")
val targetDir = if (copy) {
Expand All @@ -88,6 +103,9 @@ private class BuildSourceImpl(val snapshotRepos: List<String>, val koverVersion:
val buildSrcScript = targetDir.buildSrc?.build
buildSrcScript?.patchKoverDependency(koverVersion)

val buildScript = targetDir.build
buildScript.addKoverBlocks(koverBlocks)

return GradleBuildImpl(targetDir, copy, buildName, buildType)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import java.lang.reflect.Method

private const val DIR_PARAM = "build-directory"
private const val CHECKER_PARAM = "checker-context"
private const val GRADLE_WITH_ASSIGN_OPERATOR_VERSION = "8.12"

internal class RunCommand(val buildSource: BuildSource, val gradleArgs: List<String>)

Expand Down Expand Up @@ -183,3 +184,17 @@ internal fun File.patchKoverDependency(koverVersion: String) {
}
}
}

internal fun File.addKoverBlocks(koverBlocks: MutableList<(ScriptLanguage, String) -> String>) {
if (koverBlocks.isEmpty()) return

val language = if (name.endsWith(".kts")) ScriptLanguage.KTS else ScriptLanguage.GROOVY

val builder = StringBuilder()
koverBlocks.forEach { block ->
builder.appendLine()
builder.append(block(language, GRADLE_WITH_ASSIGN_OPERATOR_VERSION))
builder.appendLine()
}
appendText(builder.toString())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
plugins {
kotlin("jvm") version "2.1.0"
id("org.jetbrains.kotlinx.kover") version "0.7.0"
}

sourceSets.create("extra")
sourceSets.create("foo")

dependencies {
testImplementation(kotlin("test"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
}
rootProject.name = "example-source-multi"

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package kotlinx.kover.examples.sourcesets

class ExtraClass {
fun function() {
println("Example")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package kotlinx.kover.examples.sourcesets

class FooClass {
fun function() {
println("Example")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package kotlinx.kover.examples.sourcesets

class MainClass {
fun function() {
println("Example")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package kotlinx.kover.examples.sourcesets

import kotlin.test.Test

class TestClasses {
@Test
fun test() {
// no-tests
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ private fun KoverVariantSources.wrap(project: Project): KoverMergingVariantSourc
return object : KoverMergingVariantSources {
override val excludeJava: Property<Boolean> = this@wrap.excludeJava
override val excludedSourceSets: SetProperty<String> = this@wrap.excludedSourceSets
override val includedSourceSets: SetProperty<String> = this@wrap.includedSourceSets
override val project: Project = project
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import kotlinx.kover.gradle.plugin.commons.VariantOriginAttr
import kotlinx.kover.gradle.plugin.dsl.internal.KoverVariantConfigImpl
import kotlinx.kover.gradle.plugin.appliers.origin.JvmVariantOrigin
import kotlinx.kover.gradle.plugin.commons.JVM_VARIANT_NAME
import kotlinx.kover.gradle.plugin.dsl.KoverVariantSources
import kotlinx.kover.gradle.plugin.dsl.internal.KoverProjectExtensionImpl
import kotlinx.kover.gradle.plugin.tools.CoverageTool
import org.gradle.api.Project
Expand Down Expand Up @@ -48,9 +49,25 @@ internal class JvmVariantArtifacts(
}

fromOrigin(variantOrigin) { compilationName ->
compilationName !in variantConfig.sources.excludedSourceSets.get()
&& compilationName != "test"
!compilationIsExcluded(compilationName, variantConfig.sources)
}
}

private fun compilationIsExcluded(compilationName: String, variant: KoverVariantSources): Boolean {
if (compilationName in variant.excludedSourceSets.get()) {
return true
}

val included = variant.includedSourceSets.get()

if (included.isEmpty() && compilationName == "test") {
return true
}
if (included.isNotEmpty() && compilationName !in included) {
return true
}

return false
}

}
Loading

0 comments on commit 3f6745d

Please sign in to comment.