//! Tests for announcing with no receivers - critical edge cases //! //! These tests verify that the announcement system handles gracefully: //! - Announcing with 0 listeners //! - Listeners created but dropped before announce //! - Ensuring no memory leaks or panics use announcement::Announcement; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::thread; use std::time::Instant; /// Test: Announce with 0 listeners created at all /// /// Verifies: Announce with 0 listeners created at all /// Category: close #[test] fn test_announce_with_zero_listeners_created() { let (announcer, _announcement) = Announcement::::new(); // No listeners created at all // Should succeed without panic announcer.announce(42).unwrap(); } /// Test: Announce then drop announcement (no listeners exist) /// /// Verifies: Announce then drop announcement (no listeners exist) /// Category: close #[test] fn test_announce_then_drop_announcement_no_listeners() { let (announcer, announcement) = Announcement::::new(); // Drop the announcement immediately drop(announcement); // Should still succeed announcer.announce(42).unwrap(); } /// Test: Announce with listeners created but all dropped before announce /// /// Verifies: Announce with listeners created but all dropped before announce /// Category: close #[test] fn test_all_listeners_dropped_before_announce() { let (announcer, announcement) = Announcement::::new(); // Create listeners then immediately drop them { let _listener1 = announcement.listener(); let _listener2 = announcement.listener(); let _listener3 = announcement.listener(); let _listener4 = announcement.listener(); let _listener5 = announcement.listener(); // All listeners dropped here } // Should still announce successfully announcer.announce("test".to_string()).unwrap(); // Verify new listeners can still get the value let new_listener = announcement.listener(); assert_eq!(new_listener.try_listen(), Some("test".to_string())); } /// Test: Announce with listeners created but all dropped after announce /// /// Verifies: Announce with listeners created but all dropped after announce /// Category: close #[test] fn test_all_listeners_dropped_after_announce() { let (announcer, announcement) = Announcement::>::new(); let mut listeners = Vec::new(); for _ in 0..10 { listeners.push(announcement.listener()); } announcer.announce(vec![1, 2, 3]).unwrap(); // Drop all listeners after announce drop(listeners); // New listener should still get the value let new_listener = announcement.listener(); assert_eq!(new_listener.try_listen(), Some(vec![1, 2, 3])); } /// Test: Verify no memory leaks when value should be dropped /// /// Verifies: no memory leaks when value should be dropped /// Category: close #[test] fn test_no_memory_leak_value_dropped_with_last_reference() { static DROP_COUNT: AtomicUsize = AtomicUsize::new(0); #[derive(Clone, Debug)] struct DropTracker { _value: i32, } impl Drop for DropTracker { /// Test: No-receiver test: drop /// /// Verifies: Verifies drop /// Category: no-receiver fn drop(&mut self) { DROP_COUNT.fetch_add(1, Ordering::SeqCst); } } DROP_COUNT.store(0, Ordering::SeqCst); // Test 1: No listeners at all { let (announcer, announcement) = Announcement::new(); let tracker = DropTracker { _value: 1 }; announcer.announce(tracker).unwrap(); drop(announcement); } let drops_after_no_listeners = DROP_COUNT.load(Ordering::SeqCst); assert!( drops_after_no_listeners >= 1, "DropTracker should be dropped when no references exist" ); DROP_COUNT.store(0, Ordering::SeqCst); // Test 2: Listeners created and dropped { let (announcer, announcement) = Announcement::new(); let tracker = DropTracker { _value: 2 }; let listener1 = announcement.listener(); let listener2 = announcement.listener(); announcer.announce(tracker).unwrap(); // Get values to ensure clones are made let _val1 = listener1.try_listen(); let _val2 = listener2.try_listen(); // Drop everything drop(listener1); drop(listener2); drop(announcement); drop(_val1); drop(_val2); } let drops_after_listeners = DROP_COUNT.load(Ordering::SeqCst); assert!( drops_after_listeners >= 1, "All DropTracker instances should be dropped" ); } /// Test: Multiple announcements to nobody in a loop (stress test) /// /// Verifies: Multiple announcements to nobody in a loop (stress test) /// Category: close #[test] fn test_multiple_announcements_to_nobody_stress() { for i in 0..1000 { let (announcer, _announcement) = Announcement::::new(); announcer.announce(i).unwrap(); } } /// Test: Large data announced with no listeners (memory stress) /// /// Verifies: Large data announced with no listeners (memory stress) /// Category: close #[test] fn test_announce_large_data_no_listeners() { let (announcer, _announcement) = Announcement::>::new(); // Announce 10MB of data with no listeners let large_data = vec![0u8; 10 * 1024 * 1024]; announcer.announce(large_data).unwrap(); } /// Test: Arc-wrapped data with no listeners /// /// Verifies: Arc-wrapped data with no listeners /// Category: close #[test] fn test_announce_arc_data_no_listeners() { let (announcer, _announcement) = Announcement::>::new(); let data = Arc::new("shared data".to_string()); let initial_count = Arc::strong_count(&data); announcer.announce(data.clone()).unwrap(); // After announce with no listeners, Arc count should only increase by 1 (stored in OnceLock) assert_eq!(Arc::strong_count(&data), initial_count + 1); } /// Test: Announcement dropped before announcer with no listeners /// /// Verifies: Announcement dropped before announcer with no listeners /// Category: close #[test] fn test_announcement_dropped_before_announcer_no_listeners() { let (announcer, announcement) = Announcement::::new(); // Drop announcement first drop(announcement); // Announce should still succeed announcer.announce(42).unwrap(); } /// Test: Rapid creation and dropping of empty announcements /// /// Verifies: Rapid creation and dropping of empty announcements /// Category: close #[test] fn test_rapid_empty_announcement_creation() { for _ in 0..10000 { let (_announcer, announcement) = Announcement::<()>::new(); drop(announcement); } } /// Test: Threaded announce with no listeners /// /// Verifies: Threaded announce with no listeners /// Category: close #[test] fn test_threaded_announce_no_listeners() { let (announcer, announcement) = Announcement::::new(); let handle = thread::spawn(move || { announcer.announce(42).unwrap(); }); // No listeners created handle.join().unwrap(); // New listener after announce in different thread let listener = announcement.listener(); assert_eq!(listener.try_listen(), Some(42)); } /// Test: Many announcements with no listeners in parallel /// /// Verifies: Many announcements with no listeners in parallel /// Category: close #[test] fn test_parallel_announcements_no_listeners() { let mut handles = vec![]; for i in 0..100 { handles.push(thread::spawn(move || { let (announcer, _announcement) = Announcement::::new(); announcer.announce(i).unwrap(); })); } for handle in handles { handle.join().unwrap(); } } /// Test: Create and drop listeners in a tight loop, then announce /// /// Verifies: Create and drop listeners in a tight loop, then announce /// Category: close #[test] fn test_listener_churn_before_announce() { let (announcer, announcement) = Announcement::::new(); // Create and immediately drop 100,000 listeners for _ in 0..100_000 { drop(announcement.listener()); } // Should still work fine announcer.announce(42).unwrap(); // New listener should get value let listener = announcement.listener(); assert_eq!(listener.try_listen(), Some(42)); } /// Test: Listeners created in threads, all dropped before announce /// /// Verifies: Listeners created in threads, all dropped before announce /// Category: close #[test] fn test_threaded_listeners_all_dropped() { let (announcer, announcement) = Announcement::::new(); let mut handles = vec![]; // Create listeners in 10 threads for _ in 0..10 { let ann = announcement.clone(); handles.push(thread::spawn(move || { // Create 100 listeners and immediately drop them for _ in 0..100 { drop(ann.listener()); } })); } // Wait for all threads to finish for handle in handles { handle.join().unwrap(); } // Announce after all listeners are gone announcer.announce("all dropped".to_string()).unwrap(); // New listener should work let listener = announcement.listener(); assert_eq!(listener.try_listen(), Some("all dropped".to_string())); } /// Test: Close test: zero overhead no listeners /// /// Verifies: zero overhead no listeners /// Category: close #[test] fn test_zero_overhead_no_listeners() { // Warm up for _ in 0..10 { let (announcer, _) = Announcement::::new(); announcer.announce(42).unwrap(); } // Measure time for 1000 announcements with no listeners let start = Instant::now(); for i in 0..1000 { let (announcer, _) = Announcement::::new(); announcer.announce(i).unwrap(); } let duration = start.elapsed(); // Should be very fast (sub-millisecond for 1000 announcements) assert!( duration.as_millis() < 10, "Announcing to no listeners should be nearly zero overhead" ); } /// Test: Create 1M listeners, drop them all, then announce /// /// Verifies: Create 1M listeners, drop them all, then announce /// Category: close #[test] fn test_value_dropped_when_announcement_dropped() { static DROP_COUNT: AtomicUsize = AtomicUsize::new(0); #[derive(Clone, Debug)] struct ValueDropTracker { _data: Vec, } impl Drop for ValueDropTracker { /// Test: No-receiver test: drop /// /// Verifies: Verifies drop /// Category: no-receiver fn drop(&mut self) { DROP_COUNT.fetch_add(1, Ordering::SeqCst); } } DROP_COUNT.store(0, Ordering::SeqCst); { let (announcer, announcement) = Announcement::new(); let tracker = ValueDropTracker { _data: vec![1, 2, 3], }; announcer.announce(tracker).unwrap(); // No listeners, drop announcement drop(announcement); } // Value should be dropped let drops = DROP_COUNT.load(Ordering::SeqCst); assert!( drops >= 1, "Value should be dropped when announcement is dropped with no references" ); } /// Test: Complex scenario - mixed listener lifecycle with no final listeners /// /// Verifies: Complex scenario - mixed listener lifecycle with no final listeners /// Category: close #[test] fn test_complex_no_listener_scenario() { use std::collections::HashMap; let (announcer, announcement) = Announcement::>>::new(); // Phase 1: Create some listeners let listeners: Vec<_> = (0..5).map(|_| announcement.listener()).collect(); // Phase 2: Drop half of them drop(listeners); // Phase 3: Create more and drop immediately for _ in 0..10 { drop(announcement.listener()); } // Phase 4: Create listeners in a thread that exits immediately let ann = announcement.clone(); thread::spawn(move || { let _l1 = ann.listener(); let _l2 = ann.listener(); // Thread exits, dropping listeners }) .join() .unwrap(); // Phase 5: Announce with no active listeners let mut data = HashMap::new(); data.insert("key".to_string(), vec![1, 2, 3]); announcer.announce(data.clone()).unwrap(); // Phase 6: Verify new listener still works let final_listener = announcement.listener(); assert_eq!(final_listener.try_listen(), Some(data)); } /// Test: Announcer with custom Drop impl and no listeners /// /// Verifies: Announcer with custom Drop impl and no listeners /// Category: close #[test] fn test_custom_drop_type_no_listeners() { static CUSTOM_DROP: AtomicUsize = AtomicUsize::new(0); #[derive(Clone, Debug)] struct CustomDrop { id: usize, } impl Drop for CustomDrop { /// Test: No-receiver test: drop /// /// Verifies: Verifies drop /// Category: no-receiver fn drop(&mut self) { CUSTOM_DROP.fetch_add(self.id, Ordering::SeqCst); } } CUSTOM_DROP.store(0, Ordering::SeqCst); { let (announcer, announcement) = Announcement::new(); let custom = CustomDrop { id: 100 }; announcer.announce(custom).unwrap(); drop(announcement); } // Should have been dropped (at least the original + the clone in OnceLock) assert!( CUSTOM_DROP.load(Ordering::SeqCst) >= 100, "CustomDrop should be dropped at least once" ); } /// Test: Close test: announce no listeners no blocking /// /// Verifies: announce no listeners no blocking /// Category: close #[test] fn test_announce_no_listeners_no_blocking() { let (announcer, _announcement) = Announcement::::new(); let start = Instant::now(); announcer.announce("immediate".to_string()).unwrap(); let elapsed = start.elapsed(); // Should complete in microseconds assert!( elapsed.as_micros() < 1000, "Announce should not block when there are no listeners" ); } /// Test: Close test: async no listeners /// /// Verifies: async no listeners /// Category: close #[tokio::test] async fn test_async_no_listeners() { let (announcer, _announcement) = Announcement::::new(); // Announce with no async listeners announcer.announce(42).unwrap(); } /// Test: Memory safety with no listeners and large recursive structure /// /// Verifies: Memory safety with no listeners and large recursive structure /// Category: close #[test] fn test_recursive_structure_no_listeners() { #[derive(Clone, Debug)] #[allow(dead_code)] enum Tree { Leaf(i32), Node(Box, Box), } fn create_deep_tree(depth: usize) -> Tree { if depth == 0 { Tree::Leaf(42) } else { Tree::Node( Box::new(create_deep_tree(depth - 1)), Box::new(create_deep_tree(depth - 1)), ) } } let (announcer, _announcement) = Announcement::new(); // Create a tree with depth 10 (1024 leaf nodes) let tree = create_deep_tree(10); // Announce with no listeners - should handle the recursive structure properly announcer.announce(tree).unwrap(); }