summaryrefslogtreecommitdiffstats
path: root/testing/mochitest/mochitest_options.py
diff options
context:
space:
mode:
Diffstat (limited to 'testing/mochitest/mochitest_options.py')
-rw-r--r--testing/mochitest/mochitest_options.py1060
1 files changed, 1060 insertions, 0 deletions
diff --git a/testing/mochitest/mochitest_options.py b/testing/mochitest/mochitest_options.py
new file mode 100644
index 000000000..9e61670a5
--- /dev/null
+++ b/testing/mochitest/mochitest_options.py
@@ -0,0 +1,1060 @@
+# 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 abc import ABCMeta, abstractmethod, abstractproperty
+from argparse import ArgumentParser, SUPPRESS
+from distutils.util import strtobool
+from urlparse import urlparse
+import json
+import os
+import tempfile
+
+from mozdevice import DroidADB, DroidSUT
+from mozprofile import DEFAULT_PORTS
+import mozinfo
+import mozlog
+import moznetwork
+
+
+here = os.path.abspath(os.path.dirname(__file__))
+
+try:
+ from mozbuild.base import (
+ MozbuildObject,
+ MachCommandConditions as conditions,
+ )
+ build_obj = MozbuildObject.from_environment(cwd=here)
+except ImportError:
+ build_obj = None
+ conditions = None
+
+
+def get_default_valgrind_suppression_files():
+ # We are trying to locate files in the source tree. So if we
+ # don't know where the source tree is, we must give up.
+ #
+ # When this is being run by |mach mochitest --valgrind ...|, it is
+ # expected that |build_obj| is not None, and so the logic below will
+ # select the correct suppression files.
+ #
+ # When this is run from mozharness, |build_obj| is None, and we expect
+ # that testing/mozharness/configs/unittests/linux_unittests.py will
+ # select the correct suppression files (and paths to them) and
+ # will specify them using the --valgrind-supp-files= flag. Hence this
+ # function will not get called when running from mozharness.
+ #
+ # Note: keep these Valgrind .sup file names consistent with those
+ # in testing/mozharness/configs/unittests/linux_unittest.py.
+ if build_obj is None or build_obj.topsrcdir is None:
+ return []
+
+ supps_path = os.path.join(build_obj.topsrcdir, "build", "valgrind")
+
+ rv = []
+ if mozinfo.os == "linux":
+ if mozinfo.processor == "x86_64":
+ rv.append(os.path.join(supps_path, "x86_64-redhat-linux-gnu.sup"))
+ rv.append(os.path.join(supps_path, "cross-architecture.sup"))
+ elif mozinfo.processor == "x86":
+ rv.append(os.path.join(supps_path, "i386-redhat-linux-gnu.sup"))
+ rv.append(os.path.join(supps_path, "cross-architecture.sup"))
+
+ return rv
+
+
+class ArgumentContainer():
+ __metaclass__ = ABCMeta
+
+ @abstractproperty
+ def args(self):
+ pass
+
+ @abstractproperty
+ def defaults(self):
+ pass
+
+ @abstractmethod
+ def validate(self, parser, args, context):
+ pass
+
+ def get_full_path(self, path, cwd):
+ """Get an absolute path relative to cwd."""
+ return os.path.normpath(os.path.join(cwd, os.path.expanduser(path)))
+
+
+class MochitestArguments(ArgumentContainer):
+ """General mochitest arguments."""
+
+ FLAVORS = ('a11y', 'browser', 'chrome', 'jetpack-addon', 'jetpack-package', 'plain')
+ LOG_LEVELS = ("DEBUG", "INFO", "WARNING", "ERROR", "FATAL")
+
+ args = [
+ [["test_paths"],
+ {"nargs": "*",
+ "metavar": "TEST",
+ "default": [],
+ "help": "Test to run. Can be a single test file or a directory of tests "
+ "(to run recursively). If omitted, the entire suite is run.",
+ }],
+ [["-f", "--flavor"],
+ {"default": "plain",
+ "choices": FLAVORS,
+ "help": "Mochitest flavor to run, one of {}. Defaults to 'plain'.".format(FLAVORS),
+ "suppress": build_obj is not None,
+ }],
+ [["--keep-open"],
+ {"nargs": "?",
+ "type": strtobool,
+ "const": "true",
+ "default": None,
+ "help": "Always keep the browser open after tests complete. Or always close the "
+ "browser with --keep-open=false",
+ }],
+ [["--appname"],
+ {"dest": "app",
+ "default": None,
+ "help": "Override the default binary used to run tests with the path provided, e.g "
+ "/usr/bin/firefox. If you have run ./mach package beforehand, you can "
+ "specify 'dist' to run tests against the distribution bundle's binary.",
+ }],
+ [["--utility-path"],
+ {"dest": "utilityPath",
+ "default": build_obj.bindir if build_obj is not None else None,
+ "help": "absolute path to directory containing utility programs "
+ "(xpcshell, ssltunnel, certutil)",
+ "suppress": True,
+ }],
+ [["--certificate-path"],
+ {"dest": "certPath",
+ "default": None,
+ "help": "absolute path to directory containing certificate store to use testing profile",
+ "suppress": True,
+ }],
+ [["--no-autorun"],
+ {"action": "store_false",
+ "dest": "autorun",
+ "default": True,
+ "help": "Do not start running tests automatically.",
+ }],
+ [["--timeout"],
+ {"type": int,
+ "default": None,
+ "help": "The per-test timeout in seconds (default: 60 seconds).",
+ }],
+ [["--max-timeouts"],
+ {"type": int,
+ "dest": "maxTimeouts",
+ "default": None,
+ "help": "The maximum number of timeouts permitted before halting testing.",
+ }],
+ [["--total-chunks"],
+ {"type": int,
+ "dest": "totalChunks",
+ "help": "Total number of chunks to split tests into.",
+ "default": None,
+ }],
+ [["--this-chunk"],
+ {"type": int,
+ "dest": "thisChunk",
+ "help": "If running tests by chunks, the chunk number to run.",
+ "default": None,
+ }],
+ [["--chunk-by-runtime"],
+ {"action": "store_true",
+ "dest": "chunkByRuntime",
+ "help": "Group tests such that each chunk has roughly the same runtime.",
+ "default": False,
+ }],
+ [["--chunk-by-dir"],
+ {"type": int,
+ "dest": "chunkByDir",
+ "help": "Group tests together in the same chunk that are in the same top "
+ "chunkByDir directories.",
+ "default": 0,
+ }],
+ [["--run-by-dir"],
+ {"action": "store_true",
+ "dest": "runByDir",
+ "help": "Run each directory in a single browser instance with a fresh profile.",
+ "default": False,
+ }],
+ [["--shuffle"],
+ {"action": "store_true",
+ "help": "Shuffle execution order of tests.",
+ "default": False,
+ }],
+ [["--console-level"],
+ {"dest": "consoleLevel",
+ "choices": LOG_LEVELS,
+ "default": "INFO",
+ "help": "One of {} to determine the level of console logging.".format(
+ ', '.join(LOG_LEVELS)),
+ "suppress": True,
+ }],
+ [["--bisect-chunk"],
+ {"dest": "bisectChunk",
+ "default": None,
+ "help": "Specify the failing test name to find the previous tests that may be "
+ "causing the failure.",
+ }],
+ [["--start-at"],
+ {"dest": "startAt",
+ "default": "",
+ "help": "Start running the test sequence at this test.",
+ }],
+ [["--end-at"],
+ {"dest": "endAt",
+ "default": "",
+ "help": "Stop running the test sequence at this test.",
+ }],
+ [["--subsuite"],
+ {"default": None,
+ "help": "Subsuite of tests to run. Unlike tags, subsuites also remove tests from "
+ "the default set. Only one can be specified at once.",
+ }],
+ [["--setenv"],
+ {"action": "append",
+ "dest": "environment",
+ "metavar": "NAME=VALUE",
+ "default": [],
+ "help": "Sets the given variable in the application's environment.",
+ }],
+ [["--exclude-extension"],
+ {"action": "append",
+ "dest": "extensionsToExclude",
+ "default": [],
+ "help": "Excludes the given extension from being installed in the test profile.",
+ "suppress": True,
+ }],
+ [["--browser-arg"],
+ {"action": "append",
+ "dest": "browserArgs",
+ "default": [],
+ "help": "Provides an argument to the test application (e.g Firefox).",
+ "suppress": True,
+ }],
+ [["--leak-threshold"],
+ {"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.",
+ "suppress": True,
+ }],
+ [["--fatal-assertions"],
+ {"action": "store_true",
+ "dest": "fatalAssertions",
+ "default": False,
+ "help": "Abort testing whenever an assertion is hit (requires a debug build to "
+ "be effective).",
+ "suppress": True,
+ }],
+ [["--extra-profile-file"],
+ {"action": "append",
+ "dest": "extraProfileFiles",
+ "default": [],
+ "help": "Copy specified files/dirs to testing profile. Can be specified more "
+ "than once.",
+ "suppress": True,
+ }],
+ [["--install-extension"],
+ {"action": "append",
+ "dest": "extensionsToInstall",
+ "default": [],
+ "help": "Install the specified extension in the testing profile. Can be a path "
+ "to a .xpi file.",
+ }],
+ [["--profile-path"],
+ {"dest": "profilePath",
+ "default": None,
+ "help": "Directory where the profile will be stored. This directory will be "
+ "deleted after the tests are finished.",
+ "suppress": True,
+ }],
+ [["--testing-modules-dir"],
+ {"dest": "testingModulesDir",
+ "default": None,
+ "help": "Directory where testing-only JS modules are located.",
+ "suppress": True,
+ }],
+ [["--repeat"],
+ {"type": int,
+ "default": 0,
+ "help": "Repeat the tests the given number of times.",
+ }],
+ [["--run-until-failure"],
+ {"action": "store_true",
+ "dest": "runUntilFailure",
+ "default": False,
+ "help": "Run tests repeatedly but stop the first time a test fails. Default cap "
+ "is 30 runs, which can be overridden with the --repeat parameter.",
+ }],
+ [["--manifest"],
+ {"dest": "manifestFile",
+ "default": None,
+ "help": "Path to a manifestparser (.ini formatted) manifest of tests to run.",
+ "suppress": True,
+ }],
+ [["--extra-mozinfo-json"],
+ {"dest": "extra_mozinfo_json",
+ "default": None,
+ "help": "Filter tests based on a given mozinfo file.",
+ "suppress": True,
+ }],
+ [["--testrun-manifest-file"],
+ {"dest": "testRunManifestFile",
+ "default": 'tests.json',
+ "help": "Overrides the default filename of the tests.json manifest file that is "
+ "generated by the harness and used by SimpleTest. Only useful when running "
+ "multiple test runs simulatenously on the same machine.",
+ "suppress": True,
+ }],
+ [["--dump-tests"],
+ {"dest": "dump_tests",
+ "default": None,
+ "help": "Specify path to a filename to dump all the tests that will be run",
+ "suppress": True,
+ }],
+ [["--failure-file"],
+ {"dest": "failureFile",
+ "default": None,
+ "help": "Filename of the output file where we can store a .json list of failures "
+ "to be run in the future with --run-only-tests.",
+ "suppress": True,
+ }],
+ [["--run-slower"],
+ {"action": "store_true",
+ "dest": "runSlower",
+ "default": False,
+ "help": "Delay execution between tests.",
+ }],
+ [["--metro-immersive"],
+ {"action": "store_true",
+ "dest": "immersiveMode",
+ "default": False,
+ "help": "Launches tests in an immersive browser.",
+ "suppress": True,
+ }],
+ [["--httpd-path"],
+ {"dest": "httpdPath",
+ "default": None,
+ "help": "Path to the httpd.js file.",
+ "suppress": True,
+ }],
+ [["--setpref"],
+ {"action": "append",
+ "metavar": "PREF=VALUE",
+ "default": [],
+ "dest": "extraPrefs",
+ "help": "Defines an extra user preference.",
+ }],
+ [["--jsdebugger"],
+ {"action": "store_true",
+ "default": False,
+ "help": "Start the browser JS debugger before running the test. Implies --no-autorun.",
+ }],
+ [["--debug-on-failure"],
+ {"action": "store_true",
+ "default": False,
+ "dest": "debugOnFailure",
+ "help": "Breaks execution and enters the JS debugger on a test failure. Should "
+ "be used together with --jsdebugger."
+ }],
+ [["--disable-e10s"],
+ {"action": "store_false",
+ "default": True,
+ "dest": "e10s",
+ "help": "Run tests with electrolysis preferences and test filtering disabled.",
+ }],
+ [["--store-chrome-manifest"],
+ {"action": "store",
+ "help": "Destination path to write a copy of any chrome manifest "
+ "written by the harness.",
+ "default": None,
+ "suppress": True,
+ }],
+ [["--jscov-dir-prefix"],
+ {"action": "store",
+ "help": "Directory to store per-test line coverage data as json "
+ "(browser-chrome only). To emit lcov formatted data, set "
+ "JS_CODE_COVERAGE_OUTPUT_DIR in the environment.",
+ "default": None,
+ "suppress": True,
+ }],
+ [["--strict-content-sandbox"],
+ {"action": "store_true",
+ "default": False,
+ "dest": "strictContentSandbox",
+ "help": "Run tests with a more strict content sandbox (Windows only).",
+ "suppress": not mozinfo.isWin,
+ }],
+ [["--nested_oop"],
+ {"action": "store_true",
+ "default": False,
+ "help": "Run tests with nested_oop preferences and test filtering enabled.",
+ }],
+ [["--dmd"],
+ {"action": "store_true",
+ "default": False,
+ "help": "Run tests with DMD active.",
+ }],
+ [["--dmd-path"],
+ {"default": None,
+ "dest": "dmdPath",
+ "help": "Specifies the path to the directory containing the shared library for DMD.",
+ "suppress": True,
+ }],
+ [["--dump-output-directory"],
+ {"default": None,
+ "dest": "dumpOutputDirectory",
+ "help": "Specifies the directory in which to place dumped memory reports.",
+ }],
+ [["--dump-about-memory-after-test"],
+ {"action": "store_true",
+ "default": False,
+ "dest": "dumpAboutMemoryAfterTest",
+ "help": "Dump an about:memory log after each test in the directory specified "
+ "by --dump-output-directory.",
+ }],
+ [["--dump-dmd-after-test"],
+ {"action": "store_true",
+ "default": False,
+ "dest": "dumpDMDAfterTest",
+ "help": "Dump a DMD log after each test in the directory specified "
+ "by --dump-output-directory.",
+ }],
+ [["--slowscript"],
+ {"action": "store_true",
+ "default": False,
+ "help": "Do not set the JS_DISABLE_SLOW_SCRIPT_SIGNALS env variable; "
+ "when not set, recoverable but misleading SIGSEGV instances "
+ "may occur in Ion/Odin JIT code.",
+ }],
+ [["--screenshot-on-fail"],
+ {"action": "store_true",
+ "default": False,
+ "dest": "screenshotOnFail",
+ "help": "Take screenshots on all test failures. Set $MOZ_UPLOAD_DIR to a directory "
+ "for storing the screenshots."
+ }],
+ [["--quiet"],
+ {"action": "store_true",
+ "dest": "quiet",
+ "default": False,
+ "help": "Do not print test log lines unless a failure occurs.",
+ }],
+ [["--pidfile"],
+ {"dest": "pidFile",
+ "default": "",
+ "help": "Name of the pidfile to generate.",
+ "suppress": True,
+ }],
+ [["--use-test-media-devices"],
+ {"action": "store_true",
+ "default": False,
+ "dest": "useTestMediaDevices",
+ "help": "Use test media device drivers for media testing.",
+ }],
+ [["--gmp-path"],
+ {"default": None,
+ "help": "Path to fake GMP plugin. Will be deduced from the binary if not passed.",
+ "suppress": True,
+ }],
+ [["--xre-path"],
+ {"dest": "xrePath",
+ "default": None, # individual scripts will set a sane default
+ "help": "Absolute path to directory containing XRE (probably xulrunner).",
+ "suppress": True,
+ }],
+ [["--symbols-path"],
+ {"dest": "symbolsPath",
+ "default": None,
+ "help": "Absolute path to directory containing breakpad symbols, or the URL of a "
+ "zip file containing symbols",
+ "suppress": True,
+ }],
+ [["--debugger"],
+ {"default": None,
+ "help": "Debugger binary to run tests in. Program name or path.",
+ }],
+ [["--debugger-args"],
+ {"dest": "debuggerArgs",
+ "default": None,
+ "help": "Arguments to pass to the debugger.",
+ }],
+ [["--valgrind"],
+ {"default": None,
+ "help": "Valgrind binary to run tests with. Program name or path.",
+ }],
+ [["--valgrind-args"],
+ {"dest": "valgrindArgs",
+ "default": None,
+ "help": "Comma-separated list of extra arguments to pass to Valgrind.",
+ }],
+ [["--valgrind-supp-files"],
+ {"dest": "valgrindSuppFiles",
+ "default": None,
+ "help": "Comma-separated list of suppression files to pass to Valgrind.",
+ }],
+ [["--debugger-interactive"],
+ {"action": "store_true",
+ "dest": "debuggerInteractive",
+ "default": None,
+ "help": "Prevents the test harness from redirecting stdout and stderr for "
+ "interactive debuggers.",
+ "suppress": True,
+ }],
+ [["--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.",
+ }],
+ [["--enable-cpow-warnings"],
+ {"action": "store_true",
+ "dest": "enableCPOWWarnings",
+ "help": "Enable logging of unsafe CPOW usage, which is disabled by default for tests",
+ "suppress": True,
+ }],
+ [["--marionette"],
+ {"default": None,
+ "help": "host:port to use when connecting to Marionette",
+ }],
+ [["--marionette-port-timeout"],
+ {"default": None,
+ "help": "Timeout while waiting for the marionette port to open.",
+ "suppress": True,
+ }],
+ [["--marionette-socket-timeout"],
+ {"default": None,
+ "help": "Timeout while waiting to receive a message from the marionette server.",
+ "suppress": True,
+ }],
+ [["--marionette-startup-timeout"],
+ {"default": None,
+ "help": "Timeout while waiting for marionette server startup.",
+ "suppress": True,
+ }],
+ [["--cleanup-crashes"],
+ {"action": "store_true",
+ "dest": "cleanupCrashes",
+ "default": False,
+ "help": "Delete pending crash reports before running tests.",
+ "suppress": True,
+ }],
+ [["--websocket-process-bridge-port"],
+ {"default": "8191",
+ "dest": "websocket_process_bridge_port",
+ "help": "Port for websocket/process bridge. Default 8191.",
+ }],
+ ]
+
+ defaults = {
+ # Bug 1065098 - The geckomediaplugin process fails to produce a leak
+ # log for some reason.
+ 'ignoreMissingLeaks': ["geckomediaplugin"],
+ 'extensionsToExclude': ['specialpowers'],
+ # Set server information on the args object
+ 'webServer': '127.0.0.1',
+ 'httpPort': DEFAULT_PORTS['http'],
+ 'sslPort': DEFAULT_PORTS['https'],
+ 'webSocketPort': '9988',
+ # The default websocket port is incorrect in mozprofile; it is
+ # set to the SSL proxy setting. See:
+ # see https://bugzilla.mozilla.org/show_bug.cgi?id=916517
+ # args.webSocketPort = DEFAULT_PORTS['ws']
+ }
+
+ def validate(self, parser, options, context):
+ """Validate generic options."""
+
+ # for test manifest parsing.
+ mozinfo.update({"strictContentSandbox": options.strictContentSandbox})
+ # for test manifest parsing.
+ mozinfo.update({"nested_oop": options.nested_oop})
+
+ # and android doesn't use 'app' the same way, so skip validation
+ if parser.app != 'android':
+ if options.app is None:
+ if build_obj:
+ options.app = build_obj.get_binary_path()
+ else:
+ parser.error(
+ "could not find the application path, --appname must be specified")
+ elif options.app == "dist" and build_obj:
+ options.app = build_obj.get_binary_path(where='staged-package')
+
+ options.app = self.get_full_path(options.app, parser.oldcwd)
+ if not os.path.exists(options.app):
+ parser.error("Error: Path {} doesn't exist. Are you executing "
+ "$objdir/_tests/testing/mochitest/runtests.py?".format(
+ options.app))
+
+ if options.gmp_path is None and options.app and build_obj:
+ # Need to fix the location of gmp_fake which might not be shipped in the binary
+ gmp_modules = (
+ ('gmp-fake', '1.0'),
+ ('gmp-clearkey', '0.1'),
+ ('gmp-fakeopenh264', '1.0')
+ )
+ options.gmp_path = os.pathsep.join(
+ os.path.join(build_obj.bindir, *p) for p in gmp_modules)
+
+ if options.totalChunks is not None and options.thisChunk is None:
+ parser.error(
+ "thisChunk must be specified when totalChunks is specified")
+
+ if options.extra_mozinfo_json:
+ if not os.path.isfile(options.extra_mozinfo_json):
+ parser.error("Error: couldn't find mozinfo.json at '%s'."
+ % options.extra_mozinfo_json)
+
+ options.extra_mozinfo_json = json.load(open(options.extra_mozinfo_json))
+
+ if options.totalChunks:
+ if not 1 <= options.thisChunk <= options.totalChunks:
+ parser.error("thisChunk must be between 1 and totalChunks")
+
+ if options.chunkByDir and options.chunkByRuntime:
+ parser.error(
+ "can only use one of --chunk-by-dir or --chunk-by-runtime")
+
+ if options.xrePath is None:
+ # default xrePath to the app path if not provided
+ # but only if an app path was explicitly provided
+ if options.app != parser.get_default('app'):
+ options.xrePath = os.path.dirname(options.app)
+ if mozinfo.isMac:
+ options.xrePath = os.path.join(
+ os.path.dirname(
+ options.xrePath),
+ "Resources")
+ elif build_obj is not None:
+ # otherwise default to dist/bin
+ options.xrePath = build_obj.bindir
+ else:
+ parser.error(
+ "could not find xre directory, --xre-path must be specified")
+
+ # allow relative paths
+ if options.xrePath:
+ options.xrePath = self.get_full_path(options.xrePath, parser.oldcwd)
+
+ if options.profilePath:
+ options.profilePath = self.get_full_path(options.profilePath, parser.oldcwd)
+
+ if options.dmdPath:
+ options.dmdPath = self.get_full_path(options.dmdPath, parser.oldcwd)
+
+ if options.dmd and not options.dmdPath:
+ if build_obj:
+ options.dmdPath = build_obj.bindir
+ else:
+ parser.error(
+ "could not find dmd libraries, specify them with --dmd-path")
+
+ if options.utilityPath:
+ options.utilityPath = self.get_full_path(options.utilityPath, parser.oldcwd)
+
+ if options.certPath:
+ options.certPath = self.get_full_path(options.certPath, parser.oldcwd)
+ elif build_obj:
+ options.certPath = os.path.join(build_obj.topsrcdir, 'build', 'pgo', 'certs')
+
+ if options.symbolsPath and len(urlparse(options.symbolsPath).scheme) < 2:
+ options.symbolsPath = self.get_full_path(options.symbolsPath, parser.oldcwd)
+ elif not options.symbolsPath and build_obj:
+ options.symbolsPath = os.path.join(build_obj.distdir, 'crashreporter-symbols')
+
+ if options.jsdebugger:
+ options.extraPrefs += [
+ "devtools.debugger.remote-enabled=true",
+ "devtools.chrome.enabled=true",
+ "devtools.debugger.prompt-connection=false"
+ ]
+ options.autorun = False
+
+ if options.debugOnFailure and not options.jsdebugger:
+ parser.error(
+ "--debug-on-failure requires --jsdebugger.")
+
+ if options.debuggerArgs and not options.debugger:
+ parser.error(
+ "--debugger-args requires --debugger.")
+
+ if options.valgrind or options.debugger:
+ # valgrind and some debuggers may cause Gecko to start slowly. Make sure
+ # marionette waits long enough to connect.
+ options.marionette_port_timeout = 900
+ options.marionette_socket_timeout = 540
+
+ if options.store_chrome_manifest:
+ options.store_chrome_manifest = os.path.abspath(options.store_chrome_manifest)
+ if not os.path.isdir(os.path.dirname(options.store_chrome_manifest)):
+ parser.error(
+ "directory for %s does not exist as a destination to copy a "
+ "chrome manifest." % options.store_chrome_manifest)
+
+ if options.jscov_dir_prefix:
+ options.jscov_dir_prefix = os.path.abspath(options.jscov_dir_prefix)
+ if not os.path.isdir(options.jscov_dir_prefix):
+ parser.error(
+ "directory %s does not exist as a destination for coverage "
+ "data." % options.jscov_dir_prefix)
+
+ if options.testingModulesDir is None:
+ if build_obj:
+ options.testingModulesDir = os.path.join(
+ build_obj.topobjdir, '_tests', 'modules')
+ else:
+ # Try to guess the testing modules directory.
+ # This somewhat grotesque hack allows the buildbot machines to find the
+ # modules directory without having to configure the buildbot hosts. This
+ # code should never be executed in local runs because the build system
+ # should always set the flag that populates this variable. If buildbot ever
+ # passes this argument, this code can be deleted.
+ possible = os.path.join(here, os.path.pardir, 'modules')
+
+ if os.path.isdir(possible):
+ options.testingModulesDir = possible
+
+ if build_obj:
+ plugins_dir = os.path.join(build_obj.distdir, 'plugins')
+ if plugins_dir not in options.extraProfileFiles:
+ options.extraProfileFiles.append(plugins_dir)
+
+ # Even if buildbot is updated, we still want this, as the path we pass in
+ # to the app must be absolute and have proper slashes.
+ if options.testingModulesDir is not None:
+ options.testingModulesDir = os.path.normpath(
+ options.testingModulesDir)
+
+ if not os.path.isabs(options.testingModulesDir):
+ options.testingModulesDir = os.path.abspath(
+ options.testingModulesDir)
+
+ if not os.path.isdir(options.testingModulesDir):
+ parser.error('--testing-modules-dir not a directory: %s' %
+ options.testingModulesDir)
+
+ options.testingModulesDir = options.testingModulesDir.replace(
+ '\\',
+ '/')
+ if options.testingModulesDir[-1] != '/':
+ options.testingModulesDir += '/'
+
+ if options.immersiveMode:
+ if not mozinfo.isWin:
+ parser.error("immersive is only supported on Windows 8 and up.")
+ options.immersiveHelperPath = os.path.join(
+ options.utilityPath, "metrotestharness.exe")
+ if not os.path.exists(options.immersiveHelperPath):
+ parser.error("%s not found, cannot launch immersive tests." %
+ options.immersiveHelperPath)
+
+ if options.runUntilFailure:
+ if not options.repeat:
+ options.repeat = 29
+
+ if options.dumpOutputDirectory is None:
+ options.dumpOutputDirectory = tempfile.gettempdir()
+
+ if options.dumpAboutMemoryAfterTest or options.dumpDMDAfterTest:
+ if not os.path.isdir(options.dumpOutputDirectory):
+ parser.error('--dump-output-directory not a directory: %s' %
+ options.dumpOutputDirectory)
+
+ if options.useTestMediaDevices:
+ if not mozinfo.isLinux:
+ parser.error(
+ '--use-test-media-devices is only supported on Linux currently')
+ for f in ['/usr/bin/gst-launch-0.10', '/usr/bin/pactl']:
+ if not os.path.isfile(f):
+ parser.error(
+ 'Missing binary %s required for '
+ '--use-test-media-devices' % f)
+
+ if options.nested_oop:
+ options.e10s = True
+
+ options.leakThresholds = {
+ "default": options.defaultLeakThreshold,
+ "tab": 10000, # See dependencies of bug 1051230.
+ # GMP rarely gets a log, but when it does, it leaks a little.
+ "geckomediaplugin": 20000,
+ }
+
+ # XXX We can't normalize test_paths in the non build_obj case here,
+ # because testRoot depends on the flavor, which is determined by the
+ # mach command and therefore not finalized yet. Conversely, test paths
+ # need to be normalized here for the mach case.
+ if options.test_paths and build_obj:
+ # Normalize test paths so they are relative to test root
+ options.test_paths = [build_obj._wrap_path_argument(p).relpath()
+ for p in options.test_paths]
+
+ return options
+
+
+class AndroidArguments(ArgumentContainer):
+ """Android specific arguments."""
+
+ args = [
+ [["--remote-app-path"],
+ {"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.",
+ "default": None,
+ }],
+ [["--deviceIP"],
+ {"dest": "deviceIP",
+ "help": "ip address of remote device to test",
+ "default": None,
+ }],
+ [["--deviceSerial"],
+ {"dest": "deviceSerial",
+ "help": "ip address of remote device to test",
+ "default": None,
+ }],
+ [["--dm_trans"],
+ {"choices": ["adb", "sut"],
+ "default": "adb",
+ "help": "The transport to use for communication with the device [default: adb].",
+ "suppress": True,
+ }],
+ [["--adbpath"],
+ {"dest": "adbPath",
+ "default": None,
+ "help": "Path to adb binary.",
+ "suppress": True,
+ }],
+ [["--devicePort"],
+ {"dest": "devicePort",
+ "type": int,
+ "default": 20701,
+ "help": "port of remote device to test",
+ }],
+ [["--remote-product-name"],
+ {"dest": "remoteProductName",
+ "default": "fennec",
+ "help": "The executable's name of remote product to test - either \
+ fennec or firefox, defaults to fennec",
+ "suppress": True,
+ }],
+ [["--remote-logfile"],
+ {"dest": "remoteLogFile",
+ "default": None,
+ "help": "Name of log file on the device relative to the device \
+ root. PLEASE ONLY USE A FILENAME.",
+ }],
+ [["--remote-webserver"],
+ {"dest": "remoteWebServer",
+ "default": None,
+ "help": "ip address where the remote web server is hosted at",
+ }],
+ [["--http-port"],
+ {"dest": "httpPort",
+ "default": DEFAULT_PORTS['http'],
+ "help": "http port of the remote web server",
+ "suppress": True,
+ }],
+ [["--ssl-port"],
+ {"dest": "sslPort",
+ "default": DEFAULT_PORTS['https'],
+ "help": "ssl port of the remote web server",
+ "suppress": True,
+ }],
+ [["--robocop-ini"],
+ {"dest": "robocopIni",
+ "default": "",
+ "help": "name of the .ini file containing the list of tests to run",
+ }],
+ [["--robocop-apk"],
+ {"dest": "robocopApk",
+ "default": "",
+ "help": "name of the Robocop APK to use for ADB test running",
+ }],
+ [["--remoteTestRoot"],
+ {"dest": "remoteTestRoot",
+ "default": None,
+ "help": "remote directory to use as test root \
+ (eg. /mnt/sdcard/tests or /data/local/tests)",
+ "suppress": True,
+ }],
+ ]
+
+ defaults = {
+ 'dm': None,
+ # we don't want to exclude specialpowers on android just yet
+ 'extensionsToExclude': [],
+ # mochijar doesn't get installed via marionette on android
+ 'extensionsToInstall': [os.path.join(here, 'mochijar')],
+ 'logFile': 'mochitest.log',
+ 'utilityPath': None,
+ }
+
+ def validate(self, parser, options, context):
+ """Validate android options."""
+
+ if build_obj:
+ options.log_mach = '-'
+
+ device_args = {'deviceRoot': options.remoteTestRoot}
+ if options.dm_trans == "adb":
+ device_args['adbPath'] = options.adbPath
+ if options.deviceIP:
+ device_args['host'] = options.deviceIP
+ device_args['port'] = options.devicePort
+ elif options.deviceSerial:
+ device_args['deviceSerial'] = options.deviceSerial
+ options.dm = DroidADB(**device_args)
+ elif options.dm_trans == 'sut':
+ if options.deviceIP is None:
+ parser.error(
+ "If --dm_trans = sut, you must provide a device IP")
+ device_args['host'] = options.deviceIP
+ device_args['port'] = options.devicePort
+ options.dm = DroidSUT(**device_args)
+
+ if not options.remoteTestRoot:
+ options.remoteTestRoot = options.dm.deviceRoot
+
+ if options.remoteWebServer is None:
+ if os.name != "nt":
+ options.remoteWebServer = moznetwork.get_ip()
+ else:
+ parser.error(
+ "you must specify a --remote-webserver=<ip address>")
+
+ options.webServer = options.remoteWebServer
+
+ if options.remoteLogFile is None:
+ options.remoteLogFile = options.remoteTestRoot + \
+ '/logs/mochitest.log'
+
+ if options.remoteLogFile.count('/') < 1:
+ options.remoteLogFile = options.remoteTestRoot + \
+ '/' + options.remoteLogFile
+
+ if options.remoteAppPath and options.app:
+ parser.error(
+ "You cannot specify both the remoteAppPath and the app setting")
+ elif options.remoteAppPath:
+ options.app = options.remoteTestRoot + "/" + options.remoteAppPath
+ elif options.app is None:
+ if build_obj:
+ options.app = build_obj.substs['ANDROID_PACKAGE_NAME']
+ else:
+ # Neither remoteAppPath nor app are set -- error
+ parser.error("You must specify either appPath or app")
+
+ if build_obj and 'MOZ_HOST_BIN' in os.environ:
+ options.xrePath = os.environ['MOZ_HOST_BIN']
+
+ # Only reset the xrePath if it wasn't provided
+ if options.xrePath is None:
+ options.xrePath = options.utilityPath
+
+ if options.pidFile != "":
+ f = open(options.pidFile, 'w')
+ f.write("%s" % os.getpid())
+ f.close()
+
+ # Robocop specific options
+ if options.robocopIni != "":
+ if not os.path.exists(options.robocopIni):
+ parser.error(
+ "Unable to find specified robocop .ini manifest '%s'" %
+ options.robocopIni)
+ options.robocopIni = os.path.abspath(options.robocopIni)
+
+ if not options.robocopApk and build_obj:
+ options.robocopApk = os.path.join(build_obj.topobjdir, 'mobile', 'android',
+ 'tests', 'browser',
+ 'robocop', 'robocop-debug.apk')
+
+ if options.robocopApk != "":
+ if not os.path.exists(options.robocopApk):
+ parser.error(
+ "Unable to find robocop APK '%s'" %
+ options.robocopApk)
+ options.robocopApk = os.path.abspath(options.robocopApk)
+
+ # Disable e10s by default on Android because we don't run Android
+ # e10s jobs anywhere yet.
+ options.e10s = False
+ mozinfo.update({'e10s': options.e10s})
+
+ # allow us to keep original application around for cleanup while
+ # running robocop via 'am'
+ options.remoteappname = options.app
+ return options
+
+
+container_map = {
+ 'generic': [MochitestArguments],
+ 'android': [MochitestArguments, AndroidArguments],
+}
+
+
+class MochitestArgumentParser(ArgumentParser):
+ """%(prog)s [options] [test paths]"""
+
+ _containers = None
+ context = {}
+
+ def __init__(self, app=None, **kwargs):
+ ArgumentParser.__init__(self, usage=self.__doc__, conflict_handler='resolve', **kwargs)
+
+ self.oldcwd = os.getcwd()
+ self.app = app
+ if not self.app and build_obj:
+ if conditions.is_android(build_obj):
+ self.app = 'android'
+ if not self.app:
+ # platform can't be determined and app wasn't specified explicitly,
+ # so just use generic arguments and hope for the best
+ self.app = 'generic'
+
+ if self.app not in container_map:
+ self.error("Unrecognized app '{}'! Must be one of: {}".format(
+ self.app, ', '.join(container_map.keys())))
+
+ defaults = {}
+ for container in self.containers:
+ defaults.update(container.defaults)
+ group = self.add_argument_group(container.__class__.__name__, container.__doc__)
+
+ for cli, kwargs in container.args:
+ # Allocate new lists so references to original don't get mutated.
+ # allowing multiple uses within a single process.
+ if "default" in kwargs and isinstance(kwargs['default'], list):
+ kwargs["default"] = []
+
+ if 'suppress' in kwargs:
+ if kwargs['suppress']:
+ kwargs['help'] = SUPPRESS
+ del kwargs['suppress']
+
+ group.add_argument(*cli, **kwargs)
+
+ self.set_defaults(**defaults)
+ mozlog.commandline.add_logging_group(self)
+
+ @property
+ def containers(self):
+ if self._containers:
+ return self._containers
+
+ 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