From 502c52ee6b647e18bc60a97746ab5d3a8833f586 Mon Sep 17 00:00:00 2001 From: Leynos Date: Sat, 21 Jun 2025 22:39:52 +0100 Subject: [PATCH 1/4] Add design for wireframe-testing crate --- docs/wireframe-testing-crate.md | 87 +++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 docs/wireframe-testing-crate.md diff --git a/docs/wireframe-testing-crate.md b/docs/wireframe-testing-crate.md new file mode 100644 index 00000000..eafe4635 --- /dev/null +++ b/docs/wireframe-testing-crate.md @@ -0,0 +1,87 @@ +# Testing Helpers for `wireframe` + +`wireframe-testing` is a proposed companion crate providing utilities for unit +and integration tests. It focuses on driving `WireframeApp` instances with raw +frames, enabling fast tests without opening real network connections. + +## Motivation + +The existing tests in [`tests/`](../tests) use helper functions such as +`run_app_with_frame` and `run_app_with_frames` to feed length‑prefixed frames +through an in‑memory duplex stream. These helpers simplify testing handlers by +allowing assertions on encoded responses without spinning up a full server. +Encapsulating this logic in a dedicated crate keeps test code concise and +reusable across projects. + +## Crate Layout + +- `wireframe-testing` + - `Cargo.toml` enabling the `tokio` and `rstest` dependencies used by the + helpers. + - `src/lib.rs` exposing asynchronous functions for driving apps with raw + frames. + +The crate would live in a `wireframe-testing/` directory alongside the main +`wireframe` crate. + +## Proposed API + +```rust +use tokio::io::Result as IoResult; +use wireframe::app::WireframeApp; + +/// Feed a single frame into `app` using an in-memory duplex stream. +async fn drive_with_frame(app: WireframeApp, frame: Vec) -> IoResult>; + +/// Drive `app` with multiple frames, returning all bytes written by the app. +async fn drive_with_frames(app: WireframeApp, frames: Vec>) -> IoResult>; +``` + +These functions mirror the behaviour of `run_app_with_frame` and +`run_app_with_frames` found in the repository’s test utilities. They create a +`tokio::io::duplex` stream, spawn the application as a background task, and +write the provided frame(s) to the client side of the stream. After the app +finishes processing, the helpers collect the bytes written back and return them +for inspection. + +### Custom Buffer Capacity + +A variant accepting a buffer `capacity` allows fine‑tuning the size of the +in‑memory duplex channel, matching the existing +`run_app_with_frame_with_capacity` and `run_app_with_frames_with_capacity` +helpers. + +## Example Usage + +```rust +#[tokio::test] +async fn handler_echoes_message() { + let app = WireframeApp::new() + .unwrap() + .frame_processor(LengthPrefixedProcessor::default()) + .route(1, Arc::new(|_| Box::pin(async {}))) + .unwrap(); + + let frame = build_test_frame(); + let out = drive_with_frame(app, frame).await.unwrap(); + assert_eq!(out, expected_bytes()); +} +``` + +This pattern mirrors the style of `tests/routes.rs`, where handlers are invoked +with prebuilt frames and their responses decoded for assertions. + +## Benefits + +- **Isolation**: Handlers can be tested without spinning up a full server or + opening sockets. +- **Reusability**: Projects consuming `wireframe` can depend on + `wireframe-testing` in their dev‑dependencies to leverage the same helpers. +- **Clarity**: Abstracting the duplex stream logic keeps test cases focused on + behaviour instead of transport details. + +## Next Steps + +Implement the crate in a new directory, export the helper functions, and migrate +existing tests to use them. Additional fixtures (e.g., prebuilt frame +processors) can be added over time as test coverage grows. From a24ed5456120366c7f1e3398f95a147933f33785 Mon Sep 17 00:00:00 2001 From: Leynos Date: Sat, 21 Jun 2025 23:18:03 +0100 Subject: [PATCH 2/4] Extend testing crate design --- docs/wireframe-testing-crate.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/wireframe-testing-crate.md b/docs/wireframe-testing-crate.md index eafe4635..ab6cdbbd 100644 --- a/docs/wireframe-testing-crate.md +++ b/docs/wireframe-testing-crate.md @@ -35,6 +35,11 @@ async fn drive_with_frame(app: WireframeApp, frame: Vec) -> IoResult /// Drive `app` with multiple frames, returning all bytes written by the app. async fn drive_with_frames(app: WireframeApp, frames: Vec>) -> IoResult>; + +/// Encode `msg` with `bincode`, wrap it in a frame, and drive the app. +async fn drive_with_bincode(app: WireframeApp, msg: M) -> IoResult> +where + M: bincode::Encode; ``` These functions mirror the behaviour of `run_app_with_frame` and @@ -51,6 +56,14 @@ in‑memory duplex channel, matching the existing `run_app_with_frame_with_capacity` and `run_app_with_frames_with_capacity` helpers. +### Bincode Convenience Wrapper + +For most tests the input frame is preassembled from raw bytes. A small wrapper +can accept any `bincode::Encode` value and perform the encoding and framing +before delegating to `drive_with_frame`. This mirrors the patterns in +`tests/routes.rs` where structs are converted to bytes with `BincodeSerializer` +and then wrapped in a length‑prefixed frame. + ## Example Usage ```rust From 9e1a7b3bab3b24fd4745d1fbdc046803ee46cea4 Mon Sep 17 00:00:00 2001 From: Leynos Date: Sun, 22 Jun 2025 00:59:23 +0100 Subject: [PATCH 3/4] Clarify testing crate design --- docs/wireframe-testing-crate.md | 40 +++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/docs/wireframe-testing-crate.md b/docs/wireframe-testing-crate.md index ab6cdbbd..fb65341d 100644 --- a/docs/wireframe-testing-crate.md +++ b/docs/wireframe-testing-crate.md @@ -1,4 +1,4 @@ -# Testing Helpers for `wireframe` +# `wireframe-testing`: Testing Helpers for Wireframe `wireframe-testing` is a proposed companion crate providing utilities for unit and integration tests. It focuses on driving `WireframeApp` instances with raw @@ -21,6 +21,21 @@ reusable across projects. - `src/lib.rs` exposing asynchronous functions for driving apps with raw frames. +```toml +[dependencies] +tokio = { version = "1", features = ["macros", "rt"] } + +[dev-dependencies] +rstest = "0.18" +``` + +```rust +// src/lib.rs +pub mod helpers; + +pub use helpers::{drive_with_frame, drive_with_frames}; +``` + The crate would live in a `wireframe-testing/` directory alongside the main `wireframe` crate. @@ -31,13 +46,13 @@ use tokio::io::Result as IoResult; use wireframe::app::WireframeApp; /// Feed a single frame into `app` using an in-memory duplex stream. -async fn drive_with_frame(app: WireframeApp, frame: Vec) -> IoResult>; +pub async fn drive_with_frame(app: WireframeApp, frame: Vec) -> IoResult>; /// Drive `app` with multiple frames, returning all bytes written by the app. -async fn drive_with_frames(app: WireframeApp, frames: Vec>) -> IoResult>; +pub async fn drive_with_frames(app: WireframeApp, frames: Vec>) -> IoResult>; /// Encode `msg` with `bincode`, wrap it in a frame, and drive the app. -async fn drive_with_bincode(app: WireframeApp, msg: M) -> IoResult> +pub async fn drive_with_bincode(app: WireframeApp, msg: M) -> IoResult> where M: bincode::Encode; ``` @@ -56,6 +71,20 @@ in‑memory duplex channel, matching the existing `run_app_with_frame_with_capacity` and `run_app_with_frames_with_capacity` helpers. +```rust +pub async fn drive_with_frame_with_capacity( + app: WireframeApp, + frame: Vec, + capacity: usize, +) -> IoResult>; + +pub async fn drive_with_frames_with_capacity( + app: WireframeApp, + frames: Vec>, + capacity: usize, +) -> IoResult>; +``` + ### Bincode Convenience Wrapper For most tests the input frame is preassembled from raw bytes. A small wrapper @@ -67,6 +96,9 @@ and then wrapped in a length‑prefixed frame. ## Example Usage ```rust +use std::sync::Arc; +use wireframe_testing::{drive_with_frame, drive_with_frames}; + #[tokio::test] async fn handler_echoes_message() { let app = WireframeApp::new() From b35b457577301cd80c068480345ddde88b93204c Mon Sep 17 00:00:00 2001 From: Leynos Date: Sun, 22 Jun 2025 01:47:20 +0100 Subject: [PATCH 4/4] Expand wireframe_testing design --- docs/wireframe-testing-crate.md | 40 ++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/docs/wireframe-testing-crate.md b/docs/wireframe-testing-crate.md index fb65341d..bfc8ec76 100644 --- a/docs/wireframe-testing-crate.md +++ b/docs/wireframe-testing-crate.md @@ -1,6 +1,6 @@ -# `wireframe-testing`: Testing Helpers for Wireframe +# `wireframe_testing`: Testing Helpers for Wireframe -`wireframe-testing` is a proposed companion crate providing utilities for unit +`wireframe_testing` is a proposed companion crate providing utilities for unit and integration tests. It focuses on driving `WireframeApp` instances with raw frames, enabling fast tests without opening real network connections. @@ -15,7 +15,7 @@ reusable across projects. ## Crate Layout -- `wireframe-testing` +- `wireframe_testing` - `Cargo.toml` enabling the `tokio` and `rstest` dependencies used by the helpers. - `src/lib.rs` exposing asynchronous functions for driving apps with raw @@ -33,10 +33,16 @@ rstest = "0.18" // src/lib.rs pub mod helpers; -pub use helpers::{drive_with_frame, drive_with_frames}; +pub use helpers::{ + drive_with_frame, + drive_with_frames, + drive_with_frame_with_capacity, + drive_with_frames_with_capacity, + drive_with_bincode, +}; ``` -The crate would live in a `wireframe-testing/` directory alongside the main +The crate would live in a `wireframe_testing/` directory alongside the main `wireframe` crate. ## Proposed API @@ -44,6 +50,7 @@ The crate would live in a `wireframe-testing/` directory alongside the main ```rust use tokio::io::Result as IoResult; use wireframe::app::WireframeApp; +use bincode::Encode; /// Feed a single frame into `app` using an in-memory duplex stream. pub async fn drive_with_frame(app: WireframeApp, frame: Vec) -> IoResult>; @@ -64,6 +71,11 @@ write the provided frame(s) to the client side of the stream. After the app finishes processing, the helpers collect the bytes written back and return them for inspection. +Any I/O errors surfaced by the duplex stream or failures while decoding a +length prefix propagate through the returned `IoResult`. Malformed or +truncated frames therefore cause the future to resolve with an error, +allowing tests to assert on these failure conditions directly. + ### Custom Buffer Capacity A variant accepting a buffer `capacity` allows fine‑tuning the size of the @@ -83,6 +95,12 @@ pub async fn drive_with_frames_with_capacity( frames: Vec>, capacity: usize, ) -> IoResult>; + +/// The above helpers consume the `WireframeApp`. For scenarios +/// where a single app instance should be reused across calls, +/// borrow it mutably instead. +pub async fn drive_with_frame_mut(app: &mut WireframeApp, frame: Vec) -> IoResult>; +pub async fn drive_with_frames_mut(app: &mut WireframeApp, frames: Vec>) -> IoResult>; ``` ### Bincode Convenience Wrapper @@ -93,11 +111,21 @@ before delegating to `drive_with_frame`. This mirrors the patterns in `tests/routes.rs` where structs are converted to bytes with `BincodeSerializer` and then wrapped in a length‑prefixed frame. +```rust +#[derive(bincode::Encode)] +struct Ping(u8); + +let bytes = drive_with_bincode(app, Ping(1)).await.unwrap(); +assert_eq!(bytes, [0, 1]); +``` + ## Example Usage ```rust use std::sync::Arc; use wireframe_testing::{drive_with_frame, drive_with_frames}; +use wireframe::processor::LengthPrefixedProcessor; +use crate::tests::{build_test_frame, expected_bytes}; #[tokio::test] async fn handler_echoes_message() { @@ -121,7 +149,7 @@ with prebuilt frames and their responses decoded for assertions. - **Isolation**: Handlers can be tested without spinning up a full server or opening sockets. - **Reusability**: Projects consuming `wireframe` can depend on - `wireframe-testing` in their dev‑dependencies to leverage the same helpers. + `wireframe_testing` in their dev‑dependencies to leverage the same helpers. - **Clarity**: Abstracting the duplex stream logic keeps test cases focused on behaviour instead of transport details.