use std::os::fd::AsFd as _; use std::sync::atomic::Ordering; use std::time::Duration; use calloop::generic::Generic; use calloop::{EventLoop, Interest, LoopHandle, Mode, PostAction}; use niri_config::Config; use super::client::{Client, ClientId}; use super::server::Server; use crate::niri::Niri; pub struct Fixture { pub event_loop: EventLoop<'static, State>, pub handle: LoopHandle<'static, State>, pub state: State, } pub struct State { pub server: Server, pub clients: Vec, } impl Fixture { pub fn new() -> Self { Self::with_config(Config::default()) } pub fn with_config(config: Config) -> Self { let event_loop = EventLoop::try_new().unwrap(); let handle = event_loop.handle(); let server = Server::new(config); let fd = server.event_loop.as_fd().try_clone_to_owned().unwrap(); let source = Generic::new(fd, Interest::READ, Mode::Level); handle .insert_source(source, |_, _, state: &mut State| { state.server.dispatch(); Ok(PostAction::Continue) }) .unwrap(); let state = State { server, clients: Vec::new(), }; Self { event_loop, handle, state, } } pub fn dispatch(&mut self) { self.event_loop .dispatch(Duration::ZERO, &mut self.state) .unwrap(); } pub fn niri_state(&mut self) -> &mut crate::niri::State { &mut self.state.server.state } pub fn niri(&mut self) -> &mut Niri { &mut self.niri_state().niri } pub fn add_output(&mut self, n: u8, size: (u16, u16)) { let state = self.niri_state(); let niri = &mut state.niri; state.backend.headless().add_output(niri, n, size); } pub fn add_client(&mut self) -> ClientId { let client = Client::new(&self.state.server.state.niri.socket_name); let id = client.id; let fd = client.event_loop.as_fd().try_clone_to_owned().unwrap(); let source = Generic::new(fd, Interest::READ, Mode::Level); self.handle .insert_source(source, move |_, _, state: &mut State| { state.client(id).dispatch(); Ok(PostAction::Continue) }) .unwrap(); self.state.clients.push(client); self.roundtrip(id); id } pub fn client(&mut self, id: ClientId) -> &mut Client { self.state.client(id) } pub fn roundtrip(&mut self, id: ClientId) { let client = self.state.client(id); let data = client.send_sync(); while !data.done.load(Ordering::Relaxed) { self.dispatch(); } } /// Rountrip twice in a row. /// /// For some reason, when running tests on many threads at once, a single roundtrip is /// sometimes not sufficient to get the configure events to the client. /// /// I suspect that this is because these configure events are sent from the niri loop callback, /// so they arrive after the sync done event and don't get processed in that client dispatch /// cycle. I'm not sure why this would be dependent on multithreading. But if this is indeed /// the issue, then a double roundtrip fixes it. pub fn double_roundtrip(&mut self, id: ClientId) { self.roundtrip(id); self.roundtrip(id); } } impl State { pub fn client(&mut self, id: ClientId) -> &mut Client { self.clients.iter_mut().find(|c| c.id == id).unwrap() } }