tugceaktepe
tugceaktepe

Reputation: 407

Android - Jacoco does not generate xml report properly

We've integrated JaCoCo plugin to our Android project for measuring the code coverage via SonarQube. However, it could not be measured. Because SonarQube expects coverage report as an XML format. We add a custom jacocoTestReport task to build.gradle(module). It generates an xml file but it does not contain any coverage information. Have you encountered such a problem? If you had, do you have any solution?

build.gradle(root)

classpath "org.jacoco:org.jacoco.core:0.8.5"

build.gradle(module)

apply plugin: 'jacoco'




...




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"

getSourceDirectories().setFrom(files([mainSrc]))
getClassDirectories().setFrom(files([debugTree]))
getExecutionData().setFrom(fileTree(dir: "$buildDir", includes: [
        "jacoco/testDebugUnitTest.exec",
        "outputs/code-coverage/connected/*coverage.ec"
]))

Output:

enter image description here

enter image description here

Upvotes: 0

Views: 2991

Answers (3)

Shaikh Mohib
Shaikh Mohib

Reputation: 288

Baseline: AGP 7.1.2 + , jacoco 0.88, hilt 2.42, many you have to update the jacoco.gradle file.

 apply plugin: 'jacoco'
    jacoco {
    toolVersion = '0.8.7'
    }

    task jacocoTestReport { self ->
    build.dependsOn self
    }

    //Use this method to generate the HTML and XML coverage report files for 
    Unit and UI test cases
    android.testVariants.all {
    def variant = it.testedVariant
    def variantName = variant.name.capitalize()
    def unitTestTask = "test${variantName}UnitTest"
    def androidTestCoverageTask = "create${variantName}CoverageReport"
    tasks.create(name: "jacoco${name}TestReport", type: JacocoReport)

            { self ->
                group = 'Reporting'
                description = "Generates Jacoco coverage reports on the 
    ${variant.name} variant"

                reports {
                    xml.enabled(true)
                    html.enabled(true)
                }

                def excludes = [
                        // data binding
                        'android/databinding/**/*.class',
                        '**/android/databinding/*Binding.class',
                        '**/android/databinding/*',
                        '**/androidx/databinding/*',
                        '**/BR.*',
                        // android
                        '**/R.class',
                        '**/R$*.class',
                        '**/BuildConfig.*',
                        '**/Manifest*.*',
                        '**/*Test*.*',
                        'android/**/*.*',
                        // butterKnife
                        '**/*$ViewInjector*.*',
                        '**/*$ViewBinder*.*',
                        // dagger
                        '**/*_MembersInjector.class',
                        '**/Dagger*Component.class',
                        '**/Dagger*Component$Builder.class',
                        '**/*Module_*Factory.class',
                        '**/di/module/*',
                        '**/*_Factory*.*',
                        '**/*Module*.*',
                        '**/*Dagger*.*',
                        '**/*Hilt*.*',
                        // kotlin
                        '**/*MapperImpl*.*',
                        '**/*$ViewInjector*.*',
                        '**/*$ViewBinder*.*',
                        '**/BuildConfig.*',
                        '**/*Component*.*',
                        '**/*BR*.*',
                        '**/Manifest*.*',
                        '**/*$Lambda$*.*',
                        '**/*Companion*.*',
                        '**/*Module*.*',
                        '**/*Dagger*.*',
                        '**/*Hilt*.*',
                        '**/*MembersInjector*.*',
                        '**/*_MembersInjector.class',
                        '**/*_Factory*.*',
                        '**/*_Provide*Factory*.*',
                        '**/*Extensions*.*',
                        // sealed and data classes
                        '**/*$Result.*',
                        '**/*$Result$*.*'
                ]

                def javaClasses = fileTree(dir: 
    variant.javaCompileProvider.get().destinationDir,
                        excludes: excludes)
                def kotlinClasses = fileTree(dir: "${buildDir}/tmp/kotlin- 
   classes/${variant.name}",
                        excludes: excludes)
                classDirectories.setFrom(files([
                        javaClasses,
                        kotlinClasses
                ]))
                def variantSourceSets = "${project.projectDir}/src/main/java"
                sourceDirectories.setFrom(project.files(variantSourceSets))
                def androidTestsData = fileTree(dir: 
    "${buildDir}/outputs/code_coverage/${variant.name}AndroidTest/connected/", 
    includes: ["**/*.ec"])
                executionData(files([                        
"$project.buildDir/outputs/unit_test_code_coverage/${variant.name}UnitTest/test 
   ${variantName}UnitTest.exec",
                        androidTestsData
                ]))

                dependsOn unitTestTask, androidTestCoverageTask
                jacocoTestReport.dependsOn self
            }
      }

Due to AGP being updated below path is changed in the build directory.

//Replace this code

executionData.from = fileTree(dir: buildDir, includes: [
                        "jacoco/test${variantName}UnitTest.exec",
                        "/outputs/code_coverage/${variant.name}AndroidTest/connected/*coverage.ec"
                ])

//To this code

 def androidTestsData = fileTree(dir: "${buildDir}/outputs/code_coverage/${variant.name}AndroidTest/connected/", includes: ["**/*.ec"])
            executionData(files([
                    "$project.buildDir/outputs/unit_test_code_coverage/${variant.name}UnitTest/test${variantName}UnitTest.exec",
                    androidTestsData
            ]))

Upvotes: 1

tugceaktepe
tugceaktepe

Reputation: 407

After some googling, I've finally solved this problem. Source and class directories' paths were incorrect. Correct paths should be like this:

            def javaClasses = fileTree(dir: variant.javaCompileProvider.get().destinationDir,
                    excludes: fileFilter)
            def kotlinClasses = fileTree(dir: "${buildDir}/tmp/kotlin-classes/${variantName}",
                    excludes: fileFilter)

            classDirectories.setFrom(files([
                    javaClasses,
                    kotlinClasses
            ]))

            def variantSourceSets = variant.sourceSets.java.srcDirs.collect { it.path }.flatten()
            sourceDirectories.setFrom(project.files(variantSourceSets))

Regards,

Upvotes: 0

Flavio Franco
Flavio Franco

Reputation: 61

What is your gradle version? It seems there are some issues with gradle 6.4+ https://github.com/gradle/gradle/issues/14132

Upvotes: 0

Related Questions