Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 163 additions & 0 deletions wireframe_testing/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,21 @@ impl<T> TestSerializer for T where

const DEFAULT_CAPACITY: usize = 4096;

/// Drive `app` with a single length-prefixed `frame` and return the bytes
/// produced by the server.
///
/// The app runs on an in-memory duplex stream so tests need not open real
/// sockets.
///
/// ```rust
/// # use wireframe_testing::{drive_with_frame, processor};
/// # use wireframe::app::WireframeApp;
/// # async fn demo() -> tokio::io::Result<()> {
/// let app = WireframeApp::new().frame_processor(processor()).unwrap();
/// let bytes = drive_with_frame(app, vec![1, 2, 3]).await?;
/// # Ok(())
/// # }
/// ```
pub async fn drive_with_frame<S, C, E>(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider replacing the many nearly identical forwarding helper functions with a small set of macros to generate them automatically.

Here’s one way to collapse all of those identical “forwarding” helpers into a tiny macro_rules! rather than hand‐writing each one. You still keep the exact same public API, docs, and calls under the hood, but you remove dozens of near‐duplicate lines:

// before your helpers:
const DEFAULT_CAPACITY: usize = 4096;

// 1) define a small macro for the two shapes of forwarding functions
macro_rules! forward_default {
    // `drive_with_frame(mut)?`, `run_app_with_frame(mut)?`, etc.
    (
        /// $docs:tt
        $vis:vis fn $fn_name:ident ( $app:ident : $app_ty:ty, $arg:ident : $arg_ty:ty ) -> $ret:ty
        => $inner:ident ( $app, $arg, DEFAULT_CAPACITY )
    ) => {
        #[doc = $docs]
        $vis async fn $fn_name<S, C, E>(
            $app: $app_ty,
            $arg: $arg_ty,
        ) -> $ret
        where
            S: TestSerializer,
            C: Send + 'static,
            E: Packet,
        {
            $inner($app, $arg, DEFAULT_CAPACITY).await
        }
    };
}

macro_rules! forward_with_capacity {
    (
        /// $docs:tt
        $vis:vis fn $fn_name:ident ( $app:ident : $app_ty:ty, $arg:ident : $arg_ty:ty, capacity: usize ) -> $ret:ty
        => $inner:ident ( $app, $arg, capacity )
    ) => {
        #[doc = $docs]
        $vis async fn $fn_name<S, C, E>(
            $app: $app_ty,
            $arg: $arg_ty,
            capacity: usize,
        ) -> $ret
        where
            S: TestSerializer,
            C: Send + 'static,
            E: Packet,
        {
            $inner($app, $arg, capacity).await
        }
    };
}

// 2) invoke it for each variant.  Note how all the `drive_*`, `run_*`, `*_mut`
//    helpers collapse into four macro‐invocations:
forward_default! {
    /// Drive `app` with a single length‐prefixed `frame` and return the bytes
    pub fn drive_with_frame(app: WireframeApp<S, C, E>, frame: Vec<u8>) -> io::Result<Vec<u8>>
    => drive_with_frame_with_capacity(app, frame, DEFAULT_CAPACITY)
}

forward_with_capacity! {
    /// Drive `app` with a single frame using a duplex buffer of `capacity` bytes.
    pub fn drive_with_frame_with_capacity(app: WireframeApp<S, C, E>, frame: Vec<u8>, capacity: usize) -> io::Result<Vec<u8>>
    => drive_with_frames_with_capacity(app, frame, capacity)
}

// …and repeat for `drive_with_frames`, `drive_with_frames_with_capacity`,
//  `drive_with_frame_mut`, `drive_with_frame_with_capacity_mut`, etc.
//  Since each block is just forwarding, you now get all of them
//  from 4–8 lines of macro invocations instead of 50+ near‐dupes.

You can apply the same two macros for your run_* set, too. This keeps the signatures and docs exactly the same but collapses your “dozens of almost identical functions” to just a handful of macro invocations.

app: WireframeApp<S, C, E>,
frame: Vec<u8>,
Expand All @@ -45,6 +60,19 @@ where
drive_with_frame_with_capacity(app, frame, DEFAULT_CAPACITY).await
}

/// Drive `app` with a single frame using a duplex buffer of `capacity` bytes.
///
/// Adjusting the buffer size helps exercise edge cases such as small channels.
///
/// ```rust
/// # use wireframe_testing::{drive_with_frame_with_capacity, processor};
/// # use wireframe::app::WireframeApp;
/// # async fn demo() -> tokio::io::Result<()> {
/// let app = WireframeApp::new().frame_processor(processor()).unwrap();
/// let bytes = drive_with_frame_with_capacity(app, vec![0], 512).await?;
/// # Ok(())
/// # }
/// ```
pub async fn drive_with_frame_with_capacity<S, C, E>(
app: WireframeApp<S, C, E>,
frame: Vec<u8>,
Expand All @@ -58,6 +86,19 @@ where
drive_with_frames_with_capacity(app, vec![frame], capacity).await
}

