'File filter in Jacoco android test coverage report not working as expected

I am trying to add Jacoco to my project for test coverage reporting.

Referred the following blogs for that,

Most of them have the same code for file filtering, but it is not working for me.

jacoco.gradle

apply plugin: 'jacoco'

jacoco {
    toolVersion = "0.8.7"
}

project.afterEvaluate { project ->
    setupAndroidReporting()
}

def setupAndroidReporting() {
    tasks.withType(Test) {
        // Whether or not classes without source location should be instrumented
        jacoco.includeNoLocationClasses = true
        jacoco.excludes = ['jdk.internal.*']
    }

    // 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."

                def fileFilter = [
                        // data binding
                        'android/databinding/**/*.class',
                        '**/android/databinding/*Binding.class',
                        '**/android/databinding/*',
                        '**/androidx/databinding/*',
                        '**/BR.*',

                        // android
                        '**/R.class',
                        '**/R$*.class',
                        '**/BuildConfig.*',
                        '**/Manifest*.*',
                        '**/*Test*.*',
                        'android/**/*.*',

                        // 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$*.*',

                        // adapters generated by moshi
                        '**/*JsonAdapter.*',

                        // Hilt
                        '**/*Module.kt',
                        '**/di/**',
                        'dagger.hilt.internal/*',
                        'hilt_aggregated_deps/*',
                ]

                def javaTree = fileTree(dir: "${project.buildDir}/intermediates/javac/$sourceName/classes", exclude: fileFilter)
                def kotlinTree = fileTree(dir: "${project.buildDir}/tmp/kotlin-classes/$sourceName", exclude: fileFilter)
                classDirectories.from = files([javaTree], [kotlinTree])
                executionData.from = files("${project.buildDir}/jacoco/${testTaskName}.exec")
                def coverageSourceDirs = [
                        "src/main/java",
                        "src/$productFlavorName/java",
                        "src/$buildTypeName/java",
                ]

                sourceDirectories.setFrom(files(coverageSourceDirs))
                additionalSourceDirs.setFrom(files(coverageSourceDirs))

                reports {
                    csv.enabled false // change if needed
                    xml.enabled false // change if needed
                    html {
                        enabled true
                        destination file("${buildDir}/coverage-report")
                    }
                }
            }
        }
    }
    System.out.println("Test coverage report: ${buildDir}/reports/coverage/androidTest/debug/index.html")
}

// Jacoco
configurations.all {
    resolutionStrategy {
        eachDependency { details ->
            if ('org.jacoco' == details.requested.group) {
                details.useVersion "0.8.7"
            }
        }
    }
}

The generated report still includes files from these hilt directories.

Screenshot

Note:
Also noticed it is not running/reporting any tests as the coverage is shown as 0%.

P.S: Please add a comment if any required info is missing in the question.



Solution 1:[1]

Please check additionalClassDirs content. If it is not empty, try clearing it with classDirectories.setFrom(files()). This should do the trick.

The coverage task does not have dependencies on test or connectedTest, so you have to run the tests manually before starting this task.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Yuriy Kulikov