//! # Keyforkd Test Utilities //! //! This module adds a helper to set up a Tokio runtime, start a Tokio runtime with a given seed, //! start a Keyfork server on that runtime, and run a given test closure. use crate::{middleware, Keyforkd, ServiceBuilder, UnixServer}; use tokio::runtime::Builder; use keyfork_bug::bug; #[derive(Debug, thiserror::Error)] #[error("This error can never be instantiated")] #[doc(hidden)] pub struct InfallibleError { protected: (), } /// An infallible result. This type can be used to represent a function that should never error. /// /// ```rust /// use keyforkd::test_util::Infallible; /// let closure = || { /// Infallible::Ok(()) /// }; /// assert!(closure().is_ok()); /// ``` pub type Infallible = std::result::Result; /// Run a test making use of a Keyforkd server. The test may use a seed (the first argument) from a /// test suite, or (as shown in the example below) a simple seed may be used solely to ensure /// the server is capable of being interacted with. The test is in the form of a closure, expected /// to return a [`Result`] where success is a unit type (test passed) and the error is any error /// that happened during the test (alternatively, a panic may be used, and will be returned as an /// error). /// /// # Panics /// The function may panic if any errors arise while configuring and using the Tokio multithreaded /// runtime. /// /// # Examples /// ```rust /// use std::os::unix::net::UnixStream; /// let seed = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; /// keyforkd::test_util::run_test(seed.as_slice(), |path| { /// UnixStream::connect(&path).map(|_| ()) /// }).unwrap(); /// ``` #[allow(clippy::missing_errors_doc)] pub fn run_test(seed: &[u8], closure: F) -> Result<(), E> where F: FnOnce(&std::path::Path) -> Result<(), E> + Send + 'static, E: Send + 'static, { let rt = Builder::new_multi_thread() .worker_threads(2) .enable_io() .build() .expect(bug!( "can't make tokio threaded IO runtime, should be enabled via feature flags" )); let socket_dir = tempfile::tempdir().expect(bug!("can't create tempdir")); let socket_path = socket_dir.path().join("keyforkd.sock"); let result = rt.block_on(async move { let (tx, mut rx) = tokio::sync::mpsc::channel(1); let server_handle = tokio::spawn({ let socket_path = socket_path.clone(); let seed = seed.to_vec(); async move { let mut server = UnixServer::bind(&socket_path).expect(bug!("can't bind unix socket")); tx.send(()) .await .expect(bug!("couldn't send server start signal")); let service = ServiceBuilder::new() .layer(middleware::BincodeLayer::new()) .service(Keyforkd::new(seed.to_vec())); server.run(service).await.expect(bug!("Unable to start service")); } }); rx.recv() .await .expect(bug!("can't receive server start signal from channel")); let test_handle = tokio::task::spawn_blocking(move || closure(&socket_path)); let result = test_handle.await; server_handle.abort(); result }); if let Err(e) = result { if e.is_panic() { std::panic::resume_unwind(e.into_panic()); } } Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn test_run_test() { let seed = b"beefbeef"; run_test(seed, |_path| Infallible::Ok(())).expect("infallible"); } }