chore: initial release v0.1.0
Runtime-agnostic oneshot broadcast channel for Rust.
This commit is contained in:
64 files changed
+23966
No files matched your search
@@ -0,0 +1,355 @@
|
||||
//! 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());
|
||||
}
|
||||
Reference in new issue
Block a user