Bug 1473313 - Part 1: Set up geckoview build config for androidTest coverage runs. r=nalexander
authorTudor-Gabriel Vîjială <tvijiala@mozilla.com>
Tue, 24 Jul 2018 11:44:24 +0100
changeset 429993 92b8a54b4ad42d1d0203d265083b691e769fad1e
parent 429992 cca51f08f1b557a68a7f5a6dcc51b8a8a9b54efb
child 429994 0ae5ffe1725adeea25a54d1c62f5ae639cd79381
push id106034
push userbtara@mozilla.com
push dateFri, 03 Aug 2018 10:28:59 +0000
treeherdermozilla-inbound@d138efcfa006 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnalexander
bugs1473313
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
build.gradle
build/moz.configure/java.configure
mobile/android/config/mozconfigs/android-api-16-gradle-dependencies/nightly
mobile/android/config/proguard/proguard.cfg
mobile/android/geckoview/build.gradle
mobile/android/gradle.configure
mobile/android/gradle/jacoco_dependencies.gradle
mobile/android/mach_commands.py
python/mozbuild/mozbuild/test/configure/test_checks_configure.py
--- 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,