Files
announcement/tests/close_behavior_tests.rs
T
swaits e8e55b7676 chore: initial release v0.1.0
Runtime-agnostic oneshot broadcast channel for Rust.
2025-10-13 19:36:37 -06:00

248 lines
7.2 KiB
Rust

//! Tests specifically for close() behavior and mutation testing coverage
//!
//! These tests target gaps in mutation testing, particularly around the
//! explicit close() method.
use announcement::{Announcement, ListenError};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
/// Test: Verify close() actually sets the closed flag
///
/// Verifies: close() actually sets the closed flag
/// Category: close
#[test]
fn test_close_sets_closed_flag() {
let (announcer, announcement) = Announcement::<i32>::new();
// Initially not closed
assert!(!announcement.is_closed());
// Call close()
announcer.close();
// Verify closed flag is set
assert!(announcement.is_closed());
}
/// Test: Verify close() notifies waiting async listeners
///
/// Verifies: close() notifies waiting async listeners
/// Category: close
#[tokio::test]
async fn test_close_notifies_async_listeners() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// Spawn task that waits
let handle = tokio::spawn(async move { listener.listen().await });
// Give listener time to start waiting
tokio::time::sleep(Duration::from_millis(10)).await;
// Close the announcer
announcer.close();
// Listener should receive Closed error quickly
let result = tokio::time::timeout(Duration::from_millis(100), handle).await;
assert!(result.is_ok(), "Listener should be notified immediately");
let listen_result = result.unwrap().unwrap();
assert_eq!(listen_result, Err(ListenError::Closed));
}
/// Test: Close test: close notifies blocking listeners
///
/// Verifies: close notifies blocking listeners
/// Category: close
#[cfg(feature = "std")]
#[test]
fn test_close_notifies_blocking_listeners() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// Spawn thread that waits
let handle =
thread::spawn(move || listener.listen_timeout_blocking(Duration::from_millis(100)));
// Give listener time to start waiting
thread::sleep(Duration::from_millis(10));
// Close the announcer
announcer.close();
// Listener should receive Closed error quickly
let result = handle.join().unwrap();
assert_eq!(result, Err(ListenError::Closed));
}
/// Test: Verify close() notifies multiple waiting listeners
///
/// Verifies: close() notifies multiple waiting listeners
/// Category: close
#[tokio::test]
async fn test_close_notifies_all_listeners() {
let (announcer, announcement) = Announcement::<i32>::new();
// Create multiple listeners
let listeners: Vec<_> = (0..10).map(|_| announcement.listener()).collect();
// Spawn tasks for all listeners
let mut handles = Vec::new();
for listener in listeners {
handles.push(tokio::spawn(async move { listener.listen().await }));
}
// Give listeners time to start waiting
tokio::time::sleep(Duration::from_millis(10)).await;
// Close the announcer
announcer.close();
// All listeners should receive Closed error
for handle in handles {
let result = tokio::time::timeout(Duration::from_millis(100), handle).await;
assert!(result.is_ok(), "All listeners should be notified");
let listen_result = result.unwrap().unwrap();
assert_eq!(listen_result, Err(ListenError::Closed));
}
}
/// Test: Verify close() works even with no listeners
///
/// Verifies: close() works even with no listeners
/// Category: close
#[test]
fn test_close_with_no_listeners() {
let (announcer, announcement) = Announcement::<i32>::new();
// Close without creating any listeners
announcer.close();
// Verify closed flag is set
assert!(announcement.is_closed());
// Create a listener after close
let listener = announcement.listener();
assert_eq!(listener.try_listen(), None);
assert!(listener.is_closed());
}
/// Test: Verify close() prevents future listeners from waiting
///
/// Verifies: close() prevents future listeners from waiting
/// Category: close
#[tokio::test]
async fn test_close_prevents_future_waiting() {
let (announcer, announcement) = Announcement::<i32>::new();
// Close immediately
announcer.close();
// Create listener after close
let listener = announcement.listener();
// listen() should return immediately with Closed
let result = tokio::time::timeout(Duration::from_millis(50), listener.listen()).await;
assert!(result.is_ok(), "listen() should not hang after close");
let listen_result = result.unwrap();
assert_eq!(listen_result, Err(ListenError::Closed));
}
/// Test: Verify close() interacts correctly with try_listen()
///
/// Verifies: close() interacts correctly with try_listen()
/// Category: close
#[test]
fn test_close_try_listen_interaction() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// try_listen() returns None before close
assert_eq!(listener.try_listen(), None);
assert!(!listener.is_closed());
// Close the announcer
announcer.close();
// try_listen() still returns None after close
assert_eq!(listener.try_listen(), None);
// But is_closed() returns true
assert!(listener.is_closed());
}
/// Test: Close followed by listener checks in rapid succession
///
/// Verifies: Close followed by listener checks in rapid succession
/// Category: close
#[test]
fn test_close_rapid_checks() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
announcer.close();
// Rapidly check closed status
for _ in 0..1000 {
assert!(listener.is_closed());
assert_eq!(listener.try_listen(), None);
}
}
/// Test: Verify close() with Arc-wrapped value type
///
/// Verifies: close() with Arc-wrapped value type
/// Category: close
#[test]
fn test_close_with_arc_type() {
let (announcer, announcement) = Announcement::<Arc<String>>::new();
let listener = announcement.listener();
announcer.close();
assert!(announcement.is_closed());
assert!(listener.is_closed());
assert_eq!(listener.try_listen(), None);
}
/// Test: Multiple threads checking closed status after close()
///
/// Verifies: Multiple threads checking closed status after close()
/// Category: close
#[test]
fn test_close_concurrent_status_checks() {
use std::sync::Barrier;
let (announcer, announcement) = Announcement::<i32>::new();
let barrier = Arc::new(Barrier::new(11)); // 10 threads + main thread
// Create listeners in multiple threads
let handles: Vec<_> = (0..10)
.map(|_| {
let ann = announcement.clone();
let barrier = Arc::clone(&barrier);
thread::spawn(move || {
let listener = ann.listener();
// Wait for close() to complete via barrier synchronization
barrier.wait();
listener.is_closed()
})
})
.collect();
// Close the announcer
announcer.close();
// Synchronize: ensure close() completes before threads check
barrier.wait();
// All threads should see closed = true
for handle in handles {
let is_closed = handle.join().unwrap();
assert!(is_closed, "All threads should see closed flag");
}
}