Skip to content

Commit 742d2ab

Browse files
authored
Added project filter for reports
Resolves #584 PR #604
1 parent 3594ebd commit 742d2ab

File tree

9 files changed

+181
-15
lines changed

9 files changed

+181
-15
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ public abstract interface class kotlinx/kover/gradle/plugin/dsl/KoverReportFilte
172172
public abstract fun classes (Lorg/gradle/api/provider/Provider;)V
173173
public abstract fun classes ([Ljava/lang/String;)V
174174
public abstract fun classes ([Lorg/gradle/api/provider/Provider;)V
175+
public abstract fun getProjects ()Lorg/gradle/api/provider/SetProperty;
175176
public abstract fun packages (Ljava/lang/Iterable;)V
176177
public abstract fun packages (Lorg/gradle/api/provider/Provider;)V
177178
public abstract fun packages ([Ljava/lang/String;)V
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Copyright 2017-2023 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.configurator.BuildConfigurator
8+
import kotlinx.kover.gradle.plugin.test.functional.framework.starter.SlicedGeneratedTest
9+
10+
internal class ReportProjectFilterTests {
11+
private val subprojectPath = ":common"
12+
13+
@SlicedGeneratedTest(allLanguages = true)
14+
fun BuildConfigurator.testInclude() {
15+
addProjectWithKover(subprojectPath) {
16+
sourcesFrom("multiproject-common")
17+
}
18+
19+
addProjectWithKover {
20+
sourcesFrom("multiproject-user")
21+
dependencyKover(subprojectPath)
22+
23+
kover {
24+
reports {
25+
filters {
26+
includes {
27+
// show classes only from ':common' project
28+
projects.add(":c?m*")
29+
}
30+
}
31+
}
32+
}
33+
}
34+
35+
run(":koverXmlReport") {
36+
xmlReport {
37+
classCounter("org.jetbrains.CommonClass").assertCovered()
38+
classCounter("org.jetbrains.CommonInternalClass").assertCovered()
39+
classCounter("org.jetbrains.UserClass").assertAbsent()
40+
}
41+
}
42+
}
43+
44+
@SlicedGeneratedTest(allLanguages = true)
45+
fun BuildConfigurator.testExclude() {
46+
addProjectWithKover(subprojectPath) {
47+
sourcesFrom("multiproject-common")
48+
}
49+
50+
addProjectWithKover {
51+
sourcesFrom("multiproject-user")
52+
dependencyKover(subprojectPath)
53+
54+
kover {
55+
reports {
56+
filters {
57+
excludes {
58+
// exclude classes from ':common' project
59+
projects.add(":c?m*")
60+
}
61+
}
62+
}
63+
}
64+
}
65+
66+
run(":koverXmlReport") {
67+
xmlReport {
68+
classCounter("org.jetbrains.CommonClass").assertAbsent()
69+
classCounter("org.jetbrains.CommonInternalClass").assertAbsent()
70+
classCounter("org.jetbrains.UserClass").assertCovered()
71+
}
72+
}
73+
}
74+
75+
@SlicedGeneratedTest(allLanguages = true)
76+
fun BuildConfigurator.testIncludeAndExclude() {
77+
addProjectWithKover(subprojectPath) {
78+
sourcesFrom("multiproject-common")
79+
}
80+
81+
addProjectWithKover {
82+
sourcesFrom("multiproject-user")
83+
dependencyKover(subprojectPath)
84+
85+
kover {
86+
reports {
87+
filters {
88+
includes {
89+
// include all projects
90+
projects.add(":*")
91+
}
92+
excludes {
93+
// exclude classes from ':' project
94+
projects.add(":")
95+
}
96+
}
97+
}
98+
}
99+
}
100+
101+
run(":koverXmlReport") {
102+
xmlReport {
103+
classCounter("org.jetbrains.CommonClass").assertCovered()
104+
classCounter("org.jetbrains.CommonInternalClass").assertCovered()
105+
classCounter("org.jetbrains.UserClass").assertAbsent()
106+
}
107+
}
108+
}
109+
}

kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/appliers/tasks/VariantReportsSet.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,8 @@ internal class VariantReportsSet(
213213
return project.provider {
214214
ReportFilters(
215215
includesImpl.classes.get(), includesImpl.annotations.get(),
216-
excludesImpl.classes.get(), excludesImpl.annotations.get()
216+
excludesImpl.classes.get(), excludesImpl.annotations.get(),
217+
includesImpl.projects.get(), excludesImpl.projects.get(),
217218
)
218219
}
219220
}

kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/commons/Artifacts.kt

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import java.io.Serializable
1111
* The contents of a single Kover artifact.
1212
*/
1313
internal class ArtifactContent(
14+
val path: String,
1415
val sources: Set<File>,
1516
val outputs: Set<File>,
1617
val reports: Set<File>
@@ -26,16 +27,21 @@ internal class ArtifactContent(
2627
reports += it.reports
2728
}
2829

29-
return ArtifactContent(sources, outputs, reports)
30+
return ArtifactContent(path, sources, outputs, reports)
3031
}
3132

3233
fun existing(): ArtifactContent {
3334
return ArtifactContent(
35+
path,
3436
sources.filter { it.exists() }.toSet(),
3537
outputs.filter { it.exists() }.toSet(),
3638
reports.filter { it.exists() }.toSet()
3739
)
3840
}
41+
42+
companion object {
43+
val Empty = ArtifactContent("", emptySet(), emptySet(), emptySet())
44+
}
3945
}
4046

4147

@@ -47,22 +53,24 @@ internal fun ArtifactContent.write(artifactFile: File, rootDir: File) {
4753
val outputs = outputs.joinToString("\n") { it.toRelativeString(rootDir) }
4854
val reports = reports.joinToString("\n") { it.toRelativeString(rootDir) }
4955

50-
artifactFile.writeText("$sources\n\n$outputs\n\n$reports")
56+
artifactFile.writeText("$path\n$sources\n\n$outputs\n\n$reports")
5157
}
5258

5359
/**
5460
* Read Kover artifact content from the file.
5561
*/
5662
internal fun File.parseArtifactFile(rootDir: File): ArtifactContent {
57-
if (!exists() || !name.endsWith(".artifact")) return ArtifactContent(emptySet(), emptySet(), emptySet())
63+
if (!exists() || !name.endsWith(".artifact")) return ArtifactContent.Empty
5864

5965
val iterator = readLines().iterator()
66+
val projectPath = iterator.next()
67+
if (!projectPath.startsWith(':')) return ArtifactContent.Empty
6068

6169
val sources = iterator.groupUntil { it.isEmpty() }.map { rootDir.resolve(it) }.toSet()
6270
val outputs = iterator.groupUntil { it.isEmpty() }.map { rootDir.resolve(it) }.toSet()
6371
val reports = iterator.groupUntil { it.isEmpty() }.map { rootDir.resolve(it) }.toSet()
6472

65-
return ArtifactContent(sources, outputs, reports)
73+
return ArtifactContent(projectPath, sources, outputs, reports)
6674
}
6775

6876
private fun <T> Iterator<T>.groupUntil(block: (T) -> Boolean): List<T> {

kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/commons/Types.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,11 @@ internal data class ReportFilters(
7575
@get:Input
7676
val excludesClasses: Set<String> = emptySet(),
7777
@get:Input
78-
val excludesAnnotations: Set<String> = emptySet()
78+
val excludesAnnotations: Set<String> = emptySet(),
79+
@get:Input
80+
val includeProjects: Set<String> = emptySet(),
81+
@get:Input
82+
val excludeProjects: Set<String> = emptySet(),
7983
): Serializable
8084

8185
internal open class VerificationRule @Inject constructor(

kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/dsl/KoverReportsConfig.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import org.gradle.api.file.DirectoryProperty
1313
import org.gradle.api.file.RegularFileProperty
1414
import org.gradle.api.provider.Property
1515
import org.gradle.api.provider.Provider
16+
import org.gradle.api.provider.SetProperty
1617

1718
/**
1819
* Configuration of Kover reports.
@@ -467,6 +468,7 @@ public interface KoverReportFiltersConfig {
467468
* classes("com.example.FooBar?", "com.example.*Bar")
468469
* packages("com.example.subpackage")
469470
* annotatedBy("*Generated*")
471+
* projects.add(":my:lib*")
470472
* }
471473
* ```
472474
* Excludes have priority over includes.
@@ -481,6 +483,7 @@ public interface KoverReportFiltersConfig {
481483
* includes {
482484
* classes("com.example.FooBar?", "com.example.*Bar")
483485
* packages("com.example.subpackage")
486+
* projects.add(":my:lib*")
484487
* }
485488
* ```
486489
* Excludes have priority over includes.
@@ -500,6 +503,7 @@ public interface KoverReportFiltersConfig {
500503
* val somePackages =
501504
* packages(listOf("foo.b?r", "com.*.example"))
502505
* annotatedBy("*Generated*", "com.example.KoverExclude")
506+
* projects.add(":my:lib*")
503507
* }
504508
* ```
505509
*/
@@ -672,6 +676,19 @@ public interface KoverReportFilter {
672676
*/
673677
public fun annotatedBy(vararg annotationName: Provider<String>)
674678

679+
/**
680+
* Add all classes in specified project. Only the project path is used (starts with a colon).
681+
*
682+
* It is acceptable to use `*` and `?` wildcards,
683+
* `*` means any number of arbitrary characters (including no chars), `?` means one arbitrary character.
684+
*
685+
* Example:
686+
* ```
687+
* projects.add(":my:lib*")
688+
* ```
689+
*/
690+
val projects: SetProperty<String>
691+
675692
/**
676693
* Add all classes generated by Android plugin to filters.
677694
*

kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/dsl/internal/ReportsImpl.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,11 +326,13 @@ internal abstract class KoverReportFilterImpl: KoverReportFilter {
326326
internal fun extendsFrom(other: KoverReportFilterImpl) {
327327
classes.addAll(other.classes)
328328
annotations.addAll(other.annotations)
329+
projects.addAll(other.projects)
329330
}
330331

331332
internal fun clean() {
332333
classes.empty()
333334
annotations.empty()
335+
projects.empty()
334336
}
335337

336338
private fun String.packageAsClass(): String = "$this.*"

kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/reports/AbstractKoverReportTask.kt

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package kotlinx.kover.gradle.plugin.tasks.reports
66

7+
import kotlinx.kover.features.jvm.KoverFeatures
78
import kotlinx.kover.gradle.plugin.commons.*
89
import kotlinx.kover.gradle.plugin.tools.*
910
import org.gradle.api.*
@@ -38,8 +39,8 @@ internal abstract class AbstractKoverReportTask : DefaultTask() {
3839
@get:InputFiles
3940
@get:PathSensitive(PathSensitivity.RELATIVE)
4041
internal val externalSources: Provider<Set<File>> = externalArtifacts.elements.map {
41-
val content = ArtifactContent(emptySet(), emptySet(), emptySet())
42-
content.joinWith(it.map { file -> file.asFile.parseArtifactFile(rootDir) }).sources
42+
val content = ArtifactContent.Empty
43+
content.joinWith(it.map { file -> file.asFile.parseArtifactFile(rootDir).filterProjectSources() }).sources
4344
}
4445

4546
/**
@@ -48,8 +49,8 @@ internal abstract class AbstractKoverReportTask : DefaultTask() {
4849
@get:InputFiles
4950
@get:PathSensitive(PathSensitivity.RELATIVE)
5051
internal val externalReports: Provider<Set<File>> = externalArtifacts.elements.map {
51-
val content = ArtifactContent(emptySet(), emptySet(), emptySet())
52-
content.joinWith(it.map { file -> file.asFile.parseArtifactFile(rootDir) }).reports
52+
val content = ArtifactContent.Empty
53+
content.joinWith(it.map { file -> file.asFile.parseArtifactFile(rootDir).filterProjectSources() }).reports
5354
}
5455

5556
/**
@@ -58,7 +59,7 @@ internal abstract class AbstractKoverReportTask : DefaultTask() {
5859
@get:InputFiles
5960
@get:PathSensitive(PathSensitivity.RELATIVE)
6061
internal val localSources: Provider<Set<File>> = localArtifact.map {
61-
it.asFile.parseArtifactFile(rootDir).sources
62+
it.asFile.parseArtifactFile(rootDir).filterProjectSources().sources
6263
}
6364

6465
/**
@@ -67,7 +68,7 @@ internal abstract class AbstractKoverReportTask : DefaultTask() {
6768
@get:InputFiles
6869
@get:PathSensitive(PathSensitivity.RELATIVE)
6970
internal val localReports: Provider<Set<File>> = localArtifact.map {
70-
it.asFile.parseArtifactFile(rootDir).reports
71+
it.asFile.parseArtifactFile(rootDir).filterProjectSources().reports
7172
}
7273

7374
@get:Nested
@@ -94,8 +95,29 @@ internal abstract class AbstractKoverReportTask : DefaultTask() {
9495
}
9596

9697
private fun collectAllFiles(): ArtifactContent {
97-
val local = localArtifact.get().asFile.parseArtifactFile(rootDir)
98-
return local.joinWith(externalArtifacts.files.map { it.parseArtifactFile(rootDir) }).existing()
98+
val local = localArtifact.get().asFile.parseArtifactFile(rootDir).filterProjectSources()
99+
return local.joinWith(externalArtifacts.files.map { it.parseArtifactFile(rootDir).filterProjectSources() }).existing()
100+
}
101+
102+
private fun ArtifactContent.filterProjectSources(): ArtifactContent {
103+
val reportFilters = filters.get()
104+
if (reportFilters.includeProjects.isNotEmpty()) {
105+
val notIncluded = reportFilters.includeProjects.none { filter ->
106+
KoverFeatures.koverWildcardToRegex(filter).toRegex().matches(path)
107+
}
108+
if (notIncluded) {
109+
return ArtifactContent(path, emptySet(), emptySet(), reports)
110+
}
111+
}
112+
if (reportFilters.excludeProjects.isNotEmpty()) {
113+
val excluded = reportFilters.excludeProjects.any { filter ->
114+
KoverFeatures.koverWildcardToRegex(filter).toRegex().matches(path)
115+
}
116+
if (excluded) {
117+
return ArtifactContent(path, emptySet(), emptySet(), reports)
118+
}
119+
}
120+
return this
99121
}
100122
}
101123

kover-gradle-plugin/src/main/kotlin/kotlinx/kover/gradle/plugin/tasks/services/KoverArtifactGenerationTask.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,11 @@ internal abstract class KoverArtifactGenerationTask : DefaultTask() {
4242

4343
private val rootDir: File = project.rootDir
4444

45+
private val projectPath: String = project.path
46+
4547
@TaskAction
4648
fun generate() {
47-
val mainContent = ArtifactContent(sources.toSet(), outputDirs.toSet(), reports.toSet())
49+
val mainContent = ArtifactContent(projectPath, sources.toSet(), outputDirs.toSet(), reports.toSet())
4850
val additional = additionalArtifacts.files.map { it.parseArtifactFile(rootDir) }
4951
mainContent.joinWith(additional).write(artifactFile.get().asFile, rootDir)
5052
}

0 commit comments

Comments
 (0)