Backed out 7 changesets (bug 1181516, bug 1198257, bug 1193257, bug 1194166, bug 1193223, bug 1193224, bug 1181520) for breaking android 4.0 debug reftests CLOSED TREE
authorWes Kocher <wkocher@mozilla.com>
Tue, 15 Sep 2015 08:52:19 -0700
changeset 262581 d90bfbc8688a
parent 262580 f274aac3256b
child 262582 a252f3d0a8c9
push id65065
push userkwierso@gmail.com
push date2015-09-15 15:52 +0000
treeherdermozilla-inbound@d90bfbc8688a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1181516, 1198257, 1193257, 1194166, 1193223, 1193224, 1181520
milestone43.0a1
backs out25f22b3433e0
a824847677c5
6cc19a8f1bb0
bf1271f6cfa0
75b3c43770bd
81a5cf4c8f19
db3691ee6fd1
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
Backed out 7 changesets (bug 1181516, bug 1198257, bug 1193257, bug 1194166, bug 1193223, bug 1193224, bug 1181520) for breaking android 4.0 debug reftests CLOSED TREE Backed out changeset 25f22b3433e0 (bug 1194166) Backed out changeset a824847677c5 (bug 1193257) Backed out changeset 6cc19a8f1bb0 (bug 1193224) Backed out changeset bf1271f6cfa0 (bug 1198257) Backed out changeset 75b3c43770bd (bug 1193223) Backed out changeset 81a5cf4c8f19 (bug 1181516) Backed out changeset db3691ee6fd1 (bug 1181520)
layout/tools/reftest/Makefile.in
layout/tools/reftest/b2g_desktop.py
layout/tools/reftest/mach_commands.py
layout/tools/reftest/reftest-cmdline.js
layout/tools/reftest/reftest.js
layout/tools/reftest/reftestcommandline.py
layout/tools/reftest/remotereftest.py
layout/tools/reftest/runreftest.py
layout/tools/reftest/runreftestb2g.py
testing/mach_commands.py
testing/mozharness/configs/android/android_panda_releng.py
testing/mozharness/configs/android/androidarm.py
testing/mozharness/configs/android/androidarm_4_3.py
testing/mozharness/configs/android/androidx86.py
testing/mozharness/configs/b2g/emulator_automation_config.py
testing/mozharness/configs/unittests/linux_unittest.py
testing/mozharness/configs/unittests/mac_unittest.py
testing/mozharness/configs/unittests/win_unittest.py
testing/mozharness/scripts/android_emulator_unittest.py
testing/mozharness/scripts/android_panda.py
testing/mozharness/scripts/androidx86_emulator_unittest.py
testing/mozharness/scripts/b2g_desktop_unittest.py
testing/mozharness/scripts/b2g_emulator_unittest.py
testing/mozharness/scripts/desktop_unittest.py
testing/testsuite-targets.mk
testing/xpcshell/Makefile.in
testing/xpcshell/mach_commands.py
testing/xpcshell/remotexpcshelltests.py
testing/xpcshell/runtestsb2g.py
testing/xpcshell/runxpcshelltests.py
testing/xpcshell/selftest.py
testing/xpcshell/xpcshellcommandline.py
--- a/layout/tools/reftest/Makefile.in
+++ b/layout/tools/reftest/Makefile.in
@@ -2,17 +2,16 @@
 # 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/.
 
 _DEST_DIR = $(DEPTH)/_tests/reftest
 
 _HARNESS_FILES = \
   $(srcdir)/runreftest.py \
-  $(srcdir)/reftestcommandline.py \
   $(srcdir)/remotereftest.py \
   $(srcdir)/runreftestb2g.py \
   $(srcdir)/b2g_desktop.py \
   $(srcdir)/gaia_lock_screen.js \
   automation.py \
   $(topsrcdir)/testing/mozbase/mozdevice/mozdevice/devicemanager.py \
   $(topsrcdir)/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py \
   $(topsrcdir)/testing/mozbase/mozdevice/mozdevice/devicemanagerSUT.py \
--- a/layout/tools/reftest/b2g_desktop.py
+++ b/layout/tools/reftest/b2g_desktop.py
@@ -4,17 +4,17 @@
 from __future__ import print_function, unicode_literals
 
 import os
 import signal
 import sys
 
 here = os.path.abspath(os.path.dirname(__file__))
 
-from runreftest import RefTest
+from runreftest import RefTest, ReftestOptions
 
 from marionette_driver import expected
 from marionette_driver.by import By
 from marionette_driver.marionette import Marionette
 from marionette_driver.wait import Wait
 
 from mozprocess import ProcessHandler
 from mozrunner import FirefoxRunner
@@ -46,20 +46,22 @@ class B2GDesktopReftest(RefTest):
         self.marionette.set_context(self.marionette.CONTEXT_CHROME)
 
         if os.path.isfile(self.test_script):
             f = open(self.test_script, 'r')
             self.test_script = f.read()
             f.close()
         self.marionette.execute_script(self.test_script)
 
-    def run_tests(self, tests, options):
-        manifests = self.resolver.resolveManifests(options, tests)
+    def run_tests(self, test_path, options):
+        reftestlist = self.getManifestPath(test_path)
+        if not reftestlist.startswith('file://'):
+            reftestlist = 'file://%s' % reftestlist
 
-        self.profile = self.create_profile(options, manifests,
+        self.profile = self.create_profile(options, reftestlist,
                                            profile_to_clone=options.profile)
         env = self.buildBrowserEnv(options, self.profile.profile)
         kp_kwargs = { 'processOutputLine': [self._on_output],
                       'onTimeout': [self._on_timeout],
                       'kill_on_timeout': False }
 
         if not options.debugger:
             if not options.timeout:
@@ -100,18 +102,18 @@ class B2GDesktopReftest(RefTest):
                          self.last_test, status)
         elif status < 0:
             log.info("%s | application killed with signal %s",
                          self.last_test, -status)
 
         log.info("%s | Running tests: end.", os.path.basename(__file__))
         return status
 
-    def create_profile(self, options, manifests, profile_to_clone=None):
-        profile = RefTest.createReftestProfile(self, options, manifests,
+    def create_profile(self, options, reftestlist, profile_to_clone=None):
+        profile = RefTest.createReftestProfile(self, options, reftestlist,
                                                profile_to_clone=profile_to_clone)
 
         prefs = {}
         # Turn off the locale picker screen
         prefs["browser.firstrun.show.localepicker"] = False
         if not self.build_type == "mulet":
             # FIXME: With Mulet we can't set this values since Gaia won't launch
             prefs["b2g.system_startup_url"] = \
@@ -129,16 +131,17 @@ class B2GDesktopReftest(RefTest):
         prefs["browser.newtabpage.directory.ping"] = ""
         prefs["dom.ipc.tabs.disabled"] = False
         prefs["dom.mozBrowserFramesEnabled"] = True
         prefs["font.size.inflation.emPerLine"] = 0
         prefs["font.size.inflation.minTwips"] = 0
         prefs["network.dns.localDomains"] = "app://test-container.gaiamobile.org"
         prefs["reftest.browser.iframe.enabled"] = False
         prefs["reftest.remote"] = False
+        prefs["reftest.uri"] = "%s" % reftestlist
         # Set a future policy version to avoid the telemetry prompt.
         prefs["toolkit.telemetry.prompted"] = 999
         prefs["toolkit.telemetry.notifiedOptOut"] = 999
         # Disable periodic updates of service workers
         prefs["dom.serviceWorkers.periodic-updates.enabled"] = False
 
         # Set the extra prefs.
         profile.set_preferences(prefs)
@@ -184,34 +187,36 @@ class MuletReftest(B2GDesktopReftest):
         self.marionette.switch_to_frame()
         self.marionette.execute_async_script('GaiaLockScreen.unlock()')
 
     def _wait_for_homescreen(self, timeout):
         log.info("Waiting for home screen to load")
         Wait(self.marionette, timeout).until(expected.element_present(
             By.CSS_SELECTOR, '#homescreen[loading-state=false]'))
 
-def run_desktop_reftests(parser, options):
+def run_desktop_reftests(parser, options, args):
     marionette_args = {}
     if options.marionette:
         host, port = options.marionette.split(':')
         marionette_args['host'] = host
         marionette_args['port'] = int(port)
 
     if options.mulet:
         reftest = MuletReftest(marionette_args)
     else:
         reftest = B2GDesktopReftest(marionette_args)
 
-    parser.validate(options, reftest)
+    options = ReftestOptions.verifyCommonOptions(parser, options, reftest)
+    if options == None:
+        sys.exit(1)
 
     # add a -bin suffix if b2g-bin exists, but just b2g was specified
     if options.app[-4:] != '-bin':
         if os.path.isfile("%s-bin" % options.app):
             options.app = "%s-bin" % options.app
 
     if options.xrePath is None:
         options.xrePath = os.path.dirname(options.app)
 
     if options.desktop and not options.profile:
         raise Exception("must specify --profile when specifying --desktop")
 
-    sys.exit(reftest.run_tests(options.tests, options))
+    sys.exit(reftest.run_tests(args[0], options))
--- a/layout/tools/reftest/mach_commands.py
+++ b/layout/tools/reftest/mach_commands.py
@@ -18,17 +18,18 @@ from mozbuild.base import (
 )
 
 from mach.decorators import (
     CommandArgument,
     CommandProvider,
     Command,
 )
 
-import reftestcommandline
+
+DEBUGGER_HELP = 'Debugger binary to run test in. Program name or path.'
 
 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()
 
@@ -75,215 +76,383 @@ class ReftestRunner(MozbuildObject):
         # 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)
 
         self.tests_dir = os.path.join(self.topobjdir, '_tests')
         self.reftest_dir = os.path.join(self.tests_dir, 'reftest')
 
+    def _manifest_file(self, suite):
+        """Returns the manifest file used for a given test suite."""
+        files = {
+            'reftest': 'reftest.list',
+            'reftest-ipc': 'reftest.list',
+            'crashtest': 'crashtests.list',
+            'crashtest-ipc': 'crashtests.list',
+            'jstestbrowser': 'jstests.list'
+        }
+        assert suite in files
+        return files[suite]
+
+    def _find_manifest(self, suite, test_file):
+        """Return a tuple of (manifest-path, filter-string) for running test_file.
+
+        test_file can be a relative path to a single test file or manifest from
+        the top source directory, an absolute path to the same, or a directory
+        containing a manifest.
+        """
+        assert test_file
+        path_arg = self._wrap_path_argument(test_file)
+        relpath = path_arg.relpath()
+
+        if os.path.isdir(path_arg.srcdir_path()):
+            return (mozpath.join(relpath, self._manifest_file(suite)), None)
+
+        if relpath.endswith('.list'):
+            return (relpath, None)
+
+        return (self._find_manifest(suite, mozpath.dirname(test_file))[0],
+                mozpath.basename(test_file))
+
     def _make_shell_string(self, s):
         return "'%s'" % re.sub("'", r"'\''", s)
 
-    def run_b2g_test(self, b2g_home=None, xre_path=None, **kwargs):
+    def run_b2g_test(self, b2g_home=None, xre_path=None, test_file=None,
+                     suite=None, filter=None, **kwargs):
         """Runs a b2g reftest.
 
         filter is a regular expression (in JS syntax, as could be passed to the
         RegExp constructor) to select which reftests to run from the manifest.
 
         test_file is a path to a test file. It can be a relative path from the
         top source directory, an absolute filename, or a directory containing
         test files.
 
         suite is the type of reftest to run. It can be one of ('reftest',
         'crashtest').
         """
-        if kwargs["suite"] not in ('reftest', 'crashtest'):
+        if suite not in ('reftest', 'crashtest'):
             raise Exception('None or unrecognized reftest suite type.')
 
-        sys.path.insert(0, self.reftest_dir)
+        # Find the manifest file
+        if not test_file:
+            if suite == 'reftest':
+                test_file = mozpath.join('layout', 'reftests')
+            elif suite == 'crashtest':
+                test_file = mozpath.join('testing', 'crashtest')
 
-        test_subdir = {"reftest": os.path.join('layout', 'reftests'),
-                       "crashtest": os.path.join('layout', 'crashtest')}[kwargs["suite"]]
+        if not os.path.exists(os.path.join(self.topsrcdir, test_file)):
+            test_file = mozpath.relpath(os.path.abspath(test_file),
+                                             self.topsrcdir)
+
+        (manifest, single_file_filter) = self._find_manifest(suite, test_file)
+        if not os.path.exists(mozpath.join(self.topsrcdir, manifest)):
+            raise Exception('No manifest file was found at %s.' % manifest)
+        if single_file_filter:
+            if filter:
+                raise Exception('Cannot run single files in conjunction with --filter')
+            filter = single_file_filter
 
-        # Find the manifest file
-        if not kwargs["tests"]:
-            if not os.path.exists(os.path.join(self.topsrcdir, test_subdir)):
-                test_file = mozpath.relpath(os.path.abspath(test_subdir),
-                                            self.topsrcdir)
-            kwargs["tests"] = [test_subdir]
+        # Need to chdir to reftest_dir otherwise imports fail below.
+        os.chdir(self.reftest_dir)
+
+        # The imp module can spew warnings if the modules below have
+        # already been imported, ignore them.
+        with warnings.catch_warnings():
+            warnings.simplefilter('ignore')
 
+            import imp
+            path = os.path.join(self.reftest_dir, 'runreftestb2g.py')
+            with open(path, 'r') as fh:
+                imp.load_module('reftest', fh, path, ('.py', 'r', imp.PY_SOURCE))
+            import reftest
+
+        # Set up the reftest options.
+        parser = reftest.B2GOptions()
+        options, args = parser.parse_args([])
+
+        # Tests need to be served from a subdirectory of the server. Symlink
+        # topsrcdir here to get around this.
         tests = os.path.join(self.reftest_dir, 'tests')
         if not os.path.isdir(tests):
             os.symlink(self.topsrcdir, tests)
+        args.insert(0, os.path.join('tests', manifest))
 
-        for i, path in enumerate(kwargs["tests"]):
-            # Non-absolute paths are relative to the packaged directory, which
-            # has an extra tests/ at the start
-            if os.path.exists(os.path.abspath(path)):
-                path = os.path.relpath(path, os.path.join(self.topsrcdir))
-            kwargs["tests"][i] = os.path.join('tests', path)
+        for k, v in kwargs.iteritems():
+            setattr(options, k, v)
 
         if conditions.is_b2g_desktop(self):
-            return self.run_b2g_desktop(**kwargs)
-
-        return self.run_b2g_remote(b2g_home, xre_path, **kwargs)
+            if self.substs.get('ENABLE_MARIONETTE') != '1':
+                print(MARIONETTE_DISABLED % ('mochitest-b2g-desktop',
+                                             self.mozconfig['path']))
+                return 1
 
-    def run_b2g_desktop(self, **kwargs):
-        if self.substs.get('ENABLE_MARIONETTE') != '1':
-            print(MARIONETTE_DISABLED % ('mochitest-b2g-desktop',
-                                         self.mozconfig['path']))
-            return 1
-
-        if not kwargs["profile"]:
-            gaia_profile = os.environ.get('GAIA_PROFILE')
-            if not gaia_profile:
+            options.profile = options.profile or os.environ.get('GAIA_PROFILE')
+            if not options.profile:
                 print(GAIA_PROFILE_NOT_FOUND % 'reftest-b2g-desktop')
                 return 1
-            kwargs["profile"] = gaia_profile
 
-
-        if os.path.isfile(os.path.join(kwargs["profile"], 'extensions',
-                                       'httpd@gaiamobile.org')):
-            print(GAIA_PROFILE_IS_DEBUG % ('mochitest-b2g-desktop',
-                                           kwargs["profile"]))
-            return 1
+            if os.path.isfile(os.path.join(options.profile, 'extensions', \
+                    'httpd@gaiamobile.org')):
+                print(GAIA_PROFILE_IS_DEBUG % ('mochitest-b2g-desktop',
+                                               options.profile))
+                return 1
 
-        kwargs["desktop"] = True
-        kwargs["app"] = self.get_binary_path()
-        if kwargs["oop"]:
-            options.browser_arg = '-oop'
-        if not kwargs["app"].endswith('-bin'):
-            kwargs["app"] = '%s-bin' % options.app
-        if not os.path.isfile(kwargs["app"]):
-            options.app = kwargs["app"][:-len('-bin')]
+            options.desktop = True
+            options.app = self.get_binary_path()
+            if options.oop:
+                options.browser_arg = '-oop'
+            if not options.app.endswith('-bin'):
+                options.app = '%s-bin' % options.app
+            if not os.path.isfile(options.app):
+                options.app = options.app[:-len('-bin')]
 
-        return runreftestb2g.run(**kwargs)
+            return reftest.run_desktop_reftests(parser, options, args)
 
-    def run_b2g_remote(self, b2g_home, xre_path, **kwargs):
-        import runreftestb2g
 
         try:
             which.which('adb')
         except which.WhichError:
             # TODO Find adb automatically if it isn't on the path
-            raise Exception(ADB_NOT_FOUND % ('%s-remote' % kwargs["suite"], b2g_home))
+            raise Exception(ADB_NOT_FOUND % ('%s-remote' % suite, b2g_home))
 
-        kwargs["b2gPath"] = b2g_home
-        kwargs["logdir"] = self.reftest_dir
-        kwargs["httpdPath"] = os.path.join(self.topsrcdir, 'netwerk', 'test', 'httpserver')
-        kwargs["xrePath"] = xre_path
-        kwargs["ignoreWindowSize"] = True
+        options.b2gPath = b2g_home
+        options.logdir = self.reftest_dir
+        options.httpdPath = os.path.join(self.topsrcdir, 'netwerk', 'test', 'httpserver')
+        options.xrePath = xre_path
+        options.ignoreWindowSize = True
+        options.filter = filter
 
         # Don't enable oop for crashtest until they run oop in automation
-        if kwargs["suite"] == 'reftest':
-            kwargs["oop"] = True
+        if suite == 'reftest':
+            options.oop = True
+
+        return reftest.run_remote_reftests(parser, options, args)
 
-        return runreftestb2g.run_remote(**kwargs)
+    def run_desktop_test(self, test_file=None, filter=None, suite=None,
+            debugger=None, debugger_args=None, parallel=False, shuffle=False,
+            e10s=False, extraPrefs=None, this_chunk=None, total_chunks=None):
+        """Runs a reftest.
+
+        test_file is a path to a test file. It can be a relative path from the
+        top source directory, an absolute filename, or a directory containing
+        test files.
 
-    def run_desktop_test(self, **kwargs):
-        """Runs a reftest."""
-        import runreftest
+        filter is a regular expression (in JS syntax, as could be passed to the
+        RegExp constructor) to select which reftests to run from the manifest.
+
+        suite is the type of reftest to run. It can be one of ('reftest',
+        'crashtest', 'jstestbrowser').
+
+        debugger is the program name (in $PATH) or the full path of the
+        debugger to run.
 
-        if kwargs["suite"] not in ('reftest', 'crashtest', 'jstestbrowser'):
+        debugger_args are the arguments passed to the debugger.
+
+        parallel indicates whether tests should be run in parallel or not.
+
+        shuffle indicates whether to run tests in random order.
+        """
+
+        if suite not in ('reftest', 'reftest-ipc', 'crashtest', 'crashtest-ipc', 'jstestbrowser'):
             raise Exception('None or unrecognized reftest suite type.')
 
-        default_manifest = {
-            "reftest": (self.topsrcdir, "layout", "reftests", "reftest.list"),
-            "crashtest": (self.topsrcdir, "testing", "crashtest", "crashtests.list"),
-            "jstestbrowser": (self.topobjdir, "dist", "test-stage", "jsreftest", "tests",
-                              "jstests.list")
-        }
+        env = {}
+        extra_args = []
 
-        kwargs["extraProfileFiles"] = [os.path.join(self.topobjdir, "dist", "plugins")]
-        kwargs["symbolsPath"] = os.path.join(self.topobjdir, "crashreporter-symbols")
+        if test_file:
+            (path, single_file_filter) = self._find_manifest(suite, test_file)
+            if not os.path.exists(mozpath.join(self.topsrcdir, path)):
+                raise Exception('No manifest file was found at %s.' % path)
+            if single_file_filter:
+                if filter:
+                    raise Exception('Cannot run single files in conjunction with --filter')
+                filter = single_file_filter
+            env[b'TEST_PATH'] = path
+        if filter:
+            extra_args.extend(['--filter', self._make_shell_string(filter)])
 
-        if not kwargs["tests"]:
-            kwargs["tests"] = [os.path.join(*default_manifest[kwargs["suite"]])]
+        pass_thru = False
 
-        if kwargs["suite"] == "jstestbrowser":
-            kwargs["extraProfileFiles"].append(os.path.join(self.topobjdir, "dist",
-                                                            "test-stage", "jsreftest",
-                                                            "tests", "user.js"))
+        if debugger:
+            extra_args.append('--debugger=\'%s\'' % debugger)
+            pass_thru = True
+            if debugger_args:
+                # Use _make_shell_string (which quotes) so that we
+                # handle multiple args being passed to the debugger.
+                extra_args.extend(['--debugger-args', self._make_shell_string(debugger_args)])
+        else:
+            if debugger_args:
+                print("--debugger-args passed, but no debugger specified.")
+                return 1
 
-        if not kwargs["runTestsInParallel"]:
-            kwargs["logFile"] = "%s.log" % kwargs["suite"]
+        if parallel:
+            extra_args.append('--run-tests-in-parallel')
+
+        if shuffle:
+            extra_args.append('--shuffle')
 
-        #Remove the stdout handler from the internal logger and let mach deal with it
-        runreftest.log.removeHandler(runreftest.log.handlers[0])
-        self.log_manager.enable_unstructured()
-        rv = runreftest.run(**kwargs)
-        self.log_manager.disable_unstructured()
+        if e10s:
+            extra_args.append('--e10s')
+
+        if extraPrefs:
+            for pref in extraPrefs:
+                extra_args.extend(['--setpref', pref])
+
+        if this_chunk:
+            extra_args.append('--this-chunk=%s' % this_chunk)
 
-        return rv
+        if total_chunks:
+            extra_args.append('--total-chunks=%s' % total_chunks)
+
+        if extra_args:
+            args = [os.environ.get(b'EXTRA_TEST_ARGS', '')]
+            args.extend(extra_args)
+            env[b'EXTRA_TEST_ARGS'] = ' '.join(args)
+
+        # TODO hook up harness via native Python
+        return self._run_make(directory='.', target=suite, append_env=env,
+            pass_thru=pass_thru, ensure_exit_code=False)
 
 
-def process_test_objects(kwargs):
-    """|mach test| works by providing a test_objects argument, from
-    which the test path must be extracted and converted into a normal
-    reftest tests argument."""
+def ReftestCommand(func):
+    """Decorator that adds shared command arguments to reftest commands."""
+
+    debugger = CommandArgument('--debugger', metavar='DEBUGGER',
+        help=DEBUGGER_HELP)
+    func = debugger(func)
+
+    debugger_args = CommandArgument('--debugger-args', metavar='DEBUGGER_ARGS',
+        help='Arguments to pass to the debugger.')
+    func = debugger_args(func)
+
+    flter = CommandArgument('--filter', metavar='REGEX',
+        help='A JS regular expression to match test URLs against, to select '
+             'a subset of tests to run.')
+    func = flter(func)
+
+    path = CommandArgument('test_file', nargs='?', metavar='MANIFEST',
+        help='Reftest manifest file, or a directory in which to select '
+             'reftest.list. If omitted, the entire test suite is executed.')
+    func = path(func)
+
+    parallel = CommandArgument('--parallel', action='store_true',
+        help='Run tests in parallel.')
+    func = parallel(func)
+
+    shuffle = CommandArgument('--shuffle', action='store_true',
+        help='Run tests in random order.')
+    func = shuffle(func)
+
+    e10s = CommandArgument('--e10s', action='store_true',
+        help='Use content processes.')
+    func = e10s(func)
+
+    extraPrefs = CommandArgument('--setpref', action='append',
+        default=[], dest='extraPrefs', metavar='PREF=VALUE',
+        help='Set prefs in the reftest profile.')
+    func = extraPrefs(func)
+
+    totalChunks = CommandArgument('--total-chunks',
+        help = 'How many chunks to split the tests up into.')
+    func = totalChunks(func)
+
+    thisChunk = CommandArgument('--this-chunk',
+        help = 'Which chunk to run between 1 and --total-chunks.')
+    func = thisChunk(func)
+
+    return func
 
-    if "test_objects" in kwargs:
-        if kwargs["tests"] is None:
-            kwargs["tests"] = []
-        kwargs["tests"].extend(item["path"] for item in kwargs["test_objects"])
-        del kwargs["test_objects"]
+def B2GCommand(func):
+    """Decorator that adds shared command arguments to b2g reftest commands."""
+
+    busybox = CommandArgument('--busybox', default=None,
+        help='Path to busybox binary to install on device')
+    func = busybox(func)
+
+    logdir = CommandArgument('--logdir', default=None,
+        help='directory to store log files')
+    func = logdir(func)
+
+    sdcard = CommandArgument('--sdcard', default="10MB",
+        help='Define size of sdcard: 1MB, 50MB...etc')
+    func = sdcard(func)
+
+    emulator_res = CommandArgument('--emulator-res', default='800x1000',
+        help='Emulator resolution of the format \'<width>x<height>\'')
+    func = emulator_res(func)
+
+    marionette = CommandArgument('--marionette', default=None,
+        help='host:port to use when connecting to Marionette')
+    func = marionette(func)
+
+    totalChunks = CommandArgument('--total-chunks', dest='totalChunks',
+        type = int,
+        help = 'How many chunks to split the tests up into.')
+    func = totalChunks(func)
+
+    thisChunk = CommandArgument('--this-chunk', dest='thisChunk',
+        type = int,
+        help = 'Which chunk to run between 1 and --total-chunks.')
+    func = thisChunk(func)
+
+    flter = CommandArgument('--filter', metavar='REGEX',
+        help='A JS regular expression to match test URLs against, to select '
+             'a subset of tests to run.')
+    func = flter(func)
+
+    oop = CommandArgument('--enable-oop', action='store_true', dest='oop',
+        help = 'Run tests in out-of-process mode.')
+    func = oop(func)
+
+    path = CommandArgument('test_file', default=None, nargs='?',
+        metavar='TEST',
+        help='Test to run. Can be specified as a single file, a ' \
+            'directory, or omitted. If omitted, the entire test suite is ' \
+            'executed.')
+    func = path(func)
+
+    return func
 
 
 @CommandProvider
 class MachCommands(MachCommandBase):
-    @Command('reftest',
-             category='testing',
-             description='Run reftests (layout and graphics correctness).',
-             parser=reftestcommandline.DesktopArgumentsParser)
-    def run_reftest(self, **kwargs):
-        kwargs["suite"] = "reftest"
-        return self._run_reftest(**kwargs)
+    @Command('reftest', category='testing', description='Run reftests (layout and graphics correctness).')
+    @ReftestCommand
+    def run_reftest(self, test_file, **kwargs):
+        return self._run_reftest(test_file, suite='reftest', **kwargs)
 
-    @Command('jstestbrowser',
-             category='testing',
-             description='Run js/src/tests in the browser.',
-             parser=reftestcommandline.DesktopArgumentsParser)
-    def run_jstestbrowser(self, **kwargs):
-        self._mach_context.commands.dispatch("build",
-                                             self._mach_context,
-                                             what=["stage-jstests"])
-        kwargs["suite"] = "jstestbrowser"
-        return self._run_reftest(**kwargs)
+    @Command('jstestbrowser', category='testing',
+        description='Run js/src/tests in the browser.')
+    @ReftestCommand
+    def run_jstestbrowser(self, test_file, **kwargs):
+        return self._run_reftest(test_file, suite='jstestbrowser', **kwargs)
 
-    @Command('reftest-ipc',
-             category='testing',
-             description='Run IPC reftests (layout and graphics correctness, separate process).',
-             parser=reftestcommandline.DesktopArgumentsParser)
-    def run_ipc(self, **kwargs):
-        kwargs["ipc"] = True
-        kwargs["suite"] = "reftest"
-        return self._run_reftest(**kwargs)
+    @Command('reftest-ipc', category='testing',
+        description='Run IPC reftests (layout and graphics correctness, separate process).')
+    @ReftestCommand
+    def run_ipc(self, test_file, **kwargs):
+        return self._run_reftest(test_file, suite='reftest-ipc', **kwargs)
 
-    @Command('crashtest',
-             category='testing',
-             description='Run crashtests (Check if crashes on a page).',
-             parser=reftestcommandline.DesktopArgumentsParser)
-    def run_crashtest(self, **kwargs):
-        kwargs["suite"] = "crashtest"
-        return self._run_reftest(**kwargs)
+    @Command('crashtest', category='testing',
+        description='Run crashtests (Check if crashes on a page).')
+    @ReftestCommand
+    def run_crashtest(self, test_file, **kwargs):
+        return self._run_reftest(test_file, suite='crashtest', **kwargs)
 
-    @Command('crashtest-ipc',
-             category='testing',
-             description='Run IPC crashtests (Check if crashes on a page, separate process).',
-             parser=reftestcommandline.DesktopArgumentsParser)
-    def run_crashtest_ipc(self, **kwargs):
-        kwargs["ipc"] = True
-        kwargs["suite"] = "crashtest"
-        return self._run_reftest(**kwargs)
+    @Command('crashtest-ipc', category='testing',
+        description='Run IPC crashtests (Check if crashes on a page, separate process).')
+    @ReftestCommand
+    def run_crashtest_ipc(self, test_file, **kwargs):
+        return self._run_reftest(test_file, suite='crashtest-ipc', **kwargs)
 
-    def _run_reftest(self, **kwargs):
-        process_test_objects(kwargs)
+    def _run_reftest(self, test_file=None, suite=None, **kwargs):
         reftest = self._spawn(ReftestRunner)
-        return reftest.run_desktop_test(**kwargs)
+        return reftest.run_desktop_test(test_file, suite=suite, **kwargs)
 
 
 # TODO For now b2g commands will only work with the emulator,
 # they should be modified to work with all devices.
 def is_emulator(cls):
     """Emulator needs to be configured."""
     return cls.device_name.startswith('emulator')
 
@@ -292,42 +461,39 @@ def is_emulator(cls):
 class B2GCommands(MachCommandBase):
     def __init__(self, context):
         MachCommandBase.__init__(self, context)
 
         for attr in ('b2g_home', 'xre_path', 'device_name'):
             setattr(self, attr, getattr(context, attr, None))
 
     @Command('reftest-remote', category='testing',
-             description='Run a remote reftest (b2g layout and graphics correctness, remote device).',
-             conditions=[conditions.is_b2g, is_emulator],
-             parser=reftestcommandline.B2GArgumentParser)
-    def run_reftest_remote(self, **kwargs):
-        kwargs["suite"] = "reftest"
-        return self._run_reftest(**kwargs)
+        description='Run a remote reftest (b2g layout and graphics correctness, remote device).',
+        conditions=[conditions.is_b2g, is_emulator])
+    @B2GCommand
+    def run_reftest_remote(self, test_file, **kwargs):
+        return self._run_reftest(test_file, suite='reftest', **kwargs)
 
     @Command('reftest-b2g-desktop', category='testing',
-             description='Run a b2g desktop reftest (b2g desktop layout and graphics correctness).',
-             conditions=[conditions.is_b2g_desktop],
-             parser=reftestcommandline.B2GArgumentParser)
-    def run_reftest_b2g_desktop(self, **kwargs):
-        kwargs["suite"] = "reftest"
-        return self._run_reftest(**kwargs)
+        description='Run a b2g desktop reftest (b2g desktop layout and graphics correctness).',
+        conditions=[conditions.is_b2g_desktop])
+    @B2GCommand
+    def run_reftest_b2g_desktop(self, test_file, **kwargs):
+        return self._run_reftest(test_file, suite='reftest', **kwargs)
 
     @Command('crashtest-remote', category='testing',
-             description='Run a remote crashtest (Check if b2g crashes on a page, remote device).',
-             conditions=[conditions.is_b2g, is_emulator],
-             parser=reftestcommandline.B2GArgumentParser)
+        description='Run a remote crashtest (Check if b2g crashes on a page, remote device).',
+        conditions=[conditions.is_b2g, is_emulator])
+    @B2GCommand
     def run_crashtest_remote(self, test_file, **kwargs):
