Skip to content

Commit

Permalink
Added project filter for reports
Browse files Browse the repository at this point in the history
Resolves #584
  • Loading branch information
shanshin committed May 3, 2024
1 parent e218b3e commit 269939e
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 15 deletions.
@@ -0,0 +1,109 @@
/*
* Copyright 2017-2023 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.configurator.BuildConfigurator
import kotlinx.kover.gradle.plugin.test.functional.framework.starter.SlicedGeneratedTest

internal class ReportProjectFilterTests {
private val subprojectPath = ":common"

@SlicedGeneratedTest(allLanguages = true)
fun BuildConfigurator.testInclude() {
addProjectWithKover(subprojectPath) {
sourcesFrom("multiproject-common")
}

addProjectWithKover {
sourcesFrom("multiproject-user")
dependencyKover(subprojectPath)

kover {
reports {
filters {
includes {
// show classes only from ':common' project
projects.add(":c?m*")
}
}
}
}
}

run(":koverXmlReport") {
xmlReport {
classCounter("org.jetbrains.CommonClass").assertCovered()
classCounter("org.jetbrains.CommonInternalClass").assertCovered()
classCounter("org.jetbrains.UserClass").assertAbsent()
}
}
}

@SlicedGeneratedTest(allLanguages = true)
fun BuildConfigurator.testExclude() {
addProjectWithKover(subprojectPath) {
sourcesFrom("multiproject-common")
}

addProjectWithKover {
sourcesFrom("multiproject-user")
dependencyKover(subprojectPath)

kover {
reports {
filters {
excludes {
// exclude classes from ':common' project
projects.add(":c?m*")
}
}
}
}
}

run(":koverXmlReport") {
xmlReport {
classCounter("org.jetbrains.CommonClass").assertAbsent()
classCounter("org.jetbrains.CommonInternalClass").assertAbsent()
classCounter("org.jetbrains.UserClass").assertCovered()
}
}
}

@SlicedGeneratedTest(allLanguages = true)
fun BuildConfigurator.testIncludeAndExclude() {
addProjectWithKover(subprojectPath) {
sourcesFrom("multiproject-common")
}

addProjectWithKover {
sourcesFrom("multiproject-user")
dependencyKover(subprojectPath)

kover {
reports {
filters {
includes {
// include all projects
projects.add(":*")
}
excludes {
// exclude classes from ':common' project
projects.add(":c?m*")
}
}
}
}
}

run(":koverXmlReport") {
xmlReport {
classCounter("org.jetbrains.CommonClass").assertAbsent()
classCounter("org.jetbrains.CommonInternalClass").assertAbsent()
classCounter("org.jetbrains.UserClass").assertCovered()
}
}
}
}
Expand Up @@ -213,7 +213,8 @@ internal class VariantReportsSet(
return project.provider {
ReportFilters(
includesImpl.classes.get(), includesImpl.annotations.get(),
excludesImpl.classes.get(), excludesImpl.annotations.get()
excludesImpl.classes.get(), excludesImpl.annotations.get(),
includesImpl.projects.get(), excludesImpl.projects.get(),
)
}
}
Expand Down
Expand Up @@ -11,6 +11,7 @@ import java.io.Serializable
* The contents of a single Kover artifact.
*/
internal class ArtifactContent(
val path: String,
val sources: Set<File>,
val outputs: Set<File>,
val reports: Set<File>
Expand All @@ -26,16 +27,21 @@ internal class ArtifactContent(
reports += it.reports
}

return ArtifactContent(sources, outputs, reports)
return ArtifactContent(path, sources, outputs, reports)
}

fun existing(): ArtifactContent {
return ArtifactContent(
path,
sources.filter { it.exists() }.toSet(),
outputs.filter { it.exists() }.toSet(),
reports.filter { it.exists() }.toSet()
)
}

companion object {
val Empty = ArtifactContent("", emptySet(), emptySet(), emptySet())
}
}


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

