Bug 945975 - Add mach support for Android xpcshell tests; r=ted
authorGeoff Brown <gbrown@mozilla.com>
Fri, 13 Dec 2013 21:20:39 -0700
changeset 160498 00db24c44565df7e3c4b0cd0ddca67051d78f8e5
parent 160497 093a4a3a68caf597330d0cfb00af103ac2791178
child 160499 be5f5ca171f029571171f2da960c910d3808ebd3
push id25834
push userphilringnalda@gmail.com
push dateSun, 15 Dec 2013 02:20:53 +0000
treeherdermozilla-central@9fcc6330dc69 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersted
bugs945975
milestone29.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 945975 - Add mach support for Android xpcshell tests; r=ted
python/mozbuild/mozbuild/base.py
testing/xpcshell/mach_commands.py
testing/xpcshell/remotexpcshelltests.py
--- a/python/mozbuild/mozbuild/base.py
+++ b/python/mozbuild/mozbuild/base.py
@@ -585,16 +585,23 @@ class MachCommandConditions(object):
     @staticmethod
     def is_b2g_desktop(cls):
         """Must have a Boot to Gecko desktop build."""
         if hasattr(cls, 'substs'):
             return cls.substs.get('MOZ_BUILD_APP') == 'b2g' and \
                    cls.substs.get('MOZ_WIDGET_TOOLKIT') != 'gonk'
         return False
 
+    @staticmethod
+    def is_android(cls):
+        """Must have an Android build."""
+        if hasattr(cls, 'substs'):
+            return cls.substs.get('MOZ_WIDGET_TOOLKIT') == 'android'
+        return False
+
 
 class PathArgument(object):
     """Parse a filesystem path argument and transform it in various ways."""
 
     def __init__(self, arg, topsrcdir, topobjdir, cwd=None):
         self.arg = arg
         self.topsrcdir = topsrcdir
         self.topobjdir = topobjdir
--- a/testing/xpcshell/mach_commands.py
+++ b/testing/xpcshell/mach_commands.py
@@ -12,16 +12,17 @@ import os
 import shutil
 import sys
 
 from StringIO import StringIO
 
 from mozbuild.base import (
     MachCommandBase,
     MozbuildObject,
+    MachCommandConditions as conditions,
 )
 
 from mach.decorators import (
     CommandArgument,
     CommandProvider,
     Command,
 )
 
