Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add some support for WPT tests in an Android emulator through WebDriver #21213

Merged
merged 15 commits into from Jul 21, 2018
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

@@ -931,7 +931,12 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
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) => {
@@ -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<u64>,
script_timeout: u64,

/// Time to wait for a page to finish loading upon navigation.
load_timeout: Option<u64>,
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<u64>,
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<String>
}
@@ -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<WebDriverResponse> {
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 = &parameters.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);
@@ -48,33 +48,30 @@ 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.
wait_for_boot(adb)

# 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)

# These steps should happen before application start
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)])
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()


@@ -103,11 +100,90 @@ 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)])


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:
# `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)

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):
return arg


def extract_args(name, args):
previous_arg_matches = False
for i, arg in enumerate(args):
if previous_arg_matches:
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], stdout=sys.stderr) != 0:
time.sleep(1)


# Copied from Python 3.3+'s shlex.quote()
@@ -367,6 +367,21 @@ def test_wpt(self, **kwargs):
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 @@ def filter_intermittents(self, summary, log_filteredsummary, log_intermittents,
@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 = """
<script>
console.log("JavaScript is running!")
</script>
"""
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 @@ def test_android_startup(self, release, dev):
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')
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.