Bug 1507207 - Improve support for e10s mode testing on Android; r=bc
authorGeoff Brown <gbrown@mozilla.com>
Tue, 27 Nov 2018 09:41:13 -0700
changeset 507624 6cdb4edf37316a17a23b121069e92af67313c24d
parent 507623 ecfb40d8aacca5e661aa76635a2fe76474a9e5ea
child 507625 1e9f3b7f0e6daaec8bf9329a43d4abdaa07682d6
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbc
bugs1507207
milestone65.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 1507207 - Improve support for e10s mode testing on Android; r=bc
build/automation.py.in
build/mobile/remoteautomation.py
layout/tools/reftest/mach_commands.py
layout/tools/reftest/reftestcommandline.py
layout/tools/reftest/remotereftest.py
taskcluster/taskgraph/transforms/tests.py
testing/mochitest/mach_commands.py
testing/mochitest/mochitest_options.py
testing/mochitest/runtests.py
testing/mozharness/scripts/android_emulator_unittest.py
testing/mozharness/scripts/android_hardware_unittest.py
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -499,17 +499,17 @@ class Automation(object):
   def checkForCrashes(self, minidumpDir, symbolsPath):
     return mozcrash.check_for_crashes(minidumpDir, symbolsPath, test_name=self.lastTestSeen)
 
   def runApp(self, testURL, env, app, profileDir, extraArgs, utilityPath = None,
              xrePath = None, certPath = None,
              debuggerInfo = None, symbolsPath = None,
              timeout = -1, maxTime = None, onLaunch = None,
              detectShutdownLeaks = False, screenshotOnFail=False, testPath=None, bisectChunk=None,
-             valgrindPath=None, valgrindArgs=None, valgrindSuppFiles=None, outputHandler=None):
+             valgrindPath=None, valgrindArgs=None, valgrindSuppFiles=None, outputHandler=None, e10s=True):
     """
     Run the app, log the duration it took to execute, return the status code.
     Kills the app if it runs for longer than |maxTime| seconds, or outputs nothing for |timeout| seconds.
     """
 
     if utilityPath == None:
       utilityPath = self.DIST_BIN
     if xrePath == None:
--- a/build/mobile/remoteautomation.py
+++ b/build/mobile/remoteautomation.py
@@ -33,17 +33,17 @@ class RemoteAutomation(Automation):
         self.remoteProfile = remoteProfile
         self.remoteLog = remoteLog
         self.processArgs = processArgs or {}
         self.lastTestSeen = "remoteautomation.py"
         Automation.__init__(self)
 
     def runApp(self, testURL, env, app, profileDir, extraArgs,
                utilityPath=None, xrePath=None, debuggerInfo=None, symbolsPath=None,
-               timeout=-1, maxTime=None, **kwargs):
+               timeout=-1, maxTime=None, e10s=True, **kwargs):
         """
         Run the app, log the duration it took to execute, return the status code.
         Kills the app if it runs for longer than |maxTime| seconds, or outputs nothing
         for |timeout| seconds.
         """
 
         if utilityPath is None:
             utilityPath = self.DIST_BIN
@@ -54,17 +54,17 @@ class RemoteAutomation(Automation):
         self.utilityPath = utilityPath
 
         cmd, args = self.buildCommandLine(app, debuggerInfo, profileDir, testURL, extraArgs)
         startTime = datetime.datetime.now()
 
         self.lastTestSeen = "remoteautomation.py"
         self.launchApp([cmd] + args,
                        env=self.environment(env=env, crashreporter=not debuggerInfo),
-                       **self.processArgs)
+                       e10s=e10s, **self.processArgs)
 
         self.log.info("remoteautomation.py | Application pid: %d", self.pid)
 
         status = self.waitForFinish(timeout, maxTime)
         self.log.info("remoteautomation.py | Application ran for: %s",
                       str(datetime.datetime.now() - startTime))
 
         crashed = self.checkForCrashes(symbolsPath)
@@ -253,17 +253,17 @@ class RemoteAutomation(Automation):
         cmd, args = Automation.buildCommandLine(
             self, app, debuggerInfo, profileDir, testURL, extraArgs)
         try:
             args.remove('-foreground')
         except Exception:
             pass
         return app, args
 
-    def launchApp(self, cmd, env=None, messageLogger=None, counts=None):
+    def launchApp(self, cmd, env=None, e10s=True, messageLogger=None, counts=None):
         self.messageLogger = messageLogger
         self.stdoutlen = 0
 
         if self.appName and self.device.process_exist(self.appName):
             print("remoteautomation.py %s is already running. Stopping..." % self.appName)
             self.device.stop_application(self.appName, root=True)
 
         self.counts = counts
