diff --git a/app/build.gradle b/app/build.gradle index e7bd0449d..bfdb8b6e0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,6 +5,7 @@ plugins { apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' +apply plugin: 'jacoco' apply from: "$project.rootDir/automation/gradle/versionCode.gradle" apply plugin: 'androidx.navigation.safeargs.kotlin' apply plugin: 'com.google.android.gms.oss-licenses-plugin' @@ -445,6 +446,42 @@ if (project.hasProperty("raptor")) { android.defaultConfig.manifestPlaceholders.isRaptorEnabled = "true" } +if (project.hasProperty("coverage")) { + tasks.withType(Test) { + jacoco.includeNoLocationClasses = true + } + + android.applicationVariants.all { variant -> + task "jacoco${variant.name.capitalize()}TestReport"(type: JacocoReport, dependsOn: "test${variant.name.capitalize()}UnitTest") { + reports { + xml.enabled = true + html.enabled = true + } + + def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', + '**/*Test*.*', 'android/**/*.*', '**/*$[0-9].*'] + def kotlinDebugTree = fileTree(dir: "$project.buildDir/tmp/kotlin-classes/${variant.name}", excludes: fileFilter) + def javaDebugTree = fileTree(dir: "$project.buildDir/intermediates/classes/${variant.flavorName}/${variant.buildType.name}", + excludes: fileFilter) + def mainSrc = "$project.projectDir/src/main/java" + + sourceDirectories = files([mainSrc]) + classDirectories = files([kotlinDebugTree, javaDebugTree]) + executionData = fileTree(dir: project.buildDir, includes: [ + "jacoco/test${variant.name.capitalize()}UnitTest.exec", 'outputs/code-coverage/connected/*coverage.ec' + ]) + } + } + + android { + buildTypes { + debug { + testCoverageEnabled true + } + } + } +} + // ------------------------------------------------------------------------------------------------- // Task for printing all build variants to build variants in parallel in automation // ------------------------------------------------------------------------------------------------- diff --git a/automation/taskcluster/decision_task.py b/automation/taskcluster/decision_task.py index e2ae7e602..26c96c1a5 100644 --- a/automation/taskcluster/decision_task.py +++ b/automation/taskcluster/decision_task.py @@ -63,8 +63,8 @@ def pr(): for variant in get_variants_for_build_type('debug'): assemble_task_id = taskcluster.slugId() - build_tasks[assemble_task_id] = BUILDER.craft_assemble_task(variant) - build_tasks[taskcluster.slugId()] = BUILDER.craft_test_task(variant) + build_tasks[assemble_task_id] = BUILDER.craft_assemble_pr_task(variant) + build_tasks[taskcluster.slugId()] = BUILDER.craft_test_pr_task(variant, True) for craft_function in ( BUILDER.craft_detekt_task, diff --git a/automation/taskcluster/lib/tasks.py b/automation/taskcluster/lib/tasks.py index f13b798eb..318d84ba3 100644 --- a/automation/taskcluster/lib/tasks.py +++ b/automation/taskcluster/lib/tasks.py @@ -134,11 +134,13 @@ class TaskBuilder(object): }, ) - def craft_assemble_task(self, variant): + def craft_assemble_pr_task(self, variant): + assemble_gradle_command = 'assemble{}'.format(variant.for_gradle_command) + return self._craft_clean_gradle_task( name='assemble: {}'.format(variant.raw), description='Building and testing variant {}'.format(variant.raw), - gradle_task='assemble{}'.format(variant.for_gradle_command), + gradle_task=assemble_gradle_command, artifacts=_craft_artifacts_from_variant(variant), treeherder={ 'groupSymbol': variant.build_type, @@ -148,14 +150,29 @@ class TaskBuilder(object): }, 'symbol': 'A', 'tier': 1, - }, + } ) - def craft_test_task(self, variant): + def craft_test_pr_task(self, variant, run_coverage=False): + test_gradle_command = '-Pcoverage jacoco{}TestReport'.format(variant.for_gradle_command) \ + if (run_coverage and variant.abi == 'aarch64') \ + else 'test{}UnitTest'.format(variant.for_gradle_command) + post_gradle_command = ('automation/taskcluster/upload_coverage_report.sh' if run_coverage else '',) + + if variant.abi == 'aarch64': + command = ' && '.join( + cmd + for commands in ((test_gradle_command,), post_gradle_command) + for cmd in commands + if cmd + ) + else: + command = test_gradle_command + return self._craft_clean_gradle_task( name='test: {}'.format(variant.raw), description='Building and testing variant {}'.format(variant.raw), - gradle_task='test{}UnitTest'.format(variant.for_gradle_command), + gradle_task=command, treeherder={ 'groupSymbol': variant.build_type, 'jobKind': 'test', @@ -165,6 +182,9 @@ class TaskBuilder(object): 'symbol': 'T', 'tier': 1, }, + scopes=[ + 'secrets:get:project/mobile/fenix/pr' + ] ) def craft_ui_tests_task(self): @@ -304,7 +324,7 @@ class TaskBuilder(object): ) def _craft_clean_gradle_task( - self, name, description, gradle_task, artifacts=None, routes=None, treeherder=None + self, name, description, gradle_task, artifacts=None, routes=None, treeherder=None, scopes=None ): return self._craft_build_ish_task( name=name, @@ -313,6 +333,7 @@ class TaskBuilder(object): artifacts=artifacts, routes=routes, treeherder=treeherder, + scopes=scopes, ) def craft_compare_locales_task(self): @@ -334,8 +355,8 @@ class TaskBuilder(object): ) def _craft_build_ish_task( - self, name, description, command, dependencies=None, artifacts=None, scopes=None, - routes=None, treeherder=None, env_vars=None, + self, name, description, command, dependencies=None, artifacts=None, + routes=None, treeherder=None, env_vars=None, scopes=None ): dependencies = [] if dependencies is None else dependencies artifacts = {} if artifacts is None else artifacts diff --git a/automation/taskcluster/upload_coverage_report.sh b/automation/taskcluster/upload_coverage_report.sh new file mode 100755 index 000000000..137b1fa6b --- /dev/null +++ b/automation/taskcluster/upload_coverage_report.sh @@ -0,0 +1,19 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# If a command fails then do not proceed and fail this script too. +set -ex + +# Get token for uploading to codecov and append it to codecov.yml +python automation/taskcluster/helper/get-secret.py \ + -s project/mobile/fenix/pr \ + -k codecov \ + -f .cc_token \ + +# Set some environment variables that will help codecov detect the CI +export CI_BUILD_URL="https://tools.taskcluster.net/tasks/$TASK_ID" + +# Execute codecov script for uploading report +# bash <(curl -s https://codecov.io/bash) +bash <(curl -s https://codecov.io/bash) -t @.cc_token diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..733485b27 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,8 @@ +codecov: + ci: + - tools.taskcluster.net + +coverage: + precision: 2 + round: down + range: 60..80