How/when does JaCoCo create .exec files? - android

This is the configuration for JaCoCo in my build.gradle and my project (app) includes a library module which is where this JaCoCo config is specified since the app contains nothing but the Application class.
task jacocoTestReport(type: JacocoReport, dependsOn: 'testDebugUnitTest') {
reports {
xml.enabled = true
html.enabled = true
}
def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*']
def debugTree = fileTree(dir: "${buildDir}/intermediates/classes/debug", excludes: fileFilter)
def mainSrc = "${project.projectDir}/src/main/java"
sourceDirectories = files([mainSrc])
classDirectories = files([debugTree])
executionData = files("${buildDir}/jacoco/testDebugUnitTest.exec")
}
However, I am not sure how/when the testDebugUnitTest.exec is created the first time since I execute the build like so:
./gradlew clean jacocoTestReport
and because of clean in the above command, the .exec gets wiped out. For now, I manually copy it over to that location since I don't understand how/when that .exec gets generated.
And this is output in logcat for when I run this command:
Task :myLibrary:jacocoTestReport SKIPPED
14:10:16.642 [DEBUG] [org.gradle.internal.progress.DefaultBuildOperationExecutor] Build operation 'Task :myLibrary:jacocoTestReport' started
14:10:16.642 [DEBUG] [org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter] Starting to execute task ':myLibrary:jacocoTestReport'
14:10:16.642 [INFO] [org.gradle.api.internal.file.collections.DirectoryFileTree] file or directory '/path/code/android/myLibrary/build/jacoco', not found

Thanks to this post I understood that the exec and ec are created when we first run the jacocoTestReport task (defined in build.gradle):
Now, each time you run your tests, those tasks are executed and jacoco will generate the raw file of the coverage report for this set of tests in a specific format. Exec for unit tests and ec for instrumented tests, you can find them in your build folder.

Related

Is it possible for Instrumented tests to create coverage for a library module?

I am in the process of modularising the app I am working on and my coverage has dropped over 20% since splitting my instrumentation tests into the app module.
The app is being split into app, core, custom, where core is an android library and the other 2 modules are apps. The majority of the app's functionality will live in core and it is currently mostly tested through instrumentation tests which now reside in app.
Is there a way that instrumentation tests in an application module can generate a coverage report that will include library module sources?
I looked at this question here which bears great similarity to my dilemma but this seems outdated as publishNonDefault is deprecated and does nothing as libraries now publish all variants
My efforts are ongoing on this PR
It will be difficult to complete modularisation with such a drop of coverage, I would expect the coverage to be unchanged post modularisation.
EDIT: I have created a repro project here
Eventual answer came from here so all credit to them. Posting the contents of the file here for anybody looking in the future
apply plugin: 'jacoco'
jacoco {
toolVersion = "$jacocoVersion"
}
tasks.withType(Test) {
jacoco.includeNoLocationClasses = true
}
task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest', 'createDebugCoverageReport']) {
group "Reporting"
description "Generate Jacoco coverage reports."
reports {
xml.enabled = true
html.enabled = true
html.destination file("${rootProject.buildDir}/coverage-report")
}
def javaClasses = []
def kotlinClasses = []
def javaSrc = []
def kotlinSrc = []
def execution = []
def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*']
rootProject.subprojects.each { proj ->
javaClasses << fileTree(dir: "$proj.buildDir/intermediates/javac/debug", excludes: fileFilter)
kotlinClasses << fileTree(dir: "$proj.buildDir/tmp/kotlin-classes/debug", excludes: fileFilter)
javaSrc << "$proj.projectDir/src/main/java"
kotlinSrc << "$proj.projectDir/src/main/kotlin"
execution << fileTree(dir: proj.buildDir,
includes: ['jacoco/testDebugUnitTest.exec',
'outputs/code_coverage/debugAndroidTest/connected/**/*.ec'])
}
sourceDirectories = files([javaSrc, kotlinSrc])
classDirectories = files([javaClasses, kotlinClasses])
print execution
executionData = files(execution)
doLast() {
print "file://${reports.html.destination}/index.html"
}
}
FileFilter probably needs some improvement for a modern Android application eg Dagger/ViewBinding.
I applied this in my app/build.gradle and after running gradlew jacocoTestReport the report with full coverage was present in [projRoot]/build/coverage-report.
Repro project has been updated with the solution.
See the JacocoMerge task which can merge multiple jacoco execution files into a single file. You can then generate a JacocoReport from the merged exec file
No.
Just add your instrumentation tests to the module that has the code under test.
Was under a misconception that instrumentation tests could not be run on a library project due to this SO answer, I have proposed an edit to reflect the up to date documentation.
EDIT: I later found a solution but honestly it probably should not be used and this is the path you should go down. Instrumentation tests probably shouldn't be used for coverage to begin with and unless you are stuck with legacy do not use the solution presented on this page.
See the JaCoCo plugin docs which show that a task named "jacocoTestReport" is added by the JaCoCo plugin. And you can see there's an "additionalSourceDirs" property on the JacocoReport task
So you can do something like
apply plugin: 'java-library'
apply plugin: 'jacoco'
evaluationDependsOn ':another-project'
tasks.withType(JacocoReport) {
def otherSourceSet = project(':another-project').sourceSets.main
additionalSourceDirs.from otherSourceSet.allJava
additionalClassDirs.from otherSourceSet.output
}

