forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			591 lines
		
	
	
	
		
			21 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			591 lines
		
	
	
	
		
			21 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 __future__ import (
 | 
						|
    absolute_import,
 | 
						|
    print_function,
 | 
						|
    unicode_literals,
 | 
						|
)
 | 
						|
 | 
						|
import argparse
 | 
						|
import json
 | 
						|
import os
 | 
						|
import re
 | 
						|
import shutil
 | 
						|
import subprocess
 | 
						|
import sys
 | 
						|
import tempfile
 | 
						|
 | 
						|
from collections import OrderedDict
 | 
						|
 | 
						|
from six import iteritems
 | 
						|
 | 
						|
from mach.decorators import (
 | 
						|
    Command,
 | 
						|
    CommandArgument,
 | 
						|
    CommandProvider,
 | 
						|
    SubCommand,
 | 
						|
)
 | 
						|
 | 
						|
from mozbuild.base import (
 | 
						|
    MachCommandBase,
 | 
						|
    MozbuildObject,
 | 
						|
    BinaryNotFoundException,
 | 
						|
)
 | 
						|
from mozbuild import nodeutil
 | 
						|
import mozlog
 | 
						|
import mozprofile
 | 
						|
 | 
						|
 | 
						|
EX_CONFIG = 78
 | 
						|
EX_SOFTWARE = 70
 | 
						|
EX_USAGE = 64
 | 
						|
 | 
						|
 | 
						|
def setup():
 | 
						|
    # add node and npm from mozbuild to front of system path
 | 
						|
    npm, _ = nodeutil.find_npm_executable()
 | 
						|
    if not npm:
 | 
						|
        exit(EX_CONFIG, "could not find npm executable")
 | 
						|
    path = os.path.abspath(os.path.join(npm, os.pardir))
 | 
						|
    os.environ["PATH"] = "{}:{}".format(path, os.environ["PATH"])
 | 
						|
 | 
						|
 | 
						|
@CommandProvider
 | 
						|
class RemoteCommands(MachCommandBase):
 | 
						|
    def __init__(self, context):
 | 
						|
        MachCommandBase.__init__(self, context)
 | 
						|
        self.remotedir = os.path.join(self.topsrcdir, "remote")
 | 
						|
 | 
						|
    @Command("remote", category="misc",
 | 
						|
             description="Remote protocol related operations.")
 | 
						|
    def remote(self):
 | 
						|
        """The remote subcommands all relate to the remote protocol."""
 | 
						|
        self._sub_mach(['help', 'remote'])
 | 
						|
        return 1
 | 
						|
 | 
						|
    @SubCommand("remote", "vendor-puppeteer",
 | 
						|
                "Pull in latest changes of the Puppeteer client.")
 | 
						|
    @CommandArgument("--repository",
 | 
						|
                     metavar="REPO",
 | 
						|
                     required=True,
 | 
						|
                     help="The (possibly remote) repository to clone from.")
 | 
						|
    @CommandArgument("--commitish",
 | 
						|
                     metavar="COMMITISH",
 | 
						|
                     required=True,
 | 
						|
                     help="The commit or tag object name to check out.")
 | 
						|
    def vendor_puppeteer(self, repository, commitish):
 | 
						|
        puppeteer_dir = os.path.join(self.remotedir, "test", "puppeteer")
 | 
						|
 | 
						|
        # Preserve our custom mocha reporter
 | 
						|
        shutil.move(os.path.join(puppeteer_dir, "json-mocha-reporter.js"), self.remotedir)
 | 
						|
        shutil.rmtree(puppeteer_dir, ignore_errors=True)
 | 
						|
        os.makedirs(puppeteer_dir)
 | 
						|
        with TemporaryDirectory() as tmpdir:
 | 
						|
            git("clone", "-q", repository, tmpdir)
 | 
						|
            git("checkout", commitish, worktree=tmpdir)
 | 
						|
            git("checkout-index", "-a", "-f",
 | 
						|
                "--prefix", "{}/".format(puppeteer_dir),
 | 
						|
                worktree=tmpdir)
 | 
						|
 | 
						|
        # remove files which may interfere with git checkout of central
 | 
						|
        try:
 | 
						|
            os.remove(os.path.join(puppeteer_dir, ".gitattributes"))
 | 
						|
            os.remove(os.path.join(puppeteer_dir, ".gitignore"))
 | 
						|
        except OSError:
 | 
						|
            pass
 | 
						|
 | 
						|
        experimental_dir = os.path.join(puppeteer_dir, "experimental")
 | 
						|
        if os.path.isdir(experimental_dir):
 | 
						|
            shutil.rmtree(experimental_dir)
 | 
						|
 | 
						|
        shutil.move(os.path.join(self.remotedir, "json-mocha-reporter.js"), puppeteer_dir)
 | 
						|
 | 
						|
        import yaml
 | 
						|
        annotation = {
 | 
						|
            "schema": 1,
 | 
						|
            "bugzilla": {
 | 
						|
                "product": "Remote Protocol",
 | 
						|
                "component": "Agent",
 | 
						|
            },
 | 
						|
            "origin": {
 | 
						|
                "name": "puppeteer",
 | 
						|
                "description": "Headless Chrome Node API",
 | 
						|
                "url": repository,
 | 
						|
                "license": "Apache-2.0",
 | 
						|
                "release": commitish,
 | 
						|
            },
 | 
						|
        }
 | 
						|
        with open(os.path.join(puppeteer_dir, "moz.yaml"), "w") as fh:
 | 
						|
            yaml.safe_dump(annotation, fh,
 | 
						|
                           default_flow_style=False,
 | 
						|
                           encoding="utf-8",
 | 
						|
                           allow_unicode=True)
 | 
						|
 | 
						|
 | 
						|
