Files
announcement/tests/mutation_coverage.rs
swaits 319cd3b6cb
CI / Test (beta, Linux) (push) Canceled after 0s
CI / Test (nightly, Linux) (push) Canceled after 0s
CI / Test (stable, Linux) (push) Canceled after 0s
CI / Test (macos-latest) (push) Canceled after 0s
CI / Test (windows-latest) (push) Canceled after 0s
CI / Rustfmt (push) Canceled after 0s
CI / Clippy (push) Canceled after 0s
CI / MSRV (push) Canceled after 0s
CI / Documentation (push) Canceled after 0s
CI / Coverage (push) Canceled after 0s
CI / Security Audit (push) Canceled after 0s
CI / Fuzz Testing (push) Canceled after 0s
CI / Examples (push) Canceled after 0s
CI / Benchmarks (push) Canceled after 0s
fix: disable tests on macos+windows and mutation testing in CI
2025-10-13 20:22:46 -06:00

215 lines
6.6 KiB
Rust

//! 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::<i32>::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::<i32>::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<u8>,
}
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::<i32>::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));
}