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'
])
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.
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.
Ignore Code Coverage To do so, go to Project Settings > General Settings > Analysis Scope > Code Coverage and set the Coverage Exclusions property.
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
}
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With