/// Drive `app` with a sequence of frames using the default buffer size.
///
/// Each frame is written to the duplex stream in order.
///
/// ```rust
/// # use wireframe_testing::{drive_with_frames, processor};
/// # use wireframe::app::WireframeApp;
/// # async fn demo() -> tokio::io::Result<()> {
/// let app = WireframeApp::new().frame_processor(processor()).unwrap();
/// let out = drive_with_frames(app, vec![vec![1], vec![2]]).await?;
/// # Ok(())
/// # }
/// ```
pub async fn drive_with_frames<S, C, E>(
app: WireframeApp<S, C, E>,
frames: Vec<Vec<u8>>,
Expand All @@ -70,6 +111,19 @@ where
drive_with_frames_with_capacity(app, frames, DEFAULT_CAPACITY).await
}

/// Drive `app` with multiple frames using a duplex buffer of `capacity` bytes.
///
/// This variant exposes the buffer size for fine-grained control in tests.
///
/// ```rust
/// # use wireframe_testing::{drive_with_frames_with_capacity, processor};
/// # use wireframe::app::WireframeApp;
/// # async fn demo() -> tokio::io::Result<()> {
/// let app = WireframeApp::new().frame_processor(processor()).unwrap();
/// let out = drive_with_frames_with_capacity(app, vec![vec![1], vec![2]], 1024).await?;
/// # Ok(())
/// # }
/// ```
pub async fn drive_with_frames_with_capacity<S, C, E>(
app: WireframeApp<S, C, E>,
frames: Vec<Vec<u8>>,
Expand Down Expand Up @@ -102,6 +156,18 @@ where
}
}

/// Feed a single frame into a mutable `app`, allowing the instance to be reused
/// across calls.
///
/// ```rust
/// # use wireframe_testing::{drive_with_frame_mut, processor};
/// # use wireframe::app::WireframeApp;
/// # async fn demo() -> tokio::io::Result<()> {
/// let mut app = WireframeApp::new().frame_processor(processor()).unwrap();
/// let bytes = drive_with_frame_mut(&mut app, vec![1]).await?;
/// # Ok(())
/// # }
/// ```
pub async fn drive_with_frame_mut<S, C, E>(
app: &mut WireframeApp<S, C, E>,
frame: Vec<u8>,
Expand All @@ -114,6 +180,17 @@ where
drive_with_frame_with_capacity_mut(app, frame, DEFAULT_CAPACITY).await
}

/// Feed a single frame into `app` using a duplex buffer of `capacity` bytes.
///
/// ```rust
/// # use wireframe_testing::{drive_with_frame_with_capacity_mut, processor};
/// # use wireframe::app::WireframeApp;
/// # async fn demo() -> tokio::io::Result<()> {
/// let mut app = WireframeApp::new().frame_processor(processor()).unwrap();
/// let bytes = drive_with_frame_with_capacity_mut(&mut app, vec![1], 256).await?;
/// # Ok(())
/// # }
/// ```
pub async fn drive_with_frame_with_capacity_mut<S, C, E>(
app: &mut WireframeApp<S, C, E>,
frame: Vec<u8>,
Expand All @@ -127,6 +204,17 @@ where
drive_with_frames_with_capacity_mut(app, vec![frame], capacity).await
}

/// Feed multiple frames into a mutable `app`.
///
/// ```rust
/// # use wireframe_testing::{drive_with_frames_mut, processor};
/// # use wireframe::app::WireframeApp;
/// # async fn demo() -> tokio::io::Result<()> {
/// let mut app = WireframeApp::new().frame_processor(processor()).unwrap();
/// let out = drive_with_frames_mut(&mut app, vec![vec![1], vec![2]]).await?;
/// # Ok(())
/// # }
/// ```
pub async fn drive_with_frames_mut<S, C, E>(
app: &mut WireframeApp<S, C, E>,
frames: Vec<Vec<u8>>,
Expand All @@ -139,6 +227,17 @@ where
drive_with_frames_with_capacity_mut(app, frames, DEFAULT_CAPACITY).await
}

