356 lines
10 KiB
Rust
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());
|
|
}
|