-        kwargs["suite"] = "crashtest"
-        return self._run_reftest(**kwargs)
+        return self._run_reftest(test_file, suite='crashtest', **kwargs)
 
-    def _run_reftest(self, **kwargs):
-        process_test_objects(kwargs)
+    def _run_reftest(self, test_file=None, suite=None, **kwargs):
         if self.device_name:
             if self.device_name.startswith('emulator'):
                 emulator = 'arm'
                 if 'x86' in self.device_name:
                     emulator = 'x86'
                 kwargs['emulator'] = emulator
 
         reftest = self._spawn(ReftestRunner)
-        return reftest.run_b2g_test(self.b2g_home, self.xre_path, **kwargs)
+        return reftest.run_b2g_test(self.b2g_home, self.xre_path,
+            test_file, suite=suite, **kwargs)
--- a/layout/tools/reftest/reftest-cmdline.js
+++ b/layout/tools/reftest/reftest-cmdline.js
@@ -16,16 +16,47 @@ RefTestCmdLineHandler.prototype =
 {
   classID: Components.ID('{32530271-8c1b-4b7d-a812-218e42c6bb23}'),
 
   /* nsISupports */
   QueryInterface: XPCOMUtils.generateQI([nsICommandLineHandler]),
 
   /* nsICommandLineHandler */
   handle : function handler_handle(cmdLine) {
+    var args = { };
+    args.wrappedJSObject = args;
+    try {
+      var uristr = cmdLine.handleFlagWithParam("reftest", false);
+      if (uristr == null)
+        return;
+      try {
+        args.uri = cmdLine.resolveURI(uristr).spec;
+      }
+      catch (e) {
+        return;
+      }
+    }
+    catch (e) {
+      cmdLine.handleFlag("reftest", true);
+    }
+
+    try {
+      var nocache = cmdLine.handleFlag("reftestnocache", false);
+      args.nocache = nocache;
+    }
+    catch (e) {
+    }
+
+    try {
+      var skipslowtests = cmdLine.handleFlag("reftestskipslowtests", false);
+      args.skipslowtests = skipslowtests;
+    }
+    catch (e) {
+    }
+
     /* Ignore the platform's online/offline status while running reftests. */
     var ios = Components.classes["@mozilla.org/network/io-service;1"]
               .getService(Components.interfaces.nsIIOService2);
     ios.manageOfflineStatus = false;
     ios.offline = false;
 
     /**
      * Manipulate preferences by adding to the *default* branch.  Adding
@@ -42,17 +73,17 @@ RefTestCmdLineHandler.prototype =
 
 #include reftest-preferences.js
 
     var wwatch = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
                            .getService(nsIWindowWatcher);
 
     function loadReftests() {
       wwatch.openWindow(null, "chrome://reftest/content/reftest.xul", "_blank",
-                        "chrome,dialog=no,all", {});
+                        "chrome,dialog=no,all", args);
     }
 
     var remote = false;
     try {
       remote = prefs.getBoolPref("reftest.remote");
     } catch (ex) {
     }
 
--- a/layout/tools/reftest/reftest.js
+++ b/layout/tools/reftest/reftest.js
@@ -43,17 +43,17 @@ CU.import("resource://gre/modules/Servic
 var gLoadTimeout = 0;
 var gTimeoutHook = null;
 var gRemote = false;
 var gIgnoreWindowSize = false;
 var gShuffle = false;
 var gTotalChunks = 0;
 var gThisChunk = 0;
 var gContainingWindow = null;
-var gURLFilterRegex = {};
+var gURLFilterRegex = null;
 const FOCUS_FILTER_ALL_TESTS = "all";
 const FOCUS_FILTER_NEEDS_FOCUS_TESTS = "needs-focus";
 const FOCUS_FILTER_NON_NEEDS_FOCUS_TESTS = "non-needs-focus";
 var gFocusFilterMode = FOCUS_FILTER_ALL_TESTS;
 
 // "<!--CLEAR-->"
 const BLANK_URL_FOR_CLEARING = "data:text/html;charset=UTF-8,%3C%21%2D%2DCLEAR%2D%2D%3E";
 
@@ -64,17 +64,16 @@ var gB2GisMulet;                // bool
 // Are we using <iframe mozbrowser>?
 var gBrowserIsIframe;           // bool
 var gBrowserMessageManager;
 var gCanvas1, gCanvas2;
 // gCurrentCanvas is non-null between InitCurrentCanvasWithSnapshot and the next
 // RecordResult.
 var gCurrentCanvas = null;
 var gURLs;
-var gManifestsLoaded = {};
 // Map from URI spec to the number of times it remains to be used
 var gURIUseCounts;
 // Map from URI spec to the canvas rendered for that URI
 var gURICanvases;
 var gTestResults = {
   // Successful...
   Pass: 0,
   LoadOnly: 0,
@@ -386,16 +385,20 @@ function InitAndStartRefTests()
         gThisChunk = prefs.getIntPref("reftest.thisChunk");
     }
     catch(e) {
         gTotalChunks = 0;
         gThisChunk = 0;
     }
 
     try {
+        gURLFilterRegex = new RegExp(prefs.getCharPref("reftest.filter"));
+    } catch(e) {}
+
+    try {
         gFocusFilterMode = prefs.getCharPref("reftest.focusFilterMode");
     } catch(e) {}
 
     gWindowUtils = gContainingWindow.QueryInterface(CI.nsIInterfaceRequestor).getInterface(CI.nsIDOMWindowUtils);
     if (!gWindowUtils || !gWindowUtils.compareCanvases)
         throw "nsIDOMWindowUtils inteface missing";
 
     gIOService = CC[IO_SERVICE_CONTRACTID].getService(CI.nsIIOService);
@@ -441,17 +444,18 @@ function Shuffle(array)
         var temp = array[i];
         array[i] = array[j];
         array[j] = temp;
     }
 }
 
 function StartTests()
 {
-    var manifests;
+    var uri;
+#if BOOTSTRAP
     /* These prefs are optional, so we don't need to spit an error to the log */
     try {
         var prefs = Components.classes["@mozilla.org/preferences-service;1"].
                     getService(Components.interfaces.nsIPrefBranch);
     } catch(e) {
         gDumpLog("REFTEST TEST-UNEXPECTED-FAIL | | EXCEPTION: " + e + "\n");
     }
 
@@ -468,42 +472,51 @@ function StartTests()
     }
 
     try {
         gRunSlowTests = prefs.getIntPref("reftest.skipslowtests");
     } catch(e) {
         gRunSlowTests = false;
     }
 
+    try {
+        uri = prefs.getCharPref("reftest.uri");
+    } catch(e) {
+        uri = "";
+    }
+
+    if (uri == "") {
+        gDumpLog("REFTEST TEST-UNEXPECTED-FAIL | | Unable to find reftest.uri pref.  Please ensure your profile is setup properly\n");
+        DoneTests();
+    }
+#else
+    try {
+        // Need to read the manifest once we have gHttpServerPort..
+        var args = window.arguments[0].wrappedJSObject;
+
+        if ("nocache" in args && args["nocache"])
+            gNoCanvasCache = true;
+
+        if ("skipslowtests" in args && args.skipslowtests)
+            gRunSlowTests = false;
+
+        uri = args.uri;
+    } catch (e) {
+        ++gTestResults.Exception;
+        gDumpLog("REFTEST TEST-UNEXPECTED-FAIL | | EXCEPTION: " + ex + "\n");
+        DoneTests();
+    }
+#endif
+
     if (gShuffle) {
         gNoCanvasCache = true;
     }
 
-    gURLs = [];
-
     try {
-        var manifests = JSON.parse(prefs.getCharPref("reftest.manifests"));
-        gURLFilterRegex = manifests[null];
-    } catch(e) {
-        gDumpLog("REFTEST TEST-UNEXPECTED-FAIL | | Unable to find reftest.manifests pref.  Please ensure your profile is setup properly\n");
-        DoneTests();
-    }
-
-    try {
-        var globalFilter = manifests.hasOwnProperty("") ? new RegExp(manifests[""]) : null;
-        var manifestURLs = Object.keys(manifests);
-
-        // Ensure we read manifests from higher up the directory tree first so that we
-        // process includes before reading the included manifest again
-        manifestURLs.sort(function(a,b) {return a.length - b.length})
-        manifestURLs.forEach(function(manifestURL) {
-            gDumpLog("Readings manifest" + manifestURL + "\n");
-            var filter = manifests[manifestURL] ? new RegExp(manifests[manifestURL]) : null;
-            ReadTopManifest(manifestURL, [globalFilter, filter, false]);
-        });
+        ReadTopManifest(uri);
         BuildUseCounts();
 
         // Filter tests which will be skipped to get a more even distribution when chunking
         // tURLs is a temporary array containing all active tests
         var tURLs = new Array();
         for (var i = 0; i < gURLs.length; ++i) {
             if (gURLs[i].expected == EXPECTED_DEATH)
                 continue;
@@ -746,60 +759,42 @@ function AddPrefSettings(aWhere, aPrefNa
         aTestPrefSettings.push(setting);
     }
     if (aWhere != "test-") {
         aRefPrefSettings.push(setting);
     }
     return true;
 }
 
-function ReadTopManifest(aFileURL, aFilter)
+function ReadTopManifest(aFileURL)
 {
+    gURLs = new Array();
     var url = gIOService.newURI(aFileURL, null, null);
     if (!url)
         throw "Expected a file or http URL for the manifest.";
-    ReadManifest(url, EXPECTED_PASS, aFilter);
+    ReadManifest(url, EXPECTED_PASS);
 }
 
-function AddTestItem(aTest, aFilter)
+function AddTestItem(aTest)
 {
-    if (!aFilter)
-        aFilter = [null, [], false];
-
-    globalFilter = aFilter[0];
-    manifestFilter = aFilter[1];
-    invertManifest = aFilter[2];
-    if ((globalFilter && !globalFilter.test(aTest.url1.spec)) ||
-        (manifestFilter &&
-         !(invertManifest ^ manifestFilter.test(aTest.url1.spec))))
+    if (gURLFilterRegex && !gURLFilterRegex.test(aTest.url1.spec))
         return;
     if (gFocusFilterMode == FOCUS_FILTER_NEEDS_FOCUS_TESTS &&
         !aTest.needsFocus)
         return;
     if (gFocusFilterMode == FOCUS_FILTER_NON_NEEDS_FOCUS_TESTS &&
         aTest.needsFocus)
         return;
     gURLs.push(aTest);
 }
 
 // Note: If you materially change the reftest manifest parsing,
 // please keep the parser in print-manifest-dirs.py in sync.
-function ReadManifest(aURL, inherited_status, aFilter)
+function ReadManifest(aURL, inherited_status)
 {
-    // Ensure each manifest is only read once. This assumes that manifests that are
-    // included with an unusual inherited_status or filters will be read via their
-    // include before they are read directly in the case of a duplicate
-    if (gManifestsLoaded.hasOwnProperty(aURL.spec)) {
-        if (gManifestsLoaded[aURL.spec] === null)
-            return;
-        else
-            aFilter = [aFilter[0], aFilter[1], true];
-    }
-    gManifestsLoaded[aURL.spec] = aFilter[1];
-
     var secMan = CC[NS_SCRIPTSECURITYMANAGER_CONTRACTID]
                      .getService(CI.nsIScriptSecurityManager);
 
     var listURL = aURL;
     var channel = gIOService.newChannelFromURI2(aURL,
                                                 null,      // aLoadingNode
                                                 Services.scriptSecurityManager.getSystemPrincipal(),
                                                 null,      // aTriggeringPrincipal
@@ -1005,17 +1000,17 @@ function ReadManifest(aURL, inherited_st
         if (items[0] == "include") {
             if (items.length != 2)
                 throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": incorrect number of arguments to include";
             if (runHttp)
                 throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": use of include with http";
             var incURI = gIOService.newURI(items[1], null, listURL);
             secMan.checkLoadURIWithPrincipal(principal, incURI,
                                              CI.nsIScriptSecurityManager.DISALLOW_SCRIPT);
-            ReadManifest(incURI, expected_status, aFilter);
+            ReadManifest(incURI, expected_status);
         } else if (items[0] == TYPE_LOAD) {
             if (items.length != 2)
                 throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": incorrect number of arguments to load";
             if (expected_status != EXPECTED_PASS &&
                 expected_status != EXPECTED_DEATH)
                 throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": incorrect known failure type for load test";
             var [testURI] = runHttp
                             ? ServeFiles(principal, httpDepth,
@@ -1035,17 +1030,17 @@ function ReadManifest(aURL, inherited_st
                           needsFocus: needs_focus,
                           slow: slow,
                           prefSettings1: testPrefSettings,
                           prefSettings2: refPrefSettings,
                           fuzzyMaxDelta: fuzzy_max_delta,
                           fuzzyMaxPixels: fuzzy_max_pixels,
                           url1: testURI,
                           url2: null,
-                          chaosMode: chaosMode }, aFilter);
+                          chaosMode: chaosMode });
         } else if (items[0] == TYPE_SCRIPT) {
             if (items.length != 2)
                 throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": incorrect number of arguments to script";
             var [testURI] = runHttp
                             ? ServeFiles(principal, httpDepth,
                                          listURL, [items[1]])
                             : [gIOService.newURI(items[1], null, listURL)];
             var prettyPath = runHttp
@@ -1062,17 +1057,17 @@ function ReadManifest(aURL, inherited_st
                           needsFocus: needs_focus,
                           slow: slow,
                           prefSettings1: testPrefSettings,
                           prefSettings2: refPrefSettings,
                           fuzzyMaxDelta: fuzzy_max_delta,
                           fuzzyMaxPixels: fuzzy_max_pixels,
                           url1: testURI,
                           url2: null,
-                          chaosMode: chaosMode }, aFilter);
+                          chaosMode: chaosMode });
         } else if (items[0] == TYPE_REFTEST_EQUAL || items[0] == TYPE_REFTEST_NOTEQUAL) {
             if (items.length != 3)
                 throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": incorrect number of arguments to " + items[0];
             var [testURI, refURI] = runHttp
                                   ? ServeFiles(principal, httpDepth,
                                                listURL, [items[1], items[2]])
                                   : [gIOService.newURI(items[1], null, listURL),
                                      gIOService.newURI(items[2], null, listURL)];
@@ -1092,17 +1087,17 @@ function ReadManifest(aURL, inherited_st
                           needsFocus: needs_focus,
                           slow: slow,
                           prefSettings1: testPrefSettings,
                           prefSettings2: refPrefSettings,
                           fuzzyMaxDelta: fuzzy_max_delta,
                           fuzzyMaxPixels: fuzzy_max_pixels,
                           url1: testURI,
                           url2: refURI,
-                          chaosMode: chaosMode }, aFilter);
+                          chaosMode: chaosMode });
         } else {
             throw "Error in manifest file " + aURL.spec + " line " + lineNo + ": unknown test type " + items[0];
         }
     }
 }
 
 function AddURIUseCount(uri)
 {
deleted file mode 100644
--- a/layout/tools/reftest/reftestcommandline.py
+++ /dev/null
@@ -1,740 +0,0 @@
-import argparse
-import os
-from collections import OrderedDict
-from urlparse import urlparse
-
-here = os.path.abspath(os.path.dirname(__file__))
-
-
-class ReftestArgumentsParser(argparse.ArgumentParser):
-    def __init__(self, **kwargs):
-        super(ReftestArgumentsParser, self).__init__(**kwargs)
-
-        # Try to import a MozbuildObject. Success indicates that we are
-        # running from a source tree. This allows some defaults to be set
-        # from the source tree.
-        try:
-            from mozbuild.base import MozbuildObject
-            self.build_obj = MozbuildObject.from_environment(cwd=here)
-        except ImportError:
-            self.build_obj = None
-
-        self.add_argument("--xre-path",
-                          action="store",
-                          type=str,
-                          dest="xrePath",
-                          # individual scripts will set a sane default
-                          default=None,
-                          help="absolute path to directory containing XRE (probably xulrunner)")
-
-        self.add_argument("--symbols-path",
-                          action="store",
-                          type=str,
-                          dest="symbolsPath",
-                          default=None,
-                          help="absolute path to directory containing breakpad symbols, or the URL of a zip file containing symbols")
-
-        self.add_argument("--debugger",
-                          action="store",
-                          dest="debugger",
-                          help="use the given debugger to launch the application")
-
-        self.add_argument("--debugger-args",
-                          action="store",
-                          dest="debuggerArgs",
-                          help="pass the given args to the debugger _before_ "
-                          "the application on the command line")
-
-        self.add_argument("--debugger-interactive",
-                          action="store_true",
-                          dest="debuggerInteractive",
-                          help="prevents the test harness from redirecting "
-                          "stdout and stderr for interactive debuggers")
-
-        self.add_argument("--appname",
-                          action="store",
-                          type=str,
-                          dest="app",
-                          default=None,
-                          help="absolute path to application, overriding default")
-
-        self.add_argument("--extra-profile-file",
-                          action="append",
-                          dest="extraProfileFiles",
-                          default=[],
-                          help="copy specified files/dirs to testing profile")
-
-        self.add_argument("--timeout",
-                          action="store",
-                          dest="timeout",
-                          type=int,
-                          default=5 * 60,  # 5 minutes per bug 479518
-                          help="reftest will timeout in specified number of seconds. [default %(default)s].")
-
-        self.add_argument("--leak-threshold",
-                          action="store",
-                          type=int,
-                          dest="defaultLeakThreshold",
-                          default=0,
-                          help="fail if the number of bytes leaked in default "
-                          "processes through refcounted objects (or bytes "
-                          "in classes with MOZ_COUNT_CTOR and MOZ_COUNT_DTOR) "
-                          "is greater than the given number")
-
-        self.add_argument("--utility-path",
-                          action="store",
-                          type=str,
-                          dest="utilityPath",
-                          default="bindir",
-                          help="absolute path to directory containing utility "
-                          "programs (xpcshell, ssltunnel, certutil)")
-
-        self.add_argument("--total-chunks",
-                          type=int,
-                          dest="totalChunks",
-                          help="how many chunks to split the tests up into")
-
-        self.add_argument("--this-chunk",
-                          type=int,
-                          dest="thisChunk",
-                          help="which chunk to run between 1 and --total-chunks")
-
-        self.add_argument("--log-file",
-                          action="store",
-                          type=str,
-                          dest="logFile",
-                          default=None,
-                          help="file to log output to in addition to stdout")
-
-        self.add_argument("--skip-slow-tests",
-                          dest="skipSlowTests",
-                          action="store_true",
-                          default=False,
-                          help="skip tests marked as slow when running")
-
-        self.add_argument("--ignore-window-size",
-                          dest="ignoreWindowSize",
-                          action="store_true",
-                          default=False,
-                          help="ignore the window size, which may cause spurious failures and passes")
-
-        self.add_argument("--install-extension",
-                          action="append",
-                          dest="extensionsToInstall",
-                          default=[],
-                          help="install the specified extension in the testing profile. "
-                          "The extension file's name should be <id>.xpi where <id> is "
-                          "the extension's id as indicated in its install.rdf. "
-                          "An optional path can be specified too.")
-
-        self.add_argument("--setenv",
-                          action="append",
-                          type=str,
-                          default=[],
-                          dest="environment",
-                          metavar="NAME=VALUE",
-                          help="sets the given variable in the application's "
-                          "environment")
-
-        self.add_argument("--filter",
-                          action="store",
-                          type=str,
-                          dest="filter",
-                          help="specifies a regular expression (as could be passed to the JS "
-                          "RegExp constructor) to test against URLs in the reftest manifest; "
-                          "only test items that have a matching test URL will be run.")
-
-        self.add_argument("--shuffle",
-                          action="store_true",
-                          default=False,
-                          dest="shuffle",
-                          help="run reftests in random order")
-
-        self.add_argument("--focus-filter-mode",
-                          action="store",
-                          type=str,
-                          dest="focusFilterMode",
-                          default="all",
-                          help="filters tests to run by whether they require focus. "
-                          "Valid values are `all', `needs-focus', or `non-needs-focus'. "
-                          "Defaults to `all'.")
-
-        self.add_argument("--e10s",
-                          action="store_true",
-                          default=False,
-                          dest="e10s",
-                          help="enables content processes")
-
-        self.add_argument("--setpref",
-                          action="append",
-                          type=str,
-                          default=[],
-                          dest="extraPrefs",
-                          metavar="PREF=VALUE",
-                          help="defines an extra user preference")
-
-        self.add_argument("--reftest-extension-path",
-                          action="store",
-                          dest="reftestExtensionPath",
-                          help="Path to the reftest extension")
-
-        self.add_argument("--special-powers-extension-path",
-                          action="store",
-                          dest="specialPowersExtensionPath",
-                          help="Path to the special powers extension")
-
-        self.add_argument("--suite",
-                          choices=["reftest", "crashtest", "jstestbrowser"],
-                          default=None,
-                          help=argparse.SUPPRESS)
-
-        self.add_argument("tests",
-                          metavar="TEST_PATH",
-                          nargs="*",
-                          help="Path to test file, manifest file, or directory containing tests")
-
-    def get_ip(self):
-        import moznetwork
-        if os.name != "nt":
-            return moznetwork.get_ip()
-        else:
-            self.error(
-                "ERROR: you must specify a --remote-webserver=<ip address>\n")
-
-    def set_default_suite(self, options):
-        manifests = OrderedDict([("reftest.list", "reftest"),
-                                 ("crashtests.list", "crashtest"),
-                                 ("jstests.list", "jstestbrowser")])
-
-        for test_path in options.tests:
-            file_name = os.path.basename(test_path)
-            if file_name in manifests:
-                options.suite = manifests[file_name]
-                return
-
-        for test_path in options.tests:
-            for manifest_file, suite in manifests.iteritems():
-                if os.path.exists(os.path.join(test_path, manifest_file)):
-                    options.suite = suite
-                    return
-
-        self.error("Failed to determine test suite; supply --suite to set this explicitly")
-
-    def validate(self, options, reftest):
-        import sys
-
-        if not options.tests:
-            # Can't just set this in the argument parser because mach will set a default
-            self.error("Must supply at least one path to a manifest file, test directory, or test file to run.")
-
-        if options.suite is None:
-            self.set_default_suite(options)
-
-        if options.totalChunks is not None and options.thisChunk is None:
-            self.error(
-                "thisChunk must be specified when totalChunks is specified")
-
-        if options.totalChunks:
-            if not 1 <= options.thisChunk <= options.totalChunks:
-                self.error("thisChunk must be between 1 and totalChunks")
-
-        if options.logFile:
-            options.logFile = reftest.getFullPath(options.logFile)
-
-        if options.xrePath is not None:
-            if not os.access(options.xrePath, os.F_OK):
-                self.error("--xre-path '%s' not found" % options.xrePath)
-            if not os.path.isdir(options.xrePath):
-                self.error("--xre-path '%s' is not a directory" %
-                           options.xrePath)
-            options.xrePath = reftest.getFullPath(options.xrePath)
-
-        if options.reftestExtensionPath is None:
-            if self.build_obj is not None:
-                options.reftestExtensionPath = os.path.join(self.build_obj.topobjdir, "_tests",
-                                                            "reftest", "reftest")
-            else:
-                options.reftestExtensionPath = os.path.join(here, "reftest")
-
-        if (options.specialPowersExtensionPath is None and
-            options.suite in ["crashtest", "jstestbrowser"]):
-            if self.build_obj is not None:
-                options.specialPowersExtensionPath = os.path.join(self.build_obj.topobjdir, "_tests",
-                                                                  "reftest", "specialpowers")
-            else:
-                options.specialPowersExtensionPath = os.path.join(
-                    here, "specialpowers")
-
-        options.leakThresholds = {
-            "default": options.defaultLeakThreshold,
-            "tab": 5000,  # See dependencies of bug 1051230.
-        }
-
-
-class DesktopArgumentsParser(ReftestArgumentsParser):
-    def __init__(self, **kwargs):
-        super(DesktopArgumentsParser, self).__init__(**kwargs)
-
-        self.add_argument("--run-tests-in-parallel",
-                          action="store_true",
-                          default=False,
-                          dest="runTestsInParallel",
-                          help="run tests in parallel if possible")
-
-        self.add_argument("--ipc",
-                          action="store_true",
-                          default=False,
-                          help="Run in out-of-processes mode")
-
-    def _prefs_oop(self):
-        import mozinfo
-        prefs = ["layers.async-pan-zoom.enabled=true",
-                 "browser.tabs.remote.autostart=true"]
-        if mozinfo.os == "win":
-            prefs.append("layers.acceleration.disabled=true")
-
-        return prefs
-
-    def _prefs_gpu(self):
-        if mozinfo.os != "win":
-            return ["layers.acceleration.force-enabled=true"]
-        return []
-
-    def validate(self, options, reftest):
-        super(DesktopArgumentsParser, self).validate(options, reftest)
-
-        if options.ipc:
-            for item in self._prefs_oop():
-                if item not in options.extraPrefs:
-                    options.extraPrefs.append(item)
-
-        if options.runTestsInParallel:
-            if options.logFile is not None:
-                self.error("cannot specify logfile with parallel tests")
-            if options.totalChunks is not None or options.thisChunk is not None:
-                self.error(
-                    "cannot specify thisChunk or totalChunks with parallel tests")
-            if options.focusFilterMode != "all":
-                self.error("cannot specify focusFilterMode with parallel tests")
-            if options.debugger is not None:
-                self.error("cannot specify a debugger with parallel tests")
-
-        if not options.tests:
-            self.error("No test files specified.")
-
-        if options.app is None:
-            bin_dir = (self.build_obj.get_binary_path() if
-                       self.build_obj and self.build_obj.substs[
-                           'MOZ_BUILD_APP'] != 'mobile/android'
-                       else None)
-
-            if bin_dir:
-                options.app = bin_dir
-            else:
-                self.error(
-                    "could not find the application path, --appname must be specified")
-
-        options.app = reftest.getFullPath(options.app)
-        if not os.path.exists(options.app):
-            self.error("""Error: Path %(app)s doesn't exist.
-            Are you executing $objdir/_tests/reftest/runreftest.py?"""
-                       % {"app": options.app})
-
-        if options.xrePath is None:
-            options.xrePath = os.path.dirname(options.app)
-
-        if options.symbolsPath and len(urlparse(options.symbolsPath).scheme) < 2:
-            options.symbolsPath = reftest.getFullPath(options.symbolsPath)
-
-        options.utilityPath = reftest.getFullPath(options.utilityPath)
-
-
-class B2GArgumentParser(ReftestArgumentsParser):
-    def __init__(self, **kwargs):
-        super(B2GArgumentParser, self).__init__(**kwargs)
-
-        self.add_argument("--browser-arg",
-                          action="store",
-                          type=str,
-                          dest="browser_arg",
-                          help="Optional command-line arg to pass to the browser")
-
-        self.add_argument("--b2gpath",
-                          action="store",
-                          type=str,
-                          dest="b2gPath",
-                          help="path to B2G repo or qemu dir")
-
-        self.add_argument("--marionette",
-                          action="store",
-                          type=str,
-                          dest="marionette",
-                          help="host:port to use when connecting to Marionette")
-
-        self.add_argument("--emulator",
-                          action="store",
-                          type=str,
-                          dest="emulator",
-                          help="Architecture of emulator to use: x86 or arm")
-
-        self.add_argument("--emulator-res",
-                          action="store",
-                          type=str,
-                          dest="emulator_res",
-                          help="Emulator resolution of the format '<width>x<height>'")
-
-        self.add_argument("--no-window",
-                          action="store_true",
-                          dest="noWindow",
-                          default=False,
-                          help="Pass --no-window to the emulator")
-
-        self.add_argument("--adbpath",
-                          action="store",
-                          type=str,
-                          dest="adb_path",
-                          default="adb",
-                          help="path to adb")
-
-        self.add_argument("--deviceIP",
-                          action="store",
-                          type=str,
-                          dest="deviceIP",
-                          help="ip address of remote device to test")
-
-        self.add_argument("--devicePort",
-                          action="store",
-                          type=str,
-                          dest="devicePort",
-                          default="20701",
-                          help="port of remote device to test")
-
-        self.add_argument("--remote-logfile",
-                          action="store",
-                          type=str,
-                          dest="remoteLogFile",
-                          help="Name of log file on the device relative to the device root.  PLEASE ONLY USE A FILENAME.")
-
-        self.add_argument("--remote-webserver",
-                          action="store",
-                          type=str,
-                          dest="remoteWebServer",
-                          help="ip address where the remote web server is hosted at")
-
-        self.add_argument("--http-port",
-                          action="store",
-                          type=str,
-                          dest="httpPort",
-                          help="ip address where the remote web server is hosted at")
-
-        self.add_argument("--ssl-port",
-                          action="store",
-                          type=str,
-                          dest="sslPort",
-                          help="ip address where the remote web server is hosted at")
-
-        self.add_argument("--pidfile",
-                          action="store",
-                          type=str,
-                          dest="pidFile",
-                          default="",
-                          help="name of the pidfile to generate")
-
-        self.add_argument("--gecko-path",
-                          action="store",
-                          type=str,
-                          dest="geckoPath",
-                          help="the path to a gecko distribution that should "
-                          "be installed on the emulator prior to test")
-
-        self.add_argument("--logdir",
-                          action="store",
-                          type=str,
-                          dest="logdir",
-                          help="directory to store log files")
-
-        self.add_argument('--busybox',
-                          action='store',
-                          type=str,
-                          dest='busybox',
-                          help="Path to busybox binary to install on device")
-
-        self.add_argument("--httpd-path",
-                          action="store",
-                          type=str,
-                          dest="httpdPath",
-                          help="path to the httpd.js file")
-
-        self.add_argument("--profile",
-                          action="store",
-                          type=str,
-                          dest="profile",
-                          help="for desktop testing, the path to the "
-                          "gaia profile to use")
-
-        self.add_argument("--desktop",
-                          action="store_true",
-                          dest="desktop",
-                          default=False,
-                          help="Run the tests on a B2G desktop build")
-
-        self.add_argument("--mulet",
-                          action="store_true",
-                          dest="mulet",
-                          default=False,
-                          help="Run the tests on a B2G desktop build")
-
-        self.add_argument("--enable-oop",
-                          action="store_true",
-                          dest="oop",
-                          default=False,
-                          help="Run the tests out of process")
-
-        self.set_defaults(remoteTestRoot=None,
-                          logFile="reftest.log",
-                          autorun=True,
-                          closeWhenDone=True,
-                          testPath="")
-
-    def validate_remote(self, options, automation):
-        if not options.app:
-            options.app = automation.DEFAULT_APP
-
-        if not options.remoteTestRoot:
-            options.remoteTestRoot = automation._devicemanager.deviceRoot + \
-                "/reftest"
-
-        options.remoteProfile = options.remoteTestRoot + "/profile"
-
-        productRoot = options.remoteTestRoot + "/" + automation._product
-        if options.utilityPath is None:
-            options.utilityPath = productRoot + "/bin"
-
-        if not options.httpPort:
-            options.httpPort = automation.DEFAULT_HTTP_PORT
-
-        if not options.sslPort:
-            options.sslPort = automation.DEFAULT_SSL_PORT
-
-        if options.remoteWebServer is None:
-            options.remoteWebServer = self.get_ip()
-
-        options.webServer = options.remoteWebServer
-
-        if options.geckoPath and not options.emulator:
-            self.error(
-                "You must specify --emulator if you specify --gecko-path")
-
-        if options.logdir and not options.emulator:
-            self.error("You must specify --emulator if you specify --logdir")
-
-        if options.remoteLogFile is None:
-            options.remoteLogFile = "reftest.log"
-
-        options.localLogName = options.remoteLogFile
-        options.remoteLogFile = options.remoteTestRoot + \
-            '/' + options.remoteLogFile
-
-        # Ensure that the options.logfile (which the base class uses) is set to
-        # the remote setting when running remote. Also, if the user set the
-        # log file name there, use that instead of reusing the remotelogfile as
-        # above.
-        if (options.logFile):
-            # If the user specified a local logfile name use that
-            options.localLogName = options.logFile
-        options.logFile = options.remoteLogFile
-
-        # Only reset the xrePath if it wasn't provided
-        if options.xrePath is None:
-            options.xrePath = options.utilityPath
-        options.xrePath = os.path.abspath(options.xrePath)
-
-        if options.pidFile != "":
-            f = open(options.pidFile, 'w')
-            f.write("%s" % os.getpid())
-            f.close()
-
-        # 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 from the xre bundle, as
-        # required in bug 882932.
-        if not options.httpdPath:
-            options.httpdPath = os.path.join(options.xrePath, "components")
-
-        return options
-
-
-class RemoteArgumentsParser(ReftestArgumentsParser):
-    def __init__(self, **kwargs):
-        super(RemoteArgumentsParser, self).__init__()
-
-        # app, xrePath and utilityPath variables are set in main function
-        self.set_defaults(logFile="reftest.log",
-                          app="",
-                          xrePath="",
-                          utilityPath="",
-                          localLogName=None)
-
-        self.add_argument("--remote-app-path",
-                          action="store",
-                          type=str,
-                          dest="remoteAppPath",
-                          help="Path to remote executable relative to device root using only forward slashes.  Either this or app must be specified, but not both.")
-
-        self.add_argument("--deviceIP",
-                          action="store",
-                          type=str,
-                          dest="deviceIP",
-                          help="ip address of remote device to test")
-
-        self.add_argument("--deviceSerial",
-                          action="store",
-                          type=str,
-                          dest="deviceSerial",
-                          help="adb serial number of remote device to test")
-
-        self.add_argument("--devicePort",
-                          action="store",
-                          type=str,
-                          default="20701",
-                          dest="devicePort",
-                          help="port of remote device to test")
-
-        self.add_argument("--remote-product-name",
-                          action="store",
-                          type=str,
-                          dest="remoteProductName",
-                          default="fennec",
-                          help="Name of product to test - either fennec or firefox, defaults to fennec")
-
-        self.add_argument("--remote-webserver",
-                          action="store",
-                          type=str,
-                          dest="remoteWebServer",
-                          help="IP Address of the webserver hosting the reftest content")
-
-        self.add_argument("--http-port",
-                          action="store",
-                          type=str,
-                          dest="httpPort",
-                          help="port of the web server for http traffic")
-
-        self.add_argument("--ssl-port",
-                          action="store",
-                          type=str,
-                          dest="sslPort",
-                          help="Port for https traffic to the web server")
-
-        self.add_argument("--remote-logfile",
-                          action="store",
-                          type=str,
-                          dest="remoteLogFile",
-                          default="reftest.log",
-                          help="Name of log file on the device relative to device root.  PLEASE USE ONLY A FILENAME.")
-
-        self.add_argument("--pidfile",
-                          action="store",
-                          type=str,
-                          dest="pidFile",
-                          default="",
-                          help="name of the pidfile to generate")
-
-        self.add_argument("--bootstrap",
-                          action="store_true",
-                          dest="bootstrap",
-                          default=False,
-                          help="test with a bootstrap addon required for native Fennec")
-
-        self.add_argument("--dm_trans",
-                          action="store",
-                          type=str,
-                          dest="dm_trans",
-                          default="sut",
-                          help="the transport to use to communicate with device: [adb|sut]; default=sut")
-
-        self.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)")
-
-        self.add_argument("--httpd-path",
-                          action="store",
-                          type=str,
-                          dest="httpdPath",
-                          help="path to the httpd.js file")
-
-    def validate_remote(self, options, automation):
-        # Ensure our defaults are set properly for everything we can infer
-        if not options.remoteTestRoot:
-            options.remoteTestRoot = automation._devicemanager.deviceRoot + \
-                '/reftest'
-        options.remoteProfile = options.remoteTestRoot + "/profile"
-
-        if options.remoteWebServer is None:
-            options.remoteWebServer = self.get_ip()
-
-        # Verify that our remotewebserver is set properly
-        if options.remoteWebServer == '127.0.0.1':
-            self.error("ERROR: Either you specified the loopback for the remote webserver or ",
-                       "your local IP cannot be detected.  Please provide the local ip in --remote-webserver")
-
-        if not options.httpPort:
-            options.httpPort = automation.DEFAULT_HTTP_PORT
-
-        if not options.sslPort:
-            options.sslPort = automation.DEFAULT_SSL_PORT
-
-        # One of remoteAppPath (relative path to application) or the app (executable) must be
-        # set, but not both.  If both are set, we destroy the user's selection for app
-        # so instead of silently destroying a user specificied setting, we
-        # error.
-        if options.remoteAppPath and options.app:
-            self.error(
-                "ERROR: You cannot specify both the remoteAppPath and the app")
-        elif options.remoteAppPath:
-            options.app = options.remoteTestRoot + "/" + options.remoteAppPath
-        elif options.app is None:
-            # Neither remoteAppPath nor app are set -- error
-            self.error("ERROR: You must specify either appPath or app")
-
-        if options.xrePath is None:
-            self.error(
-                "ERROR: You must specify the path to the controller xre directory")
-        else:
-            # Ensure xrepath is a full path
-            options.xrePath = os.path.abspath(options.xrePath)
-
-        options.localLogName = options.remoteLogFile
-        options.remoteLogFile = options.remoteTestRoot + \
-            '/' + options.remoteLogFile
-
-        # Ensure that the options.logfile (which the base class uses) is set to
-        # the remote setting when running remote. Also, if the user set the
-        # log file name there, use that instead of reusing the remotelogfile as
-        # above.
-        if options.logFile:
-            # If the user specified a local logfile name use that
-            options.localLogName = options.logFile
-
-        options.logFile = options.remoteLogFile
-
-        if options.pidFile != "":
-            with open(options.pidFile, 'w') as f:
-                f.write(str(os.getpid()))
-
-        # 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")
-
-        if not options.ignoreWindowSize:
-            parts = automation._devicemanager.getInfo(
-                'screen')['screen'][0].split()
-            width = int(parts[0].split(':')[1])
-            height = int(parts[1].split(':')[1])
-            if (width < 1050 or height < 1050):
-                self.error("ERROR: Invalid screen resolution %sx%s, please adjust to 1366x1050 or higher" % (
-                    width, height))
--- a/layout/tools/reftest/remotereftest.py
+++ b/layout/tools/reftest/remotereftest.py
@@ -4,46 +4,180 @@
 
 import sys
 import os
 import time
 import tempfile
 import traceback
 
 # We need to know our current directory so that we can serve our test files from it.
-SCRIPT_DIRECTORY = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
+SCRIPT_DIRECTORY = os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0])))
 
-from runreftest import RefTest, ReftestResolver
+from runreftest import RefTest
+from runreftest import ReftestOptions
 from automation import Automation
 import devicemanager
 import droid
 import mozinfo
 import moznetwork
 from remoteautomation import RemoteAutomation, fennecLogcatFilters
 
-import reftestcommandline
+class RemoteOptions(ReftestOptions):
+    def __init__(self, automation):
+        ReftestOptions.__init__(self)
+        self.automation = automation
+
+        defaults = {}
+        defaults["logFile"] = "reftest.log"
+        # app, xrePath and utilityPath variables are set in main function
+        defaults["app"] = ""
+        defaults["xrePath"] = ""
+        defaults["utilityPath"] = ""
+        defaults["runTestsInParallel"] = False
+
+        self.add_option("--remote-app-path", action="store",
+                    type = "string", dest = "remoteAppPath",
+                    help = "Path to remote executable relative to device root using only forward slashes.  Either this or app must be specified, but not both.")
+        defaults["remoteAppPath"] = None
+
+        self.add_option("--deviceIP", action="store",
+                    type = "string", dest = "deviceIP",
+                    help = "ip address of remote device to test")
+        defaults["deviceIP"] = None
+
+        self.add_option("--deviceSerial", action="store",
+                    type = "string", dest = "deviceSerial",
+                    help = "adb serial number of remote device to test")
+        defaults["deviceSerial"] = None
+
+        self.add_option("--devicePort", action="store",
+                    type = "string", dest = "devicePort",
+                    help = "port of remote device to test")
+        defaults["devicePort"] = 20701
+
+        self.add_option("--remote-product-name", action="store",
+                    type = "string", dest = "remoteProductName",
+                    help = "Name of product to test - either fennec or firefox, defaults to fennec")
+        defaults["remoteProductName"] = "fennec"
+
+        self.add_option("--remote-webserver", action="store",
+                    type = "string", dest = "remoteWebServer",
+                    help = "IP Address of the webserver hosting the reftest content")
+        defaults["remoteWebServer"] = moznetwork.get_ip()
+
+        self.add_option("--http-port", action = "store",
+                    type = "string", dest = "httpPort",
+                    help = "port of the web server for http traffic")
+        defaults["httpPort"] = automation.DEFAULT_HTTP_PORT
+
+        self.add_option("--ssl-port", action = "store",
+                    type = "string", dest = "sslPort",
+                    help = "Port for https traffic to the web server")
+        defaults["sslPort"] = automation.DEFAULT_SSL_PORT
+
+        self.add_option("--remote-logfile", action="store",
+                    type = "string", dest = "remoteLogFile",
+                    help = "Name of log file on the device relative to device root.  PLEASE USE ONLY A FILENAME.")
+        defaults["remoteLogFile"] = None
 
-class RemoteReftestResolver(ReftestResolver):
-    def absManifestPath(self, path):
-        script_abs_path = os.path.join(SCRIPT_DIRECTORY, path)
-        if os.path.exists(script_abs_path):
-            rv = script_abs_path
-        elif os.path.exists(os.path.abspath(path)):
-            rv = os.path.abspath(path)
+        self.add_option("--pidfile", action = "store",
+                    type = "string", dest = "pidFile",
+                    help = "name of the pidfile to generate")
+        defaults["pidFile"] = ""
+
+        self.add_option("--bootstrap", action="store_true", dest = "bootstrap",
+                    help = "test with a bootstrap addon required for native Fennec")
+        defaults["bootstrap"] = False
+
+        self.add_option("--dm_trans", action="store",
+                    type = "string", dest = "dm_trans",
+                    help = "the transport to use to communicate with device: [adb|sut]; default=sut")
+        defaults["dm_trans"] = "sut"
+
+        self.add_option("--remoteTestRoot", action = "store",
+                    type = "string", dest = "remoteTestRoot",
+                    help = "remote directory to use as test root (eg. /mnt/sdcard/tests or /data/local/tests)")
+        defaults["remoteTestRoot"] = None
+
+        self.add_option("--httpd-path", action = "store",
+                    type = "string", dest = "httpdPath",
+                    help = "path to the httpd.js file")
+        defaults["httpdPath"] = None
+
+        defaults["localLogName"] = None
+
+        self.set_defaults(**defaults)
+
+    def verifyRemoteOptions(self, options):
+        if options.runTestsInParallel:
+            self.error("Cannot run parallel tests here")
+
+        # Ensure our defaults are set properly for everything we can infer
+        if not options.remoteTestRoot:
+            options.remoteTestRoot = self.automation._devicemanager.deviceRoot + '/reftest'
+        options.remoteProfile = options.remoteTestRoot + "/profile"
+
+        # Verify that our remotewebserver is set properly
+        if (options.remoteWebServer == None or
+            options.remoteWebServer == '127.0.0.1'):
+            print "ERROR: Either you specified the loopback for the remote webserver or ",
+            print "your local IP cannot be detected.  Please provide the local ip in --remote-webserver"
+            return None
+
+        # One of remoteAppPath (relative path to application) or the app (executable) must be
+        # set, but not both.  If both are set, we destroy the user's selection for app
+        # so instead of silently destroying a user specificied setting, we error.
+        if (options.remoteAppPath and options.app):
+            print "ERROR: You cannot specify both the remoteAppPath and the app"
+            return None
+        elif (options.remoteAppPath):
+            options.app = options.remoteTestRoot + "/" + options.remoteAppPath
+        elif (options.app == None):
+            # Neither remoteAppPath nor app are set -- error
+            print "ERROR: You must specify either appPath or app"
+            return None
+
+        if (options.xrePath == None):
+            print "ERROR: You must specify the path to the controller xre directory"
+            return None
         else:
-            print >> sys.stderr, "Could not find manifest %s" % script_abs_path
-            sys.exit(1)
-        return os.path.normpath(rv)
+            # Ensure xrepath is a full path
+            options.xrePath = os.path.abspath(options.xrePath)
+
+        # Default to <deviceroot>/reftest/reftest.log
+        if (options.remoteLogFile == None):
+            options.remoteLogFile = 'reftest.log'
+
+        options.localLogName = options.remoteLogFile
+        options.remoteLogFile = options.remoteTestRoot + '/' + options.remoteLogFile
+
+        # Ensure that the options.logfile (which the base class uses) is set to
+        # the remote setting when running remote. Also, if the user set the
+        # log file name there, use that instead of reusing the remotelogfile as above.
+        if (options.logFile):
+            # If the user specified a local logfile name use that
+            options.localLogName = options.logFile
 
-    def manifestURL(self, options, path):
-        # Dynamically build the reftest URL if possible, beware that args[0] should exist 'inside' the webroot
-        # It's possible for this url to have a leading "..", but reftest.js will fix that up
-        relPath = os.path.relpath(path, SCRIPT_DIRECTORY)
-        return "http://%s:%s/%s" % (options.remoteWebServer, options.httpPort, relPath)
+        options.logFile = options.remoteLogFile
+
+        if (options.pidFile != ""):
+            f = open(options.pidFile, 'w')
+            f.write("%s" % os.getpid())
+            f.close()
 
+        # 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")
+
+        # TODO: Copied from main, but I think these are no longer used in a post xulrunner world
+        #options.xrePath = options.remoteTestRoot + self.automation._product + '/xulrunner'
+        #options.utilityPath = options.testRoot + self.automation._product + '/bin'
+        return options
 
 class ReftestServer:
     """ Web server used to serve Reftests, for closer fidelity to the real web.
         It is virtually identical to the server used in mochitest and will only
         be used for running reftests remotely.
         Bug 581257 has been filed to refactor this wrapper around httpd.js into
         it's own class and use it in both remote and non-remote testing. """
 
@@ -121,17 +255,16 @@ class ReftestServer:
                 rtncode = self._process.poll()
                 if (rtncode == None):
                     self._process.terminate()
             except:
                 self._process.kill()
 
 class RemoteReftest(RefTest):
     remoteApp = ''
-    resolver_cls = RemoteReftestResolver
 
     def __init__(self, automation, devicemanager, options, scriptDir):
         RefTest.__init__(self)
         self.automation = automation
         self._devicemanager = devicemanager
         self.scriptDir = scriptDir
         self.remoteApp = options.app
         self.remoteProfile = options.remoteProfile
@@ -204,33 +337,30 @@ class RemoteReftest(RefTest):
 
         options.xrePath = remoteXrePath
         options.utilityPath = remoteUtilityPath
         return 0
 
     def stopWebServer(self, options):
         self.server.stop()
 
-    def createReftestProfile(self, options, manifest):
-        profile = RefTest.createReftestProfile(self,
-                                               options,
-                                               manifest,
-                                               server=options.remoteWebServer,
-                                               port=options.httpPort)
+    def createReftestProfile(self, options, reftestlist):
+        profile = RefTest.createReftestProfile(self, options, reftestlist, server=options.remoteWebServer, port=options.httpPort)
         profileDir = profile.profile
 
         prefs = {}
         prefs["app.update.url.android"] = ""
         prefs["browser.firstrun.show.localepicker"] = False
         prefs["font.size.inflation.emPerLine"] = 0
         prefs["font.size.inflation.minTwips"] = 0
         prefs["reftest.remote"] = True
         # Set a future policy version to avoid the telemetry prompt.
         prefs["toolkit.telemetry.prompted"] = 999
         prefs["toolkit.telemetry.notifiedOptOut"] = 999
+        prefs["reftest.uri"] = "%s" % reftestlist
         prefs["datareporting.policy.dataSubmissionPolicyBypassAcceptance"] = True
 
         # Point the url-classifier to the local testing server for fast failures
         prefs["browser.safebrowsing.provider.google.gethashURL"] = "http://127.0.0.1:8888/safebrowsing-dummy/gethash"
         prefs["browser.safebrowsing.provider.google.updateURL"] = "http://127.0.0.1:8888/safebrowsing-dummy/update"
         prefs["browser.safebrowsing.provider.mozilla.gethashURL"] = "http://127.0.0.1:8888/safebrowsing-dummy/gethash"
         prefs["browser.safebrowsing.provider.mozilla.updateURL"] = "http://127.0.0.1:8888/safebrowsing-dummy/update"
         # Point update checks to the local testing server for fast failures
@@ -275,16 +405,19 @@ class RemoteReftest(RefTest):
         profileDir = profile.profile
         RefTest.copyExtraFilesToProfile(self, options, profile)
         try:
             self._devicemanager.pushDir(profileDir, options.remoteProfile)
         except devicemanager.DMError:
             print "Automation Error: Failed to copy extra files to device"
             raise
 
+    def getManifestPath(self, path):
+        return path
+
     def printDeviceInfo(self, printLogcat=False):
         try:
             if printLogcat:
                 logcat = self._devicemanager.getLogcat(filterOutRegexps=fennecLogcatFilters)
                 print ''.join(logcat)
             print "Device info:"
             devinfo = self._devicemanager.getInfo()
             for category in devinfo:
@@ -335,20 +468,20 @@ class RemoteReftest(RefTest):
         RefTest.cleanup(self, profileDir)
         if (self.pidFile != ""):
             try:
                 os.remove(self.pidFile)
                 os.remove(self.pidFile + ".xpcshell.pid")
             except:
                 print "Warning: cleaning up pidfile '%s' was unsuccessful from the test harness" % self.pidFile
 
-def main():
+def main(args):
     automation = RemoteAutomation(None)
-    parser = reftestcommandline.RemoteArgumentsParser()
-    options = parser.parse_args()
+    parser = RemoteOptions(automation)
+    options, args = parser.parse_args()
 
     if (options.dm_trans == 'sut' and options.deviceIP == None):
         print "Error: If --dm_trans = sut, you must provide a device IP to connect to via the --deviceIP option"
         return 1
 
     try:
         if (options.dm_trans == "adb"):
             if (options.deviceIP):
@@ -364,61 +497,86 @@ def main():
         return 1
 
     automation.setDeviceManager(dm)
 
     if (options.remoteProductName != None):
         automation.setProduct(options.remoteProductName)
 
     # Set up the defaults and ensure options are set
-    parser.validate_remote(options, automation)
+    options = parser.verifyRemoteOptions(options)
+    if (options == None):
+        print "ERROR: Invalid options specified, use --help for a list of valid options"
+        return 1
+
+    if not options.ignoreWindowSize:
+        parts = dm.getInfo('screen')['screen'][0].split()
+        width = int(parts[0].split(':')[1])
+        height = int(parts[1].split(':')[1])
+        if (width < 1050 or height < 1050):
+            print "ERROR: Invalid screen resolution %sx%s, please adjust to 1366x1050 or higher" % (width, height)
+            return 1
 
     # Check that Firefox is installed
     expected = options.app.split('/')[-1]
     installed = dm.shellCheckOutput(['pm', 'list', 'packages', expected])
     if expected not in installed:
         print "%s is not installed on this device" % expected
         return 1
 
     automation.setAppName(options.app)
     automation.setRemoteProfile(options.remoteProfile)
     automation.setRemoteLog(options.remoteLogFile)
     reftest = RemoteReftest(automation, dm, options, SCRIPT_DIRECTORY)
-    parser.validate(options, reftest)
+    options = parser.verifyCommonOptions(options, reftest)
 
     if mozinfo.info['debug']:
         print "changing timeout for remote debug reftests from %s to 600 seconds" % options.timeout
         options.timeout = 600
 
     # Hack in a symbolic link for jsreftest
     os.system("ln -s ../jsreftest " + str(os.path.join(SCRIPT_DIRECTORY, "jsreftest")))
 
+    # Dynamically build the reftest URL if possible, beware that args[0] should exist 'inside' the webroot
+    manifest = args[0]
+    if os.path.exists(os.path.join(SCRIPT_DIRECTORY, args[0])):
+        manifest = "http://" + str(options.remoteWebServer) + ":" + str(options.httpPort) + "/" + args[0]
+    elif os.path.exists(args[0]):
+        manifestPath = os.path.abspath(args[0]).split(SCRIPT_DIRECTORY)[1].strip('/')
+        manifest = "http://" + str(options.remoteWebServer) + ":" + str(options.httpPort) + "/" + manifestPath
+    else:
+        print "ERROR: Could not find test manifest '%s'" % manifest
+        return 1
+
     # Start the webserver
     retVal = reftest.startWebServer(options)
     if retVal:
         return retVal
 
     procName = options.app.split('/')[-1]
     if (dm.processExist(procName)):
         dm.killProcess(procName)
 
     reftest.printDeviceInfo()
 
 #an example manifest name to use on the cli
 #    manifest = "http://" + options.remoteWebServer + "/reftests/layout/reftests/reftest-sanity/reftest.list"
     retVal = 0
     try:
+        cmdlineArgs = ["-reftest", manifest]
+        if options.bootstrap:
+            cmdlineArgs = []
         dm.recordLogcat()
-        retVal = reftest.runTests(options.tests, options)
+        retVal = reftest.runTests(manifest, options, cmdlineArgs)
     except:
         print "Automation Error: Exception caught while running tests"
         traceback.print_exc()
         retVal = 1
 
     reftest.stopWebServer(options)
 
     reftest.printDeviceInfo(printLogcat=True)
 
     return retVal
 
 if __name__ == "__main__":
-    sys.exit(main())
+    sys.exit(main(sys.argv[1:]))
 
--- a/layout/tools/reftest/runreftest.py
+++ b/layout/tools/reftest/runreftest.py
@@ -1,43 +1,49 @@
 # 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/.
 
 """
 Runs the reftest test harness.
 """
 
+from optparse import OptionParser
+from urlparse import urlparse
 import collections
-import json
 import multiprocessing
 import os
 import re
 import shutil
 import signal
 import subprocess
 import sys
 import threading
 
 SCRIPT_DIRECTORY = os.path.abspath(
-    os.path.realpath(os.path.dirname(__file__)))
-if SCRIPT_DIRECTORY not in sys.path:
-    sys.path.insert(0, SCRIPT_DIRECTORY)
+    os.path.realpath(os.path.dirname(sys.argv[0])))
+sys.path.insert(0, SCRIPT_DIRECTORY)
 
 import mozcrash
 import mozdebug
 import mozinfo
 import mozleak
 import mozprocess
 import mozprofile
 import mozrunner
 from mozrunner.utils import get_stack_fixer_function, test_environment
 from mozscreenshot import printstatus, dump_screen
 
