Skip to content
Closed
Show file tree
Hide file tree
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
8 changes: 8 additions & 0 deletions tests/push.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use rstest::rstest;
use tokio::time::{self, Duration};
use wireframe::push::{PushError, PushPolicy, PushPriority, PushQueues};

/// Frames are delivered to queues matching their push priority.
#[tokio::test]
async fn frames_routed_to_correct_priority_queues() {
let (mut queues, handle) = PushQueues::bounded(1, 1);
Expand All @@ -22,6 +23,7 @@ async fn frames_routed_to_correct_priority_queues() {
assert_eq!(frame2, 1);
}

/// `try_push` honours the selected queue policy when full.
#[tokio::test]
async fn try_push_respects_policy() {
let (mut queues, handle) = PushQueues::bounded(1, 1);
Expand All @@ -37,6 +39,7 @@ async fn try_push_respects_policy() {
assert_eq!(last, 3);
}

/// Push attempts return `Closed` when all queues have been shut down.
#[tokio::test]
async fn push_queues_error_on_closed() {
let (queues, handle) = PushQueues::bounded(1, 1);
Expand All @@ -50,6 +53,7 @@ async fn push_queues_error_on_closed() {
assert!(matches!(res, Err(PushError::Closed)));
}

/// A push beyond the configured rate is blocked.
#[rstest]
#[case::high(PushPriority::High)]
#[case::low(PushPriority::Low)]
Expand Down Expand Up @@ -85,6 +89,7 @@ async fn rate_limiter_blocks_when_exceeded(#[case] priority: PushPriority) {
assert_eq!((first, second), (1, 3));
}

/// Exceeding the rate limit succeeds after the window has passed.
#[tokio::test]
async fn rate_limiter_allows_after_wait() {
time::pause();
Expand All @@ -98,6 +103,7 @@ async fn rate_limiter_allows_after_wait() {
assert_eq!((a, b), (1, 2));
}

/// The limiter counts pushes from all priority queues.
#[tokio::test]
async fn rate_limiter_shared_across_priorities() {
time::pause();
Expand All @@ -118,6 +124,7 @@ async fn rate_limiter_shared_across_priorities() {
assert_eq!(frame2, 2);
}

/// Unlimited queues never block pushes.
#[tokio::test]
async fn unlimited_queues_do_not_block() {
time::pause();
Expand All @@ -131,6 +138,7 @@ async fn unlimited_queues_do_not_block() {
assert_eq!((a, b), (1, 2));
}

/// A burst up to capacity succeeds and further pushes are blocked.
#[tokio::test]
async fn rate_limiter_allows_burst_within_capacity_and_blocks_excess() {
time::pause();
Expand Down
8 changes: 8 additions & 0 deletions tests/push_policies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use tokio::{
use wireframe::push::{PushPolicy, PushPriority, PushQueues};
use wireframe_testing::{LoggerHandle, logger};

/// Builds a single-thread [`Runtime`] for async tests.
#[allow(
unused_braces,
reason = "rustc false positive for single line rstest fixtures"
Expand All @@ -23,6 +24,7 @@ fn rt() -> Runtime {
.expect("failed to build test runtime")
}

/// Verifies how queue policies log and drop when the queue is full.
#[rstest]
#[case::drop_if_full(PushPolicy::DropIfFull, false, "push queue full")]
#[case::warn_and_drop(PushPolicy::WarnAndDropIfFull, true, "push queue full")]
Expand Down Expand Up @@ -64,6 +66,7 @@ fn push_policy_behaviour(
});
}

/// Dropped frames are forwarded to the dead letter queue.
#[rstest]
fn dropped_frame_goes_to_dlq(rt: Runtime) {
rt.block_on(async {
Expand All @@ -82,12 +85,15 @@ fn dropped_frame_goes_to_dlq(rt: Runtime) {
});
}

/// Preloads the DLQ to simulate a full queue.
fn setup_dlq_full(tx: &mpsc::Sender<u8>, _rx: &mut Option<mpsc::Receiver<u8>>) {
tx.try_send(99).unwrap();
}

/// Drops the receiver to simulate a closed DLQ channel.
fn setup_dlq_closed(_: &mpsc::Sender<u8>, rx: &mut Option<mpsc::Receiver<u8>>) { drop(rx.take()); }

/// Asserts that one message is queued and the DLQ then reports empty.
fn assert_dlq_full(rx: &mut Option<mpsc::Receiver<u8>>) -> BoxFuture<'_, ()> {
Box::pin(async move {
let receiver = rx.as_mut().expect("receiver missing");
Expand All @@ -96,8 +102,10 @@ fn assert_dlq_full(rx: &mut Option<mpsc::Receiver<u8>>) -> BoxFuture<'_, ()> {
})
}

/// Confirms no receiver is present when the DLQ is closed.
fn assert_dlq_closed(_: &mut Option<mpsc::Receiver<u8>>) -> BoxFuture<'_, ()> { Box::pin(async {}) }

/// Parameterised checks for error logs when DLQ interactions fail.
#[rstest]
#[case::dlq_full(setup_dlq_full, PushPolicy::WarnAndDropIfFull, "DLQ", assert_dlq_full)]
#[case::dlq_closed(setup_dlq_closed, PushPolicy::DropIfFull, "closed", assert_dlq_closed)]
Expand Down
33 changes: 32 additions & 1 deletion wireframe_testing/src/logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@
//! This module provides a global, thread-safe logger handle for capturing and
//! inspecting log output during tests. The [`LoggerHandle`] ensures exclusive
//! access to prevent interference between concurrent tests.
//!
//! ```
//! use wireframe_testing::logger;
//!
//! #[tokio::test]
//! async fn logs_are_collected() {
//! let mut log = logger();
//! log::info!("example");
//! assert!(log.pop().is_some());
//! }
//! ```

use std::sync::{Mutex, MutexGuard, OnceLock};

Expand All @@ -11,13 +22,32 @@
/// Handle to the global logger with exclusive access.
///
/// This guard ensures tests do not interfere with each other's log capture by
/// serialising access to a [`logtest::Logger`].
/// serialising access to a [`logtest::Logger`]. Acquire it using [`logger`] or
/// [`LoggerHandle::new`].
///
/// ```
/// use wireframe_testing::logger;
/// # use log::warn;
///
/// let mut log = logger();
/// warn!("warned");
/// assert!(log.pop().is_some());
/// ```
pub struct LoggerHandle {
guard: MutexGuard<'static, Logger>,
}

impl LoggerHandle {
/// Acquire the global [`Logger`] instance.

Check warning on line 41 in wireframe_testing/src/logging.rs

View workflow job for this annotation

GitHub Actions / build-test

Diff in /home/runner/work/wireframe/wireframe/wireframe_testing/src/logging.rs
///
/// ```
/// use wireframe_testing::LoggerHandle;
/// use log::warn;
///
/// let mut log = LoggerHandle::new();
/// warn!("warned");
/// assert!(log.guard.pop().is_some());
/// ```
Comment on lines +43 to +50
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.

⚠️ Potential issue

Fix doctest compile error – avoid referencing the private guard field

The doctest accesses log.guard, which is private outside the module, so cargo test --doc will fail.

- /// let mut log = LoggerHandle::new();
- /// warn!("warned");
- /// assert!(log.guard.pop().is_some());
+ /// let mut log = LoggerHandle::new();
+ /// warn!("warned");
+ /// assert!(log.pop().is_some());
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// ```
/// use wireframe_testing::LoggerHandle;
/// use log::warn;
///
/// let mut log = LoggerHandle::new();
/// warn!("warned");
/// assert!(log.guard.pop().is_some());
/// ```
///
🤖 Prompt for AI Agents
In wireframe_testing/src/logging.rs around lines 43 to 50, the doctest
references the private field `guard` of `LoggerHandle`, causing compile errors
during documentation tests. To fix this, modify the doctest to avoid direct
access to the private `guard` field by using a public method or interface
provided by `LoggerHandle` to check or retrieve log entries instead of accessing
`guard` directly.

pub fn new() -> Self {
static LOGGER: OnceLock<Mutex<Logger>> = OnceLock::new();

Expand All @@ -38,6 +68,7 @@
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.guard }
}

/// rstest fixture returning a [`LoggerHandle`] for log assertions.
#[allow(
unused_braces,
reason = "rustc false positive for single line rstest fixtures"
Expand Down
Loading