Skip to content

Instantly share code, notes, and snippets.

@WarrenFaith
Last active March 11, 2019 11:33
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save WarrenFaith/bfd4de61977d653bb56b9939bcd6f397 to your computer and use it in GitHub Desktop.
Save WarrenFaith/bfd4de61977d653bb56b9939bcd6f397 to your computer and use it in GitHub Desktop.
Basic script setup to add tasks like "testDebugUnitTestCoverage" (when you have no flavors defined) or "testFlavorNameDebugUnitTestCoverage" (replace FlavorName with your flavor name). Limitation: Does not work with flavor dimensions!
// in your root gradle file:
buildscript {
repositories {
jcenter()
google()
mavenCentral()
}
dependencies {
classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.6.1"
}
}
// project gradle:
apply from: '../gradle/jacoco-devflavor-module.gradle'
apply from: '../gradle/jacoco.gradle'
sonarqube {
androidVariant 'devDebug'
properties {
property "sonar.jacoco.reportPaths", "${project.buildDir}/jacoco/testDevDebugUnitTest.exec"
property "sonar.java.binaries", "build/intermediates/classes/dev/debug"
property "sonar.java.test.binaries", "build/intermediates/classes/test/dev/debug"
}
}
project.tasks["sonarqube"].dependsOn "testDevDebugUnitTest"
project.afterEvaluate {
testDevDebugUnitTestCoverage.doLast {
def report = file("${jacoco.reportsDir}/testDevDebugUnitTestCoverage/testDevDebugUnitTestCoverage.xml")
logger.lifecycle("Checking coverage results: ${report}")
def parser = new XmlParser()
parser.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false)
parser.setFeature("http://apache.org/xml/features/disallow-doctype-decl", false)
def results = parser.parse(report)
def percentage = {
def covered = it.'@covered' as Double
def missed = it.'@missed' as Double
((covered / (covered + missed)) * 100).round(2)
}
def counters = results.counter
def metrics = [:]
metrics << [
'instruction': percentage(counters.find { it.'@type'.equals('INSTRUCTION') }),
'branch' : percentage(counters.find { it.'@type'.equals('BRANCH') }),
'line' : percentage(counters.find { it.'@type'.equals('LINE') }),
'complexity' : percentage(counters.find { it.'@type'.equals('COMPLEXITY') }),
'method' : percentage(counters.find { it.'@type'.equals('METHOD') }),
'class' : percentage(counters.find { it.'@type'.equals('CLASS') })
]
def failures = []
def canIncrease = []
metrics.each {
def limit = limits[it.key]
if (it.value < limit) {
failures.add("- ${it.key} coverage rate is: ${it.value}%, minimum is ${limit}%")
}
if (it.value > limit + 1) {
canIncrease.add("- ${it.key} coverage rate is: ${it.value}%, minimum is ${limit}%")
}
}
def log = printResult(logger, failures, "Code Coverage Failed")
log += printResult(logger, canIncrease, "Code Coverage Status!")
logger.log(LogLevel.INFO, log)
// use this here again if we really want to fail on our limits
// if (failures) {
// throw new GradleException(log)
// }
}
}
apply from: '../gradle/jacoco.gradle'
sonarqube {
androidVariant 'debug'
properties {
property "sonar.jacoco.reportPaths", "${project.buildDir}/jacoco/testDebugUnitTest.exec"
property "sonar.java.binaries", "build/intermediates/classes/debug"
property "sonar.java.test.binaries", "build/intermediates/classes/test/debug"
}
}
project.tasks["sonarqube"].dependsOn "testDebugUnitTest"
project.afterEvaluate {
testDebugUnitTestCoverage.doLast {
def report = file("${jacoco.reportsDir}/testDebugUnitTestCoverage/testDebugUnitTestCoverage.xml")
logger.lifecycle("Checking coverage results: ${report}")
def parser = new XmlParser()
parser.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
parser.setFeature("http://apache.org/xml/features/disallow-doctype-decl", false)
def results = parser.parse(report)
def percentage = {
def covered = it.'@covered' as Double
def missed = it.'@missed' as Double
((covered / (covered + missed)) * 100).round(2)
}
def counters = results.counter
def metrics = [:]
metrics << [
'instruction': percentage(counters.find { it.'@type'.equals('INSTRUCTION') }),
'branch' : percentage(counters.find { it.'@type'.equals('BRANCH') }),
'line' : percentage(counters.find { it.'@type'.equals('LINE') }),
'complexity' : percentage(counters.find { it.'@type'.equals('COMPLEXITY') }),
'method' : percentage(counters.find { it.'@type'.equals('METHOD') }),
'class' : percentage(counters.find { it.'@type'.equals('CLASS') })
]
def failures = []
def canIncrease = []
metrics.each {
def limit = limits[it.key]
if (it.value < limit) {
failures.add("- ${it.key} coverage rate is: ${it.value}%, minimum is ${limit}%")
}
if (it.value > limit + 1) {
canIncrease.add("- ${it.key} coverage rate is: ${it.value}%, minimum is ${limit}%")
}
}
def log = printResult(logger, failures, "Code Coverage Failed")
log += printResult(logger, canIncrease, "Code Coverage Status!")
logger.log(LogLevel.INFO, log)
// use this here again if we really want to fail on our limits
// if (failures) {
// throw new GradleException(log)
// }
}
}
apply plugin: 'jacoco'
apply plugin: "org.sonarqube"
jacoco {
toolVersion = "0.7.9"
}
ext {
limits = [
'instruction': 90,
'branch' : 90,
'line' : 90,
'complexity' : 90,
'method' : 90,
'class' : 90
]
}
sonarqube {
properties {
property "sonar.host.url", "https://sonarqube.example.net"
property "sonar.sources", "src/main/java"
property "sonar.tests", "src/test/java"
property "sonar.sourceEncoding", "UTF-8"
property "sonar.projectVersion", "${versionName}"
}
}
project.afterEvaluate {
// 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."
classDirectories = fileTree(
dir: "${project.buildDir}/intermediates/classes/${sourcePath}",
excludes: ['**/R.class', // android internal
'**/R$*.class', // android internal
'**/*$ViewInjector*.*',
'**/*$ViewBinder*.*',
'**/MemberInjectorRegistry.*', // toothpick
'**/FactoryRegistry.*', // toothpick
'**/BR.*', // android module "placeholder"
'**/BuildConfig.*', // android internal
'**/com/android/databinding/**', // android databinding
'**/android/databinding/layouts/**', // android databinding
'**/Manifest*.*', // android internal
]
)
def coverageSourceDirs = [
"src/main/java",
"src/$productFlavorName/java",
"src/$buildTypeName/java"
]
additionalSourceDirs = files(coverageSourceDirs)
sourceDirectories = files(coverageSourceDirs)
executionData = files("${project.buildDir}/jacoco/${testTaskName}.exec")
reports {
xml.enabled = true
html.enabled = true
}
}
}
}
}
ext.printResult = { logger, result, title ->
def log = ""
if (result) {
log = "------------------ " + title + " -----------------------\n"
result.each {
log += it + "\n"
}
logger.quiet(log)
}
return log
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment