193 lines
6.4 KiB
Rust
193 lines
6.4 KiB
Rust
//! Tests specifically designed to hit TOCTOU protection code paths for coverage.
|
|
//!
|
|
//! These tests target the race condition protection code in listen(), listen_blocking(),
|
|
//! and listen_timeout_blocking() that checks for values/closure after registering the listener.
|
|
|
|
use announcement::Announcement;
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
use std::sync::Arc;
|
|
|
|
#[cfg(feature = "std")]
|
|
use std::thread;
|
|
#[cfg(feature = "std")]
|
|
use std::time::Duration;
|
|
|
|
/// Test: Async listen() TOCTOU value path (line 674)
|
|
///
|
|
/// Verifies: Value announced during listener registration is caught
|
|
/// Category: race
|
|
#[tokio::test]
|
|
async fn test_async_listen_toctou_value_path() {
|
|
// Run multiple iterations to increase chance of hitting the race window
|
|
for _ in 0..1000 {
|
|
let (announcer, announcement) = Announcement::<i32>::new();
|
|
let listener = announcement.listener();
|
|
|
|
let ready = Arc::new(AtomicBool::new(false));
|
|
let ready_clone = ready.clone();
|
|
|
|
// Spawn task that announces as soon as listener starts waiting
|
|
tokio::spawn(async move {
|
|
// Spin until listener is likely setting up
|
|
while !ready_clone.load(Ordering::SeqCst) {
|
|
tokio::task::yield_now().await;
|
|
}
|
|
// Announce immediately to hit TOCTOU window
|
|
let _ = announcer.announce(42);
|
|
});
|
|
|
|
// Start listening with a flag to signal we're about to wait
|
|
ready.store(true, Ordering::SeqCst);
|
|
let result = listener.listen().await;
|
|
|
|
// Should get the value (either fast path or TOCTOU path)
|
|
assert_eq!(result, Ok(42));
|
|
}
|
|
}
|
|
|
|
/// Test: Async listen() TOCTOU closed path (line 681)
|
|
///
|
|
/// Verifies: Announcer closed during listener registration is caught
|
|
/// Category: race
|
|
#[tokio::test]
|
|
async fn test_async_listen_toctou_closed_path() {
|
|
use std::sync::Barrier;
|
|
|
|
// Run multiple iterations to increase chance of hitting the race window
|
|
for _ in 0..10000 {
|
|
let (announcer, announcement) = Announcement::<i32>::new();
|
|
let listener = announcement.listener();
|
|
|
|
let barrier = Arc::new(Barrier::new(2));
|
|
let barrier_clone = barrier.clone();
|
|
|
|
// Spawn task that drops announcer at precisely the right moment
|
|
tokio::task::spawn_blocking(move || {
|
|
// Wait for listener to start setup
|
|
barrier_clone.wait();
|
|
// Immediately drop to hit the narrow TOCTOU window
|
|
drop(announcer);
|
|
});
|
|
|
|
// Use a manual future that coordinates with the barrier
|
|
let result = tokio::task::spawn_blocking(move || {
|
|
barrier.wait(); // Signal we're about to call listen
|
|
// Immediately start listening to hit the race window
|
|
tokio::runtime::Handle::current().block_on(async { listener.listen().await })
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
// Should get Closed error (either first check or TOCTOU path)
|
|
assert!(result.is_err());
|
|
}
|
|
}
|
|
|
|
/// Test: Blocking listen_timeout TOCTOU value path (line 819)
|
|
///
|
|
/// Verifies: Value announced during listener registration is caught in blocking mode
|
|
/// Category: race
|
|
#[cfg(feature = "std")]
|
|
#[test]
|
|
fn test_blocking_timeout_toctou_value_path() {
|
|
use std::sync::Barrier;
|
|
|
|
// Run many iterations with precise synchronization
|
|
for _ in 0..10000 {
|
|
let (announcer, announcement) = Announcement::<i32>::new();
|
|
let listener = announcement.listener();
|
|
|
|
let barrier = Arc::new(Barrier::new(2));
|
|
let barrier_clone = barrier.clone();
|
|
|
|
// Spawn thread that announces at precisely the right moment
|
|
thread::spawn(move || {
|
|
// Wait for listener to start
|
|
barrier_clone.wait();
|
|
// Announce immediately to hit TOCTOU window
|
|
let _ = announcer.announce(42);
|
|
});
|
|
|
|
// Synchronize and immediately start listening
|
|
barrier.wait();
|
|
let result = listener.listen_timeout_blocking(Duration::from_millis(100));
|
|
|
|
// Should get the value (either fast path or TOCTOU path)
|
|
assert_eq!(result, Ok(42));
|
|
}
|
|
}
|
|
|
|
/// Test: Blocking listen_timeout TOCTOU closed path (line 823)
|
|
///
|
|
/// Verifies: Announcer closed during listener registration is caught in blocking mode
|
|
/// Category: race
|
|
#[cfg(feature = "std")]
|
|
#[test]
|
|
fn test_blocking_timeout_toctou_closed_path() {
|
|
use std::sync::Barrier;
|
|
|
|
// Run many iterations with precise synchronization
|
|
for _ in 0..10000 {
|
|
let (announcer, announcement) = Announcement::<i32>::new();
|
|
let listener = announcement.listener();
|
|
|
|
let barrier = Arc::new(Barrier::new(2));
|
|
let barrier_clone = barrier.clone();
|
|
|
|
// Spawn thread that drops announcer at precisely the right moment
|
|
thread::spawn(move || {
|
|
// Wait for listener to start
|
|
barrier_clone.wait();
|
|
// Drop immediately to hit TOCTOU window
|
|
drop(announcer);
|
|
});
|
|
|
|
// Synchronize and immediately start listening
|
|
barrier.wait();
|
|
let result = listener.listen_timeout_blocking(Duration::from_millis(100));
|
|
|
|
// Should get Closed error (either first check or TOCTOU path)
|
|
assert!(result.is_err());
|
|
}
|
|
}
|
|
|
|
/// Test: Blocking listen_timeout zero remaining time path (line 828)
|
|
///
|
|
/// Verifies: Timeout returns error when remaining time is zero
|
|
/// Category: unit
|
|
#[cfg(feature = "std")]
|
|
#[test]
|
|
fn test_blocking_timeout_zero_remaining() {
|
|
let (announcer, announcement) = Announcement::<i32>::new();
|
|
let listener = announcement.listener();
|
|
|
|
// Keep announcer alive but don't announce
|
|
thread::spawn(move || {
|
|
thread::sleep(Duration::from_secs(10));
|
|
drop(announcer);
|
|
});
|
|
|
|
// Use a very short timeout (1 nanosecond)
|
|
let result = listener.listen_timeout_blocking(Duration::from_nanos(1));
|
|
|
|
// Should timeout
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
/// Test: Blocking listen_timeout actual timeout path (line 828 alternative)
|
|
///
|
|
/// Verifies: Timeout returns error when deadline passes
|
|
/// Category: unit
|
|
#[cfg(feature = "std")]
|
|
#[test]
|
|
fn test_blocking_timeout_deadline_expires() {
|
|
let (_announcer, announcement) = Announcement::<i32>::new();
|
|
let listener = announcement.listener();
|
|
|
|
// Use 1 millisecond timeout with no announcement
|
|
let result = listener.listen_timeout_blocking(Duration::from_millis(1));
|
|
|
|
// Should timeout (not closed)
|
|
assert_eq!(result.unwrap_err(), announcement::ListenError::Timeout);
|
|
}
|