/coveralls-jacoco-gradle-plugin-couldbuild

Coveralls JaCoCo Gradle plugin with support for Cloudbuild

Primary LanguageKotlinMIT LicenseMIT

Coveralls Jacoco Gradle Plugin

License: MIT Deployment Status Coverage Status

A jacoco test coverage reporter gradle plugin for coveralls.io.

The plugin supports non-root packages in line with the recommended Kotlin directory structure which was missing in many other plugins for the Kotlin ecosystem.

The plugin automatically detects the root package, if it conforms to Kotlin guidelines and has a .kt file on the root level.

Usage

Gradle Plugin page

Apply the plugin with the ID: com.github.nbaztec.coveralls-jacoco.

// build.gradle.kts

buildscript {
    repositories {
        mavenCentral()
        jcenter()
    }
}

plugins {
    jacoco
    id("com.github.nbaztec.coveralls-jacoco") version "1.2.14"
}

This will add a gradle task coverallsJacoco that can be used to publish coverage report via

$ ./gradlew test jacocoTestReport coverallsJacoco

Set the value of COVERALLS_REPO_TOKEN from the project page on coveralls.io

Additionally, the following coveralls parameters may be specified via environment variables:

  • COVERALLS_PARALLEL (true/false)
  • COVERALLS_FLAG_NAME

Options

// build.gradle.kts

coverallsJacoco {
    reportPath = "" // default: "build/reports/jacoco/test/jacocoTestReport.xml"

    reportSourceSets += sourceSets.foo.allJava.srcDirs + sourceSets.bar.allJava.srcDirs // default: main
    apiEndpoint = "" // default: https://coveralls.io/api/v1/jobs 
    
    dryRun = false // default: false
    coverallsRequest = File("build/req.json") // default: null
}
  • reportPath: String - location of the jacoco xml report.
  • reportSourceSets: Iterable<File> - a list of directories where to find the source code in.
  • apiEndpoint: String - coveralls api endpoint for posting jobs.
  • dryRun: Boolean - executes the task without posting to coveralls. Useful for debugging.
  • coverallsRequest: File - writes the coveralls request payload to a file. Useful for debugging.

Excluding Files

Please refer to the official JaCoCo documentation to exclude files from the report. An example configuration is as follows:

jacocoTestReport {
    afterEvaluate {
        classDirectories = files(classDirectories.files.collect {
            fileTree(dir: it, exclude: "com/foo/**")
        })
    }
}

Multi-Project Support - Pure Kotlin/Java

To consolidate multiple JaCoCo coverage reports, the following code can be used to add a new task codeCoverageReport

tasks.register<JacocoReport>("codeCoverageReport") {
    val jacocoReportTask = this

    // If a subproject applies the 'jacoco' plugin, add the result it to the report
    subprojects {
        val subproject = this
        subproject.plugins.withType<JacocoPlugin>().configureEach {
            subproject.tasks.matching({ it.extensions.findByType<JacocoTaskExtension>() != null }).configureEach {
                val testTask = this
                sourceSets(subproject.sourceSets.main.get())
                executionData(testTask)
            }

            // To automatically run `test` every time `./gradlew codeCoverageReport` is called,
            // you may want to set up a task dependency between them as shown below.
            // Note that this requires the `test` tasks to be resolved eagerly (see `forEach`) which
            // may have a negative effect on the configuration time of your build.
            subproject.tasks.matching({ it.extensions.findByType<JacocoTaskExtension>() != null }).forEach {
                rootProject.tasks["codeCoverageReport"].dependsOn(it)
            }
        }
    }


    // enable the different report types (html, xml, csv)
    reports {
        // xml is usually used to integrate code coverage with
        // other tools like SonarQube, Coveralls or Codecov
        xml.isEnabled = true

        // HTML reports can be used to see code coverage
        // without any external tools
        html.isEnabled = true
    }

    coverallsJacoco.dependsOn(jacocoReportTask)
}

