Bug 1288827 - Allow running mochitest from test-package without specifying --appname, r=armenzg
☠☠ backed out by d6a7c3753a06 ☠ ☠
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Wed, 20 Jul 2016 11:13:30 -0400
changeset 349139 6dcb52d270c7c008563b1f739a4fa2e2db31a531
parent 348988 0aa0956567fc0178126434c013070332eb3a6fd7
child 349140 d6a7c3753a062a6c316bff09133f066981c3be99
push id1230
push userjlund@mozilla.com
push dateMon, 31 Oct 2016 18:13:35 +0000
treeherdermozilla-release@5e06e3766db2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersarmenzg
bugs1288827
milestone50.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1288827 - Allow running mochitest from test-package without specifying --appname, r=armenzg Because it is now possible for options.app to get set after 'parse_args' time, we need to make sure the argument validation happens later. To accomplish this we pass in the parser instance to 'run_test_harness' and do parser.validate there. This unfortunately requires some minor uses of global to accomplish easily due to how mach handles parsers. MozReview-Commit-ID: s3Js1aZlSE
testing/mochitest/mach_commands.py
testing/mochitest/mach_test_package_commands.py
testing/mochitest/mochitest_options.py
testing/mochitest/runtests.py
--- a/testing/mochitest/mach_commands.py
+++ b/testing/mochitest/mach_commands.py
@@ -126,16 +126,18 @@ ALL_FLAVORS = {
         }
     },
 }
 
 SUPPORTED_APPS = ['firefox', 'b2g', 'android', 'mulet']
 SUPPORTED_FLAVORS = list(chain.from_iterable([f['aliases'] for f in ALL_FLAVORS.values()]))
 CANONICAL_FLAVORS = sorted([f['aliases'][0] for f in ALL_FLAVORS.values()])
 
+parser = None
+
 
 class MochitestRunner(MozbuildObject):
 
     """Easily run mochitests.
 
     This currently contains just the basics for running mochitests. We may want
     to hook up result parsing, etc.
     """
@@ -192,17 +194,17 @@ class MochitestRunner(MozbuildObject):
         options = Namespace(**kwargs)
 
         from manifestparser import TestManifest
         if tests:
             manifest = TestManifest()
             manifest.tests.extend(tests)
             options.manifestFile = manifest
 
-        return mochitest.run_test_harness(options)
+        return mochitest.run_test_harness(parser, options)
 
     def run_desktop_test(self, context, tests=None, suite=None, **kwargs):
         """Runs a mochitest.
 
         suite is the type of mochitest to run. It can be one of ('plain',
         'chrome', 'browser', 'a11y', 'jetpack-package', 'jetpack-addon').
         """
         # runtests.py is ambiguous, so we load the file/module manually.
@@ -237,17 +239,17 @@ class MochitestRunner(MozbuildObject):
             # refresh the page to pick up modifications. Therefore leave the browser
             # open if only running a single mochitest-plain test. This behaviour can
             # be overridden by passing in --keep-open=false.
             if len(tests) == 1 and options.keep_open is None and suite == 'plain':
                 options.keep_open = True
 
         # We need this to enable colorization of output.
         self.log_manager.enable_unstructured()
-        result = mochitest.run_test_harness(options)
+        result = mochitest.run_test_harness(parser, options)
         self.log_manager.disable_unstructured()
         return result
 
     def run_android_test(self, context, tests, suite=None, **kwargs):
         host_ret = verify_host_bin()
         if host_ret != 0:
             return host_ret
 
@@ -261,17 +263,17 @@ class MochitestRunner(MozbuildObject):
         options = Namespace(**kwargs)
 
         from manifestparser import TestManifest
         if tests:
             manifest = TestManifest()
             manifest.tests.extend(tests)
             options.manifestFile = manifest
 
-        return runtestsremote.run_test_harness(options)
+        return runtestsremote.run_test_harness(parser, options)
 
     def run_robocop_test(self, context, tests, suite=None, **kwargs):
         host_ret = verify_host_bin()
         if host_ret != 0:
             return host_ret
 
         import imp
         path = os.path.join(self.mochitest_dir, 'runrobocop.py')
@@ -283,17 +285,17 @@ class MochitestRunner(MozbuildObject):
         options = Namespace(**kwargs)
 
         from manifestparser import TestManifest
         if tests:
             manifest = TestManifest()
             manifest.tests.extend(tests)
             options.manifestFile = manifest
 
-        return runrobocop.run_test_harness(options)
+        return runrobocop.run_test_harness(parser, options)
 
 # parser
 
 
 def setup_argument_parser():
     build_obj = MozbuildObject.from_environment(cwd=here)
 
     build_path = os.path.join(build_obj.topobjdir, 'build')
@@ -316,17 +318,19 @@ def setup_argument_parser():
     if conditions.is_android(build_obj):
         # On Android, check for a connected device (and offer to start an
         # emulator if appropriate) before running tests. This check must
         # be done in this admittedly awkward place because
         # MochitestArgumentParser initialization fails if no device is found.
         from mozrunner.devices.android_device import verify_android_device
         verify_android_device(build_obj, install=True, xre=True)
 
-    return MochitestArgumentParser()
+    global parser
+    parser = MochitestArgumentParser()
+    return parser
 
 
 # condition filters
 
 def is_buildapp_in(*apps):
     def is_buildapp_supported(cls):
         for a in apps:
             c = getattr(conditions, 'is_{}'.format(a), None)
--- a/testing/mochitest/mach_test_package_commands.py
+++ b/testing/mochitest/mach_test_package_commands.py
@@ -1,36 +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/.
 
 from __future__ import unicode_literals
 
+import os
 from argparse import Namespace
-import os
+from functools import partial
 
 from mach.decorators import (
     CommandProvider,
     Command,
 )
 
+parser = None
+
 
 def run_mochitest(context, **kwargs):
     args = Namespace(**kwargs)
     args.certPath = context.certs_dir
     args.utilityPath = context.bin_dir
     args.extraProfileFiles.append(os.path.join(context.bin_dir, 'plugins'))
 
+    if not args.app:
+        args.app = context.find_firefox()
+
+    if args.test_paths:
+        test_root = os.path.join(context.package_root, 'mochitest', 'tests')
+        normalize = partial(context.normalize_test_path, test_root)
+        args.test_paths = map(normalize, args.test_paths)
+
     from runtests import run_test_harness
-    return run_test_harness(args)
+    return run_test_harness(parser, args)
 
 
 def setup_argument_parser():
     from mochitest_options import MochitestArgumentParser
-    return MochitestArgumentParser(app='generic')
+    global parser
+    parser = MochitestArgumentParser(app='generic')
+    return parser
 
 
 @CommandProvider
 class MochitestCommands(object):
 
     def __init__(self, context):
         self.context = context
 
--- a/testing/mochitest/mochitest_options.py
+++ b/testing/mochitest/mochitest_options.py
@@ -1246,15 +1246,8 @@ class MochitestArgumentParser(ArgumentPa
         containers = container_map[self.app]
         self._containers = [c() for c in containers]
         return self._containers
 
     def validate(self, args):
         for container in self.containers:
             args = container.validate(self, args, self.context)
         return args
-
-    def parse_args(self, *args, **kwargs):
-        return self.validate(ArgumentParser.parse_args(self, *args, **kwargs))
-
-    def parse_known_args(self, *args, **kwargs):
-        args, remainder = ArgumentParser.parse_known_args(self, *args, **kwargs)
-        return (self.validate(args), remainder)
--- a/testing/mochitest/runtests.py
+++ b/testing/mochitest/runtests.py
@@ -46,20 +46,21 @@ from manifestparser.filters import (
     pathprefix,
     subsuite,
     tags,
 )
 
 try:
     from marionette import Marionette
     from marionette_driver.addons import Addons
-
-except ImportError:
-    # Marionette not needed nor supported on android
-    Marionette = None
+except ImportError, e:
+    # Defer ImportError until attempt to use Marionette
+    def reraise(*args, **kwargs):
+        raise(e)
+    Marionette = reraise
 
 from leaks import ShutdownLeaks, LSANLeaks
 from mochitest_options import (
     MochitestArgumentParser, build_obj, get_default_valgrind_suppression_files
 )
 from mozprofile import Profile, Preferences
 from mozprofile.permissions import ServerLocations
 from urllib import quote_plus as encodeURIComponent
@@ -2660,17 +2661,19 @@ class MochitestDesktop(MochitestBase):
 
             rootdir = '/'.join(test['path'].split('/')[:-1])
             if rootdir not in dirlist:
                 dirlist.append(rootdir)
 
         return dirlist
 
 
-def run_test_harness(options):
+def run_test_harness(parser, options):
+    parser.validate(options)
+
     logger_options = {
         key: value for key, value in vars(options).iteritems()
         if key.startswith('log') or key == 'valgrind'}
 
     runner = MochitestDesktop(logger_options)
 
     options.runByDir = False
 
@@ -2712,12 +2715,12 @@ def run_test_harness(options):
 def cli(args=sys.argv[1:]):
     # parse command line options
     parser = MochitestArgumentParser(app='generic')
     options = parser.parse_args(args)
     if options is None:
         # parsing error
         sys.exit(1)
 
-    return run_test_harness(options)
+    return run_test_harness(parser, options)
 
 if __name__ == "__main__":
     sys.exit(cli())