mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-18 15:55:36 +00:00
Bug 1513880 [wpt PR 14496] - Revert "Make testharness tests run in a top-level browsing context": Remove .orig files from conflicts, a=testonly DONTBUILD
This commit is contained in:
parent
a4eeeccf1d
commit
90d6431728
@ -1,206 +0,0 @@
|
||||
import os
|
||||
import platform
|
||||
import socket
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from copy import deepcopy
|
||||
|
||||
from ..wptcommandline import require_arg # noqa: F401
|
||||
|
||||
here = os.path.split(__file__)[0]
|
||||
|
||||
|
||||
def inherit(super_module, child_globals, product_name):
|
||||
super_wptrunner = super_module.__wptrunner__
|
||||
child_globals["__wptrunner__"] = child_wptrunner = deepcopy(super_wptrunner)
|
||||
|
||||
child_wptrunner["product"] = product_name
|
||||
|
||||
for k in ("check_args", "browser", "browser_kwargs", "executor_kwargs",
|
||||
"env_extras", "env_options"):
|
||||
attr = super_wptrunner[k]
|
||||
child_globals[attr] = getattr(super_module, attr)
|
||||
|
||||
for v in super_module.__wptrunner__["executor"].values():
|
||||
child_globals[v] = getattr(super_module, v)
|
||||
|
||||
if "run_info_extras" in super_wptrunner:
|
||||
attr = super_wptrunner["run_info_extras"]
|
||||
child_globals[attr] = getattr(super_module, attr)
|
||||
|
||||
|
||||
def cmd_arg(name, value=None):
|
||||
prefix = "-" if platform.system() == "Windows" else "--"
|
||||
rv = prefix + name
|
||||
|
||||
|
||||
def inherit(super_module, child_globals, product_name):
|
||||
super_wptrunner = super_module.__wptrunner__
|
||||
child_globals["__wptrunner__"] = child_wptrunner = deepcopy(super_wptrunner)
|
||||
|
||||
child_wptrunner["product"] = product_name
|
||||
|
||||
for k in ("check_args", "browser", "browser_kwargs", "executor_kwargs",
|
||||
"env_extras", "env_options"):
|
||||
attr = super_wptrunner[k]
|
||||
child_globals[attr] = getattr(super_module, attr)
|
||||
|
||||
for v in super_module.__wptrunner__["executor"].values():
|
||||
child_globals[v] = getattr(super_module, v)
|
||||
|
||||
if "run_info_extras" in super_wptrunner:
|
||||
attr = super_wptrunner["run_info_extras"]
|
||||
child_globals[attr] = getattr(super_module, attr)
|
||||
|
||||
|
||||
def cmd_arg(name, value=None):
|
||||
prefix = "-" if platform.system() == "Windows" else "--"
|
||||
rv = prefix + name
|
||||
if value is not None:
|
||||
rv += "=" + value
|
||||
return rv
|
||||
|
||||
|
||||
def get_free_port(start_port, exclude=None):
|
||||
"""Get the first port number after start_port (inclusive) that is
|
||||
not currently bound.
|
||||
|
||||
:param start_port: Integer port number at which to start testing.
|
||||
:param exclude: Set of port numbers to skip"""
|
||||
port = start_port
|
||||
while True:
|
||||
if exclude and port in exclude:
|
||||
port += 1
|
||||
continue
|
||||
s = socket.socket()
|
||||
try:
|
||||
s.bind(("127.0.0.1", port))
|
||||
except socket.error:
|
||||
port += 1
|
||||
else:
|
||||
return port
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
def browser_command(binary, args, debug_info):
|
||||
if debug_info:
|
||||
if debug_info.requiresEscapedArgs:
|
||||
args = [item.replace("&", "\\&") for item in args]
|
||||
debug_args = [debug_info.path] + debug_info.args
|
||||
else:
|
||||
debug_args = []
|
||||
|
||||
command = [binary] + args
|
||||
|
||||
return debug_args, command
|
||||
|
||||
|
||||
class BrowserError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Browser(object):
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
process_cls = None
|
||||
init_timeout = 30
|
||||
|
||||
def __init__(self, logger):
|
||||
"""Abstract class serving as the basis for Browser implementations.
|
||||
|
||||
The Browser is used in the TestRunnerManager to start and stop the browser
|
||||
process, and to check the state of that process. This class also acts as a
|
||||
context manager, enabling it to do browser-specific setup at the start of
|
||||
the testrun and cleanup after the run is complete.
|
||||
|
||||
:param logger: Structured logger to use for output.
|
||||
"""
|
||||
self.logger = logger
|
||||
|
||||
def __enter__(self):
|
||||
self.setup()
|
||||
return self
|
||||
|
||||
def __exit__(self, *args, **kwargs):
|
||||
self.cleanup()
|
||||
|
||||
def setup(self):
|
||||
"""Used for browser-specific setup that happens at the start of a test run"""
|
||||
pass
|
||||
|
||||
def settings(self, test):
|
||||
return {}
|
||||
|
||||
@abstractmethod
|
||||
def start(self, group_metadata, **kwargs):
|
||||
"""Launch the browser object and get it into a state where is is ready to run tests"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def stop(self, force=False):
|
||||
"""Stop the running browser process."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def pid(self):
|
||||
"""pid of the browser process or None if there is no pid"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def is_alive(self):
|
||||
"""Boolean indicating whether the browser process is still running"""
|
||||
pass
|
||||
|
||||
def setup_ssl(self, hosts):
|
||||
"""Return a certificate to use for tests requiring ssl that will be trusted by the browser"""
|
||||
raise NotImplementedError("ssl testing not supported")
|
||||
|
||||
def cleanup(self):
|
||||
"""Browser-specific cleanup that is run after the testrun is finished"""
|
||||
pass
|
||||
|
||||
def executor_browser(self):
|
||||
"""Returns the ExecutorBrowser subclass for this Browser subclass and the keyword arguments
|
||||
with which it should be instantiated"""
|
||||
return ExecutorBrowser, {}
|
||||
|
||||
def check_crash(self, process, test):
|
||||
"""Check if a crash occured and output any useful information to the
|
||||
log. Returns a boolean indicating whether a crash occured."""
|
||||
return False
|
||||
|
||||
|
||||
class NullBrowser(Browser):
|
||||
def __init__(self, logger, **kwargs):
|
||||
super(NullBrowser, self).__init__(logger)
|
||||
|
||||
def start(self, **kwargs):
|
||||
"""No-op browser to use in scenarios where the TestRunnerManager shouldn't
|
||||
actually own the browser process (e.g. Servo where we start one browser
|
||||
per test)"""
|
||||
pass
|
||||
|
||||
def stop(self, force=False):
|
||||
pass
|
||||
|
||||
def pid(self):
|
||||
return None
|
||||
|
||||
def is_alive(self):
|
||||
return True
|
||||
|
||||
def on_output(self, line):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class ExecutorBrowser(object):
|
||||
def __init__(self, **kwargs):
|
||||
"""View of the Browser used by the Executor object.
|
||||
This is needed because the Executor runs in a child process and
|
||||
we can't ship Browser instances between processes on Windows.
|
||||
|
||||
Typically this will have a few product-specific properties set,
|
||||
but in some cases it may have more elaborate methods for setting
|
||||
up the browser from the runner process.
|
||||
"""
|
||||
for k, v in kwargs.iteritems():
|
||||
setattr(self, k, v)
|
@ -1,217 +0,0 @@
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import moznetwork
|
||||
from mozprocess import ProcessHandler
|
||||
from mozprofile import FirefoxProfile
|
||||
from mozrunner import FennecEmulatorRunner
|
||||
|
||||
from tools.serve.serve import make_hosts_file
|
||||
|
||||
from .base import (get_free_port,
|
||||
cmd_arg,
|
||||
browser_command)
|
||||
from ..executors.executormarionette import (MarionetteTestharnessExecutor, # noqa: F401
|
||||
MarionetteRefTestExecutor) # noqa: F401
|
||||
from .firefox import (get_timeout_multiplier,
|
||||
update_properties,
|
||||
executor_kwargs,
|
||||
FirefoxBrowser)
|
||||
|
||||
|
||||
__wptrunner__ = {"product": "fennec",
|
||||
"check_args": "check_args",
|
||||
"browser": "FennecBrowser",
|
||||
"executor": {"testharness": "MarionetteTestharnessExecutor",
|
||||
"reftest": "MarionetteRefTestExecutor"},
|
||||
"browser_kwargs": "browser_kwargs",
|
||||
"executor_kwargs": "executor_kwargs",
|
||||
"env_extras": "env_extras",
|
||||
"env_options": "env_options",
|
||||
"run_info_extras": "run_info_extras",
|
||||
"update_properties": "update_properties"}
|
||||
|
||||
|
||||
def check_args(**kwargs):
|
||||
pass
|
||||
|
||||
|
||||
def browser_kwargs(test_type, run_info_data, config, **kwargs):
|
||||
return {"package_name": kwargs["package_name"],
|
||||
"device_serial": kwargs["device_serial"],
|
||||
"prefs_root": kwargs["prefs_root"],
|
||||
"extra_prefs": kwargs["extra_prefs"],
|
||||
"test_type": test_type,
|
||||
"debug_info": kwargs["debug_info"],
|
||||
"symbols_path": kwargs["symbols_path"],
|
||||
"stackwalk_binary": kwargs["stackwalk_binary"],
|
||||
"certutil_binary": kwargs["certutil_binary"],
|
||||
"ca_certificate_path": config.ssl_config["ca_cert_path"],
|
||||
"stackfix_dir": kwargs["stackfix_dir"],
|
||||
"binary_args": kwargs["binary_args"],
|
||||
"timeout_multiplier": get_timeout_multiplier(test_type,
|
||||
run_info_data,
|
||||
**kwargs),
|
||||
"leak_check": kwargs["leak_check"],
|
||||
"stylo_threads": kwargs["stylo_threads"],
|
||||
"chaos_mode_flags": kwargs["chaos_mode_flags"],
|
||||
"config": config,
|
||||
"install_fonts": kwargs["install_fonts"],
|
||||
"tests_root": config.doc_root}
|
||||
|
||||
|
||||
def env_extras(**kwargs):
|
||||
return []
|
||||
|
||||
|
||||
def run_info_extras(**kwargs):
|
||||
return {"e10s": False,
|
||||
"headless": False,
|
||||
"sw-e10s": False}
|
||||
|
||||
|
||||
def env_options():
|
||||
# The server host is set to public localhost IP so that resources can be accessed
|
||||
# from Android emulator
|
||||
return {"server_host": moznetwork.get_ip(),
|
||||
"bind_address": False,
|
||||
"supports_debugger": True}
|
||||
|
||||
|
||||
def write_hosts_file(config, device):
|
||||
new_hosts = make_hosts_file(config, moznetwork.get_ip())
|
||||
current_hosts = device.get_file("/etc/hosts")
|
||||
if new_hosts == current_hosts:
|
||||
return
|
||||
hosts_fd, hosts_path = tempfile.mkstemp()
|
||||
try:
|
||||
with os.fdopen(hosts_fd, "w") as f:
|
||||
f.write(new_hosts)
|
||||
device.remount()
|
||||
device.push(hosts_path, "/etc/hosts")
|
||||
finally:
|
||||
os.remove(hosts_path)
|
||||
|
||||
|
||||
class FennecBrowser(FirefoxBrowser):
|
||||
used_ports = set()
|
||||
init_timeout = 300
|
||||
shutdown_timeout = 60
|
||||
|
||||
def __init__(self, logger, prefs_root, test_type, package_name=None,
|
||||
device_serial="emulator-5444", **kwargs):
|
||||
FirefoxBrowser.__init__(self, logger, None, prefs_root, test_type, **kwargs)
|
||||
self._package_name = package_name
|
||||
self.device_serial = device_serial
|
||||
self.tests_root = kwargs["tests_root"]
|
||||
self.install_fonts = kwargs["install_fonts"]
|
||||
|
||||
@property
|
||||
def package_name(self):
|
||||
"""
|
||||
Name of app to run on emulator.
|
||||
"""
|
||||
if self._package_name is None:
|
||||
self._package_name = "org.mozilla.fennec"
|
||||
user = os.getenv("USER")
|
||||
if user:
|
||||
self._package_name += "_" + user
|
||||
return self._package_name
|
||||
|
||||
def start(self, **kwargs):
|
||||
if self.marionette_port is None:
|
||||
self.marionette_port = get_free_port(2828, exclude=self.used_ports)
|
||||
self.used_ports.add(self.marionette_port)
|
||||
|
||||
env = {}
|
||||
env["MOZ_CRASHREPORTER"] = "1"
|
||||
env["MOZ_CRASHREPORTER_SHUTDOWN"] = "1"
|
||||
env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1"
|
||||
env["STYLO_THREADS"] = str(self.stylo_threads)
|
||||
if self.chaos_mode_flags is not None:
|
||||
env["MOZ_CHAOSMODE"] = str(self.chaos_mode_flags)
|
||||
|
||||
preferences = self.load_prefs()
|
||||
|
||||
self.profile = FirefoxProfile(preferences=preferences)
|
||||
self.profile.set_preferences({"marionette.port": self.marionette_port,
|
||||
"dom.disable_open_during_load": False,
|
||||
"places.history.enabled": False,
|
||||
"dom.send_after_paint_to_content": True,
|
||||
"network.preload": True})
|
||||
if self.test_type == "reftest":
|
||||
self.logger.info("Setting android reftest preferences")
|
||||
self.profile.set_preferences({"browser.viewport.desktopWidth": 600,
|
||||
# Disable high DPI
|
||||
"layout.css.devPixelsPerPx": "1.0",
|
||||
# Ensure that the full browser element
|
||||
# appears in the screenshot
|
||||
"apz.allow_zooming": False,
|
||||
"android.widget_paints_background": False,
|
||||
# Ensure that scrollbars are always painted
|
||||
"ui.scrollbarFadeBeginDelay": 100000})
|
||||
|
||||
if self.install_fonts:
|
||||
self.logger.debug("Copying Ahem font to profile")
|
||||
font_dir = os.path.join(self.profile.profile, "fonts")
|
||||
if not os.path.exists(font_dir):
|
||||
os.makedirs(font_dir)
|
||||
with open(os.path.join(self.tests_root, "fonts", "Ahem.ttf"), "rb") as src:
|
||||
with open(os.path.join(font_dir, "Ahem.ttf"), "wb") as dest:
|
||||
dest.write(src.read())
|
||||
|
||||
if self.leak_check and kwargs.get("check_leaks", True):
|
||||
self.leak_report_file = os.path.join(self.profile.profile, "runtests_leaks.log")
|
||||
if os.path.exists(self.leak_report_file):
|
||||
os.remove(self.leak_report_file)
|
||||
env["XPCOM_MEM_BLOAT_LOG"] = self.leak_report_file
|
||||
else:
|
||||
self.leak_report_file = None
|
||||
|
||||
if self.ca_certificate_path is not None:
|
||||
self.setup_ssl()
|
||||
|
||||
debug_args, cmd = browser_command(self.package_name,
|
||||
self.binary_args if self.binary_args else [] +
|
||||
[cmd_arg("marionette"), "about:blank"],
|
||||
self.debug_info)
|
||||
|
||||
self.runner = FennecEmulatorRunner(app=self.package_name,
|
||||
profile=self.profile,
|
||||
cmdargs=cmd[1:],
|
||||
env=env,
|
||||
symbols_path=self.symbols_path,
|
||||
serial=self.device_serial,
|
||||
# TODO - choose appropriate log dir
|
||||
logdir=os.getcwd(),
|
||||
process_class=ProcessHandler,
|
||||
process_args={"processOutputLine": [self.on_output]})
|
||||
|
||||
self.logger.debug("Starting %s" % self.package_name)
|
||||
# connect to a running emulator
|
||||
self.runner.device.connect()
|
||||
|
||||
write_hosts_file(self.config, self.runner.device.device)
|
||||
|
||||
self.runner.stop()
|
||||
self.runner.start(debug_args=debug_args, interactive=self.debug_info and self.debug_info.interactive)
|
||||
|
||||
self.runner.device.device.forward(
|
||||
local="tcp:{}".format(self.marionette_port),
|
||||
remote="tcp:{}".format(self.marionette_port))
|
||||
|
||||
self.logger.debug("%s Started" % self.package_name)
|
||||
|
||||
def stop(self, force=False):
|
||||
if self.runner is not None:
|
||||
if (self.runner.device.connected and
|
||||
len(self.runner.device.device.list_forwards()) > 0):
|
||||
try:
|
||||
self.runner.device.device.remove_forwards(
|
||||
"tcp:{}".format(self.marionette_port))
|
||||
except Exception:
|
||||
self.logger.warning("Failed to remove port forwarding")
|
||||
# We assume that stopping the runner prompts the
|
||||
# browser to shut down. This allows the leak log to be written
|
||||
self.runner.stop()
|
||||
self.logger.debug("stopped")
|
@ -1,252 +0,0 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
import glob
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tarfile
|
||||
import tempfile
|
||||
import time
|
||||
from cStringIO import StringIO as CStringIO
|
||||
|
||||
import requests
|
||||
|
||||
from .base import Browser, ExecutorBrowser, require_arg
|
||||
from ..executors import executor_kwargs as base_executor_kwargs
|
||||
from ..executors.executorselenium import (SeleniumTestharnessExecutor,
|
||||
SeleniumRefTestExecutor)
|
||||
|
||||
here = os.path.split(__file__)[0]
|
||||
# Number of seconds to wait between polling operations when detecting status of
|
||||
# Sauce Connect sub-process.
|
||||
sc_poll_period = 1
|
||||
|
||||
|
||||
__wptrunner__ = {"product": "sauce",
|
||||
"check_args": "check_args",
|
||||
"browser": "SauceBrowser",
|
||||
"executor": {"testharness": "SeleniumTestharnessExecutor",
|
||||
"reftest": "SeleniumRefTestExecutor"},
|
||||
"browser_kwargs": "browser_kwargs",
|
||||
"executor_kwargs": "executor_kwargs",
|
||||
"env_extras": "env_extras",
|
||||
"env_options": "env_options"}
|
||||
|
||||
|
||||
def get_capabilities(**kwargs):
|
||||
browser_name = kwargs["sauce_browser"]
|
||||
platform = kwargs["sauce_platform"]
|
||||
version = kwargs["sauce_version"]
|
||||
build = kwargs["sauce_build"]
|
||||
tags = kwargs["sauce_tags"]
|
||||
tunnel_id = kwargs["sauce_tunnel_id"]
|
||||
prerun_script = {
|
||||
"MicrosoftEdge": {
|
||||
"executable": "sauce-storage:edge-prerun.bat",
|
||||
"background": False,
|
||||
},
|
||||
"safari": {
|
||||
"executable": "sauce-storage:safari-prerun.sh",
|
||||
"background": False,
|
||||
}
|
||||
}
|
||||
capabilities = {
|
||||
"browserName": browser_name,
|
||||
"build": build,
|
||||
"disablePopupHandler": True,
|
||||
"name": "%s %s on %s" % (browser_name, version, platform),
|
||||
"platform": platform,
|
||||
"public": "public",
|
||||
"selenium-version": "3.3.1",
|
||||
"tags": tags,
|
||||
"tunnel-identifier": tunnel_id,
|
||||
"version": version,
|
||||
"prerun": prerun_script.get(browser_name)
|
||||
}
|
||||
|
||||
if browser_name == 'MicrosoftEdge':
|
||||
capabilities['selenium-version'] = '2.4.8'
|
||||
|
||||
return capabilities
|
||||
|
||||
|
||||
def get_sauce_config(**kwargs):
|
||||
browser_name = kwargs["sauce_browser"]
|
||||
sauce_user = kwargs["sauce_user"]
|
||||
sauce_key = kwargs["sauce_key"]
|
||||
|
||||
hub_url = "%s:%s@localhost:4445" % (sauce_user, sauce_key)
|
||||
data = {
|
||||
"url": "http://%s/wd/hub" % hub_url,
|
||||
"browserName": browser_name,
|
||||
"capabilities": get_capabilities(**kwargs)
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def check_args(**kwargs):
|
||||
require_arg(kwargs, "sauce_browser")
|
||||
require_arg(kwargs, "sauce_platform")
|
||||
require_arg(kwargs, "sauce_version")
|
||||
require_arg(kwargs, "sauce_user")
|
||||
require_arg(kwargs, "sauce_key")
|
||||
|
||||
|
||||
def browser_kwargs(test_type, run_info_data, config, **kwargs):
|
||||
sauce_config = get_sauce_config(**kwargs)
|
||||
|
||||
return {"sauce_config": sauce_config}
|
||||
|
||||
|
||||
def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
|
||||
**kwargs):
|
||||
executor_kwargs = base_executor_kwargs(test_type, server_config,
|
||||
cache_manager, run_info_data, **kwargs)
|
||||
|
||||
executor_kwargs["capabilities"] = get_capabilities(**kwargs)
|
||||
|
||||
return executor_kwargs
|
||||
|
||||
|
||||
def env_extras(**kwargs):
|
||||
return [SauceConnect(**kwargs)]
|
||||
|
||||
|
||||
def env_options():
|
||||
return {"supports_debugger": False}
|
||||
|
||||
|
||||
def get_tar(url, dest):
|
||||
resp = requests.get(url, stream=True)
|
||||
resp.raise_for_status()
|
||||
with tarfile.open(fileobj=CStringIO(resp.raw.read())) as f:
|
||||
f.extractall(path=dest)
|
||||
|
||||
|
||||
class SauceConnect():
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.sauce_user = kwargs["sauce_user"]
|
||||
self.sauce_key = kwargs["sauce_key"]
|
||||
self.sauce_tunnel_id = kwargs["sauce_tunnel_id"]
|
||||
self.sauce_connect_binary = kwargs.get("sauce_connect_binary")
|
||||
self.sauce_connect_args = kwargs.get("sauce_connect_args")
|
||||
self.sauce_init_timeout = kwargs.get("sauce_init_timeout")
|
||||
self.sc_process = None
|
||||
self.temp_dir = None
|
||||
self.env_config = None
|
||||
|
||||
def __call__(self, env_options, env_config):
|
||||
self.env_config = env_config
|
||||
|
||||
return self
|
||||
|
||||
def __enter__(self):
|
||||
# Because this class implements the context manager protocol, it is
|
||||
# possible for instances to be provided to the `with` statement
|
||||
# directly. This class implements the callable protocol so that data
|
||||
# which is not available during object initialization can be provided
|
||||
# prior to this moment. Instances must be invoked in preparation for
|
||||
# the context manager protocol, but this additional constraint is not
|
||||
# itself part of the protocol.
|
||||
assert self.env_config is not None, 'The instance has been invoked.'
|
||||
|
||||
if not self.sauce_connect_binary:
|
||||
self.temp_dir = tempfile.mkdtemp()
|
||||
get_tar("https://saucelabs.com/downloads/sc-4.4.9-linux.tar.gz", self.temp_dir)
|
||||
self.sauce_connect_binary = glob.glob(os.path.join(self.temp_dir, "sc-*-linux/bin/sc"))[0]
|
||||
|
||||
self.upload_prerun_exec('edge-prerun.bat')
|
||||
self.upload_prerun_exec('safari-prerun.sh')
|
||||
|
||||
self.sc_process = subprocess.Popen([
|
||||
self.sauce_connect_binary,
|
||||
"--user=%s" % self.sauce_user,
|
||||
"--api-key=%s" % self.sauce_key,
|
||||
"--no-remove-colliding-tunnels",
|
||||
"--tunnel-identifier=%s" % self.sauce_tunnel_id,
|
||||
"--metrics-address=0.0.0.0:9876",
|
||||
"--readyfile=./sauce_is_ready",
|
||||
"--tunnel-domains",
|
||||
",".join(self.env_config.domains_set)
|
||||
] + self.sauce_connect_args)
|
||||
|
||||
tot_wait = 0
|
||||
while not os.path.exists('./sauce_is_ready') and self.sc_process.poll() is None:
|
||||
if tot_wait >= self.sauce_init_timeout:
|
||||
self.quit()
|
||||
|
||||
raise SauceException("Sauce Connect Proxy was not ready after %d seconds" % tot_wait)
|
||||
|
||||
time.sleep(sc_poll_period)
|
||||
tot_wait += sc_poll_period
|
||||
|
||||
if self.sc_process.returncode is not None:
|
||||
raise SauceException("Unable to start Sauce Connect Proxy. Process exited with code %s", self.sc_process.returncode)
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.env_config = None
|
||||
self.quit()
|
||||
if self.temp_dir and os.path.exists(self.temp_dir):
|
||||
try:
|
||||
shutil.rmtree(self.temp_dir)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def upload_prerun_exec(self, file_name):
|
||||
auth = (self.sauce_user, self.sauce_key)
|
||||
url = "https://saucelabs.com/rest/v1/storage/%s/%s?overwrite=true" % (self.sauce_user, file_name)
|
||||
|
||||
with open(os.path.join(here, 'sauce_setup', file_name), 'rb') as f:
|
||||
requests.post(url, data=f, auth=auth)
|
||||
|
||||
def quit(self):
|
||||
"""The Sauce Connect process may be managing an active "tunnel" to the
|
||||
Sauce Labs service. Issue a request to the process to close any tunnels
|
||||
and exit. If this does not occur within 5 seconds, force the process to
|
||||
close."""
|
||||
kill_wait = 5
|
||||
tot_wait = 0
|
||||
self.sc_process.terminate()
|
||||
|
||||
while self.sc_process.poll() is None:
|
||||
time.sleep(sc_poll_period)
|
||||
tot_wait += sc_poll_period
|
||||
|
||||
if tot_wait >= kill_wait:
|
||||
self.sc_process.kill()
|
||||
break
|
||||
|
||||
|
||||
class SauceException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class SauceBrowser(Browser):
|
||||
init_timeout = 300
|
||||
|
||||
def __init__(self, logger, sauce_config):
|
||||
Browser.__init__(self, logger)
|
||||
self.sauce_config = sauce_config
|
||||
|
||||
def start(self, **kwargs):
|
||||
pass
|
||||
|
||||
def stop(self, force=False):
|
||||
pass
|
||||
|
||||
def pid(self):
|
||||
return None
|
||||
|
||||
def is_alive(self):
|
||||
# TODO: Should this check something about the connection?
|
||||
return True
|
||||
|
||||
def cleanup(self):
|
||||
pass
|
||||
|
||||
def executor_browser(self):
|
||||
return ExecutorBrowser, {"webdriver_url": self.sauce_config["url"]}
|
@ -1,882 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import threading
|
||||
import traceback
|
||||
import urlparse
|
||||
import uuid
|
||||
|
||||
errors = None
|
||||
marionette = None
|
||||
pytestrunner = None
|
||||
|
||||
here = os.path.join(os.path.split(__file__)[0])
|
||||
|
||||
from .base import (CallbackHandler,
|
||||
RefTestExecutor,
|
||||
RefTestImplementation,
|
||||
TestharnessExecutor,
|
||||
WdspecExecutor,
|
||||
WebDriverProtocol,
|
||||
extra_timeout,
|
||||
strip_server)
|
||||
from .protocol import (ActionSequenceProtocolPart,
|
||||
AssertsProtocolPart,
|
||||
BaseProtocolPart,
|
||||
TestharnessProtocolPart,
|
||||
PrefsProtocolPart,
|
||||
Protocol,
|
||||
StorageProtocolPart,
|
||||
SelectorProtocolPart,
|
||||
ClickProtocolPart,
|
||||
SendKeysProtocolPart,
|
||||
TestDriverProtocolPart,
|
||||
CoverageProtocolPart)
|
||||
from ..testrunner import Stop
|
||||
from ..webdriver_server import GeckoDriverServer
|
||||
|
||||
|
||||
def do_delayed_imports():
|
||||
global errors, marionette
|
||||
|
||||
# Marionette client used to be called marionette, recently it changed
|
||||
# to marionette_driver for unfathomable reasons
|
||||
try:
|
||||
import marionette
|
||||
from marionette import errors
|
||||
except ImportError:
|
||||
from marionette_driver import marionette, errors
|
||||
|
||||
|
||||
class MarionetteBaseProtocolPart(BaseProtocolPart):
|
||||
def __init__(self, parent):
|
||||
super(MarionetteBaseProtocolPart, self).__init__(parent)
|
||||
self.timeout = None
|
||||
|
||||
def setup(self):
|
||||
self.marionette = self.parent.marionette
|
||||
|
||||
def execute_script(self, script, async=False):
|
||||
method = self.marionette.execute_async_script if async else self.marionette.execute_script
|
||||
return method(script, new_sandbox=False, sandbox=None)
|
||||
|
||||
def set_timeout(self, timeout):
|
||||
"""Set the Marionette script timeout.
|
||||
|
||||
:param timeout: Script timeout in seconds
|
||||
|
||||
"""
|
||||
if timeout != self.timeout:
|
||||
self.marionette.timeout.script = timeout
|
||||
self.timeout = timeout
|
||||
|
||||
@property
|
||||
def current_window(self):
|
||||
return self.marionette.current_window_handle
|
||||
|
||||
def set_window(self, handle):
|
||||
self.marionette.switch_to_window(handle)
|
||||
|
||||
def wait(self):
|
||||
try:
|
||||
socket_timeout = self.marionette.client.socket_timeout
|
||||
except AttributeError:
|
||||
# This can happen if there was a crash
|
||||
return
|
||||
if socket_timeout:
|
||||
try:
|
||||
self.marionette.timeout.script = socket_timeout / 2
|
||||
except IOError:
|
||||
self.logger.debug("Socket closed")
|
||||
return
|
||||
|
||||
while True:
|
||||
try:
|
||||
self.marionette.execute_async_script("")
|
||||
except errors.NoSuchWindowException:
|
||||
# The window closed
|
||||
break
|
||||
except errors.ScriptTimeoutException:
|
||||
self.logger.debug("Script timed out")
|
||||
pass
|
||||
except errors.JavascriptException as e:
|
||||
# This can happen if we navigate, but just keep going
|
||||
self.logger.debug(e.message)
|
||||
pass
|
||||
except IOError:
|
||||
self.logger.debug("Socket closed")
|
||||
break
|
||||
except Exception as e:
|
||||
self.logger.warning(traceback.format_exc(e))
|
||||
break
|
||||
|
||||
|
||||
class MarionetteTestharnessProtocolPart(TestharnessProtocolPart):
|
||||
def __init__(self, parent):
|
||||
super(MarionetteTestharnessProtocolPart, self).__init__(parent)
|
||||
self.runner_handle = None
|
||||
with open(os.path.join(here, "runner.js")) as f:
|
||||
self.runner_script = f.read()
|
||||
|
||||
def setup(self):
|
||||
self.marionette = self.parent.marionette
|
||||
|
||||
def load_runner(self, url_protocol):
|
||||
# Check if we previously had a test window open, and if we did make sure it's closed
|
||||
if self.runner_handle:
|
||||
self._close_windows()
|
||||
url = urlparse.urljoin(self.parent.executor.server_url(url_protocol),
|
||||
"/testharness_runner.html")
|
||||
self.logger.debug("Loading %s" % url)
|
||||
try:
|
||||
self.dismiss_alert(lambda: self.marionette.navigate(url))
|
||||
except Exception as e:
|
||||
self.logger.critical(
|
||||
"Loading initial page %s failed. Ensure that the "
|
||||
"there are no other programs bound to this port and "
|
||||
"that your firewall rules or network setup does not "
|
||||
"prevent access.\e%s" % (url, traceback.format_exc(e)))
|
||||
raise
|
||||
self.runner_handle = self.marionette.current_window_handle
|
||||
format_map = {"title": threading.current_thread().name.replace("'", '"')}
|
||||
self.parent.base.execute_script(self.runner_script % format_map)
|
||||
|
||||
def _close_windows(self):
|
||||
handles = self.marionette.window_handles
|
||||
runner_handle = None
|
||||
try:
|
||||
handles.remove(self.runner_handle)
|
||||
runner_handle = self.runner_handle
|
||||
except ValueError:
|
||||
# The runner window probably changed id but we can restore it
|
||||
# This isn't supposed to happen, but marionette ids are not yet stable
|
||||
# We assume that the first handle returned corresponds to the runner,
|
||||
# but it hopefully doesn't matter too much if that assumption is
|
||||
# wrong since we reload the runner in that tab anyway.
|
||||
runner_handle = handles.pop(0)
|
||||
self.logger.info("Changing harness_window to %s" % runner_handle)
|
||||
|
||||
for handle in handles:
|
||||
try:
|
||||
self.dismiss_alert(lambda: self.marionette.switch_to_window(handle))
|
||||
self.marionette.switch_to_window(handle)
|
||||
self.logger.info("Closing window %s" % handle)
|
||||
self.marionette.close()
|
||||
except errors.NoSuchWindowException:
|
||||
# We might have raced with the previous test to close this
|
||||
# window, skip it.
|
||||
pass
|
||||
self.marionette.switch_to_window(runner_handle)
|
||||
return runner_handle
|
||||
|
||||
def close_old_windows(self, url_protocol):
|
||||
runner_handle = self._close_windows()
|
||||
if runner_handle != self.runner_handle:
|
||||
self.load_runner(url_protocol)
|
||||
return self.runner_handle
|
||||
|
||||
def dismiss_alert(self, f):
|
||||
while True:
|
||||
try:
|
||||
f()
|
||||
except errors.UnexpectedAlertOpen:
|
||||
alert = self.marionette.switch_to_alert()
|
||||
try:
|
||||
alert.dismiss()
|
||||
except errors.NoAlertPresentException:
|
||||
pass
|
||||
else:
|
||||
break
|
||||
|
||||
def get_test_window(self, window_id, parent):
|
||||
test_window = None
|
||||
if window_id:
|
||||
try:
|
||||
# Try this, it's in Level 1 but nothing supports it yet
|
||||
win_s = self.parent.base.execute_script("return window['%s'];" % self.window_id)
|
||||
win_obj = json.loads(win_s)
|
||||
test_window = win_obj["window-fcc6-11e5-b4f8-330a88ab9d7f"]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if test_window is None:
|
||||
after = self.marionette.window_handles
|
||||
if len(after) == 2:
|
||||
test_window = next(iter(set(after) - set([parent])))
|
||||
elif after[0] == parent and len(after) > 2:
|
||||
# Hope the first one here is the test window
|
||||
test_window = after[1]
|
||||
else:
|
||||
raise Exception("unable to find test window")
|
||||
|
||||
assert test_window != parent
|
||||
return test_window
|
||||
|
||||
|
||||
class MarionettePrefsProtocolPart(PrefsProtocolPart):
|
||||
def setup(self):
|
||||
self.marionette = self.parent.marionette
|
||||
|
||||
def set(self, name, value):
|
||||
if value.lower() not in ("true", "false"):
|
||||
try:
|
||||
int(value)
|
||||
except ValueError:
|
||||
value = "'%s'" % value
|
||||
else:
|
||||
value = value.lower()
|
||||
|
||||
self.logger.info("Setting pref %s (%s)" % (name, value))
|
||||
|
||||
script = """
|
||||
let prefInterface = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
.getService(Components.interfaces.nsIPrefBranch);
|
||||
let pref = '%s';
|
||||
let type = prefInterface.getPrefType(pref);
|
||||
let value = %s;
|
||||
switch(type) {
|
||||
case prefInterface.PREF_STRING:
|
||||
prefInterface.setCharPref(pref, value);
|
||||
break;
|
||||
case prefInterface.PREF_BOOL:
|
||||
prefInterface.setBoolPref(pref, value);
|
||||
break;
|
||||
case prefInterface.PREF_INT:
|
||||
prefInterface.setIntPref(pref, value);
|
||||
break;
|
||||
}
|
||||
""" % (name, value)
|
||||
with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
|
||||
self.marionette.execute_script(script)
|
||||
|
||||
def clear(self, name):
|
||||
self.logger.info("Clearing pref %s" % (name))
|
||||
script = """
|
||||
let prefInterface = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
.getService(Components.interfaces.nsIPrefBranch);
|
||||
let pref = '%s';
|
||||
prefInterface.clearUserPref(pref);
|
||||
""" % name
|
||||
with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
|
||||
self.marionette.execute_script(script)
|
||||
|
||||
def get(self, name):
|
||||
script = """
|
||||
let prefInterface = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
.getService(Components.interfaces.nsIPrefBranch);
|
||||
let pref = '%s';
|
||||
let type = prefInterface.getPrefType(pref);
|
||||
switch(type) {
|
||||
case prefInterface.PREF_STRING:
|
||||
return prefInterface.getCharPref(pref);
|
||||
case prefInterface.PREF_BOOL:
|
||||
return prefInterface.getBoolPref(pref);
|
||||
case prefInterface.PREF_INT:
|
||||
return prefInterface.getIntPref(pref);
|
||||
case prefInterface.PREF_INVALID:
|
||||
return null;
|
||||
}
|
||||
""" % name
|
||||
with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
|
||||
self.marionette.execute_script(script)
|
||||
|
||||
|
||||
class MarionetteStorageProtocolPart(StorageProtocolPart):
|
||||
def setup(self):
|
||||
self.marionette = self.parent.marionette
|
||||
|
||||
def clear_origin(self, url):
|
||||
self.logger.info("Clearing origin %s" % (url))
|
||||
script = """
|
||||
let url = '%s';
|
||||
let uri = Components.classes["@mozilla.org/network/io-service;1"]
|
||||
.getService(Ci.nsIIOService)
|
||||
.newURI(url);
|
||||
let ssm = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
|
||||
.getService(Ci.nsIScriptSecurityManager);
|
||||
let principal = ssm.createCodebasePrincipal(uri, {});
|
||||
let qms = Components.classes["@mozilla.org/dom/quota-manager-service;1"]
|
||||
.getService(Components.interfaces.nsIQuotaManagerService);
|
||||
qms.clearStoragesForPrincipal(principal, "default", null, true);
|
||||
""" % url
|
||||
with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
|
||||
self.marionette.execute_script(script)
|
||||
|
||||
|
||||
class MarionetteAssertsProtocolPart(AssertsProtocolPart):
|
||||
def setup(self):
|
||||
self.assert_count = {"chrome": 0, "content": 0}
|
||||
self.chrome_assert_count = 0
|
||||
self.marionette = self.parent.marionette
|
||||
|
||||
def get(self):
|
||||
script = """
|
||||
debug = Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2);
|
||||
if (debug.isDebugBuild) {
|
||||
return debug.assertionCount;
|
||||
}
|
||||
return 0;
|
||||
"""
|
||||
|
||||
def get_count(context, **kwargs):
|
||||
try:
|
||||
context_count = self.marionette.execute_script(script, **kwargs)
|
||||
if context_count:
|
||||
self.parent.logger.info("Got %s assert count %s" % (context, context_count))
|
||||
test_count = context_count - self.assert_count[context]
|
||||
self.assert_count[context] = context_count
|
||||
return test_count
|
||||
except errors.NoSuchWindowException:
|
||||
# If the window was already closed
|
||||
self.parent.logger.warning("Failed to get assertion count; window was closed")
|
||||
except (errors.MarionetteException, IOError):
|
||||
# This usually happens if the process crashed
|
||||
pass
|
||||
|
||||
counts = []
|
||||
with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
|
||||
counts.append(get_count("chrome"))
|
||||
if self.parent.e10s:
|
||||
counts.append(get_count("content", sandbox="system"))
|
||||
|
||||
counts = [item for item in counts if item is not None]
|
||||
|
||||
if not counts:
|
||||
return None
|
||||
|
||||
return sum(counts)
|
||||
|
||||
|
||||
class MarionetteSelectorProtocolPart(SelectorProtocolPart):
|
||||
def setup(self):
|
||||
self.marionette = self.parent.marionette
|
||||
|
||||
def elements_by_selector(self, selector):
|
||||
return self.marionette.find_elements("css selector", selector)
|
||||
|
||||
|
||||
class MarionetteClickProtocolPart(ClickProtocolPart):
|
||||
def setup(self):
|
||||
self.marionette = self.parent.marionette
|
||||
|
||||
def element(self, element):
|
||||
return element.click()
|
||||
|
||||
|
||||
class MarionetteSendKeysProtocolPart(SendKeysProtocolPart):
|
||||
def setup(self):
|
||||
self.marionette = self.parent.marionette
|
||||
|
||||
def send_keys(self, element, keys):
|
||||
return element.send_keys(keys)
|
||||
|
||||
|
||||
class MarionetteActionSequenceProtocolPart(ActionSequenceProtocolPart):
|
||||
def setup(self):
|
||||
self.marionette = self.parent.marionette
|
||||
|
||||
def send_actions(self, actions):
|
||||
actions = self.marionette._to_json(actions)
|
||||
self.logger.info(actions)
|
||||
self.marionette._send_message("WebDriver:PerformActions", actions)
|
||||
|
||||
|
||||
class MarionetteTestDriverProtocolPart(TestDriverProtocolPart):
|
||||
def setup(self):
|
||||
self.marionette = self.parent.marionette
|
||||
|
||||
def send_message(self, message_type, status, message=None):
|
||||
obj = {
|
||||
"type": "testdriver-%s" % str(message_type),
|
||||
"status": str(status)
|
||||
}
|
||||
if message:
|
||||
obj["message"] = str(message)
|
||||
self.parent.base.execute_script("window.postMessage(%s, '*')" % json.dumps(obj))
|
||||
|
||||
|
||||
class MarionetteCoverageProtocolPart(CoverageProtocolPart):
|
||||
def setup(self):
|
||||
self.marionette = self.parent.marionette
|
||||
|
||||
if not self.parent.ccov:
|
||||
self.is_enabled = False
|
||||
return
|
||||
|
||||
script = """
|
||||
ChromeUtils.import("chrome://marionette/content/PerTestCoverageUtils.jsm");
|
||||
return PerTestCoverageUtils.enabled;
|
||||
"""
|
||||
with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
|
||||
self.is_enabled = self.marionette.execute_script(script)
|
||||
|
||||
def reset(self):
|
||||
script = """
|
||||
var callback = arguments[arguments.length - 1];
|
||||
|
||||
ChromeUtils.import("chrome://marionette/content/PerTestCoverageUtils.jsm");
|
||||
PerTestCoverageUtils.beforeTest().then(callback, callback);
|
||||
"""
|
||||
with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
|
||||
try:
|
||||
error = self.marionette.execute_async_script(script)
|
||||
if error is not None:
|
||||
raise Exception('Failure while resetting counters: %s' % json.dumps(error))
|
||||
except (errors.MarionetteException, IOError):
|
||||
# This usually happens if the process crashed
|
||||
pass
|
||||
|
||||
def dump(self):
|
||||
if len(self.marionette.window_handles):
|
||||
handle = self.marionette.window_handles[0]
|
||||
self.marionette.switch_to_window(handle)
|
||||
|
||||
script = """
|
||||
var callback = arguments[arguments.length - 1];
|
||||
|
||||
ChromeUtils.import("chrome://marionette/content/PerTestCoverageUtils.jsm");
|
||||
PerTestCoverageUtils.afterTest().then(callback, callback);
|
||||
"""
|
||||
with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
|
||||
try:
|
||||
error = self.marionette.execute_async_script(script)
|
||||
if error is not None:
|
||||
raise Exception('Failure while dumping counters: %s' % json.dumps(error))
|
||||
except (errors.MarionetteException, IOError):
|
||||
# This usually happens if the process crashed
|
||||
pass
|
||||
|
||||
|
||||
class MarionetteProtocol(Protocol):
|
||||
implements = [MarionetteBaseProtocolPart,
|
||||
MarionetteTestharnessProtocolPart,
|
||||
MarionettePrefsProtocolPart,
|
||||
MarionetteStorageProtocolPart,
|
||||
MarionetteSelectorProtocolPart,
|
||||
MarionetteClickProtocolPart,
|
||||
MarionetteSendKeysProtocolPart,
|
||||
MarionetteActionSequenceProtocolPart,
|
||||
MarionetteTestDriverProtocolPart,
|
||||
MarionetteAssertsProtocolPart,
|
||||
MarionetteCoverageProtocolPart]
|
||||
|
||||
def __init__(self, executor, browser, capabilities=None, timeout_multiplier=1, e10s=True, ccov=False):
|
||||
do_delayed_imports()
|
||||
|
||||
super(MarionetteProtocol, self).__init__(executor, browser)
|
||||
self.marionette = None
|
||||
self.marionette_port = browser.marionette_port
|
||||
self.capabilities = capabilities
|
||||
self.timeout_multiplier = timeout_multiplier
|
||||
self.runner_handle = None
|
||||
self.e10s = e10s
|
||||
self.ccov = ccov
|
||||
|
||||
def connect(self):
|
||||
self.logger.debug("Connecting to Marionette on port %i" % self.marionette_port)
|
||||
startup_timeout = marionette.Marionette.DEFAULT_STARTUP_TIMEOUT * self.timeout_multiplier
|
||||
self.marionette = marionette.Marionette(host='127.0.0.1',
|
||||
port=self.marionette_port,
|
||||
socket_timeout=None,
|
||||
startup_timeout=startup_timeout)
|
||||
|
||||
self.logger.debug("Waiting for Marionette connection")
|
||||
while True:
|
||||
try:
|
||||
self.marionette.raise_for_port()
|
||||
break
|
||||
except IOError:
|
||||
# When running in a debugger wait indefinitely for Firefox to start
|
||||
if self.executor.debug_info is None:
|
||||
raise
|
||||
|
||||
self.logger.debug("Starting Marionette session")
|
||||
self.marionette.start_session(self.capabilities)
|
||||
self.logger.debug("Marionette session started")
|
||||
|
||||
def after_connect(self):
|
||||
pass
|
||||
|
||||
def teardown(self):
|
||||
try:
|
||||
self.marionette._request_in_app_shutdown()
|
||||
self.marionette.delete_session(send_request=False)
|
||||
except Exception:
|
||||
# This is typically because the session never started
|
||||
pass
|
||||
if self.marionette is not None:
|
||||
del self.marionette
|
||||
super(MarionetteProtocol, self).teardown()
|
||||
|
||||
@property
|
||||
def is_alive(self):
|
||||
try:
|
||||
self.marionette.current_window_handle
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
|
||||
def on_environment_change(self, old_environment, new_environment):
|
||||
#Unset all the old prefs
|
||||
for name in old_environment.get("prefs", {}).iterkeys():
|
||||
value = self.executor.original_pref_values[name]
|
||||
if value is None:
|
||||
self.prefs.clear(name)
|
||||
else:
|
||||
self.prefs.set(name, value)
|
||||
|
||||
for name, value in new_environment.get("prefs", {}).iteritems():
|
||||
self.executor.original_pref_values[name] = self.prefs.get(name)
|
||||
self.prefs.set(name, value)
|
||||
|
||||
|
||||
class ExecuteAsyncScriptRun(object):
|
||||
def __init__(self, logger, func, protocol, url, timeout):
|
||||
self.logger = logger
|
||||
self.result = (None, None)
|
||||
self.protocol = protocol
|
||||
self.func = func
|
||||
self.url = url
|
||||
self.timeout = timeout
|
||||
self.result_flag = threading.Event()
|
||||
|
||||
def run(self):
|
||||
index = self.url.rfind("/storage/")
|
||||
if index != -1:
|
||||
# Clear storage
|
||||
self.protocol.storage.clear_origin(self.url)
|
||||
|
||||
timeout = self.timeout
|
||||
|
||||
try:
|
||||
if timeout is not None:
|
||||
self.protocol.base.set_timeout(timeout + extra_timeout)
|
||||
else:
|
||||
# We just want it to never time out, really, but marionette doesn't
|
||||
# make that possible. It also seems to time out immediately if the
|
||||
# timeout is set too high. This works at least.
|
||||
self.protocol.base.set_timeout(2**28 - 1)
|
||||
except IOError:
|
||||
self.logger.error("Lost marionette connection before starting test")
|
||||
return Stop
|
||||
|
||||
if timeout is not None:
|
||||
wait_timeout = timeout + 2 * extra_timeout
|
||||
else:
|
||||
wait_timeout = None
|
||||
|
||||
timer = threading.Timer(wait_timeout, self._timeout)
|
||||
timer.start()
|
||||
|
||||
self._run()
|
||||
|
||||
self.result_flag.wait()
|
||||
timer.cancel()
|
||||
|
||||
if self.result == (None, None):
|
||||
self.logger.debug("Timed out waiting for a result")
|
||||
self.result = False, ("EXTERNAL-TIMEOUT", None)
|
||||
elif self.result[1] is None:
|
||||
# We didn't get any data back from the test, so check if the
|
||||
# browser is still responsive
|
||||
if self.protocol.is_alive:
|
||||
self.result = False, ("INTERNAL-ERROR", None)
|
||||
else:
|
||||
self.result = False, ("CRASH", None)
|
||||
return self.result
|
||||
|
||||
def _run(self):
|
||||
try:
|
||||
self.result = True, self.func(self.protocol, self.url, self.timeout)
|
||||
except errors.ScriptTimeoutException:
|
||||
self.logger.debug("Got a marionette timeout")
|
||||
self.result = False, ("EXTERNAL-TIMEOUT", None)
|
||||
except IOError:
|
||||
# This can happen on a crash
|
||||
# Also, should check after the test if the firefox process is still running
|
||||
# and otherwise ignore any other result and set it to crash
|
||||
self.result = False, ("CRASH", None)
|
||||
except Exception as e:
|
||||
message = getattr(e, "message", "")
|
||||
if message:
|
||||
message += "\n"
|
||||
message += traceback.format_exc(e)
|
||||
self.logger.warning(traceback.format_exc())
|
||||
self.result = False, ("INTERNAL-ERROR", e)
|
||||
finally:
|
||||
self.result_flag.set()
|
||||
|
||||
def _timeout(self):
|
||||
self.result = False, ("EXTERNAL-TIMEOUT", None)
|
||||
self.result_flag.set()
|
||||
|
||||
|
||||
class MarionetteTestharnessExecutor(TestharnessExecutor):
|
||||
supports_testdriver = True
|
||||
|
||||
def __init__(self, browser, server_config, timeout_multiplier=1,
|
||||
close_after_done=True, debug_info=None, capabilities=None,
|
||||
debug=False, ccov=False, **kwargs):
|
||||
"""Marionette-based executor for testharness.js tests"""
|
||||
TestharnessExecutor.__init__(self, browser, server_config,
|
||||
timeout_multiplier=timeout_multiplier,
|
||||
debug_info=debug_info)
|
||||
self.protocol = MarionetteProtocol(self,
|
||||
browser,
|
||||
capabilities,
|
||||
timeout_multiplier,
|
||||
kwargs["e10s"],
|
||||
ccov)
|
||||
self.script = open(os.path.join(here, "testharness_webdriver.js")).read()
|
||||
self.script_resume = open(os.path.join(here, "testharness_webdriver_resume.js")).read()
|
||||
self.close_after_done = close_after_done
|
||||
self.window_id = str(uuid.uuid4())
|
||||
self.debug = debug
|
||||
|
||||
self.original_pref_values = {}
|
||||
|
||||
if marionette is None:
|
||||
do_delayed_imports()
|
||||
|
||||
def setup(self, runner):
|
||||
super(MarionetteTestharnessExecutor, self).setup(runner)
|
||||
self.protocol.testharness.load_runner(self.last_environment["protocol"])
|
||||
|
||||
def is_alive(self):
|
||||
return self.protocol.is_alive
|
||||
|
||||
def on_environment_change(self, new_environment):
|
||||
self.protocol.on_environment_change(self.last_environment, new_environment)
|
||||
|
||||
if new_environment["protocol"] != self.last_environment["protocol"]:
|
||||
self.protocol.testharness.load_runner(new_environment["protocol"])
|
||||
|
||||
def do_test(self, test):
|
||||
timeout = (test.timeout * self.timeout_multiplier if self.debug_info is None
|
||||
else None)
|
||||
|
||||
success, data = ExecuteAsyncScriptRun(self.logger,
|
||||
self.do_testharness,
|
||||
self.protocol,
|
||||
self.test_url(test),
|
||||
timeout).run()
|
||||
# The format of data depends on whether the test ran to completion or not
|
||||
# For asserts we only care about the fact that if it didn't complete, the
|
||||
# status is in the first field.
|
||||
status = None
|
||||
if not success:
|
||||
status = data[0]
|
||||
|
||||
extra = None
|
||||
if self.debug and (success or status not in ("CRASH", "INTERNAL-ERROR")):
|
||||
assertion_count = self.protocol.asserts.get()
|
||||
if assertion_count is not None:
|
||||
extra = {"assertion_count": assertion_count}
|
||||
|
||||
if success:
|
||||
return self.convert_result(test, data, extra=extra)
|
||||
|
||||
return (test.result_cls(extra=extra, *data), [])
|
||||
|
||||
def do_testharness(self, protocol, url, timeout):
|
||||
parent_window = protocol.testharness.close_old_windows(protocol)
|
||||
|
||||
if timeout is not None:
|
||||
timeout_ms = str(timeout * 1000)
|
||||
else:
|
||||
timeout_ms = "null"
|
||||
|
||||
if self.protocol.coverage.is_enabled:
|
||||
self.protocol.coverage.reset()
|
||||
|
||||
format_map = {"abs_url": url,
|
||||
"url": strip_server(url),
|
||||
"window_id": self.window_id,
|
||||
"timeout_multiplier": self.timeout_multiplier,
|
||||
"timeout": timeout_ms,
|
||||
"explicit_timeout": timeout is None}
|
||||
|
||||
script = self.script % format_map
|
||||
|
||||
protocol.base.execute_script(script, async=True)
|
||||
test_window = protocol.testharness.get_test_window(self.window_id, parent_window)
|
||||
|
||||
handler = CallbackHandler(self.logger, protocol, test_window)
|
||||
while True:
|
||||
self.protocol.base.set_window(test_window)
|
||||
result = protocol.base.execute_script(
|
||||
self.script_resume % format_map, async=True)
|
||||
if result is None:
|
||||
# This can happen if we get an content process crash
|
||||
return None
|
||||
done, rv = handler(result)
|
||||
if done:
|
||||
break
|
||||
|
||||
if self.protocol.coverage.is_enabled:
|
||||
self.protocol.coverage.dump()
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
class MarionetteRefTestExecutor(RefTestExecutor):
|
||||
def __init__(self, browser, server_config, timeout_multiplier=1,
|
||||
screenshot_cache=None, close_after_done=True,
|
||||
debug_info=None, reftest_internal=False,
|
||||
reftest_screenshot="unexpected", ccov=False,
|
||||
group_metadata=None, capabilities=None, debug=False, **kwargs):
|
||||
"""Marionette-based executor for reftests"""
|
||||
RefTestExecutor.__init__(self,
|
||||
browser,
|
||||
server_config,
|
||||
screenshot_cache=screenshot_cache,
|
||||
timeout_multiplier=timeout_multiplier,
|
||||
debug_info=debug_info)
|
||||
self.protocol = MarionetteProtocol(self, browser, capabilities,
|
||||
timeout_multiplier, kwargs["e10s"],
|
||||
ccov)
|
||||
self.implementation = (InternalRefTestImplementation
|
||||
if reftest_internal
|
||||
else RefTestImplementation)(self)
|
||||
self.implementation_kwargs = ({"screenshot": reftest_screenshot} if
|
||||
reftest_internal else {})
|
||||
|
||||
self.close_after_done = close_after_done
|
||||
self.has_window = False
|
||||
self.original_pref_values = {}
|
||||
self.group_metadata = group_metadata
|
||||
self.debug = debug
|
||||
|
||||
with open(os.path.join(here, "reftest.js")) as f:
|
||||
self.script = f.read()
|
||||
with open(os.path.join(here, "reftest-wait_marionette.js")) as f:
|
||||
self.wait_script = f.read()
|
||||
|
||||
def setup(self, runner):
|
||||
super(self.__class__, self).setup(runner)
|
||||
self.implementation.setup(**self.implementation_kwargs)
|
||||
|
||||
def teardown(self):
|
||||
try:
|
||||
self.implementation.teardown()
|
||||
handles = self.protocol.marionette.window_handles
|
||||
if handles:
|
||||
self.protocol.marionette.switch_to_window(handles[0])
|
||||
super(self.__class__, self).teardown()
|
||||
except Exception as e:
|
||||
# Ignore errors during teardown
|
||||
self.logger.warning("Exception during reftest teardown:\n%s" %
|
||||
traceback.format_exc(e))
|
||||
|
||||
def is_alive(self):
|
||||
return self.protocol.is_alive
|
||||
|
||||
def on_environment_change(self, new_environment):
|
||||
self.protocol.on_environment_change(self.last_environment, new_environment)
|
||||
|
||||
def do_test(self, test):
|
||||
if not isinstance(self.implementation, InternalRefTestImplementation):
|
||||
if self.close_after_done and self.has_window:
|
||||
self.protocol.marionette.close()
|
||||
self.protocol.marionette.switch_to_window(
|
||||
self.protocol.marionette.window_handles[-1])
|
||||
self.has_window = False
|
||||
|
||||
if not self.has_window:
|
||||
self.protocol.base.execute_script(self.script)
|
||||
self.protocol.base.set_window(self.protocol.marionette.window_handles[-1])
|
||||
self.has_window = True
|
||||
|
||||
if self.protocol.coverage.is_enabled:
|
||||
self.protocol.coverage.reset()
|
||||
|
||||
result = self.implementation.run_test(test)
|
||||
|
||||
if self.protocol.coverage.is_enabled:
|
||||
self.protocol.coverage.dump()
|
||||
|
||||
if self.debug:
|
||||
assertion_count = self.protocol.asserts.get()
|
||||
if "extra" not in result:
|
||||
result["extra"] = {}
|
||||
result["extra"]["assertion_count"] = assertion_count
|
||||
|
||||
return self.convert_result(test, result)
|
||||
|
||||
def screenshot(self, test, viewport_size, dpi):
|
||||
# https://github.com/w3c/wptrunner/issues/166
|
||||
assert viewport_size is None
|
||||
assert dpi is None
|
||||
|
||||
timeout = self.timeout_multiplier * test.timeout if self.debug_info is None else None
|
||||
|
||||
test_url = self.test_url(test)
|
||||
|
||||
return ExecuteAsyncScriptRun(self.logger,
|
||||
self._screenshot,
|
||||
self.protocol,
|
||||
test_url,
|
||||
timeout).run()
|
||||
|
||||
def _screenshot(self, protocol, url, timeout):
|
||||
protocol.marionette.navigate(url)
|
||||
|
||||
protocol.base.execute_script(self.wait_script, async=True)
|
||||
|
||||
screenshot = protocol.marionette.screenshot(full=False)
|
||||
# strip off the data:img/png, part of the url
|
||||
if screenshot.startswith("data:image/png;base64,"):
|
||||
screenshot = screenshot.split(",", 1)[1]
|
||||
|
||||
return screenshot
|
||||
|
||||
|
||||
class InternalRefTestImplementation(object):
|
||||
def __init__(self, executor):
|
||||
self.timeout_multiplier = executor.timeout_multiplier
|
||||
self.executor = executor
|
||||
|
||||
@property
|
||||
def logger(self):
|
||||
return self.executor.logger
|
||||
|
||||
def setup(self, screenshot="unexpected"):
|
||||
data = {"screenshot": screenshot}
|
||||
if self.executor.group_metadata is not None:
|
||||
data["urlCount"] = {urlparse.urljoin(self.executor.server_url(key[0]), key[1]):value
|
||||
for key, value in self.executor.group_metadata.get("url_count", {}).iteritems()
|
||||
if value > 1}
|
||||
self.executor.protocol.marionette.set_context(self.executor.protocol.marionette.CONTEXT_CHROME)
|
||||
self.executor.protocol.marionette._send_message("reftest:setup", data)
|
||||
|
||||
def run_test(self, test):
|
||||
references = self.get_references(test)
|
||||
timeout = (test.timeout * 1000) * self.timeout_multiplier
|
||||
rv = self.executor.protocol.marionette._send_message("reftest:run",
|
||||
{"test": self.executor.test_url(test),
|
||||
"references": references,
|
||||
"expected": test.expected(),
|
||||
"timeout": timeout})["value"]
|
||||
return rv
|
||||
|
||||
def get_references(self, node):
|
||||
rv = []
|
||||
for item, relation in node.references:
|
||||
rv.append([self.executor.test_url(item), self.get_references(item), relation])
|
||||
return rv
|
||||
|
||||
def teardown(self):
|
||||
try:
|
||||
self.executor.protocol.marionette._send_message("reftest:teardown", {})
|
||||
self.executor.protocol.marionette.set_context(self.executor.protocol.marionette.CONTEXT_CONTENT)
|
||||
except Exception as e:
|
||||
# Ignore errors during teardown
|
||||
self.logger.warning(traceback.format_exc(e))
|
||||
|
||||
|
||||
|
||||
class GeckoDriverProtocol(WebDriverProtocol):
|
||||
server_cls = GeckoDriverServer
|
||||
|
||||
|
||||
class MarionetteWdspecExecutor(WdspecExecutor):
|
||||
protocol_cls = GeckoDriverProtocol
|
Loading…
Reference in New Issue
Block a user