Skip to content

Commit

Permalink
Added reports filtering
Browse files Browse the repository at this point in the history
Resolves #17
  • Loading branch information
shanshin committed Jan 13, 2022
1 parent de1452a commit c5ffe36
Show file tree
Hide file tree
Showing 11 changed files with 276 additions and 79 deletions.
27 changes: 25 additions & 2 deletions README.md
Expand Up @@ -204,11 +204,17 @@ the project in which the plugin is applied (usually this is the root project):
tasks.koverMergedHtmlReport {
isEnabled = true // false to disable report generation
htmlReportDir.set(layout.buildDirectory.dir("my-merged-report/html-result"))

includes = listOf("com.example.*") // inclusion rules for classes
excludes = listOf("com.example.subpackage.*") // exclusion rules for classes
}

tasks.koverMergedXmlReport {
isEnabled = true // false to disable report generation
xmlReportFile.set(layout.buildDirectory.file("my-merged-report/result.xml"))

includes = listOf("com.example.*") // inclusion rules for classes
excludes = listOf("com.example.subpackage.*") // exclusion rules for classes
}
```
</details>
Expand All @@ -220,11 +226,17 @@ tasks.koverMergedXmlReport {
tasks.koverMergedHtmlReport {
enabled = true // false to disable report generation
htmlReportDir.set(layout.buildDirectory.dir("my-merged-report/html-result"))
includes = ['com.example.*'] // inclusion rules for classes
excludes = ['com.example.subpackage.*'] // exclusion rules for classes
}
tasks.koverMergedXmlReport {
enabled = true // false to disable report generation
xmlReportFile.set(layout.buildDirectory.file("my-merged-report/result.xml"))
includes = ['com.example.*'] // inclusion rules for classes
excludes = ['com.example.subpackage.*'] // exclusion rules for classes
}
```
</details>
Expand All @@ -241,11 +253,17 @@ the corresponding tasks in this project:
tasks.koverHtmlReport {
isEnabled = true // false to disable report generation
htmlReportDir.set(layout.buildDirectory.dir("my-project-report/html-result"))

includes = listOf("com.example.*") // inclusion rules for classes
excludes = listOf("com.example.subpackage.*") // exclusion rules for classes
}

