//! A fake `brokerd` state socket for tests: it records each question and answers with a fixed //! state, an error frame, or nothing at all (it holds the connection open without a byte). It //! stops, and releases every held connection, when dropped; every wait has a deadline. Do not //! edit. #![allow(dead_code)] // each test file uses a different part of this module use std::os::unix::net::{UnixListener, UnixStream}; use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use std::thread::JoinHandle; use std::time::{Duration, Instant}; use bxcore::{ Envelope, ErrorCode, Message, PROTOCOL_VERSION, SessionStateReply, WireError, read_frame, write_frame, }; /// How the fake answers. #[derive(Debug, Clone)] pub enum Answer { State(SessionStateReply), Error(String), /// Read the question, then send nothing until the fake is dropped (at most 30 s). Hang, } pub struct FakeBrokerd { path: PathBuf, stop: Arc, seen: Arc>>, accept: Option>, } impl FakeBrokerd { /// Listen on `path` (its directory is made) and answer every connection with `answer`. pub fn start(path: &Path, answer: Answer) -> FakeBrokerd { if let Some(dir) = path.parent() { std::fs::create_dir_all(dir).unwrap(); } let _ = std::fs::remove_file(path); let listener = UnixListener::bind(path).unwrap(); let stop = Arc::new(AtomicBool::new(false)); let seen = Arc::new(Mutex::new(Vec::new())); let (stop_accept, seen_accept) = (Arc::clone(&stop), Arc::clone(&seen)); let accept = std::thread::spawn(move || { for stream in listener.incoming() { if stop_accept.load(Ordering::SeqCst) { return; } let Ok(stream) = stream else { return }; let (stop, seen, answer) = ( Arc::clone(&stop_accept), Arc::clone(&seen_accept), answer.clone(), ); std::thread::spawn(move || serve(stream, &answer, &seen, &stop)); } }); FakeBrokerd { path: path.to_path_buf(), stop, seen, accept: Some(accept), } } /// Every question read so far, in order. pub fn seen(&self) -> Vec { self.seen.lock().unwrap().clone() } /// Waits up to 10 s until at least `n` questions were read; true if they were. pub fn wait_for(&self, n: usize) -> bool { let deadline = Instant::now() + Duration::from_secs(10); while Instant::now() < deadline { if self.seen().len() >= n { return true; } std::thread::sleep(Duration::from_millis(10)); } false } } fn serve(mut stream: UnixStream, answer: &Answer, seen: &Mutex>, stop: &AtomicBool) { let _ = stream.set_read_timeout(Some(Duration::from_secs(10))); let Ok(question) = read_frame(&mut stream) else { return; }; let id = question.id; seen.lock().unwrap().push(question); let msg = match answer { Answer::State(state) => Message::SessionStateReply(state.clone()), Answer::Error(detail) => Message::Error(WireError { code: ErrorCode::Internal, detail: detail.clone(), }), Answer::Hang => { let deadline = Instant::now() + Duration::from_secs(30); while !stop.load(Ordering::SeqCst) && Instant::now() < deadline { std::thread::sleep(Duration::from_millis(10)); } return; } }; let env = Envelope { v: PROTOCOL_VERSION, id, r#final: true, msg, }; let _ = write_frame(&mut stream, &env); } impl Drop for FakeBrokerd { fn drop(&mut self) { self.stop.store(true, Ordering::SeqCst); // Wakes the accept loop, which then sees `stop`. let _ = UnixStream::connect(&self.path); if let Some(accept) = self.accept.take() { let _ = accept.join(); } let _ = std::fs::remove_file(&self.path); } }