mirror of
https://github.com/servo/servo.git
synced 2025-06-06 00:25:37 +00:00
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>
This commit is contained in:
parent
9aa09d73b5
commit
6bad65a5a1
4 changed files with 92 additions and 14 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -4306,6 +4306,7 @@ dependencies = [
|
||||||
name = "libservo"
|
name = "libservo"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"arboard",
|
"arboard",
|
||||||
"background_hang_monitor",
|
"background_hang_monitor",
|
||||||
"base",
|
"base",
|
||||||
|
|
|
@ -124,6 +124,7 @@ gaol = "0.2.1"
|
||||||
webxr = { path = "../webxr", features = ["ipc", "glwindow", "headless", "openxr-api"] }
|
webxr = { path = "../webxr", features = ["ipc", "glwindow", "headless", "openxr-api"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
anyhow = "1.0.97"
|
||||||
http = { workspace = true }
|
http = { workspace = true }
|
||||||
libservo = { path = ".", features = ["tracing"] }
|
libservo = { path = ".", features = ["tracing"] }
|
||||||
rustls = { version = "0.23", default-features = false, features = ["aws-lc-rs"] }
|
rustls = { version = "0.23", default-features = false, features = ["aws-lc-rs"] }
|
||||||
|
|
|
@ -3,23 +3,35 @@
|
||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::{Arc, OnceLock};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use anyhow::Error;
|
||||||
use compositing::windowing::EmbedderMethods;
|
use compositing::windowing::EmbedderMethods;
|
||||||
use compositing_traits::rendering_context::{RenderingContext, SoftwareRenderingContext};
|
use compositing_traits::rendering_context::{RenderingContext, SoftwareRenderingContext};
|
||||||
|
use crossbeam_channel::{Receiver, Sender, unbounded};
|
||||||
use dpi::PhysicalSize;
|
use dpi::PhysicalSize;
|
||||||
use embedder_traits::EventLoopWaker;
|
use embedder_traits::EventLoopWaker;
|
||||||
use euclid::Scale;
|
use parking_lot::Mutex;
|
||||||
use servo::Servo;
|
use servo::Servo;
|
||||||
|
|
||||||
pub struct ServoTest {
|
pub struct ServoTest {
|
||||||
servo: Servo,
|
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 {
|
impl ServoTest {
|
||||||
pub fn new() -> Self {
|
fn new() -> Self {
|
||||||
let rendering_context = Rc::new(
|
let rendering_context = Rc::new(
|
||||||
SoftwareRenderingContext::new(PhysicalSize {
|
SoftwareRenderingContext::new(PhysicalSize {
|
||||||
width: 500,
|
width: 500,
|
||||||
|
@ -63,14 +75,59 @@ impl ServoTest {
|
||||||
pub fn servo(&self) -> &Servo {
|
pub fn servo(&self) -> &Servo {
|
||||||
&self.servo
|
&self.servo
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for ServoTest {
|
/// Run a Servo test. All tests are run in a `ServoTestThread` and serially. Currently
|
||||||
fn drop(&mut self) {
|
/// Servo does not support launching concurrent instances, in order to ensure
|
||||||
self.servo.start_shutting_down();
|
/// isolation and allow for more than a single test per instance.
|
||||||
while self.servo.spin_event_loop() {
|
pub fn run(
|
||||||
std::thread::sleep(Duration::from_millis(1));
|
test_function: impl FnOnce(&ServoTest) -> Result<(), anyhow::Error> + Send + Sync + 'static,
|
||||||
}
|
) {
|
||||||
self.servo.deinit();
|
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));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,31 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* 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;
|
mod common;
|
||||||
|
|
||||||
|
use anyhow::ensure;
|
||||||
use common::*;
|
use common::*;
|
||||||
|
use servo::WebViewBuilder;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_simple_servo_start_and_stop() {
|
fn test_simple_servo_is_not_animating_by_default() {
|
||||||
let shared_test = ServoTest::new();
|
ServoTest::run(|servo_test| {
|
||||||
assert!(!shared_test.servo().animating());
|
ensure!(!servo_test.servo().animating());
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_simple_servo_construct_webview() {
|
||||||
|
ServoTest::run(|servo_test| {
|
||||||
|
WebViewBuilder::new(servo_test.servo()).build();
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue