libservo: Add a basic WebView API test (#36791)

This should allow us to start unit testing the `WebView` API.

Testing: This is a test.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2025-05-02 18:06:26 +02:00 committed by GitHub
parent 9bc16482a3
commit 37c680dae4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 126 additions and 70 deletions

View file

@ -141,3 +141,12 @@ libservo = { path = ".", features = ["tracing"] }
rustls = { version = "0.23", default-features = false, features = ["aws-lc-rs"] }
tracing = { workspace = true }
winit = "0.30.8"
[[test]]
name = "webview"
harness = false
[[test]]
name = "servo"
harness = false

View file

@ -3,18 +3,52 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::rc::Rc;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, OnceLock};
use std::time::Duration;
use anyhow::Error;
use compositing_traits::rendering_context::{RenderingContext, SoftwareRenderingContext};
use crossbeam_channel::{Receiver, Sender, unbounded};
use dpi::PhysicalSize;
use embedder_traits::EventLoopWaker;
use parking_lot::Mutex;
use servo::{Servo, ServoBuilder};
macro_rules! run_api_tests {
($($test_function:ident), +) => {
let mut failed = false;
// Be sure that `servo_test` is dropped before exiting early.
{
let servo_test = ServoTest::new();
$(
common::run_test($test_function, stringify!($test_function), &servo_test, &mut failed);
)+
}
if failed {
std::process::exit(1);
}
}
}
pub(crate) use run_api_tests;
pub(crate) fn run_test(
test_function: fn(&ServoTest) -> Result<(), Error>,
test_name: &str,
servo_test: &ServoTest,
failed: &mut bool,
) {
match test_function(servo_test) {
Ok(_) => println!("{test_name}"),
Err(error) => {
*failed = true;
println!("{test_name}");
println!("{}", format!("\n{error:?}").replace("\n", "\n "));
},
}
}
pub struct ServoTest {
servo: Servo,
}
@ -30,7 +64,7 @@ impl Drop for ServoTest {
}
impl ServoTest {
fn new() -> Self {
pub(crate) fn new() -> Self {
let rendering_context = Rc::new(
SoftwareRenderingContext::new(PhysicalSize {
width: 500,
@ -63,58 +97,28 @@ impl ServoTest {
&self.servo
}
/// Run a Servo test. All tests are run in a `ServoTestThread` and serially. Currently
/// Servo does not support launching concurrent instances, in order to ensure
/// isolation and allow for more than a single test per instance.
pub fn run(
test_function: impl FnOnce(&ServoTest) -> Result<(), anyhow::Error> + Send + Sync + 'static,
) {
static SERVO_TEST_THREAD: Mutex<OnceLock<ServoTestThread>> = Mutex::new(OnceLock::new());
let test_thread = SERVO_TEST_THREAD.lock();
test_thread
.get_or_init(ServoTestThread::new)
.run_test(Box::new(test_function));
}
}
type TestFunction =
Box<dyn FnOnce(&ServoTest) -> Result<(), anyhow::Error> + Send + Sync + 'static>;
struct ServoTestThread {
test_function_sender: Sender<TestFunction>,
result_receiver: Receiver<Result<(), Error>>,
}
impl ServoTestThread {
fn new() -> Self {
let (result_sender, result_receiver) = unbounded();
let (test_function_sender, test_function_receiver) = unbounded();
// Defined here rather than at the end of this method in order to take advantage
// of Rust type inference.
let thread = Self {
test_function_sender,
result_receiver,
};
let _ = std::thread::spawn(move || {
let servo_test = ServoTest::new();
while let Ok(incoming_test_function) = test_function_receiver.recv() {
let _ = result_sender.send(incoming_test_function(&servo_test));
/// Spin the Servo event loop until one of:
/// - The given callback returns `Ok(false)`.
/// - The given callback returns an `Error`, in which case the `Error` will be returned.
/// - Servo has indicated that shut down is complete and we cannot spin the event loop
/// any longer.
// The dead code exception here is because not all test suites that use `common` also
// use `spin()`.
#[allow(dead_code)]
pub fn spin(&self, callback: impl Fn() -> Result<bool, Error> + 'static) -> Result<(), Error> {
let mut keep_going = true;
while keep_going {
std::thread::sleep(Duration::from_millis(1));
if !self.servo.spin_event_loop() {
return Ok(());
}
let result = callback();
match result {
Ok(result) => keep_going = result,
Err(error) => return Err(error),
}
});
thread
}
fn run_test(&self, test_function: TestFunction) {
let _ = self.test_function_sender.send(Box::new(test_function));
let result = self
.result_receiver
.recv()
.expect("Servo test thread should always return a result.");
if let Err(result) = result {
unreachable!("{result}");
}
Ok(())
}
}

View file

@ -4,7 +4,7 @@
//! Servo API unit tests.
//!
//! Since all Servo tests must rust serially on the same thread, it is important
//! Since all Servo tests must run serially on the same thread, it is important
//! that tests never panic. In order to ensure this, use `anyhow::ensure!` instead
//! of `assert!` for test assertions. `ensure!` will produce a `Result::Err` in
//! place of panicking.
@ -12,21 +12,15 @@
mod common;
use anyhow::ensure;
use common::*;
use servo::WebViewBuilder;
use common::{ServoTest, run_api_tests};
#[test]
fn test_simple_servo_is_not_animating_by_default() {
ServoTest::run(|servo_test| {
ensure!(!servo_test.servo().animating());
Ok(())
});
fn test_simple_servo_is_not_animating_by_default(
servo_test: &ServoTest,
) -> Result<(), anyhow::Error> {
ensure!(!servo_test.servo().animating());
Ok(())
}
#[test]
fn test_simple_servo_construct_webview() {
ServoTest::run(|servo_test| {
WebViewBuilder::new(servo_test.servo()).build();
Ok(())
});
fn main() {
run_api_tests!(test_simple_servo_is_not_animating_by_default);
}

View file

@ -0,0 +1,49 @@
/* 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 https://mozilla.org/MPL/2.0/. */
//! WebView API unit tests.
//!
//! Since all Servo tests must run serially on the same thread, it is important
//! that tests never panic. In order to ensure this, use `anyhow::ensure!` instead
//! of `assert!` for test assertions. `ensure!` will produce a `Result::Err` in
//! place of panicking.
mod common;
use std::cell::Cell;
use std::rc::Rc;
use anyhow::ensure;
use common::{ServoTest, run_api_tests};
use servo::{WebViewBuilder, WebViewDelegate};
#[derive(Default)]
struct WebViewDelegateImpl {
url_changed: Cell<bool>,
}
impl WebViewDelegate for WebViewDelegateImpl {
fn notify_url_changed(&self, _webview: servo::WebView, _url: url::Url) {
self.url_changed.set(true);
}
}
fn test_create_webview(servo_test: &ServoTest) -> Result<(), anyhow::Error> {
let delegate = Rc::new(WebViewDelegateImpl::default());
let webview = WebViewBuilder::new(servo_test.servo())
.delegate(delegate.clone())
.build();
servo_test.spin(move || Ok(!delegate.url_changed.get()))?;
let url = webview.url();
ensure!(url.is_some());
ensure!(url.unwrap().to_string() == "about:blank");
Ok(())
}
fn main() {
run_api_tests!(test_create_webview);
}