Bug 1473313 - Part 1: Set up geckoview build config for androidTest coverage runs. r=nalexander
This patch adds JaCoCo as a dependency for the geckoview androidTest configurations, as well as
the `mach android archive-geckoview-coverage-artifacts` command, and the `--enable-java-coverage`
mozconfig flag.
MozReview-Commit-ID: 36jNAzK44g3
--- a/build.gradle
+++ b/build.gradle
@@ -47,16 +47,17 @@ buildscript {
// For in tree plugins.
maven {
url "file://${gradle.mozconfig.topsrcdir}/mobile/android/gradle/m2repo"
}
}
ext.kotlin_version = '1.2.41'
ext.support_library_version = '26.1.0'
+ ext.jacoco_version = '0.8.1'
if (gradle.mozconfig.substs.MOZ_ANDROID_GOOGLE_PLAY_SERVICES) {
ext.google_play_services_version = '15.0.1'
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.0'
classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.8.2'
--- a/build/moz.configure/java.configure
+++ b/build/moz.configure/java.configure
@@ -61,8 +61,15 @@ def javac_version(javac):
stderr=subprocess.STDOUT).rstrip()
version = Version(output.split(' ')[-1])
if version < '1.8':
die('javac 1.8 or higher is required (found %s). '
'Check the JAVA_HOME environment variable.' % version)
return version
except subprocess.CalledProcessError as e:
die('Failed to get javac version: %s', e.output)
+
+
+# Java Code Coverage
+# ========================================================
+option('--enable-java-coverage', env='MOZ_JAVA_CODE_COVERAGE', help='Enable Java code coverage')
+
+set_config('MOZ_JAVA_CODE_COVERAGE', depends('--enable-java-coverage')(lambda v: bool(v)))
--- a/mobile/android/config/mozconfigs/android-api-16-gradle-dependencies/nightly
+++ b/mobile/android/config/mozconfigs/android-api-16-gradle-dependencies/nightly
@@ -29,16 +29,19 @@ ac_add_options --disable-tests
# advertise a bad API level. This may confuse people. As an example, please look at bug 1384482.
# If you think you can't handle the whole set of changes, please reach out to the Release
# Engineering team.
ac_add_options --with-android-min-sdk=16
ac_add_options --target=arm-linux-androideabi
ac_add_options --with-branding=mobile/android/branding/nightly
+# Pull code coverage dependencies too.
+ac_add_options --enable-java-coverage
+
export MOZILLA_OFFICIAL=1
export MOZ_TELEMETRY_REPORTING=1
export MOZ_ANDROID_MMA=1
export MOZ_ANDROID_POCKET=1
. "$topsrcdir/mobile/android/config/mozconfigs/common.override"
# End ../android-api-16-frontend/nightly.
--- a/mobile/android/config/proguard/proguard.cfg
+++ b/mobile/android/config/proguard/proguard.cfg
@@ -160,16 +160,21 @@
#-dontnote **,!ch.boye.**,!org.mozilla.gecko.sync.**
-include "play-services-keeps.cfg"
# Don't print spurious warnings from the support library.
# See: http://stackoverflow.com/questions/22441366/note-android-support-v4-text-icucompatics-cant-find-dynamically-referenced-cl
-dontnote android.support.**
+# Don't warn when classes referenced by JaCoCo are missing when running the build from android-dependencies.
+-dontwarn java.lang.instrument.**
+-dontwarn java.lang.management.**
+-dontwarn javax.management.**
+
-include "adjust-keeps.cfg"
-include "leakcanary-keeps.cfg"
-include "appcompat-v7-keeps.cfg"
-include "proguard-android.cfg"
--- a/mobile/android/geckoview/build.gradle
+++ b/mobile/android/geckoview/build.gradle
@@ -367,8 +367,76 @@ task("generateSDKBindings", type: JavaEx
dependsOn project(':annotations').jar
}
apply from: "${topsrcdir}/mobile/android/gradle/jacoco_dependencies.gradle"
if (project.hasProperty('enable_code_coverage')) {
apply from: "${topsrcdir}/mobile/android/gradle/jacoco_for_junit.gradle"
}
+
+// Set up code coverage for tests on emulators.
+if (mozconfig.substs.MOZ_JAVA_CODE_COVERAGE) {
+ apply plugin: "jacoco"
+ jacoco {
+ toolVersion = "${project.jacoco_version}"
+ }
+
+ android {
+ jacoco {
+ version = "$jacoco_version"
+ }
+ buildTypes {
+ debug {
+ testCoverageEnabled true
+ }
+ }
+ }
+
+ configurations {
+ // This configuration is used for dependencies that are not needed at compilation or
+ // runtime, but need to be exported as artifacts of the build for usage on the testing
+ // machines.
+ coverageDependency
+ }
+
+ dependencies {
+ // This is required both in the instrumented application classes and the test classes,
+ // so `api` has to be used instead of `androidTestImplementation`.
+ api "org.jacoco:org.jacoco.agent:$jacoco_version:runtime"
+
+ coverageDependency ("org.jacoco:org.jacoco.cli:$jacoco_version:nodeps") {
+ exclude group: 'org.ow2.asm', module: '*'
+ }
+ }
+
+ // This task is used by `mach android archive-geckoview-coverage-artifacts`.
+ task copyCoverageDependencies(type: Copy) {
+ from(configurations.coverageDependency) {
+ include 'org.jacoco.cli-*-nodeps.jar'
+ rename { _ -> 'target.jacoco-cli.jar' }
+ }
+ into "$buildDir/coverage"
+ }
+
+ // Generate tasks to archive compiled classfiles for later use with JaCoCo report generation.
+ // One of these tasks is used by `mach android archive-geckoview-coverage-artifacts`.
+ android.libraryVariants.all { variant ->
+ def name = variant.name
+ def compileTask = tasks.getByName("compile${name.capitalize()}JavaWithJavac")
+ task "archiveClassfiles${name.capitalize()}"(type: Zip, dependsOn: compileTask) {
+ description = "Archive compiled classfiles for $name in order to export them as code coverage artifacts."
+ def fileFilter = ['**/androidTest/**',
+ '**/test/**',
+ '**/R.class',
+ '**/R$*.class',
+ '**/BuildConfig.*',
+ '**/Manifest*.*',
+ '**/*Test*.*',
+ 'android/**/*.*']
+ from fileTree(dir: compileTask.destinationDir, excludes: fileFilter)
+ destinationDir = file("${buildDir}/coverage")
+ // Note: This task assumes only one variant of archiveClassfiles* will be used.
+ // Running multiple variants of this task will overwrite the output archive.
+ archiveName = 'target.geckoview_classfiles.zip'
+ }
+ }
+}
--- a/mobile/android/gradle.configure
+++ b/mobile/android/gradle.configure
@@ -232,26 +232,39 @@ def gradle_android_archive_geckoview_tas
'geckoview_example:assemble{geckoview_example.variant.name}'.format(geckoview_example=build_config.geckoview_example),
'geckoview_example:assemble{geckoview_example.variant.name}AndroidTest'.format(geckoview_example=build_config.geckoview_example),
'geckoview:uploadArchives',
]
set_config('GRADLE_ANDROID_ARCHIVE_GECKOVIEW_TASKS', gradle_android_archive_geckoview_tasks)
+@depends(gradle_android_build_config)
+def gradle_android_archive_geckoview_coverage_artifacts_tasks(build_config):
+ '''Gradle tasks run by |mach android archive-geckoview-coverage-artifacts|.'''
+ return [
+ 'geckoview:archiveClassfiles{geckoview.variant.name}'.format(geckoview=build_config.geckoview),
+ 'geckoview:copyCoverageDependencies',
+ ]
+
+set_config('GRADLE_ANDROID_ARCHIVE_GECKOVIEW_COVERAGE_ARTIFACTS_TASKS',
+ gradle_android_archive_geckoview_coverage_artifacts_tasks)
+
+
@depends(
gradle_android_app_tasks,
gradle_android_test_tasks,
gradle_android_lint_tasks,
gradle_android_checkstyle_tasks,
gradle_android_findbugs_tasks,
gradle_android_archive_geckoview_tasks,
gradle_android_generate_sdk_bindings_tasks,
gradle_android_generate_generated_jni_wrappers_tasks,
gradle_android_generate_fennec_jni_wrappers_tasks,
+ gradle_android_archive_geckoview_coverage_artifacts_tasks,
)
@imports(_from='itertools', _import='imap')
@imports(_from='itertools', _import='chain')
@imports(_from='itertools', _import='ifilterfalse')
def gradle_android_dependencies_tasks(*tasks):
'''Gradle tasks run by |mach android dependencies|.'''
# The union, plus a bit more, of all of the Gradle tasks
# invoked by the android-* automation jobs.
--- a/mobile/android/gradle/jacoco_dependencies.gradle
+++ b/mobile/android/gradle/jacoco_dependencies.gradle
@@ -1,13 +1,11 @@
/* -*- Mode: Groovy; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* 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/. */
-project.ext.jacoco_version = "0.7.8"
-
dependencies {
- testImplementation "org.jacoco:org.jacoco.agent:${project.jacoco_version}"
- testImplementation "org.jacoco:org.jacoco.ant:${project.jacoco_version}"
- testImplementation "org.jacoco:org.jacoco.core:${project.jacoco_version}"
- testImplementation "org.jacoco:org.jacoco.report:${project.jacoco_version}"
+ testImplementation "org.jacoco:org.jacoco.agent:$jacoco_version"
+ testImplementation "org.jacoco:org.jacoco.ant:$jacoco_version"
+ testImplementation "org.jacoco:org.jacoco.core:$jacoco_version"
+ testImplementation "org.jacoco:org.jacoco.report:$jacoco_version"
}
--- a/mobile/android/mach_commands.py
+++ b/mobile/android/mach_commands.py
@@ -408,16 +408,25 @@ class MachCommands(MachCommandBase):
# We don't want to gate producing dependency archives on clean
# lint or checkstyle, particularly because toolchain versions
# can change the outputs for those processes.
self.gradle(self.substs['GRADLE_ANDROID_DEPENDENCIES_TASKS'] +
["--continue"] + args, verbose=True)
return 0
+ @SubCommand('android', 'archive-geckoview-coverage-artifacts',
+ """Archive compiled geckoview classfiles to be used later in generating code coverage reports.""") # NOQA: E501
+ @CommandArgument('args', nargs=argparse.REMAINDER)
+ def android_archive_geckoview_classfiles(self, args):
+ self.gradle(self.substs['GRADLE_ANDROID_ARCHIVE_GECKOVIEW_COVERAGE_ARTIFACTS_TASKS'] +
+ ["--continue"] + args, verbose=True)
+
+ return 0
+
@SubCommand('android', 'archive-geckoview',
"""Create GeckoView archives.
See http://firefox-source-docs.mozilla.org/build/buildsystem/toolchains.html#firefox-for-android-with-gradle""") # NOQA: E501
@CommandArgument('args', nargs=argparse.REMAINDER)
def android_archive_geckoview(self, args):
ret = self.gradle(
self.substs['GRADLE_ANDROID_ARCHIVE_GECKOVIEW_TASKS'] + ["--continue"] + args,
verbose=True)
--- a/python/mozbuild/mozbuild/test/configure/test_checks_configure.py
+++ b/python/mozbuild/mozbuild/test/configure/test_checks_configure.py
@@ -507,16 +507,17 @@ class TestChecksConfigure(unittest.TestC
self.assertEqual(status, 0)
self.assertEqual(config, {
'JAVA': java,
'JAVAH': javah,
'JAVAC': javac,
'JAR': jar,
'JARSIGNER': jarsigner,
'KEYTOOL': keytool,
+ 'MOZ_JAVA_CODE_COVERAGE': False,
})
self.assertEqual(out, textwrap.dedent('''\
checking for java... %s
checking for javah... %s
checking for jar... %s
checking for jarsigner... %s
checking for keytool... %s
checking for javac... %s
@@ -550,16 +551,17 @@ class TestChecksConfigure(unittest.TestC
self.assertEqual(status, 0)
self.assertEqual(config, {
'JAVA': alt_java,
'JAVAH': alt_javah,
'JAVAC': alt_javac,
'JAR': alt_jar,
'JARSIGNER': alt_jarsigner,
'KEYTOOL': alt_keytool,
+ 'MOZ_JAVA_CODE_COVERAGE': False,
})
self.assertEqual(out, textwrap.dedent('''\
checking for java... %s
checking for javah... %s
checking for jar... %s
checking for jarsigner... %s
checking for keytool... %s
checking for javac... %s
@@ -579,16 +581,17 @@ class TestChecksConfigure(unittest.TestC
self.assertEqual(status, 0)
self.assertEqual(config, {
'JAVA': alt_java,
'JAVAH': alt_javah,
'JAVAC': alt_javac,
'JAR': alt_jar,
'JARSIGNER': alt_jarsigner,
'KEYTOOL': alt_keytool,
+ 'MOZ_JAVA_CODE_COVERAGE': False,
})
self.assertEqual(out, textwrap.dedent('''\
checking for java... %s
checking for javah... %s
checking for jar... %s
checking for jarsigner... %s
checking for keytool... %s
checking for javac... %s
@@ -609,28 +612,49 @@ class TestChecksConfigure(unittest.TestC
self.assertEqual(status, 0)
self.assertEqual(config, {
'JAVA': alt_java,
'JAVAH': alt_javah,
'JAVAC': alt_javac,
'JAR': alt_jar,
'JARSIGNER': alt_jarsigner,
'KEYTOOL': alt_keytool,
+ 'MOZ_JAVA_CODE_COVERAGE': False,
})
self.assertEqual(out, textwrap.dedent('''\
checking for java... %s
checking for javah... %s
checking for jar... %s
checking for jarsigner... %s
checking for keytool... %s
checking for javac... %s
checking for javac version... 1.8
''' % (alt_java, alt_javah, alt_jar, alt_jarsigner,
alt_keytool, alt_javac)))
+ # --enable-java-coverage should set MOZ_JAVA_CODE_COVERAGE.
+ config, out, status = self.get_result(
+ args=['--enable-java-coverage'],
+ includes=includes,
+ extra_paths=paths,
+ environ={
+ 'PATH': mozpath.dirname(java),
+ 'JAVA_HOME': mozpath.dirname(mozpath.dirname(java)),
+ })
+ self.assertEqual(status, 0)
+ self.assertEqual(config, {
+ 'JAVA': java,
+ 'JAVAH': javah,
+ 'JAVAC': javac,
+ 'JAR': jar,
+ 'JARSIGNER': jarsigner,
+ 'KEYTOOL': keytool,
+ 'MOZ_JAVA_CODE_COVERAGE': True,
+ })
+
def mock_old_javac(_, args):
if len(args) == 1 and args[0] == '-version':
return 0, '1.6.9', ''
self.fail("Unexpected arguments to mock_old_javac: %s" % args)
# An old javac is fatal.
paths[javac] = mock_old_javac
config, out, status = self.get_result(includes=includes,