From 780e21c970058ff32b00bffbaa457eb37fd3601f Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Thu, 19 Jul 2018 17:37:12 +0200 Subject: [PATCH 01/15] Fix Python errors in servodriver --- .../wptrunner/browsers/servodriver.py | 7 +++--- .../executors/executorservodriver.py | 25 +++++++++++++++++-- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/servodriver.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/servodriver.py index 168a576c25a..8173d4f468f 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/servodriver.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/servodriver.py @@ -38,6 +38,7 @@ def browser_kwargs(test_type, run_info_data, **kwargs): return { "binary": kwargs["binary"], "debug_info": kwargs["debug_info"], + "server_config": kwargs["config"], "user_stylesheets": kwargs.get("user_stylesheets"), } @@ -73,14 +74,14 @@ class ServoWebDriverBrowser(Browser): used_ports = set() def __init__(self, logger, binary, debug_info=None, webdriver_host="127.0.0.1", - user_stylesheets=None): + server_config=None, user_stylesheets=None): Browser.__init__(self, logger) self.binary = binary self.webdriver_host = webdriver_host self.webdriver_port = None self.proc = None self.debug_info = debug_info - self.hosts_path = write_hosts_file() + self.hosts_path = write_hosts_file(server_config) self.command = None self.user_stylesheets = user_stylesheets if user_stylesheets else [] @@ -151,7 +152,7 @@ class ServoWebDriverBrowser(Browser): def cleanup(self): self.stop() - shutil.rmtree(os.path.dirname(self.hosts_file)) + os.remove(self.hosts_path) def executor_browser(self): assert self.webdriver_port is not None diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservodriver.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservodriver.py index f649a75cd93..581d3d7683d 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservodriver.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservodriver.py @@ -11,8 +11,10 @@ from .base import (Protocol, TestharnessExecutor, strip_server) from ..testrunner import Stop +from ..webdriver_server import wait_for_service webdriver = None +ServoCommandExtensions = None here = os.path.join(os.path.split(__file__)[0]) @@ -23,6 +25,26 @@ def do_delayed_imports(): global webdriver import webdriver + global ServoCommandExtensions + class ServoCommandExtensions(object): + def __init__(self, session): + self.session = session + + @webdriver.client.command + def get_prefs(self, *prefs): + body = {"prefs": list(prefs)} + return self.session.send_command("POST", "servo/prefs/get", body) + + @webdriver.client.command + def set_prefs(self, prefs): + body = {"prefs": prefs} + return self.session.send_command("POST", "servo/prefs/set", body) + + @webdriver.client.command + def reset_prefs(self, *prefs): + body = {"prefs": list(prefs)} + return self.session.send_command("POST", "servo/prefs/reset", body) + class ServoWebDriverProtocol(Protocol): def __init__(self, executor, browser, capabilities, **kwargs): @@ -35,8 +57,7 @@ class ServoWebDriverProtocol(Protocol): def connect(self): """Connect to browser via WebDriver.""" - self.session = webdriver.Session(self.host, self.port, - extension=webdriver.servo.ServoCommandExtensions) + self.session = webdriver.Session(self.host, self.port, extension=ServoCommandExtensions) self.session.start() def after_connect(self): From 9b80369efbeab71d073e5dfc2c2f8e9af7607422 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Thu, 19 Jul 2018 17:37:27 +0200 Subject: [PATCH 02/15] Make servodriver wait until the server starts accepting TCP connections --- .../tools/wptrunner/wptrunner/executors/executorservodriver.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservodriver.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservodriver.py index 581d3d7683d..668f94a398c 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservodriver.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservodriver.py @@ -57,6 +57,9 @@ class ServoWebDriverProtocol(Protocol): def connect(self): """Connect to browser via WebDriver.""" + # Largish timeout for the case where we're booting an Android emulator. + wait_for_service((self.host, self.port), timeout=120) + self.session = webdriver.Session(self.host, self.port, extension=ServoCommandExtensions) self.session.start() From c05d30d116345f5a3f8a25c2aa197f91ebecd844 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Thu, 19 Jul 2018 16:56:53 +0200 Subject: [PATCH 03/15] Add --binary-arg support in ./mach test-wpt --product servodriver --- .../tools/wptrunner/wptrunner/browsers/servodriver.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/servodriver.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/servodriver.py index 8173d4f468f..3fc4eead7a3 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/servodriver.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/servodriver.py @@ -37,6 +37,7 @@ def check_args(**kwargs): def browser_kwargs(test_type, run_info_data, **kwargs): return { "binary": kwargs["binary"], + "binary_args": kwargs["binary_args"], "debug_info": kwargs["debug_info"], "server_config": kwargs["config"], "user_stylesheets": kwargs.get("user_stylesheets"), @@ -74,9 +75,10 @@ class ServoWebDriverBrowser(Browser): used_ports = set() def __init__(self, logger, binary, debug_info=None, webdriver_host="127.0.0.1", - server_config=None, user_stylesheets=None): + server_config=None, binary_args=None, user_stylesheets=None): Browser.__init__(self, logger) self.binary = binary + self.binary_args = binary_args or [] self.webdriver_host = webdriver_host self.webdriver_port = None self.proc = None @@ -95,7 +97,7 @@ class ServoWebDriverBrowser(Browser): debug_args, command = browser_command( self.binary, - [ + self.binary_args + [ "--hard-fail", "--webdriver", str(self.webdriver_port), "about:blank", From 4acdb8119790247bcf851752a31da3193293df81 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Thu, 19 Jul 2018 14:11:36 +0200 Subject: [PATCH 04/15] WebDriver timeout settings are not optional. Not setting one of them in a SetTimeouts requests should not remove it. This fixes a panicking `unwrap()`. --- components/webdriver_server/lib.rs | 47 +++++++++++++++--------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/components/webdriver_server/lib.rs b/components/webdriver_server/lib.rs index 150a3001c71..47390248dd3 100644 --- a/components/webdriver_server/lib.rs +++ b/components/webdriver_server/lib.rs @@ -107,14 +107,14 @@ struct WebDriverSession { /// Time to wait for injected scripts to run before interrupting them. A [`None`] value /// specifies that the script should run indefinitely. - script_timeout: Option, + script_timeout: u64, /// Time to wait for a page to finish loading upon navigation. - load_timeout: Option, + load_timeout: u64, /// Time to wait for the element location strategy when retrieving elements, and when /// waiting for an element to become interactable. - implicit_wait_timeout: Option, + implicit_wait_timeout: u64, } impl WebDriverSession { @@ -127,9 +127,9 @@ impl WebDriverSession { browsing_context_id: browsing_context_id, top_level_browsing_context_id: top_level_browsing_context_id, - script_timeout: Some(30_000), - load_timeout: Some(300_000), - implicit_wait_timeout: Some(0), + script_timeout: 30_000, + load_timeout: 300_000, + implicit_wait_timeout: 0, } } } @@ -140,7 +140,7 @@ struct Handler { resize_timeout: u32, } -#[derive(Clone, Copy, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] enum ServoExtensionRoute { GetPrefs, SetPrefs, @@ -171,7 +171,7 @@ impl WebDriverExtensionRoute for ServoExtensionRoute { } } -#[derive(Clone, PartialEq)] +#[derive(Clone, Debug, PartialEq)] enum ServoExtensionCommand { GetPrefs(GetPrefsParameters), SetPrefs(SetPrefsParameters), @@ -188,7 +188,7 @@ impl WebDriverExtensionCommand for ServoExtensionCommand { } } -#[derive(Clone, PartialEq)] +#[derive(Clone, Debug, PartialEq)] struct GetPrefsParameters { prefs: Vec } @@ -222,7 +222,7 @@ impl ToJson for GetPrefsParameters { } } -#[derive(Clone, PartialEq)] +#[derive(Clone, Debug, PartialEq)] struct SetPrefsParameters { prefs: Vec<(String, PrefValue)> } @@ -371,7 +371,7 @@ impl Handler { -> WebDriverResult { let timeout = self.session()?.load_timeout; thread::spawn(move || { - thread::sleep(Duration::from_millis(timeout.unwrap())); + thread::sleep(Duration::from_millis(timeout)); let _ = sender.send(LoadStatus::LoadTimeout); }); @@ -720,9 +720,15 @@ impl Handler { .as_mut() .ok_or(WebDriverError::new(ErrorStatus::SessionNotCreated, ""))?; - session.script_timeout = parameters.script; - session.load_timeout = parameters.page_load; - session.implicit_wait_timeout = parameters.implicit; + if let Some(timeout) = parameters.script { + session.script_timeout = timeout + } + if let Some(timeout) = parameters.page_load { + session.load_timeout = timeout + } + if let Some(timeout) = parameters.implicit { + session.implicit_wait_timeout = timeout + } Ok(WebDriverResponse::Void) } @@ -750,15 +756,10 @@ impl Handler { let func_body = ¶meters.script; let args_string = "window.webdriverCallback"; - let script = match self.session()?.script_timeout { - Some(timeout) => { - format!("setTimeout(webdriverTimeout, {}); (function(callback) {{ {} }})({})", - timeout, - func_body, - args_string) - } - None => format!("(function(callback) {{ {} }})({})", func_body, args_string), - }; + let script = format!("setTimeout(webdriverTimeout, {}); (function(callback) {{ {} }})({})", + self.session()?.script_timeout, + func_body, + args_string); let (sender, receiver) = ipc::channel().unwrap(); let command = WebDriverScriptCommand::ExecuteAsyncScript(script, sender); From 48f6e168d44d01ddcd01861a1eedf5487065a9b6 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Thu, 19 Jul 2018 16:32:47 +0200 Subject: [PATCH 05/15] Fix a webdriver timeout during server start up When sending a webdriver load URL command soon enough after starting Servo, that command could time out with a logged warning: ``` constellation: Webdriver load for closed browsing context (0,2). ``` When `closed` in this case really meant not opened yet. --- components/constellation/constellation.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index c2b14985f3a..e577307cd1b 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -931,7 +931,12 @@ impl Constellation FromCompositorMsg::GetFocusTopLevelBrowsingContext(resp_chan) => { let focus_browsing_context = self.focus_pipeline_id .and_then(|pipeline_id| self.pipelines.get(&pipeline_id)) - .map(|pipeline| pipeline.top_level_browsing_context_id); + .map(|pipeline| pipeline.top_level_browsing_context_id) + .filter(|&top_level_browsing_context_id| { + let browsing_context_id = + BrowsingContextId::from(top_level_browsing_context_id); + self.browsing_contexts.contains_key(&browsing_context_id) + }); let _ = resp_chan.send(focus_browsing_context); } FromCompositorMsg::KeyEvent(ch, key, state, modifiers) => { From f5ff709c793b490570510174cd97461bef3b4880 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Thu, 19 Jul 2018 18:44:47 +0200 Subject: [PATCH 06/15] Refactor run_in_headless_android_emulator.py into more functions --- etc/run_in_headless_android_emulator.py | 57 ++++++++++++++----------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/etc/run_in_headless_android_emulator.py b/etc/run_in_headless_android_emulator.py index 45e088e0389..7ee300589d9 100755 --- a/etc/run_in_headless_android_emulator.py +++ b/etc/run_in_headless_android_emulator.py @@ -48,28 +48,10 @@ def main(avd_name, apk_path, *args): # Now `adb shell` will work, but `adb install` needs a system service # that might still be in the midle of starting and not be responsive yet. - - # https://stackoverflow.com/a/38896494/1162888 - while 1: - with terminate_on_exit( - adb + ["shell", "getprop", "sys.boot_completed"], - stdout=subprocess.PIPE, - ) as getprop: - stdout, stderr = getprop.communicate() - if "1" in stdout: - break - time.sleep(1) + wait_for_boot(adb) check_call(adb + ["install", "-r", apk_path]) - - data_dir = "/sdcard/Android/data/com.mozilla.servo/files" - params_file = data_dir + "/android_params" - - check_call(adb + ["shell", "mkdir -p %s" % data_dir]) - check_call(adb + ["shell", "echo 'servo' > %s" % params_file]) - for arg in args: - check_call(adb + ["shell", "echo %s >> %s" % (shell_quote(arg), params_file)]) - + write_args(adb, args) check_call(adb + ["shell", "am start com.mozilla.servo/com.mozilla.servo.MainActivity"], stdout=sys.stderr) @@ -103,11 +85,38 @@ def terminate_on_exit(*args, **kwargs): process.terminate() -def check_call(*args, **kwargs): +# https://stackoverflow.com/a/38896494/1162888 +def wait_for_boot(adb): + while 1: + with terminate_on_exit( + adb + ["shell", "getprop", "sys.boot_completed"], + stdout=subprocess.PIPE, + ) as getprop: + stdout, stderr = getprop.communicate() + if "1" in stdout: + return + time.sleep(1) + + +def call(*args, **kwargs): with terminate_on_exit(*args, **kwargs) as process: - exit_code = process.wait() - if exit_code != 0: - sys.exit(exit_code) + return process.wait() + + +def check_call(*args, **kwargs): + exit_code = call(*args, **kwargs) + if exit_code != 0: + sys.exit(exit_code) + + +def write_args(adb, args): + data_dir = "/sdcard/Android/data/com.mozilla.servo/files" + params_file = data_dir + "/android_params" + + check_call(adb + ["shell", "mkdir -p %s" % data_dir]) + check_call(adb + ["shell", "echo 'servo' > %s" % params_file]) + for arg in args: + check_call(adb + ["shell", "echo %s >> %s" % (shell_quote(arg), params_file)]) # Copied from Python 3.3+'s shlex.quote() From 33234affc902d95b18d13a2306afa735e10e0e48 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Thu, 19 Jul 2018 18:50:46 +0200 Subject: [PATCH 07/15] run_in_headless_android_emulator: add port forwarding for webdriver --- etc/run_in_headless_android_emulator.py | 23 +++++++++++++++++++ .../executors/executorservodriver.py | 4 ++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/etc/run_in_headless_android_emulator.py b/etc/run_in_headless_android_emulator.py index 7ee300589d9..d1808f1a197 100755 --- a/etc/run_in_headless_android_emulator.py +++ b/etc/run_in_headless_android_emulator.py @@ -57,6 +57,7 @@ def main(avd_name, apk_path, *args): logcat_args = ["RustAndroidGlueStdouterr:D", "*:S", "-v", "raw"] with terminate_on_exit(adb + ["logcat"] + logcat_args) as logcat: + forward_webdriver(adb, args) logcat.wait() @@ -119,6 +120,28 @@ def write_args(adb, args): check_call(adb + ["shell", "echo %s >> %s" % (shell_quote(arg), params_file)]) +def forward_webdriver(adb, args): + webdriver_port = extract_arg("--webdriver", args) + if webdriver_port is not None: + wait_for_tcp_server(adb, webdriver_port) + port = "tcp:%s" % webdriver_port + check_call(adb + ["forward", port, port]) + sys.stderr.write("Forwarding WebDriver port %s to the emulator\n" % webdriver_port) + + +def extract_arg(name, args): + previous_arg_matches = False + for arg in args: + if previous_arg_matches: + return arg + previous_arg_matches = arg == name + + +def wait_for_tcp_server(adb, port): + while call(adb + ["shell", "nc -z 127.0.0.1 %s" % port]) != 0: + time.sleep(1) + + # Copied from Python 3.3+'s shlex.quote() def shell_quote(arg): # use single quotes, and put single quotes into double quotes diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservodriver.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservodriver.py index 668f94a398c..1dc5be4bb46 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservodriver.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservodriver.py @@ -57,8 +57,8 @@ class ServoWebDriverProtocol(Protocol): def connect(self): """Connect to browser via WebDriver.""" - # Largish timeout for the case where we're booting an Android emulator. - wait_for_service((self.host, self.port), timeout=120) + # Large timeout for the case where we're booting an Android emulator. + wait_for_service((self.host, self.port), timeout=300) self.session = webdriver.Session(self.host, self.port, extension=ServoCommandExtensions) self.session.start() From d7495a22970c3a307879df559d9360fbf2d104e0 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Thu, 19 Jul 2018 19:01:21 +0200 Subject: [PATCH 08/15] run_in_headless_android_emulator: add support for user stylesheets --- etc/run_in_headless_android_emulator.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/etc/run_in_headless_android_emulator.py b/etc/run_in_headless_android_emulator.py index d1808f1a197..31421c0c702 100755 --- a/etc/run_in_headless_android_emulator.py +++ b/etc/run_in_headless_android_emulator.py @@ -51,6 +51,8 @@ def main(avd_name, apk_path, *args): wait_for_boot(adb) check_call(adb + ["install", "-r", apk_path]) + args = list(args) + write_user_stylesheets(adb, args) write_args(adb, args) check_call(adb + ["shell", "am start com.mozilla.servo/com.mozilla.servo.MainActivity"], stdout=sys.stderr) @@ -120,6 +122,15 @@ def write_args(adb, args): check_call(adb + ["shell", "echo %s >> %s" % (shell_quote(arg), params_file)]) +def write_user_stylesheets(adb, args): + data_dir = "/sdcard/Android/data/com.mozilla.servo/files" + check_call(adb + ["shell", "mkdir -p %s" % data_dir]) + for i, (pos, path) in enumerate(extract_args("--user-stylesheet", args)): + remote_path = "%s/user%s.css" % (data_dir, i) + args[pos] = remote_path + check_call(adb + ["push", path, remote_path], stdout=sys.stderr) + + def forward_webdriver(adb, args): webdriver_port = extract_arg("--webdriver", args) if webdriver_port is not None: @@ -130,15 +141,20 @@ def forward_webdriver(adb, args): def extract_arg(name, args): + for _, arg in extract_args(name, args): + return arg + + +def extract_args(name, args): previous_arg_matches = False - for arg in args: + for i, arg in enumerate(args): if previous_arg_matches: - return arg + yield i, arg previous_arg_matches = arg == name def wait_for_tcp_server(adb, port): - while call(adb + ["shell", "nc -z 127.0.0.1 %s" % port]) != 0: + while call(adb + ["shell", "nc -z 127.0.0.1 %s" % port], stdout=sys.stderr) != 0: time.sleep(1) From 94d1acbcfd7939f8ed62f62fb7e7cf7cd3ee4a35 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Thu, 19 Jul 2018 19:16:07 +0200 Subject: [PATCH 09/15] run_in_headless_android_emulator: add support for reverse port forwarding --- etc/run_in_headless_android_emulator.py | 8 ++++++++ .../tools/wptrunner/wptrunner/browsers/servodriver.py | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/etc/run_in_headless_android_emulator.py b/etc/run_in_headless_android_emulator.py index 31421c0c702..1b7c63835ef 100755 --- a/etc/run_in_headless_android_emulator.py +++ b/etc/run_in_headless_android_emulator.py @@ -139,6 +139,14 @@ def forward_webdriver(adb, args): check_call(adb + ["forward", port, port]) sys.stderr.write("Forwarding WebDriver port %s to the emulator\n" % webdriver_port) + split = os.environ.get("EMULATOR_REVERSE_FORWARD_PORTS", "").split(",") + ports = [int(part) for part in split if part] + for port in ports: + port = "tcp:%s" % port + check_call(adb + ["reverse", port, port]) + if ports: + sys.stderr.write("Reverse-forwarding ports %s\n" % ", ".join(map(str, ports))) + def extract_arg(name, args): for _, arg in extract_args(name, args): diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/servodriver.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/servodriver.py index 3fc4eead7a3..74874fd6d2d 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/servodriver.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/servodriver.py @@ -84,6 +84,7 @@ class ServoWebDriverBrowser(Browser): self.proc = None self.debug_info = debug_info self.hosts_path = write_hosts_file(server_config) + self.server_ports = server_config.ports if server_config else {} self.command = None self.user_stylesheets = user_stylesheets if user_stylesheets else [] @@ -94,6 +95,11 @@ class ServoWebDriverBrowser(Browser): env = os.environ.copy() env["HOST_FILE"] = self.hosts_path env["RUST_BACKTRACE"] = "1" + env["EMULATOR_REVERSE_FORWARD_PORTS"] = ",".join( + str(port) + for _protocol, ports in self.server_ports.items() + for port in ports + ) debug_args, command = browser_command( self.binary, From e6eca2bce4e727b896c5cf30849a2e186cacdde5 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Thu, 19 Jul 2018 20:12:58 +0200 Subject: [PATCH 10/15] run_in_headless_android_emulator: add some code comments --- etc/run_in_headless_android_emulator.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/etc/run_in_headless_android_emulator.py b/etc/run_in_headless_android_emulator.py index 1b7c63835ef..32f8e18146f 100755 --- a/etc/run_in_headless_android_emulator.py +++ b/etc/run_in_headless_android_emulator.py @@ -50,16 +50,28 @@ def main(avd_name, apk_path, *args): # that might still be in the midle of starting and not be responsive yet. wait_for_boot(adb) + # These steps should happen before application start check_call(adb + ["install", "-r", apk_path]) args = list(args) write_user_stylesheets(adb, args) write_args(adb, args) + check_call(adb + ["shell", "am start com.mozilla.servo/com.mozilla.servo.MainActivity"], stdout=sys.stderr) - logcat_args = ["RustAndroidGlueStdouterr:D", "*:S", "-v", "raw"] + # Start showing logs as soon as the application starts, + # in case they say something useful while we wait in subsequent steps. + logcat_args = [ + "--format=raw", # Print no metadata, only log messages + "RustAndroidGlueStdouterr:D", # Show (debug level) Rust stdio + "*:S", # Hide everything else + ] with terminate_on_exit(adb + ["logcat"] + logcat_args) as logcat: + + # This step needs to happen after application start forward_webdriver(adb, args) + + # logcat normally won't exit on its own, wait until we get a SIGTERM signal. logcat.wait() @@ -134,7 +146,15 @@ def write_user_stylesheets(adb, args): def forward_webdriver(adb, args): webdriver_port = extract_arg("--webdriver", args) if webdriver_port is not None: + # `adb forward` will start accepting TCP connections even if the other side does not. + # (If the remote side refuses the connection, + # adb will close the local side after accepting it.) + # This is incompatible with wptrunner which relies on TCP connection acceptance + # to figure out when it can start sending WebDriver requests. + # + # So wait until the remote side starts listening before setting up the forwarding. wait_for_tcp_server(adb, webdriver_port) + port = "tcp:%s" % webdriver_port check_call(adb + ["forward", port, port]) sys.stderr.write("Forwarding WebDriver port %s to the emulator\n" % webdriver_port) From f10b47465ae9e72c2cd40f3173cb82a396debe33 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Thu, 19 Jul 2018 20:50:28 +0200 Subject: [PATCH 11/15] More Python fixes for servodriver ``` Traceback (most recent call last): File "/home/simon/servo1/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/testrunner.py", line 90, in run rv = commands[command](*args) File "/home/simon/servo1/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/testrunner.py", line 110, in wait self.executor.wait() File "/home/simon/servo1/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/base.py", line 209, in wait self.protocol.base.wait() AttributeError: 'ServoWebDriverProtocol' object has no attribute 'base' ``` --- .../wptrunner/executors/executorservodriver.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservodriver.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservodriver.py index 1dc5be4bb46..ad16ac78f7b 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservodriver.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservodriver.py @@ -6,6 +6,7 @@ import time import traceback from .base import (Protocol, + BaseProtocolPart, RefTestExecutor, RefTestImplementation, TestharnessExecutor, @@ -46,7 +47,23 @@ def do_delayed_imports(): return self.session.send_command("POST", "servo/prefs/reset", body) +class ServoBaseProtocolPart(BaseProtocolPart): + def execute_script(self, script, async=False): + pass + + def set_timeout(self, timeout): + pass + + def wait(self): + pass + + def set_window(self, handle): + pass + + class ServoWebDriverProtocol(Protocol): + implements = [ServoBaseProtocolPart] + def __init__(self, executor, browser, capabilities, **kwargs): do_delayed_imports() Protocol.__init__(self, executor, browser) From cdd64604cf0c3f837f2b20faa786d6d2595c22d3 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Thu, 19 Jul 2018 20:50:42 +0200 Subject: [PATCH 12/15] =?UTF-8?q?Don=E2=80=99t=20reverse-forward=20port=20?= =?UTF-8?q?"None"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ``` 0:27.48 pid:4157 Traceback (most recent call last): 0:27.48 pid:4157 File "/home/simon/servo1/etc/run_in_headless_android_emulator.py", line 212, in 0:27.48 pid:4157 sys.exit(main(*sys.argv[1:])) 0:27.48 pid:4157 File "/home/simon/servo1/etc/run_in_headless_android_emulator.py", line 72, in main 0:27.48 pid:4157 forward_webdriver(adb, args) 0:27.48 pid:4157 File "/home/simon/servo1/etc/run_in_headless_android_emulator.py", line 163, in forward_webdriver 0:27.48 pid:4157 ports = [int(part) for part in split if part] 0:27.48 pid:4157 ValueError: invalid literal for int() with base 10: 'None' ``` --- .../tools/wptrunner/wptrunner/browsers/servodriver.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/servodriver.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/servodriver.py index 74874fd6d2d..fe392e06651 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/servodriver.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/servodriver.py @@ -99,6 +99,7 @@ class ServoWebDriverBrowser(Browser): str(port) for _protocol, ports in self.server_ports.items() for port in ports + if port ) debug_args, command = browser_command( From 230a6da5ff59cebc0dca96afa4112e55248a78c5 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Fri, 20 Jul 2018 15:58:41 +0200 Subject: [PATCH 13/15] servodriver: fix setting preferences This makes `/_mozilla/mozilla/webgl/context_creation_error.html` pass, for example. ``` ./ mach test-wpt --product servodriver \ --binary etc/run_in_headless_android_emulator.py \ --binary-arg servo-x86 \ --binary-arg target/i686-linux-android/release/servo.apk \ /_mozilla/mozilla/webgl/context_creation_error.html ``` --- .../executors/executorservodriver.py | 41 +++++++++++++++---- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservodriver.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservodriver.py index ad16ac78f7b..47f3c39499a 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservodriver.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservodriver.py @@ -34,17 +34,35 @@ def do_delayed_imports(): @webdriver.client.command def get_prefs(self, *prefs): body = {"prefs": list(prefs)} - return self.session.send_command("POST", "servo/prefs/get", body) + return self.session.send_session_command("POST", "servo/prefs/get", body) @webdriver.client.command def set_prefs(self, prefs): body = {"prefs": prefs} - return self.session.send_command("POST", "servo/prefs/set", body) + return self.session.send_session_command("POST", "servo/prefs/set", body) @webdriver.client.command def reset_prefs(self, *prefs): body = {"prefs": list(prefs)} - return self.session.send_command("POST", "servo/prefs/reset", body) + return self.session.send_session_command("POST", "servo/prefs/reset", body) + + def change_prefs(self, old_prefs, new_prefs): + # Servo interprets reset with an empty list as reset everything + if old_prefs: + self.reset_prefs(*old_prefs.keys()) + self.set_prefs({k: parse_pref_value(v) for k, v in new_prefs.items()}) + + +# See parse_pref_from_command_line() in components/config/opts.rs +def parse_pref_value(value): + if value == "true": + return True + if value == "false": + return False + try: + return float(value) + except ValueError: + return value class ServoBaseProtocolPart(BaseProtocolPart): @@ -111,11 +129,6 @@ class ServoWebDriverProtocol(Protocol): self.logger.error(traceback.format_exc(e)) break - def on_environment_change(self, old_environment, new_environment): - #Unset all the old prefs - self.session.extension.reset_prefs(*old_environment.get("prefs", {}).keys()) - self.session.extension.set_prefs(new_environment.get("prefs", {})) - class ServoWebDriverRun(object): def __init__(self, func, session, url, timeout, current_timeout=None): @@ -215,6 +228,12 @@ class ServoWebDriverTestharnessExecutor(TestharnessExecutor): session.back() return result + def on_environment_change(self, new_environment): + self.protocol.session.extension.change_prefs( + self.last_environment.get("prefs", {}), + new_environment.get("prefs", {}) + ) + class TimeoutError(Exception): pass @@ -281,3 +300,9 @@ class ServoWebDriverRefTestExecutor(RefTestExecutor): session.url = url session.execute_async_script(self.wait_script) return session.screenshot() + + def on_environment_change(self, new_environment): + self.protocol.session.extension.change_prefs( + self.last_environment.get("prefs", {}), + new_environment.get("prefs", {}) + ) From 45b710b7f0ceef09e26f968fed87bc5af1845063 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Fri, 20 Jul 2018 17:11:02 +0200 Subject: [PATCH 14/15] Add ./mach test-wpt-android --- python/servo/testing_commands.py | 52 ++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/python/servo/testing_commands.py b/python/servo/testing_commands.py index f4092e702be..b0ef2cfa5ce 100644 --- a/python/servo/testing_commands.py +++ b/python/servo/testing_commands.py @@ -367,6 +367,21 @@ class MachCommands(CommandBase): else: return ret + @Command('test-wpt-android', + description='Run the web platform test suite in an Android emulator', + category='testing', + parser=create_parser_wpt) + def test_wpt_android(self, release=False, dev=False, binary_args=None, **kwargs): + kwargs.update( + release=release, + dev=dev, + product="servodriver", + processes=1, + binary_args=self.in_android_emulator(release, dev) + (binary_args or []), + binary=sys.executable, + ) + return self._test_wpt(**kwargs) + def _test_wpt(self, **kwargs): hosts_file_path = path.join(self.context.topdir, 'tests', 'wpt', 'hosts') os.environ["hosts_file_path"] = hosts_file_path @@ -559,28 +574,15 @@ class MachCommands(CommandBase): @CommandArgument('--dev', '-d', action='store_true', help='Run the dev build') def test_android_startup(self, release, dev): - if (release and dev) or not (release or dev): - print("Please specify one of --dev or --release.") - return 1 - - avd = "servo-x86" - target = "i686-linux-android" - print("Assuming --target " + target) - - env = self.build_env(target=target) - assert self.handle_android_target(target) - binary_path = self.get_binary_path(release, dev, android=True) - apk = binary_path + ".apk" - html = """ """ url = "data:text/html;base64," + html.encode("base64").replace("\n", "") - py = path.join(self.context.topdir, "etc", "run_in_headless_android_emulator.py") - args = [sys.executable, py, avd, apk, url] - process = subprocess.Popen(args, stdout=subprocess.PIPE, env=env) + args = self.in_android_emulator(release, dev) + args = [sys.executable] + args + [url] + process = subprocess.Popen(args, stdout=subprocess.PIPE) try: while 1: line = process.stdout.readline() @@ -593,6 +595,24 @@ class MachCommands(CommandBase): finally: process.terminate() + def in_android_emulator(self, release, dev): + if (release and dev) or not (release or dev): + print("Please specify one of --dev or --release.") + sys.exit(1) + + avd = "servo-x86" + target = "i686-linux-android" + print("Assuming --target " + target) + + env = self.build_env(target=target) + os.environ["PATH"] = env["PATH"] + assert self.handle_android_target(target) + binary_path = self.get_binary_path(release, dev, android=True) + apk = binary_path + ".apk" + + py = path.join(self.context.topdir, "etc", "run_in_headless_android_emulator.py") + return [py, avd, apk] + @Command('test-jquery', description='Run the jQuery test suite', category='testing') From 2b5bba6f12481acf315b6c55f3ac129dce373bd6 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Fri, 20 Jul 2018 18:13:59 +0200 Subject: [PATCH 15/15] =?UTF-8?q?servodriver:=20increase=20browser?= =?UTF-8?q?=E2=80=99s=20init=5Ftimeout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tools/wptrunner/wptrunner/browsers/servodriver.py | 4 +++- .../wptrunner/wptrunner/executors/executorservodriver.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/servodriver.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/servodriver.py index fe392e06651..4111d8b48a8 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/servodriver.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/servodriver.py @@ -73,6 +73,7 @@ def write_hosts_file(config): class ServoWebDriverBrowser(Browser): used_ports = set() + init_timeout = 300 # Large timeout for cases where we're booting an Android emulator def __init__(self, logger, binary, debug_info=None, webdriver_host="127.0.0.1", server_config=None, binary_args=None, user_stylesheets=None): @@ -166,4 +167,5 @@ class ServoWebDriverBrowser(Browser): def executor_browser(self): assert self.webdriver_port is not None return ExecutorBrowser, {"webdriver_host": self.webdriver_host, - "webdriver_port": self.webdriver_port} + "webdriver_port": self.webdriver_port, + "init_timeout": self.init_timeout} diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservodriver.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservodriver.py index 47f3c39499a..896aeb39eff 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservodriver.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorservodriver.py @@ -88,12 +88,12 @@ class ServoWebDriverProtocol(Protocol): self.capabilities = capabilities self.host = browser.webdriver_host self.port = browser.webdriver_port + self.init_timeout = browser.init_timeout self.session = None def connect(self): """Connect to browser via WebDriver.""" - # Large timeout for the case where we're booting an Android emulator. - wait_for_service((self.host, self.port), timeout=300) + wait_for_service((self.host, self.port), timeout=self.init_timeout) self.session = webdriver.Session(self.host, self.port, extension=ServoCommandExtensions) self.session.start()