Multi-Project Support - Android

To consolidate multiple JaCoCo coverage reports on Android multi-project configurations, the following code can be used to add a new task jacocoFullReport

Groovy DSL build.gradle

// ignore any subproject, if required `subprojects.findAll{ it.name != 'customSubProject' }`
def coveredProjects = subprojects

// configure() method takes a list as an argument and applies the configuration to the projects in this list.
configure(coveredProjects) { p ->
    p.evaluate()

    // Here we apply jacoco plugin to every project
    apply plugin: 'jacoco'
    // Set Jacoco version
    jacoco {
        toolVersion = "0.8.5"
    }

    // Here we create the task to generate Jacoco report
    // It depends to unit test task we don't have to manually running unit test before the task
    task jacocoReport(type: JacocoReport, dependsOn: 'test') {

        // Define what type of report we should generate
        // If we don't want to process the data further, html should be enough
        reports {
            xml.enabled = true
            html.enabled = true
        }

        // Setup the .class, source, and execution directories
        final fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', 'android/**/*.*']

        sourceDirectories.setFrom files(["${p.projectDir}/src/main/java"])
        classDirectories.setFrom files([
            fileTree(dir: "${p.buildDir}/classes", excludes: fileFilter),
            fileTree(dir: "${p.buildDir}/intermediates/javac/debug", excludes: fileFilter),
            fileTree(dir: "${p.buildDir}/tmp/kotlin-classes/debug", excludes: fileFilter),
        ])
        executionData.setFrom fileTree(dir: p.buildDir, includes: [
                'jacoco/*.exec', 'outputs/code-coverage/connected/*coverage.ec'
        ])
    }
}

apply plugin: 'jacoco'
apply plugin: 'com.github.nbaztec.coveralls-jacoco'

task jacocoFullReport(type: JacocoReport, group: 'Coverage reports') {
    def projects = coveredProjects

    // Here we depend on the jacocoReport task that we created before
    dependsOn(projects.jacocoReport)

    final source = files(projects.jacocoReport.sourceDirectories)

    additionalSourceDirs.setFrom source
    sourceDirectories.setFrom source

    classDirectories.setFrom files(projects.jacocoReport.classDirectories)
    executionData.setFrom files(projects.jacocoReport.executionData)

    reports {
        html {
            enabled true
            destination file("$buildDir/reports/jacoco/html")
        }
        xml {
            enabled true
            destination file("$buildDir/reports/jacoco/jacocoFullReport.xml")
        }
    }

    doFirst {
        executionData.setFrom files(executionData.findAll { it.exists() })
    }

    coverallsJacoco {
        reportPath = "$buildDir/reports/jacoco/jacocoFullReport.xml"
        reportSourceSets =  projects.jacocoReport.sourceDirectories.collect{ it.getFiles() }.flatten()
    }

    tasks.coverallsJacoco.dependsOn(it)
}

Kotlin DSL build.gradle.kts

// ignore any subproject, if required `subprojects.findAll{ it.name != 'customSubProject' }`
val coveredProjects = subprojects

// configure() method takes a list as an argument and applies the configuration to the projects in this list.
configure(coveredProjects) {
    val p = (this as org.gradle.api.internal.project.DefaultProject)
    p.evaluate()

    // Here we apply jacoco plugin to every project
    apply(jacoco)

    // Set Jacoco version
    jacoco {
        toolVersion = "0.8.5"
    }

    // Here we create the task to generate Jacoco report
    // It depends to unit test task we don't have to manually running unit test before the task
    p.task("jacocoReport", JacocoReport::class) {

        // Define what type of report we should generate
        // If we don't want to process the data further, html should be enough
        reports {
            xml.isEnabled = true
            html.isEnabled = true
        }

        // Setup the .class, source, and execution directories
        val fileTreeConfig: (ConfigurableFileTree) -> Unit = {
            it.exclude("**/R.class", "**/R$*.class", "**/BuildConfig.*", "**/Manifest*.*", "android/**/*.*")
        }

        sourceDirectories.setFrom(files("${p.projectDir}/src/main/java"))
        classDirectories.setFrom(listOf(
            fileTree("${p.buildDir}/classes", fileTreeConfig),
            fileTree("${p.buildDir}/intermediates/javac/debug", fileTreeConfig),
            fileTree("${p.buildDir}/tmp/kotlin-classes/debug", fileTreeConfig)
        ))
        executionData.setFrom(fileTree(p.buildDir) {
            include("jacoco/*.exec", "outputs/code-coverage/connected/*coverage.ec")
        })
    }.dependsOn("test")
}

