Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kotlin code coverage is not reported #37

Closed
mattinger opened this issue Oct 5, 2017 · 10 comments
Closed

Kotlin code coverage is not reported #37

mattinger opened this issue Oct 5, 2017 · 10 comments

Comments

@mattinger
Copy link

mattinger commented Oct 5, 2017

I've configured the jacoco plugin as directed in the instructions. However, the code coverage report does not include any classes written in Kotlin. The remainder of the report shows exactly what i'd expect it to, so in general, the integration works fine, with the exception of no coverage numbers for kotlin.


classpath 'com.dicedmelon.gradle:jacoco-android:0.1.2'

apply plugin: 'jacoco-android'


jacocoAndroidUnitTestReport {
  csv.enabled false
  html.enabled true
  xml.enabled true
  excludes += [ ... ]
}

jacocoTestReport {
  doLast {
    checkCoverageXml(file("$projectDir/build/reports/jacoco/jacocoTestDebugUnitTestReport/jacocoTestDebugUnitTestReport.xml"))
    checkCoverageXml(file("$projectDir/build/reports/jacoco/jacocoTestReleaseUnitTestReport/jacocoTestReleaseUnitTestReport.xml"))
  }
}

check.dependsOn jacocoTestReport
@SammyO
Copy link

SammyO commented Nov 3, 2017

Having the same issue. Any updates on this?

@saantiaguilera
Copy link

Having this exact issue. Is there an ETA or a workaround?

@mattinger
Copy link
Author

mattinger commented Nov 21, 2017

I ended up having to configure the tasks myself:

android.applicationVariants.all { variant ->
    def realVariantName = variant.name
    def variantName = variant.name.capitalize()

    task("jacoco${variantName}Report", type: JacocoReport, dependsOn: "test${variantName}UnitTest") {
        group "Reporting"
        description "Generate ${variantName} Jacoco coverage reports."

        reports {
            csv.enabled = false
            xml.enabled = true
            html.enabled = true
        }

        def debugTree = fileTree(dir: "${buildDir}/intermediates/classes/${realVariantName}", excludes: excludes)
        def kotlinDebugTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/${realVariantName}", excludes: excludes)


        sourceDirectories = files(android.sourceSets.main.java.srcDirs)
        executionData = files("${buildDir}/jacoco/test${variantName}UnitTest.exec")

        classDirectories = files([debugTree], [kotlinDebugTree])
    }

    jacocoTestReport.dependsOn += project.tasks.findByName("jacoco${variantName}Report")
}

The key here is including the kotlin-classes directory in addition to the classes directory. I hope the makers of this plugin will address this. I can try looking and seeing if i can create a PR for this.

Probably this line of code (74) in JacocoAndroidPlugin.groovy

reportTask.classDirectories =
    project.fileTree(dir: classesDir, excludes: project.jacocoAndroidUnitTestReport.excludes)

just needs another value in there.

@saantiaguilera
Copy link

saantiaguilera commented Nov 21, 2017

Ah, I've just got it to work. The main problem was that I needed to enable some noLocationClasses stuff. If someone gets here, here's how to make it work:

  1. Add includeNoLocationClasses true to all your unitTest options
android {
    ..
    testOptions.unitTests.all { 
        jacoco.includeNoLocationClasses = true 
    }
}

And thats it! I dont use this plugin because I already have a file that does pretty much this, but as @mattinger pointed out, you should include kotlin file tree

If the dev wants my file (to check that out) here it is :)

// File: jacoco.gradle
apply plugin: JacocoPlugin

final String JACOCO_FULL_REPORT_TASK = "jacocoFullReport"

tasks.withType(Test) {
    testLogging {
        events "FAILED"
        exceptionFormat "full"
    }
}

task(JACOCO_FULL_REPORT_TASK) {
    it.group 'reporting'
}

Task jacocoTestReportTask = findOrCreateJacocoTestReportTask()

getVariants().all { variant ->
    JacocoReport reportTask = createReportTask(variant)
    jacocoTestReportTask.dependsOn reportTask
}

if (project.tasks.findByName(JACOCO_FULL_REPORT_TASK)) {
    project.tasks."$JACOCO_FULL_REPORT_TASK".dependsOn jacocoTestReportTask
} else {
    project.tasks.whenTaskAdded {
        if (it.name.contentEquals(JACOCO_FULL_REPORT_TASK)) {
            it.dependsOn jacocoTestReportTask
        }
    }
}

def findOrCreateJacocoTestReportTask() {
    Task jacocoTestReportTask = project.tasks.findByName("jacocoTestReport")
    if (!jacocoTestReportTask) {
        jacocoTestReportTask = project.task("jacocoTestReport") {
            group = "reporting"
        }
    }
    return jacocoTestReportTask
}

def getVariants() {
    if (project.android.hasProperty('libraryVariants')) {
        return project.android.libraryVariants
    } else {
        return project.android.applicationVariants
    }
}

