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 /components/servo/tests/common/mod.rs | |
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>
Diffstat (limited to 'components/servo/tests/common/mod.rs')
-rw-r--r-- | components/servo/tests/common/mod.rs | 75 |
1 files changed, 66 insertions, 9 deletions
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(); } } |