diff --git a/.gitignore b/.gitignore index 324c57f7..221eb3b3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target/ **/*.rs.bk +.crush diff --git a/AGENTS.md b/AGENTS.md index 06f65d12..207724a7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -30,6 +30,7 @@ the test logic. - **Keep file size managable.** No single code file may be longer than 400 lines. + Long switch statements or dispatch tables should be broken up by feature and constituents colocated with targets. Large blocks of test data should be moved to external data files. diff --git a/CRUSH.md b/CRUSH.md new file mode 120000 index 00000000..47dc3e3d --- /dev/null +++ b/CRUSH.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index f042e08e..daf3fbc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2832,6 +2832,7 @@ dependencies = [ "tokio", "tokio-util", "tracing", + "tracing-subscriber", "tracing-test", "wireframe_testing", ] @@ -2842,6 +2843,7 @@ version = "0.1.0" dependencies = [ "bincode", "bytes", + "futures", "log", "logtest", "metrics-util", diff --git a/wireframe_testing/src/helpers.rs b/wireframe_testing/src/helpers.rs index 13749250..a31f2316 100644 --- a/wireframe_testing/src/helpers.rs +++ b/wireframe_testing/src/helpers.rs @@ -33,6 +33,29 @@ impl TestSerializer for T where const DEFAULT_CAPACITY: usize = 4096; +macro_rules! forward_default { + ( + $(#[$docs:meta])* $vis:vis fn $name:ident( + $app:ident : $app_ty:ty, + $arg:ident : $arg_ty:ty + ) -> $ret:ty + => $inner:ident($app_expr:ident, $arg_expr:expr) + ) => { + $(#[$docs])* + $vis async fn $name( + $app: $app_ty, + $arg: $arg_ty, + ) -> $ret + where + S: TestSerializer, + C: Send + 'static, + E: Packet, + { + $inner($app_expr, $arg_expr, DEFAULT_CAPACITY).await + } + }; +} + async fn drive_internal( server_fn: F, frames: Vec>, @@ -80,82 +103,85 @@ where Ok(buf) } -/// 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( - app: WireframeApp, - frame: Vec, -) -> io::Result> -where - S: TestSerializer, - C: Send + 'static, - E: Packet, -{ - drive_with_frame_with_capacity(app, frame, DEFAULT_CAPACITY).await +macro_rules! forward_with_capacity { + ( + $(#[$docs:meta])* $vis:vis fn $name:ident( + $app:ident : $app_ty:ty, + $arg:ident : $arg_ty:ty, + capacity: usize + ) -> $ret:ty + => $inner:ident($app_expr:ident, $arg_expr:expr, capacity) + ) => { + $(#[$docs])* + $vis async fn $name( + $app: $app_ty, + $arg: $arg_ty, + capacity: usize, + ) -> $ret + where + S: TestSerializer, + C: Send + 'static, + E: Packet, + { + $inner($app_expr, $arg_expr, 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( - app: WireframeApp, - frame: Vec, - capacity: usize, -) -> io::Result> -where - S: TestSerializer, - C: Send + 'static, - E: Packet, -{ - drive_with_frames_with_capacity(app, vec![frame], capacity).await +forward_default! { + /// 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 fn drive_with_frame(app: WireframeApp, frame: Vec) -> io::Result> + => drive_with_frame_with_capacity(app, frame) } -/// 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( - app: WireframeApp, - frames: Vec>, -) -> io::Result> -where - S: TestSerializer, - C: Send + 'static, - E: Packet, -{ - drive_with_frames_with_capacity(app, frames, DEFAULT_CAPACITY).await +forward_with_capacity! { + /// 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 fn drive_with_frame_with_capacity(app: WireframeApp, frame: Vec, capacity: usize) -> io::Result> + => drive_with_frames_with_capacity(app, vec![frame], capacity) +} + +forward_default! { + /// 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 fn drive_with_frames(app: WireframeApp, frames: Vec>) -> io::Result> + => drive_with_frames_with_capacity(app, frames) } /// Drive `app` with multiple frames using a duplex buffer of `capacity` bytes. @@ -189,75 +215,53 @@ where .await } -/// 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( - app: &mut WireframeApp, - frame: Vec, -) -> io::Result> -where - S: TestSerializer, - C: Send + 'static, - E: Packet, -{ - drive_with_frame_with_capacity_mut(app, frame, DEFAULT_CAPACITY).await +forward_default! { + /// 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 fn drive_with_frame_mut(app: &mut WireframeApp, frame: Vec) -> io::Result> + => drive_with_frame_with_capacity_mut(app, frame) } -/// 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( - app: &mut WireframeApp, - frame: Vec, - capacity: usize, -) -> io::Result> -where - S: TestSerializer, - C: Send + 'static, - E: Packet, -{ - drive_with_frames_with_capacity_mut(app, vec![frame], capacity).await +forward_with_capacity! { + /// 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 fn drive_with_frame_with_capacity_mut(app: &mut WireframeApp, frame: Vec, capacity: usize) -> io::Result> + => drive_with_frames_with_capacity_mut(app, vec![frame], capacity) } -/// 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( - app: &mut WireframeApp, - frames: Vec>, -) -> io::Result> -where - S: TestSerializer, - C: Send + 'static, - E: Packet, -{ - drive_with_frames_with_capacity_mut(app, frames, DEFAULT_CAPACITY).await +forward_default! { + /// 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 fn drive_with_frames_mut(app: &mut WireframeApp, frames: Vec>) -> io::Result> + => drive_with_frames_with_capacity_mut(app, frames) } /// Feed multiple frames into `app` with a duplex buffer of `capacity` bytes. @@ -323,93 +327,71 @@ where drive_with_frame(app, framed.to_vec()).await } -/// 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. -/// -/// ```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( - app: WireframeApp, - frame: Vec, -) -> io::Result> -where - S: TestSerializer, - C: Send + 'static, - E: Packet, -{ - run_app_with_frame_with_capacity(app, frame, DEFAULT_CAPACITY).await +forward_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. + /// + /// ```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 fn run_app_with_frame(app: WireframeApp, frame: Vec) -> io::Result> + => run_app_with_frame_with_capacity(app, frame) } -/// 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. -/// -/// ```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( - app: WireframeApp, - frame: Vec, - capacity: usize, -) -> io::Result> -where - S: TestSerializer, - C: Send + 'static, - E: Packet, -{ - run_app_with_frames_with_capacity(app, vec![frame], capacity).await +forward_with_capacity! { + /// 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. + /// + /// ```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 fn run_app_with_frame_with_capacity(app: WireframeApp, frame: Vec, capacity: usize) -> io::Result> + => run_app_with_frames_with_capacity(app, vec![frame], capacity) } -/// 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. -/// -/// ```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( - app: WireframeApp, - frames: Vec>, -) -> io::Result> -where - S: TestSerializer, - C: Send + 'static, - E: Packet, -{ - run_app_with_frames_with_capacity(app, frames, DEFAULT_CAPACITY).await +forward_default! { + #[allow(dead_code)] + /// 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. + /// + /// ```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(()) + /// # } + /// ``` + pub fn run_app_with_frames(app: WireframeApp, frames: Vec>) -> io::Result> + => run_app_with_frames_with_capacity(app, frames) } /// Drive `app` with multiple frames using a duplex buffer of `capacity` bytes.