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