tasks.koverXmlReport {
isEnabled = true // false to disable report generation
xmlReportFile.set(layout.buildDirectory.file("my-project-report/result.xml"))

includes = listOf("com.example.*") // inclusion rules for classes
excludes = listOf("com.example.subpackage.*") // exclusion rules for classes
}
```
</details>
Expand All @@ -257,11 +275,16 @@ tasks.koverXmlReport {
tasks.koverHtmlReport {
enabled = true // false to disable report generation
htmlReportDir.set(layout.buildDirectory.dir("my-project-report/html-result"))
includes = ['com.example.*'] // inclusion rules for classes
excludes = ['com.example.subpackage.*'] // exclusion rules for classes
}
tasks.koverXmlReport {
enabled = true // false to disable report generation
xmlReportFile.set(layout.buildDirectory.file("my-project-report/result.xml"))
includes = ['com.example.*'] // inclusion rules for classes
excludes = ['com.example.subpackage.*'] // exclusion rules for classes
}
```
</details>
Expand Down Expand Up @@ -308,7 +331,7 @@ kover {
coverageEngine.set(kotlinx.kover.api.CoverageEngine.INTELLIJ) // change instrumentation agent and reporter
intellijEngineVersion.set("1.0.640") // change version of IntelliJ agent and reporter
jacocoEngineVersion.set("0.8.7") // change version of JaCoCo agent and reporter
generateReportOnCheck.set(true) // false to do not execute `koverMergedReport` task before `check` task
generateReportOnCheck = true // false to do not execute `koverMergedReport` task before `check` task
disabledProjects = setOf() // setOf("project-name") to disable coverage for project with name `project-name`
instrumentAndroidPackage = false // true to instrument packages `android.*` and `com.android.*`
runAllTestsForProjectTask = false // true to run all tests in all projects if `koverHtmlReport`, `koverXmlReport`, `koverReport`, `koverVerify` or `check` tasks executed on some project
Expand All @@ -325,7 +348,7 @@ kover {
coverageEngine.set(kotlinx.kover.api.CoverageEngine.INTELLIJ) // change instrumentation agent and reporter
intellijEngineVersion.set('1.0.640') // change version of IntelliJ agent and reporter
jacocoEngineVersion.set('0.8.7') // change version of JaCoCo agent and reporter
generateReportOnCheck.set(true) // false to do not execute `koverMergedReport` task before `check` task
generateReportOnCheck = true // false to do not execute `koverMergedReport` task before `check` task
disabledProjects = [] // ["project-name"] to disable coverage for project with name `project-name`
instrumentAndroidPackage = false // true to instrument packages `android.*` and `com.android.*`
runAllTestsForProjectTask = false // true to run all tests in all projects if `koverHtmlReport`, `koverXmlReport`, `koverReport`, `koverVerify` or `check` tasks executed on some project
Expand Down
@@ -0,0 +1,62 @@
package kotlinx.kover.test.functional.cases

import kotlinx.kover.test.functional.cases.utils.*
import kotlinx.kover.test.functional.core.*
import kotlinx.kover.test.functional.core.BaseGradleScriptTest
import kotlin.test.*

internal class ReportsFilteringTests : BaseGradleScriptTest() {

@Test
fun testExclude() {
builder("Test exclusion of classes from XML report")
.languages(GradleScriptLanguage.KOTLIN, GradleScriptLanguage.GROOVY)
.sources("simple")
.config(
"""
tasks.koverMergedXmlReport {
excludes = listOf("org.jetbrains.*Exa?ple*")
}""".trimIndent(),
"""
tasks.koverMergedXmlReport {
excludes = ['org.jetbrains.*Exa?ple*']
}""".trimIndent()
)
.build()
.run("build") {
xml(defaultXmlReport()) {
assertCounterAbsent(classCounter("org.jetbrains.ExampleClass"))
assertCounterCovered(classCounter("org.jetbrains.SecondClass"))
}
}
}

@Test
fun testExcludeInclude() {
builder("Test inclusion and exclusion of classes in XML report")
.languages(GradleScriptLanguage.KOTLIN, GradleScriptLanguage.GROOVY)
.sources("simple")
.config(
"""
tasks.koverMergedXmlReport {
includes = listOf("org.jetbrains.*Cla?s")
excludes = listOf("org.jetbrains.*Exa?ple*")
}""".trimIndent(),

"""
tasks.koverMergedXmlReport {
includes = ['org.jetbrains.*Cla?s']
excludes = ['org.jetbrains.*Exa?ple*']
}""".trimIndent()
)
.build()
.run("build") {
xml(defaultXmlReport()) {
assertCounterAbsent(classCounter("org.jetbrains.ExampleClass"))
assertCounterAbsent(classCounter("org.jetbrains.Unused"))
assertCounterFullyCovered(classCounter("org.jetbrains.SecondClass"))
}
}
}

}
Expand Up @@ -117,7 +117,7 @@ private open class ProjectBuilderImpl<B : ProjectBuilder<B>>(val projectState: P
}

override fun config(kotlin: String, groovy: String): B {
projectState.testScripts += GradleScript(kotlin, groovy)
projectState.scripts += GradleScript(kotlin, groovy)
return this as B
}

Expand Down
4 changes: 0 additions & 4 deletions src/main/kotlin/kotlinx/kover/KoverPlugin.kt
Expand Up @@ -86,8 +86,6 @@ class KoverPlugin : Plugin<Project> {
projectProviders
) {
it.onlyIf { t -> (t as KoverVerificationTask).rules.isNotEmpty() }
// kover takes counter values from XML file. Remove after reporter upgrade
it.mustRunAfter(xmlReportTask)
it.description = "Verifies code coverage metrics of one project based on specified rules."
}

Expand Down Expand Up @@ -141,8 +139,6 @@ class KoverPlugin : Plugin<Project> {
providers
) {
it.onlyIf { t -> (t as KoverMergedVerificationTask).rules.isNotEmpty() }
// kover takes counter values from XML file. Remove after reporter upgrade
it.mustRunAfter(xmlReportTask)
it.description = "Verifies code coverage metrics of all projects based on specified rules."
}

Expand Down
8 changes: 6 additions & 2 deletions src/main/kotlin/kotlinx/kover/api/KoverTaskExtension.kt
Expand Up @@ -29,7 +29,9 @@ open class KoverTaskExtension(objects: ObjectFactory) {
public val binaryReportFile: Property<File> = objects.property(File::class.java)

/**
* Specifies class inclusion rules coverage engine. Exclusion rules have priority over inclusion ones.
* Specifies class instrumentation inclusion rules.
* Only the specified classes may be instrumented, for the remaining classes there will be zero coverage.
* Exclusion rules have priority over inclusion ones.
*
* Inclusion rules are represented as a set of fully-qualified names of the classes being instrumented.
* It's possible to use `*` and `?` wildcards.
Expand All @@ -38,7 +40,9 @@ open class KoverTaskExtension(objects: ObjectFactory) {
public var includes: List<String> = emptyList()

/**
* Specifies class exclusion rules for coverage engine. Exclusion rules have priority over inclusion ones.
* Specifies class instrumentation exclusion rules.
* The specified classes will not be instrumented and there will be zero coverage for them.
* Exclusion rules have priority over inclusion ones.
*
* Exclusion rules are represented as a set of fully-qualified names of the classes being instrumented.
* It's possible to use `*` and `?` wildcards.
Expand Down
21 changes: 21 additions & 0 deletions src/main/kotlin/kotlinx/kover/engines/commons/Reports.kt
Expand Up @@ -4,3 +4,24 @@ import java.io.*

internal class Report(val files: List<File>, val projects: List<ProjectInfo>)
internal class ProjectInfo(val sources: Iterable<File>, val outputs: Iterable<File>)

private val regexMetacharactersSet = "<([{\\^-=$!|]})+.>".toSet()

/**
* Replaces characters `*` or `.` to `.*` and `.` regexp characters.
*/
internal fun String.wildcardsToRegex(): String {
// in most cases, the characters `*` or `.` will be present therefore, we increase the capacity in advance
val builder = StringBuilder(length * 2)

forEach { char ->
when (char) {
in regexMetacharactersSet -> builder.append('\\').append(char)
'*' -> builder.append('.').append("*")
'?' -> builder.append('.')
else -> builder.append(char)
}
}

return builder.toString()
}
23 changes: 3 additions & 20 deletions src/main/kotlin/kotlinx/kover/engines/intellij/IntellijAgent.kt
Expand Up @@ -5,6 +5,7 @@
package kotlinx.kover.engines.intellij

import kotlinx.kover.api.*
import kotlinx.kover.engines.commons.*
import kotlinx.kover.engines.commons.CoverageAgent
import org.gradle.api.*
import org.gradle.api.artifacts.*
Expand Down Expand Up @@ -51,38 +52,20 @@ private class IntellijAgent(private val config: Configuration): CoverageAgent {
pw.appendLine(appendToDataFile.toString())
pw.appendLine(samplingMode.toString())
extension.includes.forEach { i ->
pw.appendLine(i.replaceWildcards())
pw.appendLine(i.wildcardsToRegex())
}

if (extension.excludes.isNotEmpty()) {
pw.appendLine("-exclude")
}

extension.excludes.forEach { e ->
pw.appendLine(e.replaceWildcards())
pw.appendLine(e.wildcardsToRegex())
}
}
}

private fun String.replaceWildcards(): String {
// in most cases, the characters `*` or `.` will be present therefore, we increase the capacity in advance
val builder = StringBuilder(length * 2)

forEach { char ->
when (char) {
in regexMetacharactersSet -> builder.append('\\').append(char)
'*' -> builder.append('.').append("*")
'?' -> builder.append('.')
else -> builder.append(char)
}
}

return builder.toString()
}
}

private val regexMetacharactersSet = "<([{\\^-=$!|]})+.>".toSet()

private fun Project.createIntellijConfig(koverExtension: KoverExtension): Configuration {
val config = project.configurations.create("IntellijKoverConfig")
config.isVisible = false
Expand Down
43 changes: 33 additions & 10 deletions src/main/kotlin/kotlinx/kover/engines/intellij/IntellijReports.kt
Expand Up @@ -16,6 +16,8 @@ internal fun Task.intellijReport(
report: Report,
xmlFile: File?,
htmlDir: File?,
includes: List<String>,
excludes: List<String>,
classpath: FileCollection
) {
xmlFile?.let {
Expand All @@ -28,7 +30,7 @@ internal fun Task.intellijReport(

val argsFile = File(temporaryDir, "intellijreport.json")
argsFile.printWriter().use { pw ->
pw.writeReportsJson(report, xmlFile, htmlDir)
pw.writeReportsJson(report, xmlFile, htmlDir, includes, excludes)
}

project.javaexec { e ->
Expand Down Expand Up @@ -95,22 +97,37 @@ JSON example:
private fun Writer.writeReportsJson(
report: Report,
xmlFile: File?,
htmlDir: File?
htmlDir: File?,
includes: List<String>,
excludes: List<String>,
) {
appendLine("{")

appendLine(""" "reports": [ """)
appendLine(report.files.joinToString(",\n ", " ") { f ->
"""{"ic": "${f.safePath()}"}"""
"""{"ic": ${f.jsonString}}"""
})
appendLine(""" ], """)

xmlFile?.also {
appendLine(""" "xml": "${it.safePath()}",""")
appendLine(""" "xml": ${it.jsonString},""")
}
htmlDir?.also {
appendLine(""" "html": "${it.safePath()}",""")
appendLine(""" "html": ${it.jsonString},""")
}

if (includes.isNotEmpty()) {
appendLine(""" "include": {""")
appendLine(includes.joinToString(", ", """ "classes": [""", "]") { i -> i.wildcardsToRegex().jsonString })
appendLine(""" },""")
}

if (excludes.isNotEmpty()) {
appendLine(""" "exclude": {""")
appendLine(excludes.joinToString(", ", """ "classes": [""", "]") { e -> e.wildcardsToRegex().jsonString })
appendLine(""" },""")
}

appendLine(""" "modules": [""")
report.projects.forEachIndexed { index, aProject ->
writeProjectReportJson(aProject, index == (report.projects.size - 1))
Expand All @@ -123,19 +140,25 @@ private fun Writer.writeProjectReportJson(projectInfo: ProjectInfo, isLast: Bool
appendLine(""" {""")
appendLine(""" "output": [""")
appendLine(
projectInfo.outputs.joinToString(",\n ", " ") { f -> '"' + f.safePath() + '"' })
projectInfo.outputs.joinToString(",\n ", " ") { f -> f.jsonString })
appendLine(""" ],""")
appendLine(""" "sources": [""")

appendLine(
projectInfo.sources.joinToString(",\n ", " ") { f -> '"' + f.safePath() + '"' })
projectInfo.sources.joinToString(",\n ", " ") { f -> f.jsonString })
appendLine(""" ]""")
appendLine(""" }${if (isLast) "" else ","}""")
}

private fun File.safePath(): String {
return canonicalPath.replace("\\", "\\\\").replace("\"", "\\\"")
}
private val File.jsonString: String
get() {
return canonicalPath.jsonString
}

private val String.jsonString: String
get() {
return '"' + replace("\\", "\\\\").replace("\"", "\\\"") + '"'
}

internal fun Task.intellijVerification(
xmlFile: File,
Expand Down

0 comments on commit c5ffe36

Please sign in to comment.