Bug 1384517 - Add "marionette doc" subcommand to generate docs; r?automatedtester draft
authorAndreas Tolfsen <ato@sny.no>
Wed, 26 Jul 2017 15:00:47 +0100
changeset 615930 d5fb04b9d357ddb686ec36eede2303e5a71ec05b
parent 615929 4852fbe2785e9a5e34618ceb9cf9589d08658024
child 615931 27f99ccec81ec5811b25f28250f8af978d109513
push id70527
push userbmo:ato@sny.no
push dateWed, 26 Jul 2017 14:58:26 +0000
reviewersautomatedtester
bugs1384517
milestone56.0a1
Bug 1384517 - Add "marionette doc" subcommand to generate docs; r?automatedtester This patch introduces a new top-level mach command "marionette" that can be invoked as `./mach marionette`. It offers two subcommands, "test" and "doc". The "test" subcommand is functionally equivalent to the existing "marionette-test" command, which this patch deprecates. The "doc" subcommand generates the Marionette server API documentation using jsdoc, which it is presumed is already installed on the system. A future patch will make this subcommand more sophisticated, but this should work for now. It also comes with an --http <host>:<port> flag which spins up an HTTPD serving the documentation. MozReview-Commit-ID: DAoHC8tHJQF
testing/marionette/mach_commands.py
--- a/testing/marionette/mach_commands.py
+++ b/testing/marionette/mach_commands.py
@@ -1,53 +1,57 @@
 # 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 absolute_import, unicode_literals
 
+import argparse
 import os
 import sys
-import argparse
+
+from mach.decorators import (
+    CommandArgument,
+    CommandProvider,
+    Command,
+    SubCommand,
+)
 
 from mozbuild.base import (
     MachCommandBase,
     MachCommandConditions as conditions,
 )
 
-from mach.decorators import (
-    CommandArgument,
-    CommandProvider,
-    Command,
-)
 
 def is_firefox_or_android(cls):
     """Must have Firefox build or Android build."""
     return conditions.is_firefox(cls) or conditions.is_android(cls)
 
-def setup_marionette_argument_parser():
+
+def create_parser_tests():
     from marionette_harness.runtests import MarionetteArguments
     from mozlog.structured import commandline
     parser = MarionetteArguments()
     commandline.add_logging_group(parser)
     return parser
 
+
 def run_marionette(tests, binary=None, topsrcdir=None, **kwargs):
     from mozlog.structured import commandline
 
     from marionette_harness.runtests import (
         MarionetteTestRunner,
         MarionetteHarness
     )
 
-    parser = setup_marionette_argument_parser()
+    parser = create_parser_tests()
 
     if not tests:
         tests = [os.path.join(topsrcdir,
-                 'testing/marionette/harness/marionette_harness/tests/unit-tests.ini')]
+                 "testing/marionette/harness/marionette_harness/tests/unit-tests.ini")]
 
     args = argparse.Namespace(tests=tests)
 
     args.binary = binary
 
     for k, v in kwargs.iteritems():
         setattr(args, k, v)
 
@@ -57,25 +61,101 @@ def run_marionette(tests, binary=None, t
                                             args,
                                             {"mach": sys.stdout})
     failed = MarionetteHarness(MarionetteTestRunner, args=vars(args)).run()
     if failed > 0:
         return 1
     else:
         return 0
 
+
 @CommandProvider
 class MachCommands(MachCommandBase):
-    @Command('marionette-test', category='testing',
-        description='Run a Marionette test (Check UI or the internal JavaScript using marionette).',
-        conditions=[is_firefox_or_android],
-        parser=setup_marionette_argument_parser,
-    )
+
+    """Deprecated in favour of ./mach marionette <subcommand>."""
+
+    @Command("marionette-test",
+             category="testing",
+             description="Remote control protocol to Gecko, used for functional UI tests and browser automation.",
+             conditions=[is_firefox_or_android],
+             parser=create_parser_tests,
+             )
     def run_marionette_test(self, tests, **kwargs):
-        if 'test_objects' in kwargs:
+        print >>sys.stderr, ("warning: ./mach marionette-test is deprecated; "
+                             "please use ./mach marionette test")
+
+        if "test_objects" in kwargs:
             tests = []
-            for obj in kwargs['test_objects']:
-                tests.append(obj['file_relpath'])
-            del kwargs['test_objects']
+            for obj in kwargs["test_objects"]:
+                tests.append(obj["file_relpath"])
+            del kwargs["test_objects"]
+
+        if not kwargs.get("binary") and conditions.is_firefox(self):
+            kwargs["binary"] = self.get_binary_path("app")
+        return run_marionette(tests, topsrcdir=self.topsrcdir, **kwargs)
+
+
+@CommandProvider
+class Marionette(MachCommandBase):
+
+    @property
+    def srcdir(self):
+        return os.path.join(self.topsrcdir, "testing/marionette")
+
+    @Command("marionette",
+             category="misc",
+             description="Remote control protocol to Gecko, used for functional UI tests and browser automation.",
+             conditions=[is_firefox_or_android],
+             )
+    def marionette(self):
+        self.parser.print_usage()
+        return 1
+
+    @SubCommand("marionette", "test",
+                description="Run browser automation tests based on Marionette harness.",
+                parser=create_parser_tests,
+                )
+    def marionette_test(self, tests, **kwargs):
+        if "test_objects" in kwargs:
+            tests = []
+            for obj in kwargs["test_objects"]:
+                tests.append(obj["file_relpath"])
+            del kwargs["test_objects"]
 
-        if not kwargs.get('binary') and conditions.is_firefox(self):
-            kwargs['binary'] = self.get_binary_path('app')
+        if not kwargs.get("binary") and conditions.is_firefox(self):
+            kwargs["binary"] = self.get_binary_path("app")
         return run_marionette(tests, topsrcdir=self.topsrcdir, **kwargs)
+
+    @SubCommand("marionette", "doc",
+                description="Generate Marionette server API documentation in testing/marionette/doc.")
+    @CommandArgument("--http",
+                     help='HTTP service address (e.g. "127.0.0.1:6060" or just ":6060".'
+                     )
+    def marionette_doc(self, http, **kwargs):
+        import subprocess
+
+        def is_marionette_source_file(filename):
+            path = os.path.join(self.srcdir, filename)
+            return (os.path.isfile(path)
+                    and filename.endswith(".js")
+                    and not filename.startswith("test_"))
+
+        srcs = [f for f in os.listdir(
+            self.srcdir) if is_marionette_source_file(f)]
+
+        proc = subprocess.Popen(
+            ["jsdoc", "-c", ".jsdoc.js"] + srcs, cwd=self.srcdir)
+        proc.wait()
+
+        if http:
+            import SimpleHTTPServer
+            import SocketServer
+
+            host, port = http.split(":")
+            host = host or "127.0.0.1"
+            port = int(port)
+
+            handler = SimpleHTTPServer.SimpleHTTPRequestHandler
+            httpd = SocketServer.TCPServer((host, int(port)), handler)
+
+            print "serving at %s:%s" % (host, port)
+            os.chdir(os.path.join(self.srcdir, "doc"))
+            httpd.serve_forever()