Bug 1303031 - (WIP) Support Android xpcshell tests from an interactive loaner draft
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Thu, 15 Sep 2016 10:20:32 -0400
changeset 604835 5701001c7b072ae5c4da2195e604d2ce3d7ba76d
parent 427338 99a239e1866a57f987b08dad796528e4ea30e622
child 636304 7489510e0c752583d44b54c9ca6005c408f170fc
push id67202
push userahalberstadt@mozilla.com
push dateThu, 06 Jul 2017 14:39:42 +0000
bugs1303031
milestone52.0a1
Bug 1303031 - (WIP) Support Android xpcshell tests from an interactive loaner MozReview-Commit-ID: 2iT5CnbXgp1
testing/tools/mach_test_package_bootstrap.py
testing/xpcshell/mach_commands.py
testing/xpcshell/mach_test_package_commands.py
testing/xpcshell/remotexpcshelltests.py
testing/xpcshell/runtestsb2g.py
testing/xpcshell/runxpcshelltests.py
testing/xpcshell/xpcshellcommandline.py
--- a/testing/tools/mach_test_package_bootstrap.py
+++ b/testing/tools/mach_test_package_bootstrap.py
@@ -124,16 +124,17 @@ def find_hostutils(context):
 def normalize_test_path(test_root, path):
     if os.path.isabs(path) or os.path.exists(path):
         return os.path.normpath(os.path.abspath(path))
 
     for parent in ancestors(test_root):
         test_path = os.path.join(parent, path)
         if os.path.exists(test_path):
             return os.path.normpath(os.path.abspath(test_path))
+    return path
 
 
 def bootstrap(test_package_root):
     test_package_root = os.path.abspath(test_package_root)
 
     # Ensure we are running Python 2.7+. We put this check here so we generate a
     # user-friendly error message rather than a cryptic stack trace on module
     # import.
