//! Tests specifically designed to catch mutations missed by cargo-mutants. //! //! These tests target specific mutations that weren't caught by existing tests. #[cfg(feature = "std")] use announcement::{Announcement, ListenError}; #[cfg(all(feature = "std", not(target_os = "macos")))] use std::sync::Arc; #[cfg(feature = "std")] use std::thread; #[cfg(feature = "std")] use std::time::{Duration, Instant}; /// Test: Mutation test: close actually closes /// /// Verifies: Verifies close actually closes /// Category: mutation /// Test that close() actually closes the announcer and wakes listeners. /// /// This catches the mutation where close() is replaced with (). /// /// This mutation is difficult to catch because close() consumes self, /// so Drop is called immediately after. We test the observable behavior: /// that calling close() results in listeners being woken up. #[test] #[cfg(feature = "std")] fn test_close_actually_closes() { let (announcer, announcement) = Announcement::::new(); let listener = announcement.listener(); // Create listener in another thread that will block with timeout let handle = thread::spawn(move || listener.listen_timeout_blocking(Duration::from_millis(100))); // Give thread time to start blocking thread::sleep(Duration::from_millis(10)); // Measure time from close() call to thread completing let start = Instant::now(); // Call close() - whether the work is done by close() or its Drop // doesn't matter for correctness, but both paths must work announcer.close(); // Verify thread receives Closed let result = handle.join().expect("Thread should not panic"); let elapsed = start.elapsed(); assert_eq!(result, Err(ListenError::Closed)); // Should complete quickly assert!( elapsed < Duration::from_millis(100), "Wakeup took too long: {:?}", elapsed ); // Verify is_closed() returns true assert!(announcement.is_closed()); } /// Test: Mutation test: close wakes multiple listeners /// /// Verifies: Verifies close wakes multiple listeners /// Category: mutation /// Test that close() wakes multiple listeners. /// /// Additional test for close() mutation - verifies multiple listeners /// are all woken up when close() is called. #[test] #[cfg(feature = "std")] fn test_close_wakes_multiple_listeners() { let (announcer, announcement) = Announcement::::new(); // Create multiple listeners in separate threads with timeout let handles: Vec<_> = (0..3) .map(|_| { let listener = announcement.listener(); thread::spawn(move || listener.listen_timeout_blocking(Duration::from_millis(100))) }) .collect(); // Give threads time to start blocking thread::sleep(Duration::from_millis(10)); // Close should wake ALL listeners announcer.close(); // Verify all threads receive Closed for handle in handles { let result = handle.join().expect("Thread should not panic"); assert_eq!(result, Err(ListenError::Closed)); } // Verify is_closed() returns true assert!(announcement.is_closed()); } /// Test: Mutation test: listen blocking non default /// /// Verifies: Verifies listen blocking non default /// Category: mutation /// Test listen_timeout_blocking with value announced from another thread. /// /// This catches the mutation where listen_timeout_blocking is replaced with Ok(Default::default()). #[test] #[cfg(feature = "std")] fn test_listen_blocking_non_default() { #[derive(Clone, Debug, PartialEq)] struct NonDefault { data: Vec, } let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); let test_value = NonDefault { data: vec![1, 2, 3, 4, 5], }; // Announce from another thread let announce_value = test_value.clone(); thread::spawn(move || { thread::sleep(Duration::from_millis(10)); announcer.announce(announce_value).unwrap(); }); // This blocks with timeout until value arrives let result = listener.listen_timeout_blocking(Duration::from_millis(100)); assert_eq!(result, Ok(test_value)); } /// Test: Mutation test: timeout actually waits /// /// Verifies: Verifies timeout actually waits /// Category: mutation /// Test that listen_timeout_blocking actually times out when no value is announced. /// /// This catches deadline calculation mutations (+ vs -). #[test] #[cfg(feature = "std")] #[cfg(not(target_os = "macos"))] fn test_timeout_actually_waits() { use std::sync::atomic::{AtomicBool, Ordering}; let keep_alive = Arc::new(AtomicBool::new(true)); let keep_alive_clone = keep_alive.clone(); let (announcer, announcement) = Announcement::::new(); let listener = announcement.listener(); // Keep announcer alive in another thread thread::spawn(move || { while keep_alive_clone.load(Ordering::Relaxed) { thread::sleep(Duration::from_millis(10)); } drop(announcer); }); let start = Instant::now(); let result = listener.listen_timeout_blocking(Duration::from_millis(100)); let elapsed = start.elapsed(); keep_alive.store(false, Ordering::Relaxed); // Should timeout after ~100ms assert_eq!(result, Err(ListenError::Timeout)); assert!( elapsed >= Duration::from_millis(90), "Should wait at least 90ms, took {:?}", elapsed ); assert!( elapsed < Duration::from_millis(150), "Should timeout within 150ms, took {:?}", elapsed ); } /// Test: Mutation test: timeout blocking correct value /// /// Verifies: Verifies timeout blocking correct value /// Category: mutation /// Test listen_timeout_blocking returns correct value when announced within timeout. /// /// This catches mutations that replace the function with Ok(Default::default()). #[test] #[cfg(feature = "std")] fn test_timeout_blocking_correct_value() { #[derive(Clone, Debug, PartialEq)] struct TestData { id: u64, name: String, } let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); let test_data = TestData { id: 999999, name: "unique_test_data_12345".to_string(), }; // Announce after delay let announce_data = test_data.clone(); thread::spawn(move || { thread::sleep(Duration::from_millis(50)); announcer.announce(announce_data).unwrap(); }); // Wait with timeout - should get the exact value we announced let result = listener.listen_timeout_blocking(Duration::from_millis(200)); assert_eq!(result, Ok(test_data)); }