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,910 @@
|
||||
//! 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));
|
||||
}
|
||||
Reference in new issue
Block a user