diff options
author | Martin Robinson <mrobinson@igalia.com> | 2025-04-16 09:54:54 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-04-16 07:54:54 +0000 |
commit | 6bad65a5a16c5a90b09fe8d0441c1d473e46e060 (patch) | |
tree | dad9b6631fd4b93f570191f96630bcb2fe33f7d4 | |
parent | 9aa09d73b5d82406b0d922a30f30b65ffcf9eb96 (diff) | |
download | servo-6bad65a5a16c5a90b09fe8d0441c1d473e46e060.tar.gz servo-6bad65a5a16c5a90b09fe8d0441c1d473e46e060.zip |
libservo: Allow running more than one Servo test in a run (#36532)
A `Servo` instance can only be constructed once per program execution
and cannot be passed between threads. This change adds a special thread
to run `Servo` unit tests. This will allow creating suites of `WebView`
unit tests.
Testing: This change includes a new test.
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
-rw-r--r-- | Cargo.lock | 1 | ||||
-rw-r--r-- | components/servo/Cargo.toml | 1 | ||||
-rw-r--r-- | components/servo/tests/common/mod.rs | 75 | ||||
-rw-r--r-- | components/servo/tests/servo.rs | 25 |
4 files changed, 90 insertions, 12 deletions
diff --git a/Cargo.lock b/Cargo.lock index fefafec532a..7efa4ea8b72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4306,6 +4306,7 @@ dependencies = [ name = "libservo" version = "0.0.1" dependencies = [ + "anyhow", "arboard", "background_hang_monitor", "base", diff --git a/components/servo/Cargo.toml b/components/servo/Cargo.toml index 158dae236b8..e4347d76af0 100644 --- a/components/servo/Cargo.toml +++ b/components/servo/Cargo.toml @@ -124,6 +124,7 @@ gaol = "0.2.1" webxr = { path = "../webxr", features = ["ipc", "glwindow", "headless", "openxr-api"] } [dev-dependencies] +anyhow = "1.0.97" http = { workspace = true } libservo = { path = ".", features = ["tracing"] } rustls = { version = "0.23", default-features = false, features = ["aws-lc-rs"] } diff --git a/components/servo/tests/common/mod.rs b/components/servo/tests/common/mod.rs index d147ff75722..9f6b3f03449 100644 --- a/components/servo/tests/common/mod.rs +++ b/components/servo/tests/common/mod.rs @@ -3,23 +3,35 @@ * 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::windowing::EmbedderMethods; use compositing_traits::rendering_context::{RenderingContext, SoftwareRenderingContext}; +use crossbeam_channel::{Receiver, Sender, unbounded}; use dpi::PhysicalSize; use embedder_traits::EventLoopWaker; -use euclid::Scale; +use parking_lot::Mutex; use servo::Servo; pub struct ServoTest { servo: Servo, } +impl Drop for ServoTest { + fn drop(&mut self) { + self.servo.start_shutting_down(); + while self.servo.spin_event_loop() { + std::thread::sleep(Duration::from_millis(1)); + } + self.servo.deinit(); + } +} + impl ServoTest { - pub fn new() -> Self { + fn new() -> Self { let rendering_context = Rc::new( SoftwareRenderingContext::new(PhysicalSize { width: 500, @@ -63,14 +75,59 @@ impl ServoTest { pub fn servo(&self) -> &Servo { &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)); + } } -impl Drop for ServoTest { - fn drop(&mut self) { - self.servo.start_shutting_down(); - while self.servo.spin_event_loop() { - std::thread::sleep(Duration::from_millis(1)); +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)); + } + }); + + 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}"); } - self.servo.deinit(); } } diff --git a/components/servo/tests/servo.rs b/components/servo/tests/servo.rs index 5c77a57194f..2f71e909bee 100644 --- a/components/servo/tests/servo.rs +++ b/components/servo/tests/servo.rs @@ -2,12 +2,31 @@ * 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/. */ +//! Servo API unit tests. +//! +//! Since all Servo tests must rust 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 anyhow::ensure; use common::*; +use servo::WebViewBuilder; + +#[test] +fn test_simple_servo_is_not_animating_by_default() { + ServoTest::run(|servo_test| { + ensure!(!servo_test.servo().animating()); + Ok(()) + }); +} #[test] -fn test_simple_servo_start_and_stop() { - let shared_test = ServoTest::new(); - assert!(!shared_test.servo().animating()); +fn test_simple_servo_construct_webview() { + ServoTest::run(|servo_test| { + WebViewBuilder::new(servo_test.servo()).build(); + Ok(()) + }); } |