Skip to content

Commit da40fa3

Browse files
authored
Implemented verification in Kover Aggregated Plugin
Added ability to specify coverage verification rules via settings or CLI Co-authored-by: Leonid Startsev <[email protected]> PR #665
1 parent 12b41c4 commit da40fa3

File tree

23 files changed

+1005
-70
lines changed

23 files changed

+1005
-70
lines changed

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

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,62 @@ public final class kotlinx/kover/gradle/aggregation/settings/KoverSettingsGradle
44
public fun apply (Lorg/gradle/api/initialization/Settings;)V
55
}
66

7+
public abstract interface class kotlinx/kover/gradle/aggregation/settings/dsl/BoundSettings {
8+
public abstract fun getAggregationForGroup ()Lorg/gradle/api/provider/Property;
9+
public abstract fun getCoverageUnits ()Lorg/gradle/api/provider/Property;
10+
public abstract fun getMaxValue ()Lorg/gradle/api/provider/Property;
11+
public abstract fun getMinValue ()Lorg/gradle/api/provider/Property;
12+
}
13+
714
public abstract interface class kotlinx/kover/gradle/aggregation/settings/dsl/KoverSettingsExtension {
815
public abstract fun enableCoverage ()V
916
public abstract fun getReports ()Lkotlinx/kover/gradle/aggregation/settings/dsl/ReportsSettings;
1017
public abstract fun reports (Lorg/gradle/api/Action;)V
1118
}
1219