@@ -48,17 +49,19 @@ class XPCShellRunner(MozbuildObject):
         manifest = os.path.join(self.topobjdir, '_tests', 'xpcshell',
             'xpcshell.ini')
 
         return self._run_xpcshell_harness(manifest=manifest, **kwargs)
 
     def run_test(self, test_file, interactive=False,
                  keep_going=False, sequential=False, shuffle=False,
                  debugger=None, debuggerArgs=None, debuggerInteractive=None,
-                 rerun_failures=False):
+                 rerun_failures=False,
+                 # ignore parameters from other platforms' options
+                 **kwargs):
         """Runs an individual xpcshell test."""
         # TODO Bug 794506 remove once mach integrates with virtualenv.
         build_path = os.path.join(self.topobjdir, 'build')
         if build_path not in sys.path:
             sys.path.append(build_path)
 
         if test_file == 'all':
             self.run_suite(interactive=interactive,
@@ -192,16 +195,100 @@ class XPCShellRunner(MozbuildObject):
         self.log_manager.terminal_handler.removeFilter(xpcshell_filter)
         self.log_manager.disable_unstructured()
 
         if not result and not xpcshell.sequential:
             print("Tests were run in parallel. Try running with --sequential "
                   "to make sure the failures were not caused by this.")
         return int(not result)
 
+class AndroidXPCShellRunner(MozbuildObject):
+    """Get specified DeviceManager"""
+    def get_devicemanager(self, devicemanager, ip, port, remote_test_root):
+        from mozdevice import devicemanagerADB, devicemanagerSUT
+        dm = None
+        if devicemanager == "adb":
+            if ip:
+                dm = devicemanagerADB.DeviceManagerADB(ip, port, packageName=None, deviceRoot=remote_test_root)
+            else:
+                dm = devicemanagerADB.DeviceManagerADB(packageName=None, deviceRoot=remote_test_root)
+        else:
+            if ip:
+                dm = devicemanagerSUT.DeviceManagerSUT(ip, port, deviceRoot=remote_test_root)
+            else:
+                raise Exception("You must provide a device IP to connect to via the --ip option")
+        return dm
+
+    """Run Android xpcshell tests."""
+    def run_test(self,
+                 test_file, keep_going,
+                 devicemanager, ip, port, remote_test_root, no_setup, local_apk,
+                 # ignore parameters from other platforms' options
+                 **kwargs):
+        # TODO Bug 794506 remove once mach integrates with virtualenv.
+        build_path = os.path.join(self.topobjdir, 'build')
+        if build_path not in sys.path:
+            sys.path.append(build_path)
+
+        import remotexpcshelltests
+
+        dm = self.get_devicemanager(devicemanager, ip, port, remote_test_root)
+
+        options = remotexpcshelltests.RemoteXPCShellOptions()
+        options.shuffle = False
+        options.sequential = True
+        options.interactive = False
+        options.debugger = None
+        options.debuggerArgs = None
+        options.setup = not no_setup
+        options.keepGoing = keep_going
+        options.objdir = self.topobjdir
+        options.localLib = os.path.join(self.topobjdir, 'dist/fennec')
+        options.localBin = os.path.join(self.topobjdir, 'dist/bin')
+        options.testingModulesDir = os.path.join(self.topobjdir, '_tests/modules')
+        options.mozInfo = os.path.join(self.topobjdir, 'mozinfo.json')
+        options.manifest = os.path.join(self.topobjdir, '_tests/xpcshell/xpcshell_android.ini')
+        options.symbolsPath = os.path.join(self.distdir, 'crashreporter-symbols')
+        if local_apk:
+            options.localAPK = local_apk
+        else:
+            for file in os.listdir(os.path.join(options.objdir, "dist")):
+                if file.endswith(".apk") and file.startswith("fennec"):
+                    options.localAPK = os.path.join(options.objdir, "dist")
+                    options.localAPK = os.path.join(options.localAPK, file)
+                    print ("using APK: " + options.localAPK)
+                    break
+            else:
+                raise Exception("You must specify an APK")
+
+        if test_file == 'all':
+            testdirs = []
+            options.testPath = None
+            options.verbose = False
+        else:
+            testdirs = test_file
+            options.testPath = test_file
+            options.verbose = True
+        dummy_log = StringIO()
+        xpcshell = remotexpcshelltests.XPCShellRemote(dm, options, args=testdirs, log=dummy_log)
+        self.log_manager.enable_unstructured()
+
+        xpcshell_filter = TestStartFilter()
+        self.log_manager.terminal_handler.addFilter(xpcshell_filter)
+
+        result = xpcshell.runTests(xpcshell='xpcshell',
+                      testClass=remotexpcshelltests.RemoteXPCShellTestThread,
+                      testdirs=testdirs,
+                      mobileArgs=xpcshell.mobileArgs,
+                      **options.__dict__)
+
+        self.log_manager.terminal_handler.removeFilter(xpcshell_filter)
+        self.log_manager.disable_unstructured()
+
+        return int(not result)
 
 @CommandProvider
 class MachCommands(MachCommandBase):
     @Command('xpcshell-test', category='testing',
         description='Run XPCOM Shell tests.')
     @CommandArgument('test_file', default='all', nargs='?', metavar='TEST',
         help='Test to run. Can be specified as a single JS file, a directory, '
              'or omitted. If omitted, the entire test suite is executed.')
@@ -220,26 +307,41 @@ class MachCommands(MachCommandBase):
     @CommandArgument('--keep-going', '-k', action='store_true',
         help='Continue running tests after a SIGINT is received.')
     @CommandArgument('--sequential', action='store_true',
         help='Run the tests sequentially.')
     @CommandArgument('--shuffle', '-s', action='store_true',
         help='Randomize the execution order of tests.')
     @CommandArgument('--rerun-failures', action='store_true',
         help='Reruns failures from last time.')
+    @CommandArgument('--devicemanager', default='adb', type=str,
+        help='(Android) Type of devicemanager to use for communication: adb or sut')
+    @CommandArgument('--ip', type=str, default=None,
+        help='(Android) IP address of device')
+    @CommandArgument('--port', type=int, default=20701,
+        help='(Android) Port of device')
+    @CommandArgument('--remote_test_root', type=str, default=None,
+        help='(Android) Remote test root such as /mnt/sdcard or /data/local')
+    @CommandArgument('--no-setup', action='store_true',
+        help='(Android) Do not copy files to device')
+    @CommandArgument('--local-apk', type=str, default=None,
+        help='(Android) Use specified Fennec APK')
     def run_xpcshell_test(self, **params):
         from mozbuild.controller.building import BuildDriver
 
         # We should probably have a utility function to ensure the tree is
         # ready to run tests. Until then, we just create the state dir (in
         # case the tree wasn't built with mach).
         self._ensure_state_subdir_exists('.')
 
         driver = self._spawn(BuildDriver)
         driver.install_tests(remove=False)
 
-        xpcshell = self._spawn(XPCShellRunner)
+        if conditions.is_android(self):
+            xpcshell = self._spawn(AndroidXPCShellRunner)
+        else:
+            xpcshell = self._spawn(XPCShellRunner)
 
         try:
             return xpcshell.run_test(**params)
         except InvalidTestPathError as e:
             print(e.message)
             return 1
--- a/testing/xpcshell/remotexpcshelltests.py
+++ b/testing/xpcshell/remotexpcshelltests.py
@@ -183,18 +183,24 @@ class RemoteXPCShellTestThread(xpcshell.
             if f is not None:
                 f.close()
 
 
 # A specialization of XPCShellTests that runs tests on an Android device
 # via devicemanager.
 class XPCShellRemote(xpcshell.XPCShellTests, object):
 
-    def __init__(self, devmgr, options, args):
-        xpcshell.XPCShellTests.__init__(self)
+    def __init__(self, devmgr, options, args, log=None):
+        xpcshell.XPCShellTests.__init__(self, log)
+
+        # Add Android version (SDK level) to mozinfo so that manifest entries
+        # can be conditional on android_version.
+        androidVersion = devmgr.shellCheckOutput(['getprop', 'ro.build.version.sdk'])
+        mozinfo.info['android_version'] = androidVersion
+
         self.localLib = options.localLib
         self.localBin = options.localBin
         self.options = options
         self.device = devmgr
         self.pathMapping = []
         self.remoteTestRoot = self.device.getTestRoot("xpcshell")
         # remoteBinDir contains xpcshell and its wrapper script, both of which must
         # be executable. Since +x permissions cannot usually be set on /mnt/sdcard,
@@ -559,21 +565,16 @@ def main():
         if (options.deviceIP == None):
             print "Error: you must provide a device IP to connect to via the --device option"
             sys.exit(1)
 
     if options.interactive and not options.testPath:
         print >>sys.stderr, "Error: You must specify a test filename in interactive mode!"
         sys.exit(1)
 
-    # Add Android version (SDK level) to mozinfo so that manifest entries
-    # can be conditional on android_version.
-    androidVersion = dm.shellCheckOutput(['getprop', 'ro.build.version.sdk'])
-    mozinfo.info['android_version'] = androidVersion
-
     xpcsh = XPCShellRemote(dm, options, args)
 
     # we don't run concurrent tests on mobile
     options.sequential = True
 
     if not xpcsh.runTests(xpcshell='xpcshell',
                           testClass=RemoteXPCShellTestThread,
                           testdirs=args[0:],