blob: 21766c7cd7aa950c758f6c5c3b22aff9b6798a7c [file] [log] [blame]
"""Launches Cobalt and runs webdriver-based Cobalt tests."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import os
import re
import sys
import thread
import threading
import _env # pylint: disable=unused-import
from starboard.tools import abstract_launcher
# pylint: disable=C6204
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
import webdriver_utils
# Pattern to match Cobalt log line for when the WebDriver port has been
# opened.
RE_WEBDRIVER_LISTEN = re.compile(r'Starting WebDriver server on port (\d+)')
# Pattern to match Cobalt log line for when a WindowDriver has been created.
RE_WINDOWDRIVER_CREATED = re.compile(
r'^\[\d+/\d+:INFO:browser_module\.cc\(\d+\)\] Created WindowDriver: ID=\S+')
# Pattern to match Cobalt log line for when a WebModule is has been loaded.
RE_WEBMODULE_LOADED = re.compile(
r'^\[\d+/\d+:INFO:browser_module\.cc\(\d+\)\] Loaded WebModule')
DEFAULT_STARTUP_TIMEOUT_SECONDS = 2 * 60
WEBDRIVER_HTTP_TIMEOUT_SECONDS = 2 * 60
COBALT_EXIT_TIMEOUT_SECONDS = 5
COBALT_WEBDRIVER_CAPABILITIES = {
'browserName': 'cobalt',
'javascriptEnabled': True,
'platform': 'LINUX'
}
_launcher = None
_webdriver = None
_windowdriver_created = threading.Event()
_webmodule_loaded = threading.Event()
def SendResume():
"""Sends a resume signal to start Cobalt from preload."""
_launcher.SendResume()
def SendSuspend():
"""Sends a system signal to put Cobalt into suspend state."""
_launcher.SendSuspend()
def GetWebDriver():
"""Returns the active connect WebDriver instance."""
return _webdriver
def GetWindowDriverCreated():
"""Returns the WindowDriver created instance."""
return _windowdriver_created
def GetWebModuleLoaded():
"""Returns the WebModule loaded instance."""
return _webmodule_loaded
class TimeoutException(Exception):
pass
class CobaltRunner(object):
"""Runs a Cobalt browser w/ a WebDriver client attached."""
test_script_started = threading.Event()
selenium_webdriver_module = None
launcher = None
webdriver = None
thread = None
failed = False
should_exit = threading.Event()
def __init__(self, platform, config, device_id, target_params, out_directory,
url, log_file, success_message):
global _launcher
self.selenium_webdriver_module = webdriver_utils.import_selenium_module(
'webdriver')
command_line_args = []
if target_params is not None:
split_target_params = target_params.split(' ')
for param in split_target_params:
command_line_args.append(param)
command_line_args.append('--debug_console=off')
command_line_args.append('--null_savegame')
command_line_args.append('--url=' + url)
self.log_file = log_file
self.success_message = success_message
read_fd, write_fd = os.pipe()
self.launcher_read_pipe = os.fdopen(read_fd, 'r')
self.launcher_write_pipe = os.fdopen(write_fd, 'w')
self.launcher = abstract_launcher.LauncherFactory(
platform,
'cobalt',
config,
device_id=device_id,
target_params=command_line_args,
output_file=self.launcher_write_pipe,
out_directory=out_directory)
_launcher = self.launcher
def _HandleLine(self):
"""Reads log lines to determine when cobalt/webdriver server start."""
while True:
line = self.launcher_read_pipe.readline()
if line:
self.log_file.write(line)
else:
break
if RE_WINDOWDRIVER_CREATED.search(line):
_windowdriver_created.set()
continue
if RE_WEBMODULE_LOADED.search(line):
_webmodule_loaded.set()
continue
# Wait for WebDriver port here then connect
if self.test_script_started.is_set():
continue
match = RE_WEBDRIVER_LISTEN.search(line)
if not match:
continue
port = match.group(1)
print('WebDriver port opened:' + port + '\n', file=self.log_file)
self._StartWebdriver(port)
def __enter__(self):
self.runner_thread = threading.Thread(target=self.Run)
self.runner_thread.start()
self.reader_thread = threading.Thread(target=self._HandleLine)
# Make this thread daemonic so that it always exits
self.reader_thread.daemon = True
self.reader_thread.start()
try:
self.WaitForStart()
except KeyboardInterrupt:
# potentially from thread.interrupt_main(). We will treat as
# a timeout regardless
self.Exit(should_fail=True)
raise TimeoutException
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
# The unittest module terminates with a SystemExit
# If this is a successful exit, then this is a successful run
success = exc_type is None or (exc_type is SystemExit and
not exc_value.code)
self.Exit(should_fail=not success)
def Exit(self, should_fail=False):
if not self.should_exit.is_set():
self._SetShouldExit(failed=should_fail)
def _SetShouldExit(self, failed=False):
"""Indicates Cobalt process should exit."""
self.failed = failed
self.should_exit.set()
try:
self.launcher.Kill()
except Exception as e: # pylint: disable=broad-except
sys.stderr.write('Exception killing launcher:\n')
sys.stderr.write('{}\n'.format(str(e)))
self.runner_thread.join(COBALT_EXIT_TIMEOUT_SECONDS)
if self.runner_thread.isAlive():
sys.stderr.write('***Runner thread still alive***\n')
# Once the write end of the pipe has been closed by the launcher, the reader
# thread will get EOF and exit.
self.reader_thread.join(COBALT_EXIT_TIMEOUT_SECONDS)
if self.reader_thread.isAlive():
sys.stderr.write('***Reader thread still alive, exiting anyway***\n')
try:
self.launcher_read_pipe.close()
except IOError:
# Ignore error from closing the pipe during a blocking read
pass
def _StartWebdriver(self, port):
global _webdriver
host, webdriver_port = self.launcher.GetHostAndPortGivenPort(port)
url = 'http://{}:{}/'.format(host, webdriver_port)
self.webdriver = self.selenium_webdriver_module.Remote(
url, COBALT_WEBDRIVER_CAPABILITIES)
self.webdriver.command_executor.set_timeout(WEBDRIVER_HTTP_TIMEOUT_SECONDS)
print('Selenium Connected\n', file=self.log_file)
_webdriver = self.webdriver
self.test_script_started.set()
def WaitForStart(self):
"""Waits for the webdriver client to attach to Cobalt."""
startup_timeout_seconds = self.launcher.GetStartupTimeout()
if not startup_timeout_seconds:
startup_timeout_seconds = DEFAULT_STARTUP_TIMEOUT_SECONDS
if not self.test_script_started.wait(startup_timeout_seconds):
self.Exit(should_fail=True)
raise TimeoutException
print('Cobalt started', file=self.log_file)
def Run(self):
"""Thread run routine."""
try:
print('Running launcher', file=self.log_file)
self.launcher.Run()
print(
'Cobalt terminated. failed: ' + str(self.failed), file=self.log_file)
if not self.failed:
print('{}\n'.format(self.success_message))
# pylint: disable=broad-except
except Exception as ex:
sys.stderr.write('Exception running Cobalt ' + str(ex))
finally:
self.launcher_write_pipe.close()
if not self.should_exit.is_set():
# If the main thread is not expecting us to exit,
# we must interrupt it.
thread.interrupt_main()
return 0