//! Tests for dropped announcer scenarios - critical edge cases //! //! These tests verify graceful behavior when the announcer is dropped //! without announcing, ensuring no deadlocks or panics occur. use announcement::Announcement; use announcement::ListenError; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::thread; use std::time::Duration; /// Test: Announcer dropped without announcing, listeners wait (verify timeout needed) /// /// Verifies: Announcer dropped without announcing, listeners wait (timeout needed) /// Category: drop #[test] fn test_announcer_dropped_listeners_wait() { let (announcer, announcement) = Announcement::::new(); let listener = announcement.listener(); // Drop the announcer without announcing drop(announcer); // try_listen should return None assert_eq!(listener.try_listen(), None); // is_announced should be false assert!(!announcement.is_announced()); } /// Test: Announcer dropped, then try_listen returns None as expected /// /// Verifies: Announcer dropped, then try_listen returns None as expected /// Category: drop #[test] fn test_announcer_dropped_try_listen_none() { let (announcer, announcement) = Announcement::::new(); // Create multiple listeners let listener1 = announcement.listener(); let listener2 = announcement.listener(); let listener3 = announcement.listener(); // Drop announcer drop(announcer); // All try_listen calls should return None assert_eq!(listener1.try_listen(), None); assert_eq!(listener2.try_listen(), None); assert_eq!(listener3.try_listen(), None); // Even new listeners created after drop should get None let listener4 = announcement.listener(); assert_eq!(listener4.try_listen(), None); } /// Test: Multiple listeners waiting when announcer dropped /// /// Verifies: Multiple listeners waiting when announcer dropped /// Category: drop #[test] fn test_multiple_listeners_announcer_dropped() { let (announcer, announcement) = Announcement::>::new(); // Create many listeners let mut listeners = Vec::new(); for _ in 0..100 { listeners.push(announcement.listener()); } // Drop announcer drop(announcer); // All listeners should get None for listener in &listeners { assert_eq!(listener.try_listen(), None); } } /// Test: Drop test: no deadlock timeout blocking announcer dropped /// /// Verifies: no deadlock timeout blocking announcer dropped /// Category: drop #[cfg(feature = "std")] #[test] fn test_no_deadlock_timeout_blocking_announcer_dropped() { let (announcer, announcement) = Announcement::::new(); let listener = announcement.listener(); // Drop announcer in another thread thread::spawn(move || { thread::sleep(Duration::from_millis(10)); drop(announcer); // Sets closed flag and notifies listeners }); // With closed detection, this returns Closed, not Timeout let result = listener.listen_timeout_blocking(Duration::from_millis(100)); assert_eq!(result, Err(ListenError::Closed)); assert!(announcement.is_closed()); } /// Test: Verify graceful behavior, no panics when announcer dropped /// /// Verifies: graceful behavior, no panics when announcer dropped /// Category: drop #[test] fn test_no_panic_announcer_dropped() { let result = std::panic::catch_unwind(|| { let (announcer, announcement) = Announcement::::new(); let listener1 = announcement.listener(); let listener2 = announcement.listener(); drop(announcer); // These should not panic let _ = listener1.try_listen(); let _ = listener2.try_listen(); let _ = announcement.is_announced(); let _ = announcement.listener(); }); assert!(result.is_ok(), "Should not panic when announcer is dropped"); } /// Test: Document behavior - announcer dropped means value never arrives /// /// Verifies: Document behavior - announcer dropped means value never arrives /// Category: drop #[test] fn test_document_dropped_announcer_behavior() { // This test documents the expected behavior when announcer is dropped let (announcer, announcement) = Announcement::::new(); let listener = announcement.listener(); // When announcer is dropped without announcing: drop(announcer); // 1. try_listen returns None assert_eq!(listener.try_listen(), None); // 2. is_announced returns false assert!(!announcement.is_announced()); // 3. New listeners also get None let new_listener = announcement.listener(); assert_eq!(new_listener.try_listen(), None); // 4. The value will NEVER arrive (no way to announce after announcer is dropped) } /// Test: Announcer dropped in spawned thread /// /// Verifies: Announcer dropped in spawned thread /// Category: drop #[test] fn test_announcer_dropped_in_thread() { let (announcer, announcement) = Announcement::::new(); let listener = announcement.listener(); thread::spawn(move || { // Announcer dropped when thread exits let _ = announcer; }) .join() .unwrap(); assert_eq!(listener.try_listen(), None); } /// Test: Race between announce and drop /// /// Verifies: Race between announce and drop /// Category: drop #[test] fn test_announcer_dropped_arc_value() { let (announcer, announcement) = Announcement::>>::new(); let listener = announcement.listener(); // Create an Arc value that would have been announced let _would_be_value = Arc::new(vec!["test".to_string()]); // Drop announcer instead of announcing drop(announcer); assert_eq!(listener.try_listen(), None); } /// Test: Many announcers dropped in parallel /// /// Verifies: Many announcers dropped in parallel /// Category: drop #[test] fn test_many_announcers_dropped_parallel() { let mut handles = vec![]; for _ in 0..100 { handles.push(thread::spawn(|| { let (announcer, announcement) = Announcement::::new(); let listener = announcement.listener(); // Drop announcer drop(announcer); // Verify listener gets None assert_eq!(listener.try_listen(), None); })); } for handle in handles { handle.join().unwrap(); } } /// Test: Announcer dropped after listeners created in multiple threads /// /// Verifies: Announcer dropped after listeners created in multiple threads /// Category: drop #[test] fn test_announcer_dropped_multi_thread_listeners() { let (announcer, announcement) = Announcement::::new(); let mut handles = vec![]; // Create listeners in multiple threads for _ in 0..10 { let ann = announcement.clone(); handles.push(thread::spawn(move || { let listener = ann.listener(); thread::sleep(Duration::from_millis(50)); listener.try_listen() })); } // Drop announcer after a short delay thread::sleep(Duration::from_millis(10)); drop(announcer); // All threads should get None for handle in handles { let result = handle.join().unwrap(); assert_eq!(result, None); } } /// Test: Memory is properly freed when announcer dropped /// /// Verifies: Memory is properly freed when announcer dropped /// Category: drop #[test] fn test_memory_freed_announcer_dropped() { static DROP_COUNT: AtomicUsize = AtomicUsize::new(0); #[derive(Clone)] struct MemoryTracker { _data: Vec, } impl Drop for MemoryTracker { /// Test: Drop test: drop /// /// Verifies: Verifies drop behavior /// Category: drop fn drop(&mut self) { DROP_COUNT.fetch_add(1, Ordering::SeqCst); } } DROP_COUNT.store(0, Ordering::SeqCst); { let (announcer, announcement) = Announcement::::new(); let _listener = announcement.listener(); // Would have announced this, but drop instead let _would_announce = MemoryTracker { _data: vec![0; 1024], }; drop(announcer); // MemoryTracker never gets into the announcement } // Only the local MemoryTracker should have been dropped assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1); } /// Test: Drop test: listener blocking wait announcer already dropped /// /// Verifies: listener blocking wait announcer already dropped /// Category: drop #[cfg(feature = "std")] #[test] fn test_listener_blocking_wait_announcer_already_dropped() { let (announcer, announcement) = Announcement::::new(); let listener = announcement.listener(); // Drop announcer first - sets closed flag drop(announcer); // With closed detection, this returns Closed immediately, not Timeout let result = listener.listen_timeout_blocking(Duration::from_millis(100)); assert_eq!(result, Err(ListenError::Closed)); assert!(announcement.is_closed()); } /// Test: Clone chain with dropped announcer /// /// Verifies: Clone chain with dropped announcer /// Category: drop #[test] fn test_clone_chain_dropped_announcer() { let (announcer, announcement) = Announcement::::new(); let ann2 = announcement.clone(); let ann3 = ann2.clone(); let l1 = announcement.listener(); let l2 = ann2.listener(); let l3 = ann3.listener(); drop(announcer); assert_eq!(l1.try_listen(), None); assert_eq!(l2.try_listen(), None); assert_eq!(l3.try_listen(), None); } /// Test: Dropped announcer with zero-sized type /// /// Verifies: Dropped announcer with zero-sized type /// Category: drop #[test] fn test_dropped_announcer_zst() { let (announcer, announcement) = Announcement::<()>::new(); let listener = announcement.listener(); drop(announcer); assert_eq!(listener.try_listen(), None); } /// Test: Dropped announcer with large type /// /// Verifies: Dropped announcer with large type /// Category: drop #[test] fn test_dropped_announcer_large_type() { #[derive(Clone, Debug, PartialEq)] struct LargeType { data: [u8; 10_000], } let (announcer, announcement) = Announcement::::new(); let listener = announcement.listener(); drop(announcer); assert_eq!(listener.try_listen(), None); } /// Test: is_announced remains false forever after announcer dropped /// /// Verifies: is_announced remains false forever after announcer dropped /// Category: drop #[test] fn test_is_announced_false_forever() { let (announcer, announcement) = Announcement::::new(); drop(announcer); // Check multiple times - should always be false for _ in 0..1000 { assert!(!announcement.is_announced()); } // Even after creating new listeners let _l1 = announcement.listener(); assert!(!announcement.is_announced()); let _l2 = announcement.listener(); assert!(!announcement.is_announced()); } /// Test: Stress test - rapid announcer drops /// /// Verifies: Stress test - rapid announcer drops /// Category: drop #[test] fn test_stress_rapid_announcer_drops() { for _ in 0..10_000 { let (announcer, announcement) = Announcement::::new(); let _listener = announcement.listener(); drop(announcer); } } /// Test: Announcer dropped with Option type /// /// Verifies: Announcer dropped with Option type /// Category: drop #[test] fn test_dropped_announcer_option() { let (announcer, announcement) = Announcement::>::new(); let listener = announcement.listener(); drop(announcer); assert_eq!(listener.try_listen(), None); // Note: try_listen returns None (no value), not Some(None) } /// Test: Announcer dropped with Result type /// /// Verifies: Announcer dropped with Result type /// Category: drop #[test] fn test_dropped_announcer_result() { let (announcer, announcement) = Announcement::>::new(); let listener = announcement.listener(); drop(announcer); assert_eq!(listener.try_listen(), None); } /// Test: Drop test: async listen announcer dropped /// /// Verifies: async listen announcer dropped /// Category: drop #[tokio::test] async fn test_async_listen_announcer_dropped() { let (announcer, announcement) = Announcement::::new(); let listener = announcement.listener(); // Drop announcer - this sets closed flag and notifies listeners drop(announcer); // With closed detection, listen() returns Closed immediately (with timeout to prevent hanging in mutation tests) let result = tokio::time::timeout(Duration::from_millis(100), listener.listen()) .await .expect("listen should not timeout when announcer is dropped"); assert_eq!(result, Err(ListenError::Closed)); // Verify closed state assert!(announcement.is_closed()); } /// Test: Verify announcer can't be used after drop (compile-time safety) /// /// Verifies: announcer can't be used after drop (compile-time safety) /// Category: drop #[test] fn test_compile_time_safety_announcer_consumed() { let (announcer, announcement) = Announcement::::new(); let _listener = announcement.listener(); // This consumes the announcer drop(announcer); // The following would not compile: // announcer.announce(42).unwrap(); // Error: announcer was moved // This is enforced at compile time by Rust's ownership system assert!(!announcement.is_announced()); } /// Test: Complex scenario with conditionally dropped announcer /// /// Verifies: Complex scenario with conditionally dropped announcer /// Category: drop #[test] fn test_conditional_announcer_drop() { fn maybe_announce(should_drop: bool) -> (Announcement, Option) { let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); if should_drop { drop(announcer); (announcement, listener.try_listen()) } else { announcer.announce(42).unwrap(); (announcement, listener.try_listen()) } } // Test both paths let (_ann1, result1) = maybe_announce(true); assert_eq!(result1, None); let (_ann2, result2) = maybe_announce(false); assert_eq!(result2, Some(42)); } /// Test: Announcer dropped in destructor of another type /// /// Verifies: Announcer dropped in destructor of another type /// Category: drop #[test] fn test_announcer_dropped_in_destructor() { struct AnnouncerHolder { announcer: Option>, } impl Drop for AnnouncerHolder { fn drop(&mut self) { // Announcer dropped here without announcing self.announcer.take(); } } let (announcer, announcement) = Announcement::::new(); let listener = announcement.listener(); { let _holder = AnnouncerHolder { announcer: Some(announcer), }; // Holder dropped here, taking announcer with it } assert_eq!(listener.try_listen(), None); } /// Test: Thread panic while holding announcer /// /// Verifies: Thread panic while holding announcer /// Category: drop #[test] fn test_thread_panic_with_announcer() { let (announcer, announcement) = Announcement::::new(); let listener = announcement.listener(); let handle = thread::spawn(move || { let _announcer = announcer; panic!("Intentional panic while holding announcer"); }); // Thread panicked, dropping announcer assert!(handle.join().is_err()); assert_eq!(listener.try_listen(), None); } /// Test: Announcer dropped with recursive structure /// /// Verifies: Announcer dropped with recursive structure /// Category: drop #[test] fn test_dropped_announcer_recursive() { #[derive(Clone, Debug, PartialEq)] struct Node { value: i32, next: Option>, } let (announcer, announcement) = Announcement::::new(); let listener = announcement.listener(); drop(announcer); assert_eq!(listener.try_listen(), None); } /// Test: Drop test: document blocking behavior /// /// Verifies: document blocking behavior /// Category: drop #[cfg(feature = "std")] #[test] fn test_document_blocking_behavior() { let (announcer, announcement) = Announcement::::new(); let listener = announcement.listener(); thread::spawn(move || { thread::sleep(Duration::from_millis(10)); drop(announcer); // Drop instead of announce }); // This will timeout because announcer was dropped let result = listener.listen_timeout_blocking(Duration::from_millis(100)); match result { Err(ListenError::Timeout) => { // Expected: timeout because announcer was dropped } Err(ListenError::Closed) => { // Also acceptable: closed because announcer was dropped } Ok(_) => panic!("Should not receive value when announcer is dropped"), } } /// Test: Multiple announcement channels with some dropped announcers /// /// Verifies: Multiple announcement channels with some dropped announcers /// Category: drop #[test] fn test_multiple_channels_some_dropped() { let (a1, ann1) = Announcement::::new(); let (a2, ann2) = Announcement::::new(); let (a3, ann3) = Announcement::::new(); let l1 = ann1.listener(); let l2 = ann2.listener(); let l3 = ann3.listener(); // Announce on first, drop others a1.announce(42).unwrap(); drop(a2); drop(a3); assert_eq!(l1.try_listen(), Some(42)); assert_eq!(l2.try_listen(), None); assert_eq!(l3.try_listen(), None); }