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

356 lines
10 KiB
Rust

//! Targeted tests for Drop implementation mutations
//!
//! These tests specifically target the Drop impl for Announcer<T> to catch
//! mutations that would otherwise cause timeouts. They use short timeouts
//! to fail fast instead of hanging.
use announcement::{Announcement, ListenError};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
/// Test: Drop sets closed flag - verify with short timeout
///
/// Verifies: Drop sets closed flag - with short timeout
/// Category: drop
#[test]
fn test_drop_sets_closed_flag_quick() {
let (announcer, announcement) = Announcement::<i32>::new();
// Verify not closed initially
assert!(!announcement.is_closed());
// Drop the announcer
drop(announcer);
// CRITICAL: Must set closed flag
// This catches the mutant that replaces Drop body with ()
assert!(announcement.is_closed(), "Drop must set closed flag");
}
/// Test: Drop test: drop wakes blocking listeners quick
///
/// Verifies: drop wakes blocking listeners quick
/// Category: drop
#[cfg(feature = "std")]
#[test]
fn test_drop_wakes_blocking_listeners_quick() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// Spawn thread that will wait
let handle =
thread::spawn(move || listener.listen_timeout_blocking(Duration::from_millis(100)));
// Give thread time to start waiting
thread::sleep(Duration::from_millis(10));
// Drop announcer - should wake listener
drop(announcer);
// Listener should receive Closed error, not Timeout
// If Drop doesn't work, this will timeout
let result = handle.join().expect("thread should not panic");
assert_eq!(
result,
Err(ListenError::Closed),
"Drop must notify waiting listeners with Closed error"
);
}
/// Test: Drop with conditional check - verifies the condition logic
///
/// Verifies: Drop with conditional check - verifies the condition logic
/// Category: drop
#[test]
fn test_drop_conditional_already_announced() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// Announce a value
announcer.announce(42).unwrap();
// Drop happens here, but should NOT set closed flag
// because value was already announced
// Verify: NOT closed (because value was announced)
assert!(
!announcement.is_closed(),
"Drop should not set closed flag if value was announced"
);
// Verify: Can still get the value
assert_eq!(listener.try_listen(), Some(42));
}
/// Test: Drop with conditional check - verifies the !closed check
///
/// Verifies: Drop with conditional check - verifies the !closed check
/// Category: drop
#[test]
fn test_drop_conditional_already_closed() {
let (announcer, announcement) = Announcement::<i32>::new();
// Explicitly close first
announcer.close();
// At this point, closed flag is already set
assert!(announcement.is_closed());
// Drop happens here automatically
// The condition should prevent double-notification
// Still closed after drop
assert!(announcement.is_closed());
}
/// Test: Multiple drops with concurrent listeners
///
/// Verifies: Multiple drops with concurrent listeners
/// Category: drop
#[tokio::test]
async fn test_drop_wakes_multiple_async_listeners() {
let (announcer, announcement) = Announcement::<i32>::new();
// Create multiple listeners
let listeners: Vec<_> = (0..5).map(|_| announcement.listener()).collect();
// Spawn tasks for all listeners with short timeout
let mut handles = Vec::new();
for listener in listeners {
handles.push(tokio::spawn(async move {
tokio::time::timeout(Duration::from_millis(100), listener.listen()).await
}));
}
// Give listeners time to start waiting
tokio::time::sleep(Duration::from_millis(10)).await;
// Drop announcer - should wake all listeners
drop(announcer);
// All listeners should receive Closed error quickly, not timeout
for handle in handles {
let result = handle.await.expect("task should not panic");
assert!(
result.is_ok(),
"listener should receive notification, not timeout"
);
assert_eq!(
result.unwrap(),
Err(ListenError::Closed),
"Drop must notify all waiting listeners"
);
}
}
/// Test: Drop notifies then listeners can check is_closed
///
/// Verifies: Drop notifies then listeners can check is_closed
/// Category: drop
#[test]
fn test_drop_notification_visible_to_new_listeners() {
let (announcer, announcement) = Announcement::<i32>::new();
// Drop without announcing
drop(announcer);
// Create listener AFTER drop
let listener = announcement.listener();
// Should immediately see closed status
assert!(listener.is_closed());
assert_eq!(listener.try_listen(), None);
}
/// Test: Verify Drop impl checks value.is_none() correctly
///
/// Verifies: Drop impl checks value.is_none() correctly
/// Category: drop
#[test]
fn test_drop_checks_value_is_none_correctly() {
// Test 1: value is None, should close
{
let (announcer, announcement) = Announcement::<i32>::new();
drop(announcer);
assert!(announcement.is_closed(), "Should close when value is None");
}
// Test 2: value is Some, should NOT close
{
let (_announcer, announcement) = Announcement::<i32>::new();
_announcer.announce(42).unwrap();
assert!(
!announcement.is_closed(),
"Should NOT close when value is Some"
);
}
}
/// Test: Verify Drop impl checks !closed correctly
///
/// Verifies: Drop impl checks !closed correctly
/// Category: drop
#[test]
fn test_drop_checks_not_closed_correctly() {
let (announcer, announcement) = Announcement::<i32>::new();
// First close explicitly
announcer.close();
// closed flag should be set
assert!(announcement.is_closed());
// Drop happens automatically here
// The !closed check should prevent re-notification
// Verify still closed (this is a sanity check)
assert!(announcement.is_closed());
}
/// Test: Stress test - many rapid drops
///
/// Verifies: Stress test - many rapid drops
/// Category: drop
#[test]
fn test_rapid_drops_stress() {
for _ in 0..1000 {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
drop(announcer);
assert!(announcement.is_closed());
assert!(listener.is_closed());
assert_eq!(listener.try_listen(), None);
}
}
/// Test: Drop with Arc value type
///
/// Verifies: Drop with Arc value type
/// Category: drop
#[test]
fn test_drop_with_arc_value() {
let (announcer, announcement) = Announcement::<Arc<String>>::new();
let listener = announcement.listener();
drop(announcer);
assert!(announcement.is_closed());
assert!(listener.is_closed());
}
/// Test: Drop test: drop ordering set closed before notify
///
/// Verifies: drop ordering set closed before notify
/// Category: drop
#[cfg(feature = "std")]
#[test]
fn test_drop_ordering_set_closed_before_notify() {
// This test verifies that closed flag is set before notification
// If the order is wrong, listeners might check closed flag before it's set
let (announcer, announcement) = Announcement::<i32>::new();
let ann_clone = announcement.clone();
let handle = thread::spawn(move || {
let listener = ann_clone.listener();
// Wait with very short timeout
let result = listener.listen_timeout_blocking(Duration::from_millis(50));
// If we get Closed, verify the flag is set
if result == Err(ListenError::Closed) {
assert!(
ann_clone.is_closed(),
"closed flag must be visible when Closed error is returned"
);
}
result
});
thread::sleep(Duration::from_millis(10));
drop(announcer);
let result = handle.join().unwrap();
assert_eq!(result, Err(ListenError::Closed));
}
/// Test: Drop in nested scope
///
/// Verifies: Drop in nested scope
/// Category: drop
#[test]
fn test_drop_in_nested_scope() {
let announcement = {
let (_announcer, announcement) = Announcement::<i32>::new();
// _announcer dropped here at end of scope
announcement
};
// Outside the scope, closed flag should be set
assert!(announcement.is_closed());
}
/// Test: Drop vs explicit close comparison
///
/// Verifies: Drop vs explicit close comparison
/// Category: drop
#[test]
fn test_drop_equivalent_to_explicit_close() {
// Test 1: explicit close
let (announcer1, announcement1) = Announcement::<i32>::new();
let listener1 = announcement1.listener();
announcer1.close();
// Test 2: drop
let (announcer2, announcement2) = Announcement::<i32>::new();
let listener2 = announcement2.listener();
drop(announcer2);
// Both should have same result
assert_eq!(announcement1.is_closed(), announcement2.is_closed());
assert_eq!(listener1.try_listen(), listener2.try_listen());
assert_eq!(listener1.is_closed(), listener2.is_closed());
}
/// Test: Drop with panic in another thread
///
/// Verifies: Drop with panic in another thread
/// Category: drop
#[test]
fn test_drop_after_panic() {
let (announcer, announcement) = Announcement::<i32>::new();
let handle = thread::spawn(move || {
let _announcer = announcer;
panic!("intentional panic");
});
let _ = handle.join();
// Drop happened despite panic
assert!(announcement.is_closed());
}
/// Test: Verify closed flag atomic ordering
///
/// Verifies: closed flag atomic ordering
/// Category: drop
#[test]
fn test_drop_closed_flag_atomic_ordering() {
// This is a documentation test - verifying our understanding
// The actual ordering is in the implementation
let (announcer, announcement) = Announcement::<i32>::new();
// Before drop, not closed
assert!(!announcement.is_closed());
// Drop sets closed flag with Release ordering
drop(announcer);
// After drop, closed flag should be visible with Acquire ordering
assert!(announcement.is_closed());
}