зеркало из https://github.com/mozilla/gecko-dev.git
1051 строка
40 KiB
Python
1051 строка
40 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/.
|
|
|
|
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,
|
|
}],
|
|
[["--cleanup-crashes"],
|
|
{"action": "store_true",
|
|
"dest": "cleanupCrashes",
|
|
"default": False,
|
|
"help": "Delete pending crash reports before running tests.",
|
|
"suppress": True,
|
|
}],
|
|
]
|
|
|
|
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
|