mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 12:51:06 +00:00
094b8c6989
The `strtobool` implementation is copied from `mach.util`. Differential Revision: https://phabricator.services.mozilla.com/D211634
1483 lines
49 KiB
Python
1483 lines
49 KiB
Python
# 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 itertools import chain
|
|
from shutil import which
|
|
|
|
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 strtobool(value: str):
|
|
# Copied from `mach.util` since `mach.util` is not guaranteed to be available
|
|
# Reimplementation of distutils.util.strtobool
|
|
# https://docs.python.org/3.9/distutils/apiref.html#distutils.util.strtobool
|
|
true_vals = ("y", "yes", "t", "true", "on", "1")
|
|
false_vals = ("n", "no", "f", "false", "off", "0")
|
|
|
|
value = value.lower()
|
|
if value in true_vals:
|
|
return 1
|
|
if value in false_vals:
|
|
return 0
|
|
|
|
raise ValueError(f'Expected one of: {", ".join(true_vals + false_vals)}')
|
|
|
|
|
|
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.",
|
|
},
|
|
],
|
|
[
|
|
["--variant"],
|
|
{
|
|
"dest": "variant",
|
|
"default": "",
|
|
"help": "use specified variant for any harness level changes.",
|
|
},
|
|
],
|
|
]
|
|
|
|
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 = which("gst-launch-0.1")
|
|
gst010 = which("gst-launch-0.10")
|
|
gst10 = which("gst-launch-1.0")
|
|
pactl = which("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
|