248 lines
7.2 KiB
Rust
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");
|
|
}
|
|
}
|