aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Robinson <mrobinson@igalia.com>2025-05-02 18:06:26 +0200
committerGitHub <noreply@github.com>2025-05-02 16:06:26 +0000
commit37c680dae4f0401825f57a59b59747dd2787c9ff (patch)
tree3bb9a33f6b97954871cb09bd81abf40349167a12
parent9bc16482a391bfa2afa040071c9d943a1c84f426 (diff)
downloadservo-37c680dae4f0401825f57a59b59747dd2787c9ff.tar.gz
servo-37c680dae4f0401825f57a59b59747dd2787c9ff.zip
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>
-rw-r--r--components/servo/Cargo.toml9
-rw-r--r--components/servo/tests/common/mod.rs114
-rw-r--r--components/servo/tests/servo.rs24
-rw-r--r--components/servo/tests/webview.rs49
4 files changed, 126 insertions, 70 deletions
diff --git a/components/servo/Cargo.toml b/components/servo/Cargo.toml
index b49f60e742a..fa64638fa4c 100644
--- a/components/servo/Cargo.toml
+++ b/components/servo/Cargo.toml
@@ -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
diff --git a/components/servo/tests/common/mod.rs b/components/servo/tests/common/mod.rs
index 8c00826a0d8..de71361e9be 100644
--- a/components/servo/tests/common/mod.rs
+++ b/components/servo/tests/common/mod.rs
@@ -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(())
}
}
diff --git a/components/servo/tests/servo.rs b/components/servo/tests/servo.rs
index 2f71e909bee..6333b0af50a 100644
--- a/components/servo/tests/servo.rs
+++ b/components/servo/tests/servo.rs
@@ -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);
}
diff --git a/components/servo/tests/webview.rs b/components/servo/tests/webview.rs
new file mode 100644
index 00000000000..4ed06e412da
--- /dev/null
+++ b/components/servo/tests/webview.rs
@@ -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);
+}