13-
public abstract interface class kotlinx/kover/gradle/aggregation/settings/dsl/ReportsSettings {
20+
public final class kotlinx/kover/gradle/aggregation/settings/dsl/KoverSettingsExtensionKt {
21+
public static final fun maxBound (Lkotlinx/kover/gradle/aggregation/settings/dsl/VerificationRuleSettings;I)V
22+
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
23+
public static final fun maxBound (Lkotlinx/kover/gradle/aggregation/settings/dsl/VerificationRuleSettings;Lorg/gradle/api/provider/Provider;)V
24+
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
25+
public static final fun minBound (Lkotlinx/kover/gradle/aggregation/settings/dsl/VerificationRuleSettings;I)V
26+
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
27+
public static final fun minBound (Lkotlinx/kover/gradle/aggregation/settings/dsl/VerificationRuleSettings;Lorg/gradle/api/provider/Provider;)V
28+
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
29+
}
30+
31+
public abstract interface class kotlinx/kover/gradle/aggregation/settings/dsl/ReportFiltersSettings {
32+
public fun clearFilters ()V
1433
public abstract fun getExcludedClasses ()Lorg/gradle/api/provider/SetProperty;
1534
public abstract fun getExcludedProjects ()Lorg/gradle/api/provider/SetProperty;
1635
public abstract fun getExcludesAnnotatedBy ()Lorg/gradle/api/provider/SetProperty;
36+
public abstract fun getExcludesInheritedFrom ()Lorg/gradle/api/provider/SetProperty;
1737
public abstract fun getIncludedClasses ()Lorg/gradle/api/provider/SetProperty;
1838
public abstract fun getIncludedProjects ()Lorg/gradle/api/provider/SetProperty;
1939
public abstract fun getIncludesAnnotatedBy ()Lorg/gradle/api/provider/SetProperty;
40+
public abstract fun getIncludesInheritedFrom ()Lorg/gradle/api/provider/SetProperty;
41+
}
42+
43+
public abstract interface class kotlinx/kover/gradle/aggregation/settings/dsl/ReportsSettings : kotlinx/kover/gradle/aggregation/settings/dsl/ReportFiltersSettings {
44+
public abstract fun getVerify ()Lkotlinx/kover/gradle/aggregation/settings/dsl/VerifySettings;
45+
public fun verify (Lorg/gradle/api/Action;)V
46+
}
47+
48+
public abstract interface class kotlinx/kover/gradle/aggregation/settings/dsl/VerificationRuleSettings {
49+
public abstract fun bound (Lorg/gradle/api/Action;)V
50+
public fun filters (Lorg/gradle/api/Action;)V
51+
public abstract fun getBounds ()Lorg/gradle/api/provider/ListProperty;
52+
public abstract fun getDisabled ()Lorg/gradle/api/provider/Property;
53+
public abstract fun getFilters ()Lkotlinx/kover/gradle/aggregation/settings/dsl/ReportFiltersSettings;
54+
public abstract fun getGroupBy ()Lorg/gradle/api/provider/Property;
55+
public abstract fun getName ()Lorg/gradle/api/provider/Property;
56+
}
57+
58+
public abstract interface class kotlinx/kover/gradle/aggregation/settings/dsl/VerifySettings {
59+
public abstract fun getRules ()Lorg/gradle/api/provider/ListProperty;
60+
public abstract fun getWarningInsteadOfFailure ()Lorg/gradle/api/provider/Property;
61+
public abstract fun rule (Ljava/lang/String;Lorg/gradle/api/Action;)V
62+
public abstract fun rule (Lorg/gradle/api/Action;)V
2063
}
2164

2265
public final class kotlinx/kover/gradle/plugin/KoverGradlePlugin : org/gradle/api/Plugin {

kover-gradle-plugin/docs/aggregated.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,46 @@ kover {
5454

5555
// -Pkover.classes.includesAnnotated=*Included*
5656
includesAnnotatedBy.add("*Included*")
57+
58+
// -Pkover.classes.includesHeir=*ParentIncluded
59+
includesInheritedFrom.add("*ParentIncluded")
60+
61+
// -Pkover.classes.excludesHeir=*ParentExcluded
62+
excludesInheritedFrom.add("*ParentExcluded")
63+
64+
verify {
65+
// -Pkover.verify.warn=true
66+
warningInsteadOfFailure = true
67+
68+
rule {
69+
name = "custom name"
70+
disabled = false
71+
groupBy = GroupingEntityType.APPLICATION
72+
73+
// specify filters for given rule, common filters will be inherited
74+
// call `clearFilters()` to avoid common filters inheritance
75+
filters {
76+
includedProjects.add(":a2")
77+
excludedProjects.add(":b2")
78+
includedClasses.add("classes.to.include2.*")
79+
excludedClasses.add("classes.to.exclude2.*")
80+
excludesAnnotatedBy.add("*.Generated2*")
81+
includesAnnotatedBy.add("*Included2*")
82+
includesInheritedFrom.add("*ParentIncluded2")
83+
excludesInheritedFrom.add("*ParentExcluded2")
84+
}
85+
86+
bounds {
87+
// append minimal bound
88+
// -Pkover.verify.min=1
89+
minValue = 1
90+
91+
// append maximal bound
92+
// -Pkover.verify.max=90
93+
maxValue = 90
94+
}
95+
}
96+
}
5797
}
5898
}
5999
```

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

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package kotlinx.kover.gradle.plugin.test.functional.cases
66

77
import kotlinx.kover.gradle.plugin.test.functional.framework.checker.CheckerContext
88
import kotlinx.kover.gradle.plugin.test.functional.framework.starter.TemplateTest
9+
import kotlin.test.assertEquals
910
import kotlin.test.assertFalse
1011
import kotlin.test.assertTrue
1112

@@ -26,31 +27,43 @@ internal class SettingsPluginTests {
2627
}
2728
}
2829

29-
@TemplateTest("settings-plugin", ["-Pkover", ":tasks", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"])
30+
@TemplateTest(
31+
"settings-plugin",
32+
["-Pkover", ":tasks", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"]
33+
)
3034
fun CheckerContext.testHasReportTasks() {
3135
taskOutput(":tasks") {
3236
assertTrue("koverXmlReport" in this)
3337
assertTrue("koverHtmlReport" in this)
3438
}
3539
}
3640

37-
@TemplateTest("settings-plugin", ["-Pkover", "koverXmlReport", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"])
41+
@TemplateTest(
42+
"settings-plugin",
43+
["-Pkover", "koverXmlReport", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"]
44+
)
3845
fun CheckerContext.testNoCompilations() {
3946
xmlReport {
4047
classCounter("tests.settings.root.RootClass").assertAbsent()
4148
classCounter("tests.settings.subproject.SubprojectClass").assertAbsent()
4249
}
4350
}
4451

45-
@TemplateTest("settings-plugin", ["-Pkover", ":compileKotlin", "koverXmlReport", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"])
52+
@TemplateTest(
53+
"settings-plugin",
54+
["-Pkover", ":compileKotlin", "koverXmlReport", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"]
55+
)
4656
fun CheckerContext.testCompilationOnlyForRoot() {
4757
xmlReport {
4858
classCounter("tests.settings.root.RootClass").assertFullyMissed()
4959
classCounter("tests.settings.subproject.SubprojectClass").assertAbsent()
5060
}
5161
}
5262

53-
@TemplateTest("settings-plugin", ["-Pkover", ":subproject:compileKotlin", ":test", "koverXmlReport", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"])
63+
@TemplateTest(
64+
"settings-plugin",
65+
["-Pkover", ":subproject:compileKotlin", ":test", "koverXmlReport", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"]
66+
)
5467
fun CheckerContext.testRootAndOnlyCompileSubproject() {
5568
xmlReport {
5669
classCounter("tests.settings.root.RootClass").assertFullyCovered()
@@ -59,23 +72,32 @@ internal class SettingsPluginTests {
5972
}
6073

6174

62-
@TemplateTest("settings-plugin", ["-Pkover", "test", "koverXmlReport", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"])
75+
@TemplateTest(
76+
"settings-plugin",
77+
["-Pkover", "test", "koverXmlReport", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"]
78+
)
6379
fun CheckerContext.testAll() {
6480
xmlReport {
6581
classCounter("tests.settings.root.RootClass").assertFullyCovered()
6682
classCounter("tests.settings.subproject.SubprojectClass").assertFullyCovered()
6783
}
6884
}
6985

70-
@TemplateTest("settings-plugin", ["-Pkover", "test", "koverXmlReport", "-Pkover.projects.excludes=:subproject", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"])
86+
@TemplateTest(
87+
"settings-plugin",
88+
["-Pkover", "test", "koverXmlReport", "-Pkover.projects.excludes=:subproject", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"]
89+
)
7190
fun CheckerContext.testExcludeSubproject() {
7291
xmlReport {
7392
classCounter("tests.settings.root.RootClass").assertFullyCovered()
7493
classCounter("tests.settings.subproject.SubprojectClass").assertAbsent()
7594
}
7695
}
7796

78-
@TemplateTest("settings-plugin", ["-Pkover", "test", "koverXmlReport", "-Pkover.classes.excludes=tests.settings.subproject.*", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"])
97+
@TemplateTest(
98+
"settings-plugin",
99+
["-Pkover", "test", "koverXmlReport", "-Pkover.classes.excludes=tests.settings.subproject.*", "-Dorg.gradle.unsafe.isolated-projects=true", "--configuration-cache", "--build-cache"]
100+
)
79101
fun CheckerContext.testExcludeClasses() {
80102
xmlReport {
81103
classCounter("tests.settings.root.RootClass").assertFullyCovered()
@@ -92,4 +114,26 @@ internal class SettingsPluginTests {
92114
classCounter("kotlinx.kover.test.android.LocalTests").assertAbsent()
93115
}
94116
}
117+
118+
@TemplateTest("settings-plugin-verify", ["check", "--configuration-cache", "--build-cache"])
119+
fun CheckerContext.testVerification() {
120+
taskOutput("koverVerify") {
121+
assertEquals(
122+
"""Kover Verification Error
123+
Rule 'named rule' violated: lines covered percentage is 50.000000, but expected minimum is 100
124+
Rule violated: lines covered percentage is 50.000000, but expected maximum is 10
125+
126+
""", this
127+
)
128+
}
129+
}
130+
131+
@TemplateTest("settings-plugin-android", ["-Pkover", ":app:testDebugUnitTest", "koverVerify", "-Pkover.verify.warn=true", "-Pkover.verify.min=100", "-Pkover.verify.max=5"])
132+
fun CheckerContext.testVerifyMin() {
133+
taskOutput("koverVerify") {
134+
assertTrue(contains("Rule 'CLI parameters' violated:\n" +
135+
" lines covered percentage is 7.407400, but expected minimum is 100\n" +
136+
" lines covered percentage is 7.407400, but expected maximum is 5"))
137+
}
138+
}
95139
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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+
plugins {
6+
kotlin("jvm") version ("2.0.0")
7+
}
8+
9+
dependencies {
10+
testImplementation(kotlin("test"))
11+
}
12+
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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+
extensions.configure<kotlinx.kover.gradle.aggregation.settings.dsl.KoverSettingsExtension> {
13+
enableCoverage()
14+
15+
reports {
16+
verify {
17+
warningInsteadOfFailure = true
18+
19+
rule("named rule") {
20+
// should fail
21+
bound {
22+
minValue = 100
23+
}
24+
}
25+
rule {
26+
// shoul pass because RootClass is fully covered
27+
name = "include class Rule"
28+
filters {
29+
includedClasses.add("tests.settings.root.RootClass")
30+
}
31+
bound {
32+
minValue = 100
33+
}
34+
}
35+
rule {
36+
name = "included project"
37+
// shoul pass because project ':subproject' is fully covered
38+
filters {
39+
includedProjects.add(":subproject")
40+
}
41+
bound {
42+
minValue = 100
43+
}
44+
}
45+
rule {
46+
// should fail
47+
bound {
48+
maxValue = 10
49+
}
50+
}
51+
}
52+
}
53+
}
54+
55+
buildCache {
56+
local {
57+
directory = "$settingsDir/build-cache"
58+
}
59+
}
60+
61+
rootProject.name = "settings-plugin-verify"
62+
63+
include(":subproject")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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 java.lang.AutoCloseable
8+
9+
class RootClass {
10+
fun action() {
11+
println("It's root class")
12+
}
13+
}
14+
15+
class InheritedClass: AutoCloseable {
16+
override fun close() {
17+
println("close")
18+
}
19+
}
20+
21+
annotation class Generated
22+
23+
@Generated
24+
class AnnotatedClass {
25+
fun function() {
26+
println("function")
27+
}
28+
}
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+
}
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+
plugins {
6+
kotlin("jvm")
7+
}
8+
9+
dependencies {
10+
testImplementation(kotlin("test"))
11+
}
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,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.subproject
6+
7+
import kotlin.test.Test
8+
9+
class SubprojectTest {
10+
@Test
11+
fun test() {
12+
SubprojectClass().action()
13+
}
14+
}

0 commit comments

Comments
 (0)