How to get code coverage for instrumentation tests in Android

In Android Studio, I didn't find the option "Run with Code coverage" option for the instrumentation tests which are written under androidTest folder. But whereas I can see it for JUnit test cases which are written in test folder.
Can anyone tell me how to get the coverage for that?
Thanks in advance.
So by your comment i assumed that you setup successfully jacoco in your gradle. Now please change or add below material in your app gradle:
coveralls {
jacocoReportPath = "${buildDir}/reports/coverage/debug/report.xml"
}
The above code make your coverage report in app/build/report/.. And you will get two folder including other. AndroidTest and tests folders. In AndroidTest folder you get coverage folder and browse index.html you will get coverage report of instrumentation test result. Same as tests folder explore it and browse index.html.
Now the main part. You have to write below code in your app gradle
task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest','connectedAndroidTest']) {
reports {
xml.enabled = true
html.enabled = true
}
// The lines below make sure we can report against Kotlin and exclude some Android Stuff
def fileFilter = [
'**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*',
'**/*Test*.*', 'android/**/*.*'
]
def debugTree = fileTree(dir: "$project.buildDir/intermediates/javac/debug/compileDebugJavaWithJavac/classes/", excludes: fileFilter)
def mainSrc = "$project.projectDir/src/main/java"
sourceDirectories = files([mainSrc])
classDirectories = files([debugTree])
executionData = fileTree(dir: project.buildDir, includes: [
'jacoco/testDebugUnitTest.exec', 'outputs/code-coverage/connected/*coverage.ec'
])
}
After successfully sync you will see a play button beside task jacocoTestRepor... Hit it and select first one like Run yourProject [jacoco]

How to run jacocoTestReport when including Android instrumental tests?

My Android app contains both Unit and Instrumental tests, and I'd like to generate a coverage report using the JaCoCo plugin (and then finally porting to coveralls.io ) . But I cannot seem to run gradlew jacocoTestReport on my local machine, because I don't have a emulator or device that connects to Android Studio. The error and complete log is up at https://pastebin.com/c7vcwseF .
Here's the portion of my app's build.gradle that contains the task definition:
# ...
task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest', 'createDebugCoverageReport']) {
reports {
xml.enabled = true
html.enabled = true
}
def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*']
def debugTree = fileTree(dir: "$project.buildDir/intermediates/classes/debug", excludes: fileFilter)
def mainSrc = "$project.projectDir/src/main/java"
sourceDirectories = files([mainSrc])
classDirectories = files([debugTree])
executionData = fileTree(dir: project.buildDir, includes: [
'jacoco/testDebugUnitTest.exec', 'outputs/code-coverage/connected/*coverage.ec'
])
}
coveralls {
jacocoReportPath 'build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml'
}
# ...
I am using the org.jacoco:org.jacoco.core:0.8.2 plugin in my Android project.
So how would one run gradlew jacocoTestReport and generate the report locally?

Jacoco code coverage on project with multi flavors and modules

As the title said, this is the problem.
I have an android flavored app with modules. The app compiled using gradle in android-studio and in TeamCity server. We have custom task for creating the code coverage that look like that:
task codeCoverageReport(type: JacocoReport) {
// Gather execution data from all subprojects
executionData fileTree(project.rootDir.absolutePath).include("**/build/jacoco/*.exec")
//Build class & source dirs
def classExcludes =['**/R.class',
'**/R$*.class',
'**/BuildConfig.class',
'**/*$InjectAdapter*.class',
'**/*$ModuleAdapter*.class',
'**/*$ViewInjector*.class']
sourceDirectories = files();
classDirectories = files();
subprojects.each{
sourceDirectories += files(it.projectDir.absolutePath + '/src/main/java')
def path1 = it.buildDir.absolutePath + '/intermediates/classes/debug'
def path2 = it.buildDir.absolutePath + '/classes/main'
classDirectories += fileTree(dir: path1, excludes: classExcludes, includes: ['**/*.class'])
classDirectories += fileTree(dir: path2, excludes: classExcludes, includes: ['**/*.class'])
}
reports {
xml.enabled true
html.enabled true
html.destination "${buildDir}/reports/jacoco"
csv.enabled false
}
doFirst {
fileTree(dir: project.rootDir.absolutePath,includes: ['**/classes/**/*.class']).each {File file ->
if (file.name.contains('$$')) {
file.renameTo(file.path.replace('$$', '$'))
}
}
}
}
Now, the problem is that the code coverage created only for the modules and not for the app (main module). After some time I understood that this is because of the flavoring - the modules are not flavored, so they have only one Jacoco exec file, but the app (main module) is flavored and so Jacoco create many exec files and don't know which one to take.
After searching for a solution in google I found a way to work this through - but this created report for each module separately, which didn't help much. I also tried to include specific the exec of one of the flavors in the execution data, which worked, but I don't know how to create something that will work for all the flavors. I found things like this solution, but for some reason that didn't work with Jacoco.
What is the correct way to create code coverage for app with modules and flavors?
Thanks,
Omer