@@ -285,18 +285,18 @@ class RemoteAutomation(Automation):
             url = args[-1:][0]
             if url.startswith('/'):
                 # this is probably a reftest profile directory, not a url
                 url = None
             else:
                 args = args[:-1]
             if 'geckoview' in self.appName:
                 activity = "TestRunnerActivity"
-                self.device.launch_activity(self.appName, activity, e10s=True, moz_env=env,
-                                            extra_args=args, url=url)
+                self.device.launch_activity(self.appName, activity_name=activity, e10s=e10s,
+                                            moz_env=env, extra_args=args, url=url)
             else:
                 self.device.launch_fennec(self.appName, moz_env=env, extra_args=args, url=url)
 
         # Setting timeout at 1 hour since on a remote device this takes much longer.
         # Temporarily increased to 110 minutes because no more chunks can be created.
         self.timeout = 6600
 
         # Used to buffer log messages until we meet a line break
--- a/layout/tools/reftest/mach_commands.py
+++ b/layout/tools/reftest/mach_commands.py
@@ -137,16 +137,20 @@ class ReftestRunner(MozbuildObject):
         args.printDeviceInfo = False
 
         from mozrunner.devices.android_device import grant_runtime_permissions, get_adb_path
         grant_runtime_permissions(self, args.app, device_serial=args.deviceSerial)
 
         if not args.adb_path:
             args.adb_path = get_adb_path(self)
 
+        if 'geckoview' not in args.app:
+            args.e10s = False
+            print("using e10s=False for non-geckoview app")
+
         # A symlink and some path manipulations are required so that test
         # manifests can be found both locally and remotely (via a url)
         # using the same relative path.
         if args.suite == "jstestbrowser":
             staged_js_dir = os.path.join(self.topobjdir, "dist", "test-stage", "jsreftest")
             tests = os.path.join(self.reftest_dir, 'jsreftest')
             if not os.path.isdir(tests):
                 os.symlink(staged_js_dir, tests)
--- a/layout/tools/reftest/reftestcommandline.py
+++ b/layout/tools/reftest/reftestcommandline.py
@@ -480,12 +480,9 @@ class RemoteArgumentsParser(ReftestArgum
 
         # httpd-path is specified by standard makefile targets and may be specified
         # on the command line to select a particular version of httpd.js. If not
         # specified, try to select the one from hostutils.zip, as required in
         # bug 882932.
         if not options.httpdPath:
             options.httpdPath = os.path.join(options.utilityPath, "components")
 
-        # Disable e10s by default on Android because we don't run Android
-        # e10s jobs anywhere yet.
-        options.e10s = False
         return options
--- a/layout/tools/reftest/remotereftest.py
+++ b/layout/tools/reftest/remotereftest.py
@@ -373,17 +373,18 @@ class RemoteReftest(RefTest):
         status, self.lastTestSeen = self.automation.runApp(None, env,
                                                            binary,
                                                            profile.profile,
                                                            cmdargs,
                                                            utilityPath=options.utilityPath,
                                                            xrePath=options.xrePath,
                                                            debuggerInfo=debuggerInfo,
                                                            symbolsPath=symbolsPath,
-                                                           timeout=timeout)
+                                                           timeout=timeout,
+                                                           e10s=options.e10s)
 
         self.cleanup(profile.profile)
         return status
 
     def cleanup(self, profileDir):
         self.device.rm(self.remoteTestRoot,  force=True, recursive=True)
         self.device.rm(self.remoteProfile, force=True, recursive=True)
         self.device.rm(self.remoteCache, force=True, recursive=True)
