Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SonarQube Code Coverage Could not account for Kotlin files on an Android Project

Below is my sonarqube properties snippet:

sonarqube {
   properties{
      property "sonar.junit.reportPaths", "build/test-results/testDebugUnitTest/*.xml"
      property("sonar.coverage.jacoco.xmlReportPaths", "build/reports/jacocoTestReport.xml"
}
}

Jacoco configuration and properties are working fine, how did I confirm this? I created a java class and wrote a unit test for it, sonarqube recognises this and recorded it as part of the Code Coverage, while it basically ignore all the Kotlin file test. I went ahead to change a kotlin file to java and also write a UnitTest for it and yes, it got recorgnised and add as part of the Code Coverage, again, the kotlin file tests were ignored.

Below is my Jacoco.gradle by the way:

apply plugin: 'jacoco'

ext {
    coverageExclusions = [
            '**/*Activity*.*',
            '**/*Fragment*.*',
            '**/R.class',
            '**/R$*.class',
            '**/BuildConfig.*',
    ]
}

jacoco {
    toolVersion = '0.8.6'
    reportsDir = file("$buildDir/reports")
}

tasks.withType(Test) {
    jacoco.includeNoLocationClasses = true
    jacoco.excludes = ['jdk.internal.*']
}


tasks.withType(Test) {
    finalizedBy jacocoTestReport // report is always generated after tests run
}

task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest']) {
    group = "Reporting"
    description = "Generate Jacoco coverage reports for Debug build"

    reports {
        xml.enabled(true)
        html.enabled(true)
        xml.destination(file("build/reports/jacocoTestReport.xml"))
    }

    def debugTree = fileTree(dir: "${buildDir}/intermediates/javac/debug/classes", excludes: coverageExclusions)
    def mainSrc = "/src/main/java"

    additionalSourceDirs.from = files(mainSrc)
    sourceDirectories.from = files([mainSrc])
    classDirectories.from = files([debugTree])

    executionData.from = fileTree(dir: project.buildDir, includes: [
            'jacoco/testDebugUnitTest.exec', 'outputs/code-coverage/connected/*coverage.ec'
    ])
like image 735
Wale Avatar asked Oct 26 '22 11:10

Wale


People also ask

Does SonarQube support Kotlin?

SonarQube supports 25+ languages as well and generates reports of code smells ,vulnerabilities and bugs. SonarQube 7.9 added support for Kotlin language to analyze code quality of code developed in Kotlin. SonarQube integration in Android : Step 1 : Download the latest version of SonarQube and unzip it.

Does SonarQube do code coverage?

SonarQube itself does not calculate coverage. To include coverage results in your analysis, you must set up a third-party coverage tool and configure SonarQube to import the results produced by that tool. Below, you'll find guidelines and resources, as well as language- and tool-specific analysis parameters.

How do I bypass code coverage in Sonar?

Ignore Code Coverage To do so, go to Project Settings > General Settings > Analysis Scope > Code Coverage and set the Coverage Exclusions property.


2 Answers

After so much research and debugging, and also with info's from @LarryX answer above, I was able to play around the classes as he said, below is my working Jacoco.gradle file.

apply plugin: 'jacoco'

jacoco {
    toolVersion = "0.8.4"
}

tasks.withType(Test) {
    jacoco.includeNoLocationClasses = true
    jacoco.excludes = ['jdk.internal.*']
}

project.afterEvaluate {
    (android.hasProperty('applicationVariants')
            ? android.'applicationVariants'
            : android.'libraryVariants')
            .all { variant ->
                def variantName = variant.name
                def unitTestTask = "test${variantName.capitalize()}UnitTest"
                def androidTestCoverageTask = "create${variantName.capitalize()}CoverageReport"

                tasks.create(name: "${unitTestTask}Coverage", type: JacocoReport, dependsOn: [
                        "$unitTestTask",
                        "$androidTestCoverageTask"
                ]) {
                    group = "Reporting"
                    description = "Generate Jacoco coverage reports for the ${variantName.capitalize()} build"

                    reports {
                        html.enabled(true)
                        xml.enabled(true)
                        csv.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/${variantName}",
                            excludes: excludes)

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

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

                    def androidTestsData = fileTree(dir: "${buildDir}/outputs/code_coverage/${variantName}AndroidTest/connected/", includes: ["**/*.ec"])

                    executionData(files([
                            "$project.buildDir/jacoco/${unitTestTask}.exec",
                            androidTestsData
                    ]))
                }

            }
}

Take note of :

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

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

Also, take note of project.afterEvaluate I'm not sure this is relevant but cause I think you must have run and generated your test before this section runs anyways. This solution also works for a library if not an actual android application.

And then my sonarqube.gradle file below.

apply plugin: "org.sonarqube"

    sonarqube {
        properties {
            property "sonar.host.url", "http://localhost:9000/"
            property "sonar.projectKey", "fair"
            property "sonar.projectName", "fair"
            property "sonar.login", "de9d79fe4d3aef9879567afc91a2ce465038d9be"
            property "sonar.projectVersion", "${android.defaultConfig.versionName}"
    
            property "sonar.junit.reportsPath", "build/test-results/testDebugUnitTest"
            property "sonar.java.coveragePlugin", "jacoco"
            property "sonar.sourceEncoding", "UTF-8"
            property "sonar.android.lint.report", "build/reports/lint-results.xml"
            property "sonar.jacoco.reportPaths", "build/jacoco/testDebugUnitTest.exec"
            property "sonar.jacoco.itReportPath", fileTree(dir: project.projectDir, includes: ["**/*.ec"])
            property "sonar.coverage.jacoco.xmlReportPaths", "build/reports/jacoco/testDebugUnitTestCoverage/testDebugUnitTestCoverage.xml"
        }
    }
    tasks.sonarqube.dependsOn ":app:testDebugUnitTestCoverage"

Finally my build.gradle(project)

buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.1.2"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.32"
        classpath "org.jacoco:org.jacoco.core:0.8.7"
        classpath("org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3")
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
like image 189
Wale Avatar answered Nov 08 '22 08:11

Wale


Please check jacoco config for java + kotlin project:

http://vgaidarji.me/blog/2017/12/20/how-to-configure-jacoco-for-kotlin-and-java-project/

Key is to include kotlin directories in sourceDirectories and classeDirectories

like image 30
LarryX Avatar answered Nov 08 '22 08:11

LarryX