/// Feed multiple frames into `app` with a duplex buffer of `capacity` bytes.
///
/// ```rust
/// # use wireframe_testing::{drive_with_frames_with_capacity_mut, processor};
/// # use wireframe::app::WireframeApp;
/// # async fn demo() -> tokio::io::Result<()> {
/// let mut app = WireframeApp::new().frame_processor(processor()).unwrap();
/// let out = drive_with_frames_with_capacity_mut(&mut app, vec![vec![1], vec![2]], 64).await?;
/// # Ok(())
/// # }
/// ```
pub async fn drive_with_frames_with_capacity_mut<S, C, E>(
app: &mut WireframeApp<S, C, E>,
frames: Vec<Vec<u8>>,
Expand Down Expand Up @@ -167,6 +266,19 @@ where
buf
}

/// Encode `msg` using bincode, frame it and drive `app`.
///
/// ```rust
/// # use wireframe_testing::{drive_with_bincode, processor};
/// # use wireframe::app::WireframeApp;
/// #[derive(bincode::Encode)]
/// struct Ping(u8);
/// # async fn demo() -> tokio::io::Result<()> {
/// let app = WireframeApp::new().frame_processor(processor()).unwrap();
/// let bytes = drive_with_bincode(app, Ping(1)).await?;
/// # Ok(())
/// # }
/// ```
pub async fn drive_with_bincode<M, S, C, E>(
app: WireframeApp<S, C, E>,
msg: M,
Expand Down Expand Up @@ -194,6 +306,16 @@ where
///
/// Returns any I/O errors encountered while interacting with the in-memory
/// duplex stream.
///
/// ```rust
/// # use wireframe_testing::{run_app_with_frame, processor};
/// # use wireframe::app::WireframeApp;
/// # async fn demo() -> tokio::io::Result<()> {
/// let app = WireframeApp::new().frame_processor(processor()).unwrap();
/// let out = run_app_with_frame(app, vec![1]).await?;
/// # Ok(())
/// # }
/// ```
pub async fn run_app_with_frame<S, C, E>(
app: WireframeApp<S, C, E>,
frame: Vec<u8>,
Expand All @@ -215,6 +337,16 @@ where
/// # Panics
///
/// Panics if the spawned task running the application panics.
///
/// ```rust
/// # use wireframe_testing::{run_app_with_frame_with_capacity, processor};
/// # use wireframe::app::WireframeApp;
/// # async fn demo() -> tokio::io::Result<()> {
/// let app = WireframeApp::new().frame_processor(processor()).unwrap();
/// let out = run_app_with_frame_with_capacity(app, vec![1], 128).await?;
/// # Ok(())
/// # }
/// ```
pub async fn run_app_with_frame_with_capacity<S, C, E>(
app: WireframeApp<S, C, E>,
frame: Vec<u8>,
Expand All @@ -234,6 +366,16 @@ where
///
/// Returns any I/O errors encountered while interacting with the in-memory
/// duplex stream.
///
/// ```rust
/// # use wireframe_testing::{run_app_with_frames, processor};
/// # use wireframe::app::WireframeApp;
/// # async fn demo() -> tokio::io::Result<()> {
/// let app = WireframeApp::new().frame_processor(processor()).unwrap();
/// let out = run_app_with_frames(app, vec![vec![1], vec![2]]).await?;
/// # Ok(())
/// # }
/// ```
#[allow(dead_code)]
pub async fn run_app_with_frames<S, C, E>(
app: WireframeApp<S, C, E>,
Expand All @@ -256,6 +398,16 @@ where
/// # Panics
///
/// Panics if the spawned task running the application panics.
///
/// ```rust
/// # use wireframe_testing::{run_app_with_frames_with_capacity, processor};
/// # use wireframe::app::WireframeApp;
/// # async fn demo() -> tokio::io::Result<()> {
/// let app = WireframeApp::new().frame_processor(processor()).unwrap();
/// let out = run_app_with_frames_with_capacity(app, vec![vec![1], vec![2]], 64).await?;
/// # Ok(())
/// # }
/// ```
pub async fn run_app_with_frames_with_capacity<S, C, E>(
app: WireframeApp<S, C, E>,
frames: Vec<Vec<u8>>,
Expand Down Expand Up @@ -291,6 +443,17 @@ where
/// # Panics
///
/// Panics if `handle_connection` fails.
///
/// ```rust
/// # use wireframe_testing::{run_with_duplex_server, processor};
/// # use wireframe::app::WireframeApp;
/// # async fn demo() {
/// let app = WireframeApp::new()
/// .frame_processor(processor())
/// .unwrap();
/// run_with_duplex_server(app).await;
/// }
/// ```
pub async fn run_with_duplex_server<S, C, E>(app: WireframeApp<S, C, E>)
where
S: TestSerializer,
Expand Down
Loading