0

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

Cœur
  • 37,241
  • 25
  • 195
  • 267
tugceaktepe
  • 195
  • 1
  • 2
  • 17

3 Answers3

1

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
            ]))
Shaikh Mohib
  • 278
  • 3
  • 11
0

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

  • Hi @flavio-franco, our gradle version is 6.2.2 – tugceaktepe Mar 22 '21 at 11:19
  • @tugcekolcu I've configured jacoco and sonar following these configs https://medium.com/wandera-engineering/android-kotlin-code-coverage-with-jacoco-sonar-and-gradle-plugin-6-x-3933ed503a6e I used gradle 6.3 without issues. – Flavio Franco Mar 23 '21 at 17:54
  • 1
    Hi @flavio-franco, I've solved the problem. Directories' paths were incorrect. I've just added solution. Thank you for interest. :) – tugceaktepe Apr 28 '21 at 11:18
0

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,

tugceaktepe
  • 195
  • 1
  • 2
  • 17