plugins {
    jacoco
    id("com.github.nbaztec.coveralls-jacoco") version "1.2.4"
}

tasks {
    register("jacocoFullReport", JacocoReport::class) {
        val jacocoReportTask = this

        group = "Coverage reports"
        val projects = coveredProjects

        // Here we depend on the jacocoReport task that we created before
        val subTasks = projects.map { it.task<JacocoReport>("jacocoReport") }
        dependsOn(subTasks)

        val subSourceDirs = subTasks.map { files(it.sourceDirectories) }
        additionalSourceDirs.setFrom(subSourceDirs)
        sourceDirectories.setFrom(subSourceDirs)

        classDirectories.setFrom(subTasks.map { files(it.classDirectories) })
        executionData.setFrom(subTasks.map { files(it.executionData) })

        reports {
            html.isEnabled = true
            html.destination = file("$buildDir/reports/jacoco/html")

            xml.isEnabled = true
            xml.destination = file("$buildDir/reports/jacoco/jacocoFullReport.xml")
        }

        doFirst {
            executionData.setFrom(files(executionData.filter { it.exists() }))
        }

        coverallsJacoco {
            dependsOn(jacocoReportTask)

            reportPath = "$buildDir/reports/jacoco/jacocoFullReport.xml"
            reportSourceSets = subSourceDirs.flatMap { it.files }
        }
    }
}

CI Usage

The plugin can be used with the following CI providers:

  • Travis-CI & Travis-Pro
  • CircleCI
  • Github Actions
  • Jenkins
  • Codeship
  • Buildkite
  • Gitlab CI
  • Bitrise

Travis

Set the COVERALLS_REPO_TOKEN via Environment Variables or Encryption Keys and set up a job as follows:

language: java
script: ./gradlew test jacocoTestReport coverallsJacoco

CircleCI

Set the COVERALLS_REPO_TOKEN via CircleCI's Environment Variables and set up a job as follows:

jobs:
  check:
    docker:
      - image: amazoncorretto:11
    steps:
      - run: ./gradlew test jacocoTestReport coverallsJacoco

Github Actions

Set the COVERALLS_REPO_TOKEN via Github's Environment Variables or Secrets and set up a job as follows:

jobs:
  tests:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
      with:
        ref: ${{ github.event.pull_request.head.sha }}
    - name: test and publish coverage
      env:
        COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
      run: ./gradlew test jacocoTestReport coverallsJacoco

For running on publicly forked PRs, the plugin uses a (undocumented) API and uses GITHUB_TOKEN to identify the repo instead, as follows:

jobs:
  tests:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
      with:
        ref: ${{ github.event.pull_request.head.sha }}
    - name: test and publish coverage
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: ./gradlew test jacocoTestReport coverallsJacoco

Buildkite

See Buildkite environment variables documentation

Gitlab CI

See Gitlab CI predefined variables documentation

Bitrise CI

See Bitrise CI predefined variables documentation

Other CI

For other CIs, the following default environment variables are supported:

CI_NAME
CI_BUILD_NUMBER
CI_BUILD_URL
CI_BRANCH
CI_PULL_REQUEST