androidkotlinjacocogradle-kotlin-dslandroid-jacoco

Jacoco doesn't work in a Android Library module, coverage.ec file is always empty


I'm trying to use jacoco in a multi module android project. The project is similar to NowInAndroid. The Jacoco.kt file is the same:

private val coverageExclusions = listOf(
    // Android
    "**/R.class",
    "**/R\$*.class",
    "**/BuildConfig.*",
    "**/Manifest*.*",
    "**/*_Hilt*.class",
    "**/Hilt_*.class",
)

private fun String.capitalize() = replaceFirstChar {
    if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
}

internal fun Project.configureJacoco(
    androidComponentsExtension: AndroidComponentsExtension<*, *, *>
) {

    configure<JacocoPluginExtension> {
        toolVersion = "0.8.7"
    }

    androidComponentsExtension.onVariants { variant ->
        val myObjFactory = project.objects
        val buildDir = layout.buildDirectory.get().asFile
        val allJars: ListProperty<RegularFile> = myObjFactory.listProperty(RegularFile::class.java)
        val allDirectories: ListProperty<Directory> = myObjFactory.listProperty(Directory::class.java)
        val reportTask =
            tasks.register("create${variant.name.capitalize()}CombinedCoverageReport", JacocoReport::class) {
                classDirectories.setFrom(
                    allJars,
                    allDirectories.map { dirs ->
                        dirs.map { dir ->
                            myObjFactory.fileTree().setDir(dir).exclude(coverageExclusions)
                        }
                    }
                )
                reports {
                    xml.required.set(true)
                    html.required.set(true)
                }

                // TODO: This is missing files in src/debug/, src/prod, src/demo, src/demoDebug...
                sourceDirectories.setFrom(files("$projectDir/src/main"))
                println("This is the projectDir -> ${projectDir}")

                executionData.setFrom(
                    project.fileTree("$buildDir/outputs/unit_test_code_coverage/${variant.name}UnitTest")
                        .matching { include("**/*.exec") },

                    project.fileTree("$buildDir/outputs/code_coverage/${variant.name}AndroidTest")
                        .matching { include("**/*.ec") }
                )
            }


        variant.artifacts.forScope(ScopedArtifacts.Scope.PROJECT)
            .use(reportTask)
            .toGet(
                ScopedArtifact.CLASSES,
                { _ -> allJars },
                { _ -> allDirectories },
            )
    }

    tasks.withType<Test>().configureEach {
        configure<JacocoTaskExtension> {
            // Required for JaCoCo + Robolectric
            // https://github.com/robolectric/robolectric/issues/2230
            isIncludeNoLocationClasses = true

            // Required for JDK 11 with the above
            // https://github.com/gradle/gradle/issues/5184#issuecomment-391982009
            excludes = listOf("jdk.internal.*")
        }
    }

}

I've also created an AndroidApplicationJacocoConventionPlugin and an AndroidLibraryJacocoConventionPlugin. Just like NowInAndroid.

When I run the androidTest in the app module it works fine, the coverage.ec file is created, but when I try to run it in a library module it creates the coverage.ec but the file is empty, can someone help me with it?

I just don't understand why it works for the application module, but not for the other modules.


Solution

  • I found out that the problem was that the apk generated by the androidTest wasn't debuggable in my project. Adding:

    (this as com.android.build.gradle.internal.dsl.BuildType).isDebuggable = true
    

    For the library module solved the problem.