-import reftestcommandline
+here = os.path.abspath(os.path.dirname(__file__))
+
+try:
+    from mozbuild.base import MozbuildObject
+    build_obj = MozbuildObject.from_environment(cwd=here)
+except ImportError:
+    build_obj = None
 
 # set up logging handler a la automation.py.in for compatability
 import logging
 log = logging.getLogger()
 
 
 def resetGlobalLog():
     while log.handlers:
@@ -121,96 +127,24 @@ class ReftestThread(threading.Thread):
                     haveSuppressedSummaryLine = True
                     break
             if haveSuppressedSummaryLine:
                 continue
 
             if summaryHeadRegex.search(line) is None:
                 yield line
 
-class ReftestResolver(object):
-    def defaultManifest(self, suite):
-        return {"reftest": "reftest.list",
-                "crashtest": "crashtests.list",
-                "jstestbrowser": "jstests.list"}[suite]
-
-    def directoryManifest(self, suite, path):
-        return os.path.join(path, self.defaultManifest(suite))
-
-    def findManifest(self, suite, test_file, subdirs=True):
-        """Return a tuple of (manifest-path, filter-string) for running test_file.
-
-        test_file is a path to a test or a manifest file
-        """
-        rv = []
-        default_manifest = self.defaultManifest(suite)
-        if not os.path.isabs(test_file):
-            test_file = self.absManifestPath(test_file)
-
-        if os.path.isdir(test_file):
-            for dirpath, dirnames, filenames in os.walk(test_file):
-                if default_manifest in filenames:
-                    rv.append((os.path.join(dirpath, default_manifest), None))
-                    # We keep recursing into subdirectories which means that in the case
-                    # of include directives we get the same manifest multiple times.
-                    # However reftest.js will only read each manifest once
-
-        elif test_file.endswith('.list'):
-            if os.path.exists(test_file):
-                rv = [(test_file, None)]
-        else:
-            dirname, pathname = os.path.split(test_file)
-            found = True
-            while not os.path.exists(os.path.join(dirname, default_manifest)):
-                dirname, suffix = os.path.split(dirname)
-                pathname = os.path.join(suffix, pathname)
-                if os.path.dirname(dirname) == dirname:
-                    found = False
-                    break
-            if found:
-                rv = [(os.path.join(dirname, default_manifest),
-                       r".*(?:/|\\)%s$" % pathname)]
-
-        return rv
-
-    def absManifestPath(self, path):
-        return os.path.normpath(os.path.abspath(path))
-
-    def manifestURL(self, options, path):
-        return "file://%s" % path
-
-    def resolveManifests(self, options, tests):
-        suite = options.suite
-        manifests = {}
-        for testPath in tests:
-            for manifest, filter_str in self.findManifest(suite, testPath):
-                manifest = self.manifestURL(options, manifest)
-                if manifest not in manifests:
-                    manifests[manifest] = set()
-                manifests[manifest].add(filter_str)
-
-        for key in manifests.iterkeys():
-            if os.path.split(key)[1] != self.defaultManifest(suite):
-                print >> sys.stderr, "Invalid manifest for suite %s, %s" %(options.suite, key)
-                sys.exit(1)
-            if None in manifests[key]:
-                manifests[key] = None
-            else:
-                manifests[key] = "|".join(list(manifests[key]))
-        return manifests
 
 class RefTest(object):
     oldcwd = os.getcwd()
-    resolver_cls = ReftestResolver
 
     def __init__(self):
         self.update_mozinfo()
         self.lastTestSeen = 'reftest'
         self.haveDumpedScreen = False
-        self.resolver = self.resolver_cls()
 
     def update_mozinfo(self):
         """walk up directories to find mozinfo.json update the info"""
         # TODO: This should go in a more generic place, e.g. mozinfo
 
         path = SCRIPT_DIRECTORY
         dirs = set()
         while path != os.path.expanduser('~'):
@@ -219,25 +153,40 @@ class RefTest(object):
             dirs.add(path)
             path = os.path.split(path)[0]
         mozinfo.find_and_update_from_json(*dirs)
 
     def getFullPath(self, path):
         "Get an absolute path relative to self.oldcwd."
         return os.path.normpath(os.path.join(self.oldcwd, os.path.expanduser(path)))
 
-    def createReftestProfile(self, options, manifests, server='localhost', port=0,
-                             profile_to_clone=None):
-        """Sets up a profile for reftest.
+    def getManifestPath(self, path):
+        "Get the path of the manifest, and for remote testing this function is subclassed to point to remote manifest"
+        path = self.getFullPath(path)
+        if os.path.isdir(path):
+            defaultManifestPath = os.path.join(path, 'reftest.list')
+            if os.path.exists(defaultManifestPath):
+                path = defaultManifestPath
+            else:
+                defaultManifestPath = os.path.join(path, 'crashtests.list')
+                if os.path.exists(defaultManifestPath):
+                    path = defaultManifestPath
+        return path
 
-        :param options: Object containing command line options
-        :param manifests: Dictionary of the form {manifest_path: [filters]}
-        :param server: Server name to use for http tests
-        :param profile_to_clone: Path to a profile to use as the basis for the
-                                 test profile"""
+    def makeJSString(self, s):
+        return '"%s"' % re.sub(r'([\\"])', r'\\\1', s)
+
+    def createReftestProfile(self, options, manifest, server='localhost', port=0,
+                             special_powers=True, profile_to_clone=None):
+        """
+          Sets up a profile for reftest.
+          'manifest' is the path to the reftest.list file we want to test with.  This is used in
+          the remote subclass in remotereftest.py so we can write it to a preference for the
+          bootstrap extension.
+        """
 
         locations = mozprofile.permissions.ServerLocations()
         locations.add_host(server, scheme='http', port=port)
         locations.add_host(server, scheme='https', port=port)
 
         # Set preferences for communication between our command line arguments
         # and the reftest harness.  Preferences that are required for reftest
         # to work should instead be set in reftest-cmdline.js .
@@ -246,20 +195,21 @@ class RefTest(object):
         if options.totalChunks:
             prefs['reftest.totalChunks'] = options.totalChunks
         if options.thisChunk:
             prefs['reftest.thisChunk'] = options.thisChunk
         if options.logFile:
             prefs['reftest.logFile'] = options.logFile
         if options.ignoreWindowSize:
             prefs['reftest.ignoreWindowSize'] = True
+        if options.filter:
+            prefs['reftest.filter'] = options.filter
         if options.shuffle:
             prefs['reftest.shuffle'] = True
         prefs['reftest.focusFilterMode'] = options.focusFilterMode
-        prefs['reftest.manifests'] = json.dumps(manifests)
 
         # Ensure that telemetry is disabled, so we don't connect to the telemetry
         # server in the middle of the tests.
         prefs['toolkit.telemetry.enabled'] = False
         prefs['toolkit.telemetry.unified'] = False
         # Likewise for safebrowsing.
         prefs['browser.safebrowsing.enabled'] = False
         prefs['browser.safebrowsing.malware.enabled'] = False
@@ -294,20 +244,23 @@ class RefTest(object):
             thispref = v.split('=')
             if len(thispref) < 2:
                 print "Error: syntax error in --setpref=" + v
                 sys.exit(1)
             prefs[thispref[0]] = mozprofile.Preferences.cast(
                 thispref[1].strip())
 
         # install the reftest extension bits into the profile
-        addons = [options.reftestExtensionPath]
+        addons = []
+        addons.append(os.path.join(SCRIPT_DIRECTORY, "reftest"))
 
-        if options.specialPowersExtensionPath is not None:
-            addons.append(options.specialPowersExtensionPath)
+        # I would prefer to use "--install-extension reftest/specialpowers", but that requires tight coordination with
+        # release engineering and landing on multiple branches at once.
+        if special_powers and (manifest.endswith('crashtests.list') or manifest.endswith('jstests.list')):
+            addons.append(os.path.join(SCRIPT_DIRECTORY, 'specialpowers'))
             # SpecialPowers requires insecure automation-only features that we
             # put behind a pref.
             prefs['security.turn_off_all_security_so_that_viruses_can_take_over_this_computer'] = True
 
         # Install distributed extensions, if application has any.
         distExtDir = os.path.join(options.app[:options.app.rfind(os.sep)],
                                   "distribution", "extensions")
         if os.path.isdir(distExtDir):
@@ -378,31 +331,27 @@ class RefTest(object):
                                             processOutputLine=_psKill)
         process.run()
         process.wait()
 
     def cleanup(self, profileDir):
         if profileDir:
             shutil.rmtree(profileDir, True)
 
-    def runTests(self, tests, options, cmdlineArgs=None):
+    def runTests(self, testPath, options, cmdlineArgs=None):
         # Despite our efforts to clean up servers started by this script, in practice
         # we still see infrequent cases where a process is orphaned and interferes
         # with future tests, typically because the old server is keeping the port in use.
         # Try to avoid those failures by checking for and killing orphan servers before
         # trying to start new ones.
         self.killNamedOrphans('ssltunnel')
         self.killNamedOrphans('xpcshell')
 
-        manifests = self.resolver.resolveManifests(options, tests)
-        if options.filter:
-            manifests[""] = options.filter
-
-        if not hasattr(options, "runTestsInParallel") or not options.runTestsInParallel:
-            return self.runSerialTests(manifests, options, cmdlineArgs)
+        if not options.runTestsInParallel:
+            return self.runSerialTests(testPath, options, cmdlineArgs)
 
         cpuCount = multiprocessing.cpu_count()
 
         # We have the directive, technology, and machine to run multiple test instances.
         # Experimentation says that reftests are not overly CPU-intensive, so we can run
         # multiple jobs per CPU core.
         #
         # Our Windows machines in automation seem to get upset when we run a lot of
@@ -423,16 +372,17 @@ class RefTest(object):
                               "--total-chunks=%d" % jobsWithoutFocus,
                               "--this-chunk=%d" % chunkNumber]
 
         for jobArgs in perProcessArgs:
             try:
                 jobArgs.remove("--run-tests-in-parallel")
             except:
                 pass
+            jobArgs.insert(-1, "--no-run-tests-in-parallel")
             jobArgs[0:0] = [sys.executable, "-u"]
 
         threads = [ReftestThread(args) for args in perProcessArgs[1:]]
         for t in threads:
             t.start()
 
         while True:
             # The test harness in each individual thread will be doing timeout
@@ -645,27 +595,28 @@ class RefTest(object):
 
         crashed = mozcrash.check_for_crashes(os.path.join(profile.profile, "minidumps"),
                                              symbolsPath, test_name=self.lastTestSeen)
         runner.cleanup()
         if not status and crashed:
             status = 1
         return status
 
-    def runSerialTests(self, manifests, options, cmdlineArgs=None):
+    def runSerialTests(self, testPath, options, cmdlineArgs=None):
         debuggerInfo = None
         if options.debugger:
             debuggerInfo = mozdebug.get_debugger_info(options.debugger, options.debuggerArgs,
                                                       options.debuggerInteractive)
 
         profileDir = None
         try:
+            reftestlist = self.getManifestPath(testPath)
             if cmdlineArgs == None:
-                cmdlineArgs = []
-            profile = self.createReftestProfile(options, manifests)
+                cmdlineArgs = ['-reftest', reftestlist]
+            profile = self.createReftestProfile(options, reftestlist)
             profileDir = profile.profile  # name makes more sense
 
             # browser environment
             browserEnv = self.buildBrowserEnv(options, profileDir)
 
             log.info("REFTEST INFO | runreftest.py | Running tests: start.\n")
             status = self.runApp(profile,
                                  binary=options.app,
@@ -703,31 +654,214 @@ class RefTest(object):
                 dest = os.path.join(profileDir, os.path.basename(abspath))
                 shutil.copytree(abspath, dest)
             else:
                 log.warning(
                     "WARNING | runreftest.py | Failed to copy %s to profile", abspath)
                 continue
 
 
-def run(**kwargs):
-    # Mach gives us kwargs; this is a way to turn them back into an
-    # options object
-    parser = reftestcommandline.DesktopArgumentsParser()
-    reftest = RefTest()
-    parser.set_defaults(**kwargs)
-    options = parser.parse_args(kwargs["tests"])
-    parser.validate(options, reftest)
-    return reftest.runTests(options.tests, options)
+class ReftestOptions(OptionParser):
+
+    def __init__(self):
+        OptionParser.__init__(self)
+        defaults = {}
+        self.add_option("--xre-path",
+                        action="store", type="string", dest="xrePath",
+                        # individual scripts will set a sane default
+                        default=None,
+                        help="absolute path to directory containing XRE (probably xulrunner)")
+        self.add_option("--symbols-path",
+                        action="store", type="string", dest="symbolsPath",
+                        default=None,
+                        help="absolute path to directory containing breakpad symbols, or the URL of a zip file containing symbols")
+        self.add_option("--debugger",
+                        action="store", dest="debugger",
+                        help="use the given debugger to launch the application")
+        self.add_option("--debugger-args",
+                        action="store", dest="debuggerArgs",
+                        help="pass the given args to the debugger _before_ "
+                        "the application on the command line")
+        self.add_option("--debugger-interactive",
+                        action="store_true", dest="debuggerInteractive",
+                        help="prevents the test harness from redirecting "
+                        "stdout and stderr for interactive debuggers")
+        self.add_option("--appname",
+                        action="store", type="string", dest="app",
+                        help="absolute path to application, overriding default")
+        # Certain paths do not make sense when we're cross compiling Fennec.  This
+        # logic is cribbed from the example in
+        # python/mozbuild/mozbuild/mach_commands.py.
+        defaults['app'] = build_obj.get_binary_path() if \
+            build_obj and build_obj.substs['MOZ_BUILD_APP'] != 'mobile/android' else None
+
+        self.add_option("--extra-profile-file",
+                        action="append", dest="extraProfileFiles",
+                        default=[],
+                        help="copy specified files/dirs to testing profile")
+        self.add_option("--timeout",
+                        action="store", dest="timeout", type="int",
+                        default=5 * 60,  # 5 minutes per bug 479518
+                        help="reftest will timeout in specified number of seconds. [default %default s].")
+        self.add_option("--leak-threshold",
+                        action="store", type="int", dest="defaultLeakThreshold",
+                        default=0,
+                        help="fail if the number of bytes leaked in default "
+                        "processes through refcounted objects (or bytes "
+                               "in classes with MOZ_COUNT_CTOR and MOZ_COUNT_DTOR) "
+                               "is greater than the given number")
+        self.add_option("--utility-path",
+                        action="store", type="string", dest="utilityPath",
+                        help="absolute path to directory containing utility "
+                        "programs (xpcshell, ssltunnel, certutil)")
+        defaults["utilityPath"] = build_obj.bindir if \
+            build_obj and build_obj.substs['MOZ_BUILD_APP'] != 'mobile/android' else None
+
+        self.add_option("--total-chunks",
+                        type="int", dest="totalChunks",
+                        help="how many chunks to split the tests up into")
+        defaults["totalChunks"] = None
+
+        self.add_option("--this-chunk",
+                        type="int", dest="thisChunk",
+                        help="which chunk to run between 1 and --total-chunks")
+        defaults["thisChunk"] = None
+
+        self.add_option("--log-file",
+                        action="store", type="string", dest="logFile",
+                        default=None,
+                        help="file to log output to in addition to stdout")
+        defaults["logFile"] = None
+
+        self.add_option("--skip-slow-tests",
+                        dest="skipSlowTests", action="store_true",
+                        help="skip tests marked as slow when running")
+        defaults["skipSlowTests"] = False
+
+        self.add_option("--ignore-window-size",
+                        dest="ignoreWindowSize", action="store_true",
+                        help="ignore the window size, which may cause spurious failures and passes")
+        defaults["ignoreWindowSize"] = False
+
+        self.add_option("--install-extension",
+                        action="append", dest="extensionsToInstall",
+                        help="install the specified extension in the testing profile. "
+                        "The extension file's name should be <id>.xpi where <id> is "
+                               "the extension's id as indicated in its install.rdf. "
+                               "An optional path can be specified too.")
+        defaults["extensionsToInstall"] = []
+
+        self.add_option("--run-tests-in-parallel",
+                        action="store_true", dest="runTestsInParallel",
+                        help="run tests in parallel if possible")
+        self.add_option("--no-run-tests-in-parallel",
+                        action="store_false", dest="runTestsInParallel",
+                        help="do not run tests in parallel")
+        defaults["runTestsInParallel"] = False
+
+        self.add_option("--setenv",
+                        action="append", type="string",
+                        dest="environment", metavar="NAME=VALUE",
+                        help="sets the given variable in the application's "
+                        "environment")
+        defaults["environment"] = []
+
+        self.add_option("--filter",
+                        action="store", type="string", dest="filter",
+                        help="specifies a regular expression (as could be passed to the JS "
+                        "RegExp constructor) to test against URLs in the reftest manifest; "
+                               "only test items that have a matching test URL will be run.")
+        defaults["filter"] = None
+
+        self.add_option("--shuffle",
+                        action="store_true", dest="shuffle",
+                        help="run reftests in random order")
+        defaults["shuffle"] = False
+
+        self.add_option("--focus-filter-mode",
+                        action="store", type="string", dest="focusFilterMode",
+                        help="filters tests to run by whether they require focus. "
+                        "Valid values are `all', `needs-focus', or `non-needs-focus'. "
+                               "Defaults to `all'.")
+        defaults["focusFilterMode"] = "all"
+
+        self.add_option("--e10s",
+                        action="store_true",
+                        dest="e10s",
+                        help="enables content processes")
+        defaults["e10s"] = False
+
+        self.add_option("--setpref",
+                        action="append", type="string",
+                        default=[],
+                        dest="extraPrefs", metavar="PREF=VALUE",
+                        help="defines an extra user preference")
+
+        self.set_defaults(**defaults)
+
+    def verifyCommonOptions(self, options, reftest):
+        if options.totalChunks is not None and options.thisChunk is None:
+            self.error("thisChunk must be specified when totalChunks is specified")
+
+        if options.totalChunks:
+            if not 1 <= options.thisChunk <= options.totalChunks:
+                self.error("thisChunk must be between 1 and totalChunks")
+
+        if options.logFile:
+            options.logFile = reftest.getFullPath(options.logFile)
+
+        if options.xrePath is not None:
+            if not os.access(options.xrePath, os.F_OK):
+                self.error("--xre-path '%s' not found" % options.xrePath)
+            if not os.path.isdir(options.xrePath):
+                self.error("--xre-path '%s' is not a directory" %
+                           options.xrePath)
+            options.xrePath = reftest.getFullPath(options.xrePath)
+
+        if options.runTestsInParallel:
+            if options.logFile is not None:
+                self.error("cannot specify logfile with parallel tests")
+            if options.totalChunks is not None and options.thisChunk is None:
+                self.error("cannot specify thisChunk or totalChunks with parallel tests")
+            if options.focusFilterMode != "all":
+                self.error("cannot specify focusFilterMode with parallel tests")
+            if options.debugger is not None:
+                self.error("cannot specify a debugger with parallel tests")
+
+        options.leakThresholds = {
+            "default": options.defaultLeakThreshold,
+            "tab": 5000,  # See dependencies of bug 1051230.
+        }
+
+        return options
 
 
 def main():
-    parser = reftestcommandline.DesktopArgumentsParser()
+    parser = ReftestOptions()
     reftest = RefTest()
 
-    options = parser.parse_args()
-    parser.validate(options, reftest)
+    options, args = parser.parse_args()
+    if len(args) != 1:
+        print >>sys.stderr, "No reftest.list specified."
+        sys.exit(1)
+
+    options = parser.verifyCommonOptions(options, reftest)
+    if options.app is None:
+        parser.error("could not find the application path, --appname must be specified")
 
-    sys.exit(reftest.runTests(options.tests, options))
+    options.app = reftest.getFullPath(options.app)
+    if not os.path.exists(options.app):
+        print """Error: Path %(app)s doesn't exist.
+Are you executing $objdir/_tests/reftest/runreftest.py?""" \
+                % {"app": options.app}
+        sys.exit(1)
 
+    if options.xrePath is None:
+        options.xrePath = os.path.dirname(options.app)
+
+    if options.symbolsPath and len(urlparse(options.symbolsPath).scheme) < 2:
+        options.symbolsPath = reftest.getFullPath(options.symbolsPath)
+    options.utilityPath = reftest.getFullPath(options.utilityPath)
+
+    sys.exit(reftest.runTests(args[0], options))
 
 if __name__ == "__main__":
     main()
--- a/layout/tools/reftest/runreftestb2g.py
+++ b/layout/tools/reftest/runreftestb2g.py
@@ -5,28 +5,217 @@
 import ConfigParser
 import os
 import sys
 import tempfile
 import traceback
 
 # We need to know our current directory so that we can serve our test files from it.
 here = os.path.abspath(os.path.dirname(__file__))
-if here not in sys.path:
-    sys.path.insert(0, here)
 
 from automation import Automation
 from b2gautomation import B2GRemoteAutomation
 from b2g_desktop import run_desktop_reftests
-from remotereftest import RemoteReftestResolver, ReftestServer
 from runreftest import RefTest
-import reftestcommandline
+from runreftest import ReftestOptions
+from remotereftest import ReftestServer
 
 from mozdevice import DeviceManagerADB, DMError
 from marionette import Marionette
+import moznetwork
+
+class B2GOptions(ReftestOptions):
+
+    def __init__(self, **kwargs):
+        defaults = {}
+        ReftestOptions.__init__(self)
+        # This is only used for procName in run_remote_reftests.
+        defaults["app"] = Automation.DEFAULT_APP
+
+        self.add_option("--browser-arg", action="store",
+                    type = "string", dest = "browser_arg",
+                    help = "Optional command-line arg to pass to the browser")
+        defaults["browser_arg"] = None
+
+        self.add_option("--b2gpath", action="store",
+                    type = "string", dest = "b2gPath",
+                    help = "path to B2G repo or qemu dir")
+        defaults["b2gPath"] = None
+
+        self.add_option("--marionette", action="store",
+                    type = "string", dest = "marionette",
+                    help = "host:port to use when connecting to Marionette")
+        defaults["marionette"] = None
+
+        self.add_option("--emulator", action="store",
+                    type="string", dest = "emulator",
+                    help = "Architecture of emulator to use: x86 or arm")
+        defaults["emulator"] = None
+        self.add_option("--emulator-res", action="store",
+                    type="string", dest = "emulator_res",
+                    help = "Emulator resolution of the format '<width>x<height>'")
+        defaults["emulator_res"] = None
+
+        self.add_option("--no-window", action="store_true",
+                    dest = "noWindow",
+                    help = "Pass --no-window to the emulator")
+        defaults["noWindow"] = False
+
+        self.add_option("--adbpath", action="store",
+                    type = "string", dest = "adb_path",
+                    help = "path to adb")
+        defaults["adb_path"] = "adb"
+
+        self.add_option("--deviceIP", action="store",
+                    type = "string", dest = "deviceIP",
+                    help = "ip address of remote device to test")
+        defaults["deviceIP"] = None
+
+        self.add_option("--devicePort", action="store",
+                    type = "string", dest = "devicePort",
+                    help = "port of remote device to test")
+        defaults["devicePort"] = 20701
+
+        self.add_option("--remote-logfile", action="store",
+                    type = "string", dest = "remoteLogFile",
+                    help = "Name of log file on the device relative to the device root.  PLEASE ONLY USE A FILENAME.")
+        defaults["remoteLogFile"] = None
+
+        self.add_option("--remote-webserver", action = "store",
+                    type = "string", dest = "remoteWebServer",
+                    help = "ip address where the remote web server is hosted at")
+        defaults["remoteWebServer"] = None
+
+        self.add_option("--http-port", action = "store",
+                    type = "string", dest = "httpPort",
+                    help = "ip address where the remote web server is hosted at")
+        defaults["httpPort"] = None
+
+        self.add_option("--ssl-port", action = "store",
+                    type = "string", dest = "sslPort",
+                    help = "ip address where the remote web server is hosted at")
+        defaults["sslPort"] = None
+
+        self.add_option("--pidfile", action = "store",
+                    type = "string", dest = "pidFile",
+                    help = "name of the pidfile to generate")
+        defaults["pidFile"] = ""
+        self.add_option("--gecko-path", action="store",
+                        type="string", dest="geckoPath",
+                        help="the path to a gecko distribution that should "
+                        "be installed on the emulator prior to test")
+        defaults["geckoPath"] = None
+        self.add_option("--logdir", action="store",
+                        type="string", dest="logdir",
+                        help="directory to store log files")
+        defaults["logdir"] = None
+        self.add_option('--busybox', action='store',
+                        type='string', dest='busybox',
+                        help="Path to busybox binary to install on device")
+        defaults['busybox'] = None
+        self.add_option("--httpd-path", action = "store",
+                    type = "string", dest = "httpdPath",
+                    help = "path to the httpd.js file")
+        defaults["httpdPath"] = None
+        self.add_option("--profile", action="store",
+                    type="string", dest="profile",
+                    help="for desktop testing, the path to the "
+                         "gaia profile to use")
+        defaults["profile"] = None
+        self.add_option("--desktop", action="store_true",
+                        dest="desktop",
+                        help="Run the tests on a B2G desktop build")
+        defaults["desktop"] = False
+        self.add_option("--mulet", action="store_true",
+                        dest="mulet",
+                        help="Run the tests on a B2G desktop build")
+        defaults["mulet"] = False
+        self.add_option("--enable-oop", action="store_true",
+                        dest="oop",
+                        help="Run the tests out of process")
+        defaults["oop"] = False
+        defaults["remoteTestRoot"] = None
+        defaults["logFile"] = "reftest.log"
+        defaults["autorun"] = True
+        defaults["closeWhenDone"] = True
+        defaults["testPath"] = ""
+        defaults["runTestsInParallel"] = False
+
+        self.set_defaults(**defaults)
+
+    def verifyRemoteOptions(self, options, auto):
+        if options.runTestsInParallel:
+            self.error("Cannot run parallel tests here")
+
+        if not options.remoteTestRoot:
+            options.remoteTestRoot = auto._devicemanager.deviceRoot + "/reftest"
+
+        options.remoteProfile = options.remoteTestRoot + "/profile"
+
+        productRoot = options.remoteTestRoot + "/" + auto._product
+        if options.utilityPath is None:
+            options.utilityPath = productRoot + "/bin"
+
+        if options.remoteWebServer == None:
+            if os.name != "nt":
+                options.remoteWebServer = moznetwork.get_ip()
+            else:
+                print "ERROR: you must specify a --remote-webserver=<ip address>\n"
+                return None
+
+        options.webServer = options.remoteWebServer
+
+        if not options.httpPort:
+            options.httpPort = auto.DEFAULT_HTTP_PORT
+
+        if not options.sslPort:
+            options.sslPort = auto.DEFAULT_SSL_PORT
+
+        if options.geckoPath and not options.emulator:
+            self.error("You must specify --emulator if you specify --gecko-path")
+
+        if options.logdir and not options.emulator:
+            self.error("You must specify --emulator if you specify --logdir")
+
+        #if not options.emulator and not options.deviceIP:
+        #    print "ERROR: you must provide a device IP"
+        #    return None
+
+        if options.remoteLogFile == None:
+            options.remoteLogFile = "reftest.log"
+
+        options.localLogName = options.remoteLogFile
+        options.remoteLogFile = options.remoteTestRoot + '/' + options.remoteLogFile
+
+        # Ensure that the options.logfile (which the base class uses) is set to
+        # the remote setting when running remote. Also, if the user set the
+        # log file name there, use that instead of reusing the remotelogfile as above.
+        if (options.logFile):
+            # If the user specified a local logfile name use that
+            options.localLogName = options.logFile
+        options.logFile = options.remoteLogFile
+
+        # Only reset the xrePath if it wasn't provided
+        if options.xrePath == None:
+            options.xrePath = options.utilityPath
+        options.xrePath = os.path.abspath(options.xrePath)
+
+        if options.pidFile != "":
+            f = open(options.pidFile, 'w')
+            f.write("%s" % os.getpid())
+            f.close()
+
+        # 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 from the xre bundle, as required in bug 882932.
+        if not options.httpdPath:
+            options.httpdPath = os.path.join(options.xrePath, "components")
+
+        return options
+
 
 class ProfileConfigParser(ConfigParser.RawConfigParser):
     """Subclass of RawConfigParser that outputs .ini files in the exact
        format expected for profiles.ini, which is slightly different
        than the default format.
     """
 
     def optionxform(self, optionstr):
@@ -49,17 +238,16 @@ class ProfileConfigParser(ConfigParser.R
             fp.write("\n")
 
 class B2GRemoteReftest(RefTest):
 
     _devicemanager = None
     localProfile = None
     remoteApp = ''
     profile = None
-    resolver_cls = RemoteReftestResolver
 
     def __init__(self, automation, devicemanager, options, scriptDir):
         RefTest.__init__(self)
         self.automation = automation
         self._devicemanager = devicemanager
         self.runSSLTunnel = False
         self.remoteTestRoot = options.remoteTestRoot
         self.remoteProfile = options.remoteProfile
@@ -225,35 +413,36 @@ class B2GRemoteReftest(RefTest):
 
         self._devicemanager.pushFile(newProfilesIni, self.remoteProfilesIniPath)
         try:
             os.remove(newProfilesIni)
         except:
             pass
 
 
-    def createReftestProfile(self, options, manifests):
-        profile = RefTest.createReftestProfile(self, options, manifests,
-                                               server=options.remoteWebServer)
+    def createReftestProfile(self, options, reftestlist):
+        profile = RefTest.createReftestProfile(self, options, reftestlist,
+                                               server=options.remoteWebServer,
+                                               special_powers=False)
         profileDir = profile.profile
 
         prefs = {}
 
         # Turn off the locale picker screen
         prefs["browser.firstrun.show.localepicker"] = False
         prefs["b2g.system_startup_url"] = "app://test-container.gaiamobile.org/index.html"
         prefs["b2g.system_manifest_url"] = "app://test-container.gaiamobile.org/manifest.webapp"
         prefs["dom.ipc.tabs.disabled"] = False
         prefs["dom.mozBrowserFramesEnabled"] = True
         prefs["font.size.inflation.emPerLine"] = 0
         prefs["font.size.inflation.minTwips"] = 0
         prefs["network.dns.localDomains"] = "app://test-container.gaiamobile.org"
         prefs["reftest.browser.iframe.enabled"] = False
         prefs["reftest.remote"] = True
-
+        prefs["reftest.uri"] = "%s" % reftestlist
         # Set a future policy version to avoid the telemetry prompt.
         prefs["toolkit.telemetry.prompted"] = 999
         prefs["toolkit.telemetry.notifiedOptOut"] = 999
         # Make sure we disable system updates
         prefs["app.update.enabled"] = False
         prefs["app.update.url"] = ""
         prefs["app.update.url.override"] = ""
         # Disable webapp updates
@@ -301,16 +490,19 @@ class B2GRemoteReftest(RefTest):
         profileDir = profile.profile
         RefTest.copyExtraFilesToProfile(self, options, profile)
         try:
             self._devicemanager.pushDir(profileDir, options.remoteProfile)
         except DMError:
             print "Automation Error: Failed to copy extra files to device"
             raise
 
+    def getManifestPath(self, path):
+        return path
+
     def environment(self, **kwargs):
      return self.automation.environment(**kwargs)
 
     def runApp(self, profile, binary, cmdargs, env,
                timeout=None, debuggerInfo=None,
                symbolsPath=None, options=None):
         status = self.automation.runApp(None, env,
                                         binary,
@@ -319,17 +511,17 @@ class B2GRemoteReftest(RefTest):
                                         utilityPath=options.utilityPath,
                                         xrePath=options.xrePath,
                                         debuggerInfo=debuggerInfo,
                                         symbolsPath=symbolsPath,
                                         timeout=timeout)
         return status
 
 
-def run_remote_reftests(parser, options):
+def run_remote_reftests(parser, options, args):
     auto = B2GRemoteAutomation(None, "fennec", context_chrome=True)
 
     # create our Marionette instance
     kwargs = {}
     if options.emulator:
         kwargs['emulator'] = options.emulator
         auto.setEmulator(True)
         if options.noWindow:
@@ -362,80 +554,90 @@ def run_remote_reftests(parser, options)
         kwargs = {'adbPath': options.adb_path,
                   'deviceRoot': options.remoteTestRoot}
         if options.deviceIP:
             kwargs.update({'host': options.deviceIP,
                            'port': options.devicePort})
         dm = DeviceManagerADB(**kwargs)
     auto.setDeviceManager(dm)
 
-    parser.validate_remote(options, auto)
+    options = parser.verifyRemoteOptions(options, auto)
+
+    if (options == None):
+        print "ERROR: Invalid options specified, use --help for a list of valid options"
+        sys.exit(1)
 
     # TODO fix exception
     if not options.ignoreWindowSize:
         parts = dm.getInfo('screen')['screen'][0].split()
         width = int(parts[0].split(':')[1])
         height = int(parts[1].split(':')[1])
         if (width < 1366 or height < 1050):
             print "ERROR: Invalid screen resolution %sx%s, please adjust to 1366x1050 or higher" % (width, height)
             return 1
 
     auto.setProduct("b2g")
     auto.test_script = os.path.join(here, 'b2g_start_script.js')
     auto.test_script_args = [options.remoteWebServer, options.httpPort]
     auto.logFinish = "REFTEST TEST-START | Shutdown"
 
     reftest = B2GRemoteReftest(auto, dm, options, here)
-    parser.validate(options, reftest)
+    options = parser.verifyCommonOptions(options, reftest)
 
     logParent = os.path.dirname(options.remoteLogFile)
     dm.mkDir(logParent);
     auto.setRemoteLog(options.remoteLogFile)
     auto.setServerInfo(options.webServer, options.httpPort, options.sslPort)
 
     # Hack in a symbolic link for jsreftest
     os.system("ln -s %s %s" % (os.path.join('..', 'jsreftest'), os.path.join(here, 'jsreftest')))
 
+    # Dynamically build the reftest URL if possible, beware that args[0] should exist 'inside' the webroot
+    manifest = args[0]
+    if os.path.exists(os.path.join(here, args[0])):
+        manifest = "http://%s:%s/%s" % (options.remoteWebServer, options.httpPort, args[0])
+    elif os.path.exists(args[0]):
+        manifestPath = os.path.abspath(args[0]).split(here)[1].strip('/')
+        manifest = "http://%s:%s/%s" % (options.remoteWebServer, options.httpPort, manifestPath)
+    else:
+        print "ERROR: Could not find test manifest '%s'" % manifest
+        return 1
 
     # Start the webserver
     retVal = 1
     try:
         retVal = reftest.startWebServer(options)
         if retVal:
             return retVal
         procName = options.app.split('/')[-1]
         if (dm.processExist(procName)):
             dm.killProcess(procName)
 
-        retVal = reftest.runTests(options.tests, options)
+        cmdlineArgs = ["-reftest", manifest]
+        if getattr(options, 'bootstrap', False):
+            cmdlineArgs = []
+
+        retVal = reftest.runTests(manifest, options, cmdlineArgs)
     except:
         print "Automation Error: Exception caught while running tests"
         traceback.print_exc()
         reftest.stopWebServer(options)
         try:
             reftest.cleanup(None)
         except:
             pass
         return 1
 
     reftest.stopWebServer(options)
     return retVal
 
-def run_remote(**kwargs):
-    # Tests need to be served from a subdirectory of the server. Symlink
-    # topsrcdir here to get around this.
-    parser = reftestcommandline.B2GArgumentParser()
-    parser.set_defaults(**kwargs)
-    options = parser.parse_args(kwargs["tests"])
-    return run_remote_reftests(parser, options)
-
-def main():
-    parser = reftestcommandline.B2GArgumentParser()
-    options = parser.parse_args()
+def main(args=sys.argv[1:]):
+    parser = B2GOptions()
+    options, args = parser.parse_args(args)
 
     if options.desktop or options.mulet:
-        return run_desktop_reftests(parser, options)
-    return run_remote_reftests(parser, options)
+        return run_desktop_reftests(parser, options, args)
+    return run_remote_reftests(parser, options, args)
 
 
 if __name__ == "__main__":
     sys.exit(main())
 
--- a/testing/mach_commands.py
+++ b/testing/mach_commands.py
@@ -92,17 +92,17 @@ TEST_SUITES = {
     },
     'luciddream': {
         'mach_command': 'luciddream',
         'kwargs': {'test_paths': None},
     },
     'reftest': {
         'aliases': ('RR', 'rr', 'Rr'),
         'mach_command': 'reftest',
-        'kwargs': {'tests': None},
+        'kwargs': {'test_file': None},
     },
     'reftest-ipc': {
         'aliases': ('Ripc',),
         'mach_command': 'reftest-ipc',
         'kwargs': {'test_file': None},
     },
     'web-platform-tests': {
         'aliases': ('wpt',),
@@ -135,20 +135,17 @@ TEST_FLAVORS = {
     'chrome': {
         'mach_command': 'mochitest',
         'kwargs': {'flavor': 'chrome', 'test_paths': []},
     },
     'mochitest': {
         'mach_command': 'mochitest',
         'kwargs': {'flavor': 'mochitest', 'test_paths': []},
     },
-    'reftest': {
-        'mach_command': 'reftest',
-        'kwargs': {'tests': []}
-    },
+    'reftest': { },
     'steeplechase': { },
     'web-platform-tests': {
         'mach_command': 'web-platform-tests',
         'kwargs': {'include': []}
     },
     'webapprt-chrome': {
         'mach_command': 'mochitest',
         'kwargs': {'flavor': 'webapprt-chrome', 'test_paths': []},
--- a/testing/mozharness/configs/android/android_panda_releng.py
+++ b/testing/mozharness/configs/android/android_panda_releng.py
@@ -92,19 +92,18 @@ config = {
                 "--xre-path=../hostutils/xre",
                 "--utility-path=../hostutils/bin",
                 "--app=%(app_name)s",
                 "--ignore-window-size",
                 "--bootstrap",
                 "--http-port=%(http_port)s",
                 "--ssl-port=%(ssl_port)s",
                 "--symbols-path=%(symbols_path)s",
-                "--suite=crashtest",
+                "reftest/tests/testing/crashtest/crashtests.list"
             ],
-            "tests": ["reftest/tests/testing/crashtest/crashtests.list",],
             "run_filename": "remotereftest.py",
             "testsdir": "reftest"
         },
         "jittest": {
             "options": [
                 "bin/js",
                 "--remote",
                 "-j",
@@ -124,22 +123,21 @@ config = {
             "options": [
                 "--deviceIP=%(device_ip)s",
                 "--xre-path=../hostutils/xre",
                 "--utility-path=../hostutils/bin",
                 "--app=%(app_name)s",
                 "--ignore-window-size",
                 "--bootstrap",
                 "--extra-profile-file=jsreftest/tests/user.js",
+                "jsreftest/tests/jstests.list",
                 "--http-port=%(http_port)s",
                 "--ssl-port=%(ssl_port)s",
-                "--symbols-path=%(symbols_path)s",
-                "--suite=jstestbrowser",
+                "--symbols-path=%(symbols_path)s"
             ],
-            "tests": ["jsreftest/tests/jstests.list",],
             "run_filename": "remotereftest.py",
             "testsdir": "reftest"
         },
         "mochitest": {
             "options": [
                 "--dm_trans=sut",
                 "--deviceIP=%(device_ip)s",
                 "--xre-path=../hostutils/xre",
@@ -163,19 +161,18 @@ config = {
                 "--xre-path=../hostutils/xre",
                 "--utility-path=../hostutils/bin",
                 "--app=%(app_name)s",
                 "--ignore-window-size",
                 "--bootstrap",
                 "--http-port=%(http_port)s",
                 "--ssl-port=%(ssl_port)s",
                 "--symbols-path=%(symbols_path)s",
-                "--suite=reftest",
+                "reftest/tests/layout/reftests/reftest.list"
             ],
-            "tests": ["reftest/tests/layout/reftests/reftest.list"],
             "run_filename": "remotereftest.py",
             "testsdir": "reftest"
         },
         "robocop": {
             "options": [
                 "--dm_trans=sut",
                 "--deviceIP=%(device_ip)s",
                 "--xre-path=../hostutils/xre",
--- a/testing/mozharness/configs/android/androidarm.py
+++ b/testing/mozharness/configs/android/androidarm.py
@@ -135,19 +135,18 @@ config = {
                 "--deviceIP=%(device_ip)s",
                 "--devicePort=%(device_port)s",
                 "--http-port=%(http_port)s",
                 "--ssl-port=%(ssl_port)s",
                 "--httpd-path",
                 "%(modules_dir)s",
                 "--symbols-path=%(symbols_path)s",
                 "--total-chunks=16",
-                "--suite=reftest",
+                "tests/layout/reftests/reftest.list",
             ],
-            "tests": ["tests/layout/reftests/reftest.list"],
         },
         "crashtest": {
             "run_filename": "remotereftest.py",
             "testsdir": "reftest",
             "options": [
                 "--app=%(app)s",
                 "--ignore-window-size",
                 "--bootstrap",
@@ -157,19 +156,18 @@ config = {
                 "--deviceIP=%(device_ip)s",
                 "--devicePort=%(device_port)s",
                 "--http-port=%(http_port)s",
                 "--ssl-port=%(ssl_port)s",
                 "--httpd-path",
                 "%(modules_dir)s",
                 "--symbols-path=%(symbols_path)s",
                 "--total-chunks=2",
-                "--suite=crashtest",
+                "tests/testing/crashtest/crashtests.list",
             ],
-            "tests": ["tests/testing/crashtest/crashtests.list"],
         },
         "jsreftest": {
             "run_filename": "remotereftest.py",
             "testsdir": "reftest",
             "options": [
                 "--app=%(app)s",
                 "--ignore-window-size",
                 "--bootstrap",
@@ -178,21 +176,20 @@ config = {
                 "--utility-path=%(utility_path)s",
                 "--deviceIP=%(device_ip)s",
                 "--devicePort=%(device_port)s",
                 "--http-port=%(http_port)s",
                 "--ssl-port=%(ssl_port)s",
                 "--httpd-path",
                 "%(modules_dir)s",
                 "--symbols-path=%(symbols_path)s",
+                "../jsreftest/tests/jstests.list",
                 "--total-chunks=6",
                 "--extra-profile-file=jsreftest/tests/user.js",
-                "--suite=jstestbrowser",
             ],
-            "tests": ["../jsreftest/tests/jstests.list"],
         },
         "xpcshell": {
             "run_filename": "remotexpcshelltests.py",
             "testsdir": "xpcshell",
             "options": [
                 "--deviceIP=%(device_ip)s",
                 "--devicePort=%(device_port)s",
                 "--xre-path=%(xre_path)s",
@@ -313,77 +310,93 @@ config = {
             "extra_args": ["--this-chunk=3"],
         },
         "mochitest-gl-4": {
             "category": "mochitest-gl",
             "extra_args": ["--this-chunk=4"],
         },
         "reftest-1": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=16", "--this-chunk=1"],
+            "extra_args": ["--total-chunks=16", "--this-chunk=1",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-2": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=16", "--this-chunk=2"],
+            "extra_args": ["--total-chunks=16", "--this-chunk=2",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-3": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=16", "--this-chunk=3"],
+            "extra_args": ["--total-chunks=16", "--this-chunk=3",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-4": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=16", "--this-chunk=4"],
+            "extra_args": ["--total-chunks=16", "--this-chunk=4",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-5": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=16", "--this-chunk=5"],
+            "extra_args": ["--total-chunks=16", "--this-chunk=5",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-6": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=16", "--this-chunk=6"],
+            "extra_args": ["--total-chunks=16", "--this-chunk=6",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-7": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=16", "--this-chunk=7"],
+            "extra_args": ["--total-chunks=16", "--this-chunk=7",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-8": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=16", "--this-chunk=8"],
+            "extra_args": ["--total-chunks=16", "--this-chunk=8",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-9": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=16", "--this-chunk=9"],
+            "extra_args": ["--total-chunks=16", "--this-chunk=9",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-10": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=16", "--this-chunk=10"],
+            "extra_args": ["--total-chunks=16", "--this-chunk=10",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-11": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=16", "--this-chunk=11"],
+            "extra_args": ["--total-chunks=16", "--this-chunk=11",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-12": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=16", "--this-chunk=12"],
+            "extra_args": ["--total-chunks=16", "--this-chunk=12",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-13": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=16", "--this-chunk=13"],
+            "extra_args": ["--total-chunks=16", "--this-chunk=13",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-14": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=16", "--this-chunk=14"],
+            "extra_args": ["--total-chunks=16", "--this-chunk=14",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-15": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=16", "--this-chunk=15"],
+            "extra_args": ["--total-chunks=16", "--this-chunk=15",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-16": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=16", "--this-chunk=16"],
+            "extra_args": ["--total-chunks=16", "--this-chunk=16",
+                "tests/layout/reftests/reftest.list"]
         },
         "crashtest-1": {
             "category": "crashtest",
             "extra_args": ["--this-chunk=1"],
         },
         "crashtest-2": {
             "category": "crashtest",
             "extra_args": ["--this-chunk=2"],
--- a/testing/mozharness/configs/android/androidarm_4_3.py
+++ b/testing/mozharness/configs/android/androidarm_4_3.py
@@ -133,19 +133,18 @@ config = {
                 "--remote-webserver=%(remote_webserver)s",
                 "--xre-path=%(xre_path)s",
                 "--utility-path=%(utility_path)s",
                 "--http-port=%(http_port)s",
                 "--ssl-port=%(ssl_port)s",
                 "--httpd-path", "%(modules_dir)s",
                 "--symbols-path=%(symbols_path)s",
                 "--total-chunks=16",
-                "--suite=reftest",
+                "tests/layout/reftests/reftest.list",
             ],
-            "tests": ["tests/layout/reftests/reftest.list",],
         },
         "crashtest": {
             "run_filename": "remotereftest.py",
             "testsdir": "reftest",
             "options": [
                 "--app=%(app)s",
                 "--ignore-window-size",
                 "--dm_trans=adb",
@@ -154,37 +153,35 @@ config = {
                 "--xre-path=%(xre_path)s",
                 "--utility-path=%(utility_path)s",
                 "--http-port=%(http_port)s",
                 "--ssl-port=%(ssl_port)s",
                 "--httpd-path",
                 "%(modules_dir)s",
                 "--symbols-path=%(symbols_path)s",
                 "--total-chunks=2",
-                "--suite=crashtest",
+                "tests/testing/crashtest/crashtests.list",
             ],
-            "tests": ["tests/testing/crashtest/crashtests.list",],
         },
         "jsreftest": {
             "run_filename": "remotereftest.py",
             "testsdir": "reftest",
             "options": [
                 "--app=%(app)s",
                 "--ignore-window-size",
                 "--dm_trans=adb",
                 "--bootstrap",
                 "--remote-webserver=%(remote_webserver)s", "--xre-path=%(xre_path)s",
                 "--utility-path=%(utility_path)s", "--http-port=%(http_port)s",
                 "--ssl-port=%(ssl_port)s", "--httpd-path", "%(modules_dir)s",
                 "--symbols-path=%(symbols_path)s",
+                "../jsreftest/tests/jstests.list",
                 "--total-chunks=6",
                 "--extra-profile-file=jsreftest/tests/user.js",
-                "--suite=jstestbrowser",
             ],
-            "tests": ["../jsreftest/tests/jstests.list",],
         },
         "xpcshell": {
             "run_filename": "remotexpcshelltests.py",
             "testsdir": "xpcshell",
             "options": [
                 "--dm_trans=adb",
                 "--xre-path=%(xre_path)s",
                 "--testing-modules-dir=%(modules_dir)s",
@@ -373,205 +370,253 @@ config = {
             "extra_args": ["--this-chunk=3"],
         },
         "mochitest-gl-4": {
             "category": "mochitest-gl",
             "extra_args": ["--this-chunk=4"],
         },
         "reftest-1": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=48", "--this-chunk=1"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=1",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-2": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=48", "--this-chunk=2"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=2",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-3": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=48", "--this-chunk=3"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=3",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-4": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=48", "--this-chunk=4"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=4",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-5": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=48", "--this-chunk=5"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=5",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-6": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=48", "--this-chunk=6"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=6",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-7": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=48", "--this-chunk=7"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=7",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-8": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=48", "--this-chunk=8"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=8",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-9": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=48", "--this-chunk=9"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=9",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-10": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=48", "--this-chunk=10"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=10",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-11": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=48", "--this-chunk=11"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=11",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-12": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=48", "--this-chunk=12"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=12",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-13": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=48", "--this-chunk=13"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=13",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-14": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=48", "--this-chunk=14"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=14",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-15": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=48", "--this-chunk=15"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=15",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-16": {
             "category": "reftest",
-            "extra_args": ["--total-chunks=48", "--this-chunk=16"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=16",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-17": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=17"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=17",
+            "tests/layout/reftests/reftest.list"]
         },
        "reftest-18": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=18"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=18",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-19": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=19"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=19",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-20": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=20"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=20",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-21": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=21"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=21",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-22": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=22"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=22",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-23": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=23"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=23",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-24": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=24"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=24",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-25": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=25"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=25",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-26": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=26"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=26",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-27": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=27"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=27",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-28": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=28"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=28",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-29": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=29"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=29",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-30": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=30"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=30",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-31": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=31"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=31",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-32": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=32"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=32",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-33": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=33"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=33",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-34": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=34"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=34",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-35": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=35"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=35",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-36": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=36"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=36",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-37": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=37"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=37",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-38": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=38"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=38",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-39": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=39"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=39",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-40": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=40"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=40",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-41": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=41"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=41",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-42": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=42"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=42",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-43": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=43"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=43",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-44": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=44"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=44",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-45": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=45"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=45",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-46": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=46"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=46",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-47": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=47"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=47",
+            "tests/layout/reftests/reftest.list"]
         },
         "reftest-48": {
             "category": "reftest",
-            "extra args": ["--total-chunks=48", "--this-chunk=48"],
+            "extra_args": ["--total-chunks=48", "--this-chunk=48",
+            "tests/layout/reftests/reftest.list"]
         },
         "crashtest-1": {
             "category": "crashtest",
             "extra_args": ["--this-chunk=1"],
         },
         "crashtest-2": {
             "category": "crashtest",
             "extra_args": ["--this-chunk=2"],
--- a/testing/mozharness/configs/android/androidx86.py
+++ b/testing/mozharness/configs/android/androidx86.py
@@ -130,63 +130,49 @@ config = {
                         "--log-raw=%(raw_log_file)s",
                         "--log-errorsummary=%(error_summary_file)s",
                     ],
         },
     }, # end suite_definitions
     "test_suite_definitions": {
         "jsreftest": {
             "category": "reftest",
-            "tests": ["../jsreftest/tests/jstests.list"],
-            "extra_args": [
-                "--suite=jstestbrowser",
-                "--extra-profile-file=jsreftest/tests/user.js"
-            ]
+            "extra_args": ["../jsreftest/tests/jstests.list",
+                "--extra-profile-file=jsreftest/tests/user.js"]
         },
         "mochitest-1": {
             "category": "mochitest",
             "extra_args": ["--total-chunks=2", "--this-chunk=1"],
         },
         "mochitest-2": {
             "category": "mochitest",
             "extra_args": ["--total-chunks=2", "--this-chunk=2"],
         },
         "mochitest-gl": {
             "category": "mochitest",
             "extra_args": ["--subsuite=webgl"],
         },
         "reftest-1": {
             "category": "reftest",
-            "extra_args": [
-                "--suite=reftest",
-                "--total-chunks=3",
-                "--this-chunk=1",
-            ],
-            "tests": ["tests/layout/reftests/reftest.list"],
+            "extra_args": ["--total-chunks=3", "--this-chunk=1",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-2": {
-            "extra_args": [
-                "--suite=reftest",
-                "--total-chunks=3",
-                "--this-chunk=2",
-            ],
-            "tests": ["tests/layout/reftests/reftest.list"],
+            "category": "reftest",
+            "extra_args": ["--total-chunks=3", "--this-chunk=2",
+                "tests/layout/reftests/reftest.list"]
         },
         "reftest-3": {
-            "extra_args": [
-                "--suite=reftest",
-                "--total-chunks=3",
-                "--this-chunk=3",
-            ],
-            "tests": ["tests/layout/reftests/reftest.list"],
+            "category": "reftest",
+            "extra_args": ["--total-chunks=3", "--this-chunk=3",
+                "tests/layout/reftests/reftest.list"]
         },
         "crashtest": {
             "category": "reftest",
-            "extra_args": ["--suite=crashtest"],
-            "tests": ["tests/testing/crashtest/crashtests.list"]
+            "extra_args": ["tests/testing/crashtest/crashtests.list"]
         },
         "xpcshell": {
             "category": "xpcshell",
             # XXX --manifest is superceded by testing/config/mozharness/android_x86_config.py.
             # Remove when Gecko 35 no longer in tbpl.
             "extra_args": ["--manifest=tests/xpcshell_android.ini"]
         },
     }, # end of "test_definitions"
--- a/testing/mozharness/configs/b2g/emulator_automation_config.py
+++ b/testing/mozharness/configs/b2g/emulator_automation_config.py
@@ -69,19 +69,18 @@ config = {
                 "--logdir=%(logcat_dir)s",
                 "--remote-webserver=%(remote_webserver)s",
                 "--ignore-window-size",
                 "--xre-path=%(xre_path)s",
                 "--symbols-path=%(symbols_path)s",
                 "--busybox=%(busybox)s",
                 "--total-chunks=%(total_chunks)s",
                 "--this-chunk=%(this_chunk)s",
-                "--suite=crashtest",
+                "tests/testing/crashtest/crashtests.list"
             ],
-            "tests": ["tests/testing/crashtest/crashtests.list",],
             "run_filename": "runreftestb2g.py",
             "testsdir": "reftest"
         },
         "jsreftest": {
             "options": [
                 "--adbpath=%(adbpath)s",
                 "--b2gpath=%(b2gpath)s",
                 "--emulator=%(emulator)s",
@@ -90,18 +89,18 @@ config = {
                 "--remote-webserver=%(remote_webserver)s",
                 "--ignore-window-size",
                 "--xre-path=%(xre_path)s",
                 "--symbols-path=%(symbols_path)s",
                 "--busybox=%(busybox)s",
                 "--total-chunks=%(total_chunks)s",
                 "--this-chunk=%(this_chunk)s",
                 "--extra-profile-file=jsreftest/tests/user.js",
+                "jsreftest/tests/jstests.list"
             ],
-            "tests": ["jsreftest/tests/jstests.list",],
             "run_filename": "remotereftest.py",
             "testsdir": "reftest"
         },
         "mochitest": {
             "options": [
                 "--adbpath=%(adbpath)s",
                 "--b2gpath=%(b2gpath)s",
                 "--emulator=%(emulator)s",
@@ -157,18 +156,18 @@ config = {
                 "--remote-webserver=%(remote_webserver)s",
                 "--ignore-window-size",
                 "--xre-path=%(xre_path)s",
                 "--symbols-path=%(symbols_path)s",
                 "--busybox=%(busybox)s",
                 "--total-chunks=%(total_chunks)s",
                 "--this-chunk=%(this_chunk)s",
                 "--enable-oop",
+                "tests/layout/reftests/reftest.list"
             ],
-            "tests": ["tests/layout/reftests/reftest.list",],
             "run_filename": "runreftestsb2g.py",
             "testsdir": "reftest"
         },
         "xpcshell": {
             "options": [
                 "--adbpath=%(adbpath)s",
                 "--b2gpath=%(b2gpath)s",
                 "--emulator=%(emulator)s",
--- a/testing/mozharness/configs/unittests/linux_unittest.py
+++ b/testing/mozharness/configs/unittests/linux_unittest.py
@@ -1,33 +1,33 @@
 import os
 import platform
 
 # OS Specifics
-ABS_WORK_DIR = os.path.join(os.getcwd(), "build")
+ABS_WORK_DIR = os.path.join(os.getcwd(), 'build')
 BINARY_PATH = os.path.join(ABS_WORK_DIR, "firefox", "firefox-bin")
 INSTALLER_PATH = os.path.join(ABS_WORK_DIR, "installer.tar.bz2")
 XPCSHELL_NAME = "xpcshell"
-EXE_SUFFIX = ""
+EXE_SUFFIX = ''
 DISABLE_SCREEN_SAVER = True
 ADJUST_MOUSE_AND_SCREEN = False
-if platform.architecture()[0] == "64bit":
+if platform.architecture()[0] == '64bit':
     TOOLTOOL_MANIFEST_PATH = "config/tooltool-manifests/linux64/releng.manifest"
     MINIDUMP_STACKWALK_PATH = "linux64-minidump_stackwalk"
 else:
     TOOLTOOL_MANIFEST_PATH = "config/tooltool-manifests/linux32/releng.manifest"
     MINIDUMP_STACKWALK_PATH = "linux32-minidump_stackwalk"
 
 #####
 config = {
     "buildbot_json_path": "buildprops.json",
     "exes": {
-        "python": "/tools/buildbot/bin/python",
-        "virtualenv": ["/tools/buildbot/bin/python", "/tools/misc-python/virtualenv.py"],
-        "tooltool.py": "/tools/tooltool.py",
+        'python': '/tools/buildbot/bin/python',
+        'virtualenv': ['/tools/buildbot/bin/python', '/tools/misc-python/virtualenv.py'],
+        'tooltool.py': "/tools/tooltool.py",
     },
     "find_links": [
         "http://pypi.pvt.build.mozilla.org/pub",
         "http://pypi.pub.build.mozilla.org/pub",
     ],
     "pip_index": False,
     ###
     "installer_path": INSTALLER_PATH,
@@ -195,73 +195,45 @@ config = {
     },
     # local webapprt suites
     "all_webapprt_suites": {
         "chrome": ["--webapprt-chrome", "--browser-arg=-test-mode"],
         "content": ["--webapprt-content"]
     },
     # local reftest suites
     "all_reftest_suites": {
-        "reftest": {
-            "options": ["--suite=reftest"],
-            "tests": ["tests/reftest/tests/layout/reftests/reftest.list"]
-        },
-        "crashtest": {
-            "options": ["--suite=crashtest"],
-            "tests": ["tests/reftest/tests/testing/crashtest/crashtests.list"]
-        },
-        "jsreftest": {
-            "options":["--extra-profile-file=tests/jsreftest/tests/user.js",
-                       "--suite=jstestbrowser"],
-            "tests": ["tests/jsreftest/tests/jstests.list"]
-        },
-        "reftest-ipc": {
-            "env": {
-                "MOZ_OMTC_ENABLED": "1",
-                "MOZ_DISABLE_CONTEXT_SHARING_GLX": "1"
-            },
-            "options": ["--suite=reftest",
-                        "--setpref=browser.tabs.remote=true",
-                        "--setpref=browser.tabs.remote.autostart=true",
-                        "--setpref=layers.offmainthreadcomposition.testing.enabled=true",
-                        "--setpref=layers.async-pan-zoom.enabled=true"],
-            "tests": ["tests/reftest/tests/layout/reftests/reftest-sanity/reftest.list"]
-        },
-        "reftest-no-accel": {
-            "options": ["--suite=reftest",
-                        "--setpref=layers.acceleration.force-enabled=disabled"],
-            "tests": ["tests/reftest/tests/layout/reftests/reftest.list"]},
-        "crashtest-ipc": {
-            "env": {
-                "MOZ_OMTC_ENABLED": "1",
-                "MOZ_DISABLE_CONTEXT_SHARING_GLX": "1"
-            },
-            "options": ["--suite=crashtest",
-                        "--setpref=browser.tabs.remote=true",
-                        "--setpref=browser.tabs.remote.autostart=true",
-                        "--setpref=layers.offmainthreadcomposition.testing.enabled=true",
-                        "--setpref=layers.async-pan-zoom.enabled=true"],
-            "tests": ["tests/reftest/tests/testing/crashtest/crashtests.list"]
-        },
+        "reftest": ["tests/reftest/tests/layout/reftests/reftest.list"],
+        "crashtest": ["tests/reftest/tests/testing/crashtest/crashtests.list"],
+        "jsreftest": ["--extra-profile-file=tests/jsreftest/tests/user.js", "tests/jsreftest/tests/jstests.list"],
+        "reftest-ipc": {'env': {'MOZ_OMTC_ENABLED': '1',
+                                'MOZ_DISABLE_CONTEXT_SHARING_GLX': '1'},
+                        'options': ['--setpref=browser.tabs.remote=true',
+                                    '--setpref=browser.tabs.remote.autostart=true',
+                                    '--setpref=layers.offmainthreadcomposition.testing.enabled=true',
+                                    '--setpref=layers.async-pan-zoom.enabled=true',
+                                    'tests/reftest/tests/layout/reftests/reftest-sanity/reftest.list']},
+        "reftest-no-accel": ['--setpref=layers.acceleration.force-enabled=disabled',
+                             'tests/reftest/tests/layout/reftests/reftest.list'],
+        "crashtest-ipc": {'env': {'MOZ_OMTC_ENABLED': '1',
+                                  'MOZ_DISABLE_CONTEXT_SHARING_GLX': '1'},
+                          'options': ['--setpref=browser.tabs.remote=true',
+                                      '--setpref=browser.tabs.remote.autostart=true',
+                                      '--setpref=layers.offmainthreadcomposition.testing.enabled=true',
+                                      '--setpref=layers.async-pan-zoom.enabled=true',
+                                      'tests/reftest/tests/testing/crashtest/crashtests.list']},
     },
     "all_xpcshell_suites": {
-        "xpcshell": {
-            "options": ["--xpcshell=%(abs_app_dir)s/" + XPCSHELL_NAME,
-                        "--manifest=tests/xpcshell/tests/all-test-dirs.list"],
-            "tests": []
-        },
-        "xpcshell-addons": {
-            "options": ["--xpcshell=%(abs_app_dir)s/" + XPCSHELL_NAME,
-                        "--tag=addons",
-                        "--manifest=tests/xpcshell/tests/all-test-dirs.list"],
-            "tests": []
-        },
+        "xpcshell": ["--manifest=tests/xpcshell/tests/all-test-dirs.list",
+                     "%(abs_app_dir)s/" + XPCSHELL_NAME],
+        "xpcshell-addons": ["--manifest=tests/xpcshell/tests/all-test-dirs.list",
+                            "--tag=addons",
+                            "%(abs_app_dir)s/" + XPCSHELL_NAME]
     },
     "all_cppunittest_suites": {
-        "cppunittest": ["tests/cppunittest"]
+        "cppunittest": ['tests/cppunittest']
     },
     "all_gtest_suites": {
         "gtest": []
     },
     "all_jittest_suites": {
         "jittest": [],
         "jittest1": ["--total-chunks=2", "--this-chunk=1"],
         "jittest2": ["--total-chunks=2", "--this-chunk=2"],
--- a/testing/mozharness/configs/unittests/mac_unittest.py
+++ b/testing/mozharness/configs/unittests/mac_unittest.py
@@ -162,55 +162,34 @@ config = {
     },
     # local webapprt suites
     "all_webapprt_suites": {
         "chrome": ["--webapprt-chrome", "--browser-arg=-test-mode"],
         "content": ["--webapprt-content"]
     },
     # local reftest suites
     "all_reftest_suites": {
-        "reftest": {
-            'options': ["--suite=reftest"],
-            'tests': ["tests/reftest/tests/layout/reftests/reftest.list"]
-        },
-        "crashtest": {
-            'options': ["--suite=crashtest"],
-            'tests': ["tests/reftest/tests/testing/crashtest/crashtests.list"]
-        },
-        "jsreftest": {
-            'options':["--extra-profile-file=tests/jsreftest/tests/user.js"],
-            'tests': ["tests/jsreftest/tests/jstests.list"]
-        },
-        "reftest-ipc": {
-            'options': ['--suite=reftest',
-                        '--setpref=browser.tabs.remote=true',
+        "reftest": ["tests/reftest/tests/layout/reftests/reftest.list"],
+        "crashtest": ["tests/reftest/tests/testing/crashtest/crashtests.list"],
+        "jsreftest": ["--extra-profile-file=tests/jsreftest/tests/user.js", "tests/jsreftest/tests/jstests.list"],
+        "reftest-ipc": ['--setpref=browser.tabs.remote=true',
                         '--setpref=browser.tabs.remote.autostart=true',
-                        '--setpref=layers.async-pan-zoom.enabled=true'],
-            'tests': ['tests/reftest/tests/layout/reftests/reftest-sanity/reftest.list']
-        },
-        "crashtest-ipc": {
-            'options': ['--suite=crashtest',
-                        '--setpref=browser.tabs.remote=true',
-                        '--setpref=browser.tabs.remote.autostart=true',
-                        '--setpref=layers.async-pan-zoom.enabled=true'],
-            'tests': ['tests/reftest/tests/testing/crashtest/crashtests.list']
-        },
+                        '--setpref=layers.async-pan-zoom.enabled=true',
+                        'tests/reftest/tests/layout/reftests/reftest-sanity/reftest.list'],
+        "crashtest-ipc": ['--setpref=browser.tabs.remote=true',
+                          '--setpref=browser.tabs.remote.autostart=true',
+                          '--setpref=layers.async-pan-zoom.enabled=true',
+                          'tests/reftest/tests/testing/crashtest/crashtests.list'],
     },
     "all_xpcshell_suites": {
-        "xpcshell": {
-            'options': ["--xpcshell=%(abs_app_dir)s/" + XPCSHELL_NAME,
-                        "--manifest=tests/xpcshell/tests/all-test-dirs.list"],
-            'tests': []
-        },
-        "xpcshell-addons": {
-            'options': ["--xpcshell=%(abs_app_dir)s/" + XPCSHELL_NAME,
-                        "--tag=addons",
-                        "--manifest=tests/xpcshell/tests/all-test-dirs.list"],
-            'tests': []
-        },
+        "xpcshell": ["--manifest=tests/xpcshell/tests/all-test-dirs.list",
+                     "%(abs_app_dir)s/" + XPCSHELL_NAME],
+        "xpcshell-addons": ["--manifest=tests/xpcshell/tests/all-test-dirs.list",
+                            "--tag=addons",
+                            "%(abs_app_dir)s/" + XPCSHELL_NAME]
     },
     "all_cppunittest_suites": {
         "cppunittest": ['tests/cppunittest']
     },
     "all_gtest_suites": {
         "gtest": []
     },
     "all_jittest_suites": {
--- a/testing/mozharness/configs/unittests/win_unittest.py
+++ b/testing/mozharness/configs/unittests/win_unittest.py
@@ -172,66 +172,38 @@ config = {
     },
     # local webapprt suites
     "all_webapprt_suites": {
         "chrome": ["--webapprt-chrome", "--browser-arg=-test-mode"],
         "content": ["--webapprt-content"]
     },
     # local reftest suites
     "all_reftest_suites": {
-        "reftest": {
-            'options': ["--suite=reftest"],
-            'tests': ["tests/reftest/tests/layout/reftests/reftest.list"]
-        },
-        "crashtest": {
-            'options': ["--suite=crashtest"],
-            'tests': ["tests/reftest/tests/testing/crashtest/crashtests.list"]
-        },
-        "jsreftest": {
-            'options':["--extra-profile-file=tests/jsreftest/tests/user.js"],
-            'tests': ["tests/jsreftest/tests/jstests.list"]
-        },
-        "reftest-ipc": {
-            'options': ['--suite=reftest',
-                        '--setpref=browser.tabs.remote=true',
+        "reftest": ["tests/reftest/tests/layout/reftests/reftest.list"],
+        "crashtest": ["tests/reftest/tests/testing/crashtest/crashtests.list"],
+        "jsreftest": ["--extra-profile-file=tests/jsreftest/tests/user.js", "tests/jsreftest/tests/jstests.list"],
+        "reftest-ipc": ['--setpref=browser.tabs.remote=true',
                         '--setpref=browser.tabs.remote.autostart=true',
-                        '--setpref=layers.async-pan-zoom.enabled=true'],
-            'tests': ['tests/reftest/tests/layout/reftests/reftest-sanity/reftest.list']
-        },
-        "reftest-no-accel": {
-            "options": ["--suite=reftest",
-                        "--setpref=gfx.direct2d.disabled=true",
-                        "--setpref=layers.acceleration.disabled=true"],
-            "tests": ["tests/reftest/tests/layout/reftests/reftest.list"]
-        },
-        "reftest-omtc": {
-            "options": ["--suite=reftest",
-                        "--setpref=layers.offmainthreadcomposition.enabled=true"],
-            "tests": ["tests/reftest/tests/layout/reftests/reftest.list"]
-        },
-        "crashtest-ipc": {
-            "options": ["--suite=crashtest",
-                        '--setpref=browser.tabs.remote=true',
-                        '--setpref=browser.tabs.remote.autostart=true',
-                        '--setpref=layers.async-pan-zoom.enabled=true'],
-            "tests": ['tests/reftest/tests/testing/crashtest/crashtests.list'],
-        },
+                        '--setpref=layers.async-pan-zoom.enabled=true',
+                        'tests/reftest/tests/layout/reftests/reftest-sanity/reftest.list'],
+        "reftest-no-accel": ["--setpref=gfx.direct2d.disabled=true", "--setpref=layers.acceleration.disabled=true",
+                             "tests/reftest/tests/layout/reftests/reftest.list"],
+        "reftest-omtc": ["--setpref=layers.offmainthreadcomposition.enabled=true",
+                         "tests/reftest/tests/layout/reftests/reftest.list"],
+        "crashtest-ipc": ['--setpref=browser.tabs.remote=true',
+                          '--setpref=browser.tabs.remote.autostart=true',
+                          '--setpref=layers.async-pan-zoom.enabled=true',
+                          'tests/reftest/tests/testing/crashtest/crashtests.list'],
     },
     "all_xpcshell_suites": {
-        "xpcshell": {
-            'options': ["--xpcshell=%(abs_app_dir)s/" + XPCSHELL_NAME,
-                        "--manifest=tests/xpcshell/tests/all-test-dirs.list"],
-            'tests': []
-        },
-        "xpcshell-addons": {
-            'options': ["--xpcshell=%(abs_app_dir)s/" + XPCSHELL_NAME,
-                        "--tag=addons",
-                        "--manifest=tests/xpcshell/tests/all-test-dirs.list"],
-            'tests': []
-        },
+        "xpcshell": ["--manifest=tests/xpcshell/tests/all-test-dirs.list",
+                     "%(abs_app_dir)s/" + XPCSHELL_NAME],
+        "xpcshell-addons": ["--manifest=tests/xpcshell/tests/all-test-dirs.list",
+                            "--tag=addons",
+                            "%(abs_app_dir)s/" + XPCSHELL_NAME]
     },
     "all_cppunittest_suites": {
         "cppunittest": ['tests/cppunittest']
     },
     "all_gtest_suites": {
         "gtest": []
     },
     "all_jittest_suites": {
--- a/testing/mozharness/scripts/android_emulator_unittest.py
+++ b/testing/mozharness/scripts/android_emulator_unittest.py
@@ -478,25 +478,16 @@ class AndroidEmulatorTest(BlobUploadMixi
 
         for arg in self.test_suite_definitions[self.test_suite]["extra_args"]:
             argname = arg.split('=')[0]
             # only add the extra arg if it wasn't already defined by in-tree configs
             if any(a.split('=')[0] == argname for a in cmd):
                 continue
             cmd.append(arg)
 
-        tests = None
-        if "tests" in self.test_suite_definitions[self.test_suite]:
-            tests = self.test_suite_definitions[self.test_suite]["tests"]
-        elif "tests" in self.config["suite_definitions"][suite_category]:
-            tests = self.config["suite_definitions"][suite_category]["tests"]
-
-        if tests:
-            cmd.extend(tests)
-
         return cmd
 
     def _tooltool_fetch(self, url):
         c = self.config
         dirs = self.query_abs_dirs()
 
         manifest_path = self.download_file(
             url,
--- a/testing/mozharness/scripts/android_panda.py
+++ b/testing/mozharness/scripts/android_panda.py
@@ -247,19 +247,16 @@ class PandaTest(TestingMixin, MercurialS
                     self._install_app()
                 cmd = abs_base_cmd[:]
                 replace_dict = {}
                 for arg in suites[suite]:
                     cmd.append(arg % replace_dict)
 
                 cmd = self.append_harness_extra_args(cmd)
 
-                tests = self.config["suite_definitions"][suite_category].get("tests", [])
-                abs_base_cmd += tests
-
                 tbpl_status, log_level = None, None
                 error_list = BaseErrorList + [{
                     'regex': re.compile(r"(?:TEST-UNEXPECTED-FAIL|PROCESS-CRASH) \| .* \| (application crashed|missing output line for total leaks!|negative leaks caught!|\d+ bytes leaked)"),
                     'level': ERROR,
                 }]
                 c = self.config
                 if c.get('minidump_stackwalk_path'):
                     env['MINIDUMP_STACKWALK'] = c['minidump_stackwalk_path']
--- a/testing/mozharness/scripts/androidx86_emulator_unittest.py
+++ b/testing/mozharness/scripts/androidx86_emulator_unittest.py
@@ -447,21 +447,16 @@ class AndroidEmulatorTest(BlobUploadMixi
 
         for arg in self.test_suite_definitions[suite_name]["extra_args"]:
             argname = arg.split('=')[0]
             # only add the extra arg if it wasn't already defined by in-tree configs
             if any(a.split('=')[0] == argname for a in cmd):
                 continue
             cmd.append(arg)
 
-        if "tests" in self.test_suite_definitions[suite_name]:
-            cmd.extend(self.suite_definitions[suite_name]["tests"])
-        elif "tests" in c["suite_definitions"][suite_category]:
-            cmd.extend(self.config["suite_definitions"][suite_category]["tests"])
-
         return cmd
 
     def preflight_run_tests(self):
         super(AndroidEmulatorTest, self).preflight_run_tests()
 
         if not os.path.isfile(self.adb_path):
             self.fatal("The adb binary '%s' is not a valid file!" % self.adb_path)
 
--- a/testing/mozharness/scripts/b2g_desktop_unittest.py
+++ b/testing/mozharness/scripts/b2g_desktop_unittest.py
@@ -149,29 +149,24 @@ class B2GDesktopTest(BlobUploadMixin, Te
             'this_chunk': self.config.get('this_chunk'),
             'cert_path': dirs['abs_certs_dir'],
             'browser_arg': self.config.get('browser_arg'),
             'raw_log_file': raw_log_file,
             'error_summary_file': error_summary_file,
         }
 
         if suite not in self.config["suite_definitions"]:
-            self.fatal("'%s' not defined in the config!" % suite)
+            self.fatal("'%s' not defined in the config!" % suite),
 
         options = self.config["suite_definitions"][suite]["options"]
         if options:
             for option in options:
                 option = option % str_format_values
                 if not option.endswith('None'):
                     cmd.append(option)
-
-        tests = self.config["suite_definitions"][suite].get("tests")
-        if tests:
-            cmd.extend(tests)
-
         return cmd
 
     def download_and_extract(self):
         suite_category = self.config['test_suite']
         super(B2GDesktopTest, self).download_and_extract(suite_categories=[suite_category])
 
     def preflight_run_tests(self):
         super(B2GDesktopTest, self).preflight_run_tests()
--- a/testing/mozharness/scripts/b2g_emulator_unittest.py
+++ b/testing/mozharness/scripts/b2g_emulator_unittest.py
@@ -276,21 +276,16 @@ class B2GEmulatorTest(TestingMixin, VCSM
             self.fatal("Key '%s' not defined in the config!" % suite)
 
         options = self.config["suite_definitions"][suite]["options"]
         if options:
             for option in options:
                 option = option % str_format_values
                 if not option.endswith('None'):
                     cmd.append(option)
-
-        tests = self.config["suite_definitions"][suite].get("tests", [])
-        if tests:
-            cmd.extend(tests)
-
         return cmd
 
     def _query_adb(self):
         return self.which('adb') or \
             os.getenv('ADB_PATH') or \
             os.path.join(self.query_abs_dirs()['abs_b2g-distro_dir'],
                          'out', 'host', 'linux-x86', 'bin', 'adb')
 
@@ -336,20 +331,19 @@ class B2GEmulatorTest(TestingMixin, VCSM
         error_list = self.error_list
         error_list.extend(BaseErrorList)
 
         suite = self.config['test_suite']
         if suite not in self.test_suites:
             self.fatal("Don't know how to run --test-suite '%s'!" % suite)
 
         cmd = self._query_abs_base_cmd(suite)
+        cwd = dirs['abs_%s_dir' % suite]
         cmd = self.append_harness_extra_args(cmd)
 
-        cwd = dirs['abs_%s_dir' % suite]
-
         # TODO we probably have to move some of the code in
         # scripts/desktop_unittest.py and scripts/marionette.py to
         # mozharness.mozilla.testing.unittest so we can share it.
         # In the short term, I'm ok with some duplication of code if it
         # expedites things; please file bugs to merge if that happens.
 
         suite_name = [x for x in self.test_suites if x in self.config['test_suite']][0]
         if self.config.get('this_chunk'):
--- a/testing/mozharness/scripts/desktop_unittest.py
+++ b/testing/mozharness/scripts/desktop_unittest.py
@@ -583,18 +583,18 @@ class DesktopUnittest(TestingMixin, Merc
 
                     # Mac specific, but points to abs_app_dir on other
                     # platforms.
                     'abs_res_dir': abs_res_dir,
                 }
                 options_list = []
                 env = {}
                 if isinstance(suites[suite], dict):
-                    options_list = suites[suite]['options'] + suites[suite].get("tests", [])
-                    env = copy.deepcopy(suites[suite].get('env', {}))
+                    options_list = suites[suite]['options']
+                    env = copy.deepcopy(suites[suite]['env'])
                 else:
                     options_list = suites[suite]
 
                 for arg in options_list:
                     cmd.append(arg % replace_dict)
 
                 cmd = self.append_harness_extra_args(cmd)
 
--- a/testing/testsuite-targets.mk
+++ b/testing/testsuite-targets.mk
@@ -169,17 +169,17 @@ endif
 RUN_REFTEST = rm -f ./$@.log && $(PYTHON) _tests/reftest/runreftest.py \
   --extra-profile-file=$(DIST)/plugins \
   $(SYMBOLS_PATH) $(EXTRA_TEST_ARGS) $(1) | tee ./$@.log
 
 REMOTE_REFTEST = rm -f ./$@.log && $(PYTHON) _tests/reftest/remotereftest.py \
   --dm_trans=$(DM_TRANS) --ignore-window-size \
   --app=$(TEST_PACKAGE_NAME) --deviceIP=${TEST_DEVICE} --xre-path=${MOZ_HOST_BIN} \
   --httpd-path=_tests/modules \
-  $(SYMBOLS_PATH) $(EXTRA_TEST_ARGS) $(1) | tee ./$@.log
+  $(SYMBOLS_PATH) $(EXTRA_TEST_ARGS) '$(1)' | tee ./$@.log
 
 RUN_REFTEST_B2G = rm -f ./$@.log && $(PYTHON) _tests/reftest/runreftestb2g.py \
   --remote-webserver=10.0.2.2 --b2gpath=${B2G_PATH} --adbpath=${ADB_PATH} \
   --xre-path=${MOZ_HOST_BIN} $(SYMBOLS_PATH) --ignore-window-size \
   --httpd-path=_tests/modules \
   $(EXTRA_TEST_ARGS) '$(1)' | tee ./$@.log
 
 ifeq ($(OS_ARCH),WINNT) #{
@@ -204,17 +204,17 @@ reftest-remote:
     elif [ ! -d ${MOZ_HOST_BIN} ]; then \
         echo 'MOZ_HOST_BIN does not specify a directory'; \
     elif [ ! -f ${MOZ_HOST_BIN}/xpcshell ]; then \
         echo 'xpcshell not found in MOZ_HOST_BIN'; \
     elif [ '${TEST_DEVICE}' = '' -a '$(DM_TRANS)' != 'adb' ]; then \
         echo 'please prepare your host with the environment variable TEST_DEVICE'; \
     else \
         ln -s $(abspath $(topsrcdir)) _tests/reftest/tests; \
-        $(call REMOTE_REFTEST,'tests/$(TEST_PATH)'); \
+        $(call REMOTE_REFTEST,tests/$(TEST_PATH)); \
         $(CHECK_TEST_ERROR); \
     fi
 
 reftest-b2g: TEST_PATH?=layout/reftests/reftest.list
 reftest-b2g:
 	@if [ '${MOZ_HOST_BIN}' = '' ]; then \
 		echo 'environment variable MOZ_HOST_BIN must be set to a directory containing host xpcshell'; \
 	elif [ ! -d ${MOZ_HOST_BIN} ]; then \
--- a/testing/xpcshell/Makefile.in
+++ b/testing/xpcshell/Makefile.in
@@ -5,17 +5,16 @@
 
 include $(topsrcdir)/config/rules.mk
 
 # Harness files from the srcdir
 TEST_HARNESS_FILES := \
   runxpcshelltests.py \
   remotexpcshelltests.py \
   runtestsb2g.py \
-  xpcshellcommandline.py \
   head.js \
   node-spdy \
   moz-spdy \
   node-http2 \
   moz-http2 \
   $(NULL)
 
 # Extra files needed from $(topsrcdir)/build
--- a/testing/xpcshell/mach_commands.py
+++ b/testing/xpcshell/mach_commands.py
@@ -20,113 +20,193 @@ from mozbuild.base import (
 )
 
 from mach.decorators import (
     CommandArgument,
     CommandProvider,
     Command,
 )
 
-from xpcshellcommandline import parser_desktop, parser_remote, parser_b2g
+_parser = argparse.ArgumentParser()
+structured.commandline.add_logging_group(_parser)
 
 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_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__))
 
 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.
 class InvalidTestPathError(Exception):
     """Exception raised when the test path is not valid."""
 
 
 class XPCShellRunner(MozbuildObject):
     """Run xpcshell tests."""
     def run_suite(self, **kwargs):
-        return self._run_xpcshell_harness(**kwargs)
+        from manifestparser import TestManifest
+        manifest = TestManifest(manifests=[os.path.join(self.topobjdir,
+            '_tests', 'xpcshell', 'xpcshell.ini')])
+
+        return self._run_xpcshell_harness(manifest=manifest, **kwargs)
 
-    def run_test(self, **kwargs):
+    def run_test(self, test_paths, interactive=False,
+                 keep_going=False, sequential=False, shuffle=False,
+                 debugger=None, debuggerArgs=None, debuggerInteractive=None,
+                 jsDebugger=False, jsDebuggerPort=None,
+                 rerun_failures=False, test_objects=None, verbose=False,
+                 log=None, test_tags=None, dump_tests=None,
+                 # ignore parameters from other platforms' options
+                 **kwargs):
         """Runs an individual xpcshell test."""
         from mozbuild.testing import TestResolver
         from manifestparser import TestManifest
 
         # 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)
 
         src_build_path = os.path.join(self.topsrcdir, 'mozilla', 'build')
         if os.path.isdir(src_build_path):
             sys.path.append(src_build_path)
 
-        self.run_suite(**kwargs)
+        if test_paths == 'all':
+            self.run_suite(interactive=interactive,
+                           keep_going=keep_going, shuffle=shuffle, sequential=sequential,
+                           debugger=debugger, debuggerArgs=debuggerArgs,
+                           debuggerInteractive=debuggerInteractive,
+                           jsDebugger=jsDebugger, jsDebuggerPort=jsDebuggerPort,
+                           rerun_failures=rerun_failures,
+                           verbose=verbose, log=log, test_tags=test_tags, dump_tests=dump_tests)
+            return
+        elif test_paths:
+            test_paths = [self._wrap_path_argument(p).relpath() for p in test_paths]
+
+        if test_objects:
+            tests = test_objects
+        else:
+            resolver = self._spawn(TestResolver)
+            tests = list(resolver.resolve_tests(paths=test_paths,
+                flavor='xpcshell'))
+
+        if not tests:
+            raise InvalidTestPathError('We could not find an xpcshell test '
+                'for the passed test path. Please select a path that is '
+                'a test file or is a directory containing xpcshell tests.')
+
+        # Dynamically write out a manifest holding all the discovered tests.
+        manifest = TestManifest()
+        manifest.tests.extend(tests)
 
+        args = {
+            'interactive': interactive,
+            'keep_going': keep_going,
+            'shuffle': shuffle,
+            'sequential': sequential,
+            'debugger': debugger,
+            'debuggerArgs': debuggerArgs,
+            'debuggerInteractive': debuggerInteractive,
+            'jsDebugger': jsDebugger,
+            'jsDebuggerPort': jsDebuggerPort,
+            'rerun_failures': rerun_failures,
+            'manifest': manifest,
+            'verbose': verbose,
+            'log': log,
+            'test_tags': test_tags,
+            'dump_tests': dump_tests,
+        }
 
-    def _run_xpcshell_harness(self, **kwargs):
+        return self._run_xpcshell_harness(**args)
+
+    def _run_xpcshell_harness(self, manifest,
+                              test_path=None, shuffle=False, interactive=False,
+                              keep_going=False, sequential=False,
+                              debugger=None, debuggerArgs=None, debuggerInteractive=None,
+                              jsDebugger=False, jsDebuggerPort=None,
+                              rerun_failures=False, verbose=False, log=None, test_tags=None,
+                              dump_tests=None):
+
         # 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')
         modules_dir = os.path.join(self.topobjdir, '_tests', 'modules')
         # We want output from the test to be written immediately if we are only
         # running a single test.
-        single_test = (kwargs["testPaths"] is not None or
+        single_test = (test_path is not None or
                        (manifest and len(manifest.test_paths())==1))
-        sequential = kwargs["sequential"] or single_test
-
-        if kwargs["xpcshell"] is None:
-            kwargs["xpcshell"] = self.get_binary_path('xpcshell')
-
-        if kwargs["mozInfo"] is None:
-            kwargs["mozInfo"] = os.path.join(self.topobjdir, 'mozinfo.json')
-
-        if kwargs["symbolsPath"] is None:
-            kwargs["symbolsPath"] = os.path.join(self.distdir, 'crashreporter-symbols')
+        sequential = sequential or single_test
 
-        if kwargs["logfiles"] is None:
-            kwargs["logfiles"] = False
-
-        if kwargs["profileName"] is None:
-            kwargs["profileName"] = "firefox"
-
-        if kwargs["pluginsPath"] is None:
-            kwargs['pluginsPath'] = os.path.join(self.distdir, 'plugins')
+        args = {
+            'manifest': manifest,
+            'xpcshell': self.get_binary_path('xpcshell'),
+            'mozInfo': os.path.join(self.topobjdir, 'mozinfo.json'),
+            'symbolsPath': os.path.join(self.distdir, 'crashreporter-symbols'),
+            'interactive': interactive,
+            'keepGoing': keep_going,
+            'logfiles': False,
+            'sequential': sequential,
+            'shuffle': shuffle,
+            'testsRootDir': tests_dir,
+            'testingModulesDir': modules_dir,
+            'profileName': 'firefox',
+            'verbose': verbose or single_test,
+            'xunitFilename': os.path.join(self.statedir, 'xpchsell.xunit.xml'),
+            'xunitName': 'xpcshell',
+            'pluginsPath': os.path.join(self.distdir, 'plugins'),
+            'debugger': debugger,
+            'debuggerArgs': debuggerArgs,
+            'debuggerInteractive': debuggerInteractive,
+            'jsDebugger': jsDebugger,
+            'jsDebuggerPort': jsDebuggerPort,
+            'test_tags': test_tags,
+            'dump_tests': dump_tests,
+            'utility_path': self.bindir,
+        }
 
-        if kwargs["utility_path"] is None:
-            kwargs['utility_path'] = self.bindir
+        if test_path is not None:
+            args['testPath'] = test_path
 
-        if kwargs["manifest"] is None:
-            kwargs["manifest"] = os.path.join(tests_dir, "xpcshell.ini")
-
-        if kwargs["failure_manifest"] is None:
-            kwargs["failure_manifest"] = os.path.join(self.statedir, 'xpcshell.failures.ini')
+        # A failure manifest is written by default. If --rerun-failures is
+        # specified and a prior failure manifest is found, the prior manifest
+        # will be run. A new failure manifest is always written over any
+        # prior failure manifest.
+        failure_manifest_path = os.path.join(self.statedir, 'xpcshell.failures.ini')
+        rerun_manifest_path = os.path.join(self.statedir, 'xpcshell.rerun.ini')
+        if os.path.exists(failure_manifest_path) and rerun_failures:
+            shutil.move(failure_manifest_path, rerun_manifest_path)
+            args['manifest'] = rerun_manifest_path
+        elif os.path.exists(failure_manifest_path):
+            os.remove(failure_manifest_path)
+        elif rerun_failures:
+            print("No failures were found to re-run.")
+            return 0
+        args['failureManifest'] = failure_manifest_path
 
         # Python through 2.7.2 has issues with unicode in some of the
         # arguments. Work around that.
         filtered_args = {}
-        for k, v in kwargs.iteritems():
+        for k, v in args.items():
             if isinstance(v, unicode_type):
                 v = v.encode('utf-8')
 
             if isinstance(k, unicode_type):
                 k = k.encode('utf-8')
 
             filtered_args[k] = v
 
@@ -152,71 +232,83 @@ class AndroidXPCShellRunner(MozbuildObje
         else:
             if ip:
                 dm = mozdevice.DroidSUT(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, **kwargs):
+    def run_test(self,
+                 test_paths, keep_going,
+                 devicemanager, ip, port, remote_test_root, no_setup, local_apk,
+                 test_objects=None, log=None,
+                 # 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(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
-
-        if not kwargs["localLib"]:
-            kwargs["localLib"] = os.path.join(self.topobjdir, 'dist/fennec')
-
-        if not kwargs["localBin"]:
-            kwargs["localBin"] = os.path.join(self.topobjdir, 'dist/bin')
+        dm = self.get_devicemanager(devicemanager, ip, port, remote_test_root)
 
-        if not kwargs["testingModulesDir"]:
-            kwargs["testingModulesDir"] = os.path.join(self.topobjdir, '_tests/modules')
-
-        if not kwargs["mozInfo"]:
-            kwargs["mozInfo"] = os.path.join(self.topobjdir, 'mozinfo.json')
-
-        if not kwargs["manifest"]:
-            kwargs["manifest"] = os.path.join(self.topobjdir, '_tests/xpcshell/xpcshell.ini')
-
-        if not kwargs["symbolsPath"]:
-            kwargs["symbolsPath"] = os.path.join(self.distdir, 'crashreporter-symbols')
-
-        if not kwargs["localAPK"]:
-            for file_name in os.listdir(os.path.join(kwargs["objdir"], "dist")):
-                if file_name.endswith(".apk") and file_name.startswith("fennec"):
-                    kwargs["localAPK"] = os.path.join(kwargs["objdir"], "dist", file_name)
-                    print ("using APK: %s" % kwargs["localAPK"])
+        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.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")
 
-        options = argparse.Namespace(**kwargs)
-        xpcshell = remotexpcshelltests.XPCShellRemote(dm, options, log)
+        if test_paths == 'all':
+            testdirs = []
+            options.testPath = None
+            options.verbose = False
+        elif test_objects:
+            if len(test_objects) > 1:
+                print('Warning: only the first test will be used.')
+            testdirs = test_objects[0]['dir_relpath']
+            options.testPath = test_objects[0]['path']
+            options.verbose = True
+        else:
+            if len(test_paths) > 1:
+                print('Warning: only the first test path argument will be used.')
+            testdirs = test_paths[0]
+            options.testPath = test_paths[0]
+            options.verbose = True
 
-        result = xpcshell.runTests(testClass=remotexpcshelltests.RemoteXPCShellTestThread,
-                                   mobileArgs=xpcshell.mobileArgs,
-                                   **vars(options))
+        xpcshell = remotexpcshelltests.XPCShellRemote(dm, options, testdirs, log)
 
-        self.log_manager.disable_unstructured()
+        result = xpcshell.runTests(xpcshell='xpcshell',
+                      testClass=remotexpcshelltests.RemoteXPCShellTestThread,
+                      testdirs=testdirs,
+                      mobileArgs=xpcshell.mobileArgs,
+                      **options.__dict__)
+
 
         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.
@@ -253,90 +345,132 @@ class B2GXPCShellRunner(MozbuildObject):
             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, **kwargs):
+    def run_test(self, test_paths, b2g_home=None, busybox=None, device_name=None,
+                 test_objects=None, log=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', kwargs["b2g_home"]))
+            print(ADB_NOT_FOUND % ('mochitest-remote', b2g_home))
             sys.exit(1)
 
+        test_path = None
+        if test_objects:
+            if len(test_objects) > 1:
+                print('Warning: Only the first test will be used.')
+
+            test_path = self._wrap_path_argument(test_objects[0]['path'])
+        elif test_paths:
+            if len(test_paths) > 1:
+                print('Warning: Only the first test path will be used.')
+
+            test_path = self._wrap_path_argument(test_paths[0]).relpath()
+
         import runtestsb2g
-
-        log = kwargs.pop("log")
-        self.log_manager.enable_unstructured()
-
-        if kwargs["xpcshell"] is None:
-            kwargs["xpcshell"] = "xpcshell"
-        if kwargs["b2g_path"] is None:
-            kwargs["b2g_path"] = kwargs["b2g_home"]
-        if kwargs["busybox"] is None:
-            kwargs["busybox"] = os.environ.get('BUSYBOX')
-        if kwargs["busybox"] is None:
-            kwargs["busybox"] = self._download_busybox(kwargs["b2g_home"], kwargs["emulator"])
+        parser = runtestsb2g.B2GOptions()
+        options, args = parser.parse_args([])
 
-        if kwargs["localLib"] is None:
-            kwargs["localLib"] = self.bin_dir
-        if kwargs["localBin"] is None:
-            kwargs["localBin"] = self.bin_dir
-        if kwargs["logdir"] is None:
-            kwargs["logdir"] = self.xpcshell_dir
-        if kwargs["manifest"] is None:
-            kwargs["manifest"] = os.path.join(self.xpcshell_dir, 'xpcshell.ini')
-        if kwargs["mozInfo"] is None:
-            kwargs["mozInfo"] = os.path.join(self.topobjdir, 'mozinfo.json')
-        if kwargs["objdir"] is None:
-            kwargs["objdir"] = self.topobjdir
-        if kwargs["symbolsPath"] is None:
-            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
+        options.b2g_path = b2g_home
+        options.busybox = busybox or os.environ.get('BUSYBOX')
+        options.localLib = self.bin_dir
+        options.localBin = self.bin_dir
+        options.logdir = self.xpcshell_dir
+        options.manifest = os.path.join(self.xpcshell_dir, 'xpcshell.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 kwargs["device_name"].startswith('emulator') and 'x86' in kwargs["device_name"]:
-            kwargs["emulator"] = 'x86'
-
-        parser = parser_b2g()
-        options = argparse.Namespace(**kwargs)
-        rv = runtestsb2g.run_remote_xpcshell(parser, options, log)
+        options.emulator = 'arm'
+        if device_name.startswith('emulator'):
+            if 'x86' in device_name:
+                options.emulator = 'x86'
 
-        self.log_manager.disable_unstructured()
-        return rv
+        if not options.busybox:
+            options.busybox = self._download_busybox(b2g_home, options.emulator)
 
-def get_parser():
-    build_obj = MozbuildObject.from_environment(cwd=here)
-    if conditions.is_android(build_obj):
-        return parser_remote()
-    elif conditions.is_b2g(build_obj):
-        return parser_b2g()
-    else:
-        return parser_desktop()
+        return runtestsb2g.run_remote_xpcshell(parser, options, args, log)
 
 @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',
-             description='Run XPCOM Shell tests (API direct unit testing)',
-             conditions=[lambda *args: True],
-             parser=get_parser)
-
+        description='Run XPCOM Shell tests (API direct unit testing)',
+        conditions=[lambda *args: True],
+        parser=_parser)
+    @CommandArgument('test_paths', 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('--verbose', '-v', action='store_true',
+        help='Provide full output from each test process.')
+    @CommandArgument("--debugger", default=None, metavar='DEBUGGER',
+                     help = "Run xpcshell under the given debugger.")
+    @CommandArgument("--debugger-args", default=None, metavar='ARGS', type=str,
+                     dest = "debuggerArgs",
+                     help = "pass the given args to the debugger _before_ "
+                            "the application on the command line")
+    @CommandArgument("--debugger-interactive", action = "store_true",
+                     dest = "debuggerInteractive",
+                     help = "prevents the test harness from redirecting "
+                            "stdout and stderr for interactive debuggers")
+    @CommandArgument("--jsdebugger", dest="jsDebugger", action="store_true",
+                     help="Waits for a devtools JS debugger to connect before "
+                          "starting the test.")
+    @CommandArgument("--jsdebugger-port", dest="jsDebuggerPort",
+                     type=int, default=6000,
+                     help="The port to listen on for a debugger connection if "
+                          "--jsdebugger is specified (default=6000).")
+    @CommandArgument('--interactive', '-i', action='store_true',
+        help='Open an xpcshell prompt before running tests.')
+    @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('--tag', action='append', dest='test_tags',
+        help='Filter out tests that don\'t have the given tag. Can be used '
+             'multiple times in which case the test must contain at least one '
+             'of the given tags.')
+    @CommandArgument('--dump-tests', default=None, type=str, dest='dump_tests',
+        help='Specify path to a filename to dump all the tests that will be run')
+    @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')
+    @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('.')
 
--- a/testing/xpcshell/remotexpcshelltests.py
+++ b/testing/xpcshell/remotexpcshelltests.py
@@ -11,18 +11,16 @@ import runxpcshelltests as xpcshell
 import tempfile
 from zipfile import ZipFile
 from mozlog import commandline
 import shutil
 import mozdevice
 import mozfile
 import mozinfo
 
-from xpcshellcommandline import parser_remote
-
 here = os.path.dirname(os.path.abspath(__file__))
 
 def remoteJoin(path1, path2):
     return posixpath.join(path1, path2)
 
 class RemoteXPCShellTestThread(xpcshell.XPCShellTestThread):
     def __init__(self, *args, **kwargs):
         xpcshell.XPCShellTestThread.__init__(self, *args, **kwargs)
@@ -102,25 +100,25 @@ class RemoteXPCShellTestThread(xpcshell.
 
                 yield path
 
         self.remoteHere = self.remoteForLocal(test['here'])
 
         return (list(sanitize_list(test['head'], 'head')),
                 list(sanitize_list(test['tail'], 'tail')))
 
-    def buildXpcsCmd(self):
+    def buildXpcsCmd(self, testdir):
         # change base class' paths to remote paths and use base class to build command
         self.xpcshell = remoteJoin(self.remoteBinDir, "xpcw")
         self.headJSPath = remoteJoin(self.remoteScriptsDir, 'head.js')
         self.httpdJSPath = remoteJoin(self.remoteComponentsDir, 'httpd.js')
         self.httpdManifest = remoteJoin(self.remoteComponentsDir, 'httpd.manifest')
         self.testingModulesDir = self.remoteModulesDir
         self.testharnessdir = self.remoteScriptsDir
-        xpcshell.XPCShellTestThread.buildXpcsCmd(self)
+        xpcshell.XPCShellTestThread.buildXpcsCmd(self, testdir)
         # remove "-g <dir> -a <dir>" and add "--greomni <apk>"
         del(self.xpcsCmd[1:5])
         if self.options.localAPK:
             self.xpcsCmd.insert(3, '--greomni')
             self.xpcsCmd.insert(4, self.remoteAPK)
 
         if self.remoteDebugger:
             # for example, "/data/local/gdbserver" "localhost:12345"
@@ -213,17 +211,17 @@ 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, log):
+    def __init__(self, devmgr, options, args, log):
         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
@@ -514,111 +512,169 @@ class XPCShellRemote(xpcshell.XPCShellTe
             # Foopies have an older mozdevice ver without retryLimit
             self.device.pushDir(self.xpcDir, self.remoteScriptsDir)
 
     def setupMinidumpDir(self):
         if self.device.dirExists(self.remoteMinidumpDir):
             self.device.removeDir(self.remoteMinidumpDir)
         self.device.mkDir(self.remoteMinidumpDir)
 
-    def buildTestList(self, test_tags=None, test_paths=None):
-        xpcshell.XPCShellTests.buildTestList(self, test_tags=test_tags, test_paths=test_paths)
+    def buildTestList(self, test_tags=None):
+        xpcshell.XPCShellTests.buildTestList(self, test_tags=test_tags)
         uniqueTestPaths = set([])
         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 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
+class RemoteXPCShellOptions(xpcshell.XPCShellOptions):
+
+    def __init__(self):
+        xpcshell.XPCShellOptions.__init__(self)
+        defaults = {}
+
+        self.add_option("--deviceIP", action="store",
+                        type = "string", dest = "deviceIP",
+                        help = "ip address of remote device to test")
+        defaults["deviceIP"] = None
+
+        self.add_option("--devicePort", action="store",
+                        type = "string", dest = "devicePort",
+                        help = "port of remote device to test")
+        defaults["devicePort"] = 20701
+
+        self.add_option("--dm_trans", action="store",
+                        type = "string", dest = "dm_trans",
+                        help = "the transport to use to communicate with device: [adb|sut]; default=sut")
+        defaults["dm_trans"] = "sut"
+
+        self.add_option("--objdir", action="store",
+                        type = "string", dest = "objdir",
+                        help = "local objdir, containing xpcshell binaries")
+        defaults["objdir"] = None
+
+        self.add_option("--apk", action="store",
+                        type = "string", dest = "localAPK",
+                        help = "local path to Fennec APK")
+        defaults["localAPK"] = None
+
+        self.add_option("--noSetup", action="store_false",
+                        dest = "setup",
+                        help = "do not copy any files to device (to be used only if device is already setup)")
+        defaults["setup"] = True
+
+        self.add_option("--local-lib-dir", action="store",
+                        type = "string", dest = "localLib",
+                        help = "local path to library directory")
+        defaults["localLib"] = None
+
+        self.add_option("--local-bin-dir", action="store",
+                        type = "string", dest = "localBin",
+                        help = "local path to bin directory")
+        defaults["localBin"] = None
+
+        self.add_option("--remoteTestRoot", action = "store",
+                    type = "string", dest = "remoteTestRoot",
+                    help = "remote directory to use as test root (eg. /mnt/sdcard/tests or /data/local/tests)")
+        defaults["remoteTestRoot"] = None
+
+        self.set_defaults(**defaults)
+
+    def verifyRemoteOptions(self, options):
+        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:
+                    self.error("Couldn't find local library dir, specify --local-lib-dir")
+            elif options.objdir:
+                options.localLib = os.path.join(options.objdir, 'dist/bin')
+            elif os.path.isfile(os.path.join(here, '..', 'bin', 'xpcshell')):
+                # assume tests are being run from a tests.zip
+                options.localLib = os.path.abspath(os.path.join(here, '..', 'bin'))
             else:
-                parser.error("Couldn't find local library dir, specify --local-lib-dir")
-        elif options.objdir:
-            options.localLib = os.path.join(options.objdir, 'dist/bin')
-        elif os.path.isfile(os.path.join(here, '..', 'bin', 'xpcshell')):
-            # assume tests are being run from a tests.zip
-            options.localLib = os.path.abspath(os.path.join(here, '..', 'bin'))
-        else:
-            parser.error("Couldn't find local library dir, specify --local-lib-dir")
+                self.error("Couldn't find local library dir, specify --local-lib-dir")
 
-    if options.localBin is None:
-        if options.objdir:
-            for path in ['dist/bin', 'bin']:
-                options.localBin = os.path.join(options.objdir, path)
-                if os.path.isdir(options.localBin):
-                    break
+        if options.localBin is None:
+            if options.objdir:
+                for path in ['dist/bin', 'bin']:
+                    options.localBin = os.path.join(options.objdir, path)
+                    if os.path.isdir(options.localBin):
+                        break
+                else:
+                    self.error("Couldn't find local binary dir, specify --local-bin-dir")
+            elif os.path.isfile(os.path.join(here, '..', 'bin', 'xpcshell')):
+                # assume tests are being run from a tests.zip
+                options.localBin = os.path.abspath(os.path.join(here, '..', 'bin'))
             else:
-                parser.error("Couldn't find local binary dir, specify --local-bin-dir")
-        elif os.path.isfile(os.path.join(here, '..', 'bin', 'xpcshell')):
-            # assume tests are being run from a tests.zip
-            options.localBin = os.path.abspath(os.path.join(here, '..', 'bin'))
-        else:
-            parser.error("Couldn't find local binary dir, specify --local-bin-dir")
-    return options
+                self.error("Couldn't find local binary dir, specify --local-bin-dir")
+        return options
 
 class PathMapping:
 
     def __init__(self, localDir, remoteDir):
         self.local = localDir
         self.remote = remoteDir
 
 def main():
+
     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()
+    parser = RemoteXPCShellOptions()
     commandline.add_logging_group(parser)
-    options = parser.parse_args()
+    options, args = 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)
+    options = parser.verifyRemoteOptions(options)
     log = commandline.setup_logging("Remote XPCShell",
                                     options,
                                     {"tbpl": sys.stdout})
 
+    if len(args) < 1 and options.manifest is None:
+        print >>sys.stderr, """Usage: %s <test dirs>
+             or: %s --manifest=test.manifest """ % (sys.argv[0], sys.argv[0])
+        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)
 
     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, args, log)
 
     # we don't run concurrent tests on mobile
     options.sequential = True
 
-    if not xpcsh.runTests(testClass=RemoteXPCShellTestThread,
+    if not xpcsh.runTests(xpcshell='xpcshell',
+                          testClass=RemoteXPCShellTestThread,
+                          testdirs=args[0:],
                           mobileArgs=xpcsh.mobileArgs,
-                          **vars(options)):
+                          **options.__dict__):
         sys.exit(1)
 
 
 if __name__ == '__main__':
     main()
--- a/testing/xpcshell/runtestsb2g.py
+++ b/testing/xpcshell/runtestsb2g.py
@@ -4,25 +4,24 @@
 # 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/.
 
 import sys
 import os
 sys.path.insert(0, os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0]))))
 
 import traceback
-import remotexpcshelltests
-from remotexpcshelltests import RemoteXPCShellTestThread, XPCShellRemote
+from remotexpcshelltests import RemoteXPCShellTestThread, XPCShellRemote, RemoteXPCShellOptions
 from mozdevice import devicemanagerADB, DMError
 from mozlog import commandline
 
 DEVICE_TEST_ROOT = '/data/local/tests'
 
+
 from marionette import Marionette
-from xpcshellcommandline import parser_b2g
 
 class B2GXPCShellTestThread(RemoteXPCShellTestThread):
     # Overridden
     def launchProcess(self, cmd, stdout, stderr, env, cwd, timeout=None):
         try:
             # This returns 1 even when tests pass - hardcode returncode to 0 (bug 773703)
             outputFile = RemoteXPCShellTestThread.launchProcess(self, cmd, stdout, stderr, env, cwd, timeout=timeout)
             self.shellReturnCode = 0
@@ -71,29 +70,97 @@ class B2GXPCShellRemote(XPCShellRemote):
     def pushLibs(self):
         if not self.options.use_device_libs:
             count = XPCShellRemote.pushLibs(self)
             if not count:
                 # couldn't find any libs, fallback to device libs
                 self.env['LD_LIBRARY_PATH'] = '/system/b2g'
                 self.options.use_device_libs = True
 
-def verifyRemoteOptions(parser, options):
-    if options.b2g_path is None:
-        parser.error("Need to specify a --b2gpath")
+class B2GOptions(RemoteXPCShellOptions):
+
+    def __init__(self):
+        RemoteXPCShellOptions.__init__(self)
+        defaults = {}
+
+        self.add_option('--b2gpath', action='store',
+                        type='string', dest='b2g_path',
+                        help="Path to B2G repo or qemu dir")
+        defaults['b2g_path'] = None
+
+        self.add_option('--emupath', action='store',
+                        type='string', dest='emu_path',
+                        help="Path to emulator folder (if different "
+                                                      "from b2gpath")
 
-    if options.geckoPath and not options.emulator:
-        parser.error("You must specify --emulator if you specify --gecko-path")
+        self.add_option('--no-clean', action='store_false',
+                        dest='clean',
+                        help="Do not clean TESTROOT. Saves [lots of] time")
+        defaults['clean'] = True
+
+        defaults['emu_path'] = None
+
+        self.add_option('--emulator', action='store',
+                        type='string', dest='emulator',
+                        help="Architecture of emulator to use: x86 or arm")
+        defaults['emulator'] = None
+
+        self.add_option('--no-window', action='store_true',
+                        dest='no_window',
+                        help="Pass --no-window to the emulator")
+        defaults['no_window'] = False
+
+        self.add_option('--adbpath', action='store',
+                        type='string', dest='adb_path',
+                        help="Path to adb")
+        defaults['adb_path'] = 'adb'
 
-    if options.logdir and not options.emulator:
-        parser.error("You must specify --emulator if you specify --logdir")
-    return remotexpcshelltests.verifyRemoteOptions(parser, options)
+        self.add_option('--address', action='store',
+                        type='string', dest='address',
+                        help="host:port of running Gecko instance to connect to")
+        defaults['address'] = None
 
-def run_remote_xpcshell(parser, options, log):
-    options = verifyRemoteOptions(parser, options)
+        self.add_option('--use-device-libs', action='store_true',
+                        dest='use_device_libs',
+                        help="Don't push .so's")
+        defaults['use_device_libs'] = False
+        self.add_option("--gecko-path", action="store",
+                        type="string", dest="geckoPath",
+                        help="the path to a gecko distribution that should "
+                        "be installed on the emulator prior to test")
+        defaults["geckoPath"] = None
+        self.add_option("--logdir", action="store",
+                        type="string", dest="logdir",
+                        help="directory to store log files")
+        defaults["logdir"] = None
+        self.add_option('--busybox', action='store',
+                        type='string', dest='busybox',
+                        help="Path to busybox binary to install on device")
+        defaults['busybox'] = None
+
+        defaults["remoteTestRoot"] = DEVICE_TEST_ROOT
+        defaults['dm_trans'] = 'adb'
+        defaults['debugger'] = None
+        defaults['debuggerArgs'] = None
+
+        self.set_defaults(**defaults)
+
+    def verifyRemoteOptions(self, options):
+        if options.b2g_path is None:
+            self.error("Need to specify a --b2gpath")
+
+        if options.geckoPath and not options.emulator:
+            self.error("You must specify --emulator if you specify --gecko-path")
+
+        if options.logdir and not options.emulator:
+            self.error("You must specify --emulator if you specify --logdir")
+        return RemoteXPCShellOptions.verifyRemoteOptions(self, options)
+
+def run_remote_xpcshell(parser, options, args, log):
+    options = parser.verifyRemoteOptions(options)
 
     # Create the Marionette instance
     kwargs = {}
     if options.emulator:
         kwargs['emulator'] = options.emulator
         if options.no_window:
             kwargs['noWindow'] = True
         if options.geckoPath:
@@ -123,41 +190,39 @@ 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, args, log)
 
     # we don't run concurrent tests on mobile
     options.sequential = True
 
-    if options.xpcshell is None:
-        options.xpcshell = "xpcshell"
-
     try:
-        if not xpcsh.runTests(testClass=B2GXPCShellTestThread,
-                              mobileArgs=xpcsh.mobileArgs,
-                              **vars(options)):
+        if not xpcsh.runTests(xpcshell='xpcshell', testdirs=args[0:],
+                                 testClass=B2GXPCShellTestThread,
+                                 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 = parser_b2g()
+    parser = B2GOptions()
     commandline.add_logging_group(parser)
-    options = parser.parse_args()
+    options, args = parser.parse_args()
     log = commandline.setup_logging("Remote XPCShell",
                                     options,
                                     {"tbpl": sys.stdout})
-    run_remote_xpcshell(parser, options, log)
+    run_remote_xpcshell(parser, options, args, 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()
--- a/testing/xpcshell/runxpcshelltests.py
+++ b/testing/xpcshell/runxpcshelltests.py
@@ -18,36 +18,33 @@ import shutil
 import signal
 import sys
 import time
 import traceback
 
 from collections import deque, namedtuple
 from distutils import dir_util
 from multiprocessing import cpu_count
-from argparse import ArgumentParser
+from optparse import OptionParser
 from subprocess import Popen, PIPE, STDOUT
 from tempfile import mkdtemp, gettempdir
 from threading import (
     Timer,
     Thread,
     Event,
     current_thread,
 )
 
 try:
     import psutil
     HAVE_PSUTIL = True
 except Exception:
     HAVE_PSUTIL = False
 
 from automation import Automation
-from xpcshellcommandline import parser_desktop
-
-SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
 
 HARNESS_TIMEOUT = 5 * 60
 
 # benchmarking on tbpl revealed that this works best for now
 NUM_THREADS = int(cpu_count() * 4)
 
 EXPECTED_LOG_ACTIONS = set([
     "test_status",
@@ -60,17 +57,17 @@ EXPECTED_LOG_ACTIONS = set([
 here = os.path.dirname(__file__)
 mozbase = os.path.realpath(os.path.join(os.path.dirname(here), 'mozbase'))
 
 if os.path.isdir(mozbase):
     for package in os.listdir(mozbase):
         sys.path.append(os.path.join(mozbase, package))
 
 from manifestparser import TestManifest
-from manifestparser.filters import chunk_by_slice, tags, pathprefix
+from manifestparser.filters import chunk_by_slice, tags
 from mozlog import commandline
 import mozcrash
 import mozinfo
 from mozrunner.utils import get_stack_fixer_function
 
 # --------------------------------------------------------------
 
 # TODO: perhaps this should be in a more generally shared location?
@@ -98,17 +95,17 @@ def cleanup_encoding(s):
 """ Control-C handling """
 gotSIGINT = False
 def markGotSIGINT(signum, stackFrame):
     global gotSIGINT
     gotSIGINT = True
 
 class XPCShellTestThread(Thread):
     def __init__(self, test_object, event, cleanup_dir_list, retry=True,
-            app_dir_key=None, interactive=False,
+            tests_root_dir=None, app_dir_key=None, interactive=False,
             verbose=False, pStdout=None, pStderr=None, keep_going=False,
             log=None, **kwargs):
         Thread.__init__(self)
         self.daemon = True
 
         self.test_object = test_object
         self.cleanup_dir_list = cleanup_dir_list
         self.retry = retry
@@ -128,16 +125,17 @@ class XPCShellTestThread(Thread):
         self.env = copy.deepcopy(kwargs.get('env'))
         self.symbolsPath = kwargs.get('symbolsPath')
         self.logfiles = kwargs.get('logfiles')
         self.xpcshell = kwargs.get('xpcshell')
         self.xpcsRunArgs = kwargs.get('xpcsRunArgs')
         self.failureManifest = kwargs.get('failureManifest')
         self.stack_fixer_function = kwargs.get('stack_fixer_function')
 
+        self.tests_root_dir = tests_root_dir
         self.app_dir_key = app_dir_key
         self.interactive = interactive
         self.verbose = verbose
         self.pStdout = pStdout
         self.pStderr = pStderr
         self.keep_going = keep_going
         self.log = log
 
@@ -410,17 +408,17 @@ class XPCShellTestThread(Thread):
 
                 yield path
 
         headlist = test_object['head'] if 'head' in test_object else ''
         taillist = test_object['tail'] if 'tail' in test_object else ''
         return (list(sanitize_list(headlist, 'head')),
                 list(sanitize_list(taillist, 'tail')))
 
-    def buildXpcsCmd(self):
+    def buildXpcsCmd(self, testdir):
         """
           Load the root head.js file as the first file in our test path, before other head, test, and tail files.
           On a remote system, we overload this to add additional command line arguments, so this gets overloaded.
         """
         # - NOTE: if you rename/add any of the constants set here, update
         #   do_load_child_test_harness() in head.js
         if not self.appPath:
             self.appPath = self.xrePath
@@ -612,17 +610,17 @@ class XPCShellTestThread(Thread):
         test_dir = os.path.dirname(path)
 
         # Create a profile and a temp dir that the JS harness can stick
         # a profile and temporary data in
         self.profileDir = self.setupProfileDir()
         self.tempDir = self.setupTempDir()
         self.mozInfoJSPath = self.setupMozinfoJS()
 
-        self.buildXpcsCmd()
+        self.buildXpcsCmd(test_dir)
         head_files, tail_files = self.getHeadAndTailFiles(self.test_object)
         cmdH = self.buildCmdHead(head_files, tail_files, self.xpcsCmd)
 
         # The test file will have to be loaded after the head files.
         cmdT = self.buildCmdTestFile(path)
 
         args = self.xpcsRunArgs[:]
         if 'debug' in self.test_object:
@@ -765,60 +763,40 @@ class XPCShellTestThread(Thread):
 class XPCShellTests(object):
 
     def __init__(self, log=None):
         """ Initializes node status and logger. """
         self.log = log
         self.harness_timeout = HARNESS_TIMEOUT
         self.nodeProc = {}
 
-    def getTestManifest(self, manifest):
-        if isinstance(manifest, TestManifest):
-            return manifest
-        elif manifest is not None:
-            manifest = os.path.normpath(os.path.abspath(manifest))
-            if os.path.isfile(manifest):
-                return TestManifest([manifest], strict=True)
-            else:
-                ini_path = os.path.join(manifest, "xpcshell.ini")
-        else:
-            ini_path = os.path.join(SCRIPT_DIR, "tests", "xpcshell.ini")
-
-        if os.path.exists(ini_path):
-            return TestManifest([ini_path], strict=True)
-        else:
-            print >> sys.stderr, ("Failed to find manifest at %s; use --manifest "
-                                  "to set path explicitly." % (ini_path,))
-            sys.exit(1)
-
-    def buildTestList(self, test_tags=None, test_paths=None):
+    def buildTestList(self, test_tags=None):
         """
           read the xpcshell.ini manifest and set self.alltests to be
           an array of test objects.
 
           if we are chunking tests, it will be done here as well
         """
-
-        if test_paths is None:
-            test_paths = []
+        if isinstance(self.manifest, TestManifest):
+            mp = self.manifest
+        else:
+            mp = TestManifest(strict=True)
+            if self.manifest is None:
+                for testdir in self.testdirs:
+                    if testdir:
+                        mp.read(os.path.join(testdir, 'xpcshell.ini'))
+            else:
+                mp.read(self.manifest)
 
-        if len(test_paths) == 1 and test_paths[0].endswith(".js"):
-            self.singleFile = os.path.basename(test_paths[0])
-        else:
-            self.singleFile = None
-
-        mp = self.getTestManifest(self.manifest)
+        self.buildTestPath()
 
         filters = []
         if test_tags:
             filters.append(tags(test_tags))
 
-        if test_paths:
-            filters.append(pathprefix(test_paths))
-
         if self.singleFile is None and self.totalChunks > 1:
             filters.append(chunk_by_slice(self.thisChunk, self.totalChunks))
         try:
             self.alltests = mp.active_tests(filters=filters, **mozinfo.info)
         except TypeError:
             sys.stderr.write("*** offending mozinfo.info: %s\n" % repr(mozinfo.info))
             raise
 
@@ -935,16 +913,41 @@ class XPCShellTests(object):
             else:
                 if sys.platform == 'os2emx':
                     pStdout = None
                 else:
                     pStdout = PIPE
                 pStderr = STDOUT
         return pStdout, pStderr
 
+    def buildTestPath(self):
+        """
+          If we specifiy a testpath, set the self.testPath variable to be the given directory or file.
+
+          |testPath| will be the optional path only, or |None|.
+          |singleFile| will be the optional test only, or |None|.
+        """
+        self.singleFile = None
+        if self.testPath is not None:
+            if self.testPath.endswith('.js'):
+            # Split into path and file.
+                if self.testPath.find('/') == -1:
+                    # Test only.
+                    self.singleFile = self.testPath
+                else:
+                    # Both path and test.
+                    # Reuse |testPath| temporarily.
+                    self.testPath = self.testPath.rsplit('/', 1)
+                    self.singleFile = self.testPath[1]
+                    self.testPath = self.testPath[0]
+            else:
+                # Path only.
+                # Simply remove optional ending separator.
+                self.testPath = self.testPath.rstrip("/")
+
     def verifyDirPath(self, dirname):
         """
           Simple wrapper to get the absolute path for a given directory name.
           On a remote system, we need to overload this to work on the remote filesystem.
         """
         return os.path.abspath(dirname)
 
     def trySetupNode(self):
@@ -1036,38 +1039,38 @@ class XPCShellTests(object):
         its path and the source manifest."""
 
         relpath_key = 'file_relpath' if 'file_relpath' in test_object else 'relpath'
         path = test_object[relpath_key].replace('\\', '/');
         if 'dupe-manifest' in test_object and 'ancestor-manifest' in test_object:
             return '%s:%s' % (os.path.basename(test_object['ancestor-manifest']), path)
         return path
 
-    def runTests(self, xpcshell=None, xrePath=None, appPath=None, symbolsPath=None,
-                 manifest=None, testPaths=None, mobileArgs=None,
+    def runTests(self, xpcshell, xrePath=None, appPath=None, symbolsPath=None,
+                 manifest=None, testdirs=None, testPath=None, mobileArgs=None,
                  interactive=False, verbose=False, keepGoing=False, logfiles=True,
                  thisChunk=1, totalChunks=1, debugger=None,
                  debuggerArgs=None, debuggerInteractive=False,
                  profileName=None, mozInfo=None, sequential=False, shuffle=False,
-                 testingModulesDir=None, pluginsPath=None,
+                 testsRootDir=None, testingModulesDir=None, pluginsPath=None,
                  testClass=XPCShellTestThread, failureManifest=None,
                  log=None, stream=None, jsDebugger=False, jsDebuggerPort=0,
-                 test_tags=None, dump_tests=None, utility_path=None,
-                 rerun_failures=False, failure_manifest=None, **otherOptions):
+                 test_tags=None, dump_tests=None, utility_path=None, **otherOptions):
         """Run xpcshell tests.
 
         |xpcshell|, is the xpcshell executable to use to run the tests.
         |xrePath|, if provided, is the path to the XRE to use.
         |appPath|, if provided, is the path to an application directory.
         |symbolsPath|, if provided is the path to a directory containing
           breakpad symbols for processing crashes in tests.
         |manifest|, if provided, is a file containing a list of
           test directories to run.
-        |testPaths|, if provided, is a list of paths to files or directories containing
-                     tests to run.
+        |testdirs|, if provided, is a list of absolute paths of test directories.
+          No-manifest only option.
+        |testPath|, if provided, indicates a single path and/or test to run.
         |pluginsPath|, if provided, custom plugins directory to be returned from
           the xpcshell dir svc provider for NS_APP_PLUGINS_DIR_LIST.
         |interactive|, if set to True, indicates to provide an xpcshell prompt
           instead of automatically executing the test.
         |verbose|, if set to True, will cause stdout/stderr from tests to
           be printed always
         |logfiles|, if set to False, indicates not to save output to log files.
           Non-interactive only option.
@@ -1075,45 +1078,40 @@ class XPCShellTests(object):
           to launch xpcshell.
         |debuggerArgs|, if set, specifies arguments to use with the debugger.
         |debuggerInteractive|, if set, allows the debugger to be run in interactive
           mode.
         |profileName|, if set, specifies the name of the application for the profile
           directory if running only a subset of tests.
         |mozInfo|, if set, specifies specifies build configuration information, either as a filename containing JSON, or a dict.
         |shuffle|, if True, execute tests in random order.
+        |testsRootDir|, absolute path to root directory of all tests. This is used
+          by xUnit generation to determine the package name of the tests.
         |testingModulesDir|, if provided, specifies where JS modules reside.
           xpcshell will register a resource handler mapping this path.
         |otherOptions| may be present for the convenience of subclasses
         """
 
         global gotSIGINT
 
+        if testdirs is None:
+            testdirs = []
+
         # Try to guess modules directory.
         # This somewhat grotesque hack allows the buildbot machines to find the
         # modules directory without having to configure the buildbot hosts. This
         # code path should never be executed in local runs because the build system
         # should always set this argument.
         if not testingModulesDir:
             ourDir = os.path.dirname(__file__)
             possible = os.path.join(ourDir, os.path.pardir, 'modules')
 
             if os.path.isdir(possible):
                 testingModulesDir = possible
 
-        if rerun_failures:
-            if os.path.exists(failure_manifest):
-                rerun_manifest = os.path.join(os.path.dirname(failure_manifest), "rerun.ini")
-                shutil.copyfile(failure_manifest, rerun_manifest)
-                os.remove(failure_manifest)
-                manifest = rerun_manifest
-            else:
-                print >> sys.stderr, "No failures were found to re-run."
-                sys.exit(1)
-
         if testingModulesDir:
             # The resource loader expects native paths. Depending on how we were
             # invoked, a UNIX style path may sneak in on Windows. We try to
             # normalize that.
             testingModulesDir = os.path.normpath(testingModulesDir)
 
             if not os.path.isabs(testingModulesDir):
                 testingModulesDir = os.path.abspath(testingModulesDir)
@@ -1132,29 +1130,35 @@ class XPCShellTests(object):
             JSDebuggerInfo = namedtuple('JSDebuggerInfo', ['port'])
             self.jsDebuggerInfo = JSDebuggerInfo(port=jsDebuggerPort)
 
         self.xpcshell = xpcshell
         self.xrePath = xrePath
         self.appPath = appPath
         self.symbolsPath = symbolsPath
         self.manifest = manifest
+        self.testdirs = testdirs
+        self.testPath = testPath
         self.dump_tests = dump_tests
         self.interactive = interactive
         self.verbose = verbose
         self.keepGoing = keepGoing
         self.logfiles = logfiles
         self.totalChunks = totalChunks
         self.thisChunk = thisChunk
         self.profileName = profileName or "xpcshell"
         self.mozInfo = mozInfo
         self.testingModulesDir = testingModulesDir
         self.pluginsPath = pluginsPath
         self.sequential = sequential
-        self.failure_manifest = failure_manifest
+
+        if not testdirs and not manifest:
+            # nothing to test!
+            self.log.error("Error: No test dirs or test manifest specified!")
+            return False
 
         self.testCount = 0
         self.passCount = 0
         self.failCount = 0
         self.todoCount = 0
 
         self.setAbsPath()
         self.buildXpcsRunArgs()
@@ -1197,17 +1201,17 @@ class XPCShellTests(object):
             appDirKey = self.mozInfo["appname"] + "-appdir"
 
         # We have to do this before we build the test list so we know whether or
         # not to run tests that depend on having the node spdy server
         self.trySetupNode()
 
         pStdout, pStderr = self.getPipes()
 
-        self.buildTestList(test_tags, testPaths)
+        self.buildTestList(test_tags)
         if self.singleFile:
             self.sequential = True
 
         if shuffle:
             random.shuffle(self.alltests)
 
         self.cleanup_dir_list = []
         self.try_again_list = []
@@ -1225,17 +1229,17 @@ class XPCShellTests(object):
             'testharnessdir': self.testharnessdir,
             'profileName': self.profileName,
             'singleFile': self.singleFile,
             'env': self.env, # making a copy of this in the testthreads
             'symbolsPath': self.symbolsPath,
             'logfiles': self.logfiles,
             'xpcshell': self.xpcshell,
             'xpcsRunArgs': self.xpcsRunArgs,
-            'failureManifest': self.failure_manifest,
+            'failureManifest': failureManifest,
             'harness_timeout': self.harness_timeout,
             'stack_fixer_function': self.stack_fixer_function,
         }
 
         if self.sequential:
             # Allow user to kill hung xpcshell subprocess with SIGINT
             # when we are only running tests sequentially.
             signal.signal(signal.SIGINT, markGotSIGINT)
@@ -1270,20 +1274,23 @@ class XPCShellTests(object):
             # are re-run.
 
             path = test_object['path']
             test_object['id'] = self.makeTestId(test_object)
 
             if self.singleFile and not path.endswith(self.singleFile):
                 continue
 
+            if self.testPath and path.find(self.testPath) == -1:
+                continue
+
             self.testCount += 1
 
             test = testClass(test_object, self.event, self.cleanup_dir_list,
-                    app_dir_key=appDirKey,
+                    tests_root_dir=testsRootDir, app_dir_key=appDirKey,
                     interactive=interactive,
                     verbose=verbose or test_object.get("verbose") == "true",
                     pStdout=pStdout, pStderr=pStderr,
                     keep_going=keepGoing, log=self.log,
                     mobileArgs=mobileArgs, **kwargs)
             if 'run-sequentially' in test_object or self.sequential:
                 sequential_tests.append(test)
             else:
@@ -1365,17 +1372,17 @@ class XPCShellTests(object):
                     break
                 keep_going = test.keep_going
 
         # retry tests that failed when run in parallel
         if self.try_again_list:
             self.log.info("Retrying tests that failed when run in parallel.")
         for test_object in self.try_again_list:
             test = testClass(test_object, self.event, self.cleanup_dir_list,
-                    retry=False,
+                    retry=False, tests_root_dir=testsRootDir,
                     app_dir_key=appDirKey, interactive=interactive,
                     verbose=verbose, pStdout=pStdout, pStderr=pStderr,
                     keep_going=keepGoing, log=self.log, mobileArgs=mobileArgs,
                     **kwargs)
             test.start()
             test.join()
             self.addTestResults(test)
             # did the test encounter any exception?
@@ -1418,30 +1425,136 @@ class XPCShellTests(object):
         if gotSIGINT and not keepGoing:
             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
 
+class XPCShellOptions(OptionParser):
+    def __init__(self):
+        """Process command line arguments and call runTests() to do the real work."""
+        OptionParser.__init__(self)
+        self.add_option("--app-path",
+                        type="string", dest="appPath", default=None,
+                        help="application directory (as opposed to XRE directory)")
+        self.add_option("--interactive",
+                        action="store_true", dest="interactive", default=False,
+                        help="don't automatically run tests, drop to an xpcshell prompt")
+        self.add_option("--verbose",
+                        action="store_true", dest="verbose", default=False,
+                        help="always print stdout and stderr from tests")
+        self.add_option("--keep-going",
+                        action="store_true", dest="keepGoing", default=False,
+                        help="continue running tests after test killed with control-C (SIGINT)")
+        self.add_option("--logfiles",
+                        action="store_true", dest="logfiles", default=True,
+                        help="create log files (default, only used to override --no-logfiles)")
+        self.add_option("--dump-tests",
+                        type="string", dest="dump_tests", default=None,
+                        help="Specify path to a filename to dump all the tests that will be run")
+        self.add_option("--manifest",
+                        type="string", dest="manifest", default=None,
+                        help="Manifest of test directories to use")
+        self.add_option("--no-logfiles",
+                        action="store_false", dest="logfiles",
+                        help="don't create log files")
+        self.add_option("--sequential",
+                        action="store_true", dest="sequential", default=False,
+                        help="Run all tests sequentially")
+        self.add_option("--test-path",
+                        type="string", dest="testPath", default=None,
+                        help="single path and/or test filename to test")
+        self.add_option("--tests-root-dir",
+                        type="string", dest="testsRootDir", default=None,
+                        help="absolute path to directory where all tests are located. this is typically $(objdir)/_tests")
+        self.add_option("--testing-modules-dir",
+                        dest="testingModulesDir", default=None,
+                        help="Directory where testing modules are located.")
+        self.add_option("--test-plugin-path",
+                        type="string", dest="pluginsPath", default=None,
+                        help="Path to the location of a plugins directory containing the test plugin or plugins required for tests. "
+                             "By default xpcshell's dir svc provider returns gre/plugins. Use test-plugin-path to add a directory "
+                             "to return for NS_APP_PLUGINS_DIR_LIST when queried.")
+        self.add_option("--total-chunks",
+                        type = "int", dest = "totalChunks", default=1,
+                        help = "how many chunks to split the tests up into")
+        self.add_option("--this-chunk",
+                        type = "int", dest = "thisChunk", default=1,
+                        help = "which chunk to run between 1 and --total-chunks")
+        self.add_option("--profile-name",
+                        type = "string", dest="profileName", default=None,
+                        help="name of application profile being tested")
+        self.add_option("--build-info-json",
+                        type = "string", dest="mozInfo", default=None,
+                        help="path to a mozinfo.json including information about the build configuration. defaults to looking for mozinfo.json next to the script.")
+        self.add_option("--shuffle",
+                        action="store_true", dest="shuffle", default=False,
+                        help="Execute tests in random order")
+        self.add_option("--failure-manifest", dest="failureManifest",
+                        action="store",
+                        help="path to file where failure manifest will be written.")
+        self.add_option("--xre-path",
+                        action = "store", type = "string", dest = "xrePath",
+                        # individual scripts will set a sane default
+                        default = None,
+                        help = "absolute path to directory containing XRE (probably xulrunner)")
+        self.add_option("--symbols-path",
+                        action = "store", type = "string", dest = "symbolsPath",
+                        default = None,
+                        help = "absolute path to directory containing breakpad symbols, or the URL of a zip file containing symbols")
+        self.add_option("--debugger",
+                        action = "store", dest = "debugger",
+                        help = "use the given debugger to launch the application")
+        self.add_option("--debugger-args",
+                        action = "store", dest = "debuggerArgs",
+                        help = "pass the given args to the debugger _before_ "
+                           "the application on the command line")
+        self.add_option("--debugger-interactive",
+                        action = "store_true", dest = "debuggerInteractive",
+                        help = "prevents the test harness from redirecting "
+                          "stdout and stderr for interactive debuggers")
+        self.add_option("--jsdebugger", dest="jsDebugger", action="store_true",
+                        help="Waits for a devtools JS debugger to connect before "
+                             "starting the test.")
+        self.add_option("--jsdebugger-port", type="int", dest="jsDebuggerPort",
+                        default=6000,
+                        help="The port to listen on for a debugger connection if "
+                             "--jsdebugger is specified.")
+        self.add_option("--tag",
+                        action="append", dest="test_tags",
+                        default=None,
+                        help="filter out tests that don't have the given tag. Can be "
+                             "used multiple times in which case the test must contain "
+                             "at least one of the given tags.")
+        self.add_option("--utility-path",
+                        action="store", dest="utility_path",
+                        default=None,
+                        help="Path to a directory containing utility programs, such "
+                             "as stack fixer scripts.")
 
 def main():
-    parser = parser_desktop()
+    parser = XPCShellOptions()
     commandline.add_logging_group(parser)
-    options = parser.parse_args()
+    options, args = parser.parse_args()
+
 
     log = commandline.setup_logging("XPCShell", options, {"tbpl": sys.stdout})
 
-    if options.xpcshell is None:
-        print >> sys.stderr, """Must provide path to xpcshell using --xpcshell"""
+    if len(args) < 2 and options.manifest is None or \
+       (len(args) < 1 and options.manifest is not None):
+        print >>sys.stderr, """Usage: %s <path to xpcshell> <test dirs>
+              or: %s --manifest=test.manifest <path to xpcshell>""" % (sys.argv[0],
+                                                              sys.argv[0])
+        sys.exit(1)
 
     xpcsh = XPCShellTests(log)
 
     if options.interactive and not options.testPath:
         print >>sys.stderr, "Error: You must specify a test filename in interactive mode!"
         sys.exit(1)
 
-    if not xpcsh.runTests(**vars(options)):
+    if not xpcsh.runTests(args[0], testdirs=args[1:], **options.__dict__):
         sys.exit(1)
 
 if __name__ == '__main__':
     main()
--- a/testing/xpcshell/selftest.py
+++ b/testing/xpcshell/selftest.py
@@ -417,16 +417,17 @@ tail =
         Assert that self.x.runTests with manifest=self.manifest
         returns |expected|.
         """
         self.assertEquals(expected,
                           self.x.runTests(xpcshellBin,
                                           manifest=self.manifest,
                                           mozInfo=mozinfo.info,
                                           shuffle=shuffle,
+                                          testsRootDir=self.tempdir,
                                           verbose=verbose,
                                           sequential=True,
                                           utility_path=self.utility_path),
                           msg="""Tests should have %s, log:
 ========
 %s
 ========
 """ % ("passed" if expected else "failed", self.log.getvalue()))
deleted file mode 100644
--- a/testing/xpcshell/xpcshellcommandline.py
+++ /dev/null
@@ -1,202 +0,0 @@
-import argparse
-
-def add_common_arguments(parser):
-    parser.add_argument("--app-path",
-                        type=unicode, dest="appPath", default=None,
-                        help="application directory (as opposed to XRE directory)")
-    parser.add_argument("--interactive",
-                        action="store_true", dest="interactive", default=False,
-                        help="don't automatically run tests, drop to an xpcshell prompt")
-    parser.add_argument("--verbose",
-                        action="store_true", dest="verbose", default=False,
-                        help="always print stdout and stderr from tests")
-    parser.add_argument("--keep-going",
-                        action="store_true", dest="keepGoing", default=False,
-                        help="continue running tests after test killed with control-C (SIGINT)")
-    parser.add_argument("--logfiles",
-                        action="store_true", dest="logfiles", default=True,
-                        help="create log files (default, only used to override --no-logfiles)")
-    parser.add_argument("--dump-tests", type=str, dest="dump_tests", default=None,
-                        help="Specify path to a filename to dump all the tests that will be run")
-    parser.add_argument("--manifest",
-                        type=unicode, dest="manifest", default=None,
-                        help="Manifest of test directories to use")
-    parser.add_argument("--no-logfiles",
-                        action="store_false", dest="logfiles",
-                        help="don't create log files")
-    parser.add_argument("--sequential",
-                        action="store_true", dest="sequential", default=False,
-                        help="Run all tests sequentially")
-    parser.add_argument("--testing-modules-dir",
-                        dest="testingModulesDir", default=None,
-                        help="Directory where testing modules are located.")
-    parser.add_argument("--test-plugin-path",
-                        type=str, dest="pluginsPath", default=None,
-                        help="Path to the location of a plugins directory containing the test plugin or plugins required for tests. "
-                        "By default xpcshell's dir svc provider returns gre/plugins. Use test-plugin-path to add a directory "
-                        "to return for NS_APP_PLUGINS_DIR_LIST when queried.")
-    parser.add_argument("--total-chunks",
-                        type=int, dest="totalChunks", default=1,
-                        help="how many chunks to split the tests up into")
-    parser.add_argument("--this-chunk",
-                        type=int, dest="thisChunk", default=1,
-                        help="which chunk to run between 1 and --total-chunks")
-    parser.add_argument("--profile-name",
-                        type=str, dest="profileName", default=None,
-                        help="name of application profile being tested")
-    parser.add_argument("--build-info-json",
-                        type=str, dest="mozInfo", default=None,
-                        help="path to a mozinfo.json including information about the build configuration. defaults to looking for mozinfo.json next to the script.")
-    parser.add_argument("--shuffle",
-                        action="store_true", dest="shuffle", default=False,
-                        help="Execute tests in random order")
-    parser.add_argument("--xre-path",
-                        action="store", type=str, dest="xrePath",
-                        # individual scripts will set a sane default
-                        default=None,
-                        help="absolute path to directory containing XRE (probably xulrunner)")
-    parser.add_argument("--symbols-path",
-                        action="store", type=str, dest="symbolsPath",
-                        default=None,
-                        help="absolute path to directory containing breakpad symbols, or the URL of a zip file containing symbols")
-    parser.add_argument("--debugger",
-                        action="store", dest="debugger",
-                        help="use the given debugger to launch the application")
-    parser.add_argument("--debugger-args",
-                        action="store", dest="debuggerArgs",
-                        help="pass the given args to the debugger _before_ "
-                        "the application on the command line")
-    parser.add_argument("--debugger-interactive",
-                        action="store_true", dest="debuggerInteractive",
-                        help="prevents the test harness from redirecting "
-                        "stdout and stderr for interactive debuggers")
-    parser.add_argument("--jsdebugger", dest="jsDebugger", action="store_true",
-                        help="Waits for a devtools JS debugger to connect before "
-                        "starting the test.")
-    parser.add_argument("--jsdebugger-port", type=int, dest="jsDebuggerPort",
-                        default=6000,
-                        help="The port to listen on for a debugger connection if "
-                        "--jsdebugger is specified.")
-    parser.add_argument("--tag",
-                        action="append", dest="test_tags",
-                        default=None,
-                        help="filter out tests that don't have the given tag. Can be "
-                        "used multiple times in which case the test must contain "
-                        "at least one of the given tags.")
-    parser.add_argument("--utility-path",
-                        action="store", dest="utility_path",
-                        default=None,
-                        help="Path to a directory containing utility programs, such "
-                        "as stack fixer scripts.")
-    parser.add_argument("--xpcshell",
-                        action="store", dest="xpcshell",
-                        default=None,
-                        help="Path to xpcshell binary")
-    # This argument can be just present, or the path to a manifest file. The
-    # just-present case is usually used for mach which can provide a default
-    # path to the failure file from the previous run
-    parser.add_argument("--rerun-failures",
-                        action="store_true",
-                        help="Rerun failures from the previous run, if any")
-    parser.add_argument("--failure-manifest",
-                        action="store",
-                        help="Path to a manifest file from which to rerun failures "
-                        "(with --rerun-failure) or in which to record failed tests")
-    parser.add_argument("testPaths", nargs="*", default=None,
-                        help="Paths of tests to run.")
-
-def add_remote_arguments(parser):
-    parser.add_argument("--deviceIP", action="store", type=str, dest="deviceIP",
-                        help="ip address of remote device to test")
-
-    parser.add_argument("--devicePort", action="store", type=str, dest="devicePort",
-                        default=20701, help="port of remote device to test")
-
-    parser.add_argument("--dm_trans", action="store", type=str, dest="dm_trans",
-                        choices=["adb", "sut"], default="sut",
-                        help="the transport to use to communicate with device: [adb|sut]; default=sut")
-
-    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",
-                        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)")
-
-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")
-
-    parser.add_argument('--no-clean', action='store_false', dest='clean', default=True,
-                        help="Do not clean TESTROOT. Saves [lots of] time")
-
-    parser.add_argument('--emulator', action='store', type=str, dest='emulator',
-                        default="arm", choices=["x86", "arm"],
-                        help="Architecture of emulator to use: x86 or arm")
-
-    parser.add_argument('--no-window', action='store_true', dest='no_window', default=False,
-                        help="Pass --no-window to the emulator")
-
-    parser.add_argument('--adbpath', action='store', type=str, dest='adb_path',
-                        default="adb", help="Path to adb")
-
-    parser.add_argument('--address', action='store', type=str, dest='address',
-                        help="host:port of running Gecko instance to connect to")
-
-    parser.add_argument('--use-device-libs', action='store_true', dest='use_device_libs',
-                        default=None, help="Don't push .so's")
-
-    parser.add_argument("--gecko-path", action="store", type=str, dest="geckoPath",
-                        help="the path to a gecko distribution that should "
-                        "be installed on the emulator prior to test")
-
-    parser.add_argument("--logdir", action="store", type=str, dest="logdir",
-                        help="directory to store log files")
-
-    parser.add_argument('--busybox', action='store', type=str, dest='busybox',
-                        help="Path to busybox binary to install on device")
-
-    parser.set_defaults(remoteTestRoot="/data/local/tests",
-                        dm_trans="adb")
-
-def parser_desktop():
-    parser = argparse.ArgumentParser()
-    add_common_arguments(parser)
-    return parser
-
-def parser_remote():
-    parser = argparse.ArgumentParser()
-    common = parser.add_argument_group("Common Options")
-    add_common_arguments(common)
-    remote = parser.add_argument_group("Remote Options")
-    add_remote_arguments(remote)
-
-    return parser
-
-def parser_b2g():
-    parser = argparse.ArgumentParser()
-    common = parser.add_argument_group("Common Options")
-    add_common_arguments(common)
-    remote = parser.add_argument_group("Remote Options")
-    add_remote_arguments(remote)
-    b2g = parser.add_argument_group("B2G Options")
-    add_b2g_arguments(b2g)
-
-    return parser