def git(*args, **kwargs):
 | 
						|
    cmd = ("git",)
 | 
						|
    if kwargs.get("worktree"):
 | 
						|
        cmd += ("-C", kwargs["worktree"])
 | 
						|
    cmd += args
 | 
						|
 | 
						|
    pipe = kwargs.get("pipe")
 | 
						|
    git_p = subprocess.Popen(cmd,
 | 
						|
                             env={"GIT_CONFIG_NOSYSTEM": "1"},
 | 
						|
                             stdout=subprocess.PIPE,
 | 
						|
                             stderr=subprocess.PIPE)
 | 
						|
    pipe_p = None
 | 
						|
    if pipe:
 | 
						|
        pipe_p = subprocess.Popen(pipe, stdin=git_p.stdout, stderr=subprocess.PIPE)
 | 
						|
 | 
						|
    if pipe:
 | 
						|
        _, pipe_err = pipe_p.communicate()
 | 
						|
    out, git_err = git_p.communicate()
 | 
						|
 | 
						|
    # use error from first program that failed
 | 
						|
    if git_p.returncode > 0:
 | 
						|
        exit(EX_SOFTWARE, git_err)
 | 
						|
    if pipe and pipe_p.returncode > 0:
 | 
						|
        exit(EX_SOFTWARE, pipe_err)
 | 
						|
 | 
						|
    return out
 | 
						|
 | 
						|
 | 
						|
def npm(*args, **kwargs):
 | 
						|
    from mozprocess import processhandler
 | 
						|
    env = None
 | 
						|
    if kwargs.get("env"):
 | 
						|
        env = os.environ.copy()
 | 
						|
        env.update(kwargs["env"])
 | 
						|
 | 
						|
    proc_kwargs = {}
 | 
						|
    if "processOutputLine" in kwargs:
 | 
						|
        proc_kwargs["processOutputLine"] = kwargs["processOutputLine"]
 | 
						|
 | 
						|
    p = processhandler.ProcessHandler(cmd="npm",
 | 
						|
                                      args=list(args),
 | 
						|
                                      cwd=kwargs.get("cwd"),
 | 
						|
                                      env=env,
 | 
						|
                                      universal_newlines=True,
 | 
						|
                                      **proc_kwargs)
 | 
						|
    if not kwargs.get("wait", True):
 | 
						|
        return p
 | 
						|
 | 
						|
    wait_proc(p, cmd="npm", exit_on_fail=kwargs.get("exit_on_fail", True))
 | 
						|
 | 
						|
    return p.returncode
 | 
						|
 | 
						|
 | 
						|