--- a/testing/xpcshell/mach_commands.py
+++ b/testing/xpcshell/mach_commands.py
@@ -36,16 +36,17 @@ If you have a B2G build, this can be fou
 '''.lstrip()
 
 BUSYBOX_URLS = {
     'arm': 'http://www.busybox.net/downloads/binaries/latest/busybox-armv7l',
     'x86': 'http://www.busybox.net/downloads/binaries/latest/busybox-i686'
 }
 
 here = os.path.abspath(os.path.dirname(__file__))
+parser = None
 
 if sys.version_info[0] < 3:
     unicode_type = unicode
 else:
     unicode_type = str
 
 # This should probably be consolidated with similar classes in other test
 # runners.
@@ -72,19 +73,16 @@ class XPCShellRunner(MozbuildObject):
 
         return self.run_suite(**kwargs)
 
 
     def _run_xpcshell_harness(self, **kwargs):
         # Obtain a reference to the xpcshell test runner.
         import runxpcshelltests
 
-        log = kwargs.pop("log")
-
-        xpcshell = runxpcshelltests.XPCShellTests(log=log)
         self.log_manager.enable_unstructured()
 
         tests_dir = os.path.join(self.topobjdir, '_tests', 'xpcshell')
         # We want output from the test to be written immediately if we are only
         # running a single test.
         single_test = (len(kwargs["testPaths"]) == 1 and
                        os.path.isfile(kwargs["testPaths"][0]) or
                        kwargs["manifest"] and
@@ -142,17 +140,18 @@ class XPCShellRunner(MozbuildObject):
             if isinstance(v, unicode_type):
                 v = v.encode('utf-8')
 
             if isinstance(k, unicode_type):
                 k = k.encode('utf-8')
 
             filtered_args[k] = v
 
-        result = xpcshell.runTests(**filtered_args)
+        options = Namespace(**kwargs)
+        result = runxpcshelltests.run_test_harness(parser, options)
 
         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)
 
@@ -180,17 +179,16 @@ class AndroidXPCShellRunner(MozbuildObje
         if build_path not in sys.path:
             sys.path.append(build_path)
 
         import remotexpcshelltests
 
         dm = self.get_devicemanager(kwargs["dm_trans"], kwargs["deviceIP"], kwargs["devicePort"],
                                     kwargs["remoteTestRoot"])
 
-        log = kwargs.pop("log")
         self.log_manager.enable_unstructured()
 
         if kwargs["xpcshell"] is None:
             kwargs["xpcshell"] = "xpcshell"
 
         if not kwargs["objdir"]:
             kwargs["objdir"] = self.topobjdir
 
@@ -220,17 +218,17 @@ class AndroidXPCShellRunner(MozbuildObje
                     break
             else:
                 raise Exception("APK not found in objdir. You must specify an APK.")
 
         if not kwargs["sequential"]:
             kwargs["sequential"] = True
 
         options = argparse.Namespace(**kwargs)
-        xpcshell = remotexpcshelltests.XPCShellRemote(dm, options, log)
+        remotexpcshelltests.run_test_harness(parser, options)
 
         result = xpcshell.runTests(testClass=remotexpcshelltests.RemoteXPCShellTestThread,
                                    mobileArgs=xpcshell.mobileArgs,
                                    **vars(options))
 
         self.log_manager.disable_unstructured()
 
         return int(not result)
@@ -284,17 +282,16 @@ class B2GXPCShellRunner(MozbuildObject):
             which.which('adb')
         except which.WhichError:
             # TODO Find adb automatically if it isn't on the path
             print(ADB_NOT_FOUND % ('mochitest-remote', kwargs["b2g_home"]))
             sys.exit(1)
 
         import runtestsb2g
 
-        log = kwargs.pop("log")
         self.log_manager.enable_unstructured()
 
         if kwargs["device_name"].startswith('emulator') and 'x86' in kwargs["device_name"]:
             kwargs["emulator"] = 'x86'
 
         if kwargs["xpcshell"] is None:
             kwargs["xpcshell"] = "xpcshell"
         if kwargs["b2g_path"] is None:
@@ -320,29 +317,33 @@ class B2GXPCShellRunner(MozbuildObject):
             kwargs["symbolsPath"] = os.path.join(self.distdir, 'crashreporter-symbols')
         if kwargs["testingModulesDir"] is None:
             kwargs["testingModulesDir"] = os.path.join(self.tests_dir, 'modules')
         if kwargs["use_device_libs"] is None:
             kwargs["use_device_libs"] = True
 
         parser = parser_b2g()
         options = argparse.Namespace(**kwargs)
-        rv = runtestsb2g.run_remote_xpcshell(parser, options, log)
+        rv = runtestsb2g.run_test_harness(parser, options)
 
         self.log_manager.disable_unstructured()
         return rv
 
+
 def get_parser():
+    global parser
     build_obj = MozbuildObject.from_environment(cwd=here)
     if conditions.is_android(build_obj):
-        return parser_remote()
+        parser = parser_remote()
     elif conditions.is_b2g(build_obj):
-        return parser_b2g()
+        parser =  parser_b2g()
     else:
-        return parser_desktop()
+        parser = parser_desktop()
+    return parser
+
 
 @CommandProvider
 class MachCommands(MachCommandBase):
     def __init__(self, context):
         MachCommandBase.__init__(self, context)
 
         for attr in ('b2g_home', 'device_name'):
             setattr(self, attr, getattr(context, attr, None))
--- a/testing/xpcshell/mach_test_package_commands.py
+++ b/testing/xpcshell/mach_test_package_commands.py
@@ -4,61 +4,98 @@
 
 from __future__ import unicode_literals
 
 import os
 import sys
 from argparse import Namespace
 from functools import partial
 
-
-import mozlog
-from xpcshellcommandline import parser_desktop
-
 from mach.decorators import (
     CommandProvider,
     Command,
 )
 
+here = os.path.abspath(os.path.dirname(__file__))
+parser = None
+
 
 def run_xpcshell(context, **kwargs):
+    import mozinfo
+    import mozlog
+
     args = Namespace(**kwargs)
+    args.testingModulesDir = context.modules_dir
+
+    args.log = mozlog.commandline.setup_logging(
+        "XPCShellTests",
+        args,
+        {"mach": sys.stdout},
+        {"verbose": True}
+    )
+
+    if args.testPaths:
+        test_root = os.path.join(context.package_root, 'xpcshell', 'tests')
+        normalize = partial(context.normalize_test_path, test_root)
+        args.testPaths = map(normalize, args.testPaths)
+
+    if mozinfo.info.get('buildapp') == 'mobile/android':
+        return run_android_xpcshell(context, args)
+    return run_desktop_xpcshell(context, args)
+
+
+def run_desktop_xpcshell(context, args):
+    import runxpcshelltests
+
     args.appPath = args.appPath or os.path.dirname(context.firefox_bin)
-    args.e10s = context.mozharness_config.get('e10s', args.e10s)
     args.utility_path = context.bin_dir
-    args.testingModulesDir = context.modules_dir
 
     if not args.xpcshell:
         args.xpcshell = os.path.join(args.appPath, 'xpcshell')
 
     if not args.pluginsPath:
         for path in context.ancestors(args.appPath, depth=2):
             test = os.path.join(path, 'plugins')
             if os.path.isdir(test):
                 args.pluginsPath = test
                 break
 
-    log = mozlog.commandline.setup_logging("XPCShellTests",
-                                           args,
-                                           {"mach": sys.stdout},
-                                           {"verbose": True})
+    return runxpcshelltests.run_test_harness(parser, args)
+
+
+def run_android_xpcshell(context, args):
+    import remotexpcshelltests
+    args.localAPK = os.path.join(context.mozharness_workdir, 'target.apk')
+    args.xrePath = context.hostutils
 
-    if args.testPaths:
-        test_root = os.path.join(context.package_root, 'xpcshell', 'tests')
-        normalize = partial(context.normalize_test_path, test_root)
-        args.testPaths = map(normalize, args.testPaths)
+    config = context.mozharness_config
+    if config:
+        args.remoteWebServer = config['remote_webserver']
+        args.httpPort = config['emulator']['http_port']
+        args.sslPort = config['emulator']['ssl_port']
+        args.adb_path = config['exes']['adb'] % {'abs_work_dir': context.mozharness_workdir}
+    return remotexpcshelltests.run_test_harness(parser, args)
+
 
-    import runxpcshelltests
-    xpcshell = runxpcshelltests.XPCShellTests(log=log)
-    return xpcshell.runTests(**vars(args))
+def get_argument_parser():
+    import mozinfo
+    import xpcshellcommandline
+
+    global parser
+    mozinfo.find_and_update_from_json(os.path.dirname(here))
+    if mozinfo.info.get('buildapp') == 'mobile/android':
+        parser = xpcshellcommandline.parser_remote()
+    else:
+        parser = xpcshellcommandline.parser_desktop()
+    return parser
 
 
 @CommandProvider
 class MochitestCommands(object):
 
     def __init__(self, context):
         self.context = context
 
     @Command('xpcshell-test', category='testing',
              description='Run the xpcshell harness.',
-             parser=parser_desktop)
+             parser=get_argument_parser)
     def xpcshell(self, **kwargs):
         return run_xpcshell(self.context, **kwargs)
--- a/testing/xpcshell/remotexpcshelltests.py
+++ b/testing/xpcshell/remotexpcshelltests.py
@@ -541,16 +541,27 @@ class XPCShellRemote(xpcshell.XPCShellTe
         for test in self.alltests:
             uniqueTestPaths.add(test['here'])
         for testdir in uniqueTestPaths:
             abbrevTestDir = os.path.relpath(testdir, self.xpcDir)
             remoteScriptDir = remoteJoin(self.remoteScriptsDir, abbrevTestDir)
             self.pathMapping.append(PathMapping(testdir, remoteScriptDir))
 
 def verifyRemoteOptions(parser, options):
+    if not options.localAPK:
+        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 >>sys.stderr, "using APK: " + options.localAPK
+                break
+        else:
+            print >>sys.stderr, "Error: please specify an APK"
+            sys.exit(1)
+
     if options.localLib is None:
         if options.localAPK and options.objdir:
             for path in ['dist/fennec', 'fennec/lib']:
                 options.localLib = os.path.join(options.objdir, path)
                 if os.path.isdir(options.localLib):
                     break
             else:
                 parser.error("Couldn't find local library dir, specify --local-lib-dir")
@@ -578,62 +589,54 @@ def verifyRemoteOptions(parser, options)
     return options
 
 class PathMapping:
 
     def __init__(self, localDir, remoteDir):
         self.local = localDir
         self.remote = remoteDir
 
-def main():
+def run_test_harness(parser, options):
     if sys.version_info < (2,7):
         print >>sys.stderr, "Error: You must use python version 2.7 or newer but less than 3.0"
         sys.exit(1)
 
-    parser = parser_remote()
-    options = parser.parse_args()
-    if not options.localAPK:
-        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 >>sys.stderr, "using APK: " + options.localAPK
-                break
-        else:
-            print >>sys.stderr, "Error: please specify an APK"
-            sys.exit(1)
+    options = verifyRemoteOptions(parser, options)
+
+    dm_args = {
+        'deviceRoot': options.remoteTestRoot,
+        'host': options.deviceIP,
+        'port': options.devicePort,
+    }
 
-    options = verifyRemoteOptions(parser, options)
-    log = commandline.setup_logging("Remote XPCShell",
-                                    options,
-                                    {"tbpl": sys.stdout})
+    dm_cls = mozdevice.DroidSUT
+    if options.dm_trans == 'adb':
+        dm_args['adbPath'] = options.adb_path
+        dm_cls = mozdevice.DroidADB
+    elif not options.deviceIP:
+        print "Error: you must provide a device IP to connect to via the --device option"
+        sys.exit(1)
 
-    if options.dm_trans == "adb":
-        if options.deviceIP:
-            dm = mozdevice.DroidADB(options.deviceIP, options.devicePort, packageName=None, deviceRoot=options.remoteTestRoot)
-        else:
-            dm = mozdevice.DroidADB(packageName=None, deviceRoot=options.remoteTestRoot)
-    else:
-        if not options.deviceIP:
-            print "Error: you must provide a device IP to connect to via the --device option"
-            sys.exit(1)
-        dm = mozdevice.DroidSUT(options.deviceIP, options.devicePort, deviceRoot=options.remoteTestRoot)
+    dm = dm_cls(**dm_args)
 
     if options.interactive and not options.testPath:
         print >>sys.stderr, "Error: You must specify a test filename in interactive mode!"
         sys.exit(1)
 
     if options.xpcshell is None:
         options.xpcshell = "xpcshell"
 
-    xpcsh = XPCShellRemote(dm, options, log)
+    xpcsh = XPCShellRemote(dm, options, options.log)
 
     # we don't run concurrent tests on mobile
     options.sequential = True
 
-    if not xpcsh.runTests(testClass=RemoteXPCShellTestThread,
+    return xpcsh.runTests(testClass=RemoteXPCShellTestThread,
                           mobileArgs=xpcsh.mobileArgs,
-                          **vars(options)):
-        sys.exit(1)
-
+                          **vars(options))
 
 if __name__ == '__main__':
-    main()
+    parser = parser_remote()
+    options = parser.parse_args()
+    options.log = commandline.setup_logging("Remote XPCShell",
+                                            options,
+                                            {"tbpl": sys.stdout})
+    sys.exit(run_test_harness(parser, options))
--- a/testing/xpcshell/runtestsb2g.py
+++ b/testing/xpcshell/runtestsb2g.py
@@ -82,17 +82,18 @@ def verifyRemoteOptions(parser, options)
 
     if options.geckoPath and not options.emulator:
         parser.error("You must specify --emulator if you specify --gecko-path")
 
     if options.logdir and not options.emulator:
         parser.error("You must specify --emulator if you specify --logdir")
     return remotexpcshelltests.verifyRemoteOptions(parser, options)
 
-def run_remote_xpcshell(parser, options, log):
+
+def run_test_harness(parser, options):
     options = verifyRemoteOptions(parser, options)
 
     # Create the Marionette instance
     kwargs = {}
     if options.emulator:
         kwargs['emulator'] = options.emulator
         if options.no_window:
             kwargs['noWindow'] = True
@@ -123,17 +124,17 @@ def run_remote_xpcshell(parser, options,
         if options.deviceIP:
             kwargs['host'] = options.deviceIP
             kwargs['port'] = options.devicePort
         kwargs['deviceRoot'] = options.remoteTestRoot
         dm = devicemanagerADB.DeviceManagerADB(**kwargs)
 
     if not options.remoteTestRoot:
         options.remoteTestRoot = dm.deviceRoot
-    xpcsh = B2GXPCShellRemote(dm, options, log)
+    xpcsh = B2GXPCShellRemote(dm, options, options.log)
 
     # we don't run concurrent tests on mobile
     options.sequential = True
 
     if options.xpcshell is None:
         options.xpcshell = "xpcshell"
 
     try:
@@ -141,22 +142,20 @@ def run_remote_xpcshell(parser, options,
                               mobileArgs=xpcsh.mobileArgs,
                               **vars(options)):
             sys.exit(1)
     except:
         print "Automation Error: Exception caught while running tests"
         traceback.print_exc()
         sys.exit(1)
 
-def main():
-    parser = parser_b2g()
-    options = parser.parse_args()
-    log = commandline.setup_logging("Remote XPCShell",
-                                    options,
-                                    {"tbpl": sys.stdout})
-    run_remote_xpcshell(parser, options, log)
 
 # You usually run this like :
 # python runtestsb2g.py --emulator arm --b2gpath $B2GPATH --manifest $MANIFEST [--xre-path $MOZ_HOST_BIN
 #                                                                               --adbpath $ADB_PATH
 #                                                                               ...]
 if __name__ == '__main__':
-    main()
+    parser = parser_b2g()
+    options = parser.parse_args()
+    options.log = commandline.setup_logging("Remote XPCShell",
+                                            options,
+                                            {"tbpl": sys.stdout})
+    sys.exit(run_test_harnesS(parser, options)
--- a/testing/xpcshell/runxpcshelltests.py
+++ b/testing/xpcshell/runxpcshelltests.py
@@ -1474,28 +1474,26 @@ class XPCShellTests(object):
             self.log.error("TEST-UNEXPECTED-FAIL | Received SIGINT (control-C), so stopped run. " \
                            "(Use --keep-going to keep running tests after killing one with SIGINT)")
             return False
 
         self.log.suite_end()
         return self.failCount == 0
 
 
-def main():
-    parser = parser_desktop()
-    options = parser.parse_args()
-
-    log = commandline.setup_logging("XPCShell", options, {"tbpl": sys.stdout})
-
+def run_test_harness(parser, options):
     if options.xpcshell is None:
         print >> sys.stderr, """Must provide path to xpcshell using --xpcshell"""
-
-    xpcsh = XPCShellTests(log)
+        return 1
 
     if options.interactive and not options.testPath:
         print >>sys.stderr, "Error: You must specify a test filename in interactive mode!"
-        sys.exit(1)
+        return 1
 
-    if not xpcsh.runTests(**vars(options)):
-        sys.exit(1)
+    xpcsh = XPCShellTests(options.log)
+    return xpcsh.runTests(**vars(options))
+
 
 if __name__ == '__main__':
-    main()
+    parser = parser_desktop()
+    options = parser.parse_args()
+    options.log = commandline.setup_logging("XPCShell", options, {"tbpl": sys.stdout})
+    sys.exit(run_test_harness(parser, options))
--- a/testing/xpcshell/xpcshellcommandline.py
+++ b/testing/xpcshell/xpcshellcommandline.py
@@ -127,29 +127,31 @@ def add_remote_arguments(parser):
 
     parser.add_argument("--objdir", action="store", type=str, dest="objdir",
                         help="local objdir, containing xpcshell binaries")
 
 
     parser.add_argument("--apk", action="store", type=str, dest="localAPK",
                         help="local path to Fennec APK")
 
-
     parser.add_argument("--noSetup", action="store_false", dest="setup", default=True,
                         help="do not copy any files to device (to be used only if device is already setup)")
 
-    parser.add_argument("--local-lib-dir", action="store", type=str, dest="localLib",
+    parser.add_argument("--local-lib-dir", action="store", type=str, dest="localLib", default=None,
                         help="local path to library directory")
 
     parser.add_argument("--local-bin-dir", action="store", type=str, dest="localBin",
                         help="local path to bin directory")
 
     parser.add_argument("--remoteTestRoot", action="store", type=str, dest="remoteTestRoot",
                         help="remote directory to use as test root (eg. /mnt/sdcard/tests or /data/local/tests)")
 
+    parser.add_argument('--adbpath', action='store', type=str, dest='adb_path',
+                        default="adb", help="Path to adb")
+
 def add_b2g_arguments(parser):
     parser.add_argument('--b2gpath', action='store', type=str, dest='b2g_path',
                         help="Path to B2G repo or qemu dir")
 
     parser.add_argument('--emupath', action='store', type=str, dest='emu_path',
                         help="Path to emulator folder (if different "
                         "from b2gpath")