Jacoco code coverage in Android Studio with flavors

I've been trying to run Jacoco test coverage for quiet some time now. I've tried several possible solutions reported in these topics:
Android test code coverage with JaCoCo Gradle plugin
How do I get a jacoco coverage report using Android gradle plugin 0.10.0 or higher?
Im running the tests in a emulatated device using genymotion.
Here is what i added to build.gradle:
apply plugin: 'jacoco'
android{
jacoco {
version "0.7.1.201405082137"
}
buildTypes{
debug{
testCoverageEnabled = true
}
}
}
jacoco {
toolVersion "0.7.1.201405082137"
}
To run it i use something like
./gradlew clean
./gradlew createFLAVOR_NAMEDebugCoverageReport
The relevant generated files/folder are:
/build/intermediates/coverage-instrumented-classes
/build/intermediates/jacoco
/build/outputs/code-coverage/connected/flavors/MyFlavor/coverage.ec
However, there is nothing # build/reports/jacoco/test/html/index.html or any html page/code coverage report # /build/outputs.
I've also tried to create a dedicated task to build a coverage report:
def coverageSourceDirs = [
'src/main/java',
]
task jacocoTestReport(type: JacocoReport, dependsOn: "connectedAndroidTestFLAVOR_NAMEDebug") {
group = "Reporting"
description = "Generate Jacoco coverage reports after running tests."
reports {
xml.enabled = true
html.enabled = true
}
classDirectories = fileTree(
dir: './build/intermediates/classes/debug',
excludes: ['**/R*.class',
'**/*$InjectAdapter.class',
'**/*$ModuleAdapter.class',
'**/*$ViewInjector*.class'
])
sourceDirectories = files(coverageSourceDirs)
executionData = files("$buildDir/jacoco/connectedAndroidTestMyFlavorDebug.exec")
// Bit hacky but fixes https://code.google.com/p/android/issues/detail?id=69174.
// We iterate through the compiled .class tree and rename $$ to $.
doFirst {
new File("$buildDir/intermediates/classes/").eachFileRecurse { file ->
if (file.name.contains('$$')) {
file.renameTo(file.path.replace('$$', '$'))
}
}
}
}
Then ./gradlew clean and ./gradlew jacocoTestReport. The output is the same as above, so, no html page with coverage report or any other coverage file.
I'm currently using Android Studio v1.0.2 with the latest gradle version.
Im fairly new to gradle, so it is possible im missing something basic here.
Thanks
After spending the whole day chasing this issue i found out what's the problem. Contrary to the examples i've seen the file generated by the testDebug build is not the .exec file #$buildDir/jacoco/testDebug.exec.
With my gradle and studio version the file generated is a .ec #build/outputs/code-coverage/connected/flavors/myFlavor/coverage.ec
I didn't found any relevant information related to this. It may be a recent change, however, by creating a custom JacocoReport task and changing the executionData variable accordingly i've solved the problem.
Here is my implementation:
task jacocoTestReport(type: JacocoReport) {
def coverageSourceDirs = [
'src/main/java'
]
group = "Reporting"
description = "Generates Jacoco coverage reports"
reports {
xml{
enabled = true
destination "${buildDir}/reports/jacoco/jacoco.xml"
}
csv.enabled false
html{
enabled true
destination "${buildDir}/jacocoHtml"
}
}
classDirectories = fileTree(
dir: 'build/intermediates/classes',
excludes: ['**/R.class',
'**/R$*.class',
'**/BuildConfig.*',
'**/Manifest*.*',
'**/*Activity*.*',
'**/*Fragment*.*'
]
)
sourceDirectories = files(coverageSourceDirs)
additionalSourceDirs = files(coverageSourceDirs)
executionData = files('build/outputs/code-coverage/connected/flavors/smartcompanion/coverage.ec')
}

Categories

Resources