Bug 932362 - Create mach target for running b2g xpcshell tests, r=jgriffin
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Tue, 11 Mar 2014 10:08:25 -0400
changeset 190227 5b490c2f1fb4e5eb98755d35ad9509ce6bb03584
parent 190226 1f24f6d7a426092f3fc10cdbe3820339dc555121
child 190228 cbf30c2f215b665a6e0389ff55fd46205684d347
push id1
push usersledru@mozilla.com
push dateThu, 04 Dec 2014 17:57:20 +0000
reviewersjgriffin
bugs932362
milestone30.0a1
Bug 932362 - Create mach target for running b2g xpcshell tests, r=jgriffin
testing/xpcshell/mach_commands.py
testing/xpcshell/runtestsb2g.py
--- a/testing/xpcshell/mach_commands.py
+++ b/testing/xpcshell/mach_commands.py
@@ -6,31 +6,41 @@
 
 from __future__ import unicode_literals, print_function
 
 import mozpack.path
 import logging
 import os
 import shutil
 import sys
+import urllib2
 
 from StringIO import StringIO
 
 from mozbuild.base import (
     MachCommandBase,
     MozbuildObject,
     MachCommandConditions as conditions,
 )
 
 from mach.decorators import (
     CommandArgument,
     CommandProvider,
     Command,
 )
 
+ADB_NOT_FOUND = '''
+The %s command requires the adb binary to be on your path.
+
+If you have a B2G build, this can be found in
+'%s/out/host/<platform>/bin'.
+'''.lstrip()
+
+BUSYBOX_URL = 'http://www.busybox.net/downloads/binaries/latest/busybox-armv7l'
+
 
 if sys.version_info[0] < 3:
     unicode_type = unicode
 else:
     unicode_type = str
 
 # Simple filter to omit the message emitted as a test file begins.
 class TestStartFilter(logging.Filter):
@@ -269,19 +279,109 @@ class AndroidXPCShellRunner(MozbuildObje
                       mobileArgs=xpcshell.mobileArgs,
                       **options.__dict__)
 
         self.log_manager.terminal_handler.removeFilter(xpcshell_filter)
         self.log_manager.disable_unstructured()
 
         return int(not result)
 
+class B2GXPCShellRunner(MozbuildObject):
+    def __init__(self, *args, **kwargs):
+        MozbuildObject.__init__(self, *args, **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)
+
+        build_path = os.path.join(self.topsrcdir, 'build')
+        if build_path not in sys.path:
+            sys.path.append(build_path)
+
+        self.tests_dir = os.path.join(self.topobjdir, '_tests')
+        self.xpcshell_dir = os.path.join(self.tests_dir, 'xpcshell')
+        self.bin_dir = os.path.join(self.distdir, 'bin')
+
+    def _download_busybox(self, b2g_home):
+        system_bin = os.path.join(b2g_home, 'out', 'target', 'product', 'generic', 'system', 'bin')
+        busybox_path = os.path.join(system_bin, 'busybox')
+
+        if os.path.isfile(busybox_path):
+            return busybox_path
+
+        if not os.path.isdir(system_bin):
+            os.makedirs(system_bin)
+
+        try:
+            data = urllib2.urlopen(BUSYBOX_URL)
+        except urllib2.URLError:
+            print('There was a problem downloading busybox. Proceeding without it,' \
+                  'initial setup will be slow.')
+            return
+
+        with open(busybox_path, 'wb') as f:
+            f.write(data.read())
+        return busybox_path
+
+    def run_test(self, test_file, b2g_home=None, busybox=None,
+                 # ignore parameters from other platforms' options
+                 **kwargs):
+        try:
+            import which
+            which.which('adb')
+        except which.WhichError:
+            # TODO Find adb automatically if it isn't on the path
+            print(ADB_NOT_FOUND % ('mochitest-remote', b2g_home))
+            sys.exit(1)
+
+        test_path = None
+        if test_file:
+            test_path = self._wrap_path_argument(test_file).relpath()
+
+        import runtestsb2g
+        parser = runtestsb2g.B2GOptions()
+        options, args = parser.parse_args([])
+
+        options.b2g_path = b2g_home
+        options.busybox = busybox or os.environ.get('BUSYBOX')
+        options.emulator = 'arm'
+        options.localLib = self.bin_dir
+        options.localBin = self.bin_dir
+        options.logcat_dir = self.xpcshell_dir
+        options.manifest = os.path.join(self.xpcshell_dir, 'xpcshell_b2g.ini')
+        options.mozInfo = os.path.join(self.topobjdir, 'mozinfo.json')
+        options.objdir = self.topobjdir
+        options.symbolsPath = os.path.join(self.distdir, 'crashreporter-symbols'),
+        options.testingModulesDir = os.path.join(self.tests_dir, 'modules')
+        options.testsRootDir = self.xpcshell_dir
+        options.testPath = test_path
+        options.use_device_libs = True
+
+        if not options.busybox:
+            options.busybox = self._download_busybox(b2g_home)
+
+        return runtestsb2g.run_remote_xpcshell(parser, options, args)
+
+def is_platform_supported(cls):
+    """Must have a Firefox, Android or B2G build."""
+    return conditions.is_android(cls) or \
+           conditions.is_b2g(cls) or \
+           conditions.is_firefox(cls)
+
 @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))
