Skip to content

Instantly share code, notes, and snippets.

@mohitgupt
Last active January 24, 2026 04:26
Show Gist options
  • Select an option

  • Save mohitgupt/d1694f259816271e9a9cc18b1f197439 to your computer and use it in GitHub Desktop.

Select an option

Save mohitgupt/d1694f259816271e9a9cc18b1f197439 to your computer and use it in GitHub Desktop.
name: Android CI
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: gradle
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Generate JaCoCo report
run: ./gradlew clean jacocoTestReport
- name: Upload Report
uses: actions/upload-artifact@v4
with:
name: report.xml
path: ${{ github.workspace }}/**/build/reports/jacoco/**/jacocoTestReport.xml
- name: JaCoCo Report
id: jacoco
uses: Madrapps/jacoco-report@v1.7.2
with:
paths: |
${{ github.workspace }}/**/build/reports/jacoco/**/jacocoTestReport.xml,
${{ github.workspace }}/**/build/reports/jacoco/**/debugCoverage.xml
token: ${{ secrets.GITHUB_TOKEN }}
min-coverage-overall: 80
min-coverage-changed-files: 80
title: Code Coverage
- name: Get the Coverage info
run: |
echo "Total coverage ${{ steps.jacoco.outputs.coverage-overall }}"
echo "Changed Files coverage ${{ steps.jacoco.outputs.coverage-changed-files }}"
- name: Fail PR if overall coverage is less than 80%
if: ${{ steps.jacoco.outputs.coverage-overall < 80.0 }}
uses: actions/github-script@v6
with:
script: |
core.setFailed('Overall coverage is less than 80%!')
import com.google.firebase.perf.plugin.FirebasePerfExtension
...
android {
buildTypes {
debug {
...
configure<FirebasePerfExtension> {
setInstrumentationEnabled(false)
}
}
}
}
...
apply(from = "jacoco.gradle.kts")
import org.gradle.api.tasks.testing.Test
import org.gradle.testing.jacoco.plugins.JacocoPluginExtension
import org.gradle.testing.jacoco.plugins.JacocoTaskExtension
import org.gradle.testing.jacoco.tasks.JacocoReport
apply(plugin = "jacoco")
configure<JacocoPluginExtension> {
toolVersion = "0.8.12"
}
tasks.withType<Test> {
configure<JacocoTaskExtension> {
isIncludeNoLocationClasses = true
excludes = listOf("jdk.internal.*")
}
}
tasks.register<JacocoReport>("jacocoTestReport") {
dependsOn("testDebugUnitTest")
reports {
xml.required.set(true)
html.required.set(true)
}
val exclusions = listOf(
"**/R.class",
"**/R\$*.class",
"**/BuildConfig.*",
"**/Manifest*.*",
"**/*Test*.*",
"android/**/*.*",
"**/Lambda\$*.class",
"**/Lambda.class",
"**/*Lambda.class",
"**/*Lambda*.class",
"**/*_MembersInjector.class",
"**/Dagger*.*",
"**/*Dagger*.*",
"**/*_Factory.class",
"**/*_Provide*Factory.class",
"**/*_ViewBinding*.*",
"**/BR.class",
"**/DataBinderMapperImpl.class",
"**/DataBinderMapperImpl\$*.class",
"**/DataBindingInfo.class",
"**/*\$Creator.class",
"**/*\$DefaultImpls.class",
"**/*\$Companion.class"
)
sourceDirectories.setFrom(files("${project.projectDir}/src/main/java"))
// Using specific paths for Java and Kotlin classes to avoid "different class with same name" error
val javaClasses = fileTree("${layout.buildDirectory.get().asFile}/intermediates/javac/debug/classes") {
exclude(exclusions)
}
val kotlinClasses = fileTree("${layout.buildDirectory.get().asFile}/tmp/kotlin-classes/debug") {
exclude(exclusions)
}
classDirectories.setFrom(files(javaClasses, kotlinClasses))
executionData.setFrom(fileTree(layout.buildDirectory.get().asFile) {
include(listOf("jacoco/testDebugUnitTest.exec", "outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec"))
})
doLast {
val reportFile = file("${layout.buildDirectory.get().asFile}/reports/jacoco/jacocoTestReport/jacocoTestReport.xml")
if (reportFile.exists()) {
val factory = javax.xml.parsers.DocumentBuilderFactory.newInstance()
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false)
val parser = factory.newDocumentBuilder()
val doc = parser.parse(reportFile)
val root = doc.documentElement
val nodeList = root.childNodes
println("-------------------------------------------------------")
println("Code Coverage Summary (Project Level):")
for (i in 0 until nodeList.length) {
val node = nodeList.item(i)
if (node is org.w3c.dom.Element && node.tagName == "counter") {
val type = node.getAttribute("type")
val missed = node.getAttribute("missed").toDouble()
val covered = node.getAttribute("covered").toDouble()
val total = missed + covered
val percentage = if (total > 0) (covered / total) * 100 else 0.0
if (type == "INSTRUCTION") {
println("Instructions : ${String.format("%.2f", percentage)}% (${covered.toInt()}/${total.toInt()})")
} else if (type == "BRANCH") {
println("Branches : ${String.format("%.2f", percentage)}% (${covered.toInt()}/${total.toInt()})")
} else if (type == "LINE") {
println("Lines : ${String.format("%.2f", percentage)}% (${covered.toInt()}/${total.toInt()})")
}
}
}
println("-------------------------------------------------------")
} else {
println("Jacoco report XML not found at ${reportFile.absolutePath}")
}
}
}
// Task to upload coverage and test reports to BrowserStack.
tasks.register("uploadReportsToBrowserStack") {
dependsOn("jacocoTestReport")
doLast {
val bsUser = if (project.hasProperty("bsUser")) project.property("bsUser") else "YOUR_ID"
val bsKey = if (project.hasProperty("bsKey")) project.property("bsKey") else "YOUR_KEY"
val buildId = java.text.SimpleDateFormat("yyyy-MM-dd_HH-mm").format(java.util.Date())
// Upload JUnit XML Reports (to Automation Upload endpoint)
val junitReportDir = file("${layout.buildDirectory.get().asFile}/test-results/testDebugUnitTest")
if (junitReportDir.exists()) {
println("Uploading JUnit reports to BrowserStack...")
junitReportDir.listFiles { f -> f.extension == "xml" }?.forEach { xmlFile ->
exec {
commandLine(
"curl", "-u", "$bsUser:$bsKey", "-vvv",
"-X", "POST",
"-F", "data=@${xmlFile.absolutePath}",
"-F", "projectName=TambolaCaller",
"-F", "buildName=Unit Test Coverage",
"-F", "buildIdentifier=$buildId",
"-F", "tags=unit_test, android",
"-F", "frameworkVersion=junit, 4.13.2",
"https://upload-automation.browserstack.com/upload"
)
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment