Skip to content

Commit 8ac03a7

Browse files
authored
Add verification of the generated code coverage report (#6)
* Add verification of the generated coverage report * Convert some "Java style" Kotlin code to be more "Kotlin style" * Code cleanup and other micro improvements
1 parent 579eae8 commit 8ac03a7

File tree

9 files changed

+106
-71
lines changed

9 files changed

+106
-71
lines changed

gradle/dependencies.gradle

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ ext {
33
minSdk : 21,
44
targetSdk : 28,
55
compileSdk : 28,
6-
// Kotlin version is used in multiple places hence it is available as version variable
76
kotlin : "1.3.11"
87
]
98
projectDependency = [
@@ -20,7 +19,9 @@ ext {
2019
junit : "junit:junit:4.12",
2120
truth : "com.google.truth:truth:0.42",
2221
supportTestRunner : "com.android.support.test:runner:1.0.2",
23-
espressoCore : "com.android.support.test.espresso:espresso-core:3.0.2"
22+
espressoCore : "com.android.support.test.espresso:espresso-core:3.0.2",
23+
commonsCsv : "org.apache.commons:commons-csv:1.6",
24+
kotlinTest : "org.jetbrains.kotlin:kotlin-test:${projectVersion.kotlin}"
2425
]
2526

2627
// Used by the plugin-version-handler.gradle
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
#Tue Jan 22 16:40:52 CET 2019
12
distributionBase=GRADLE_USER_HOME
23
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-bin.zip
44
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists
6+
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip

plugin/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,11 @@ dependencies {
5353
implementation projectDependency.kotlinStdlibJdk7
5454

5555
testImplementation projectDependency.kotlinStdlibJdk7
56+
testImplementation projectDependency.kotlinTest
5657
testImplementation gradleTestKit()
5758
testImplementation projectDependency.junit
5859
testImplementation projectDependency.truth
60+
testImplementation projectDependency.commonsCsv
5961
}
6062

6163
apply from: rootProject.file('publish.gradle')

plugin/src/main/kotlin/org/neotech/plugin/rootcoverage/RootCoveragePlugin.kt

Lines changed: 39 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -31,39 +31,35 @@ class RootCoveragePlugin : Plugin<Project> {
3131
project.afterEvaluate { createCoverageTaskForRoot(it) }
3232
}
3333

34-
private fun getFileFilterPatterns(): List<String> {
35-
return listOf(
36-
"**/AutoValue_*.*", // Filter to remove generated files from: https://github.com/google/auto
37-
//"**/*JavascriptBridge.class",
38-
39-
// Android Databinding
40-
"**/*databinding",
41-
"**/*binders",
42-
"**/*layouts",
43-
"**/BR.class", // Filter to remove generated databinding files
44-
45-
// Core Android generated class filters
46-
"**/R.class",
47-
"**/R$*.class",
48-
"**/Manifest*.*",
49-
"**/BuildConfig.class",
50-
"android/**/*.*",
51-
52-
"**/*\$ViewBinder*.*",
53-
"**/*\$ViewInjector*.*",
54-
"**/Lambda$*.class",
55-
"**/Lambda.class",
56-
"**/*Lambda.class",
57-
"**/*Lambda*.class",
58-
"**/*\$InjectAdapter.class",
59-
"**/*\$ModuleAdapter.class",
60-
"**/*\$ViewInjector*.class") + rootProjectExtension.excludes
61-
}
62-
63-
private fun getBuildVariantFor(project: Project): String {
64-
return rootProjectExtension.buildVariantOverrides[project.path]
65-
?: rootProjectExtension.buildVariant
66-
}
34+
private fun getFileFilterPatterns(): List<String> = listOf(
35+
"**/AutoValue_*.*", // Filter to remove generated files from: https://github.com/google/auto
36+
//"**/*JavascriptBridge.class",
37+
38+
// Android Databinding
39+
"**/*databinding",
40+
"**/*binders",
41+
"**/*layouts",
42+
"**/BR.class", // Filter to remove generated databinding files
43+
44+
// Core Android generated class filters
45+
"**/R.class",
46+
"**/R$*.class",
47+
"**/Manifest*.*",
48+
"**/BuildConfig.class",
49+
"android/**/*.*",
50+
51+
"**/*\$ViewBinder*.*",
52+
"**/*\$ViewInjector*.*",
53+
"**/Lambda$*.class",
54+
"**/Lambda.class",
55+
"**/*Lambda.class",
56+
"**/*Lambda*.class",
57+
"**/*\$InjectAdapter.class",
58+
"**/*\$ModuleAdapter.class",
59+
"**/*\$ViewInjector*.class") + rootProjectExtension.excludes
60+
61+
private fun getBuildVariantFor(project: Project): String =
62+
rootProjectExtension.buildVariantOverrides[project.path] ?: rootProjectExtension.buildVariant
6763

6864
private fun getExecutionDataFilePatterns(): List<String> {
6965
val list = mutableListOf<String>()
@@ -99,10 +95,13 @@ class RootCoveragePlugin : Plugin<Project> {
9995
task.group = "reporting"
10096
task.description = "Generates a Jacoco report with combined results from all the subprojects."
10197

102-
task.reports.html.isEnabled = true
103-
task.reports.xml.isEnabled = false
104-
task.reports.csv.isEnabled = false
98+
task.reports.html.isEnabled = rootProjectExtension.generateHtml
99+
task.reports.xml.isEnabled = rootProjectExtension.generateXml
100+
task.reports.csv.isEnabled = rootProjectExtension.generateCsv
101+
105102
task.reports.html.destination = project.file("${project.buildDir}/reports/jacoco")
103+
task.reports.xml.destination = project.file("${project.buildDir}/reports/jacoco.xml")
104+
task.reports.csv.destination = project.file("${project.buildDir}/reports/jacoco.csv")
106105

107106
// Add some run-time checks.
108107
task.doFirst {
@@ -200,8 +199,6 @@ class RootCoveragePlugin : Plugin<Project> {
200199
val javaClassTrees = javaClassOutputs.files.map { file ->
201200
project.fileTree(file, excludes = getFileFilterPatterns()).excludeNonClassFiles()
202201
}
203-
// Hard coded alternative to get class files for Java.
204-
//val classesTree = project.fileTree(mapOf("dir" to "${project.buildDir}/intermediates/classes/${variant.dirName}", "excludes" to getFileFilterPatterns()))
205202

206203
// TODO: No idea how to dynamically get the kotlin class files output folder, so for now this is hardcoded.
207204
// TODO: For some reason the tmp/kotlin-classes folder does not use the variant.dirName property, for now we instead use the variant.name.
@@ -226,21 +223,9 @@ class RootCoveragePlugin : Plugin<Project> {
226223
// Make the root task depend on the sub-project code coverage task
227224
rootTask.dependsOn(subModuleTask)
228225

229-
// Set or add the sub-task class directories to the root task
230-
if (rootTask.classDirectories == null) {
231-
rootTask.classDirectories.setFrom(subModuleTask.classDirectories)
232-
} else {
233-
rootTask.additionalClassDirs(subModuleTask.classDirectories)
234-
}
235-
236-
// Set or add the sub-task source directories to the root task
237-
if (rootTask.sourceDirectories == null) {
238-
rootTask.sourceDirectories.setFrom(subModuleTask.sourceDirectories)
239-
} else {
240-
rootTask.additionalSourceDirs(subModuleTask.sourceDirectories)
241-
}
242-
243-
// Add the sub-task class directories to the root task
244-
rootTask.executionData(subModuleTask.executionData)
226+
// Add the sub-task class directories, source directories and executionData to the root task
227+
rootTask.classDirectories.from(subModuleTask.classDirectories)
228+
rootTask.sourceDirectories.from(subModuleTask.sourceDirectories)
229+
rootTask.executionData.from(subModuleTask.executionData)
245230
}
246231
}

plugin/src/main/kotlin/org/neotech/plugin/rootcoverage/RootCoveragePluginExtension.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import com.android.builder.model.TestVariantBuildOutput
44

55
open class RootCoveragePluginExtension {
66

7+
var generateCsv: Boolean = false
8+
var generateHtml: Boolean = true
9+
var generateXml: Boolean = false
710
var buildVariant: String = "debug"
811
var buildVariantOverrides: Map<String, String> = mutableMapOf()
912
var excludes: List<String> = mutableListOf()

plugin/src/main/kotlin/org/neotech/plugin/rootcoverage/Utilities.kt

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,14 @@ import org.gradle.api.file.FileTree
77
/**
88
* Executes an include match on the FileTree that only includes files with the .class extension.
99
*/
10-
fun FileTree.excludeNonClassFiles(): FileTree {
11-
return matching {
12-
it.include("**/*.class")
13-
}
14-
}
10+
fun FileTree.excludeNonClassFiles(): FileTree = matching { it.include("**/*.class") }
1511

1612
/**
1713
* Wrapper around Project.fileTree(Map<String, ?>) to use it easier from Kotlin code (no need to create the map every time).
1814
* Currently only supports the dir, excludes and includes properties.
1915
* */
20-
fun Project.fileTree(dir: Any, excludes: List<String> = listOf(), includes: List<String> = listOf()): ConfigurableFileTree {
21-
return fileTree(mapOf(
22-
"dir" to dir,
23-
"excludes" to excludes,
24-
"includes" to includes))
25-
}
16+
fun Project.fileTree(dir: Any, excludes: List<String> = listOf(), includes: List<String> = listOf()): ConfigurableFileTree =
17+
fileTree(mapOf(
18+
"dir" to dir,
19+
"excludes" to excludes,
20+
"includes" to includes))
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package org.neotech.plugin.rootcoverage
2+
3+
import org.apache.commons.csv.CSVFormat
4+
import org.apache.commons.csv.CSVParser
5+
import org.apache.commons.csv.CSVRecord
6+
import java.io.File
7+
import java.nio.charset.StandardCharsets
8+
import kotlin.test.assertEquals
9+
10+
class CoverageReport private constructor(
11+
private val instructionMissedColumn: Int,
12+
private val branchMissedColumn: Int,
13+
private val packageColumn: Int,
14+
private val classColumn: Int,
15+
private val records: List<CSVRecord>) {
16+
17+
private fun CSVRecord?.assertFullCoverageCoverage() {
18+
kotlin.test.assertNotNull(this)
19+
assertEquals(this[instructionMissedColumn]?.toInt(), 0)
20+
assertEquals(this[branchMissedColumn]?.toInt(), 0)
21+
}
22+
23+
fun assertFullCoverage(packageName: String, className: String) {
24+
find(packageName, className).assertFullCoverageCoverage()
25+
}
26+
27+
fun find(packageName: String, className: String): CSVRecord? = records.find {
28+
it[packageColumn] == packageName && it[classColumn] == className
29+
}
30+
31+
companion object {
32+
33+
fun from(file: File): CoverageReport = CSVParser.parse(file, StandardCharsets.UTF_8, CSVFormat.DEFAULT.withHeader()).use {
34+
CoverageReport(
35+
it.headerMap["INSTRUCTION_MISSED"]!!,
36+
it.headerMap["BRANCH_MISSED"]!!,
37+
it.headerMap["PACKAGE"]!!,
38+
it.headerMap["CLASS"]!!,
39+
it.records)
40+
}
41+
}
42+
}

plugin/src/test/kotlin/org/neotech/plugin/rootcoverage/IntegrationTest.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
package org.neotech.plugin.rootcoverage
22

33
import com.google.common.truth.Truth.assertThat
4+
import org.gradle.testing.jacoco.tasks.JacocoReport
45
import org.gradle.testkit.runner.GradleRunner
56
import org.gradle.testkit.runner.TaskOutcome
6-
import org.junit.Assert.assertEquals
77
import org.junit.Test
88
import org.junit.runner.RunWith
99
import org.junit.runners.Parameterized
10-
import org.junit.runners.Parameterized.Parameters
1110
import java.io.File
1211
import java.io.OutputStreamWriter
12+
import kotlin.test.assertEquals
1313

1414
@RunWith(Parameterized::class)
1515
class IntegrationTest(
@@ -32,13 +32,18 @@ class IntegrationTest(
3232

3333
assertThat(result.output).contains("BUILD SUCCESSFUL")
3434
assertEquals(result.task(":rootCodeCoverageReport")!!.outcome, TaskOutcome.SUCCESS)
35+
36+
val report = CoverageReport.from(File(projectRoot, "build/reports/jacoco.csv"))
37+
38+
report.assertFullCoverage("org.neotech.library.android", "LibraryAndroidJava")
39+
report.assertFullCoverage("org.neotech.library.android", "LibraryAndroidKotlin")
3540
}
3641

3742
companion object {
3843

3944
// This method is used by the JVM (Parameterized JUnit Runner)
4045
@Suppress("unused")
41-
@Parameters(name = "{1}")
46+
@Parameterized.Parameters(name = "{1}")
4247
@JvmStatic
4348
fun parameters(): List<Array<Any>> {
4449
return File("src/test/test-fixtures")

plugin/src/test/test-fixtures/multi-module/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ jacoco {
3939
import com.android.builder.model.TestVariantBuildOutput.TestType
4040

4141
rootCoverage {
42+
generateCsv true
4243
buildVariant "debug"
4344
testTypes = [TestType.UNIT, TestType.ANDROID_TEST]
4445
}

0 commit comments

Comments
 (0)