Files
swaits e8e55b7676 chore: initial release v0.1.0
Runtime-agnostic oneshot broadcast channel for Rust.
2025-10-13 19:36:37 -06:00

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
);
}
}
}