Skip to content

Commit

Permalink
Implemented verification in Kover Aggregated Plugin
Browse files Browse the repository at this point in the history
Added ability to specify coverage verification rules via settings or CLI

Co-authored-by: Leonid Startsev <sandwwraith@users.noreply.github.com>

PR #665
  • Loading branch information
shanshin authored Sep 3, 2024
1 parent 12b41c4 commit da40fa3
Show file tree
Hide file tree
Showing 23 changed files with 1,005 additions and 70 deletions.
45 changes: 44 additions & 1 deletion kover-gradle-plugin/api/kover-gradle-plugin.api
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,62 @@ public final class kotlinx/kover/gradle/aggregation/settings/KoverSettingsGradle
public fun apply (Lorg/gradle/api/initialization/Settings;)V
}

public abstract interface class kotlinx/kover/gradle/aggregation/settings/dsl/BoundSettings {
public abstract fun getAggregationForGroup ()Lorg/gradle/api/provider/Property;
public abstract fun getCoverageUnits ()Lorg/gradle/api/provider/Property;
public abstract fun getMaxValue ()Lorg/gradle/api/provider/Property;
public abstract fun getMinValue ()Lorg/gradle/api/provider/Property;
}

public abstract interface class kotlinx/kover/gradle/aggregation/settings/dsl/KoverSettingsExtension {
public abstract fun enableCoverage ()V
public abstract fun getReports ()Lkotlinx/kover/gradle/aggregation/settings/dsl/ReportsSettings;
public abstract fun reports (Lorg/gradle/api/Action;)V
}

