diff --git a/README.md b/README.md index 68f29603..bffa7213 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![Join Slack group](https://img.shields.io/badge/Chat-Slack-blue?link=https://chat.bitrise.io/)](https://chat.bitrise.io/) [![trace-gradle-plugin](https://img.shields.io/maven-central/v/io.bitrise.trace.plugin/trace-gradle-plugin?label=trace-gradle-plugin)](https://search.maven.org/artifact/io.bitrise.trace.plugin/trace-gradle-plugin/) [![trace-sdk](https://img.shields.io/maven-central/v/io.bitrise.trace/trace-sdk?label=trace-sdk)](https://search.maven.org/artifact/io.bitrise.trace/trace-sdk/) +[![codecov](https://codecov.io/gh/bitrise-io/trace-android-sdk/branch/main/graph/badge.svg?token=9zDLykViPd)](https://codecov.io/gh/bitrise-io/trace-android-sdk) Catch bugs before they reach production — get detailed crash reports and monitor how your app is performing across the entire install base. When issues are detected we show you exactly what diff --git a/bitrise.yml b/bitrise.yml index a1e941c1..148708e2 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -293,13 +293,6 @@ workflows: - module: trace-sdk - arguments: "--stacktrace" title: android_build - - android-unit-test: - inputs: - - project_location: "$PROJECT_LOCATION" - - module: trace-sdk - - arguments: "--stacktrace" - - variant: "$VARIANT" - title: android_unit_test - cache-push@2: title: cache_push - script: @@ -1026,11 +1019,11 @@ workflows: if [ $RUN_TESTKIT == 'true' ] then - $BITRISE_SOURCE_DIR/gradlew ":trace-sdk:connectedAndroidTest" --stacktrace + $BITRISE_SOURCE_DIR/gradlew ":trace-sdk:testDebugUnitTestCoverage" --stacktrace else - $BITRISE_SOURCE_DIR/gradlew ":trace-sdk:connectedAndroidTest" "-Pandroid.testInstrumentationRunnerArguments.notAnnotation=io.bitrise.trace.TestKitTest" --stacktrace + $BITRISE_SOURCE_DIR/gradlew ":trace-sdk:testDebugUnitTestCoverage" "-Pandroid.testInstrumentationRunnerArguments.notAnnotation=io.bitrise.trace.TestKitTest" --stacktrace fi - title: trace-sdk connectedAndroidTest + title: trace-sdk connectedAndroidTest with coverage - script@1: inputs: - content: | @@ -1042,6 +1035,9 @@ workflows: $BITRISE_SOURCE_DIR/gradlew ":trace-test-application:connectedAndroidTest" --stacktrace title: trace-test-application connectedAndroidTest + - codecov@2: + inputs: + - CODECOV_TOKEN: "$CODECOV_TOKEN" - cache-push@2: title: cache_push - script@1: diff --git a/build.gradle b/build.gradle index acd2f3e7..e57ca0b4 100644 --- a/build.gradle +++ b/build.gradle @@ -18,7 +18,8 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.0.2' + classpath 'com.android.tools.build:gradle:4.1.3' + classpath 'org.jacoco:org.jacoco.core:0.8.1' classpath files('./buildSrc/build/classes/java/main') } } diff --git a/trace-sdk/build.gradle b/trace-sdk/build.gradle index 02a28b0a..ab5dd6a3 100644 --- a/trace-sdk/build.gradle +++ b/trace-sdk/build.gradle @@ -1,3 +1,4 @@ +apply plugin: 'jacoco' apply plugin: 'com.android.library' android { @@ -8,6 +9,7 @@ android { targetSdkVersion 30 versionCode = "$project.versionCode" as int versionName "$project.version" + buildConfigField('String', 'VERSION_NAME', "\"${project.version}\"") testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" javaCompileOptions { annotationProcessorOptions { @@ -16,6 +18,10 @@ android { } } buildTypes { + debug { + minifyEnabled false + testCoverageEnabled true + } release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' @@ -27,6 +33,92 @@ android { } } +jacoco { + toolVersion = "0.8.5" +} + +tasks.withType(Test) { + jacoco.includeNoLocationClasses = true + jacoco.excludes = ['jdk.internal.*'] + // see related issue https://github.com/gradle/gradle/issues/5184#issuecomment-457865951 +} + +project.afterEvaluate { + // A Set that will contain all the report task names for each variant + Set reportTaskNames = new HashSet<>() + android.'libraryVariants' + .all { variant -> + def variantName = variant.name + def unitTestTask = "test${variantName.capitalize()}UnitTest" + def androidTestCoverageTask = "create${variantName.capitalize()}CoverageReport" + + // Add the report task name for the given variant to the Set above + reportTaskNames.add("create${variantName.capitalize()}AndroidTestCoverageReport") + + 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 = [ + '**/R.class', + '**/R$*.class', + '**/BuildConfig.*', + '**/Manifest*.*', + '**/*Test*.*', + 'android/**/*.*', + ] + + def javaClasses = fileTree(dir: variant.javaCompileProvider.get().destinationDir, + excludes: excludes) + + classDirectories.setFrom(files([ + javaClasses + ])) + + 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 + ])) + } + } + // Get the task of running the connected tests + Task connectedAndroidTestTask = tasks.findByName("connectedAndroidTest") + // Create a set that will contain the required dependsOn elements + Set deps = new HashSet<>(); + connectedAndroidTestTask.dependsOn.each { + // Filter out Jacoco report tasks. this is an issue, connected tests should not depend on them + String providerName = ((TaskProvider) it).getName() + if (!reportTaskNames.grep{it == providerName}) { + deps.add(it) + } + } + // Replace the original dependsOn with the filtered + connectedAndroidTestTask.setDependsOn(deps) + + // Finally make sure that each report task depends on the connected tests. + reportTaskNames.forEach({ + // Will be null, if testCoverageEnabled is false for the given variant + def reportTask = tasks.findByName(it) + if (reportTask != null) { + reportTask.dependsOn(connectedAndroidTestTask) + } + }) +} + dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "androidx.concurrent:concurrent-futures:${versions.concurrentFutures}" @@ -40,16 +132,16 @@ dependencies { implementation "com.squareup.retrofit2:retrofit:${versions.retrofit}" implementation "com.squareup.okhttp3:logging-interceptor:${versions.okhttp}" implementation "com.squareup.retrofit2:converter-gson:${versions.converterGson}" - implementation("com.squareup.okhttp3:okhttp"){ + implementation("com.squareup.okhttp3:okhttp") { version { require versions.okhttp } because constraintReasons.okhttp } implementation "io.bitrise.trace.internal:opencensus:${versions.opencensus}" - implementation("com.google.guava:listenablefuture:${versions.listenableFuture}"){ + implementation("com.google.guava:listenablefuture:${versions.listenableFuture}") { because constraintReasons.guava } implementation "io.azam.ulidj:ulidj:${versions.ulidj}" - implementation("com.scottyab:rootbeer-lib:${versions.rootbeer}"){ + implementation("com.scottyab:rootbeer-lib:${versions.rootbeer}") { because constraintReasons.rootBeer } @@ -62,10 +154,10 @@ dependencies { androidTestImplementation("androidx.test:runner:${versions.androidxTest}") { exclude group: "org.hamcrest", module: "hamcrest-core" } - androidTestImplementation ("androidx.test:rules:${versions.androidxTest}") { + androidTestImplementation("androidx.test:rules:${versions.androidxTest}") { exclude group: "org.hamcrest", module: "hamcrest-core" } - androidTestImplementation ("androidx.test.ext:junit:${versions.extJunit}") { + androidTestImplementation("androidx.test.ext:junit:${versions.extJunit}") { exclude group: "org.hamcrest", module: "hamcrest-core" } androidTestImplementation "org.hamcrest:hamcrest:${versions.hamcrest}"