+
     @Command('xpcshell-test', category='testing',
+        conditions=[is_platform_supported],
         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.')
     @CommandArgument("--debugger", default=None, metavar='DEBUGGER',
                      help = "Run xpcshell under the given debugger.")
     @CommandArgument("--debugger-args", default=None, metavar='ARGS', type=str,
                      dest = "debuggerArgs",
@@ -308,29 +408,34 @@ class MachCommands(MachCommandBase):
     @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')
+    @CommandArgument('--busybox', type=str, default=None,
+        help='(B2G) Path to busybox binary (speeds up installation of tests).')
     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)
 
         if conditions.is_android(self):
             xpcshell = self._spawn(AndroidXPCShellRunner)
+        elif conditions.is_b2g(self):
+            xpcshell = self._spawn(B2GXPCShellRunner)
+            params['b2g_home'] = self.b2g_home
         else:
             xpcshell = self._spawn(XPCShellRunner)
         xpcshell.cwd = self._mach_context.cwd
 
         try:
             return xpcshell.run_test(**params)
         except InvalidTestPathError as e:
             print(e.message)
--- a/testing/xpcshell/runtestsb2g.py
+++ b/testing/xpcshell/runtestsb2g.py
@@ -148,19 +148,17 @@ class B2GOptions(RemoteXPCShellOptions):
 
         if options.geckoPath and not options.emulator:
             self.error("You must specify --emulator if you specify --gecko-path")
 
         if options.logcat_dir and not options.emulator:
             self.error("You must specify --emulator if you specify --logcat-dir")
         return RemoteXPCShellOptions.verifyRemoteOptions(self, options)
 
-def main():
-    parser = B2GOptions()
-    options, args = parser.parse_args()
+def run_remote_xpcshell(parser, options, args):
     options = parser.verifyRemoteOptions(options)
 
     # Create the Marionette instance
     kwargs = {}
     if options.emulator:
         kwargs['emulator'] = options.emulator
         if options.no_window:
             kwargs['noWindow'] = True
@@ -207,16 +205,21 @@ def main():
                                  mobileArgs=xpcsh.mobileArgs,
                                  **options.__dict__):
             sys.exit(1)
     except:
         print "Automation Error: Exception caught while running tests"
         traceback.print_exc()
         sys.exit(1)
 
+def main():
+    parser = B2GOptions()
+    options, args = parser.parse_args()
+
+    run_remote_xpcshell(parser, options, args)
 
 # You usually run this like :
 # python runtestsb2g.py --emulator arm --b2gpath $B2GPATH --manifest $MANIFEST [--xre-path $MOZ_HOST_BIN
 #                                                                               --adbpath $ADB_PATH
 #                                                                               ...]
 #
 # For xUnit output you should also pass in --tests-root-dir ..objdir-gecko/_tests
 #                                          --xunit-file ..objdir_gecko/_tests/results.xml