318 lines
9.3 KiB
Rust
318 lines
9.3 KiB
Rust
//! Critical high-priority tests identified in quality assessment.
|
|
//! These tests cover edge cases that could cause memory leaks, races, or UB.
|
|
|
|
use announcement::Announcement;
|
|
#[cfg(feature = "std")]
|
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
use std::sync::Arc;
|
|
use std::thread;
|
|
#[cfg(feature = "std")]
|
|
use std::time::Duration;
|
|
|
|
/// Test: Arc refcount: stress test
|
|
///
|
|
/// Verifies: Arc refcounting works under stress
|
|
/// Category: refcount
|
|
// Verifies no memory leaks under rapid announce/drop cycles
|
|
#[test]
|
|
fn test_arc_refcount_stress() {
|
|
for iteration in 0..10_000 {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let data = Arc::new(vec![0u8; 1024]);
|
|
let initial_count = Arc::strong_count(&data);
|
|
|
|
let listener = announcement.listener();
|
|
|
|
// Announce with Arc
|
|
announcer.announce(data.clone()).unwrap();
|
|
|
|
// Retrieve value
|
|
let received = listener.try_listen().unwrap();
|
|
|
|
// Verify refcount is reasonable
|
|
// Should be: original + announcement storage + received = 3
|
|
assert_eq!(
|
|
Arc::strong_count(&received),
|
|
initial_count + 2,
|
|
"Refcount mismatch at iteration {}",
|
|
iteration
|
|
);
|
|
|
|
// Drop everything and verify no leak
|
|
drop(received);
|
|
drop(listener);
|
|
drop(announcement);
|
|
|
|
// After all drops, only original reference should remain
|
|
assert_eq!(
|
|
Arc::strong_count(&data),
|
|
initial_count,
|
|
"Memory leak detected at iteration {}",
|
|
iteration
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Test: Arc refcount: multiple listener refs
|
|
///
|
|
/// Verifies: Arc refcounts correct with multiple listeners
|
|
/// Category: refcount
|
|
#[test]
|
|
fn test_arc_refcount_multiple_listeners() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let data = Arc::new(vec![0u8; 1024]);
|
|
let initial_count = Arc::strong_count(&data);
|
|
|
|
// Create 100 listeners
|
|
let listeners: Vec<_> = (0..100).map(|_| announcement.listener()).collect();
|
|
|
|
// Announce
|
|
announcer.announce(data.clone()).unwrap();
|
|
|
|
// All listeners retrieve
|
|
let values: Vec<_> = listeners.iter().map(|l| l.try_listen().unwrap()).collect();
|
|
|
|
// Refcount should be: original + storage + 100 listeners = 102
|
|
assert_eq!(Arc::strong_count(&values[0]), initial_count + 101);
|
|
|
|
// Drop listeners one by one and verify refcount decreases
|
|
drop(listeners);
|
|
assert_eq!(Arc::strong_count(&values[0]), initial_count + 101);
|
|
|
|
drop(values);
|
|
drop(announcement);
|
|
|
|
// Only original should remain
|
|
assert_eq!(Arc::strong_count(&data), initial_count);
|
|
}
|
|
|
|
/// Test: Integration test: close race with active listen
|
|
///
|
|
/// Verifies: Verifies close race with active listen
|
|
/// Category: integration
|
|
#[cfg(feature = "std")]
|
|
#[tokio::test]
|
|
async fn test_close_race_with_active_listen() {
|
|
use announcement::ListenError;
|
|
|
|
for iteration in 0..1_000 {
|
|
let (announcer, announcement) = Announcement::<i32>::new();
|
|
let listener = announcement.listener();
|
|
|
|
// Spawn task that will listen
|
|
let listen_task = tokio::spawn(async move { listener.listen().await });
|
|
|
|
// Give listener a moment to start waiting
|
|
tokio::time::sleep(Duration::from_micros(1)).await;
|
|
|
|
// Close while listener is actively waiting
|
|
announcer.close();
|
|
|
|
// Listener should wake up with Closed error
|
|
let result = listen_task.await.unwrap();
|
|
assert_eq!(
|
|
result,
|
|
Err(ListenError::Closed),
|
|
"Expected Closed error at iteration {}",
|
|
iteration
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Test: Integration test: close during listener setup
|
|
///
|
|
/// Verifies: Verifies close during listener setup
|
|
/// Category: integration
|
|
#[cfg(feature = "std")]
|
|
#[tokio::test]
|
|
async fn test_close_during_listener_setup() {
|
|
use announcement::ListenError;
|
|
|
|
for _ in 0..1_000 {
|
|
let (announcer, announcement) = Announcement::<i32>::new();
|
|
|
|
// Spawn multiple listeners that will race with close
|
|
let mut tasks = vec![];
|
|
for _ in 0..10 {
|
|
let listener = announcement.listener();
|
|
tasks.push(tokio::spawn(async move { listener.listen().await }));
|
|
}
|
|
|
|
// Close immediately
|
|
announcer.close();
|
|
|
|
// All listeners should get Closed error
|
|
for task in tasks {
|
|
let result = task.await.unwrap();
|
|
assert_eq!(result, Err(ListenError::Closed));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Test: Integration test: weak memory ordering visibility
|
|
///
|
|
/// Verifies: Verifies weak memory ordering visibility
|
|
/// Category: integration
|
|
// Verifies that happens-before relationships work correctly
|
|
#[cfg(feature = "std")]
|
|
#[test]
|
|
fn test_weak_memory_ordering_visibility() {
|
|
let shared_data = Arc::new(AtomicUsize::new(0));
|
|
|
|
for iteration in 0..1_000 {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let data1 = shared_data.clone();
|
|
let data2 = shared_data.clone();
|
|
let listener = announcement.listener();
|
|
|
|
let writer = thread::spawn(move || {
|
|
// Write to shared data
|
|
data1.store(42, Ordering::Relaxed);
|
|
|
|
// Announce (provides synchronization)
|
|
announcer.announce(()).unwrap();
|
|
});
|
|
|
|
let reader = thread::spawn(move || {
|
|
// Wait for announcement
|
|
listener.listen_blocking().unwrap();
|
|
|
|
// Due to happens-before, must see the write
|
|
data2.load(Ordering::Relaxed)
|
|
});
|
|
|
|
writer.join().unwrap();
|
|
let seen_value = reader.join().unwrap();
|
|
|
|
assert_eq!(
|
|
seen_value, 42,
|
|
"Memory ordering violation at iteration {}",
|
|
iteration
|
|
);
|
|
|
|
// Reset for next iteration
|
|
shared_data.store(0, Ordering::Relaxed);
|
|
}
|
|
}
|
|
|
|
/// Test: Integration test: weak memory ordering multiple observers
|
|
///
|
|
/// Verifies: Verifies weak memory ordering multiple observers
|
|
/// Category: integration
|
|
#[cfg(feature = "std")]
|
|
#[test]
|
|
fn test_weak_memory_ordering_multiple_observers() {
|
|
for iteration in 0..1_000 {
|
|
let shared_data = Arc::new(AtomicUsize::new(0));
|
|
let (announcer, announcement) = Announcement::new();
|
|
|
|
// Multiple reader threads
|
|
let mut handles = vec![];
|
|
for i in 0..8 {
|
|
let data = shared_data.clone();
|
|
let listener = announcement.listener();
|
|
|
|
handles.push(thread::spawn(move || {
|
|
listener.listen_blocking().unwrap();
|
|
(i, data.load(Ordering::Relaxed))
|
|
}));
|
|
}
|
|
|
|
// Writer thread
|
|
let data_writer = shared_data.clone();
|
|
let writer = thread::spawn(move || {
|
|
data_writer.store(99, Ordering::Relaxed);
|
|
announcer.announce(()).unwrap();
|
|
});
|
|
|
|
writer.join().unwrap();
|
|
|
|
// All readers must see the write
|
|
for handle in handles {
|
|
let (thread_id, value) = handle.join().unwrap();
|
|
assert_eq!(
|
|
value, 99,
|
|
"Thread {} didn't see write at iteration {}",
|
|
thread_id, iteration
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Test: Integration test: arc drop race with announcement
|
|
///
|
|
/// Verifies: Verifies arc drop race with announcement
|
|
/// Category: integration
|
|
#[test]
|
|
fn test_arc_drop_race_with_announcement() {
|
|
for _ in 0..10_000 {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let data = Arc::new(vec![0u8; 1024]);
|
|
let data_clone = data.clone();
|
|
|
|
// Thread that rapidly creates and drops listeners
|
|
let ann1 = announcement.clone();
|
|
let dropper = thread::spawn(move || {
|
|
for _ in 0..10 {
|
|
let listener = ann1.listener();
|
|
let _ = listener.try_listen();
|
|
drop(listener);
|
|
}
|
|
});
|
|
|
|
// Thread that announces
|
|
let announcer_thread = thread::spawn(move || {
|
|
thread::yield_now();
|
|
announcer.announce(data_clone).unwrap();
|
|
});
|
|
|
|
dropper.join().unwrap();
|
|
announcer_thread.join().unwrap();
|
|
|
|
// Verify final refcount
|
|
let listener = announcement.listener();
|
|
let received = listener.try_listen().unwrap();
|
|
|
|
// Should be: original + storage + received
|
|
assert!(Arc::strong_count(&received) >= 2);
|
|
}
|
|
}
|
|
|
|
/// Test: Integration test: closed flag visibility
|
|
///
|
|
/// Verifies: Verifies closed flag visibility
|
|
/// Category: integration
|
|
#[test]
|
|
fn test_closed_flag_visibility() {
|
|
for iteration in 0..10_000 {
|
|
let (announcer, announcement) = Announcement::<i32>::new();
|
|
|
|
// Multiple threads checking is_closed
|
|
let mut handles = vec![];
|
|
for _ in 0..4 {
|
|
let ann = announcement.clone();
|
|
handles.push(thread::spawn(move || {
|
|
// Spin until closed
|
|
while !ann.is_closed() {
|
|
thread::yield_now();
|
|
}
|
|
ann.is_closed() // Must be true
|
|
}));
|
|
}
|
|
|
|
// Close after threads start
|
|
thread::yield_now();
|
|
drop(announcer);
|
|
|
|
// All threads must eventually see closed
|
|
for handle in handles {
|
|
let result = handle.join().unwrap();
|
|
assert!(
|
|
result,
|
|
"Failed to see closed flag at iteration {}",
|
|
iteration
|
|
);
|
|
}
|
|
}
|
|
}
|