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

803 lines
23 KiB
Rust

//! Runtime comparison tests for the announcement crate.
//! Verifies identical behavior across different async runtimes and blocking mode.
use announcement::Announcement;
use std::sync::Arc;
use std::thread;
use std::time::Duration;
// === TOKIO TESTS ===
/// Test: Runtime test: basic announce and listen
///
/// Verifies: Basic announcement and listening works correctly on the runtime
/// Category: runtime
#[tokio::test]
async fn tokio_basic_announce_listen() {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
announcer.announce(42).unwrap();
assert_eq!(listener.listen().await.unwrap(), 42);
}
/// Test: Runtime test: listen initiated before announce
///
/// Verifies: Listener waiting before announce receives value correctly on the runtime
/// Category: runtime
#[tokio::test]
async fn tokio_listen_before_announce() {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
let handle = tokio::spawn(async move { listener.listen().await.unwrap() });
tokio::time::sleep(Duration::from_millis(10)).await;
announcer.announce(42).unwrap();
assert_eq!(handle.await.unwrap(), 42);
}
/// Test: Runtime test: multiple concurrent listeners
///
/// Verifies: Multiple listeners all receive the same announced value on the runtime
/// Category: runtime
#[tokio::test]
async fn tokio_multiple_listeners() {
let (announcer, announcement) = Announcement::new();
let mut handles = vec![];
for i in 0..10 {
let listener = announcement.listener();
handles.push(tokio::spawn(async move {
(i, listener.listen().await.unwrap())
}));
}
announcer.announce(42).unwrap();
for handle in handles {
let (_, value) = handle.await.unwrap();
assert_eq!(value, 42);
}
}
/// Test: Runtime test: multi-threaded execution
///
/// Verifies: Multi-threaded runtime executes announcements correctly
/// Category: runtime
#[tokio::test(flavor = "multi_thread")]
async fn tokio_multi_threaded() {
let (announcer, announcement) = Announcement::new();
let mut handles = vec![];
for i in 0..20 {
let listener = announcement.listener();
handles.push(tokio::spawn(async move {
(i, listener.listen().await.unwrap())
}));
}
tokio::time::sleep(Duration::from_millis(10)).await;
announcer.announce(42).unwrap();
for handle in handles {
let (_, value) = handle.await.unwrap();
assert_eq!(value, 42);
}
}
/// Test: Runtime test: current-thread execution
///
/// Verifies: Current-thread runtime executes announcements correctly
/// Category: runtime
#[tokio::test(flavor = "current_thread")]
async fn tokio_current_thread() {
let (announcer, announcement) = Announcement::new();
let mut handles = vec![];
for i in 0..20 {
let listener = announcement.listener();
handles.push(tokio::spawn(async move {
(i, listener.listen().await.unwrap())
}));
}
tokio::time::sleep(Duration::from_millis(10)).await;
announcer.announce(42).unwrap();
for handle in handles {
let (_, value) = handle.await.unwrap();
assert_eq!(value, 42);
}
}
/// Test: Runtime test: select/race pattern
///
/// Verifies: Runtime select patterns work correctly with multiple announcements
/// Category: runtime
#[tokio::test]
async fn tokio_select_pattern() {
let (announcer1, announcement1) = Announcement::new();
let (announcer2, announcement2) = Announcement::new();
let listener1 = announcement1.listener();
let listener2 = announcement2.listener();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(50)).await;
announcer2.announce(2).unwrap();
});
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(20)).await;
announcer1.announce(1).unwrap();
});
tokio::select! {
val = listener1.listen() => assert_eq!(val, Ok(1)),
val = listener2.listen() => assert_eq!(val, Ok(2)),
}
}
/// Test: Runtime test: timeout behavior
///
/// Verifies: Runtime timeout mechanisms work correctly with announcements
/// Category: runtime
#[tokio::test]
async fn tokio_timeout_behavior() {
let (_announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
let result = tokio::time::timeout(Duration::from_millis(100), listener.listen()).await;
assert!(result.is_err()); // Should timeout
}
/// Test: Runtime test: blocking task interoperability
///
/// Verifies: Runtime blocking task spawning interoperates correctly with async
/// Category: runtime
#[tokio::test]
async fn tokio_spawn_blocking_interop() {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
let blocking_handle = tokio::task::spawn_blocking(move || {
// Blocking thread operation
thread::sleep(Duration::from_millis(10));
announcer.announce(42).unwrap();
});
let value = listener.listen().await.unwrap();
assert_eq!(value, 42);
blocking_handle.await.unwrap();
}
/// Test: Runtime test: mixed async and sync listeners
///
/// Verifies: Mix of async and synchronous listeners work correctly on the runtime
/// Category: runtime
#[tokio::test]
async fn tokio_mixed_async_sync() {
let (announcer, announcement) = Announcement::new();
// Async listeners
let mut async_handles = vec![];
for _ in 0..5 {
let listener = announcement.listener();
async_handles.push(tokio::spawn(
async move { listener.listen().await.unwrap() },
));
}
// Sync listeners in blocking threads
let mut sync_handles = vec![];
for _ in 0..5 {
let listener = announcement.listener();
sync_handles.push(tokio::task::spawn_blocking(move || loop {
if let Some(val) = listener.try_listen() {
return val;
}
thread::sleep(Duration::from_millis(1));
}));
}
announcer.announce(42).unwrap();
for handle in async_handles {
assert_eq!(handle.await.unwrap(), 42);
}
for handle in sync_handles {
assert_eq!(handle.await.unwrap(), 42);
}
}
// === SMOL TESTS (replacing async-std) ===
/// Test: Runtime test: basic announce and listen
///
/// Verifies: Basic announcement and listening works correctly on the runtime
/// Category: runtime
#[test]
fn smol_basic_announce_listen() {
smol::block_on(async {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
announcer.announce(42).unwrap();
assert_eq!(listener.listen().await.unwrap(), 42);
});
}
/// Test: Runtime test: listen initiated before announce
///
/// Verifies: Listener waiting before announce receives value correctly on the runtime
/// Category: runtime
#[test]
fn smol_listen_before_announce() {
smol::block_on(async {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
let handle = smol::spawn(async move { listener.listen().await.unwrap() });
smol::Timer::after(Duration::from_millis(10)).await;
announcer.announce(42).unwrap();
assert_eq!(handle.await, 42);
});
}
/// Test: Runtime test: multiple concurrent listeners
///
/// Verifies: Multiple listeners all receive the same announced value on the runtime
/// Category: runtime
#[test]
fn smol_multiple_listeners() {
smol::block_on(async {
let (announcer, announcement) = Announcement::new();
let mut handles = vec![];
for i in 0..10 {
let listener = announcement.listener();
handles.push(smol::spawn(
async move { (i, listener.listen().await.unwrap()) },
));
}
announcer.announce(42).unwrap();
for handle in handles {
let (_, value) = handle.await;
assert_eq!(value, 42);
}
});
}
/// Test: Runtime test: timeout behavior
///
/// Verifies: Runtime timeout mechanisms work correctly with announcements
/// Category: runtime
#[test]
fn smol_timeout_behavior() {
smol::block_on(async {
let (_announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
let result = smol::future::or(
async {
listener.listen().await?;
Ok(())
},
async {
smol::Timer::after(Duration::from_millis(100)).await;
Err::<(), _>(announcement::ListenError::Timeout)
},
)
.await;
assert!(result.is_err()); // Should timeout
});
}
/// Test: Runtime test: blocking task interoperability
///
/// Verifies: Runtime blocking task spawning interoperates correctly with async
/// Category: runtime
#[test]
fn smol_spawn_blocking_interop() {
smol::block_on(async {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
let blocking_handle = smol::unblock(move || {
// Blocking thread operation
thread::sleep(Duration::from_millis(10));
announcer.announce(42).unwrap();
});
let value = listener.listen().await.unwrap();
assert_eq!(value, 42);
blocking_handle.await;
});
}
// === SMOL TESTS (already in smol_runtime.rs, adding comparisons) ===
/// Test: Runtime test: Smol runtime basic operations
///
/// Verifies: Basic announcement and listening works on Smol runtime
/// Category: runtime
#[test]
fn smol_basic_operations() {
smol::block_on(async {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
announcer.announce(42).unwrap();
assert_eq!(listener.listen().await.unwrap(), 42);
});
}
/// Test: Runtime test: concurrent tasks
///
/// Verifies: Concurrent tasks work correctly on the runtime
/// Category: runtime
#[test]
fn smol_concurrent_tasks() {
smol::block_on(async {
let (announcer, announcement) = Announcement::new();
let mut handles = vec![];
for i in 0..10 {
let listener = announcement.listener();
handles.push(smol::spawn(
async move { (i, listener.listen().await.unwrap()) },
));
}
smol::Timer::after(Duration::from_millis(10)).await;
announcer.announce(42).unwrap();
for handle in handles {
let (_, value) = handle.await;
assert_eq!(value, 42);
}
});
}
// === BLOCKING TESTS ===
/// Test: Runtime test: blocking mode basic operations
///
/// Verifies: Basic announcement and listen operations work in blocking mode
/// Category: runtime
#[cfg(feature = "std")]
#[test]
fn blocking_basic_operations() {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
announcer.announce(42).unwrap();
assert_eq!(
listener.listen_timeout_blocking(Duration::from_millis(100)),
Ok(42)
);
}
/// Test: Runtime test: blocking mode timeout
///
/// Verifies: Timeout behavior works correctly in blocking mode
/// Category: runtime
#[cfg(feature = "std")]
#[test]
fn blocking_timeout() {
let (_announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
let result = listener.listen_timeout_blocking(Duration::from_millis(100));
assert_eq!(result, Err(announcement::ListenError::Timeout));
}
/// Test: Runtime test: blocking mode multiple threads
///
/// Verifies: Multiple blocking threads receive announced values correctly
/// Category: runtime
#[cfg(feature = "std")]
#[test]
fn blocking_multiple_threads() {
let (announcer, announcement) = Announcement::new();
let mut handles = vec![];
for i in 0..10 {
let listener = announcement.listener();
handles.push(thread::spawn(move || {
(
i,
listener.listen_timeout_blocking(Duration::from_millis(100)),
)
}));
}
thread::sleep(Duration::from_millis(10));
announcer.announce(42).unwrap();
for handle in handles {
let (_, value) = handle.join().unwrap();
assert_eq!(value, Ok(42));
}
}
// === COMPARISON TESTS ===
// Test that verifies identical behavior across all runtimes
/// Test: Runtime test: all runtimes basic comparison
///
/// Verifies: All runtimes produce identical results for basic operations
/// Category: runtime
#[test]
fn compare_all_runtimes_basic() {
// Tokio multi-thread
let tokio_multi_result = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
announcer.announce(42).unwrap();
listener.listen().await.unwrap()
});
// Tokio current-thread
let tokio_current_result = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
announcer.announce(42).unwrap();
listener.listen().await.unwrap()
});
// Smol
let smol_result2 = smol::block_on(async {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
announcer.announce(42).unwrap();
listener.listen().await.unwrap()
});
// Smol
let smol_result = smol::block_on(async {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
announcer.announce(42).unwrap();
listener.listen().await.unwrap()
});
// Blocking
#[cfg(feature = "std")]
let blocking_result = {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
announcer.announce(42).unwrap();
listener.listen_timeout_blocking(Duration::from_millis(100))
};
// All should be identical
assert_eq!(tokio_multi_result, 42);
assert_eq!(tokio_current_result, 42);
assert_eq!(smol_result2, 42);
assert_eq!(smol_result, 42);
#[cfg(feature = "std")]
assert_eq!(blocking_result, Ok(42));
}
/// Test: Runtime test: all runtimes concurrent comparison
///
/// Verifies: All runtimes produce identical results for concurrent operations
/// Category: runtime
#[test]
fn compare_all_runtimes_concurrent() {
let test_concurrent = |runtime: &str| -> Vec<i32> {
match runtime {
"tokio-multi" => tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let (announcer, announcement) = Announcement::new();
let mut handles = vec![];
for _ in 0..5 {
let listener = announcement.listener();
handles.push(tokio::spawn(
async move { listener.listen().await.unwrap() },
));
}
announcer.announce(42).unwrap();
let mut results = vec![];
for handle in handles {
results.push(handle.await.unwrap());
}
results
}),
"tokio-current" => tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let (announcer, announcement) = Announcement::new();
let mut handles = vec![];
for _ in 0..5 {
let listener = announcement.listener();
handles.push(tokio::spawn(
async move { listener.listen().await.unwrap() },
));
}
announcer.announce(42).unwrap();
let mut results = vec![];
for handle in handles {
results.push(handle.await.unwrap());
}
results
}),
"smol2" => smol::block_on(async {
let (announcer, announcement) = Announcement::new();
let mut handles = vec![];
for _ in 0..5 {
let listener = announcement.listener();
handles.push(smol::spawn(async move { listener.listen().await.unwrap() }));
}
announcer.announce(42).unwrap();
let mut results = vec![];
for handle in handles {
results.push(handle.await);
}
results
}),
"smol" => smol::block_on(async {
let (announcer, announcement) = Announcement::new();
let mut handles = vec![];
for _ in 0..5 {
let listener = announcement.listener();
handles.push(smol::spawn(async move { listener.listen().await.unwrap() }));
}
announcer.announce(42).unwrap();
let mut results = vec![];
for handle in handles {
results.push(handle.await);
}
results
}),
_ => panic!("Unknown runtime"),
}
};
let tokio_multi = test_concurrent("tokio-multi");
let tokio_current = test_concurrent("tokio-current");
let smol2 = test_concurrent("smol2");
let smol = test_concurrent("smol");
// All should produce same results
assert_eq!(tokio_multi, vec![42; 5]);
assert_eq!(tokio_current, vec![42; 5]);
assert_eq!(smol2, vec![42; 5]);
assert_eq!(smol, vec![42; 5]);
}
// === CROSS-RUNTIME TESTS ===
/// Test: Runtime test: cross-runtime announcement
///
/// Verifies: Announcement works correctly when components use different runtimes
/// Category: runtime
#[test]
fn cross_runtime_announcement() {
let (announcer, announcement) = Announcement::new();
let announcement = Arc::new(announcement);
// Create listener in one runtime
let ann1 = announcement.clone();
let tokio_listener = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async move { ann1.listener() });
// Create listener in another runtime
let ann2 = announcement.clone();
let smol_listener = smol::block_on(async move { ann2.listener() });
// Announce in yet another runtime
smol::block_on(async {
announcer.announce(42).unwrap();
});
// Verify in original runtimes
let tokio_result = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async { tokio_listener.listen().await.unwrap() });
let smol_result = smol::block_on(async { smol_listener.listen().await.unwrap() });
assert_eq!(tokio_result, 42);
assert_eq!(smol_result, 42);
}
// === BEHAVIOR CONSISTENCY TESTS ===
/// Test: Runtime test: consistent is_announced behavior
///
/// Verifies: is_announced() returns consistent results across invocations
/// Category: runtime
#[test]
fn verify_consistent_is_announced() {
let test_is_announced = || {
let (announcer, announcement) = Announcement::new();
let before = announcement.is_announced();
announcer.announce(42).unwrap();
let after = announcement.is_announced();
(before, after)
};
// Run multiple times to ensure consistency
for _ in 0..10 {
let (before, after) = test_is_announced();
assert!(!before);
assert!(after);
}
}
/// Test: Runtime test: consistent try_listen behavior
///
/// Verifies: try_listen() returns consistent results across invocations
/// Category: runtime
#[test]
fn verify_consistent_try_listen() {
let test_try_listen = || {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
let before = listener.try_listen();
announcer.announce(42).unwrap();
let after = listener.try_listen();
(before, after)
};
// Run multiple times to ensure consistency
for _ in 0..10 {
let (before, after) = test_try_listen();
assert_eq!(before, None);
assert_eq!(after, Some(42));
}
}
/// Test: Runtime test: multiple concurrent listeners
///
/// Verifies: Multiple listeners all receive the same announced value on the runtime
/// Category: runtime
#[test]
fn verify_consistent_multiple_listeners() {
let test_multiple = || {
let (announcer, announcement) = Announcement::new();
let listeners: Vec<_> = (0..5).map(|_| announcement.listener()).collect();
announcer.announce(42).unwrap();
listeners.iter().map(|l| l.try_listen()).collect::<Vec<_>>()
};
// Run multiple times
for _ in 0..10 {
let results = test_multiple();
assert_eq!(results, vec![Some(42); 5]);
}
}
// === FINAL COMPARISON SUMMARY ===
/// Test: Runtime test: comparison summary
///
/// Verifies: All runtime comparison tests pass and produce consistent results
/// Category: runtime
#[test]
fn runtime_comparison_summary() {
println!("Runtime Comparison Test Summary:");
println!("================================");
let mut all_passed = true;
// Test each runtime
{
// Tokio
let tokio_ok = std::panic::catch_unwind(|| {
tokio::runtime::Runtime::new().unwrap().block_on(async {
let (ann, announcement) = Announcement::new();
let l = announcement.listener();
ann.announce(1).unwrap();
assert_eq!(l.listen().await.unwrap(), 1);
});
})
.is_ok();
println!("Tokio: {}", if tokio_ok { "✓ PASS" } else { "✗ FAIL" });
all_passed = all_passed && tokio_ok;
// Smol
let smol_ok = std::panic::catch_unwind(|| {
smol::block_on(async {
let (ann, announcement) = Announcement::new();
let l = announcement.listener();
ann.announce(3).unwrap();
assert_eq!(l.listen().await.unwrap(), 3);
});
})
.is_ok();
println!("Smol: {}", if smol_ok { "✓ PASS" } else { "✗ FAIL" });
all_passed = all_passed && smol_ok;
}
// Blocking
#[cfg(feature = "std")]
{
let blocking_ok = std::panic::catch_unwind(|| {
let (ann, announcement) = Announcement::new();
let l = announcement.listener();
ann.announce(4).unwrap();
assert_eq!(l.listen_timeout_blocking(Duration::from_millis(100)), Ok(4));
})
.is_ok();
println!(
"Blocking: {}",
if blocking_ok { "✓ PASS" } else { "✗ FAIL" }
);
all_passed = all_passed && blocking_ok;
}
println!("================================");
println!(
"Overall: {}",
if all_passed {
"✓ ALL PASS"
} else {
"✗ SOME FAILED"
}
);
assert!(all_passed, "Not all runtime tests passed");
}