mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-08-18 07:29:28 +00:00
410 lines
15 KiB
Python
410 lines
15 KiB
Python
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||
|
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||
|
|
||
|
import hashlib
|
||
|
import os
|
||
|
import socket
|
||
|
import sys
|
||
|
import threading
|
||
|
import time
|
||
|
import traceback
|
||
|
import urlparse
|
||
|
import uuid
|
||
|
from collections import defaultdict
|
||
|
|
||
|
marionette = None
|
||
|
|
||
|
here = os.path.join(os.path.split(__file__)[0])
|
||
|
|
||
|
from .base import (ExecutorException,
|
||
|
Protocol,
|
||
|
RefTestExecutor,
|
||
|
RefTestImplementation,
|
||
|
TestExecutor,
|
||
|
TestharnessExecutor,
|
||
|
testharness_result_converter,
|
||
|
reftest_result_converter,
|
||
|
strip_server)
|
||
|
from ..testrunner import Stop
|
||
|
|
||
|
# Extra timeout to use after internal test timeout at which the harness
|
||
|
# should force a timeout
|
||
|
extra_timeout = 5 # seconds
|
||
|
|
||
|
def do_delayed_imports():
|
||
|
global marionette
|
||
|
global errors
|
||
|
try:
|
||
|
import marionette
|
||
|
from marionette import errors
|
||
|
except ImportError:
|
||
|
from marionette_driver import marionette, errors
|
||
|
|
||
|
|
||
|
class MarionetteProtocol(Protocol):
|
||
|
def __init__(self, executor, browser):
|
||
|
do_delayed_imports()
|
||
|
|
||
|
Protocol.__init__(self, executor, browser)
|
||
|
self.marionette = None
|
||
|
self.marionette_port = browser.marionette_port
|
||
|
|
||
|
def setup(self, runner):
|
||
|
"""Connect to browser via Marionette."""
|
||
|
Protocol.setup(self, runner)
|
||
|
|
||
|
self.logger.debug("Connecting to marionette on port %i" % self.marionette_port)
|
||
|
self.marionette = marionette.Marionette(host='localhost', port=self.marionette_port)
|
||
|
|
||
|
# XXX Move this timeout somewhere
|
||
|
self.logger.debug("Waiting for Marionette connection")
|
||
|
while True:
|
||
|
success = self.marionette.wait_for_port(60)
|
||
|
#When running in a debugger wait indefinitely for firefox to start
|
||
|
if success or self.executor.debug_info is None:
|
||
|
break
|
||
|
|
||
|
session_started = False
|
||
|
if success:
|
||
|
try:
|
||
|
self.logger.debug("Starting Marionette session")
|
||
|
self.marionette.start_session()
|
||
|
except Exception as e:
|
||
|
self.logger.warning("Starting marionette session failed: %s" % e)
|
||
|
else:
|
||
|
self.logger.debug("Marionette session started")
|
||
|
session_started = True
|
||
|
|
||
|
if not success or not session_started:
|
||
|
self.logger.warning("Failed to connect to Marionette")
|
||
|
self.executor.runner.send_message("init_failed")
|
||
|
else:
|
||
|
try:
|
||
|
self.after_connect()
|
||
|
except Exception:
|
||
|
self.logger.warning("Post-connection steps failed")
|
||
|
self.logger.error(traceback.format_exc())
|
||
|
self.executor.runner.send_message("init_failed")
|
||
|
else:
|
||
|
self.executor.runner.send_message("init_succeeded")
|
||
|
|
||
|
def teardown(self):
|
||
|
try:
|
||
|
self.marionette.delete_session()
|
||
|
except Exception:
|
||
|
# This is typically because the session never started
|
||
|
pass
|
||
|
del self.marionette
|
||
|
|
||
|
def is_alive(self):
|
||
|
"""Check if the marionette connection is still active"""
|
||
|
try:
|
||
|
# Get a simple property over the connection
|
||
|
self.marionette.current_window_handle
|
||
|
except Exception:
|
||
|
return False
|
||
|
return True
|
||
|
|
||
|
def after_connect(self):
|
||
|
# Turn off debug-level logging by default since this is so verbose
|
||
|
with self.marionette.using_context("chrome"):
|
||
|
self.marionette.execute_script("""
|
||
|
Components.utils.import("resource://gre/modules/Log.jsm");
|
||
|
Log.repository.getLogger("Marionette").level = Log.Level.Info;
|
||
|
""")
|
||
|
self.load_runner("http")
|
||
|
|
||
|
def load_runner(self, protocol):
|
||
|
# Check if we previously had a test window open, and if we did make sure it's closed
|
||
|
self.marionette.execute_script("if (window.wrappedJSObject.win) {window.wrappedJSObject.win.close()}")
|
||
|
url = urlparse.urljoin(self.executor.server_url(protocol), "/testharness_runner.html")
|
||
|
self.logger.debug("Loading %s" % url)
|
||
|
try:
|
||
|
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)))
|
||
|
self.marionette.execute_script(
|
||
|
"document.title = '%s'" % threading.current_thread().name.replace("'", '"'))
|
||
|
|
||
|
def wait(self):
|
||
|
while True:
|
||
|
try:
|
||
|
self.marionette.execute_async_script("");
|
||
|
except errors.ScriptTimeoutException:
|
||
|
pass
|
||
|
except (socket.timeout, IOError):
|
||
|
break
|
||
|
except Exception as e:
|
||
|
self.logger.error(traceback.format_exc(e))
|
||
|
break
|
||
|
|
||
|
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.clear_user_pref(name)
|
||
|
else:
|
||
|
self.set_pref(name, value)
|
||
|
|
||
|
for name, value in new_environment.get("prefs", {}).iteritems():
|
||
|
self.executor.original_pref_values[name] = self.get_pref(name)
|
||
|
self.set_pref(name, value)
|
||
|
|
||
|
def set_pref(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_user_pref(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_pref(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 MarionetteRun(object):
|
||
|
def __init__(self, logger, func, marionette, url, timeout):
|
||
|
self.logger = logger
|
||
|
self.result = None
|
||
|
self.marionette = marionette
|
||
|
self.func = func
|
||
|
self.url = url
|
||
|
self.timeout = timeout
|
||
|
self.result_flag = threading.Event()
|
||
|
|
||
|
def run(self):
|
||
|
timeout = self.timeout
|
||
|
|
||
|
try:
|
||
|
if timeout is not None:
|
||
|
self.marionette.set_script_timeout((timeout + extra_timeout) * 1000)
|
||
|
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.marionette.set_script_timeout(2**31 - 1)
|
||
|
except IOError:
|
||
|
self.logger.error("Lost marionette connection before starting test")
|
||
|
return Stop
|
||
|
|
||
|
executor = threading.Thread(target = self._run)
|
||
|
executor.start()
|
||
|
|
||
|
if timeout is not None:
|
||
|
wait_timeout = timeout + 2 * extra_timeout
|
||
|
else:
|
||
|
wait_timeout = None
|
||
|
|
||
|
flag = self.result_flag.wait(wait_timeout)
|
||
|
if self.result is None:
|
||
|
self.logger.debug("Timed out waiting for a result")
|
||
|
assert not flag
|
||
|
self.result = False, ("EXTERNAL-TIMEOUT", None)
|
||
|
|
||
|
return self.result
|
||
|
|
||
|
def _run(self):
|
||
|
try:
|
||
|
self.result = True, self.func(self.marionette, self.url, self.timeout)
|
||
|
except errors.ScriptTimeoutException:
|
||
|
self.logger.debug("Got a marionette timeout")
|
||
|
self.result = False, ("EXTERNAL-TIMEOUT", None)
|
||
|
except (socket.timeout, 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.result = False, ("ERROR", e)
|
||
|
|
||
|
finally:
|
||
|
self.result_flag.set()
|
||
|
|
||
|
|
||
|
class MarionetteTestharnessExecutor(TestharnessExecutor):
|
||
|
def __init__(self, browser, server_config, timeout_multiplier=1, close_after_done=True,
|
||
|
debug_info=None):
|
||
|
"""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)
|
||
|
self.script = open(os.path.join(here, "testharness_marionette.js")).read()
|
||
|
self.close_after_done = close_after_done
|
||
|
self.window_id = str(uuid.uuid4())
|
||
|
|
||
|
self.original_pref_values = {}
|
||
|
|
||
|
if marionette is None:
|
||
|
do_delayed_imports()
|
||
|
|
||
|
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.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 = MarionetteRun(self.logger,
|
||
|
self.do_testharness,
|
||
|
self.protocol.marionette,
|
||
|
self.test_url(test),
|
||
|
timeout).run()
|
||
|
if success:
|
||
|
return self.convert_result(test, data)
|
||
|
|
||
|
return (test.result_cls(*data), [])
|
||
|
|
||
|
def do_testharness(self, marionette, url, timeout):
|
||
|
if self.close_after_done:
|
||
|
marionette.execute_script("if (window.wrappedJSObject.win) {window.wrappedJSObject.win.close()}")
|
||
|
|
||
|
if timeout is not None:
|
||
|
timeout_ms = str(timeout * 1000)
|
||
|
else:
|
||
|
timeout_ms = "null"
|
||
|
|
||
|
script = self.script % {"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}
|
||
|
|
||
|
return marionette.execute_async_script(script, new_sandbox=False)
|
||
|
|
||
|
|
||
|
class MarionetteRefTestExecutor(RefTestExecutor):
|
||
|
def __init__(self, browser, server_config, timeout_multiplier=1,
|
||
|
screenshot_cache=None, close_after_done=True, debug_info=None):
|
||
|
"""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)
|
||
|
self.implementation = RefTestImplementation(self)
|
||
|
self.close_after_done = close_after_done
|
||
|
self.has_window = False
|
||
|
self.original_pref_values = {}
|
||
|
|
||
|
with open(os.path.join(here, "reftest.js")) as f:
|
||
|
self.script = f.read()
|
||
|
with open(os.path.join(here, "reftest-wait.js")) as f:
|
||
|
self.wait_script = f.read()
|
||
|
|
||
|
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 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.marionette.execute_script(self.script)
|
||
|
self.protocol.marionette.switch_to_window(self.protocol.marionette.window_handles[-1])
|
||
|
self.has_window = True
|
||
|
|
||
|
result = self.implementation.run_test(test)
|
||
|
|
||
|
return self.convert_result(test, result)
|
||
|
|
||
|
def screenshot(self, test):
|
||
|
timeout = self.timeout_multiplier * test.timeout if self.debug_info is None else None
|
||
|
|
||
|
test_url = self.test_url(test)
|
||
|
|
||
|
return MarionetteRun(self.logger,
|
||
|
self._screenshot,
|
||
|
self.protocol.marionette,
|
||
|
test_url,
|
||
|
timeout).run()
|
||
|
|
||
|
def _screenshot(self, marionette, url, timeout):
|
||
|
marionette.navigate(url)
|
||
|
|
||
|
marionette.execute_async_script(self.wait_script)
|
||
|
|
||
|
screenshot = marionette.screenshot()
|
||
|
# strip off the data:img/png, part of the url
|
||
|
if screenshot.startswith("data:image/png;base64,"):
|
||
|
screenshot = screenshot.split(",", 1)[1]
|
||
|
|
||
|
return screenshot
|