def wait_proc(p, cmd=None, exit_on_fail=True, output_timeout=None):
 | 
						|
    try:
 | 
						|
        p.run(outputTimeout=output_timeout)
 | 
						|
        p.wait()
 | 
						|
        if p.timedOut:
 | 
						|
            # In some cases, we wait longer for a mocha timeout
 | 
						|
            print("Timed out after {} seconds of no output".format(output_timeout))
 | 
						|
    finally:
 | 
						|
        p.kill()
 | 
						|
    if exit_on_fail and p.returncode > 0:
 | 
						|
        msg = ("%s: exit code %s" % (cmd, p.returncode) if cmd
 | 
						|
               else "exit code %s" % p.returncode)
 | 
						|
        exit(p.returncode, msg)
 | 
						|
 | 
						|
 | 
						|
class MochaOutputHandler(object):
 | 
						|
    def __init__(self, logger, expected):
 | 
						|
        self.hook_re = re.compile('"before\b?.*" hook|"after\b?.*" hook')
 | 
						|
 | 
						|
        self.logger = logger
 | 
						|
        self.proc = None
 | 
						|
        self.test_results = OrderedDict()
 | 
						|
        self.expected = expected
 | 
						|
        self.unexpected_skips = set()
 | 
						|
 | 
						|
        self.has_unexpected = False
 | 
						|
        self.logger.suite_start([], name="puppeteer-tests")
 | 
						|
        self.status_map = {
 | 
						|
            "CRASHED": "CRASH",
 | 
						|
            "OK": "PASS",
 | 
						|
            "TERMINATED": "CRASH",
 | 
						|
            "pass": "PASS",
 | 
						|
            "fail": "FAIL",
 | 
						|
            "pending": "SKIP"
 | 
						|
        }
 | 
						|
 | 
						|
    @property
 | 
						|
    def pid(self):
 | 
						|
        return self.proc and self.proc.pid
 | 
						|
 | 
						|
    def __call__(self, line):
 | 
						|
        event = None
 | 
						|
        try:
 | 
						|
            if line.startswith('[') and line.endswith(']'):
 | 
						|
                event = json.loads(line)
 | 
						|
            self.process_event(event)
 | 
						|
        except ValueError:
 | 
						|
            pass
 | 
						|
        finally:
 | 
						|
            self.logger.process_output(self.pid, line, command="npm")
 | 
						|
 | 
						|
    def process_event(self, event):
 | 
						|
        if isinstance(event, list) and len(event) > 1:
 | 
						|
            status = self.status_map.get(event[0])
 | 
						|
            test_start = event[0] == 'test-start'
 | 
						|
            if not status and not test_start:
 | 
						|
                return
 | 
						|
            test_info = event[1]
 | 
						|
            test_name = test_info.get("fullTitle", "")
 | 
						|
            test_path = test_info.get("file", "")
 | 
						|
            test_err = test_info.get("err")
 | 
						|
            if status == "FAIL" and test_err:
 | 
						|
                if "timeout" in test_err.lower():
 | 
						|
                    status = "TIMEOUT"
 | 
						|
            if test_name and test_path:
 | 
						|
                test_name = "{} ({})".format(test_name, os.path.basename(test_path))
 | 
						|
            # mocha hook failures are not tracked in metadata
 | 
						|
            if status != "PASS" and self.hook_re.search(test_name):
 | 
						|
                self.logger.error("TEST-UNEXPECTED-ERROR %s" % (test_name,))
 | 
						|
                return
 | 
						|
            if test_start:
 | 
						|
                self.logger.test_start(test_name)
 | 
						|
                return
 | 
						|
            expected = self.expected.get(test_name, ["PASS"])
 | 
						|
            # mozlog doesn't really allow unexpected skip,
 | 
						|
            # so if a test is disabled just expect that and note the unexpected skip
 | 
						|
            # Also, mocha doesn't log test-start for skipped tests
 | 
						|
            if status == "SKIP":
 | 
						|
                self.logger.test_start(test_name)
 | 
						|
                if self.expected and status not in expected:
 | 
						|
                    self.unexpected_skips.add(test_name)
 | 
						|
                expected = ["SKIP"]
 | 
						|
            known_intermittent = expected[1:]
 | 
						|
            expected_status = expected[0]
 | 
						|
 | 
						|
            self.test_results[test_name] = status
 | 
						|
            self.logger.test_end(test_name,
 | 
						|
                                 status=status,
 | 
						|
                                 expected=expected_status,
 | 
						|
                                 known_intermittent=known_intermittent)
 | 
						|
 | 
						|
            if status not in expected:
 | 
						|
                self.has_unexpected = True
 | 
						|
 | 
						|
    def new_expected(self):
 | 
						|
        new_expected = OrderedDict()
 | 
						|
        for test_name, status in iteritems(self.test_results):
 | 
						|
            if test_name not in self.expected:
 | 
						|
                new_status = [status]
 | 
						|
            else:
 | 
						|
                if status in self.expected[test_name]:
 | 
						|
                    new_status = self.expected[test_name]
 | 
						|
                else:
 | 
						|
                    new_status = [status]
 | 
						|
            new_expected[test_name] = new_status
 | 
						|
        return new_expected
 | 
						|
 | 
						|
    def after_end(self, subset=False):
 | 
						|
        if not subset:
 | 
						|
            missing = set(self.expected) - set(self.test_results)
 | 
						|
            extra = set(self.test_results) - set(self.expected)
 | 
						|
            if missing:
 | 
						|
                self.has_unexpected = True
 | 
						|
                for test_name in missing:
 | 
						|
                    self.logger.error("TEST-UNEXPECTED-MISSING %s" % (test_name,))
 | 
						|
            if self.expected and extra:
 | 
						|
                self.has_unexpected = True
 | 
						|
                for test_name in extra:
 | 
						|
                    self.logger.error("TEST-UNEXPECTED-MISSING Unknown new test %s" % (test_name,))
 | 
						|
 | 
						|
        if self.unexpected_skips:
 | 
						|
            self.has_unexpected = True
 | 
						|
            for test_name in self.unexpected_skips:
 | 
						|
                self.logger.error("TEST-UNEXPECTED-MISSING Unexpected skipped %s" % (test_name,))
 | 
						|
        self.logger.suite_end()
 | 
						|
 | 
						|
 | 
						|
