diff --git a/crates/xy-supervisor/src/child.rs b/crates/xy-supervisor/src/child.rs new file mode 100644 index 0000000..fd9cc15 --- /dev/null +++ b/crates/xy-supervisor/src/child.rs @@ -0,0 +1,92 @@ +use std::sync::Arc; +use tokio::sync::{Mutex, oneshot}; + +#[async_trait::async_trait] +pub trait ChildHandle: Send + 'static { + fn pid(&self) -> u32; + async fn wait(&mut self) -> std::io::Result>; + fn terminate(&mut self) -> std::io::Result<()>; + fn kill(&mut self) -> std::io::Result<()>; +} + +pub struct MockChild { + pid: u32, + exit_rx: Arc>>>, + terminate_tx: Option>, + kill_tx: Option>, +} + +pub struct MockChildController { + pub exit_tx: Option>>, + pub terminate_rx: oneshot::Receiver<()>, + pub kill_rx: oneshot::Receiver<()>, +} + +impl MockChild { + pub fn new(pid: u32) -> (Self, MockChildController) { + let (exit_tx, exit_rx) = oneshot::channel(); + let (terminate_tx, terminate_rx) = oneshot::channel(); + let (kill_tx, kill_rx) = oneshot::channel(); + let child = Self { + pid, + exit_rx: Arc::new(Mutex::new(exit_rx)), + terminate_tx: Some(terminate_tx), + kill_tx: Some(kill_tx), + }; + let ctl = MockChildController { + exit_tx: Some(exit_tx), + terminate_rx, + kill_rx, + }; + (child, ctl) + } +} + +#[async_trait::async_trait] +impl ChildHandle for MockChild { + fn pid(&self) -> u32 { + self.pid + } + + async fn wait(&mut self) -> std::io::Result> { + let mut rx = self.exit_rx.lock().await; + match (&mut *rx).await { + Ok(code) => Ok(code), + Err(_) => Err(std::io::Error::other("exit_tx dropped")), + } + } + + fn terminate(&mut self) -> std::io::Result<()> { + if let Some(tx) = self.terminate_tx.take() { + let _ = tx.send(()); + } + Ok(()) + } + + fn kill(&mut self) -> std::io::Result<()> { + if let Some(tx) = self.kill_tx.take() { + let _ = tx.send(()); + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn mock_child_exit() { + let (mut child, mut ctl) = MockChild::new(123); + assert_eq!(child.pid(), 123); + ctl.exit_tx.take().unwrap().send(Some(0)).unwrap(); + assert_eq!(child.wait().await.unwrap(), Some(0)); + } + + #[tokio::test] + async fn mock_child_terminate() { + let (mut child, mut ctl) = MockChild::new(1); + child.terminate().unwrap(); + ctl.terminate_rx.try_recv().unwrap(); + } +} diff --git a/crates/xy-supervisor/src/lib.rs b/crates/xy-supervisor/src/lib.rs index 1747681..e536cee 100644 --- a/crates/xy-supervisor/src/lib.rs +++ b/crates/xy-supervisor/src/lib.rs @@ -1 +1,5 @@ //! Process-supervision primitives for the xy daemon. + +pub mod child; + +pub use child::{ChildHandle, MockChild, MockChildController};