Skip to content

Instantly share code, notes, and snippets.

@carlosezam
Last active November 26, 2021 03:27
Show Gist options
  • Select an option

  • Save carlosezam/2b71dc8c29c16c4c1dac126299c6c081 to your computer and use it in GitHub Desktop.

Select an option

Save carlosezam/2b71dc8c29c16c4c1dac126299c6c081 to your computer and use it in GitHub Desktop.
Android code analysis

Jacoco

Gradle project level

// build.gradle.kts

buildscript {
    dependencies {
        classpath ("org.jacoco:org.jacoco.core:$jacoco_version")
    }
}

Gradle module level

// module/build.gradle
plugins {
    id("jacoco")
}

apply( from = "${project.rootDir}/jacoco.gradle" )

android {
    buildTypes {
        getByName("debug") {
            isTestCoverageEnabled = true
        }
    }
}

Gradle jacoco

// jacoco.gradle

jacoco{
    toolVersion = "0.8.7"
}

project.afterEvaluate { project ->
    setupAndroidReporting()
}

def setupAndroidReporting() {
    tasks.withType(Test) {
        // Whether or not classes without source location should be instrumented
        jacoco.includeNoLocationClasses = true
        jacoco.excludes = ['jdk.internal.*']
    }

    def fileFilter = [
            // data binding
            'android/databinding/**/*.class',
            '**/android/databinding/*Binding.class',
            '**/android/databinding/*',
            '**/androidx/databinding/*',
            '**/BR.*',
            // android
            '**/R.class',
            '**/R$*.class',
            '**/BuildConfig.*',
            '**/Manifest*.*',
            '**/*Test*.*',
            'android/**/*.*',
            // 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$*.*',
            // adapters generated by moshi
            '**/*JsonAdapter.*',
    ]

    // Grab all build types with coverage enabled
    def buildTypes = android.buildTypes
            .findAll { type -> type.testCoverageEnabled }
            .collect { type -> type.name }

    // Grab all product flavors
    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"
            System.out.println("Task -> $testTaskName")


            // 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."

                def javaTree = fileTree(dir: "${project.buildDir}/intermediates/javac/$sourceName/classes", excludes: fileFilter)
                def kotlinTree = fileTree(dir: "${project.buildDir}/tmp/kotlin-classes/$sourceName", excludes: fileFilter)
                classDirectories.from = files([javaTree], [kotlinTree])

                // find . -name *.exec
                //executionData.from = files("${project.buildDir}/outputs/unit_test_code_coverage/${testTaskName}.exec")
                //executionData.from = files("${project.buildDir}/jacoco/${testTaskName}.exec")
                executionData.from = fileTree( dir: project.buildDir, includes: ["**/*.exec", "**/*.ec"])
                def coverageSourceDirs = ["src/main/java",
                                          "src/$productFlavorName/java",
                                          "src/$buildTypeName/java"]

                sourceDirectories.setFrom(files(coverageSourceDirs))
                //additionalSourceDirs.setFrom(files(coverageSourceDirs))

                reports {
                    csv.enabled false // change if needed
                    xml {
                        enabled true
                        destination file("${buildDir}/coverage-report/${testTaskName}Coverage.xml")
                    }
                    html {
                        enabled true
                        destination file("${buildDir}/coverage-report")
                    }
                }
            }
        }
    }
}

android {
    buildTypes {
        debug {
            testCoverageEnabled true
        }
    }
}

Sonarqube

Install docker image

docker run --stop-timeout 3600 sonarqube
docker start sonarqube

Gradle project level

// build.gradle.kts
plugins {
    id("org.sonarqube").version("3.3")
}

apply( from= "sonarqube.gradle" )

Gradle module level

plugins {
    id("org.sonarqube")
}

//sonarqube{
//    properties {
//        // setup jacoco coverage reports
//        property( "sonar.coverage.jacoco.xmlReportPaths", "${project.buildDir}/coverage-report/testDebugUnitTestCoverage.xml" )
//    }
//}

Sonarque

// sonarqube.gradle
sonarqube{
    properties {
        property("sonar.projectName", "Project title")
        property("sonar.projectKey", "com.example.project")
        property("sonar.host.url","http://localhost:9000")
        property("sonar.login","****")
        property("sonar.password","*******")
    }
}

subprojects {
    afterEvaluate{

        def hasSonarqube = pluginManager.hasPlugin("org.sonarqube")
        def hasJacoco = pluginManager.hasPlugin("jacoco")

        if( hasSonarqube && hasJacoco ) {
            def jacocoReport = "${project.name}/build/coverage-report/testDebugUnitTestCoverage.xml"

            println("Sonarqube coverage -> $jacocoReport")

            //tasks.withType(JacocoReport){
            //    println("xmlReporthPaths " + it.reports.xml.destination )
            //}

            sonarqube{
                properties {
                    // setup jacoco coverage reports
                    property( "sonar.coverage.jacoco.xmlReportPaths", jacocoReport)
                }
            }
        }
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment