Files
feresaria/compositor/src/resume/tests.rs
T

303 lines
9.9 KiB
Rust

use std::io::{BufRead, BufReader};
use std::process::{Child, Stdio};
use std::sync::mpsc::{self, Receiver};
use std::time::Instant;
use super::*;
#[test]
fn warning_suppression_resets_at_subscription_not_at_next_resume() {
let mut recovery = Recovery::default();
assert!(!recovery.subscribed(), "startup is not a recovery or a resume");
assert!(recovery.unavailable());
assert!(!recovery.unavailable(), "one warning per outage");
assert!(recovery.subscribed());
assert!(!recovery.subscribed(), "one reconciliation per recovery");
assert!(recovery.unavailable(), "another outage must warn even without a resume");
assert!(!recovery.unavailable());
}
fn stop_and_join(mut monitor: Monitor) {
let worker = monitor.worker.take().unwrap();
drop(monitor);
let deadline = Instant::now() + Duration::from_secs(1);
while !worker.is_finished() && Instant::now() < deadline {
std::thread::sleep(Duration::from_millis(2));
}
assert!(worker.is_finished(), "cancellation did not stop the worker promptly");
worker.join().unwrap();
}
#[test]
fn shutdown_interrupts_retry_delay() {
let (attempt, attempted) = mpsc::channel();
let monitor = start(
move || {
attempt.send(()).unwrap();
Err(zbus::Error::Failure("test connection failure".into()))
},
RETRY_DELAY,
|_| panic!("no subscription succeeded"),
)
.unwrap();
attempted.recv_timeout(Duration::from_secs(1)).unwrap();
stop_and_join(monitor);
assert!(
attempted.try_recv().is_err(),
"shutdown must not start another connection"
);
}
struct Bus {
process: Child,
address: String,
}
impl Bus {
fn new() -> Self {
let mut process = crate::process::command("dbus-daemon")
.args(["--session", "--nofork", "--print-address=1"])
.stdout(Stdio::piped())
.spawn()
.unwrap();
let mut address = String::new();
BufReader::new(process.stdout.take().unwrap())
.read_line(&mut address)
.unwrap();
Self {
process,
address: address.trim().to_owned(),
}
}
fn stop(&mut self) {
let _ = self.process.kill();
let _ = self.process.wait();
}
fn manager(&self) -> zbus::blocking::Connection {
struct Manager;
#[zbus::interface(name = "org.freedesktop.login1.Manager")]
impl Manager {
#[zbus(property)]
fn lid_closed(&self) -> bool {
false
}
}
zbus::blocking::connection::Builder::address(self.address.as_str())
.unwrap()
.name("org.freedesktop.login1")
.unwrap()
.serve_at("/org/freedesktop/login1", Manager)
.unwrap()
.build()
.unwrap()
}
}
impl Drop for Bus {
fn drop(&mut self) {
self.stop();
}
}
fn signal(manager: &zbus::blocking::Connection, sleeping: bool) {
manager
.emit_signal(
None::<&str>,
"/org/freedesktop/login1",
"org.freedesktop.login1.Manager",
"PrepareForSleep",
&(sleeping,),
)
.unwrap();
}
fn confirm_subscription(manager: &zbus::blocking::Connection, events: &Receiver<Refresh>) {
let deadline = Instant::now() + Duration::from_secs(2);
loop {
signal(manager, false);
if let Ok(event) = events.recv_timeout(Duration::from_millis(10)) {
assert_eq!(event, Refresh::Resumed);
break;
}
assert!(Instant::now() < deadline, "listener did not subscribe");
}
}
#[test]
fn reconnect_reconciles_a_resume_missed_during_the_outage() {
if !crate::startup_tests::private_runtime("resume::tests::reconnect_reconciles_a_resume_missed_during_the_outage") {
return;
}
let mut bus = Bus::new();
let manager = bus.manager();
let address = Arc::new(Mutex::new(bus.address.clone()));
let worker_address = address.clone();
let (sender, events) = mpsc::channel();
let (attempt, attempts) = mpsc::channel();
let monitor = start(
move || {
attempt.send(()).unwrap();
zbus::connection::Builder::address(worker_address.lock().unwrap().as_str())
},
Duration::from_millis(40),
move |event| sender.send(event).is_ok(),
)
.unwrap();
confirm_subscription(&manager, &events);
attempts.recv_timeout(Duration::from_secs(1)).unwrap();
*address.lock().unwrap() = "unix:path=/nonexistent/ferese-test-bus".into();
bus.stop();
attempts.recv_timeout(Duration::from_secs(1)).unwrap();
let replacement = Bus::new();
let manager = replacement.manager();
signal(&manager, false); // No listener can receive this resume.
*address.lock().unwrap() = replacement.address.clone();
assert_eq!(events.recv_timeout(Duration::from_secs(2)).unwrap(), Refresh::Reconcile);
assert!(events.recv_timeout(Duration::from_millis(80)).is_err());
signal(&manager, true);
assert!(events.recv_timeout(Duration::from_millis(40)).is_err());
signal(&manager, false);
assert_eq!(events.recv_timeout(Duration::from_secs(1)).unwrap(), Refresh::Resumed);
stop_and_join(monitor);
}
#[test]
fn shutdown_interrupts_a_healthy_signal_wait() {
if !crate::startup_tests::private_runtime("resume::tests::shutdown_interrupts_a_healthy_signal_wait") {
return;
}
let bus = Bus::new();
let manager = bus.manager();
let address = bus.address.clone();
let (sender, events) = mpsc::channel();
let monitor = start(
move || zbus::connection::Builder::address(address.as_str()),
RETRY_DELAY,
move |event| sender.send(event).is_ok(),
)
.unwrap();
confirm_subscription(&manager, &events);
stop_and_join(monitor);
}
#[test]
fn logind_replacement_reconciles_without_a_bus_disconnect() {
if !crate::startup_tests::private_runtime("resume::tests::logind_replacement_reconciles_without_a_bus_disconnect") {
return;
}
let bus = Bus::new();
let manager = bus.manager();
let address = bus.address.clone();
let (sender, events) = mpsc::channel();
let monitor = start(
move || zbus::connection::Builder::address(address.as_str()),
Duration::from_millis(40),
move |event| sender.send(event).is_ok(),
)
.unwrap();
confirm_subscription(&manager, &events);
manager.release_name("org.freedesktop.login1").unwrap();
assert!(
events.recv_timeout(Duration::from_millis(100)).is_err(),
"absent logind is not recovery"
);
let replacement = bus.manager();
assert_eq!(events.recv_timeout(Duration::from_secs(2)).unwrap(), Refresh::Reconcile);
signal(&replacement, false);
assert_eq!(events.recv_timeout(Duration::from_secs(1)).unwrap(), Refresh::Resumed);
stop_and_join(monitor);
}
#[test]
fn shutdown_interrupts_connection_authentication() {
if !crate::startup_tests::private_runtime("resume::tests::shutdown_interrupts_connection_authentication") {
return;
}
let directory = tempfile::tempdir().unwrap();
let path = directory.path().join("silent-bus");
let listener = std::os::unix::net::UnixListener::bind(&path).unwrap();
listener.set_nonblocking(true).unwrap();
let address = format!("unix:path={}", path.display());
let monitor = start(
move || zbus::connection::Builder::address(address.as_str()),
RETRY_DELAY,
|_| panic!("authentication cannot complete"),
)
.unwrap();
let deadline = Instant::now() + Duration::from_secs(1);
let _connection = loop {
match listener.accept() {
Ok((stream, _)) => break stream,
Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {
assert!(Instant::now() < deadline);
std::thread::sleep(Duration::from_millis(2));
}
Err(error) => panic!("{error}"),
}
};
stop_and_join(monitor);
}
#[test]
fn ownership_acquired_before_subscription_baseline_is_not_a_second_outage() {
if !crate::startup_tests::private_runtime(
"resume::tests::ownership_acquired_before_subscription_baseline_is_not_a_second_outage",
) {
return;
}
let bus = Bus::new();
let (connection, mut owners, manager, baseline) = future::block_on(async {
let connection = zbus::connection::Builder::address(bus.address.as_str())
.unwrap()
.build()
.await
.unwrap();
let proxy = zbus::Proxy::new(
&connection,
"org.freedesktop.login1",
"/org/freedesktop/login1",
"org.freedesktop.login1.Manager",
)
.await
.unwrap();
let owners = proxy.receive_owner_changed().await.unwrap();
// Force acquisition after AddMatch but before the listener's baseline.
let manager = bus.manager();
let reply = connection
.call_method(
Some("org.freedesktop.DBus"),
"/org/freedesktop/DBus",
Some("org.freedesktop.DBus"),
"GetNameOwner",
&("org.freedesktop.login1",),
)
.await
.unwrap();
(connection, owners, manager, reply.recv_position())
});
let (sender, events) = mpsc::channel();
let worker = std::thread::spawn(move || {
// Keep the same connection alive so receive sequences remain comparable.
let _connection = connection;
future::block_on(owner_changed_after(&mut owners, baseline));
sender.send(()).unwrap();
});
assert!(
events.recv_timeout(Duration::from_millis(100)).is_err(),
"queued acquisition must not interrupt the recovered listener"
);
manager.release_name("org.freedesktop.login1").unwrap();
events.recv_timeout(Duration::from_secs(2)).unwrap();
worker.join().unwrap();
}