911 lines
26 KiB
Rust
911 lines
26 KiB
Rust
//! Panic Safety Tests
|
|
//!
|
|
//! These tests verify that the library maintains safety invariants even when panics occur.
|
|
//! Panic safety means:
|
|
//! 1. No memory leaks
|
|
//! 2. No undefined behavior
|
|
//! 3. Data structures remain consistent
|
|
//! 4. Resources are properly cleaned up
|
|
//! 5. No deadlocks or hangs after panic recovery
|
|
|
|
use announcement::Announcement;
|
|
use std::panic::{catch_unwind, AssertUnwindSafe};
|
|
use std::sync::Arc;
|
|
|
|
// =============================================================================
|
|
// Basic Panic Safety Tests
|
|
// =============================================================================
|
|
|
|
/// Test: Panic safety: panic during Drop implementation
|
|
///
|
|
/// Verifies: System remains safe when announced value panics during Drop
|
|
/// Category: panic
|
|
#[test]
|
|
fn test_panic_during_drop_in_value() {
|
|
// Test that panicking during Drop of the announced value doesn't break things
|
|
|
|
#[derive(Debug)]
|
|
struct PanicOnDrop {
|
|
should_panic: bool,
|
|
}
|
|
|
|
impl Drop for PanicOnDrop {
|
|
fn drop(&mut self) {
|
|
if self.should_panic {
|
|
panic!("Intentional panic in Drop");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create announcement with panic-on-drop value
|
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let _listener = announcement.listener();
|
|
|
|
announcer
|
|
.announce(Arc::new(PanicOnDrop { should_panic: true }))
|
|
.unwrap();
|
|
|
|
// When Arc drops, it should panic, but we catch it
|
|
}));
|
|
|
|
// The panic should be caught
|
|
assert!(result.is_err());
|
|
|
|
// Create another announcement to verify system still works
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
announcer.announce(42).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(42));
|
|
}
|
|
|
|
/// Test: Panic safety: panic during Clone/Drop interaction
|
|
///
|
|
/// Verifies: System remains safe when panic occurs during value cloning or dropping
|
|
/// Category: panic
|
|
#[test]
|
|
fn test_panic_in_clone_implementation() {
|
|
// Test panic during T::clone() doesn't break announcement
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct PanicOnClone {
|
|
panic_count: Arc<std::sync::atomic::AtomicUsize>,
|
|
panic_on_nth: usize,
|
|
}
|
|
|
|
impl Drop for PanicOnClone {
|
|
fn drop(&mut self) {
|
|
let count = self
|
|
.panic_count
|
|
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
|
if count == self.panic_on_nth {
|
|
panic!("Panic on Nth drop");
|
|
}
|
|
}
|
|
}
|
|
|
|
let panic_count = Arc::new(std::sync::atomic::AtomicUsize::new(0));
|
|
|
|
let _result = catch_unwind(AssertUnwindSafe(|| {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let value = PanicOnClone {
|
|
panic_count: panic_count.clone(),
|
|
panic_on_nth: 2, // Panic on 3rd drop
|
|
};
|
|
|
|
announcer.announce(value).unwrap();
|
|
|
|
// First try_listen - might panic
|
|
let _v1 = listener.try_listen();
|
|
}));
|
|
|
|
// System should still work after panic
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
announcer.announce(42).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(42));
|
|
}
|
|
|
|
/// Test: Panic safety: explicit announcer drop
|
|
///
|
|
/// Verifies: Dropping announcer without announcing properly closes listeners
|
|
/// Category: panic
|
|
#[test]
|
|
fn test_announcer_drop_with_panic_guard() {
|
|
// Test that dropping announcer without announcing works
|
|
|
|
let (announcer, announcement) = Announcement::<i32>::new();
|
|
let listener = announcement.listener();
|
|
|
|
assert!(!listener.is_closed());
|
|
|
|
// Drop announcer explicitly
|
|
drop(announcer);
|
|
|
|
// Listener should see closed
|
|
assert!(listener.is_closed());
|
|
}
|
|
|
|
// =============================================================================
|
|
// Arc Reference Counting Under Panic
|
|
// =============================================================================
|
|
|
|
/// Test: Panic safety: Arc reference counting
|
|
///
|
|
/// Verifies: Arc refcounts remain correct when panics occur while holding references
|
|
/// Category: panic
|
|
#[test]
|
|
fn test_arc_refcount_with_panic() {
|
|
// Verify Arc refcounts are correct even when panics occur
|
|
|
|
let (announcer, announcement) = Announcement::new();
|
|
let data = Arc::new(vec![1, 2, 3, 4, 5]);
|
|
|
|
announcer.announce(data.clone()).unwrap();
|
|
|
|
// Create listeners
|
|
let l1 = announcement.listener();
|
|
let l2 = announcement.listener();
|
|
|
|
// Get values
|
|
let v1 = l1.try_listen().unwrap();
|
|
let _v2 = l2.try_listen().unwrap();
|
|
|
|
// Refcount should be: data + announcement storage + v1 + v2 = 4
|
|
assert_eq!(Arc::strong_count(&v1), 4);
|
|
|
|
// Cause panic while holding references
|
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
let _v3 = l1.try_listen().unwrap();
|
|
panic!("Test panic");
|
|
}));
|
|
|
|
assert!(result.is_err());
|
|
|
|
// Refcount should still be correct
|
|
assert_eq!(Arc::strong_count(&v1), 4);
|
|
}
|
|
|
|
/// Test: Panic safety: announcement clone with panic
|
|
///
|
|
/// Verifies: Cloned announcements remain functional after panic in sibling clone
|
|
/// Category: panic
|
|
#[test]
|
|
fn test_announcement_clone_with_panic() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
|
|
// Clone announcement
|
|
let announcement2 = announcement.clone();
|
|
|
|
// Panic while one is in scope
|
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
let _l = announcement2.listener();
|
|
panic!("Test panic");
|
|
}));
|
|
|
|
assert!(result.is_err());
|
|
|
|
// Original announcement should still work
|
|
let listener = announcement.listener();
|
|
announcer.announce(42).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(42));
|
|
}
|
|
|
|
/// Test: Panic safety: listener clone with panic
|
|
///
|
|
/// Verifies: Cloned listeners remain functional after panic in sibling clone
|
|
/// Category: panic
|
|
#[test]
|
|
fn test_listener_clone_with_panic() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
// Clone listener
|
|
let listener2 = listener.clone();
|
|
|
|
// Announce value
|
|
announcer.announce(42).unwrap();
|
|
|
|
// Panic while using clone
|
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
let _v = listener2.try_listen();
|
|
panic!("Test panic");
|
|
}));
|
|
|
|
assert!(result.is_err());
|
|
|
|
// Original listener should still work
|
|
assert_eq!(listener.try_listen(), Some(42));
|
|
}
|
|
|
|
// =============================================================================
|
|
// Multiple Panic Scenarios
|
|
// =============================================================================
|
|
|
|
/// Test: Panic safety: sequential panics
|
|
///
|
|
/// Verifies: System handles multiple panics in sequence without breaking
|
|
/// Category: panic
|
|
#[test]
|
|
fn test_sequential_panics() {
|
|
// Multiple panics in sequence shouldn't break the system
|
|
|
|
for i in 0..5 {
|
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(i).unwrap();
|
|
|
|
if i % 2 == 0 {
|
|
panic!("Even panic");
|
|
}
|
|
|
|
assert_eq!(listener.try_listen(), Some(i));
|
|
}));
|
|
|
|
if i % 2 == 0 {
|
|
assert!(result.is_err());
|
|
} else {
|
|
assert!(result.is_ok());
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Test: Panic safety: nested panic scenarios
|
|
///
|
|
/// Verifies: System handles panic within panic handler context
|
|
/// Category: panic
|
|
#[test]
|
|
fn test_nested_panics() {
|
|
// Panic within panic handler
|
|
|
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
let (announcer, announcement) = Announcement::<i32>::new();
|
|
let _listener = announcement.listener();
|
|
|
|
// Drop announcer (will set closed flag)
|
|
drop(announcer);
|
|
|
|
// Now cause a panic
|
|
panic!("First panic");
|
|
}));
|
|
|
|
assert!(result.is_err());
|
|
|
|
// System should still work
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
announcer.announce(42).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(42));
|
|
}
|
|
|
|
// =============================================================================
|
|
// Async Panic Safety Tests (requires async feature)
|
|
// =============================================================================
|
|
|
|
/// Test: Panic safety: panic during async listen
|
|
///
|
|
/// Verifies: System remains functional when async task panics after listen completes
|
|
/// Category: panic
|
|
#[tokio::test]
|
|
async fn test_panic_during_async_listen() {
|
|
use std::time::Duration;
|
|
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
// Spawn task that will panic
|
|
let handle = tokio::spawn(async move {
|
|
tokio::time::sleep(Duration::from_millis(10)).await;
|
|
let _value = listener.listen().await.unwrap();
|
|
panic!("Panic after listen");
|
|
});
|
|
|
|
// Announce value
|
|
announcer.announce(42).unwrap();
|
|
|
|
// Task should panic
|
|
let result = handle.await;
|
|
assert!(result.is_err());
|
|
|
|
// System should still work
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
announcer.announce(42).unwrap();
|
|
assert_eq!(listener.listen().await, Ok(42));
|
|
}
|
|
|
|
/// Test: Panic safety: panic before async listen completion
|
|
///
|
|
/// Verifies: System remains functional when async task panics before listen completes
|
|
/// Category: panic
|
|
#[tokio::test]
|
|
async fn test_panic_before_async_listen_completes() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
// Start listening but panic before completion
|
|
let handle = tokio::spawn(async move {
|
|
tokio::select! {
|
|
_ = listener.listen() => {
|
|
panic!("Should not reach here");
|
|
}
|
|
_ = tokio::time::sleep(tokio::time::Duration::from_millis(5)) => {
|
|
panic!("Timeout panic");
|
|
}
|
|
}
|
|
});
|
|
|
|
// Task should panic
|
|
let result = handle.await;
|
|
assert!(result.is_err());
|
|
|
|
// Announcer should still work
|
|
announcer.announce(42).unwrap();
|
|
}
|
|
|
|
/// Test: Panic safety: multiple async tasks with selective panics
|
|
///
|
|
/// Verifies: Some async tasks can panic while others complete successfully
|
|
/// Category: panic
|
|
#[tokio::test]
|
|
async fn test_multiple_async_tasks_with_panics() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
|
|
// Spawn multiple tasks, some will panic
|
|
let mut handles = vec![];
|
|
|
|
for i in 0..10 {
|
|
let listener = announcement.listener();
|
|
handles.push(tokio::spawn(async move {
|
|
let value = listener.listen().await.unwrap();
|
|
|
|
if i % 3 == 0 {
|
|
panic!("Task {} panic", i);
|
|
}
|
|
|
|
value
|
|
}));
|
|
}
|
|
|
|
// Announce value
|
|
announcer.announce(42).unwrap();
|
|
|
|
// Check results - some panic, some succeed
|
|
for (i, handle) in handles.into_iter().enumerate() {
|
|
let result = handle.await;
|
|
|
|
if i % 3 == 0 {
|
|
assert!(result.is_err(), "Task {} should have panicked", i);
|
|
} else {
|
|
assert_eq!(result.unwrap(), 42, "Task {} should have succeeded", i);
|
|
}
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Blocking Mode Panic Safety (requires std feature)
|
|
// =============================================================================
|
|
|
|
/// Test: Panic safety: panic during blocking listen
|
|
///
|
|
/// Verifies: System remains functional when blocking thread panics after listen
|
|
/// Category: panic
|
|
#[cfg(feature = "std")]
|
|
#[test]
|
|
fn test_panic_during_blocking_listen() {
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let handle = thread::spawn(move || {
|
|
let _value = listener.listen_timeout_blocking(Duration::from_millis(100));
|
|
panic!("Panic after blocking listen with timeout");
|
|
});
|
|
|
|
// Give thread time to start
|
|
std::thread::sleep(std::time::Duration::from_millis(10));
|
|
|
|
// Announce value
|
|
announcer.announce(42).unwrap();
|
|
|
|
// Thread should panic
|
|
let result = handle.join();
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
/// Test: Panic safety: multiple blocking threads with selective panics
|
|
///
|
|
/// Verifies: Some blocking threads can panic while others complete successfully
|
|
/// Category: panic
|
|
#[cfg(feature = "std")]
|
|
#[test]
|
|
fn test_multiple_blocking_threads_with_panics() {
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
|
|
let (announcer, announcement) = Announcement::new();
|
|
|
|
let mut handles = vec![];
|
|
|
|
for i in 0..5 {
|
|
let listener = announcement.listener();
|
|
handles.push(thread::spawn(move || {
|
|
let value = listener.listen_timeout_blocking(Duration::from_millis(100));
|
|
|
|
if i % 2 == 0 {
|
|
panic!("Thread {} panic", i);
|
|
}
|
|
|
|
value
|
|
}));
|
|
}
|
|
|
|
thread::sleep(Duration::from_millis(10));
|
|
|
|
announcer.announce(42).unwrap();
|
|
|
|
// Check results
|
|
for (i, handle) in handles.into_iter().enumerate() {
|
|
let result = handle.join();
|
|
|
|
if i % 2 == 0 {
|
|
assert!(result.is_err());
|
|
} else {
|
|
assert_eq!(result.unwrap(), Ok(42));
|
|
}
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// Edge Cases and Stress Tests
|
|
// =============================================================================
|
|
|
|
/// Test: Panic safety: panic with many listeners
|
|
///
|
|
/// Verifies: Panicking while iterating over many listeners doesn't affect others
|
|
/// Category: panic
|
|
#[test]
|
|
fn test_panic_with_large_number_of_listeners() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
|
|
// Create many listeners
|
|
let listeners: Vec<_> = (0..1000).map(|_| announcement.listener()).collect();
|
|
|
|
announcer.announce(42).unwrap();
|
|
|
|
// Panic while iterating
|
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
for (i, listener) in listeners.iter().enumerate() {
|
|
let _value = listener.try_listen();
|
|
|
|
if i == 500 {
|
|
panic!("Panic at 500");
|
|
}
|
|
}
|
|
}));
|
|
|
|
assert!(result.is_err());
|
|
|
|
// Remaining listeners should still work
|
|
for listener in listeners.iter().skip(501) {
|
|
assert_eq!(listener.try_listen(), Some(42));
|
|
}
|
|
}
|
|
|
|
/// Test: Panic safety: panic during is_announced checks
|
|
///
|
|
/// Verifies: System remains functional when panic occurs during state checks
|
|
/// Category: panic
|
|
#[test]
|
|
fn test_panic_during_is_announced_checks() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
|
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
assert!(!announcement.is_announced());
|
|
panic!("Panic before announce");
|
|
}));
|
|
|
|
assert!(result.is_err());
|
|
|
|
// Should still be able to announce
|
|
announcer.announce(42).unwrap();
|
|
assert!(announcement.is_announced());
|
|
}
|
|
|
|
/// Test: Panic safety: panic during is_closed checks
|
|
///
|
|
/// Verifies: System remains functional when panic occurs during closure checks
|
|
/// Category: panic
|
|
#[test]
|
|
fn test_panic_during_is_closed_checks() {
|
|
let (announcer, announcement) = Announcement::<i32>::new();
|
|
let listener = announcement.listener();
|
|
|
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
assert!(!announcement.is_closed());
|
|
panic!("Panic before close");
|
|
}));
|
|
|
|
assert!(result.is_err());
|
|
|
|
// Should still be able to close
|
|
announcer.close();
|
|
assert!(announcement.is_closed());
|
|
assert!(listener.is_closed());
|
|
}
|
|
|
|
/// Test: Panic safety: no memory leaks on panic
|
|
///
|
|
/// Verifies: Repeated panics during announcement don't cause memory leaks
|
|
/// Category: panic
|
|
#[test]
|
|
fn test_memory_not_leaked_on_panic() {
|
|
// Create and drop many announcements with panics
|
|
|
|
for _ in 0..100 {
|
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let _listener = announcement.listener();
|
|
|
|
announcer.announce(vec![1; 1000]).unwrap();
|
|
|
|
panic!("Test panic");
|
|
}));
|
|
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
// If there were memory leaks, this would show up in memory profilers
|
|
// For now, we just verify the test completes
|
|
}
|
|
|
|
/// Test: Panic safety: close with panic recovery
|
|
///
|
|
/// Verifies: System remains functional after panic following close operation
|
|
/// Category: panic
|
|
#[test]
|
|
fn test_announcer_close_with_panic_recovery() {
|
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
let (announcer, announcement) = Announcement::<i32>::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.close();
|
|
|
|
assert!(listener.is_closed());
|
|
panic!("Panic after close");
|
|
}));
|
|
|
|
assert!(result.is_err());
|
|
|
|
// System should still work
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
announcer.announce(42).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(42));
|
|
}
|
|
|
|
/// Test: Panic safety: UnwindSafe trait implementation
|
|
///
|
|
/// Verifies: All types implement UnwindSafe and RefUnwindSafe traits
|
|
/// Category: panic
|
|
#[test]
|
|
fn test_unwind_safe_trait_impl() {
|
|
// Verify types implement UnwindSafe and RefUnwindSafe
|
|
|
|
fn assert_unwind_safe<T: std::panic::UnwindSafe>() {}
|
|
fn assert_ref_unwind_safe<T: std::panic::RefUnwindSafe>() {}
|
|
|
|
assert_unwind_safe::<announcement::Announcement<i32>>();
|
|
assert_unwind_safe::<announcement::Announcer<i32>>();
|
|
assert_unwind_safe::<announcement::Listener<i32>>();
|
|
|
|
assert_ref_unwind_safe::<announcement::Announcement<i32>>();
|
|
assert_ref_unwind_safe::<announcement::Listener<i32>>();
|
|
}
|
|
|
|
// =============================================================================
|
|
// Complex Panic Scenarios
|
|
// =============================================================================
|
|
|
|
/// Test: Panic safety: panic in complex type Drop
|
|
///
|
|
/// Verifies: System handles panics during drop of complex nested types
|
|
/// Category: panic
|
|
#[test]
|
|
fn test_panic_in_complex_type_drop() {
|
|
#[derive(Debug)]
|
|
struct ComplexType {
|
|
_data: Vec<Vec<String>>,
|
|
should_panic: bool,
|
|
}
|
|
|
|
impl Drop for ComplexType {
|
|
fn drop(&mut self) {
|
|
if self.should_panic {
|
|
panic!("Complex drop panic");
|
|
}
|
|
}
|
|
}
|
|
|
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let complex = ComplexType {
|
|
_data: vec![vec!["test".to_string(); 10]; 10],
|
|
should_panic: false,
|
|
};
|
|
|
|
announcer.announce(Arc::new(complex)).unwrap();
|
|
|
|
let value = listener.try_listen().unwrap();
|
|
|
|
// Modify to panic on drop
|
|
// (Actually, we can't modify Arc contents, so this tests Arc safety)
|
|
drop(value);
|
|
}));
|
|
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
/// Test: Panic safety: panic recovery in async loop
|
|
///
|
|
/// Verifies: Async system recovers from panics across multiple iterations
|
|
/// Category: panic
|
|
#[tokio::test]
|
|
async fn test_panic_recovery_in_async_loop() {
|
|
// Test that async system recovers from panics in loop
|
|
|
|
for i in 0..5 {
|
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(i).unwrap();
|
|
|
|
if i == 2 {
|
|
panic!("Panic on iteration 2");
|
|
}
|
|
|
|
assert_eq!(listener.try_listen(), Some(i));
|
|
}));
|
|
|
|
if i == 2 {
|
|
assert!(result.is_err());
|
|
} else {
|
|
assert!(result.is_ok());
|
|
}
|
|
}
|
|
|
|
// Async system should still work
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
announcer.announce(42).unwrap();
|
|
assert_eq!(listener.listen().await, Ok(42));
|
|
}
|
|
|
|
// Note: Double-panic test removed because double panics abort the process by design.
|
|
// This is correct Rust behavior to prevent undefined behavior during unwinding.
|
|
|
|
// =============================================================================
|
|
// Additional Panic Safety Tests
|
|
// =============================================================================
|
|
|
|
/// Test: Panic safety: panic with cloned announcements
|
|
///
|
|
/// Verifies: Cloned announcements remain functional after panic in siblings
|
|
/// Category: panic
|
|
#[test]
|
|
fn test_panic_with_cloned_announcements() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let announcement2 = announcement.clone();
|
|
let announcement3 = announcement.clone();
|
|
|
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
let _l1 = announcement.listener();
|
|
let _l2 = announcement2.listener();
|
|
panic!("Test panic with clones");
|
|
}));
|
|
|
|
assert!(result.is_err());
|
|
|
|
// announcement3 should still work
|
|
let listener = announcement3.listener();
|
|
announcer.announce(42).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(42));
|
|
}
|
|
|
|
/// Test: Panic safety: panic during listener creation
|
|
///
|
|
/// Verifies: System handles panics occurring during listener creation process
|
|
/// Category: panic
|
|
#[test]
|
|
fn test_panic_during_listener_creation() {
|
|
for _ in 0..10 {
|
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
let (_announcer, announcement) = Announcement::<i32>::new();
|
|
|
|
// Create some listeners
|
|
let _l1 = announcement.listener();
|
|
let _l2 = announcement.listener();
|
|
|
|
// Panic during creation
|
|
panic!("Panic during listener creation");
|
|
}));
|
|
|
|
assert!(result.is_err());
|
|
}
|
|
}
|
|
|
|
/// Test: Panic safety: panic with many clones
|
|
///
|
|
/// Verifies: System handles panic while iterating over many clones
|
|
/// Category: panic
|
|
#[test]
|
|
fn test_panic_with_many_clones() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
|
|
// Create many clones
|
|
let clones: Vec<_> = (0..100).map(|_| announcement.clone()).collect();
|
|
|
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
for (i, clone) in clones.iter().enumerate() {
|
|
let _listener = clone.listener();
|
|
|
|
if i == 50 {
|
|
panic!("Panic at clone 50");
|
|
}
|
|
}
|
|
}));
|
|
|
|
assert!(result.is_err());
|
|
|
|
// Original should still work
|
|
let listener = announcement.listener();
|
|
announcer.announce(42).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(42));
|
|
}
|
|
|
|
/// Test: Panic safety: panic during concurrent announces
|
|
///
|
|
/// Verifies: Panics in one announcement don't affect concurrent announcements
|
|
/// Category: panic
|
|
#[tokio::test]
|
|
async fn test_panic_during_concurrent_announces() {
|
|
// Test that panics don't affect concurrent operations
|
|
|
|
for _ in 0..10 {
|
|
let (announcer1, announcement1) = Announcement::new();
|
|
let (announcer2, announcement2) = Announcement::new();
|
|
|
|
let l1 = announcement1.listener();
|
|
let l2 = announcement2.listener();
|
|
|
|
// Spawn task that will panic
|
|
let h1 = tokio::spawn(async move {
|
|
announcer1.announce(42).unwrap();
|
|
panic!("Panic after first announce");
|
|
});
|
|
|
|
// Spawn task that should succeed
|
|
let h2 = tokio::spawn(async move {
|
|
tokio::time::sleep(tokio::time::Duration::from_millis(5)).await;
|
|
announcer2.announce(100).unwrap();
|
|
});
|
|
|
|
// First task should panic
|
|
assert!(h1.await.is_err());
|
|
|
|
// Second task should succeed
|
|
assert!(h2.await.is_ok());
|
|
|
|
// Both values should be available
|
|
assert_eq!(l1.listen().await, Ok(42));
|
|
assert_eq!(l2.listen().await, Ok(100));
|
|
}
|
|
}
|
|
|
|
/// Test: Panic safety: panic with nested Arc structures
|
|
///
|
|
/// Verifies: System handles panics with complex nested Arc data structures
|
|
/// Category: panic
|
|
#[test]
|
|
fn test_panic_in_nested_structures() {
|
|
#[derive(Debug, Clone)]
|
|
struct Nested {
|
|
_data: Arc<Vec<Arc<String>>>,
|
|
}
|
|
|
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let nested = Nested {
|
|
_data: Arc::new(vec![
|
|
Arc::new("a".to_string()),
|
|
Arc::new("b".to_string()),
|
|
Arc::new("c".to_string()),
|
|
]),
|
|
};
|
|
|
|
announcer.announce(nested).unwrap();
|
|
|
|
let _value = listener.try_listen();
|
|
|
|
panic!("Panic with nested structure");
|
|
}));
|
|
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
/// Test: Panic safety: panic mixing blocking and async
|
|
///
|
|
/// Verifies: Panics in blocking threads don't affect async operations
|
|
/// Category: panic
|
|
#[cfg(feature = "std")]
|
|
#[test]
|
|
fn test_panic_mixing_blocking_and_async() {
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener1 = announcement.listener();
|
|
let listener2 = announcement.listener();
|
|
|
|
// Blocking thread with timeout
|
|
let h1 = thread::spawn(move || {
|
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
let _value = listener1.listen_timeout_blocking(Duration::from_millis(100));
|
|
panic!("Panic in blocking thread with timeout");
|
|
}));
|
|
assert!(result.is_err());
|
|
});
|
|
|
|
// Announce from main thread
|
|
thread::sleep(std::time::Duration::from_millis(10));
|
|
announcer.announce(42).unwrap();
|
|
|
|
h1.join().unwrap();
|
|
|
|
// Other listener should still work
|
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
|
let result = rt.block_on(async { listener2.listen().await });
|
|
assert_eq!(result, Ok(42));
|
|
}
|
|
|
|
/// Test: Panic safety: panic after immediate announcer drop
|
|
///
|
|
/// Verifies: System handles panic occurring after announce and immediate drop
|
|
/// Category: panic
|
|
#[test]
|
|
fn test_panic_with_immediate_drop() {
|
|
// Test panic when announcer is dropped immediately after announce
|
|
for _ in 0..20 {
|
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(42).unwrap();
|
|
// Announcer dropped here
|
|
|
|
assert_eq!(listener.try_listen(), Some(42));
|
|
|
|
panic!("Panic after announce and drop");
|
|
}));
|
|
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
// System should still work
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
announcer.announce(100).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(100));
|
|
}
|