//! 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, 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::::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::::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::::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::::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() {} fn assert_ref_unwind_safe() {} assert_unwind_safe::>(); assert_unwind_safe::>(); assert_unwind_safe::>(); assert_ref_unwind_safe::>(); assert_ref_unwind_safe::>(); } // ============================================================================= // 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>, 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::::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>>, } 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)); }