artifactFile.writeText("$sources\n\n$outputs\n\n$reports")
artifactFile.writeText("$path\n$sources\n\n$outputs\n\n$reports")
}

/**
* Read Kover artifact content from the file.
*/
internal fun File.parseArtifactFile(rootDir: File): ArtifactContent {
if (!exists() || !name.endsWith(".artifact")) return ArtifactContent(emptySet(), emptySet(), emptySet())
if (!exists() || !name.endsWith(".artifact")) return ArtifactContent.Empty

val iterator = readLines().iterator()
val projectPath = iterator.next()
if (!projectPath.startsWith(':')) return ArtifactContent.Empty

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

return ArtifactContent(sources, outputs, reports)
return ArtifactContent(projectPath, sources, outputs, reports)
}

private fun <T> Iterator<T>.groupUntil(block: (T) -> Boolean): List<T> {
Expand Down
Expand Up @@ -75,7 +75,11 @@ internal data class ReportFilters(
@get:Input
val excludesClasses: Set<String> = emptySet(),
@get:Input
val excludesAnnotations: Set<String> = emptySet()
val excludesAnnotations: Set<String> = emptySet(),
@get:Input
val includeProjects: Set<String> = emptySet(),
@get:Input
val excludeProjects: Set<String> = emptySet(),
): Serializable

internal open class VerificationRule @Inject constructor(
Expand Down
Expand Up @@ -13,6 +13,7 @@ import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.provider.SetProperty

/**
* Configuration of Kover reports.
Expand Down Expand Up @@ -467,6 +468,7 @@ public interface KoverReportFiltersConfig {
* classes("com.example.FooBar?", "com.example.*Bar")
* packages("com.example.subpackage")
* annotatedBy("*Generated*")
* projects.add(":my:project-path")
* }
* ```
* Excludes have priority over includes.
Expand All @@ -481,6 +483,7 @@ public interface KoverReportFiltersConfig {
* includes {
* classes("com.example.FooBar?", "com.example.*Bar")
* packages("com.example.subpackage")
* projects.add(":my:project-path")
* }
* ```
* Excludes have priority over includes.
Expand All @@ -500,6 +503,7 @@ public interface KoverReportFiltersConfig {
* val somePackages =
* packages(listOf("foo.b?r", "com.*.example"))
* annotatedBy("*Generated*", "com.example.KoverExclude")
* projects.add(":my:project-path")
* }
* ```
*/
Expand Down Expand Up @@ -672,6 +676,19 @@ public interface KoverReportFilter {
*/
public fun annotatedBy(vararg annotationName: Provider<String>)

/**
* Add all classes in specified project. Only the project path is used (starts with a colon).
*
* It is acceptable to use `*` and `?` wildcards,
* `*` means any number of arbitrary characters (including no chars), `?` means one arbitrary character.
*
* Example:
* ```
* projects.add(":my:project-path")
* ```
*/
val projects: SetProperty<String>

/**
* Add all classes generated by Android plugin to filters.
*
Expand Down
Expand Up @@ -326,11 +326,13 @@ internal abstract class KoverReportFilterImpl: KoverReportFilter {
internal fun extendsFrom(other: KoverReportFilterImpl) {
classes.addAll(other.classes)
annotations.addAll(other.annotations)
projects.addAll(other.projects)
}

internal fun clean() {
classes.empty()
annotations.empty()
projects.empty()
}

private fun String.packageAsClass(): String = "$this.*"
Expand Down
Expand Up @@ -4,6 +4,7 @@

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

import kotlinx.kover.features.jvm.KoverFeatures
import kotlinx.kover.gradle.plugin.commons.*
import kotlinx.kover.gradle.plugin.tools.*
import org.gradle.api.*
Expand Down Expand Up @@ -38,8 +39,8 @@ internal abstract class AbstractKoverReportTask : DefaultTask() {
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
internal val externalSources: Provider<Set<File>> = externalArtifacts.elements.map {
val content = ArtifactContent(emptySet(), emptySet(), emptySet())
content.joinWith(it.map { file -> file.asFile.parseArtifactFile(rootDir) }).sources
val content = ArtifactContent.Empty
content.joinWith(it.map { file -> file.asFile.parseArtifactFile(rootDir).filterArtifact() }).sources
}

/**
Expand All @@ -48,8 +49,8 @@ internal abstract class AbstractKoverReportTask : DefaultTask() {
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
internal val externalReports: Provider<Set<File>> = externalArtifacts.elements.map {
val content = ArtifactContent(emptySet(), emptySet(), emptySet())
content.joinWith(it.map { file -> file.asFile.parseArtifactFile(rootDir) }).reports
val content = ArtifactContent.Empty
content.joinWith(it.map { file -> file.asFile.parseArtifactFile(rootDir).filterArtifact() }).reports
}

/**
Expand All @@ -58,7 +59,7 @@ internal abstract class AbstractKoverReportTask : DefaultTask() {
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
internal val localSources: Provider<Set<File>> = localArtifact.map {
it.asFile.parseArtifactFile(rootDir).sources
it.asFile.parseArtifactFile(rootDir).filterArtifact().sources
}

/**
Expand All @@ -67,7 +68,7 @@ internal abstract class AbstractKoverReportTask : DefaultTask() {
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
internal val localReports: Provider<Set<File>> = localArtifact.map {
it.asFile.parseArtifactFile(rootDir).reports
it.asFile.parseArtifactFile(rootDir).filterArtifact().reports
}

@get:Nested
Expand All @@ -94,8 +95,36 @@ internal abstract class AbstractKoverReportTask : DefaultTask() {
}

private fun collectAllFiles(): ArtifactContent {
val local = localArtifact.get().asFile.parseArtifactFile(rootDir)
return local.joinWith(externalArtifacts.files.map { it.parseArtifactFile(rootDir) }).existing()
val local = localArtifact.get().asFile.parseArtifactFile(rootDir).filterArtifact()
return local.joinWith(externalArtifacts.files.map { it.parseArtifactFile(rootDir).filterArtifact() }).existing()
}

private fun ArtifactContent.filterArtifact(): ArtifactContent {
println("START FILTER")
val reportFilters = filters.get()
println("FILTER INCLUDES ${reportFilters.includeProjects}")
if (reportFilters.includeProjects.isNotEmpty()) {
println("INCLUDED = ${reportFilters.includeProjects}")
val notIncluded = reportFilters.includeProjects.none { filter ->
KoverFeatures.koverWildcardToRegex(filter).toRegex().matches(path)
}
if (notIncluded) {
println("INCLUDED FILTER")
return ArtifactContent.Empty
}
}
println("FILTER EXCLUDES ${reportFilters.excludeProjects}")
if (reportFilters.excludeProjects.isNotEmpty()) {
val excluded = reportFilters.excludeProjects.any { filter ->
KoverFeatures.koverWildcardToRegex(filter).toRegex().matches(path)
}
if (excluded) {
println("EXCLUDED FILTER")
return ArtifactContent.Empty
}
}
println("FILTERED OK")
return this
}
}

Expand Up @@ -42,9 +42,11 @@ internal abstract class KoverArtifactGenerationTask : DefaultTask() {

private val rootDir: File = project.rootDir

private val projectPath: String = project.path

@TaskAction
fun generate() {
val mainContent = ArtifactContent(sources.toSet(), outputDirs.toSet(), reports.toSet())
val mainContent = ArtifactContent(projectPath, sources.toSet(), outputDirs.toSet(), reports.toSet())
val additional = additionalArtifacts.files.map { it.parseArtifactFile(rootDir) }
mainContent.joinWith(additional).write(artifactFile.get().asFile, rootDir)
}
Expand Down

0 comments on commit 269939e

Please sign in to comment.