aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Robinson <mrobinson@igalia.com>2025-04-16 09:54:54 +0200
committerGitHub <noreply@github.com>2025-04-16 07:54:54 +0000
commit6bad65a5a16c5a90b09fe8d0441c1d473e46e060 (patch)
treedad9b6631fd4b93f570191f96630bcb2fe33f7d4
parent9aa09d73b5d82406b0d922a30f30b65ffcf9eb96 (diff)
downloadservo-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.lock1
-rw-r--r--components/servo/Cargo.toml1
-rw-r--r--components/servo/tests/common/mod.rs75
-rw-r--r--components/servo/tests/servo.rs25
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(())
+ });
}