diff options
author | Martin Robinson <mrobinson@igalia.com> | 2025-05-02 18:06:26 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-05-02 16:06:26 +0000 |
commit | 37c680dae4f0401825f57a59b59747dd2787c9ff (patch) | |
tree | 3bb9a33f6b97954871cb09bd81abf40349167a12 | |
parent | 9bc16482a391bfa2afa040071c9d943a1c84f426 (diff) | |
download | servo-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.toml | 9 | ||||
-rw-r--r-- | components/servo/tests/common/mod.rs | 114 | ||||
-rw-r--r-- | components/servo/tests/servo.rs | 24 | ||||
-rw-r--r-- | components/servo/tests/webview.rs | 49 |
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); +} |