# tempfile.TemporaryDirectory missing from Python 2.7
 | 
						|
class TemporaryDirectory(object):
 | 
						|
    def __init__(self):
 | 
						|
        self.path = tempfile.mkdtemp()
 | 
						|
        self._closed = False
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<{} {!r}>".format(self.__class__.__name__, self.path)
 | 
						|
 | 
						|
    def __enter__(self):
 | 
						|
        return self.path
 | 
						|
 | 
						|
    def __exit__(self, exc, value, tb):
 | 
						|
        self.clean()
 | 
						|
 | 
						|
    def __del__(self):
 | 
						|
        self.clean()
 | 
						|
 | 
						|
    def clean(self):
 | 
						|
        if self.path and not self._closed:
 | 
						|
            shutil.rmtree(self.path)
 | 
						|
            self._closed = True
 | 
						|
 | 
						|
 | 
						|
class PuppeteerRunner(MozbuildObject):
 | 
						|
    def __init__(self, *args, **kwargs):
 | 
						|
        super(PuppeteerRunner, self).__init__(*args, **kwargs)
 | 
						|
 | 
						|
        self.remotedir = os.path.join(self.topsrcdir, "remote")
 | 
						|
        self.puppeteer_dir = os.path.join(self.remotedir, "test", "puppeteer")
 | 
						|
 | 
						|
    def run_test(self, logger, *tests, **params):
 | 
						|
        """
 | 
						|
        Runs Puppeteer unit tests with npm.
 | 
						|
 | 
						|
        Possible optional test parameters:
 | 
						|
 | 
						|
        `binary`:
 | 
						|
          Path for the browser binary to use.  Defaults to the local
 | 
						|
          build.
 | 
						|
        `headless`:
 | 
						|
          Boolean to indicate whether to activate Firefox' headless mode.
 | 
						|
        `extra_prefs`:
 | 
						|
          Dictionary of extra preferences to write to the profile,
 | 
						|
          before invoking npm.  Overrides default preferences.
 | 
						|
        `write_results`:
 | 
						|
          Path to write the results json file
 | 
						|
        `subset`
 | 
						|
          Indicates only a subset of tests are being run, so we should
 | 
						|
          skip the check for missing results
 | 
						|
        """
 | 
						|
        setup()
 | 
						|
 | 
						|
        binary = params.get("binary") or self.get_binary_path()
 | 
						|
        product = params.get("product", "firefox")
 | 
						|
 | 
						|
        env = {
 | 
						|
            # Print browser process ouptut
 | 
						|
            "DUMPIO": "1",
 | 
						|
            # Checked by Puppeteer's custom mocha config
 | 
						|
            "CI": "1",
 | 
						|
            # Causes some tests to be skipped due to assumptions about install
 | 
						|
            "PUPPETEER_ALT_INSTALL": "1"
 | 
						|
        }
 | 
						|
        extra_options = {}
 | 
						|
        for k, v in params.get("extra_launcher_options", {}).items():
 | 
						|
            extra_options[k] = json.loads(v)
 | 
						|
 | 
						|
        # Override upstream defaults: no retries, shorter timeout
 | 
						|
        mocha_options = [
 | 
						|
            "--reporter", "./json-mocha-reporter.js",
 | 
						|
            "--retries", "0",
 | 
						|
            "--fullTrace",
 | 
						|
            "--timeout", "15000",
 | 
						|
            "--no-parallel",
 | 
						|
        ]
 | 
						|
        if product == "firefox":
 | 
						|
            env["BINARY"] = binary
 | 
						|
            env["PUPPETEER_PRODUCT"] = "firefox"
 | 
						|
        command = ["run", "unit", "--"] + mocha_options
 | 
						|
 | 
						|
        env["HEADLESS"] = str(params.get("headless", False))
 | 
						|
 | 
						|
        prefs = {}
 | 
						|
        for k, v in params.get("extra_prefs", {}).items():
 | 
						|
            prefs[k] = mozprofile.Preferences.cast(v)
 | 
						|
 | 
						|
        if prefs:
 | 
						|
            extra_options["extraPrefsFirefox"] = prefs
 | 
						|
 | 
						|
        if extra_options:
 | 
						|
            env["EXTRA_LAUNCH_OPTIONS"] = json.dumps(extra_options)
 | 
						|
 | 
						|
        expected_path = os.path.join(os.path.dirname(__file__),
 | 
						|
                                     "puppeteer-expected.json")
 | 
						|
        if product == "firefox" and os.path.exists(expected_path):
 | 
						|
            with open(expected_path) as f:
 | 
						|
                expected_data = json.load(f)
 | 
						|
        else:
 | 
						|
            expected_data = {}
 | 
						|
 | 
						|
        output_handler = MochaOutputHandler(logger, expected_data)
 | 
						|
        proc = npm(*command, cwd=self.puppeteer_dir, env=env,
 | 
						|
                   processOutputLine=output_handler, wait=False)
 | 
						|
        output_handler.proc = proc
 | 
						|
 | 
						|
        # Puppeteer unit tests don't always clean-up child processes in case of
 | 
						|
        # failure, so use an output_timeout as a fallback
 | 
						|
        wait_proc(proc, "npm", output_timeout=60, exit_on_fail=False)
 | 
						|
 | 
						|
        output_handler.after_end(params.get("subset", False))
 | 
						|
 | 
						|
        # Non-zero return codes are non-fatal for now since we have some
 | 
						|
        # issues with unresolved promises that shouldn't otherwise block
 | 
						|
        # running the tests
 | 
						|
        if proc.returncode != 0:
 | 
						|
            logger.warning("npm exited with code %s" % proc.returncode)
 | 
						|
 | 
						|
        if params["write_results"]:
 | 
						|
            with open(params["write_results"], "w") as f:
 | 
						|
                json.dump(output_handler.new_expected(), f, indent=2,
 | 
						|
                          separators=(",", ": "))
 | 
						|
 | 
						|
        if output_handler.has_unexpected:
 | 
						|
            exit(1, "Got unexpected results")
 | 
						|
 | 
						|
 | 
						|
def create_parser_puppeteer():
 | 
						|
    p = argparse.ArgumentParser()
 | 
						|
    p.add_argument("--product",
 | 
						|
                   type=str,
 | 
						|
                   default="firefox",
 | 
						|
                   choices=["chrome", "firefox"])
 | 
						|
    p.add_argument("--binary",
 | 
						|
                   type=str,
 | 
						|
                   help="Path to browser binary.  Defaults to local Firefox build.")
 | 
						|
    p.add_argument("--enable-fission",
 | 
						|
                   action="store_true",
 | 
						|
                   help="Enable Fission (site isolation) in Gecko.")
 | 
						|
    p.add_argument("-z", "--headless",
 | 
						|
                   action="store_true",
 | 
						|
                   help="Run browser in headless mode.")
 | 
						|
    p.add_argument("--setpref",
 | 
						|
                   action="append",
 | 
						|
                   dest="extra_prefs",
 | 
						|
                   metavar="<pref>=<value>",
 | 
						|
                   help="Defines additional user preferences.")
 | 
						|
    p.add_argument("--setopt",
 | 
						|
                   action="append",
 | 
						|
                   dest="extra_options",
 | 
						|
                   metavar="<option>=<value>",
 | 
						|
                   help="Defines additional options for `puppeteer.launch`.")
 | 
						|
    p.add_argument("-v",
 | 
						|
                   dest="verbosity",
 | 
						|
                   action="count",
 | 
						|
                   default=0,
 | 
						|
                   help="Increase remote agent logging verbosity to include "
 | 
						|
                        "debug level messages with -v, trace messages with -vv,"
 | 
						|
                        "and to not truncate long trace messages with -vvv")
 | 
						|
    p.add_argument("--write-results",
 | 
						|
                   action="store",
 | 
						|
                   nargs="?",
 | 
						|
                   default=None,
 | 
						|
                   const=os.path.join(os.path.dirname(__file__),
 | 
						|
                                      "puppeteer-expected.json"),
 | 
						|
                   help="Path to write updated results to (defaults to the "
 | 
						|
                        "expectations file if the argument is provided but "
 | 
						|
                        "no path is passed)")
 | 
						|
    p.add_argument("--subset",
 | 
						|
                   action="store_true",
 | 
						|
                   default=False,
 | 
						|
                   help="Indicate that only a subset of the tests are running, "
 | 
						|
                        "so checks for missing tests should be skipped")
 | 
						|
    p.add_argument("tests", nargs="*")
 | 
						|
    mozlog.commandline.add_logging_group(p)
 | 
						|
    return p
 | 
						|
 | 
						|
 | 
						|
@CommandProvider
 | 
						|
class PuppeteerTest(MachCommandBase):
 | 
						|
    @Command("puppeteer-test", category="testing",
 | 
						|
             description="Run Puppeteer unit tests.",
 | 
						|
             parser=create_parser_puppeteer)
 | 
						|
    def puppeteer_test(self, binary=None, enable_fission=False, headless=False,
 | 
						|
                       extra_prefs=None, extra_options=None, verbosity=0,
 | 
						|
                       tests=None, product="firefox", write_results=None,
 | 
						|
                       subset=False, **kwargs):
 | 
						|
 | 
						|
        logger = mozlog.commandline.setup_logging("puppeteer-test",
 | 
						|
                                                  kwargs,
 | 
						|
                                                  {"mach": sys.stdout})
 | 
						|
 | 
						|
        # moztest calls this programmatically with test objects or manifests
 | 
						|
        if "test_objects" in kwargs and tests is not None:
 | 
						|
            logger.error("Expected either 'test_objects' or 'tests'")
 | 
						|
            exit(1)
 | 
						|
 | 
						|
        if product != "firefox" and extra_prefs is not None:
 | 
						|
            logger.error("User preferences are not recognized by %s" % product)
 | 
						|
            exit(1)
 | 
						|
 | 
						|
        if "test_objects" in kwargs:
 | 
						|
            tests = []
 | 
						|
            for test in kwargs["test_objects"]:
 | 
						|
                tests.append(test["path"])
 | 
						|
 | 
						|
        prefs = {}
 | 
						|
        for s in (extra_prefs or []):
 | 
						|
            kv = s.split("=")
 | 
						|
            if len(kv) != 2:
 | 
						|
                logger.error("syntax error in --setpref={}".format(s))
 | 
						|
                exit(EX_USAGE)
 | 
						|
            prefs[kv[0]] = kv[1].strip()
 | 
						|
 | 
						|
        options = {}
 | 
						|
        for s in (extra_options or []):
 | 
						|
            kv = s.split("=")
 | 
						|
            if len(kv) != 2:
 | 
						|
                logger.error("syntax error in --setopt={}".format(s))
 | 
						|
                exit(EX_USAGE)
 | 
						|
            options[kv[0]] = kv[1].strip()
 | 
						|
 | 
						|
        if enable_fission:
 | 
						|
            prefs.update({"fission.autostart": True,
 | 
						|
                          "dom.serviceWorkers.parent_intercept": True,
 | 
						|
                          "browser.tabs.documentchannel": True})
 | 
						|
 | 
						|
        if verbosity == 1:
 | 
						|
            prefs["remote.log.level"] = "Debug"
 | 
						|
        elif verbosity > 1:
 | 
						|
            prefs["remote.log.level"] = "Trace"
 | 
						|
        if verbosity > 2:
 | 
						|
            prefs["remote.log.truncate"] = False
 | 
						|
 | 
						|
        self.install_puppeteer(product)
 | 
						|
 | 
						|
        params = {"binary": binary,
 | 
						|
                  "headless": headless,
 | 
						|
                  "extra_prefs": prefs,
 | 
						|
                  "product": product,
 | 
						|
                  "extra_launcher_options": options,
 | 
						|
                  "write_results": write_results,
 | 
						|
                  "subset": subset}
 | 
						|
        puppeteer = self._spawn(PuppeteerRunner)
 | 
						|
        try:
 | 
						|
            return puppeteer.run_test(logger, *tests, **params)
 | 
						|
        except BinaryNotFoundException as e:
 | 
						|
            logger.error(e)
 | 
						|
            logger.info(e.help())
 | 
						|
            exit(1)
 | 
						|
        except Exception as e:
 | 
						|
            exit(EX_SOFTWARE, e)
 | 
						|
 | 
						|
    def install_puppeteer(self, product):
 | 
						|
        setup()
 | 
						|
        env = {}
 | 
						|
        from mozversioncontrol import get_repository_object
 | 
						|
        repo = get_repository_object(self.topsrcdir)
 | 
						|
        puppeteer_dir = os.path.join("remote", "test", "puppeteer")
 | 
						|
        changed_files = False
 | 
						|
        for f in repo.get_changed_files():
 | 
						|
            if f.startswith(puppeteer_dir) and f.endswith(".ts"):
 | 
						|
                changed_files = True
 | 
						|
                break
 | 
						|
 | 
						|
        if product != "chrome":
 | 
						|
            env["PUPPETEER_SKIP_DOWNLOAD"] = "1"
 | 
						|
        lib_dir = os.path.join(self.topsrcdir, puppeteer_dir, "lib")
 | 
						|
        if changed_files and os.path.isdir(lib_dir):
 | 
						|
            # clobber lib to force `tsc compile` step
 | 
						|
            shutil.rmtree(lib_dir)
 | 
						|
        npm("install",
 | 
						|
            cwd=os.path.join(self.topsrcdir, puppeteer_dir),
 | 
						|
            env=env)
 | 
						|
 | 
						|
 | 
						|
def exit(code, error=None):
 | 
						|
    if error is not None:
 | 
						|
        if isinstance(error, Exception):
 | 
						|
            import traceback
 | 
						|
            traceback.print_exc()
 | 
						|
        else:
 | 
						|
            message = str(error).split("\n")[0].strip()
 | 
						|
            print("{}: {}".format(sys.argv[0], message), file=sys.stderr)
 | 
						|
    sys.exit(code)
 |