use std::path::{Path, PathBuf};
use crate::session::{Backend, DeviceId, RawEvent};
use crate::{Session, SessionError, SessionEvent};
#[derive(Debug, Clone, PartialEq, Eq)]
enum Call {
Open(PathBuf),
Close(DeviceId),
Acknowledge,
Switch(i32),
}
#[derive(Debug, Default)]
struct FakeSeat {
calls: Vec<Call>,
next_id: i32,
next_fd: i32,
deliver: Vec<RawEvent>,
fail_next_open: bool,
fail_close: bool,
}
impl FakeSeat {
fn new() -> Self {
Self {
next_id: 1,
next_fd: 100,
..Self::default()
}
}
fn queue(&mut self, event: RawEvent) {
self.deliver.push(event);
}
fn opens(&self) -> Vec<&PathBuf> {
self.calls
.iter()
.filter_map(|call| match call {
Call::Open(path) => Some(path),
_ => None,
})
.collect()
}
}
impl Backend for FakeSeat {
fn open_device(&mut self, path: &Path) -> Result<(DeviceId, i32), SessionError> {
self.calls.push(Call::Open(path.to_path_buf()));
if self.fail_next_open {
self.fail_next_open = false;
return Err(SessionError::Seat("refused".to_owned()));
}
let id = DeviceId(self.next_id);
let fd = self.next_fd;
self.next_id += 1;
self.next_fd += 1;
Ok((id, fd))
}
fn close_device(&mut self, id: DeviceId) -> Result<(), SessionError> {
self.calls.push(Call::Close(id));
if self.fail_close {
return Err(SessionError::Seat("close refused".to_owned()));
}
Ok(())
}
fn acknowledge_pause(&mut self) -> Result<(), SessionError> {
self.calls.push(Call::Acknowledge);
Ok(())
}
fn switch_session(&mut self, session: i32) -> Result<(), SessionError> {
self.calls.push(Call::Switch(session));
Ok(())
}
fn poll_fd(&self) -> Option<i32> {
Some(7)
}
fn dispatch(&mut self, _timeout: i32, events: &mut Vec<RawEvent>) -> Result<(), SessionError> {
events.append(&mut self.deliver);
Ok(())
}
}
fn with_card() -> Session<FakeSeat> {
let mut session = Session::new(FakeSeat::new());
session.take_device("/dev/dri/card0").expect("take");
session
}
#[test]
fn taking_a_device_records_it() {
let mut session = Session::new(FakeSeat::new());
let device = session.take_device("/dev/dri/card0").expect("take");
assert_eq!(device.path, Path::new("/dev/dri/card0"));
assert_eq!(device.fd, 100);
assert_eq!(session.devices().len(), 1);
assert!(session.is_active());
}
#[test]
fn taking_the_same_path_twice_holds_one_device() {
let mut session = Session::new(FakeSeat::new());
let first = session.take_device("/dev/dri/card0").expect("take");
let again = session.take_device("/dev/dri/card0").expect("take again");
assert_eq!(first, again);
assert_eq!(session.devices().len(), 1);
}
#[test]
fn a_pause_is_acknowledged_and_reported() {
let mut session = with_card();
session.backend_mut().queue(RawEvent::Disable);
session.dispatch(0).expect("dispatch");
assert!(!session.is_active(), "the display is not ours any more");
assert_eq!(session.next_event(), Some(SessionEvent::Paused));
assert!(
session.backend().calls.contains(&Call::Acknowledge),
"an unacknowledged pause stalls the switch"
);
}
#[test]
fn a_device_cannot_be_taken_while_paused() {
let mut session = with_card();
session.backend_mut().queue(RawEvent::Disable);
session.dispatch(0).expect("dispatch");
assert_eq!(
session.take_device("/dev/dri/card1").unwrap_err(),
SessionError::Inactive
);
}
#[test]
fn a_resume_replaces_every_descriptor() {
let mut session = with_card();
let before = session.devices()[0].fd;
session.backend_mut().queue(RawEvent::Disable);
session.backend_mut().queue(RawEvent::Enable);
session.dispatch(0).expect("dispatch");
assert_eq!(session.next_event(), Some(SessionEvent::Paused));
let Some(SessionEvent::Resumed { devices }) = session.next_event() else {
panic!("expected a resume");
};
assert_eq!(devices.len(), 1);
assert_ne!(devices[0].fd, before, "a fresh descriptor, not the old one");
assert_eq!(devices[0].path, Path::new("/dev/dri/card0"));
assert!(session.is_active());
}
#[test]
fn a_resume_closes_before_it_reopens() {
let mut session = with_card();
session.backend_mut().queue(RawEvent::Disable);
session.backend_mut().queue(RawEvent::Enable);
session.dispatch(0).expect("dispatch");
let calls = &session.backend().calls;
let close = calls
.iter()
.position(|call| matches!(call, Call::Close(_)))
.expect("the old handle is closed");
let reopen = calls
.iter()
.rposition(|call| matches!(call, Call::Open(_)))
.expect("reopened");
assert!(close < reopen, "closed before reopened: {calls:?}");
}
#[test]
fn every_held_device_comes_back() {
let mut session = Session::new(FakeSeat::new());
for path in ["/dev/dri/card0", "/dev/dri/card1", "/dev/input/event0"] {
session.take_device(path).expect("take");
}
session.backend_mut().queue(RawEvent::Disable);
session.backend_mut().queue(RawEvent::Enable);
session.dispatch(0).expect("dispatch");
session.next_event();
let Some(SessionEvent::Resumed { devices }) = session.next_event() else {
panic!("expected a resume");
};
assert_eq!(devices.len(), 3);
let paths: Vec<&Path> = devices.iter().map(|d| d.path.as_path()).collect();
assert_eq!(
paths,
vec![
Path::new("/dev/dri/card0"),
Path::new("/dev/dri/card1"),
Path::new("/dev/input/event0"),
],
"and in the order they were taken"
);
}
#[test]
fn a_released_device_does_not_come_back() {
let mut session = Session::new(FakeSeat::new());
session.take_device("/dev/dri/card0").expect("take");
session.take_device("/dev/dri/card1").expect("take");
session.release_device("/dev/dri/card0").expect("release");
session.backend_mut().queue(RawEvent::Disable);
session.backend_mut().queue(RawEvent::Enable);
session.dispatch(0).expect("dispatch");
session.next_event();
let Some(SessionEvent::Resumed { devices }) = session.next_event() else {
panic!("expected a resume");
};
assert_eq!(devices.len(), 1);
assert_eq!(devices[0].path, Path::new("/dev/dri/card1"));
}
#[test]
fn releasing_something_never_taken_is_an_error() {
let mut session = with_card();
assert_eq!(
session.release_device("/dev/dri/card9").unwrap_err(),
SessionError::UnknownDevice
);
assert_eq!(session.devices().len(), 1, "and holds what it had");
}
#[test]
fn a_repeated_pause_is_reported_once() {
let mut session = with_card();
session.backend_mut().queue(RawEvent::Disable);
session.backend_mut().queue(RawEvent::Disable);
session.dispatch(0).expect("dispatch");
assert_eq!(session.next_event(), Some(SessionEvent::Paused));
assert_eq!(session.next_event(), None, "not twice");
}
#[test]
fn a_resume_without_a_pause_changes_nothing() {
let mut session = with_card();
let before = session.devices().to_vec();
session.backend_mut().queue(RawEvent::Enable);
session.dispatch(0).expect("dispatch");
assert_eq!(session.next_event(), None);
assert_eq!(session.devices(), before, "descriptors are untouched");
}
#[test]
fn a_resume_that_cannot_reopen_reports_it() {
let mut session = with_card();
session.backend_mut().fail_next_open = true;
session.backend_mut().queue(RawEvent::Disable);
session.backend_mut().queue(RawEvent::Enable);
session
.dispatch(0)
.expect("one lost device is not a failed dispatch");
assert_eq!(session.next_event(), Some(SessionEvent::Paused));
assert!(matches!(
session.next_event(),
Some(SessionEvent::DeviceLost { path, .. }) if path == Path::new("/dev/dri/card0")
));
assert_eq!(
session.next_event(),
Some(SessionEvent::Resumed { devices: vec![] })
);
assert!(session.devices().is_empty(), "the lost device is not held");
}
#[test]
fn a_device_that_cannot_reopen_does_not_cost_the_others() {
let mut session = with_card();
session.take_device("/dev/dri/card1").expect("take");
session.backend_mut().fail_next_open = true;
session.backend_mut().queue(RawEvent::Disable);
session.backend_mut().queue(RawEvent::Enable);
session.dispatch(0).expect("dispatch");
assert_eq!(session.next_event(), Some(SessionEvent::Paused));
assert!(matches!(
session.next_event(),
Some(SessionEvent::DeviceLost { path, .. }) if path == Path::new("/dev/dri/card0")
));
let Some(SessionEvent::Resumed { devices }) = session.next_event() else {
panic!("the rest resume");
};
assert_eq!(devices.len(), 1);
assert_eq!(devices[0].path, Path::new("/dev/dri/card1"));
assert_eq!(session.devices(), devices.as_slice());
}
#[test]
fn a_refused_close_does_not_stop_the_reopen() {
let mut session = with_card();
session.backend_mut().fail_close = true;
session.backend_mut().queue(RawEvent::Disable);
session.backend_mut().queue(RawEvent::Enable);
session.dispatch(0).expect("dispatch");
assert_eq!(session.next_event(), Some(SessionEvent::Paused));
let Some(SessionEvent::Resumed { devices }) = session.next_event() else {
panic!("resumed");
};
assert_eq!(devices.len(), 1);
}
#[test]
fn events_arrive_in_the_order_they_happened() {
let mut session = with_card();
for _ in 0..3 {
session.backend_mut().queue(RawEvent::Disable);
session.backend_mut().queue(RawEvent::Enable);
}
session.dispatch(0).expect("dispatch");
let mut seen = Vec::new();
while let Some(event) = session.next_event() {
seen.push(matches!(event, SessionEvent::Paused));
}
assert_eq!(
seen,
vec![true, false, true, false, true, false],
"three switches away and back"
);
}
#[test]
fn switching_asks_the_seat_and_waits() {
let mut session = with_card();
session.switch_session(3).expect("switch");
assert!(session.backend().calls.contains(&Call::Switch(3)));
assert!(
session.is_active(),
"still ours until the seat says otherwise"
);
assert_eq!(session.next_event(), None);
}
#[test]
fn the_poll_descriptor_comes_from_the_seat() {
let session = with_card();
assert_eq!(session.poll_fd(), Some(7));
}
#[test]
fn dispatching_nothing_is_quiet() {
let mut session = with_card();
session.dispatch(0).expect("dispatch");
assert_eq!(session.next_event(), None);
assert!(session.is_active());
assert_eq!(
session.backend().opens().len(),
1,
"an idle dispatch reopens nothing"
);
}