128 lines
4.6 KiB
Rust
128 lines
4.6 KiB
Rust
//! # 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 enum UninstantiableError {}
|
|
|
|
/// A panicable result. This type can be used when a closure chooses to panic instead of
|
|
/// returning an error. This doesn't necessarily mean a closure _has_ to panic, and its absence
|
|
/// doesn't imply a closure _can't_ panic, but this is a useful utility function for writing tests,
|
|
/// to avoid the necessity of making custom error types.
|
|
///
|
|
/// ```rust
|
|
/// use keyforkd::test_util::Panicable;
|
|
/// let closure = || {
|
|
/// Panicable::Ok(())
|
|
/// };
|
|
/// assert!(closure().is_ok());
|
|
/// ```
|
|
pub type Panicable = std::result::Result<(), UninstantiableError>;
|
|
|
|
/// 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
|
|
/// The test utility provides a socket that can be connected to for deriving keys.
|
|
///
|
|
/// ```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();
|
|
/// ```
|
|
///
|
|
/// The `keyforkd-client` crate uses the `KEYFORKD_SOCKET_PATH` variable to determine the default
|
|
/// socket path. The test will export the environment variable so it may be used by default.
|
|
///
|
|
/// ```rust
|
|
/// use std::os::unix::net::UnixStream;
|
|
/// let seed = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
|
/// keyforkd::test_util::run_test(seed.as_slice(), |path| {
|
|
/// assert_eq!(std::env::var_os("KEYFORKD_SOCKET_PATH").unwrap(), path.as_os_str());
|
|
/// UnixStream::connect(&path).map(|_| ())
|
|
/// }).unwrap();
|
|
/// ```
|
|
#[allow(clippy::missing_errors_doc)]
|
|
pub fn run_test<F, E>(seed: &[u8], closure: F) -> std::result::Result<(), E>
|
|
where
|
|
F: FnOnce(&std::path::Path) -> std::result::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.clone()));
|
|
server
|
|
.run(service)
|
|
.await
|
|
.expect(bug!("Unable to start service"));
|
|
}
|
|
});
|
|
|
|
rx.recv()
|
|
.await
|
|
.expect(bug!("can't receive server start signal from channel"));
|
|
std::env::set_var("KEYFORKD_SOCKET_PATH", &socket_path);
|
|
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 let Ok(reason) = e.try_into_panic() {
|
|
std::panic::resume_unwind(reason);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_run_test() {
|
|
let seed = b"beefbeef";
|
|
run_test(seed, |_path| Panicable::Ok(())).expect("infallible");
|
|
}
|
|
}
|