def createReportTask(def variant) {
    def sourceDirs = sourceDirs(variant)
    def classesDir = classesDir(variant)
    def testTask = testTask(variant)
    def executionData = executionDataFile(testTask)
    return project.task("jacoco${testTask.name.capitalize()}Report", type: JacocoReport) { JacocoReport reportTask ->
        reportTask.dependsOn testTask
        reportTask.group = "reporting"
        reportTask.description = "Generates Jacoco coverage reports for the ${variant.name} variant."
        reportTask.executionData = project.files(executionData)
        reportTask.sourceDirectories = project.files(sourceDirs)
        reportTask.classDirectories = classesDir
        reportTask.reports {
            csv.enabled false
            html.enabled true
            xml.enabled true
        }
    }
}

def sourceDirs(variant) {
    variant.sourceSets.java.srcDirs.collect { it.path }.flatten()
}

def classesDir(variant) {
    def filters = ['**/R.class',
            '**/R$*.class',
            '**/BuildConfig.class',
            '**/*$ViewInjector*.*',
            '**/*$ViewBinder*.*',
            '**/Manifest*.*',
            '**/*Dagger*.*',
            '**/*MembersInjector*.*',
            '**/*_Provide*Factory*.*',
            '**/*_Factory*.*' ]
    def javaTree = fileTree(dir: variant.javaCompile.destinationDir, excludes: filters)
    def kotlinTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/${variant}", excludes: filters)

    files([ javaTree ], [ kotlinTree ])
}

def testTask(variant) {
    project.tasks.withType(Test).find { task -> task.name =~ /test${variant.name.capitalize()}UnitTest/ }
}

def executionDataFile(Task testTask) {
    testTask.jacoco.destinationFile.path
}

UPDATE: Yet, seeing the jacoco bugtracker, it seems inlined methods are not being tracked (but affect the coverage), so be prepared to have a lower coverage than the one you expect

@mattinger
Copy link
Author

Another option is to use the default tasks, and reconfigure them:


project.afterEvaluate {
    android.applicationVariants.all { variant ->
        def realVariantName = variant.name
        def variantName = variant.name.capitalize()

        def task = project.tasks["jacocoTest${variantName}UnitTestReport"]
        def defaultExcludes = task.classDirectories.excludes

        def excludes = defaultExcludes + [...]

        def kotlinDebugTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/${realVariantName}", excludes: excludes)

        task.classDirectories.excludes = excludes
        task.classDirectories += kotlinDebugTree
    }
}

@cooperkong
Copy link

none of those works if project only have kotlin in test but java for the rest

@almozavr
Copy link

hey everyone, I've struggled a lot configuring jacoco with custom excludes and kotlin on gradle 4.6 and android plugin 3.x, so thanks everyone for their comments, though I'd like to share the only one config does work for me:

apply plugin: "jacoco"

jacoco {
    toolVersion = deps.test.jacocoVersion
}

tasks.withType(Test) {
    jacoco.includeNoLocationClasses = true
}

task('jacocoReports') {
    group = "Reporting"
    description = "Generate Jacoco coverage reports for all variants"
}

variants().all { variant ->
    def variantName = variant.name
    def variantCapName = variant.name.capitalize()
    def variantTask = task("jacoco${variantCapName}Report", type: JacocoReport, dependsOn: "test${variantCapName}UnitTest") {
        group = "Reporting"
        description = "Generate Jacoco coverage reports for $variantCapName"
        reports {
            xml.enabled = true
            html.enabled = true
            csv.enabled = false
        }
            def fileFilter = [ '**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*', 
            '**/di/**',
            '**/*Controller*.*',  '**/*Navigator*.*', '**/*Layout*.*',
            '**/*Store$State.*'
            ]
            def classTree = fileTree(
                    dir: variant.javaCompiler.destinationDir,
                    excludes: fileFilter
            ) + fileTree(
                    dir: "$buildDir/tmp/kotlin-classes/$variantName",
                    excludes: fileFilter
            )
            
            sourceDirectories = files([
                    "src/main/java", "src/main/kotlin",
                    "src/$variantName/java", "src/$variantName/kotlin"
            ])
            classDirectories = files([classTree])
            executionData = files("${buildDir}/jacoco/junitPlatformTest${variantCapName}.exec")
    }
    jacocoReports.dependsOn variantTask
}

check.dependsOn jacocoReports

def variants() {
    if (project.android.hasProperty('libraryVariants')) {
        return project.android.libraryVariants
    } else {
        return project.android.applicationVariants
    }
}

Also, it solves #23

@almozavr
Copy link

Once more, I've reviewed the whole coverage approach and found out there is no reason to exclude something from report but rather set custom violation rules for particular packages/classes. Here is the resulting config https://gist.github.com/almozavr/3cd46c4b4f0babbfc136db91924ab56e

Sorry for an offtopic, but again would like to share a working config for android+jacoco link

@code-schreiber
Copy link

This tutorial provided a simpler way to make coverage work for Kotlin and/or Java projects:
https://proandroiddev.com/unified-code-coverage-for-android-revisited-44789c9b722f

@arturdm
Copy link
Owner

arturdm commented Feb 26, 2019

Fixed in 0.1.4 release.

@arturdm arturdm closed this as completed Feb 26, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants