Bug 1288827 - Allow running mochitest from test-package without specifying --appname, r?armenzg draft
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Wed, 20 Jul 2016 11:13:30 -0400
changeset 393864 c3684d30e6ce64a4e469fb0c22ff678c73b1b7ec
parent 393304 fef429fba4c64c5b9c0c823a6ab713edbbcd4220
child 526680 222346d16223904c74b2ff4313c900abde0dd7d0
push id24426
push userahalberstadt@mozilla.com
push dateThu, 28 Jul 2016 15:51:51 +0000
reviewersarmenzg
bugs1288827
milestone50.0a1
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/runrobocop.py
testing/mochitest/runtests.py
testing/mochitest/runtestsb2g.py
testing/mochitest/runtestsremote.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/runrobocop.py
+++ b/testing/mochitest/runrobocop.py
@@ -527,17 +527,19 @@ class RobocopTestRunner(MochitestDesktop
             print "INFO | runtests.py | Test summary: start."
             logResult = self.logTestSummary()
             print "INFO | runtests.py | Test summary: end."
             if worstTestResult == 0:
                 worstTestResult = logResult
         return worstTestResult
 
 
-def run_test_harness(options):
+def run_test_harness(parser, options):
+    parser.validate(options)
+
     if options is None:
         raise ValueError(
             "Invalid options specified, use --help for a list of valid options")
     message_logger = MessageLogger(logger=None)
     process_args = {'messageLogger': message_logger}
     auto = RemoteAutomation(None, "fennec", processArgs=process_args)
     auto.setDeviceManager(options.dm)
     runResult = -1
@@ -572,12 +574,12 @@ def run_test_harness(options):
             pass
         message_logger.finish()
     return runResult
 
 
 def main(args=sys.argv[1:]):
     parser = MochitestArgumentParser(app='android')
     options = parser.parse_args(args)
-    return run_test_harness(options)
+    return run_test_harness(parser, options)
 
 if __name__ == "__main__":
     sys.exit(main())
--- 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())
--- a/testing/mochitest/runtestsb2g.py
+++ b/testing/mochitest/runtestsb2g.py
@@ -355,17 +355,19 @@ class MochitestB2G(MochitestBase):
         if len(self.urlOpts) > 0:
             test_url += "?" + "&".join(self.urlOpts)
         self.start_script_args.append(test_url)
 
         options.profilePath = self.app_ctx.remote_profile
         options.logFile = self.local_log
 
 
-def run_test_harness(options):
+def run_test_harness(parser, options):
+    parser.validate(options)
+
     # create our Marionette instance
     marionette_args = {
         'adb_path': options.adbPath,
         'emulator': options.emulator,
         'no_window': options.noWindow,
         'logdir': options.logdir,
         'busybox': options.busybox,
         'symbols_path': options.symbolsPath,
@@ -408,12 +410,12 @@ def run_test_harness(options):
     mochitest.message_logger.finish()
 
     return retVal
 
 
 def main():
     parser = MochitestArgumentParser(app='b2g')
     options = parser.parse_args()
-    return run_test_harness(options)
+    return run_test_harness(parser, options)
 
 if __name__ == "__main__":
     sys.exit(main())
--- a/testing/mochitest/runtestsremote.py
+++ b/testing/mochitest/runtestsremote.py
@@ -288,17 +288,19 @@ class MochiRemote(MochitestDesktop):
 
         # remove args not supported by automation.py
         kwargs.pop('marionette_args', None)
         kwargs.pop('quiet', None)
 
         return self._automation.runApp(*args, **kwargs)
 
 
-def run_test_harness(options):
+def run_test_harness(parser, options):
+    parser.validate(options)
+
     message_logger = MessageLogger(logger=None)
     process_args = {'messageLogger': message_logger}
     auto = RemoteAutomation(None, "fennec", processArgs=process_args)
 
     if options is None:
         raise ValueError("Invalid options specified, use --help for a list of valid options")
 
     options.runByDir = False
@@ -380,13 +382,13 @@ def run_test_harness(options):
 
     return retVal
 
 
 def main(args=sys.argv[1:]):
     parser = MochitestArgumentParser(app='android')
     options = parser.parse_args(args)
 
-    return run_test_harness(options)
+    return run_test_harness(parser, options)
 
 
 if __name__ == "__main__":
     sys.exit(main())