--- a/taskcluster/taskgraph/transforms/tests.py
+++ b/taskcluster/taskgraph/transforms/tests.py
@@ -200,17 +200,17 @@ test_description_schema = Schema({
     # Whether to run this task with the serviceworker e10s redesign enabled
     # (desktop-test only).  If 'both', run one task with and one task without.
     # Tasks with this enabled have have "-sw" appended to the test name and
     # treeherder group.
     Optional('serviceworker-e10s'): optionally_keyed_by(
         'test-platform', 'project',
         Any(bool, 'both')),
 
-    # Whether to run this task with e10s (desktop-test only).  If false, run
+    # Whether to run this task with e10s.  If false, run
     # without e10s; if true, run with e10s; if 'both', run one task with and
     # one task without e10s.  E10s tasks have "-e10s" appended to the test name
     # and treeherder group.
     Required('e10s'): optionally_keyed_by(
         'test-platform', 'project',
         Any(bool, 'both')),
 
     # Whether the task should run with WebRender enabled or not.
@@ -424,17 +424,17 @@ def handle_keyed_by_mozharness(config, t
 @transforms.add
 def set_defaults(config, tests):
     for test in tests:
         build_platform = test['build-platform']
         if build_platform.startswith('android'):
             # all Android test tasks download internal objects from tooltool
             test['mozharness']['tooltool-downloads'] = True
             test['mozharness']['actions'] = ['get-secrets']
-            # Android doesn't do e10s
+            # Fennec is non-e10s; geckoview handled in set_target
             test['e10s'] = False
             # loopback-video is always true for Android, but false for other
             # platform phyla
             test['loopback-video'] = True
         else:
             # all non-android tests want to run the bits that require node
             test['mozharness']['set-moz-node-path'] = True
             test.setdefault('e10s', True)
@@ -546,16 +546,18 @@ def handle_artifact_prefix(config, tests
 @transforms.add
 def set_target(config, tests):
     for test in tests:
         build_platform = test['build-platform']
         target = None
         if 'target' in test:
             resolve_keyed_by(test, 'target', item_name=test['test-name'])
             target = test['target']
+            if target and 'geckoview' in target:
+                test['e10s'] = True
         if not target:
             if build_platform.startswith('macosx'):
                 target = 'target.dmg'
             elif build_platform.startswith('android'):
                 target = 'target.apk'
             elif build_platform.startswith('win'):
                 target = 'target.zip'
             else:
--- a/testing/mochitest/mach_commands.py
+++ b/testing/mochitest/mach_commands.py
@@ -172,16 +172,21 @@ class MochitestRunner(MozbuildObject):
         options = Namespace(**kwargs)
 
         from manifestparser import TestManifest
         if tests and not options.manifestFile:
             manifest = TestManifest()
             manifest.tests.extend(tests)
             options.manifestFile = manifest
 
+        # Firefox for Android doesn't use e10s
+        if options.app is None or 'geckoview' not in options.app:
+            options.e10s = False
+            print("using e10s=False for non-geckoview app")
+
         return runtestsremote.run_test_harness(parser, options)
 
     def run_geckoview_junit_test(self, context, **kwargs):
         host_ret = verify_host_bin()
         if host_ret != 0:
             return host_ret
 
         import runjunit
@@ -203,16 +208,20 @@ class MochitestRunner(MozbuildObject):
         options = Namespace(**kwargs)
 
         from manifestparser import TestManifest
         if tests and not options.manifestFile:
             manifest = TestManifest()
             manifest.tests.extend(tests)
             options.manifestFile = manifest
 
+        # robocop only used for Firefox for Android - non-e10s
+        options.e10s = False
+        print("using e10s=False for robocop")
+
         return runrobocop.run_test_harness(parser, options)
 
 # parser
 
 
 def setup_argument_parser():
     build_obj = MozbuildObject.from_environment(cwd=here)
 
--- a/testing/mochitest/mochitest_options.py
+++ b/testing/mochitest/mochitest_options.py
@@ -992,21 +992,16 @@ class AndroidArguments(ArgumentContainer
                 parser.error(
                     "--coverage-output-dir must be specified when using --enable-coverage")
             parent_dir = os.path.dirname(options.coverage_output_dir)
             if not os.path.isdir(options.coverage_output_dir):
                 parser.error(
                     "The directory for the coverage output does not exist: %s" %
                     parent_dir)
 
-        # Disable e10s by default on Android because we don't run Android
-        # e10s jobs anywhere yet.
-        options.e10s = False
-        mozinfo.update({'e10s': options.e10s})
-
         # allow us to keep original application around for cleanup while
         # running robocop via 'am'
         options.remoteappname = options.app
         return options
 
 
 container_map = {
     'generic': [MochitestArguments],
--- a/testing/mochitest/runtests.py
+++ b/testing/mochitest/runtests.py
@@ -2072,17 +2072,18 @@ toolbar#nav-bar {
                valgrindPath=None,
                valgrindArgs=None,
                valgrindSuppFiles=None,
                symbolsPath=None,
                timeout=-1,
                detectShutdownLeaks=False,
                screenshotOnFail=False,
                bisectChunk=None,
-               marionette_args=None):
+               marionette_args=None,
+               e10s=True):
         """
         Run the app, log the duration it took to execute, return the status code.
         Kills the app if it runs for longer than |maxTime| seconds, or outputs nothing
         for |timeout| seconds.
         """
         # It can't be the case that both a with-debugger and an
         # on-Valgrind run have been requested.  doTests() should have
         # already excluded this possibility.
@@ -2774,16 +2775,17 @@ toolbar#nav-bar {
                     valgrindArgs=valgrindArgs,
                     valgrindSuppFiles=valgrindSuppFiles,
                     symbolsPath=options.symbolsPath,
                     timeout=timeout,
                     detectShutdownLeaks=detectShutdownLeaks,
                     screenshotOnFail=options.screenshotOnFail,
                     bisectChunk=options.bisectChunk,
                     marionette_args=marionette_args,
+                    e10s=options.e10s
                 )
                 status = ret or status
         except KeyboardInterrupt:
             self.log.info("runtests.py | Received keyboard interrupt.\n")
             status = -1
         except Exception as e:
             traceback.print_exc()
             self.log.error(
--- a/testing/mozharness/scripts/android_emulator_unittest.py
+++ b/testing/mozharness/scripts/android_emulator_unittest.py
@@ -20,16 +20,19 @@ from mozharness.base.script import BaseS
 from mozharness.mozilla.mozbase import MozbaseMixin
 from mozharness.mozilla.testing.android import AndroidMixin
 from mozharness.mozilla.testing.testbase import TestingMixin, testing_config_options
 from mozharness.mozilla.testing.codecoverage import (
     CodeCoverageMixin,
     code_coverage_config_options
 )
 
+SUITE_DEFAULT_E10S = ['geckoview-junit', 'mochitest', 'reftest']
+SUITE_NO_E10S = ['cppunittest', 'geckoview-junit', 'xpcshell']
+
 
 class AndroidEmulatorTest(TestingMixin, BaseScript, MozbaseMixin, CodeCoverageMixin,
                           AndroidMixin):
     """
        A mozharness script for Android functional tests (like mochitests and reftests)
        run on an Android emulator. This script starts and manages an Android emulator
        for the duration of the required tests. This is like desktop_unittest.py, but
        for Android emulator test platforms.
@@ -70,16 +73,23 @@ class AndroidEmulatorTest(TestingMixin, 
          }
     ], [
         ["--log-tbpl-level"],
         {"action": "store",
          "dest": "log_tbpl_level",
          "default": "info",
          "help": "Set log level (debug|info|warning|error|critical|fatal)",
          }
+    ], [
+        ['--e10s', ],
+        {"action": "store_true",
+         "dest": "e10s",
+         "default": False,
+         "help": "Run tests with multiple processes.",
+         }
     ]] + copy.deepcopy(testing_config_options) + \
         copy.deepcopy(code_coverage_config_options)
 
     def __init__(self, require_config_file=False):
         super(AndroidEmulatorTest, self).__init__(
             config_options=self.config_options,
             all_actions=['clobber',
                          'setup-avds',
@@ -116,16 +126,17 @@ class AndroidEmulatorTest(TestingMixin, 
             if m:
                 self.test_suite = m.group(1)
                 if self.this_chunk is None:
                     self.this_chunk = m.group(2)
         self.xre_path = None
         self.device_serial = 'emulator-5554'
         self.log_raw_level = c.get('log_raw_level')
         self.log_tbpl_level = c.get('log_tbpl_level')
+        self.e10s = c.get('e10s')
 
     def query_abs_dirs(self):
         if self.abs_dirs:
             return self.abs_dirs
         abs_dirs = super(AndroidEmulatorTest, self).query_abs_dirs()
         dirs = {}
         dirs['abs_test_install_dir'] = os.path.join(
             abs_dirs['abs_work_dir'], 'tests')
@@ -225,16 +236,28 @@ class AndroidEmulatorTest(TestingMixin, 
             if '%(app)' in option:
                 # only query package name if requested
                 cmd.extend([option % {'app': self.query_package_name()}])
             else:
                 option = option % str_format_values
                 if option:
                     cmd.extend([option])
 
+        if 'mochitest' in self.test_suite:
+            category = 'mochitest'
+        elif 'reftest' in self.test_suite or 'crashtest' in self.test_suite:
+            category = 'reftest'
+        else:
+            category = self.test_suite
+        if category not in SUITE_NO_E10S:
+            if category in SUITE_DEFAULT_E10S and not self.e10s:
+                cmd.extend(['--disable-e10s'])
+            elif category not in SUITE_DEFAULT_E10S and self.e10s:
+                cmd.extend(['--e10s'])
+
         if not (self.verify_enabled or self.per_test_coverage):
             if user_paths:
                 cmd.extend(user_paths.split(':'))
             elif not (self.verify_enabled or self.per_test_coverage):
                 if self.this_chunk is not None:
                     cmd.extend(['--this-chunk', self.this_chunk])
                 if self.total_chunks is not None:
                     cmd.extend(['--total-chunks', self.total_chunks])
--- a/testing/mozharness/scripts/android_hardware_unittest.py
+++ b/testing/mozharness/scripts/android_hardware_unittest.py
@@ -17,16 +17,19 @@ sys.path.insert(1, os.path.dirname(sys.p
 
 from mozharness.base.log import FATAL
 from mozharness.base.script import BaseScript, PreScriptAction
 from mozharness.mozilla.mozbase import MozbaseMixin
 from mozharness.mozilla.testing.android import AndroidMixin
 from mozharness.mozilla.testing.testbase import TestingMixin, testing_config_options
 from mozharness.mozilla.testing.codecoverage import CodeCoverageMixin
 
+SUITE_DEFAULT_E10S = ['geckoview-junit', 'mochitest', 'reftest']
+SUITE_NO_E10S = ['cppunittest', 'xpcshell']
+
 
 class AndroidHardwareTest(TestingMixin, BaseScript, MozbaseMixin,
                           CodeCoverageMixin, AndroidMixin):
     config_options = [[
         ["--test-suite"],
         {"action": "store",
          "dest": "test_suite",
          "default": None
@@ -61,16 +64,23 @@ class AndroidHardwareTest(TestingMixin, 
          }
     ], [
         ["--log-tbpl-level"],
         {"action": "store",
          "dest": "log_tbpl_level",
          "default": "info",
          "help": "Set log level (debug|info|warning|error|critical|fatal)",
          }
+    ], [
+        ['--e10s', ],
+        {"action": "store_true",
+         "dest": "e10s",
+         "default": False,
+         "help": "Run tests with multiple processes.",
+         }
     ]] + copy.deepcopy(testing_config_options)
 
     def __init__(self, require_config_file=False):
         super(AndroidHardwareTest, self).__init__(
             config_options=self.config_options,
             all_actions=['clobber',
                          'download-and-extract',
                          'create-virtualenv',
@@ -105,16 +115,17 @@ class AndroidHardwareTest(TestingMixin, 
             m = re.match("(.*)-(\d*)", self.test_suite)
             if m:
                 self.test_suite = m.group(1)
                 if self.this_chunk is None:
                     self.this_chunk = m.group(2)
         self.xre_path = None
         self.log_raw_level = c.get('log_raw_level')
         self.log_tbpl_level = c.get('log_tbpl_level')
+        self.e10s = c.get('e10s')
 
     def query_abs_dirs(self):
         if self.abs_dirs:
             return self.abs_dirs
         abs_dirs = super(AndroidHardwareTest, self).query_abs_dirs()
         dirs = {}
         dirs['abs_test_install_dir'] = os.path.join(
             abs_dirs['abs_work_dir'], 'tests')
@@ -220,16 +231,28 @@ class AndroidHardwareTest(TestingMixin, 
         if user_paths:
             cmd.extend(user_paths.split(':'))
         elif not self.verify_enabled:
             if self.this_chunk is not None:
                 cmd.extend(['--this-chunk', self.this_chunk])
             if self.total_chunks is not None:
                 cmd.extend(['--total-chunks', self.total_chunks])
 
+        if 'mochitest' in self.test_suite:
+            category = 'mochitest'
+        elif 'reftest' in self.test_suite or 'crashtest' in self.test_suite:
+            category = 'reftest'
+        else:
+            category = self.test_suite
+        if category not in SUITE_NO_E10S:
+            if category in SUITE_DEFAULT_E10S and not self.e10s:
+                cmd.extend(['--disable-e10s'])
+            elif category not in SUITE_DEFAULT_E10S and self.e10s:
+                cmd.extend(['--e10s'])
+
         try_options, try_tests = self.try_args(self.test_suite)
         cmd.extend(try_options)
         if not self.verify_enabled and not self.per_test_coverage:
             cmd.extend(self.query_tests_args(
                 self.config["suite_definitions"][self.test_suite].get("tests"),
                 None,
                 try_tests))