diff --git a/Cargo.lock b/Cargo.lock index 5d495cbd..12b03ee3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -564,4 +564,15 @@ dependencies = [ "rstest", "serde", "tokio", + "wireframe_testing", +] + +[[package]] +name = "wireframe_testing" +version = "0.1.0" +dependencies = [ + "bincode", + "bytes", + "tokio", + "wireframe", ] diff --git a/Cargo.toml b/Cargo.toml index 71f202d6..c86a0409 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ log = "0.4" [dev-dependencies] rstest = "0.18.2" +wireframe_testing = { path = "wireframe_testing" } [lints.clippy] pedantic = "warn" diff --git a/tests/routes.rs b/tests/routes.rs index 843495ae..41836db8 100644 --- a/tests/routes.rs +++ b/tests/routes.rs @@ -12,9 +12,10 @@ use wireframe::{ message::Message, serializer::BincodeSerializer, }; - -mod util; -use util::{run_app_with_frame, run_app_with_frames}; +use wireframe_testing::{ + drive_with_frame as run_app_with_frame, + drive_with_frames as run_app_with_frames, +}; #[derive(bincode::Encode, bincode::BorrowDecode, PartialEq, Debug)] struct TestEnvelope { diff --git a/tests/util.rs b/tests/util.rs deleted file mode 100644 index 52489879..00000000 --- a/tests/util.rs +++ /dev/null @@ -1,120 +0,0 @@ -use rstest::fixture; -use tokio::io::{self, AsyncReadExt, AsyncWriteExt, duplex}; -use wireframe::{ - app::{Packet, WireframeApp}, - frame::LengthPrefixedProcessor, - serializer::Serializer, -}; - -/// Feed a single frame into `app` and collect the response bytes. -/// -/// # Errors -/// -/// Propagates I/O errors from the in-memory connection. -/// -/// # Panics -/// -/// Panics if the spawned task running the application panics. -/// Optional duplex buffer capacity for `run_app_with_frame`. -const DEFAULT_CAPACITY: usize = 4096; - -/// Create a default length-prefixed frame processor for tests. -#[fixture] -#[rustfmt::skip] -pub fn processor() -> LengthPrefixedProcessor { - LengthPrefixedProcessor::default() -} - -/// Run `app` with a single input `frame` using the default buffer capacity. -/// -/// # Errors -/// -/// Returns any I/O errors encountered while interacting with the in-memory -/// duplex stream. -pub async fn run_app_with_frame( - app: WireframeApp, - frame: Vec, -) -> io::Result> -where - S: Serializer + Send + Sync + 'static, - C: Send + 'static, - E: Packet, -{ - run_app_with_frame_with_capacity(app, frame, DEFAULT_CAPACITY).await -} - -/// Drive `app` with a single frame using a duplex buffer of `capacity` bytes. -/// -/// # Errors -/// -/// Propagates any I/O errors from the in-memory connection. -/// -/// # Panics -/// -/// Panics if the spawned task running the application panics. -pub async fn run_app_with_frame_with_capacity( - app: WireframeApp, - frame: Vec, - capacity: usize, -) -> io::Result> -where - S: Serializer + Send + Sync + 'static, - C: Send + 'static, - E: Packet, -{ - run_app_with_frames_with_capacity(app, vec![frame], capacity).await -} - -/// Run `app` with multiple input `frames` using the default buffer capacity. -/// -/// # Errors -/// -/// Returns any I/O errors encountered while interacting with the in-memory -/// duplex stream. -pub async fn run_app_with_frames( - app: WireframeApp, - frames: Vec>, -) -> io::Result> -where - S: Serializer + Send + Sync + 'static, - C: Send + 'static, - E: Packet, -{ - run_app_with_frames_with_capacity(app, frames, DEFAULT_CAPACITY).await -} - -/// Drive `app` with multiple frames using a duplex buffer of `capacity` bytes. -/// -/// # Errors -/// -/// Propagates any I/O errors from the in-memory connection. -/// -/// # Panics -/// -/// Panics if the spawned task running the application panics. -pub async fn run_app_with_frames_with_capacity( - app: WireframeApp, - frames: Vec>, - capacity: usize, -) -> io::Result> -where - S: Serializer + Send + Sync + 'static, - C: Send + 'static, - E: Packet, -{ - let (mut client, server) = duplex(capacity); - let server_task = tokio::spawn(async move { - app.handle_connection(server).await; - }); - - for frame in &frames { - client.write_all(frame).await?; - } - client.shutdown().await?; - - let mut buf = Vec::new(); - client.read_to_end(&mut buf).await?; - - server_task.await.unwrap(); - Ok(buf) -} diff --git a/wireframe_testing/Cargo.toml b/wireframe_testing/Cargo.toml new file mode 100644 index 00000000..4012da81 --- /dev/null +++ b/wireframe_testing/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "wireframe_testing" +version = "0.1.0" +edition = "2024" + +[dependencies] +tokio = { version = "1", features = ["macros", "rt"] } +wireframe = { path = ".." } +bincode = "2" +bytes = "1" + +[dev-dependencies] +rstest = "0.18" + +[lib] +path = "src/lib.rs" diff --git a/wireframe_testing/src/helpers.rs b/wireframe_testing/src/helpers.rs new file mode 100644 index 00000000..f2a7f059 --- /dev/null +++ b/wireframe_testing/src/helpers.rs @@ -0,0 +1,139 @@ +use bincode::Encode; +use bytes::BytesMut; +use tokio::io::{self, AsyncReadExt, AsyncWriteExt, DuplexStream, duplex}; +use wireframe::{ + app::{Packet, WireframeApp}, + frame::FrameProcessor, + serializer::Serializer, +}; + +const DEFAULT_CAPACITY: usize = 4096; + +/// Feed a single frame into `app` using an in-memory duplex stream. +pub async fn drive_with_frame( + app: WireframeApp, + frame: Vec, +) -> io::Result> +where + S: Serializer + Send + Sync + 'static, + C: Send + 'static, + E: Packet, +{ + drive_with_frame_with_capacity(app, frame, DEFAULT_CAPACITY).await +} + +/// Drive `app` with multiple frames, returning all bytes written by the app. +pub async fn drive_with_frames( + app: WireframeApp, + frames: Vec>, +) -> io::Result> +where + S: Serializer + Send + Sync + 'static, + C: Send + 'static, + E: Packet, +{ + drive_with_frames_with_capacity(app, frames, DEFAULT_CAPACITY).await +} + +/// Feed `app` a single frame with a custom duplex buffer capacity. +pub async fn drive_with_frame_with_capacity( + app: WireframeApp, + frame: Vec, + capacity: usize, +) -> io::Result> +where + S: Serializer + Send + Sync + 'static, + C: Send + 'static, + E: Packet, +{ + drive_with_frames_with_capacity(app, vec![frame], capacity).await +} + +/// Drive `app` with multiple frames using a duplex buffer of `capacity` bytes. +pub async fn drive_with_frames_with_capacity( + app: WireframeApp, + frames: Vec>, + capacity: usize, +) -> io::Result> +where + S: Serializer + Send + Sync + 'static, + C: Send + 'static, + E: Packet, +{ + let (mut client, server) = duplex(capacity); + let server_task = tokio::spawn(async move { + app.handle_connection(server).await; + }); + + send_frames(&mut client, &frames).await?; + client.shutdown().await?; + + let mut buf = Vec::new(); + client.read_to_end(&mut buf).await?; + + server_task.await.expect("app task panicked"); + Ok(buf) +} + +/// Borrow `app` mutably and feed it a single frame. +pub async fn drive_with_frame_mut( + app: &mut WireframeApp, + frame: Vec, +) -> io::Result> +where + S: Serializer + Send + Sync, + C: Send, + E: Packet, +{ + drive_with_frames_mut(app, vec![frame]).await +} + +/// Borrow `app` mutably and feed it multiple frames. +pub async fn drive_with_frames_mut( + app: &mut WireframeApp, + frames: Vec>, +) -> io::Result> +where + S: Serializer + Send + Sync, + C: Send, + E: Packet, +{ + let (mut client, server) = duplex(DEFAULT_CAPACITY); + + send_frames(&mut client, &frames).await?; + client.shutdown().await?; + + app.handle_connection(server).await; + + let mut buf = Vec::new(); + client.read_to_end(&mut buf).await?; + + Ok(buf) +} + +/// Encode `msg` using `bincode` and drive the app with the resulting frame. +pub async fn drive_with_bincode( + app: WireframeApp, + msg: M, +) -> io::Result> +where + S: Serializer + Send + Sync + 'static, + C: Send + 'static, + E: Packet, + M: Encode, +{ + let bytes = bincode::encode_to_vec(msg, bincode::config::standard()) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + let mut framed = BytesMut::with_capacity(4 + bytes.len()); + wireframe::frame::LengthPrefixedProcessor::default() + .encode(&bytes, &mut framed) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + drive_with_frame(app, framed.to_vec()).await +} + +async fn send_frames(stream: &mut DuplexStream, frames: &[Vec]) -> io::Result<()> { + for frame in frames { + stream.write_all(frame).await?; + } + Ok(()) +} diff --git a/wireframe_testing/src/lib.rs b/wireframe_testing/src/lib.rs new file mode 100644 index 00000000..fa4fad28 --- /dev/null +++ b/wireframe_testing/src/lib.rs @@ -0,0 +1,11 @@ +pub mod helpers; + +pub use helpers::{ + drive_with_bincode, + drive_with_frame, + drive_with_frame_mut, + drive_with_frame_with_capacity, + drive_with_frames, + drive_with_frames_mut, + drive_with_frames_with_capacity, +};