Hi, I am trying to add Jacoco, code coverage report, into my Android project.
As this is long file, I try to put some related parts.
Although I am able to successfully run following command on my laptop, Bitrise CI complains that:
Task ‘testFlvDebugUnitTestCoverage’ not found in root project ‘src’.
This command is being executed on Bitrise:
$ ./gradlew “–build-file” “build.gradle” “testFlvDebugUnitTestCoverage” “–continue” “–stacktrace”
So, any idea would be appreciated.
Architecture of my app looks like:
-\ passenger-android
---- build.gradle
---- jacoco.gradle
---- setings.gradle
----\ passenger-app
-------- build.gradle
----\ passenger-sdk (this is library for passenger-app)
-------- build.gradle
This is what I have in build.gradle file of passenger-app:
apply plugin: 'com.android.application'
android androidConfiguration
apply plugin: 'io.fabric'
apply plugin: 'com.getkeepsafe.dexcount'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.neenbedankt.android-apt'
apply from: "$rootDir/jacoco.gradle"
android {
....
buildTypes {
debug {
testCoverageEnabled = true
}
....
}
This is what I have in build.gradle file of passenger-sdk:
apply plugin: 'com.android.library'
android androidConfiguration
apply plugin: 'com.neenbedankt.android-apt'
apply from: "$rootDir/jacoco.gradle"
android {
....
}
This is what I have in build.gradle file in root of project:
buildscript {
repositories {
jcenter()
maven { url 'https://maven.fabric.io/public' }
maven { url 'http://maven.apptimize.com/artifactory/repo' }
maven { url 'https://zendesk.artifactoryonline.com/zendesk/repo' }
maven { url "http://www.leanplum.com/leanplum-sdks/" }
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.0'
classpath 'io.fabric.tools:gradle:1.22.0'
classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.6.2'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
classpath 'com.google.gms:google-services:3.0.0'
}
}
ext {
androidConfiguration = {
sourceSets {
main {
manifest.srcFile 'src/main/AndroidManifest.xml'
java.srcDirs = ['src/main/java']
resources.srcDirs = ['src/main/resources']
aidl.srcDirs = ['src/main/java']
renderscript.srcDirs = ['src/main/java']
res.srcDirs = ['src/main/res']
assets.srcDirs = ['src/main/assets']
}
}
testOptions {
unitTests.all {
jacoco {
includeNoLocationClasses = true
}
}
}
}
}
And Finally this is my jacoco.gradle file:
import java.util.regex.Pattern
apply plugin: 'jacoco'
/**
* Jacoco is test coverage tool.
*
* Latest version can be found at following link but be careful upgrading —
* the latest version may not be compatible yet, resulting in quirks like empty coverage reports.
* https://bintray.com/bintray/jcenter/org.jacoco:org.jacoco.core
*
* You can run the test via following command:
* $./gradlew testFlvDebugUnitTestCoverage
*/
jacoco {
toolVersion = "0.7.9"
}
project.afterEvaluate {
// Grab all build types and product flavors
def buildTypes = android.buildTypes.collect { type -> type.name }
def productFlavors = android.productFlavors.collect { flavor -> flavor.name }
// When no product flavors defined, use empty
if (!productFlavors) productFlavors.add('')
productFlavors.each { productFlavorName ->
buildTypes.each { buildTypeName ->
def sourceName, sourcePath
if (!productFlavorName) {
sourceName = sourcePath = "${buildTypeName}"
} else {
sourceName = "${productFlavorName}${buildTypeName.capitalize()}"
sourcePath = "${productFlavorName}/${buildTypeName}"
}
def testTaskName = "test${sourceName.capitalize()}UnitTest"
// Create coverage task of form 'testFlavorTypeCoverage' depending on 'testFlavorTypeUnitTest'
task "${testTaskName}Coverage"(type: JacocoReport, dependsOn: "$testTaskName") {
group = "Reporting"
description = "Generate Jacoco coverage reports on the ${sourceName.capitalize()} build."
classDirectories = fileTree(
dir: "${project.buildDir}/intermediates/classes/${sourcePath}",
excludes: [
'**/R.class',
'**/R$*.class',
'**/*$ViewInjector*.*',
'**/*$ViewBinder*.*',
'**/BuildConfig.*',
'**/Manifest*.*',
// ignore ButterKnife generated code
'**/**$ViewBinder*.class',
// ignore Dagger 2 generated code
'**/Dagger*.class',
'**/**_MembersInjector*.class',
'**/**Module_*.class',
'**/**_Factory*.class',
// ignore AutoValue classes
'**/*AutoValue_*.class',
// ignore AutoValueGson classes
'**/AutoValueGson_*.class',
// ignore RxJava Closure classes
'**/**AjcClosure**.class',
// ignore eventbus
'**/AppEventBusIndex.class',
// ignore MyApp classes
'com/flv1/passenger/BuildConfigHelper.class',
'com/flv1/passenger/di/**',
'com/flv2/passenger/BuildConfigHelper.class',
'com/flv2/passenger/di/**',
'**/*Constants.class'
]
)
def coverageSourceDirs = [
"src/main/java",
"src/$productFlavorName/java",
"src/$buildTypeName/java"
]
additionalSourceDirs = files(coverageSourceDirs)
sourceDirectories = files(coverageSourceDirs)
executionData = files("${project.buildDir}/jacoco/${testTaskName}.exec")
reports {
xml.enabled = true
html.enabled = true
}
doLast {
println "csv report has been generated to file://${reports.csv.destination}"
println "xml report has been generated to file://${reports.xml.destination}"
println "html report has been generated to file://${reports.html.destination}/index.html"
printTotalCoverageFromHtml(new File("${reports.html.destination}/index.html"))
}
}
}
}
}
// prints total coverage to console
void printTotalCoverageFromHtml(File file) {
def html = file.getText();
def instructionsPattern = Pattern.compile(">([\\d\\,]+)\\sof\\s([\\d\\,]+)<")
def matcher = instructionsPattern.matcher(html)
matcher.find()
def missed = matcher.group(1)
def total = matcher.group(2)
def percentsPattern = Pattern.compile(">([\\d]+)%<")
matcher = percentsPattern.matcher(html)
matcher.find()
def percents = matcher.group(1)
def covered = Integer.parseInt(total.replace(",", "")) - Integer.parseInt(missed.replace(",", ""))
println("Coverage: total instructions ${covered}/${total} - ${percents}%")
}