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.py1459
1 files changed, 1459 insertions, 0 deletions
diff --git a/testing/mochitest/mochitest_options.py b/testing/mochitest/mochitest_options.py
new file mode 100644
index 0000000000..0b4a03f5b2
--- /dev/null
+++ b/testing/mochitest/mochitest_options.py
@@ -0,0 +1,1459 @@
+# 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/.
+
+import json
+import os
+import sys
+import tempfile
+from abc import ABCMeta, abstractmethod, abstractproperty
+from argparse import SUPPRESS, ArgumentParser
+from distutils import spawn
+from distutils.util import strtobool
+from itertools import chain
+
+import mozinfo
+import mozlog
+import moznetwork
+import six
+from mozprofile import DEFAULT_PORTS
+from six.moves.urllib.parse import urlparse
+
+here = os.path.abspath(os.path.dirname(__file__))
+
+try:
+ from mozbuild.base import MachCommandConditions as conditions
+ from mozbuild.base import MozbuildObject
+
+ build_obj = MozbuildObject.from_environment(cwd=here)
+except ImportError:
+ build_obj = None
+ conditions = None
+
+
+# Maps test flavors to data needed to run them
+ALL_FLAVORS = {
+ "mochitest": {
+ "suite": "plain",
+ "aliases": ("plain", "mochitest"),
+ "enabled_apps": ("firefox", "android"),
+ "extra_args": {
+ "flavor": "plain",
+ },
+ "install_subdir": "tests",
+ },
+ "chrome": {
+ "suite": "chrome",
+ "aliases": ("chrome", "mochitest-chrome"),
+ "enabled_apps": ("firefox"),
+ "extra_args": {
+ "flavor": "chrome",
+ },
+ },
+ "browser-chrome": {
+ "suite": "browser",
+ "aliases": ("browser", "browser-chrome", "mochitest-browser-chrome", "bc"),
+ "enabled_apps": ("firefox", "thunderbird"),
+ "extra_args": {
+ "flavor": "browser",
+ },
+ },
+ "a11y": {
+ "suite": "a11y",
+ "aliases": ("a11y", "mochitest-a11y", "accessibility"),
+ "enabled_apps": ("firefox",),
+ "extra_args": {
+ "flavor": "a11y",
+ },
+ },
+}
+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()])
+
+
+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-pc-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-pc-linux-gnu.sup"))
+ rv.append(os.path.join(supps_path, "cross-architecture.sup"))
+
+ return rv
+
+
+@six.add_metaclass(ABCMeta)
+class ArgumentContainer:
+ @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."""
+
+ 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"],
+ {
+ "choices": SUPPORTED_FLAVORS,
+ "metavar": "{{{}}}".format(", ".join(CANONICAL_FLAVORS)),
+ "default": None,
+ "help": "Only run tests of this flavor.",
+ },
+ ],
+ [
+ ["--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", # NOQA: E501
+ "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-manifest"],
+ {
+ "action": "store_true",
+ "dest": "runByManifest",
+ "help": "Run each manifest in a single browser instance with a fresh profile.",
+ "default": False,
+ "suppress": True,
+ },
+ ],
+ [
+ ["--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,
+ },
+ ],
+ [
+ ["--conditioned-profile"],
+ {
+ "dest": "conditionedProfile",
+ "action": "store_true",
+ "default": False,
+ "help": "Download and run with a full conditioned profile.",
+ },
+ ],
+ [
+ ["--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.",
+ },
+ ],
+ [
+ ["--httpd-path"],
+ {
+ "dest": "httpdPath",
+ "default": None,
+ "help": "Path to the httpd.js file.",
+ "suppress": True,
+ },
+ ],
+ [
+ ["--use-http3-server"],
+ {
+ "dest": "useHttp3Server",
+ "default": False,
+ "help": "Whether to use the Http3 server",
+ "action": "store_true",
+ },
+ ],
+ [
+ ["--use-http2-server"],
+ {
+ "dest": "useHttp2Server",
+ "default": False,
+ "help": "Whether to use the Http2 server",
+ "action": "store_true",
+ },
+ ],
+ [
+ ["--setpref"],
+ {
+ "action": "append",
+ "metavar": "PREF=VALUE",
+ "default": [],
+ "dest": "extraPrefs",
+ "help": "Defines an extra user preference.",
+ },
+ ],
+ [
+ ["--jsconsole"],
+ {
+ "action": "store_true",
+ "default": False,
+ "help": "Open the Browser Console.",
+ },
+ ],
+ [
+ ["--jsdebugger"],
+ {
+ "action": "store_true",
+ "default": False,
+ "help": "Start the browser JS debugger before running the test.",
+ },
+ ],
+ [
+ ["--jsdebugger-path"],
+ {
+ "default": None,
+ "dest": "jsdebuggerPath",
+ "help": "Path to a Firefox binary that will be used to run the toolbox. Should "
+ "be used together with --jsdebugger.",
+ },
+ ],
+ [
+ ["--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.",
+ },
+ ],
+ [
+ ["--enable-a11y-checks"],
+ {
+ "action": "store_true",
+ "default": False,
+ "dest": "a11y_checks",
+ "help": "Run tests with accessibility checks enabled.",
+ },
+ ],
+ [
+ ["--disable-fission"],
+ {
+ "action": "store_true",
+ "default": False,
+ "dest": "disable_fission",
+ "help": "Run tests with fission (site isolation) disabled.",
+ },
+ ],
+ [
+ ["--enable-xorigin-tests"],
+ {
+ "action": "store_true",
+ "default": False,
+ "dest": "xOriginTests",
+ "help": "Run tests in a cross origin iframe.",
+ },
+ ],
+ [
+ ["--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,
+ },
+ ],
+ [
+ ["--dmd"],
+ {
+ "action": "store_true",
+ "default": False,
+ "help": "Run tests with DMD active.",
+ },
+ ],
+ [
+ ["--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 (and an accompanying about:memory log) after each test. "
+ "These will be dumped into your default temp directory, NOT the directory "
+ "specified by --dump-output-directory. The logs are numbered by test, and "
+ "each test will include output that indicates the DMD output filename.",
+ },
+ ],
+ [
+ ["--screenshot-on-fail"],
+ {
+ "action": "store_true",
+ "default": False,
+ "dest": "screenshotOnFail",
+ "help": "Take screenshots on all test failures. Set $MOZ_UPLOAD_DIR to a directory " # NOQA: E501
+ "for storing the screenshots.",
+ },
+ ],
+ [
+ ["--quiet"],
+ {
+ "action": "store_true",
+ "dest": "quiet",
+ "default": False,
+ "help": "Do not print test log lines unless a failure occurs.",
+ },
+ ],
+ [
+ ["--headless"],
+ {
+ "action": "store_true",
+ "dest": "headless",
+ "default": False,
+ "help": "Run tests in headless mode.",
+ },
+ ],
+ [
+ ["--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.",
+ },
+ ],
+ [
+ ["--marionette"],
+ {
+ "default": None,
+ "help": "host:port to use when connecting to Marionette",
+ },
+ ],
+ [
+ ["--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.",
+ },
+ ],
+ [
+ ["--failure-pattern-file"],
+ {
+ "default": None,
+ "dest": "failure_pattern_file",
+ "help": "File describes all failure patterns of the tests.",
+ "suppress": True,
+ },
+ ],
+ [
+ ["--sandbox-read-whitelist"],
+ {
+ "default": [],
+ "dest": "sandboxReadWhitelist",
+ "action": "append",
+ "help": "Path to add to the sandbox whitelist.",
+ "suppress": True,
+ },
+ ],
+ [
+ ["--verify"],
+ {
+ "action": "store_true",
+ "default": False,
+ "help": "Run tests in verification mode: Run many times in different "
+ "ways, to see if there are intermittent failures.",
+ },
+ ],
+ [
+ ["--verify-fission"],
+ {
+ "action": "store_true",
+ "default": False,
+ "help": "Run tests once without Fission, once with Fission",
+ },
+ ],
+ [
+ ["--verify-max-time"],
+ {
+ "type": int,
+ "default": 3600,
+ "help": "Maximum time, in seconds, to run in --verify mode.",
+ },
+ ],
+ [
+ ["--profiler"],
+ {
+ "action": "store_true",
+ "dest": "profiler",
+ "default": False,
+ "help": "Run the Firefox Profiler and get a performance profile of the "
+ "mochitest. This is useful to find performance issues, and also "
+ "to see what exactly the test is doing. To get profiler options run: "
+ "`MOZ_PROFILER_HELP=1 ./mach run`",
+ },
+ ],
+ [
+ ["--profiler-save-only"],
+ {
+ "action": "store_true",
+ "dest": "profilerSaveOnly",
+ "default": False,
+ "help": "Run the Firefox Profiler and save it to the path specified by the "
+ "MOZ_UPLOAD_DIR environment variable.",
+ },
+ ],
+ [
+ ["--run-failures"],
+ {
+ "action": "store",
+ "dest": "runFailures",
+ "default": "",
+ "help": "Run fail-if/skip-if tests that match a keyword given.",
+ },
+ ],
+ [
+ ["--timeout-as-pass"],
+ {
+ "action": "store_true",
+ "dest": "timeoutAsPass",
+ "default": False,
+ "help": "treat harness level timeouts as passing (used for quarantine jobs).",
+ },
+ ],
+ [
+ ["--crash-as-pass"],
+ {
+ "action": "store_true",
+ "dest": "crashAsPass",
+ "default": False,
+ "help": "treat harness level crashes as passing (used for quarantine jobs).",
+ },
+ ],
+ [
+ ["--compare-preferences"],
+ {
+ "action": "store_true",
+ "dest": "comparePrefs",
+ "default": False,
+ "help": "Compare preferences at the end of each test and report changed ones as failures.",
+ },
+ ],
+ [
+ ["--restart-after-failure"],
+ {
+ "dest": "restartAfterFailure",
+ "default": False,
+ "help": "Terminate the session on first failure and restart where you left off.",
+ },
+ ],
+ ]
+
+ defaults = {
+ # Bug 1065098 - The gmplugin process fails to produce a leak
+ # log for some reason.
+ "ignoreMissingLeaks": ["gmplugin"],
+ "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."""
+
+ # and android doesn't use 'app' the same way, so skip validation
+ if parser.app != "android":
+ if options.app is None:
+ if build_obj:
+ from mozbuild.base import BinaryNotFoundException
+
+ try:
+ options.app = build_obj.get_binary_path()
+ except BinaryNotFoundException as e:
+ print("{}\n\n{}\n".format(e, e.help()))
+ sys.exit(1)
+ 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.flavor is None:
+ options.flavor = "plain"
+
+ for value in ALL_FLAVORS.values():
+ if options.flavor in value["aliases"]:
+ options.flavor = value["suite"]
+ break
+
+ 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.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.debugOnFailure and not options.jsdebugger:
+ parser.error("--debug-on-failure requires --jsdebugger.")
+
+ if options.jsdebuggerPath and not options.jsdebugger:
+ parser.error("--jsdebugger-path 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_startup_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:
+ # Try to guess the testing modules directory.
+ possible = [os.path.join(here, os.path.pardir, "modules")]
+ if build_obj:
+ possible.insert(
+ 0, os.path.join(build_obj.topobjdir, "_tests", "modules")
+ )
+
+ for p in possible:
+ if os.path.isdir(p):
+ options.testingModulesDir = p
+ break
+
+ # Paths to specialpowers and mochijar from the tests archive.
+ options.stagedAddons = [
+ os.path.join(here, "extensions", "specialpowers"),
+ os.path.join(here, "mochijar"),
+ ]
+ if build_obj:
+ objdir_xpi_stage = os.path.join(build_obj.distdir, "xpi-stage")
+ if os.path.isdir(objdir_xpi_stage):
+ options.stagedAddons = [
+ os.path.join(objdir_xpi_stage, "specialpowers"),
+ os.path.join(objdir_xpi_stage, "mochijar"),
+ ]
+ plugins_dir = os.path.join(build_obj.distdir, "plugins")
+ if (
+ os.path.isdir(plugins_dir)
+ and 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.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"
+ )
+
+ gst01 = spawn.find_executable("gst-launch-0.1")
+ gst010 = spawn.find_executable("gst-launch-0.10")
+ gst10 = spawn.find_executable("gst-launch-1.0")
+ pactl = spawn.find_executable("pactl")
+
+ if not (gst01 or gst10 or gst010):
+ parser.error(
+ "Missing gst-launch-{0.1,0.10,1.0}, required for "
+ "--use-test-media-devices"
+ )
+
+ if not pactl:
+ parser.error(
+ "Missing binary pactl required for " "--use-test-media-devices"
+ )
+
+ # The a11y and chrome flavors can't run with e10s.
+ if options.flavor in ("a11y", "chrome") and options.e10s:
+ parser.error(
+ "mochitest-{} does not support e10s, try again with "
+ "--disable-e10s.".format(options.flavor)
+ )
+
+ # If e10s explicitly disabled and no fission option specified, disable fission
+ if (not options.e10s) and (not options.disable_fission):
+ options.disable_fission = True
+
+ options.leakThresholds = {
+ "default": options.defaultLeakThreshold,
+ "tab": options.defaultLeakThreshold,
+ "forkserver": options.defaultLeakThreshold,
+ # GMP rarely gets a log, but when it does, it leaks a little.
+ "gmplugin": 20000,
+ }
+
+ # See the dependencies of bug 1401764.
+ if mozinfo.isWin:
+ options.leakThresholds["tab"] = 1000
+
+ # 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 = [
+ [
+ ["--no-install"],
+ {
+ "action": "store_true",
+ "default": False,
+ "help": "Skip the installation of the APK.",
+ },
+ ],
+ [
+ ["--aab"],
+ {
+ "action": "store_true",
+ "default": False,
+ "help": "Install the test_runner app using AAB.",
+ },
+ ],
+ [
+ ["--deviceSerial"],
+ {
+ "dest": "deviceSerial",
+ "help": "adb serial number of remote device. This is required "
+ "when more than one device is connected to the host. "
+ "Use 'adb devices' to see connected devices.",
+ "default": None,
+ },
+ ],
+ [
+ ["--adbpath"],
+ {
+ "dest": "adbPath",
+ "default": None,
+ "help": "Path to adb binary.",
+ "suppress": True,
+ },
+ ],
+ [
+ ["--remote-webserver"],
+ {
+ "dest": "remoteWebServer",
+ "default": None,
+ "help": "IP address of the remote web server.",
+ },
+ ],
+ [
+ ["--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,
+ },
+ ],
+ [
+ ["--remoteTestRoot"],
+ {
+ "dest": "remoteTestRoot",
+ "default": None,
+ "help": "Remote directory to use as test root "
+ "(eg. /data/local/tmp/test_root).",
+ "suppress": True,
+ },
+ ],
+ [
+ ["--enable-coverage"],
+ {
+ "action": "store_true",
+ "default": False,
+ "help": "Enable collecting code coverage information when running "
+ "junit tests.",
+ },
+ ],
+ [
+ ["--coverage-output-dir"],
+ {
+ "action": "store",
+ "default": None,
+ "help": "When using --enable-java-coverage, save the code coverage report "
+ "files to this directory.",
+ },
+ ],
+ ]
+
+ defaults = {
+ # 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 = "-"
+
+ objdir_xpi_stage = os.path.join(build_obj.distdir, "xpi-stage")
+ if os.path.isdir(objdir_xpi_stage):
+ options.extensionsToInstall = [
+ os.path.join(objdir_xpi_stage, "mochijar"),
+ os.path.join(objdir_xpi_stage, "specialpowers"),
+ ]
+
+ if options.remoteWebServer is None:
+ options.remoteWebServer = moznetwork.get_ip()
+
+ options.webServer = options.remoteWebServer
+
+ if options.app is None:
+ options.app = "org.mozilla.geckoview.test_runner"
+
+ 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 build_obj:
+ options.topsrcdir = build_obj.topsrcdir
+
+ if options.pidFile != "":
+ f = open(options.pidFile, "w")
+ f.write("%s" % os.getpid())
+ f.close()
+
+ if options.coverage_output_dir and not options.enable_coverage:
+ parser.error("--coverage-output-dir must be used with --enable-coverage")
+ if options.enable_coverage:
+ if not options.autorun:
+ parser.error("--enable-coverage cannot be used with --no-autorun")
+ if not options.coverage_output_dir:
+ parser.error(
+ "--coverage-output-dir must be specified when using --enable-coverage"
+ )
+ parent_dir = os.path.dirname(options.coverage_output_dir)
+ if not os.path.isdir(options.coverage_output_dir):
+ parser.error(
+ "The directory for the coverage output does not exist: %s"
+ % parent_dir
+ )
+
+ # allow us to keep original application around for cleanup while
+ # running tests
+ 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