public abstract interface class kotlinx/kover/gradle/aggregation/settings/dsl/ReportsSettings {
public final class kotlinx/kover/gradle/aggregation/settings/dsl/KoverSettingsExtensionKt {
public static final fun maxBound (Lkotlinx/kover/gradle/aggregation/settings/dsl/VerificationRuleSettings;I)V
public static final fun maxBound (Lkotlinx/kover/gradle/aggregation/settings/dsl/VerificationRuleSettings;ILkotlinx/kover/gradle/plugin/dsl/CoverageUnit;Lkotlinx/kover/gradle/plugin/dsl/AggregationType;)V
public static final fun maxBound (Lkotlinx/kover/gradle/aggregation/settings/dsl/VerificationRuleSettings;Lorg/gradle/api/provider/Provider;)V
public static synthetic fun maxBound$default (Lkotlinx/kover/gradle/aggregation/settings/dsl/VerificationRuleSettings;ILkotlinx/kover/gradle/plugin/dsl/CoverageUnit;Lkotlinx/kover/gradle/plugin/dsl/AggregationType;ILjava/lang/Object;)V
public static final fun minBound (Lkotlinx/kover/gradle/aggregation/settings/dsl/VerificationRuleSettings;I)V
public static final fun minBound (Lkotlinx/kover/gradle/aggregation/settings/dsl/VerificationRuleSettings;ILkotlinx/kover/gradle/plugin/dsl/CoverageUnit;Lkotlinx/kover/gradle/plugin/dsl/AggregationType;)V
public static final fun minBound (Lkotlinx/kover/gradle/aggregation/settings/dsl/VerificationRuleSettings;Lorg/gradle/api/provider/Provider;)V
public static synthetic fun minBound$default (Lkotlinx/kover/gradle/aggregation/settings/dsl/VerificationRuleSettings;ILkotlinx/kover/gradle/plugin/dsl/CoverageUnit;Lkotlinx/kover/gradle/plugin/dsl/AggregationType;ILjava/lang/Object;)V
}

public abstract interface class kotlinx/kover/gradle/aggregation/settings/dsl/ReportFiltersSettings {
public fun clearFilters ()V
public abstract fun getExcludedClasses ()Lorg/gradle/api/provider/SetProperty;
public abstract fun getExcludedProjects ()Lorg/gradle/api/provider/SetProperty;
public abstract fun getExcludesAnnotatedBy ()Lorg/gradle/api/provider/SetProperty;
public abstract fun getExcludesInheritedFrom ()Lorg/gradle/api/provider/SetProperty;
public abstract fun getIncludedClasses ()Lorg/gradle/api/provider/SetProperty;
public abstract fun getIncludedProjects ()Lorg/gradle/api/provider/SetProperty;
public abstract fun getIncludesAnnotatedBy ()Lorg/gradle/api/provider/SetProperty;
public abstract fun getIncludesInheritedFrom ()Lorg/gradle/api/provider/SetProperty;
}

public abstract interface class kotlinx/kover/gradle/aggregation/settings/dsl/ReportsSettings : kotlinx/kover/gradle/aggregation/settings/dsl/ReportFiltersSettings {
public abstract fun getVerify ()Lkotlinx/kover/gradle/aggregation/settings/dsl/VerifySettings;
public fun verify (Lorg/gradle/api/Action;)V
}

public abstract interface class kotlinx/kover/gradle/aggregation/settings/dsl/VerificationRuleSettings {
public abstract fun bound (Lorg/gradle/api/Action;)V
public fun filters (Lorg/gradle/api/Action;)V
public abstract fun getBounds ()Lorg/gradle/api/provider/ListProperty;
public abstract fun getDisabled ()Lorg/gradle/api/provider/Property;
public abstract fun getFilters ()Lkotlinx/kover/gradle/aggregation/settings/dsl/ReportFiltersSettings;
public abstract fun getGroupBy ()Lorg/gradle/api/provider/Property;
public abstract fun getName ()Lorg/gradle/api/provider/Property;
}

public abstract interface class kotlinx/kover/gradle/aggregation/settings/dsl/VerifySettings {
public abstract fun getRules ()Lorg/gradle/api/provider/ListProperty;
public abstract fun getWarningInsteadOfFailure ()Lorg/gradle/api/provider/Property;
public abstract fun rule (Ljava/lang/String;Lorg/gradle/api/Action;)V
public abstract fun rule (Lorg/gradle/api/Action;)V
}

public final class kotlinx/kover/gradle/plugin/KoverGradlePlugin : org/gradle/api/Plugin {
Expand Down
40 changes: 40 additions & 0 deletions kover-gradle-plugin/docs/aggregated.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,46 @@ kover {

// -Pkover.classes.includesAnnotated=*Included*
includesAnnotatedBy.add("*Included*")

// -Pkover.classes.includesHeir=*ParentIncluded
includesInheritedFrom.add("*ParentIncluded")

// -Pkover.classes.excludesHeir=*ParentExcluded
excludesInheritedFrom.add("*ParentExcluded")

verify {
// -Pkover.verify.warn=true
warningInsteadOfFailure = true

rule {
name = "custom name"
disabled = false
groupBy = GroupingEntityType.APPLICATION

// specify filters for given rule, common filters will be inherited
// call `clearFilters()` to avoid common filters inheritance
filters {
includedProjects.add(":a2")
excludedProjects.add(":b2")
includedClasses.add("classes.to.include2.*")
excludedClasses.add("classes.to.exclude2.*")
excludesAnnotatedBy.add("*.Generated2*")
includesAnnotatedBy.add("*Included2*")
includesInheritedFrom.add("*ParentIncluded2")
excludesInheritedFrom.add("*ParentExcluded2")
}

bounds {
// append minimal bound
// -Pkover.verify.min=1
minValue = 1

// append maximal bound
// -Pkover.verify.max=90
maxValue = 90
}
}
}
}
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package kotlinx.kover.gradle.plugin.test.functional.cases

import kotlinx.kover.gradle.plugin.test.functional.framework.checker.CheckerContext
import kotlinx.kover.gradle.plugin.test.functional.framework.starter.TemplateTest
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue

Expand All @@ -26,31 +27,43 @@ internal class SettingsPluginTests {
}
}

@TemplateTest("settings-plugin", ["-Pkover", ":tasks", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"])
@TemplateTest(
"settings-plugin",
["-Pkover", ":tasks", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"]
)
fun CheckerContext.testHasReportTasks() {
taskOutput(":tasks") {
assertTrue("koverXmlReport" in this)
assertTrue("koverHtmlReport" in this)
}
}

@TemplateTest("settings-plugin", ["-Pkover", "koverXmlReport", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"])
@TemplateTest(
"settings-plugin",
["-Pkover", "koverXmlReport", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"]
)
fun CheckerContext.testNoCompilations() {
xmlReport {
classCounter("tests.settings.root.RootClass").assertAbsent()
classCounter("tests.settings.subproject.SubprojectClass").assertAbsent()
}
}

@TemplateTest("settings-plugin", ["-Pkover", ":compileKotlin", "koverXmlReport", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"])
@TemplateTest(
"settings-plugin",
["-Pkover", ":compileKotlin", "koverXmlReport", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"]
)
fun CheckerContext.testCompilationOnlyForRoot() {
xmlReport {
classCounter("tests.settings.root.RootClass").assertFullyMissed()
classCounter("tests.settings.subproject.SubprojectClass").assertAbsent()
}
}

@TemplateTest("settings-plugin", ["-Pkover", ":subproject:compileKotlin", ":test", "koverXmlReport", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"])
@TemplateTest(
"settings-plugin",
["-Pkover", ":subproject:compileKotlin", ":test", "koverXmlReport", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"]
)
fun CheckerContext.testRootAndOnlyCompileSubproject() {
xmlReport {
classCounter("tests.settings.root.RootClass").assertFullyCovered()
Expand All @@ -59,23 +72,32 @@ internal class SettingsPluginTests {
}


@TemplateTest("settings-plugin", ["-Pkover", "test", "koverXmlReport", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"])
@TemplateTest(
"settings-plugin",
["-Pkover", "test", "koverXmlReport", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"]
)
fun CheckerContext.testAll() {
xmlReport {
classCounter("tests.settings.root.RootClass").assertFullyCovered()
classCounter("tests.settings.subproject.SubprojectClass").assertFullyCovered()
}
}

@TemplateTest("settings-plugin", ["-Pkover", "test", "koverXmlReport", "-Pkover.projects.excludes=:subproject", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"])
@TemplateTest(
"settings-plugin",
["-Pkover", "test", "koverXmlReport", "-Pkover.projects.excludes=:subproject", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"]
)
fun CheckerContext.testExcludeSubproject() {
xmlReport {
classCounter("tests.settings.root.RootClass").assertFullyCovered()
classCounter("tests.settings.subproject.SubprojectClass").assertAbsent()
}
}

@TemplateTest("settings-plugin", ["-Pkover", "test", "koverXmlReport", "-Pkover.classes.excludes=tests.settings.subproject.*", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"])
@TemplateTest(
"settings-plugin",
["-Pkover", "test", "koverXmlReport", "-Pkover.classes.excludes=tests.settings.subproject.*", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"]
)
fun CheckerContext.testExcludeClasses() {
xmlReport {
classCounter("tests.settings.root.RootClass").assertFullyCovered()
Expand All @@ -92,4 +114,26 @@ internal class SettingsPluginTests {
classCounter("kotlinx.kover.test.android.LocalTests").assertAbsent()
}
}

@TemplateTest("settings-plugin-verify", ["check", "--configuration-cache", "--build-cache"])
fun CheckerContext.testVerification() {
taskOutput("koverVerify") {
assertEquals(
"""Kover Verification Error
Rule 'named rule' violated: lines covered percentage is 50.000000, but expected minimum is 100
Rule violated: lines covered percentage is 50.000000, but expected maximum is 10
""", this
)
}
}

@TemplateTest("settings-plugin-android", ["-Pkover", ":app:testDebugUnitTest", "koverVerify", "-Pkover.verify.warn=true", "-Pkover.verify.min=100", "-Pkover.verify.max=5"])
fun CheckerContext.testVerifyMin() {
taskOutput("koverVerify") {
assertTrue(contains("Rule 'CLI parameters' violated:\n" +
" lines covered percentage is 7.407400, but expected minimum is 100\n" +
" lines covered percentage is 7.407400, but expected maximum is 5"))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

plugins {
kotlin("jvm") version ("2.0.0")
}

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
pluginManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
}

plugins {
id("org.jetbrains.kotlinx.kover.aggregation") version "SNAPSHOT"
}

extensions.configure<kotlinx.kover.gradle.aggregation.settings.dsl.KoverSettingsExtension> {
enableCoverage()

reports {
verify {
warningInsteadOfFailure = true

rule("named rule") {
// should fail
bound {
minValue = 100
}
}
rule {
// shoul pass because RootClass is fully covered
name = "include class Rule"
filters {
includedClasses.add("tests.settings.root.RootClass")
}
bound {
minValue = 100
}
}
rule {
name = "included project"
// shoul pass because project ':subproject' is fully covered
filters {
includedProjects.add(":subproject")
}
bound {
minValue = 100
}
}
rule {
// should fail
bound {
maxValue = 10
}
}
}
}
}

buildCache {
local {
directory = "$settingsDir/build-cache"
}
}

rootProject.name = "settings-plugin-verify"

include(":subproject")
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package tests.settings.root

import java.lang.AutoCloseable

class RootClass {
fun action() {
println("It's root class")
}
}

class InheritedClass: AutoCloseable {
override fun close() {
println("close")
}
}

annotation class Generated

@Generated
class AnnotatedClass {
fun function() {
println("function")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package tests.settings.root

import kotlin.test.Test

class RootTest {
@Test
fun test() {
RootClass().action()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

plugins {
kotlin("jvm")
}

dependencies {
testImplementation(kotlin("test"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package tests.settings.subproject

class SubprojectClass {
fun action() {
println("It's class from the subproject")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package tests.settings.subproject

import kotlin.test.Test

class SubprojectTest {
@Test
fun test() {
SubprojectClass().action()
}
}
Loading

0 comments on commit da40fa3

Please sign in to comment.