chore: initial release v0.1.0

Runtime-agnostic oneshot broadcast channel for Rust.
This commit is contained in:
swaits committed 2025-10-13 19:36:37 -06:00
commit e8e55b7676
64 files changed
+23966

No files matched your search

+396
View File
@@ -0,0 +1,396 @@
# Test Documentation Standard
This document defines the standard format for documenting tests in the `announcement` crate. Every test in this codebase MUST have a properly formatted header following these conventions.
## Table of Contents
1. [Header Format](#header-format)
2. [Category Definitions](#category-definitions)
3. [Writing Guidelines](#writing-guidelines)
4. [Examples](#examples)
5. [Maintenance](#maintenance)
## Header Format
### Standard Test Header
```rust
/// Test: [One-line summary of what is tested]
///
/// Verifies: [What behavior/invariant is verified]
/// Category: [Type: unit/integration/property/stress/timing/etc]
/// Coverage: [What code paths/scenarios are exercised]
#[test]
fn test_name() {
// test body
}
```
### Property Test Header
```rust
/// Property: [Invariant being tested]
///
/// Verifies: [What this property guarantees]
/// Strategy: [What input generation strategy is used]
proptest! {
#[test]
fn prop_name(inputs...) {
// property test body
}
}
```
### Async Test Header
```rust
/// Test: [One-line summary]
///
/// Verifies: [Behavior being verified]
/// Runtime: tokio/smol
/// Category: [Type]
#[tokio::test]
async fn test_name() {
// async test body
}
```
### Test Header with Notes
For tests with special conditions or important context:
```rust
/// Test: [Summary]
///
/// Verifies: [Behavior]
/// Category: [Type]
/// Note: [Any important context - e.g., "Runs 1000 iterations to catch race conditions"]
#[test]
fn test_name() {
// test body
}
```
**Historical Note**: Previously, timing-sensitive tests used `#[cfg(not(tarpaulin))]` to exclude them from coverage due to `cargo-tarpaulin`'s high instrumentation overhead. With the migration to `llvm-cov`, all tests now run during coverage analysis.
## Field Definitions
### Test / Property
**Required:** Yes
**Format:** One sentence, imperative mood
**Purpose:** Immediately tells the reader what this test does
**Examples:**
- "Test: Basic async listen operation"
- "Test: Multiple listeners receive same value"
- "Property: All listeners receive announced value"
### Verifies
**Required:** Yes
**Format:** One sentence describing the guarantee or behavior
**Purpose:** Explains what invariant or behavior this test validates
**Examples:**
- "Verifies: Listeners can wait asynchronously and receive values"
- "Verifies: Broadcast semantics work correctly"
- "Verifies: All N listeners receive the same announced value regardless of N"
### Category
**Required:** Yes
**Format:** One of the defined categories (see below)
**Purpose:** Classifies the test for filtering and organization
**Examples:**
- "Category: integration"
- "Category: property"
- "Category: stress"
### Coverage / Strategy
**Optional:** Use "Coverage" for regular tests, "Strategy" for property tests
**Format:** Brief description of scope or approach
**Purpose:** Explains what code paths or input strategies are used
**Examples:**
- "Coverage: Async listen path, event notification, value cloning"
- "Strategy: Generates 1-100 listeners with random values"
### Runtime
**Optional:** For async tests only
**Format:** tokio/smol/both
**Purpose:** Specifies which async runtime(s) are tested
**Example:** "Runtime: tokio"
### Excluded
**Optional:** Only for tests excluded from coverage or CI
**Format:** "Reason: explanation"
**Purpose:** Explains why test is excluded
**Example:** "Excluded: Coverage: timing-sensitive"
### Note
**Optional:** For additional context
**Format:** Any relevant information
**Purpose:** Provides context that doesn't fit other fields
**Example:** "Note: Runs 1000 iterations to catch race conditions"
## Category Definitions
### unit
Tests of individual functions or methods in isolation. Should be fast and deterministic.
### integration
End-to-end tests combining multiple components. Tests public API behavior.
### property
Property-based tests using `proptest` or `quickcheck`. Validates invariants across many generated inputs.
### stress
High-volume tests with many iterations or large inputs. May take longer to run.
### timing
Tests dependent on specific timing or delays. Often excluded from coverage due to flakiness.
### race
Tests for race conditions, TOCTOU bugs, or concurrency edge cases.
### concurrency
Multi-threaded tests verifying thread safety and concurrent access patterns.
### panic
Tests verifying correct behavior when panics occur. Tests panic safety and resource cleanup.
### edge
Tests of boundary conditions, extreme values, and unusual inputs.
### type
Tests verifying correct behavior with different Rust types.
### memory
Tests verifying memory safety, leak prevention, or Arc refcounting.
### mutation
Tests designed to detect semantic bugs through mutation testing.
## Writing Guidelines
### Be Concise
Test headers should be scannable. Aim for clarity over completeness.
**Good:**
```rust
/// Test: Listener receives value after announce
///
/// Verifies: Basic async listen operation works correctly
/// Category: integration
```
**Bad:**
```rust
/// Test: This test creates an announcement channel with an announcer and an announcement,
/// then creates a listener from the announcement, spawns an async task that announces a value,
/// and verifies that the listener can successfully receive that value through the async
/// listen() method...
///
/// [excessive verbosity]
```
### Be Specific
Say exactly what's being tested, not vague generalities.
**Good:**
```rust
/// Property: Arc refcount equals 1 + N listeners after announce
///
/// Verifies: No reference leaks in Arc-wrapped values
/// Strategy: Tests with 0-50 listeners
```
**Bad:**
```rust
/// Property: Arc stuff works
///
/// Verifies: Reference counting
/// Strategy: Random inputs
```
### Use Imperative Mood
Start test summaries with verbs.
**Good:**
- "Test: Multiple listeners receive same value"
- "Test: Panic in Drop doesn't leak memory"
**Bad:**
- "Test: When multiple listeners are created..."
- "Test: The case where Drop panics..."
### Keep Categories Consistent
Use defined categories. Don't invent new ones without updating this document.
### Document the Why
The "Verifies" field should explain WHY the test exists, not just WHAT it does.
**Good:**
```rust
/// Verifies: Channel cleanup happens correctly when announcer drops without announcing
```
**Bad:**
```rust
/// Verifies: Test passes
```
### Note Special Conditions
If a test has unusual requirements, exclusions, or setup, note it.
```rust
/// Test: Timeout behavior under load
///
/// Verifies: Timeouts work correctly even with many concurrent operations
/// Category: stress
/// Excluded: Coverage: takes too long (5+ seconds)
/// Note: Runs 10,000 iterations to ensure timing is reliable
```
## Examples
### Example 1: Simple Unit Test
```rust
/// Test: is_announced returns false before announce, true after
///
/// Verifies: State transition from unannounced to announced
/// Category: unit
#[test]
fn test_is_announced() {
let (announcer, announcement) = Announcement::<i32>::new();
assert!(!announcement.is_announced());
announcer.announce(42).unwrap();
assert!(announcement.is_announced());
}
```
### Example 2: Integration Test
```rust
/// Test: Multiple async listeners receive broadcast value
///
/// Verifies: Broadcast semantics work across multiple async tasks
/// Runtime: tokio
/// Category: integration
/// Coverage: Async listen, concurrent task spawning, value cloning
#[tokio::test]
async fn test_multiple_async_listeners() {
let (announcer, announcement) = Announcement::new();
// test body...
}
```
### Example 3: Property Test
```rust
/// Property: All N listeners receive the same announced value
///
/// Verifies: Broadcast semantics hold for arbitrary listener counts
/// Strategy: Generates 1-100 listeners with random i32 values
proptest! {
#[test]
fn prop_all_listeners_receive_value(value: i32, listener_count in 1usize..100) {
// property test body...
}
}
```
### Example 4: Stress Test
```rust
/// Test: TOCTOU protection under extreme contention
///
/// Verifies: Double-check pattern prevents races even with many threads
/// Category: race
/// Note: Runs 1000 iterations with tight timing windows to catch race conditions
#[test]
fn test_toctou_extreme_contention() {
// stress test body...
}
```
### Example 5: Panic Safety Test
```rust
/// Test: Arc refcount remains correct after panic
///
/// Verifies: Reference counting is panic-safe
/// Category: panic
/// Coverage: Arc refcount tracking, panic recovery, Drop behavior
#[test]
fn test_arc_refcount_with_panic() {
// panic safety test body...
}
```
## Maintenance
### Adding New Tests
When adding a new test:
1. **Choose the right category** - Review [Category Definitions](#category-definitions)
2. **Write the header first** - Document what you're testing before writing the test
3. **Keep it concise** - One-line summaries, focused "Verifies" statements
4. **Be consistent** - Follow existing patterns in the same test file
### Updating Existing Tests
When modifying a test:
1. **Update the header** - If the test behavior changes, update the documentation
2. **Maintain format** - Keep the same structure as other tests
3. **Check categories** - Ensure the category is still accurate
### Reviewing Test Documentation
When reviewing PRs:
1. **Check for headers** - Every test must have a header
2. **Verify clarity** - Test purpose should be immediately obvious
3. **Validate categories** - Categories should match the definitions
4. **Ensure consistency** - Format should match examples in this document
### Cross-Reference with TEST_DOCUMENTATION.md
This document defines the **in-code** header format. The file `TEST_DOCUMENTATION.md` provides a **high-level overview** of the entire test suite. When adding tests:
1. Update in-code headers (this document)
2. Update TEST_DOCUMENTATION.md if adding new test categories or files
3. Keep both documents in sync
## Rationale
### Why Use Headers?
1. **Immediate Clarity** - Know what a test does without reading the entire body
2. **Easy Scanning** - Quickly find tests by category or purpose
3. **Better Maintenance** - Clear documentation helps future developers
4. **Professional Polish** - Shows attention to detail and code quality
5. **Onboarding** - New contributors understand tests faster
### Why This Format?
1. **Rust Conventions** - Uses standard `///` doc comments
2. **Concise** - Respects the codebase's preference for simplicity
3. **Structured** - Fields provide consistent organization
4. **Scannable** - Visual consistency makes scanning fast
5. **Flexible** - Optional fields allow customization without clutter
### Why Mandatory?
Test documentation is not optional in this codebase. Every test MUST have a header because:
1. **Code Quality** - Documentation is part of code quality
2. **Maintainability** - Undocumented tests are technical debt
3. **Consistency** - Partial documentation is worse than none
4. **Professional Standards** - We hold ourselves to high standards
---
**Last Updated**: 2025-10-13
**Test Count**: ~628 tests across 24 files
**Compliance**: 100% (in progress)
For high-level test suite overview, see [TEST_DOCUMENTATION.md](../TEST_DOCUMENTATION.md).
+128
View File
@@ -0,0 +1,128 @@
//! Race condition test: Immediate announcer drop
//!
//! This test verifies that all waiting listeners successfully receive
//! a value even when the announcer is dropped immediately after announce().
//!
//! Pattern being tested:
//! 1. Create announcement and listeners
//! 2. Spawn N tasks calling .listen().await
//! 3. Call announce()
//! 4. IMMEDIATELY drop announcer (before awaiting task handles)
//! 5. Verify ALL listeners successfully receive the value
//!
//! If this test FAILS, there's a race condition where the announcer dropping
//! can close the channel before all listeners read the value.
use announcement::Announcement;
use std::sync::Arc;
/// Test: Race: try_listen after drop
///
/// Verifies: Verifies try_listen() works after announcer has dropped
/// Category: race
#[tokio::test]
async fn test_immediate_drop_after_announce() {
// Test with various listener counts
for num_listeners in [1, 10, 100, 1000] {
let (announcer, announcement) = Announcement::new();
// Spawn N listeners
let mut handles = Vec::with_capacity(num_listeners);
for i in 0..num_listeners {
let listener = announcement.listener();
handles.push(tokio::spawn(async move {
listener.listen().await.map(|v: Arc<i32>| (*v, i))
}));
}
// Small delay to ensure tasks are spawned and waiting
tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
// Announce (announcer is consumed and dropped by announce())
let value = Arc::new(42);
announcer.announce(value.clone()).unwrap();
// Verify ALL listeners received the value successfully
for handle in handles {
let result = handle.await.expect("Task should not panic");
assert!(
result.is_ok(),
"Listener should successfully receive value after immediate announcer drop"
);
let (received_value, _) = result.unwrap();
assert_eq!(
received_value, 42,
"Received value should match announced value"
);
}
}
}
/// Test: Race: try_listen after drop
///
/// Verifies: Verifies try_listen() works after announcer has dropped
/// Category: race
#[tokio::test]
async fn test_announce_then_drop_no_delay() {
// Even more aggressive: no delay between spawn and announce
let (announcer, announcement) = Announcement::new();
let mut handles = Vec::with_capacity(100);
for i in 0..100 {
let listener = announcement.listener();
handles.push(tokio::spawn(async move {
listener.listen().await.map(|v: Arc<i32>| (*v, i))
}));
}
// NO DELAY - announce immediately (announcer consumed by announce)
let value = Arc::new(999);
announcer.announce(value.clone()).unwrap();
// All should still receive
for handle in handles {
let result = handle.await.expect("Task should not panic");
assert!(result.is_ok(), "All listeners should receive value");
assert_eq!(result.unwrap().0, 999);
}
}
/// Test: Race: try_listen after drop
///
/// Verifies: Verifies try_listen() works after announcer has dropped
/// Category: race
#[tokio::test]
async fn test_drop_before_listen_completes() {
// Test: Does listen() after announce + drop work?
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
let value = Arc::new(777);
announcer.announce(value.clone()).unwrap();
// Announcer consumed by announce()
// This should still work - value is already available
let result = listener.listen().await;
assert!(result.is_ok(), "Should receive value even after drop");
assert_eq!(*result.unwrap(), 777);
}
/// Test: Race: try_listen after drop
///
/// Verifies: Verifies try_listen() works after announcer has dropped
/// Category: race
#[test]
fn test_try_listen_after_drop() {
// Synchronous version: try_listen after drop
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
let value = Arc::new(555);
announcer.announce(value.clone()).unwrap();
// Announcer consumed by announce()
// try_listen should work
let result = listener.try_listen();
assert!(result.is_some(), "Should get value via try_listen");
assert_eq!(*result.unwrap(), 555);
}
+246
View File
@@ -0,0 +1,246 @@
//! Arc refcount stress test - rapid announce/drop cycles
//! Tests for memory leaks in rapid channel creation/destruction with Arc values.
use announcement::Announcement;
use std::sync::Arc;
use std::thread;
/// Test: Arc refcount: cycle detection
///
/// Verifies: Arc reference cycles are handled correctly
/// Category: refcount
// Test 1: Rapid Arc refcount cycles (single-threaded)
#[test]
fn test_arc_refcount_rapid_cycles() {
let data = Arc::new(vec![0u8; 1024]); // 1KB test data
let initial_count = Arc::strong_count(&data);
// Run 10,000 create/announce/drop cycles
for i in 0..10_000 {
{
let (announcer, announcement) = Announcement::new();
// Create a few listeners
let l1 = announcement.listener();
let l2 = announcement.listener();
let l3 = announcement.listener();
// Announce the Arc
announcer.announce(data.clone()).unwrap();
// Retrieve values
let _v1 = l1.try_listen().unwrap();
let _v2 = l2.try_listen().unwrap();
let _v3 = l3.try_listen().unwrap();
// Everything drops here when block ends
}
// Periodic check after everything has dropped
if i % 1000 == 0 {
let current_count = Arc::strong_count(&data);
assert_eq!(
current_count, initial_count,
"Refcount leak detected at iteration {}: expected {}, got {}",
i, initial_count, current_count
);
}
}
// Final verification
assert_eq!(
Arc::strong_count(&data),
initial_count,
"Final refcount mismatch after 10k cycles"
);
}
/// Test: Refcount test: arc refcount mixed drop patterns
///
/// Verifies: Verifies arc refcount mixed drop patterns
/// Category: refcount
// Test 2: Rapid Arc cycles with mixed drop patterns
#[test]
fn test_arc_refcount_mixed_drop_patterns() {
let data = Arc::new(vec![42u8; 512]);
let initial_count = Arc::strong_count(&data);
for i in 0..5_000 {
{
let (announcer, announcement) = Announcement::new();
// Create various numbers of listeners
let listener_count = (i % 10) + 1;
let mut listeners = Vec::new();
for _ in 0..listener_count {
listeners.push(announcement.listener());
}
// Announce
announcer.announce(data.clone()).unwrap();
// Drop listeners in different patterns
match i % 4 {
0 => drop(listeners), // Drop all at once
1 => {
// Drop one at a time
for l in listeners {
drop(l);
}
}
2 => {
// Retrieve then drop
let values: Vec<_> =
listeners.iter().map(|l| l.try_listen().unwrap()).collect();
drop(values);
drop(listeners);
}
_ => {
// Drop half, retrieve from rest
let mid = listeners.len() / 2;
drop(listeners.drain(0..mid).collect::<Vec<_>>());
for l in &listeners {
let _ = l.try_listen();
}
}
}
// Block ends - everything drops including announcement
}
// Verify refcount periodically after everything dropped
if i % 500 == 0 {
assert_eq!(Arc::strong_count(&data), initial_count);
}
}
assert_eq!(Arc::strong_count(&data), initial_count);
}
/// Test: Arc refcount: cycle detection
///
/// Verifies: Arc reference cycles are handled correctly
/// Category: refcount
// Test 3: Concurrent Arc refcount stress
#[test]
fn test_arc_refcount_concurrent_cycles() {
let data = Arc::new(vec![0u8; 2048]);
let initial_count = Arc::strong_count(&data);
let mut handles = Vec::new();
// 8 threads all doing rapid cycles
for thread_id in 0..8 {
let data_clone = data.clone();
handles.push(thread::spawn(move || {
for i in 0..1_000 {
let (announcer, announcement) = Announcement::new();
let mut listeners = Vec::new();
for _ in 0..5 {
listeners.push(announcement.listener());
}
announcer.announce(data_clone.clone()).unwrap();
// Mix of operations
match i % 3 {
0 => {
// Just drop everything
drop(listeners);
}
1 => {
// Retrieve and verify
for l in &listeners {
let v = l.try_listen().unwrap();
assert_eq!(v.len(), 2048);
}
}
_ => {
// Mixed
let _ = listeners[0].try_listen();
drop(listeners);
}
}
if i % 100 == 0 && thread_id == 0 {
println!("Thread {} completed {} iterations", thread_id, i);
}
}
}));
}
// Wait for all threads
for handle in handles {
handle.join().unwrap();
}
// Final refcount should be back to initial
assert_eq!(
Arc::strong_count(&data),
initial_count,
"Refcount leak after concurrent stress test"
);
}
/// Test: Refcount test: arc refcount with announcer drop
///
/// Verifies: Verifies arc refcount with announcer drop
/// Category: refcount
// Test 4: Arc refcount with announcer drop (no announce)
#[test]
fn test_arc_refcount_with_announcer_drop() {
let data = Arc::new(vec![1u8; 256]);
let initial_count = Arc::strong_count(&data);
for _ in 0..10_000 {
let (announcer, announcement) = Announcement::<Arc<Vec<u8>>>::new();
let _l1 = announcement.listener();
let _l2 = announcement.listener();
// Drop announcer WITHOUT announcing
drop(announcer);
// Listeners should see closed, no Arc refcount change for data
// since it was never announced
}
// Refcount should be unchanged
assert_eq!(Arc::strong_count(&data), initial_count);
}
/// Test: Refcount test: arc refcount with listener cloning
///
/// Verifies: Verifies arc refcount with listener cloning
/// Category: refcount
// Test 5: Arc refcount with listener cloning stress
#[test]
fn test_arc_refcount_with_listener_cloning() {
let data = Arc::new(vec![7u8; 512]);
let initial_count = Arc::strong_count(&data);
for _ in 0..5_000 {
let (announcer, announcement) = Announcement::new();
let l1 = announcement.listener();
// Clone listener many times
let mut clones = vec![l1.clone()];
for _ in 0..10 {
clones.push(clones[0].clone());
}
announcer.announce(data.clone()).unwrap();
// All clones retrieve
for l in &clones {
let _v = l.try_listen().unwrap();
}
// Everything drops
}
assert_eq!(Arc::strong_count(&data), initial_count);
}
+247
View File
@@ -0,0 +1,247 @@
//! Tests specifically for close() behavior and mutation testing coverage
//!
//! These tests target gaps in mutation testing, particularly around the
//! explicit close() method.
use announcement::{Announcement, ListenError};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
/// Test: Verify close() actually sets the closed flag
///
/// Verifies: close() actually sets the closed flag
/// Category: close
#[test]
fn test_close_sets_closed_flag() {
let (announcer, announcement) = Announcement::<i32>::new();
// Initially not closed
assert!(!announcement.is_closed());
// Call close()
announcer.close();
// Verify closed flag is set
assert!(announcement.is_closed());
}
/// Test: Verify close() notifies waiting async listeners
///
/// Verifies: close() notifies waiting async listeners
/// Category: close
#[tokio::test]
async fn test_close_notifies_async_listeners() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// Spawn task that waits
let handle = tokio::spawn(async move { listener.listen().await });
// Give listener time to start waiting
tokio::time::sleep(Duration::from_millis(10)).await;
// Close the announcer
announcer.close();
// Listener should receive Closed error quickly
let result = tokio::time::timeout(Duration::from_millis(100), handle).await;
assert!(result.is_ok(), "Listener should be notified immediately");
let listen_result = result.unwrap().unwrap();
assert_eq!(listen_result, Err(ListenError::Closed));
}
/// Test: Close test: close notifies blocking listeners
///
/// Verifies: close notifies blocking listeners
/// Category: close
#[cfg(feature = "std")]
#[test]
fn test_close_notifies_blocking_listeners() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// Spawn thread that waits
let handle =
thread::spawn(move || listener.listen_timeout_blocking(Duration::from_millis(100)));
// Give listener time to start waiting
thread::sleep(Duration::from_millis(10));
// Close the announcer
announcer.close();
// Listener should receive Closed error quickly
let result = handle.join().unwrap();
assert_eq!(result, Err(ListenError::Closed));
}
/// Test: Verify close() notifies multiple waiting listeners
///
/// Verifies: close() notifies multiple waiting listeners
/// Category: close
#[tokio::test]
async fn test_close_notifies_all_listeners() {
let (announcer, announcement) = Announcement::<i32>::new();
// Create multiple listeners
let listeners: Vec<_> = (0..10).map(|_| announcement.listener()).collect();
// Spawn tasks for all listeners
let mut handles = Vec::new();
for listener in listeners {
handles.push(tokio::spawn(async move { listener.listen().await }));
}
// Give listeners time to start waiting
tokio::time::sleep(Duration::from_millis(10)).await;
// Close the announcer
announcer.close();
// All listeners should receive Closed error
for handle in handles {
let result = tokio::time::timeout(Duration::from_millis(100), handle).await;
assert!(result.is_ok(), "All listeners should be notified");
let listen_result = result.unwrap().unwrap();
assert_eq!(listen_result, Err(ListenError::Closed));
}
}
/// Test: Verify close() works even with no listeners
///
/// Verifies: close() works even with no listeners
/// Category: close
#[test]
fn test_close_with_no_listeners() {
let (announcer, announcement) = Announcement::<i32>::new();
// Close without creating any listeners
announcer.close();
// Verify closed flag is set
assert!(announcement.is_closed());
// Create a listener after close
let listener = announcement.listener();
assert_eq!(listener.try_listen(), None);
assert!(listener.is_closed());
}
/// Test: Verify close() prevents future listeners from waiting
///
/// Verifies: close() prevents future listeners from waiting
/// Category: close
#[tokio::test]
async fn test_close_prevents_future_waiting() {
let (announcer, announcement) = Announcement::<i32>::new();
// Close immediately
announcer.close();
// Create listener after close
let listener = announcement.listener();
// listen() should return immediately with Closed
let result = tokio::time::timeout(Duration::from_millis(50), listener.listen()).await;
assert!(result.is_ok(), "listen() should not hang after close");
let listen_result = result.unwrap();
assert_eq!(listen_result, Err(ListenError::Closed));
}
/// Test: Verify close() interacts correctly with try_listen()
///
/// Verifies: close() interacts correctly with try_listen()
/// Category: close
#[test]
fn test_close_try_listen_interaction() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// try_listen() returns None before close
assert_eq!(listener.try_listen(), None);
assert!(!listener.is_closed());
// Close the announcer
announcer.close();
// try_listen() still returns None after close
assert_eq!(listener.try_listen(), None);
// But is_closed() returns true
assert!(listener.is_closed());
}
/// Test: Close followed by listener checks in rapid succession
///
/// Verifies: Close followed by listener checks in rapid succession
/// Category: close
#[test]
fn test_close_rapid_checks() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
announcer.close();
// Rapidly check closed status
for _ in 0..1000 {
assert!(listener.is_closed());
assert_eq!(listener.try_listen(), None);
}
}
/// Test: Verify close() with Arc-wrapped value type
///
/// Verifies: close() with Arc-wrapped value type
/// Category: close
#[test]
fn test_close_with_arc_type() {
let (announcer, announcement) = Announcement::<Arc<String>>::new();
let listener = announcement.listener();
announcer.close();
assert!(announcement.is_closed());
assert!(listener.is_closed());
assert_eq!(listener.try_listen(), None);
}
/// Test: Multiple threads checking closed status after close()
///
/// Verifies: Multiple threads checking closed status after close()
/// Category: close
#[test]
fn test_close_concurrent_status_checks() {
use std::sync::Barrier;
let (announcer, announcement) = Announcement::<i32>::new();
let barrier = Arc::new(Barrier::new(11)); // 10 threads + main thread
// Create listeners in multiple threads
let handles: Vec<_> = (0..10)
.map(|_| {
let ann = announcement.clone();
let barrier = Arc::clone(&barrier);
thread::spawn(move || {
let listener = ann.listener();
// Wait for close() to complete via barrier synchronization
barrier.wait();
listener.is_closed()
})
})
.collect();
// Close the announcer
announcer.close();
// Synchronize: ensure close() completes before threads check
barrier.wait();
// All threads should see closed = true
for handle in handles {
let is_closed = handle.join().unwrap();
assert!(is_closed, "All threads should see closed flag");
}
}
+363
View File
@@ -0,0 +1,363 @@
//! Close signal race condition tests
//! Tests that closing an announcer properly signals listeners even during active listen operations.
use announcement::{Announcement, ListenError};
#[cfg(feature = "std")]
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, Barrier,
};
#[cfg(feature = "std")]
use std::thread;
use std::time::Duration;
/// Test: Race condition test: close during blocking listen
///
/// Verifies: Verifies no race condition in close during blocking listen
/// Category: race
#[tokio::test]
async fn test_close_during_active_listen() {
for iteration in 0..1_000 {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// Spawn task that will listen
let listen_handle = tokio::spawn(async move { listener.listen().await });
// Close at varying times
match iteration % 5 {
0 => {
// Close immediately
announcer.close();
}
1 => {
// Short delay
tokio::time::sleep(Duration::from_micros(1)).await;
announcer.close();
}
2 => {
// Medium delay
tokio::time::sleep(Duration::from_micros(10)).await;
announcer.close();
}
3 => {
// Drop instead of explicit close
drop(announcer);
}
_ => {
// Yield and close
tokio::task::yield_now().await;
announcer.close();
}
}
// Listener should see Closed error
let result = listen_handle.await.unwrap();
assert_eq!(result, Err(ListenError::Closed), "Iteration {}", iteration);
}
}
#[cfg(feature = "std")]
/// Test: Race condition test: close during blocking listen
///
/// Verifies: Verifies no race condition in close during blocking listen
/// Category: race
#[test]
fn test_close_during_blocking_listen() {
for iteration in 0..500 {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// Thread that will block
let handle = thread::spawn(move || listener.listen_blocking());
// Close at varying times
match iteration % 4 {
0 => {
// Immediate
announcer.close();
}
1 => {
// Short delay
thread::sleep(Duration::from_micros(100));
announcer.close();
}
2 => {
// Drop
thread::sleep(Duration::from_micros(50));
drop(announcer);
}
_ => {
// Yield and close
thread::yield_now();
announcer.close();
}
}
let result = handle.join().unwrap();
assert_eq!(result, Err(ListenError::Closed));
}
}
/// Test: Race condition test: close during timeout blocking
///
/// Verifies: Verifies no race condition in close during timeout blocking
/// Category: race
#[tokio::test]
async fn test_close_with_concurrent_listeners() {
for _ in 0..500 {
let (announcer, announcement) = Announcement::<i32>::new();
let mut handles = Vec::new();
// Spawn 10 listeners
for _ in 0..10 {
let listener = announcement.listener();
handles.push(tokio::spawn(async move { listener.listen().await }));
}
// Give them time to set up
tokio::time::sleep(Duration::from_micros(10)).await;
// Close
announcer.close();
// All should see Closed
for handle in handles {
let result = handle.await.unwrap();
assert_eq!(result, Err(ListenError::Closed));
}
}
}
/// Test: Race condition test: close during timeout blocking
///
/// Verifies: Verifies no race condition in close during timeout blocking
/// Category: race
#[tokio::test]
async fn test_close_in_toctou_window() {
for iteration in 0..2_000 {
let (announcer, announcement) = Announcement::<i32>::new();
let announcement_clone = announcement.clone();
// Thread that closes at random time
let close_handle = tokio::spawn(async move {
// Vary timing to hit different points
match iteration % 7 {
0 => {}
1 => tokio::task::yield_now().await,
2 => tokio::time::sleep(Duration::from_nanos(1)).await,
3 => tokio::time::sleep(Duration::from_nanos(10)).await,
4 => tokio::time::sleep(Duration::from_nanos(100)).await,
5 => tokio::time::sleep(Duration::from_micros(1)).await,
_ => tokio::time::sleep(Duration::from_micros(10)).await,
}
announcer.close();
});
// Task that listens
let listen_handle = tokio::spawn(async move {
let listener = announcement_clone.listener();
listener.listen().await
});
close_handle.await.unwrap();
let result = listen_handle.await.unwrap();
// Must see either Closed error or timeout (but not hang)
assert_eq!(result, Err(ListenError::Closed));
}
}
#[cfg(feature = "std")]
/// Test: Race condition test: close during timeout blocking
///
/// Verifies: Verifies no race condition in close during timeout blocking
/// Category: race
#[test]
fn test_close_during_timeout_blocking() {
for iteration in 0..500 {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
let handle =
thread::spawn(move || listener.listen_timeout_blocking(Duration::from_secs(2)));
// Close at varying times
if iteration % 2 == 0 {
thread::sleep(Duration::from_micros(50));
}
announcer.close();
let result = handle.join().unwrap();
assert_eq!(result, Err(ListenError::Closed));
}
}
/// Test: Race condition test: close with barrier sync
///
/// Verifies: Verifies no race condition in close with barrier sync
/// Category: race
#[tokio::test]
async fn test_rapid_close_listen_cycles() {
for _ in 0..1_000 {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
let listen_handle = tokio::spawn(async move { listener.listen().await });
// Close immediately
announcer.close();
let result = listen_handle.await.unwrap();
assert_eq!(result, Err(ListenError::Closed));
}
}
#[cfg(feature = "std")]
/// Test: Race condition test: close with barrier sync
///
/// Verifies: Verifies no race condition in close with barrier sync
/// Category: race
#[test]
fn test_close_with_barrier_sync() {
for _ in 0..500 {
let (announcer, announcement) = Announcement::<i32>::new();
let barrier = Arc::new(Barrier::new(11)); // 10 listeners + closer
let mut handles = Vec::new();
// 10 threads that will block
for _ in 0..10 {
let listener = announcement.listener();
let barrier_clone = barrier.clone();
handles.push(thread::spawn(move || {
barrier_clone.wait(); // Sync start
listener.listen_blocking()
}));
}
// Closer thread
let closer = thread::spawn(move || {
barrier.wait(); // Sync with listeners
// Close immediately after barrier
announcer.close();
});
closer.join().unwrap();
// All listeners should see Closed
for handle in handles {
let result = handle.join().unwrap();
assert_eq!(result, Err(ListenError::Closed));
}
}
}
/// Test: Stress test: close propagation stress
///
/// Verifies: Verifies system handles close propagation stress correctly under stress
/// Category: race
#[tokio::test]
async fn test_close_detection_is_closed() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
assert!(!announcement.is_closed());
assert!(!listener.is_closed());
// Close in background
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(10)).await;
announcer.close();
});
// Poll until closed
let start = tokio::time::Instant::now();
loop {
if listener.is_closed() {
break;
}
if start.elapsed() > Duration::from_secs(1) {
panic!("is_closed() never became true");
}
tokio::time::sleep(Duration::from_micros(100)).await;
}
// Now listen() should return Closed immediately
let result = listener.listen().await;
assert_eq!(result, Err(ListenError::Closed));
}
#[cfg(feature = "std")]
/// Test: Stress test: close propagation stress
///
/// Verifies: Verifies system handles close propagation stress correctly under stress
/// Category: race
#[test]
fn test_close_propagation_stress() {
let running = Arc::new(AtomicBool::new(true));
let mut handles = Vec::new();
// Run for 5 seconds
for _ in 0..10 {
let running_clone = running.clone();
handles.push(thread::spawn(move || {
let mut count = 0;
while running_clone.load(Ordering::Relaxed) {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// Spawn blocking thread
let h = thread::spawn(move || listener.listen_blocking());
// Close quickly
thread::yield_now();
announcer.close();
let result = h.join().unwrap();
assert_eq!(result, Err(ListenError::Closed));
count += 1;
}
count
}));
}
thread::sleep(Duration::from_secs(5));
running.store(false, Ordering::Relaxed);
let total: usize = handles.into_iter().map(|h| h.join().unwrap()).sum();
println!("Close propagation stress: {} successful cycles", total);
}
#[tokio::test]
async fn test_close_with_try_listen_checks() {
for _ in 0..1_000 {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// Task doing rapid try_listen
let check_handle = tokio::spawn(async move {
for _ in 0..100 {
let _ = listener.try_listen();
tokio::task::yield_now().await;
}
listener
});
// Close
tokio::task::yield_now().await;
announcer.close();
// Get listener back and verify it sees closed
let listener = check_handle.await.unwrap();
assert!(listener.is_closed());
assert_eq!(listener.try_listen(), None);
}
}
+665
View File
@@ -0,0 +1,665 @@
//! Closed Detection Tests
//!
//! Tests for the closed detection API that notifies listeners when
//! an announcer is dropped or explicitly closed without announcing.
//!
//! NOTE: All async listen() calls use short timeouts (100ms) to prevent
//! hanging if Drop impl is mutated. This ensures fast failure detection
//! in mutation testing.
use announcement::{Announcement, ListenError};
use std::sync::Arc;
use std::time::Duration;
/// Timeout for listen operations in tests (short to catch Drop impl mutations)
const LISTEN_TIMEOUT: Duration = Duration::from_millis(100);
// =============================================================================
// Basic Closed Detection Tests
// =============================================================================
/// Test: Close test: dropped announcer notifies listener
///
/// Verifies: Verifies dropped announcer notifies listener behavior
/// Category: close
#[tokio::test]
async fn test_dropped_announcer_notifies_listener() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// Drop announcer without announcing
drop(announcer);
// Listener should receive Closed error (with timeout to prevent hanging in mutation tests)
let result = tokio::time::timeout(LISTEN_TIMEOUT, listener.listen())
.await
.expect("listen should not timeout when announcer is dropped");
assert_eq!(result, Err(ListenError::Closed));
}
/// Test: Close test: explicit close notifies listener
///
/// Verifies: Verifies explicit close notifies listener behavior
/// Category: close
#[tokio::test]
async fn test_explicit_close_notifies_listener() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// Explicitly close
announcer.close();
// Listener should receive Closed error (with timeout to prevent hanging)
let result = tokio::time::timeout(LISTEN_TIMEOUT, listener.listen())
.await
.expect("listen should not timeout when announcer is closed");
assert_eq!(result, Err(ListenError::Closed));
}
/// Test: Close test: multiple listeners all notified on drop
///
/// Verifies: Verifies multiple listeners all notified on drop behavior
/// Category: close
#[tokio::test]
async fn test_multiple_listeners_all_notified_on_drop() {
let (announcer, announcement) = Announcement::<i32>::new();
let l1 = announcement.listener();
let l2 = announcement.listener();
let l3 = announcement.listener();
// Drop announcer
drop(announcer);
// All listeners should receive Closed error
assert_eq!(
tokio::time::timeout(LISTEN_TIMEOUT, l1.listen())
.await
.expect("listen should not timeout"),
Err(ListenError::Closed)
);
assert_eq!(
tokio::time::timeout(LISTEN_TIMEOUT, l2.listen())
.await
.expect("listen should not timeout"),
Err(ListenError::Closed)
);
assert_eq!(
tokio::time::timeout(LISTEN_TIMEOUT, l3.listen())
.await
.expect("listen should not timeout"),
Err(ListenError::Closed)
);
}
/// Test: Close test: multiple listeners all notified on close
///
/// Verifies: Verifies multiple listeners all notified on close behavior
/// Category: close
#[tokio::test]
async fn test_multiple_listeners_all_notified_on_close() {
let (announcer, announcement) = Announcement::<i32>::new();
let l1 = announcement.listener();
let l2 = announcement.listener();
let l3 = announcement.listener();
// Explicit close
announcer.close();
// All listeners should receive Closed error
assert_eq!(
tokio::time::timeout(LISTEN_TIMEOUT, l1.listen())
.await
.expect("listen should not timeout"),
Err(ListenError::Closed)
);
assert_eq!(
tokio::time::timeout(LISTEN_TIMEOUT, l2.listen())
.await
.expect("listen should not timeout"),
Err(ListenError::Closed)
);
assert_eq!(
tokio::time::timeout(LISTEN_TIMEOUT, l3.listen())
.await
.expect("listen should not timeout"),
Err(ListenError::Closed)
);
}
/// Test: Close test: is closed after drop
///
/// Verifies: Verifies is closed after drop behavior
/// Category: close
#[test]
fn test_is_closed_after_drop() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
assert!(!announcement.is_closed());
assert!(!listener.is_closed());
drop(announcer);
assert!(announcement.is_closed());
assert!(listener.is_closed());
}
/// Test: Close test: is closed after explicit close
///
/// Verifies: Verifies is closed after explicit close behavior
/// Category: close
#[test]
fn test_is_closed_after_explicit_close() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
assert!(!announcement.is_closed());
assert!(!listener.is_closed());
announcer.close();
assert!(announcement.is_closed());
assert!(listener.is_closed());
}
// =============================================================================
// Concurrent Closed Detection Tests
// =============================================================================
/// Test: Close test: concurrent listeners waiting when closed
///
/// Verifies: Verifies concurrent listeners waiting when closed behavior
/// Category: close
#[tokio::test]
async fn test_concurrent_listeners_waiting_when_closed() {
let (announcer, announcement) = Announcement::<i32>::new();
// Spawn multiple waiting listeners
let mut handles = vec![];
for _ in 0..10 {
let listener = announcement.listener();
handles.push(tokio::spawn(async move {
tokio::time::timeout(LISTEN_TIMEOUT, listener.listen())
.await
.expect("listen should not timeout")
}));
}
// Give them time to start waiting
tokio::time::sleep(Duration::from_millis(10)).await;
// Drop announcer - all should wake with Closed error
drop(announcer);
// Verify all received Closed error
for handle in handles {
assert_eq!(handle.await.unwrap(), Err(ListenError::Closed));
}
}
/// Test: Close test: race drop during listen setup
///
/// Verifies: Verifies race drop during listen setup behavior
/// Category: close
#[tokio::test]
async fn test_race_drop_during_listen_setup() {
// Test the race condition where announcer is dropped while listener
// is setting up (between checking and registering event listener)
for _ in 0..100 {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
let handle = tokio::spawn(async move {
tokio::time::timeout(LISTEN_TIMEOUT, listener.listen())
.await
.expect("listen should not timeout")
});
// Drop immediately (race with listen setup)
drop(announcer);
// Should still receive Closed error
assert_eq!(handle.await.unwrap(), Err(ListenError::Closed));
}
}
/// Test: Close test: mixed waiting and polling listeners
///
/// Verifies: Verifies mixed waiting and polling listeners behavior
/// Category: close
#[tokio::test]
async fn test_mixed_waiting_and_polling_listeners() {
let (announcer, announcement) = Announcement::<i32>::new();
// Some listeners waiting
let mut async_handles = vec![];
for _ in 0..5 {
let listener = announcement.listener();
async_handles.push(tokio::spawn(async move {
tokio::time::timeout(LISTEN_TIMEOUT, listener.listen())
.await
.expect("listen should not timeout")
}));
}
// Some listeners will poll after close
let poll_listeners: Vec<_> = (0..5).map(|_| announcement.listener()).collect();
tokio::time::sleep(Duration::from_millis(10)).await;
// Close announcer
announcer.close();
// Verify waiting listeners get Closed
for handle in async_handles {
assert_eq!(handle.await.unwrap(), Err(ListenError::Closed));
}
// Verify polling listeners also get Closed
for listener in poll_listeners {
assert_eq!(
tokio::time::timeout(LISTEN_TIMEOUT, listener.listen())
.await
.expect("listen should not timeout"),
Err(ListenError::Closed)
);
}
}
// =============================================================================
// Blocking Mode Closed Detection Tests
// =============================================================================
/// Test: Close test: listen blocking returns closed
///
/// Verifies: Verifies listen blocking returns closed behavior
/// Category: close
#[cfg(feature = "std")]
#[test]
fn test_listen_blocking_returns_closed() {
use std::thread;
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
let handle = thread::spawn(move || listener.listen_blocking());
thread::sleep(Duration::from_millis(10));
drop(announcer);
assert_eq!(handle.join().unwrap(), Err(ListenError::Closed));
}
/// Test: Close test: listen timeout blocking returns closed not timeout
///
/// Verifies: Verifies listen timeout blocking returns closed not timeout behavior
/// Category: close
#[cfg(feature = "std")]
#[test]
fn test_listen_timeout_blocking_returns_closed_not_timeout() {
use std::thread;
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
let handle = thread::spawn(move || listener.listen_timeout_blocking(Duration::from_secs(10)));
thread::sleep(Duration::from_millis(10));
drop(announcer);
// Should return Closed, not Timeout (even though timeout is 10 seconds)
assert_eq!(handle.join().unwrap(), Err(ListenError::Closed));
}
/// Test: Close test: multiple blocking threads notified
///
/// Verifies: Verifies multiple blocking threads notified behavior
/// Category: close
#[cfg(feature = "std")]
#[test]
fn test_multiple_blocking_threads_notified() {
use std::thread;
let (announcer, announcement) = Announcement::<i32>::new();
let mut handles = vec![];
for _ in 0..5 {
let listener = announcement.listener();
handles.push(thread::spawn(move || listener.listen_blocking()));
}
thread::sleep(Duration::from_millis(10));
drop(announcer);
for handle in handles {
assert_eq!(handle.join().unwrap(), Err(ListenError::Closed));
}
}
// =============================================================================
// Edge Cases and Ordering Tests
// =============================================================================
/// Test: Close test: listener created after close
///
/// Verifies: Verifies listener created after close behavior
/// Category: close
#[tokio::test]
async fn test_listener_created_after_close() {
let (announcer, announcement) = Announcement::<i32>::new();
// Close first
announcer.close();
// Create listener after close
let listener = announcement.listener();
// Should immediately return Closed (fast path)
assert_eq!(
tokio::time::timeout(LISTEN_TIMEOUT, listener.listen())
.await
.expect("listen should not timeout"),
Err(ListenError::Closed)
);
}
/// Test: Close test: announce takes precedence over close
///
/// Verifies: Verifies announce takes precedence over close behavior
/// Category: close
#[tokio::test]
async fn test_announce_takes_precedence_over_close() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// Announce a value
announcer.announce(42).unwrap();
// Listener should get the value, not Closed
assert_eq!(
tokio::time::timeout(LISTEN_TIMEOUT, listener.listen())
.await
.expect("listen should not timeout"),
Ok(42)
);
// is_closed should be false because announcement succeeded
assert!(!announcement.is_closed());
}
/// Test: Close test: drop immediately after creation
///
/// Verifies: Verifies drop immediately after creation behavior
/// Category: close
#[test]
fn test_drop_immediately_after_creation() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// Drop immediately
drop(announcer);
// Should be closed
assert!(announcement.is_closed());
assert!(listener.is_closed());
}
/// Test: Close test: try listen with closed announcer
///
/// Verifies: Verifies try listen with closed announcer behavior
/// Category: close
#[tokio::test]
async fn test_try_listen_with_closed_announcer() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// Drop announcer
drop(announcer);
// try_listen returns None (doesn't distinguish between not-announced and closed)
assert_eq!(listener.try_listen(), None);
// But can check is_closed to distinguish
assert!(listener.is_closed());
}
/// Test: Close test: cloned announcement sees closed state
///
/// Verifies: Verifies cloned announcement sees closed state behavior
/// Category: close
#[tokio::test]
async fn test_cloned_announcement_sees_closed_state() {
let (announcer, announcement) = Announcement::<i32>::new();
let announcement2 = announcement.clone();
let l1 = announcement.listener();
let l2 = announcement2.listener();
drop(announcer);
// Both announcements and their listeners see closed state
assert!(announcement.is_closed());
assert!(announcement2.is_closed());
assert_eq!(
tokio::time::timeout(LISTEN_TIMEOUT, l1.listen())
.await
.expect("listen should not timeout"),
Err(ListenError::Closed)
);
assert_eq!(
tokio::time::timeout(LISTEN_TIMEOUT, l2.listen())
.await
.expect("listen should not timeout"),
Err(ListenError::Closed)
);
}
// =============================================================================
// Multiple Runtime Tests for Closed Detection
// =============================================================================
/// Test: Close test: tokio closed detection
///
/// Verifies: Verifies tokio closed detection behavior
/// Category: close
#[tokio::test]
async fn test_tokio_closed_detection() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
drop(announcer);
assert_eq!(
tokio::time::timeout(LISTEN_TIMEOUT, listener.listen())
.await
.expect("listen should not timeout"),
Err(ListenError::Closed)
);
}
#[test]
#[cfg_attr(
miri,
ignore = "Miri doesn't support timerfd_create syscall used by smol"
)]
/// Test: Close test: smol closed detection
///
/// Verifies: Verifies smol closed detection behavior
/// Category: close
fn test_smol_closed_detection() {
smol::block_on(async {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
drop(announcer);
assert_eq!(
smol::future::or(
async {
smol::Timer::after(LISTEN_TIMEOUT).await;
panic!("listen should not timeout")
},
listener.listen()
)
.await,
Err(ListenError::Closed)
);
});
}
// =============================================================================
// Stress Tests for Closed Detection
// =============================================================================
/// Test: Close test: many listeners closed
///
/// Verifies: Verifies many listeners closed behavior
/// Category: close
#[tokio::test]
async fn test_many_listeners_closed() {
let (announcer, announcement) = Announcement::<i32>::new();
let mut handles = vec![];
for _ in 0..100 {
let listener = announcement.listener();
handles.push(tokio::spawn(async move {
tokio::time::timeout(LISTEN_TIMEOUT, listener.listen())
.await
.expect("listen should not timeout")
}));
}
tokio::time::sleep(Duration::from_millis(10)).await;
drop(announcer);
for handle in handles {
assert_eq!(handle.await.unwrap(), Err(ListenError::Closed));
}
}
/// Test: Close test: repeated close scenarios
///
/// Verifies: Verifies repeated close scenarios behavior
/// Category: close
#[tokio::test]
async fn test_repeated_close_scenarios() {
// Run the close scenario many times to catch any race conditions
for _ in 0..1000 {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
let handle = tokio::spawn(async move {
tokio::time::timeout(LISTEN_TIMEOUT, listener.listen())
.await
.expect("listen should not timeout")
});
drop(announcer);
assert_eq!(handle.await.unwrap(), Err(ListenError::Closed));
}
}
// =============================================================================
// Integration with Other Features
// =============================================================================
/// Test: Close test: closed with arc value type
///
/// Verifies: Verifies closed with arc value type behavior
/// Category: close
#[tokio::test]
async fn test_closed_with_arc_value_type() {
let (announcer, announcement) = Announcement::<Arc<String>>::new();
let listener = announcement.listener();
drop(announcer);
assert_eq!(
tokio::time::timeout(LISTEN_TIMEOUT, listener.listen())
.await
.expect("listen should not timeout"),
Err(ListenError::Closed)
);
}
/// Test: Close test: is announced vs is closed
///
/// Verifies: Verifies is announced vs is closed behavior
/// Category: close
#[tokio::test]
async fn test_is_announced_vs_is_closed() {
let (announcer, announcement) = Announcement::<i32>::new();
// Initially: not announced, not closed
assert!(!announcement.is_announced());
assert!(!announcement.is_closed());
// After drop: not announced, is closed
drop(announcer);
assert!(!announcement.is_announced());
assert!(announcement.is_closed());
}
/// Test: Close test: announced then never closed
///
/// Verifies: Verifies announced then never closed behavior
/// Category: close
#[tokio::test]
async fn test_announced_then_never_closed() {
let (announcer, announcement) = Announcement::<i32>::new();
announcer.announce(42).unwrap();
// After announce: is announced, not closed
assert!(announcement.is_announced());
assert!(!announcement.is_closed());
}
// =============================================================================
// Error Handling Tests
// =============================================================================
/// Test: Close test: closed error display
///
/// Verifies: Verifies closed error display behavior
/// Category: close
#[tokio::test]
async fn test_closed_error_display() {
let err = ListenError::Closed;
assert_eq!(err.to_string(), "announcer was dropped without announcing");
}
/// Test: Close test: listen multiple times after closed
///
/// Verifies: Verifies listen multiple times after closed behavior
/// Category: close
#[tokio::test]
async fn test_listen_multiple_times_after_closed() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
drop(announcer);
// Multiple listen calls should all return Closed
assert_eq!(
tokio::time::timeout(LISTEN_TIMEOUT, listener.listen())
.await
.expect("listen should not timeout"),
Err(ListenError::Closed)
);
assert_eq!(
tokio::time::timeout(LISTEN_TIMEOUT, listener.listen())
.await
.expect("listen should not timeout"),
Err(ListenError::Closed)
);
assert_eq!(
tokio::time::timeout(LISTEN_TIMEOUT, listener.listen())
.await
.expect("listen should not timeout"),
Err(ListenError::Closed)
);
}
File diff suppressed because it is too large. Load diff
+317
View File
@@ -0,0 +1,317 @@
//! 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
);
}
}
}
+763
View File
@@ -0,0 +1,763 @@
//! Deadlock prevention tests for the announcement crate.
//! Verifies that the crate cannot deadlock in any scenario.
use announcement::Announcement;
#[cfg(feature = "std")]
use announcement::ListenError;
use std::sync::{Arc, Barrier, Mutex};
use std::thread;
use std::time::{Duration, Instant};
#[cfg(feature = "std")]
/// Test: Deadlock prevention: listen timeout actually times out
///
/// Verifies: Verifies listen timeout actually times out does not cause deadlock
/// Category: deadlock
#[test]
fn test_listen_timeout_actually_times_out() {
let (_announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// Don't announce, just let it timeout
let start = Instant::now();
let result = listener.listen_timeout_blocking(Duration::from_millis(100));
let elapsed = start.elapsed();
assert_eq!(result, Err(ListenError::Timeout));
assert!(elapsed >= Duration::from_millis(100));
// Increased tolerance for coverage instrumentation overhead (tarpaulin adds significant delay)
assert!(elapsed < Duration::from_secs(1)); // Very generous tolerance
}
#[cfg(feature = "std")]
/// Test: Deadlock prevention: announcer dropped listeners detect closed
///
/// Verifies: Verifies announcer dropped listeners detect closed does not cause deadlock
/// Category: deadlock
#[test]
fn test_announcer_dropped_listeners_detect_closed() {
let announcement = {
let (announcer, announcement) = Announcement::<String>::new();
drop(announcer); // Drop announcer immediately - sets closed flag
announcement
};
let listener = announcement.listener();
// With closed detection, this returns Closed immediately, not Timeout
let result = listener.listen_timeout_blocking(Duration::from_millis(100));
assert_eq!(result, Err(ListenError::Closed));
assert!(announcement.is_closed());
}
/// Test: Deadlock prevention: no deadlock nested announcements
///
/// Verifies: Verifies no deadlock nested announcements cannot cause deadlock
/// Category: deadlock
#[test]
fn test_no_deadlock_nested_announcements() {
// A -> B -> C chain
let (ann_a, announcement_a) = Announcement::<i32>::new();
let (ann_b, announcement_b) = Announcement::<i32>::new();
let (ann_c, announcement_c) = Announcement::<i32>::new();
let listener_a = announcement_a.listener();
let listener_b = announcement_b.listener();
let listener_c = announcement_c.listener();
// Thread 1: A -> B
let h1 = thread::spawn(move || {
ann_a.announce(1).unwrap();
loop {
if let Some(val) = listener_b.try_listen() {
return val;
}
thread::yield_now();
}
});
// Thread 2: B -> C
let h2 = thread::spawn(move || {
ann_b.announce(2).unwrap();
loop {
if let Some(val) = listener_c.try_listen() {
return val;
}
thread::yield_now();
}
});
// Thread 3: C -> complete
let h3 = thread::spawn(move || {
ann_c.announce(3).unwrap();
loop {
if let Some(val) = listener_a.try_listen() {
return val;
}
thread::yield_now();
}
});
// All should complete without deadlock
assert_eq!(h1.join().unwrap(), 2);
assert_eq!(h2.join().unwrap(), 3);
assert_eq!(h3.join().unwrap(), 1);
}
/// Test: Deadlock prevention: no deadlock circular deps
///
/// Verifies: Verifies no deadlock circular deps cannot cause deadlock
/// Category: deadlock
#[test]
fn test_no_deadlock_circular_deps() {
let (ann1, announcement1) = Announcement::<&str>::new();
let (ann2, announcement2) = Announcement::<&str>::new();
let listener1_for_2 = announcement1.listener();
let listener2_for_1 = announcement2.listener();
// These would deadlock with improper locking
let h1 = thread::spawn(move || {
ann1.announce("from1").unwrap();
let start = Instant::now();
loop {
if let Some(val) = listener2_for_1.try_listen() {
return val;
}
if start.elapsed() > Duration::from_secs(1) {
return "timeout1";
}
thread::yield_now();
}
});
let h2 = thread::spawn(move || {
ann2.announce("from2").unwrap();
let start = Instant::now();
loop {
if let Some(val) = listener1_for_2.try_listen() {
return val;
}
if start.elapsed() > Duration::from_secs(1) {
return "timeout2";
}
thread::yield_now();
}
});
let r1 = h1.join().unwrap();
let r2 = h2.join().unwrap();
assert_eq!(r1, "from2");
assert_eq!(r2, "from1");
}
/// Test: Deadlock prevention: no deadlock multiple mutexes
///
/// Verifies: Verifies no deadlock multiple mutexes cannot cause deadlock
/// Category: deadlock
#[test]
fn test_no_deadlock_multiple_mutexes() {
let (announcer, announcement) = Announcement::new();
let mutex1 = Arc::new(Mutex::new(0));
let mutex2 = Arc::new(Mutex::new(0));
let m1_clone = mutex1.clone();
let m2_clone = mutex2.clone();
let listener1 = announcement.listener();
let h1 = thread::spawn(move || {
let _lock1 = m1_clone.lock().unwrap();
thread::sleep(Duration::from_millis(10));
let _lock2 = m2_clone.lock().unwrap();
loop {
if let Some(val) = listener1.try_listen() {
return val;
}
thread::yield_now();
}
});
let m1_clone2 = mutex1.clone();
let m2_clone2 = mutex2.clone();
let listener2 = announcement.listener();
let h2 = thread::spawn(move || {
let _lock2 = m2_clone2.lock().unwrap();
thread::sleep(Duration::from_millis(10));
let _lock1 = m1_clone2.lock().unwrap();
loop {
if let Some(val) = listener2.try_listen() {
return val;
}
thread::yield_now();
}
});
// This might deadlock with the mutexes, but announcement should still work
thread::sleep(Duration::from_millis(50));
announcer.announce(42).unwrap();
// Set a timeout for the test
let start = Instant::now();
while start.elapsed() < Duration::from_secs(2) {
if h1.is_finished() && h2.is_finished() {
break;
}
thread::sleep(Duration::from_millis(10));
}
// The mutex operations might deadlock, but announcement itself doesn't
// This test shows announcement doesn't contribute to deadlocks
}
#[cfg(feature = "std")]
/// Test: Deadlock prevention: no deadlock blocking ops
///
/// Verifies: Verifies no deadlock blocking ops cannot cause deadlock
/// Category: deadlock
#[test]
fn test_no_deadlock_blocking_ops() {
let (announcer, announcement) = Announcement::new();
let mut handles = vec![];
// Multiple blocking listeners
for _ in 0..10 {
let listener = announcement.listener();
handles.push(thread::spawn(move || {
listener.listen_timeout_blocking(Duration::from_millis(500))
}));
}
// Let them block for a bit
thread::sleep(Duration::from_millis(100));
// Announce
announcer.announce(42).unwrap();
// All should complete
for handle in handles {
let result = handle.join().unwrap();
assert_eq!(result, Ok(42));
}
}
/// Test: Deadlock prevention: no deadlock with panic
///
/// Verifies: Verifies no deadlock with panic cannot cause deadlock
/// Category: deadlock
#[test]
fn test_no_deadlock_with_panic() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// Thread that panics
let h = thread::spawn(move || {
let _l = announcement.listener();
panic!("Intentional panic");
});
// Wait for panic
let _ = h.join();
// Should still work
announcer.announce(42).unwrap();
assert_eq!(listener.try_listen(), Some(42));
}
/// Test: Deadlock prevention: no deadlock poisoned mutex value
///
/// Verifies: Verifies no deadlock poisoned mutex value cannot cause deadlock
/// Category: deadlock
#[test]
fn test_no_deadlock_poisoned_mutex_value() {
let (announcer, announcement) = Announcement::new();
let mutex = Arc::new(Mutex::new(42));
let mutex_clone = mutex.clone();
// Poison the mutex
let _ = thread::spawn(move || {
let _guard = mutex_clone.lock().unwrap();
panic!("Poison the mutex");
})
.join();
// Announce the poisoned mutex
announcer.announce(mutex.clone()).unwrap();
let listener = announcement.listener();
let received = listener.try_listen().unwrap();
// Can still receive, even if mutex is poisoned
assert!(received.lock().is_err()); // It's poisoned
}
/// Test: Deadlock prevention: no deadlock recursive listeners
///
/// Verifies: Verifies no deadlock recursive listeners cannot cause deadlock
/// Category: deadlock
#[test]
fn test_no_deadlock_recursive_listeners() {
fn create_recursive_listeners(announcement: &Announcement<i32>, depth: usize) {
if depth == 0 {
return;
}
let _listener = announcement.listener();
create_recursive_listeners(announcement, depth - 1);
}
let (announcer, announcement) = Announcement::new();
// Create deeply nested listeners
create_recursive_listeners(&announcement, 1000);
// Should still work
announcer.announce(42).unwrap();
let final_listener = announcement.listener();
assert_eq!(final_listener.try_listen(), Some(42));
}
/// Test: Deadlock prevention: no deadlock barrier sync
///
/// Verifies: Verifies no deadlock barrier sync cannot cause deadlock
/// Category: deadlock
#[test]
fn test_no_deadlock_barrier_sync() {
let (announcer, announcement) = Announcement::new();
let barrier = Arc::new(Barrier::new(11));
let mut handles = vec![];
for _ in 0..10 {
let listener = announcement.listener();
let barrier = barrier.clone();
handles.push(thread::spawn(move || {
// Wait at barrier
barrier.wait();
// Try to receive
let start = Instant::now();
loop {
if let Some(val) = listener.try_listen() {
return val;
}
if start.elapsed() > Duration::from_secs(1) {
panic!("Timeout waiting for value");
}
thread::yield_now();
}
}));
}
// Main thread also waits
barrier.wait();
// Then announces
announcer.announce(42).unwrap();
// All should complete
for handle in handles {
assert_eq!(handle.join().unwrap(), 42);
}
}
/// Test: Deadlock prevention: no deadlock channel chains
///
/// Verifies: Verifies no deadlock channel chains cannot cause deadlock
/// Category: deadlock
#[test]
fn test_no_deadlock_channel_chains() {
// Create channels separately to avoid ownership issues
let mut announcers = Vec::new();
let mut announcements = Vec::new();
for _ in 0..10 {
let (announcer, announcement) = Announcement::<i32>::new();
announcers.push(Some(announcer));
announcements.push(announcement);
}
// Chain announcements
let mut handles = vec![];
for i in 0..9 {
let listener = announcements[i].listener();
let next_announcer = announcers[i + 1].take().unwrap();
handles.push(thread::spawn(move || loop {
if let Some(val) = listener.try_listen() {
next_announcer.announce(val + 1).unwrap();
break;
}
thread::yield_now();
}));
}
// Start the chain
announcers[0].take().unwrap().announce(1).unwrap();
// Wait for chain to complete
thread::sleep(Duration::from_millis(100));
// Check final value
let final_listener = announcements[9].listener();
let start = Instant::now();
loop {
if let Some(val) = final_listener.try_listen() {
assert_eq!(val, 10); // 1 + 9 increments
break;
}
if start.elapsed() > Duration::from_secs(1) {
panic!("Chain didn't complete");
}
thread::yield_now();
}
for handle in handles {
handle.join().unwrap();
}
}
#[cfg(feature = "std")]
/// Test: Deadlock prevention: no deadlock simultaneous timeouts
///
/// Verifies: Verifies no deadlock simultaneous timeouts cannot cause deadlock
/// Category: deadlock
#[test]
fn test_no_deadlock_simultaneous_timeouts() {
let (_announcer, announcement) = Announcement::<i32>::new();
let mut handles = vec![];
// Many threads timing out simultaneously
for _ in 0..50 {
let listener = announcement.listener();
handles.push(thread::spawn(move || {
listener.listen_timeout_blocking(Duration::from_millis(100))
}));
}
// All should timeout without deadlock
for handle in handles {
let result = handle.join().unwrap();
assert_eq!(result, Err(ListenError::Timeout));
}
}
#[cfg(feature = "std")]
/// Test: Deadlock prevention: no deadlock mixed blocking
///
/// Verifies: Verifies no deadlock mixed blocking cannot cause deadlock
/// Category: deadlock
#[test]
fn test_no_deadlock_mixed_blocking() {
let (announcer, announcement) = Announcement::new();
let mut handles = vec![];
// Blocking listeners
for _ in 0..5 {
let listener = announcement.listener();
handles.push(thread::spawn(move || {
listener.listen_timeout_blocking(Duration::from_secs(1))
}));
}
// Non-blocking listeners
for _ in 0..5 {
let listener = announcement.listener();
handles.push(thread::spawn(move || {
let start = Instant::now();
loop {
if let Some(val) = listener.try_listen() {
return Ok(val);
}
if start.elapsed() > Duration::from_secs(1) {
return Err(ListenError::Timeout);
}
thread::yield_now();
}
}));
}
thread::sleep(Duration::from_millis(100));
announcer.announce(42).unwrap();
// All should complete
for handle in handles {
let result = handle.join().unwrap();
assert_eq!(result, Ok(42));
}
}
/// Test: Deadlock prevention: no deadlock rapid churn
///
/// Verifies: Verifies no deadlock rapid churn cannot cause deadlock
/// Category: deadlock
#[test]
fn test_no_deadlock_rapid_churn() {
let duration = Duration::from_secs(2);
let start = Instant::now();
while start.elapsed() < duration {
let (announcer, announcement) = Announcement::<i32>::new();
let mut handles = vec![];
// Rapid creation
for _ in 0..10 {
let listener = announcement.listener();
handles.push(thread::spawn(move || listener.try_listen()));
}
// Quick announce
announcer.announce(42).unwrap();
// Rapid destruction
for handle in handles {
handle.join().unwrap();
}
}
}
#[cfg(feature = "std")]
/// Test: Deadlock prevention: no deadlock error propagation
///
/// Verifies: Verifies no deadlock error propagation cannot cause deadlock
/// Category: deadlock
#[test]
fn test_no_deadlock_error_propagation() {
let (_announcer, announcement) = Announcement::<Result<i32, String>>::new();
let mut handles = vec![];
for i in 0..10 {
let listener = announcement.listener();
handles.push(thread::spawn(move || {
match listener.listen_timeout_blocking(Duration::from_millis(100)) {
Ok(_val) => format!("Thread {} got value", i),
Err(ListenError::Timeout) => format!("Thread {} timed out", i),
Err(ListenError::Closed) => format!("Thread {} got closed", i),
}
}));
}
// All timeout, but no deadlock
for handle in handles {
let result = handle.join().unwrap();
assert!(result.contains("timed out"));
}
}
/// Test: Deadlock prevention: no deadlock signal pattern
///
/// Verifies: Verifies no deadlock signal pattern cannot cause deadlock
/// Category: deadlock
#[test]
fn test_no_deadlock_signal_pattern() {
let (shutdown_ann, shutdown) = Announcement::<()>::new();
let (ready_ann, ready) = Announcement::<()>::new();
let shutdown_listener = shutdown.listener();
let ready_listener = ready.listener();
// Worker thread
let worker = thread::spawn(move || {
// Signal ready
ready_ann.announce(()).unwrap();
// Work until shutdown
let start = Instant::now();
loop {
if shutdown_listener.try_listen().is_some() {
return "Shutdown received";
}
if start.elapsed() > Duration::from_secs(2) {
return "Timeout";
}
thread::yield_now();
}
});
// Wait for ready
let start = Instant::now();
loop {
if ready_listener.try_listen().is_some() {
break;
}
if start.elapsed() > Duration::from_secs(1) {
panic!("Ready signal timeout");
}
thread::yield_now();
}
// Send shutdown
shutdown_ann.announce(()).unwrap();
let result = worker.join().unwrap();
assert_eq!(result, "Shutdown received");
}
/// Test: Deadlock prevention: no deadlock cross thread deps
///
/// Verifies: Verifies no deadlock cross thread deps cannot cause deadlock
/// Category: deadlock
#[test]
fn test_no_deadlock_cross_thread_deps() {
let (ann1, announcement1) = Announcement::new();
let (ann2, announcement2) = Announcement::new();
let (ann3, announcement3) = Announcement::new();
let l1 = announcement1.listener();
let l2 = announcement2.listener();
let l3 = announcement3.listener();
// Complex dependency graph
let h1 = thread::spawn(move || {
ann1.announce(1).unwrap();
// Wait for 3
loop {
if let Some(val) = l3.try_listen() {
return val;
}
thread::yield_now();
}
});
let h2 = thread::spawn(move || {
// Wait for 1
loop {
if l1.try_listen().is_some() {
break;
}
thread::yield_now();
}
ann2.announce(2).unwrap();
2
});
let h3 = thread::spawn(move || {
// Wait for 2
loop {
if l2.try_listen().is_some() {
break;
}
thread::yield_now();
}
ann3.announce(3).unwrap();
3
});
// All should complete without deadlock
assert_eq!(h1.join().unwrap(), 3);
assert_eq!(h2.join().unwrap(), 2);
assert_eq!(h3.join().unwrap(), 3);
}
#[cfg(feature = "std")]
/// Test: Deadlock prevention: no deadlock timeout cascade
///
/// Verifies: Verifies no deadlock timeout cascade cannot cause deadlock
/// Category: deadlock
#[test]
fn test_no_deadlock_timeout_cascade() {
// Create chains separately
let mut announcers = Vec::new();
let mut announcements = Vec::new();
for _ in 0..5 {
let (announcer, announcement) = Announcement::<i32>::new();
announcers.push(Some(announcer));
announcements.push(announcement);
}
let mut handles = vec![];
for i in 0..4 {
let listener = announcements[i].listener();
let announcer = announcers[i + 1].take().unwrap();
handles.push(thread::spawn(move || {
match listener.listen_timeout_blocking(Duration::from_millis(200)) {
Ok(val) => {
announcer.announce(val + 1).ok();
Ok(val)
}
Err(e) => Err(e),
}
}));
}
// Don't announce to first, causing cascade
// First announcer dropped at end of function -> Closed
// Cascade means others either timeout or get closed
for handle in handles {
let result = handle.join().unwrap();
// Accept either Timeout or Closed - both are valid depending on timing
assert!(
result == Err(ListenError::Timeout) || result == Err(ListenError::Closed),
"Expected Timeout or Closed, got {:?}",
result
);
}
}
/// Test: Deadlock prevention: no deadlock summary
///
/// Verifies: Verifies no deadlock summary cannot cause deadlock
/// Category: deadlock
#[test]
fn test_no_deadlock_summary() {
// This test verifies that common deadlock scenarios are prevented
// 1. No internal locks that could deadlock
let (ann, announcement) = Announcement::<i32>::new();
let l1 = announcement.listener();
let l2 = announcement.listener();
// Concurrent operations don't deadlock
let h1 = thread::spawn(move || l1.try_listen());
let h2 = thread::spawn(move || l2.try_listen());
ann.announce(42).unwrap();
h1.join().unwrap();
h2.join().unwrap();
// 2. No blocking infinite waits (with timeout)
#[cfg(feature = "std")]
{
let (_ann, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
let result = listener.listen_timeout_blocking(Duration::from_millis(10));
assert_eq!(result, Err(ListenError::Timeout));
}
// 3. Clean panic handling
let (ann, announcement) = Announcement::<i32>::new();
let _listener = announcement.listener();
let _ = std::panic::catch_unwind(|| {
panic!("Test panic");
});
// Still works after panic
ann.announce(42).unwrap();
println!("All deadlock prevention tests passed!");
}
+355
View File
@@ -0,0 +1,355 @@
//! Targeted tests for Drop implementation mutations
//!
//! These tests specifically target the Drop impl for Announcer<T> to catch
//! mutations that would otherwise cause timeouts. They use short timeouts
//! to fail fast instead of hanging.
use announcement::{Announcement, ListenError};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
/// Test: Drop sets closed flag - verify with short timeout
///
/// Verifies: Drop sets closed flag - with short timeout
/// Category: drop
#[test]
fn test_drop_sets_closed_flag_quick() {
let (announcer, announcement) = Announcement::<i32>::new();
// Verify not closed initially
assert!(!announcement.is_closed());
// Drop the announcer
drop(announcer);
// CRITICAL: Must set closed flag
// This catches the mutant that replaces Drop body with ()
assert!(announcement.is_closed(), "Drop must set closed flag");
}
/// Test: Drop test: drop wakes blocking listeners quick
///
/// Verifies: drop wakes blocking listeners quick
/// Category: drop
#[cfg(feature = "std")]
#[test]
fn test_drop_wakes_blocking_listeners_quick() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// Spawn thread that will wait
let handle =
thread::spawn(move || listener.listen_timeout_blocking(Duration::from_millis(100)));
// Give thread time to start waiting
thread::sleep(Duration::from_millis(10));
// Drop announcer - should wake listener
drop(announcer);
// Listener should receive Closed error, not Timeout
// If Drop doesn't work, this will timeout
let result = handle.join().expect("thread should not panic");
assert_eq!(
result,
Err(ListenError::Closed),
"Drop must notify waiting listeners with Closed error"
);
}
/// Test: Drop with conditional check - verifies the condition logic
///
/// Verifies: Drop with conditional check - verifies the condition logic
/// Category: drop
#[test]
fn test_drop_conditional_already_announced() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// Announce a value
announcer.announce(42).unwrap();
// Drop happens here, but should NOT set closed flag
// because value was already announced
// Verify: NOT closed (because value was announced)
assert!(
!announcement.is_closed(),
"Drop should not set closed flag if value was announced"
);
// Verify: Can still get the value
assert_eq!(listener.try_listen(), Some(42));
}
/// Test: Drop with conditional check - verifies the !closed check
///
/// Verifies: Drop with conditional check - verifies the !closed check
/// Category: drop
#[test]
fn test_drop_conditional_already_closed() {
let (announcer, announcement) = Announcement::<i32>::new();
// Explicitly close first
announcer.close();
// At this point, closed flag is already set
assert!(announcement.is_closed());
// Drop happens here automatically
// The condition should prevent double-notification
// Still closed after drop
assert!(announcement.is_closed());
}
/// Test: Multiple drops with concurrent listeners
///
/// Verifies: Multiple drops with concurrent listeners
/// Category: drop
#[tokio::test]
async fn test_drop_wakes_multiple_async_listeners() {
let (announcer, announcement) = Announcement::<i32>::new();
// Create multiple listeners
let listeners: Vec<_> = (0..5).map(|_| announcement.listener()).collect();
// Spawn tasks for all listeners with short timeout
let mut handles = Vec::new();
for listener in listeners {
handles.push(tokio::spawn(async move {
tokio::time::timeout(Duration::from_millis(100), listener.listen()).await
}));
}
// Give listeners time to start waiting
tokio::time::sleep(Duration::from_millis(10)).await;
// Drop announcer - should wake all listeners
drop(announcer);
// All listeners should receive Closed error quickly, not timeout
for handle in handles {
let result = handle.await.expect("task should not panic");
assert!(
result.is_ok(),
"listener should receive notification, not timeout"
);
assert_eq!(
result.unwrap(),
Err(ListenError::Closed),
"Drop must notify all waiting listeners"
);
}
}
/// Test: Drop notifies then listeners can check is_closed
///
/// Verifies: Drop notifies then listeners can check is_closed
/// Category: drop
#[test]
fn test_drop_notification_visible_to_new_listeners() {
let (announcer, announcement) = Announcement::<i32>::new();
// Drop without announcing
drop(announcer);
// Create listener AFTER drop
let listener = announcement.listener();
// Should immediately see closed status
assert!(listener.is_closed());
assert_eq!(listener.try_listen(), None);
}
/// Test: Verify Drop impl checks value.is_none() correctly
///
/// Verifies: Drop impl checks value.is_none() correctly
/// Category: drop
#[test]
fn test_drop_checks_value_is_none_correctly() {
// Test 1: value is None, should close
{
let (announcer, announcement) = Announcement::<i32>::new();
drop(announcer);
assert!(announcement.is_closed(), "Should close when value is None");
}
// Test 2: value is Some, should NOT close
{
let (_announcer, announcement) = Announcement::<i32>::new();
_announcer.announce(42).unwrap();
assert!(
!announcement.is_closed(),
"Should NOT close when value is Some"
);
}
}
/// Test: Verify Drop impl checks !closed correctly
///
/// Verifies: Drop impl checks !closed correctly
/// Category: drop
#[test]
fn test_drop_checks_not_closed_correctly() {
let (announcer, announcement) = Announcement::<i32>::new();
// First close explicitly
announcer.close();
// closed flag should be set
assert!(announcement.is_closed());
// Drop happens automatically here
// The !closed check should prevent re-notification
// Verify still closed (this is a sanity check)
assert!(announcement.is_closed());
}
/// Test: Stress test - many rapid drops
///
/// Verifies: Stress test - many rapid drops
/// Category: drop
#[test]
fn test_rapid_drops_stress() {
for _ in 0..1000 {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
drop(announcer);
assert!(announcement.is_closed());
assert!(listener.is_closed());
assert_eq!(listener.try_listen(), None);
}
}
/// Test: Drop with Arc value type
///
/// Verifies: Drop with Arc value type
/// Category: drop
#[test]
fn test_drop_with_arc_value() {
let (announcer, announcement) = Announcement::<Arc<String>>::new();
let listener = announcement.listener();
drop(announcer);
assert!(announcement.is_closed());
assert!(listener.is_closed());
}
/// Test: Drop test: drop ordering set closed before notify
///
/// Verifies: drop ordering set closed before notify
/// Category: drop
#[cfg(feature = "std")]
#[test]
fn test_drop_ordering_set_closed_before_notify() {
// This test verifies that closed flag is set before notification
// If the order is wrong, listeners might check closed flag before it's set
let (announcer, announcement) = Announcement::<i32>::new();
let ann_clone = announcement.clone();
let handle = thread::spawn(move || {
let listener = ann_clone.listener();
// Wait with very short timeout
let result = listener.listen_timeout_blocking(Duration::from_millis(50));
// If we get Closed, verify the flag is set
if result == Err(ListenError::Closed) {
assert!(
ann_clone.is_closed(),
"closed flag must be visible when Closed error is returned"
);
}
result
});
thread::sleep(Duration::from_millis(10));
drop(announcer);
let result = handle.join().unwrap();
assert_eq!(result, Err(ListenError::Closed));
}
/// Test: Drop in nested scope
///
/// Verifies: Drop in nested scope
/// Category: drop
#[test]
fn test_drop_in_nested_scope() {
let announcement = {
let (_announcer, announcement) = Announcement::<i32>::new();
// _announcer dropped here at end of scope
announcement
};
// Outside the scope, closed flag should be set
assert!(announcement.is_closed());
}
/// Test: Drop vs explicit close comparison
///
/// Verifies: Drop vs explicit close comparison
/// Category: drop
#[test]
fn test_drop_equivalent_to_explicit_close() {
// Test 1: explicit close
let (announcer1, announcement1) = Announcement::<i32>::new();
let listener1 = announcement1.listener();
announcer1.close();
// Test 2: drop
let (announcer2, announcement2) = Announcement::<i32>::new();
let listener2 = announcement2.listener();
drop(announcer2);
// Both should have same result
assert_eq!(announcement1.is_closed(), announcement2.is_closed());
assert_eq!(listener1.try_listen(), listener2.try_listen());
assert_eq!(listener1.is_closed(), listener2.is_closed());
}
/// Test: Drop with panic in another thread
///
/// Verifies: Drop with panic in another thread
/// Category: drop
#[test]
fn test_drop_after_panic() {
let (announcer, announcement) = Announcement::<i32>::new();
let handle = thread::spawn(move || {
let _announcer = announcer;
panic!("intentional panic");
});
let _ = handle.join();
// Drop happened despite panic
assert!(announcement.is_closed());
}
/// Test: Verify closed flag atomic ordering
///
/// Verifies: closed flag atomic ordering
/// Category: drop
#[test]
fn test_drop_closed_flag_atomic_ordering() {
// This is a documentation test - verifying our understanding
// The actual ordering is in the implementation
let (announcer, announcement) = Announcement::<i32>::new();
// Before drop, not closed
assert!(!announcement.is_closed());
// Drop sets closed flag with Release ordering
drop(announcer);
// After drop, closed flag should be visible with Acquire ordering
assert!(announcement.is_closed());
}
+618
View File
@@ -0,0 +1,618 @@
//! Tests for dropped announcer scenarios - critical edge cases
//!
//! These tests verify graceful behavior when the announcer is dropped
//! without announcing, ensuring no deadlocks or panics occur.
use announcement::Announcement;
use announcement::ListenError;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
/// Test: Announcer dropped without announcing, listeners wait (verify timeout needed)
///
/// Verifies: Announcer dropped without announcing, listeners wait (timeout needed)
/// Category: drop
#[test]
fn test_announcer_dropped_listeners_wait() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// Drop the announcer without announcing
drop(announcer);
// try_listen should return None
assert_eq!(listener.try_listen(), None);
// is_announced should be false
assert!(!announcement.is_announced());
}
/// Test: Announcer dropped, then try_listen returns None as expected
///
/// Verifies: Announcer dropped, then try_listen returns None as expected
/// Category: drop
#[test]
fn test_announcer_dropped_try_listen_none() {
let (announcer, announcement) = Announcement::<String>::new();
// Create multiple listeners
let listener1 = announcement.listener();
let listener2 = announcement.listener();
let listener3 = announcement.listener();
// Drop announcer
drop(announcer);
// All try_listen calls should return None
assert_eq!(listener1.try_listen(), None);
assert_eq!(listener2.try_listen(), None);
assert_eq!(listener3.try_listen(), None);
// Even new listeners created after drop should get None
let listener4 = announcement.listener();
assert_eq!(listener4.try_listen(), None);
}
/// Test: Multiple listeners waiting when announcer dropped
///
/// Verifies: Multiple listeners waiting when announcer dropped
/// Category: drop
#[test]
fn test_multiple_listeners_announcer_dropped() {
let (announcer, announcement) = Announcement::<Vec<u8>>::new();
// Create many listeners
let mut listeners = Vec::new();
for _ in 0..100 {
listeners.push(announcement.listener());
}
// Drop announcer
drop(announcer);
// All listeners should get None
for listener in &listeners {
assert_eq!(listener.try_listen(), None);
}
}
/// Test: Drop test: no deadlock timeout blocking announcer dropped
///
/// Verifies: no deadlock timeout blocking announcer dropped
/// Category: drop
#[cfg(feature = "std")]
#[test]
fn test_no_deadlock_timeout_blocking_announcer_dropped() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// Drop announcer in another thread
thread::spawn(move || {
thread::sleep(Duration::from_millis(10));
drop(announcer); // Sets closed flag and notifies listeners
});
// With closed detection, this returns Closed, not Timeout
let result = listener.listen_timeout_blocking(Duration::from_millis(100));
assert_eq!(result, Err(ListenError::Closed));
assert!(announcement.is_closed());
}
/// Test: Verify graceful behavior, no panics when announcer dropped
///
/// Verifies: graceful behavior, no panics when announcer dropped
/// Category: drop
#[test]
fn test_no_panic_announcer_dropped() {
let result = std::panic::catch_unwind(|| {
let (announcer, announcement) = Announcement::<i32>::new();
let listener1 = announcement.listener();
let listener2 = announcement.listener();
drop(announcer);
// These should not panic
let _ = listener1.try_listen();
let _ = listener2.try_listen();
let _ = announcement.is_announced();
let _ = announcement.listener();
});
assert!(result.is_ok(), "Should not panic when announcer is dropped");
}
/// Test: Document behavior - announcer dropped means value never arrives
///
/// Verifies: Document behavior - announcer dropped means value never arrives
/// Category: drop
#[test]
fn test_document_dropped_announcer_behavior() {
// This test documents the expected behavior when announcer is dropped
let (announcer, announcement) = Announcement::<String>::new();
let listener = announcement.listener();
// When announcer is dropped without announcing:
drop(announcer);
// 1. try_listen returns None
assert_eq!(listener.try_listen(), None);
// 2. is_announced returns false
assert!(!announcement.is_announced());
// 3. New listeners also get None
let new_listener = announcement.listener();
assert_eq!(new_listener.try_listen(), None);
// 4. The value will NEVER arrive (no way to announce after announcer is dropped)
}
/// Test: Announcer dropped in spawned thread
///
/// Verifies: Announcer dropped in spawned thread
/// Category: drop
#[test]
fn test_announcer_dropped_in_thread() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
thread::spawn(move || {
// Announcer dropped when thread exits
let _ = announcer;
})
.join()
.unwrap();
assert_eq!(listener.try_listen(), None);
}
/// Test: Race between announce and drop
///
/// Verifies: Race between announce and drop
/// Category: drop
#[test]
fn test_announcer_dropped_arc_value() {
let (announcer, announcement) = Announcement::<Arc<Vec<String>>>::new();
let listener = announcement.listener();
// Create an Arc value that would have been announced
let _would_be_value = Arc::new(vec!["test".to_string()]);
// Drop announcer instead of announcing
drop(announcer);
assert_eq!(listener.try_listen(), None);
}
/// Test: Many announcers dropped in parallel
///
/// Verifies: Many announcers dropped in parallel
/// Category: drop
#[test]
fn test_many_announcers_dropped_parallel() {
let mut handles = vec![];
for _ in 0..100 {
handles.push(thread::spawn(|| {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// Drop announcer
drop(announcer);
// Verify listener gets None
assert_eq!(listener.try_listen(), None);
}));
}
for handle in handles {
handle.join().unwrap();
}
}
/// Test: Announcer dropped after listeners created in multiple threads
///
/// Verifies: Announcer dropped after listeners created in multiple threads
/// Category: drop
#[test]
fn test_announcer_dropped_multi_thread_listeners() {
let (announcer, announcement) = Announcement::<String>::new();
let mut handles = vec![];
// Create listeners in multiple threads
for _ in 0..10 {
let ann = announcement.clone();
handles.push(thread::spawn(move || {
let listener = ann.listener();
thread::sleep(Duration::from_millis(50));
listener.try_listen()
}));
}
// Drop announcer after a short delay
thread::sleep(Duration::from_millis(10));
drop(announcer);
// All threads should get None
for handle in handles {
let result = handle.join().unwrap();
assert_eq!(result, None);
}
}
/// Test: Memory is properly freed when announcer dropped
///
/// Verifies: Memory is properly freed when announcer dropped
/// Category: drop
#[test]
fn test_memory_freed_announcer_dropped() {
static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
#[derive(Clone)]
struct MemoryTracker {
_data: Vec<u8>,
}
impl Drop for MemoryTracker {
/// Test: Drop test: drop
///
/// Verifies: Verifies drop behavior
/// Category: drop
fn drop(&mut self) {
DROP_COUNT.fetch_add(1, Ordering::SeqCst);
}
}
DROP_COUNT.store(0, Ordering::SeqCst);
{
let (announcer, announcement) = Announcement::<MemoryTracker>::new();
let _listener = announcement.listener();
// Would have announced this, but drop instead
let _would_announce = MemoryTracker {
_data: vec![0; 1024],
};
drop(announcer);
// MemoryTracker never gets into the announcement
}
// Only the local MemoryTracker should have been dropped
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1);
}
/// Test: Drop test: listener blocking wait announcer already dropped
///
/// Verifies: listener blocking wait announcer already dropped
/// Category: drop
#[cfg(feature = "std")]
#[test]
fn test_listener_blocking_wait_announcer_already_dropped() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// Drop announcer first - sets closed flag
drop(announcer);
// With closed detection, this returns Closed immediately, not Timeout
let result = listener.listen_timeout_blocking(Duration::from_millis(100));
assert_eq!(result, Err(ListenError::Closed));
assert!(announcement.is_closed());
}
/// Test: Clone chain with dropped announcer
///
/// Verifies: Clone chain with dropped announcer
/// Category: drop
#[test]
fn test_clone_chain_dropped_announcer() {
let (announcer, announcement) = Announcement::<i32>::new();
let ann2 = announcement.clone();
let ann3 = ann2.clone();
let l1 = announcement.listener();
let l2 = ann2.listener();
let l3 = ann3.listener();
drop(announcer);
assert_eq!(l1.try_listen(), None);
assert_eq!(l2.try_listen(), None);
assert_eq!(l3.try_listen(), None);
}
/// Test: Dropped announcer with zero-sized type
///
/// Verifies: Dropped announcer with zero-sized type
/// Category: drop
#[test]
fn test_dropped_announcer_zst() {
let (announcer, announcement) = Announcement::<()>::new();
let listener = announcement.listener();
drop(announcer);
assert_eq!(listener.try_listen(), None);
}
/// Test: Dropped announcer with large type
///
/// Verifies: Dropped announcer with large type
/// Category: drop
#[test]
fn test_dropped_announcer_large_type() {
#[derive(Clone, Debug, PartialEq)]
struct LargeType {
data: [u8; 10_000],
}
let (announcer, announcement) = Announcement::<LargeType>::new();
let listener = announcement.listener();
drop(announcer);
assert_eq!(listener.try_listen(), None);
}
/// Test: is_announced remains false forever after announcer dropped
///
/// Verifies: is_announced remains false forever after announcer dropped
/// Category: drop
#[test]
fn test_is_announced_false_forever() {
let (announcer, announcement) = Announcement::<i32>::new();
drop(announcer);
// Check multiple times - should always be false
for _ in 0..1000 {
assert!(!announcement.is_announced());
}
// Even after creating new listeners
let _l1 = announcement.listener();
assert!(!announcement.is_announced());
let _l2 = announcement.listener();
assert!(!announcement.is_announced());
}
/// Test: Stress test - rapid announcer drops
///
/// Verifies: Stress test - rapid announcer drops
/// Category: drop
#[test]
fn test_stress_rapid_announcer_drops() {
for _ in 0..10_000 {
let (announcer, announcement) = Announcement::<i32>::new();
let _listener = announcement.listener();
drop(announcer);
}
}
/// Test: Announcer dropped with Option type
///
/// Verifies: Announcer dropped with Option type
/// Category: drop
#[test]
fn test_dropped_announcer_option() {
let (announcer, announcement) = Announcement::<Option<String>>::new();
let listener = announcement.listener();
drop(announcer);
assert_eq!(listener.try_listen(), None);
// Note: try_listen returns None (no value), not Some(None)
}
/// Test: Announcer dropped with Result type
///
/// Verifies: Announcer dropped with Result type
/// Category: drop
#[test]
fn test_dropped_announcer_result() {
let (announcer, announcement) = Announcement::<Result<i32, String>>::new();
let listener = announcement.listener();
drop(announcer);
assert_eq!(listener.try_listen(), None);
}
/// Test: Drop test: async listen announcer dropped
///
/// Verifies: async listen announcer dropped
/// Category: drop
#[tokio::test]
async fn test_async_listen_announcer_dropped() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// Drop announcer - this sets closed flag and notifies listeners
drop(announcer);
// With closed detection, listen() returns Closed immediately (with timeout to prevent hanging in mutation tests)
let result = tokio::time::timeout(Duration::from_millis(100), listener.listen())
.await
.expect("listen should not timeout when announcer is dropped");
assert_eq!(result, Err(ListenError::Closed));
// Verify closed state
assert!(announcement.is_closed());
}
/// Test: Verify announcer can't be used after drop (compile-time safety)
///
/// Verifies: announcer can't be used after drop (compile-time safety)
/// Category: drop
#[test]
fn test_compile_time_safety_announcer_consumed() {
let (announcer, announcement) = Announcement::<i32>::new();
let _listener = announcement.listener();
// This consumes the announcer
drop(announcer);
// The following would not compile:
// announcer.announce(42).unwrap(); // Error: announcer was moved
// This is enforced at compile time by Rust's ownership system
assert!(!announcement.is_announced());
}
/// Test: Complex scenario with conditionally dropped announcer
///
/// Verifies: Complex scenario with conditionally dropped announcer
/// Category: drop
#[test]
fn test_conditional_announcer_drop() {
fn maybe_announce(should_drop: bool) -> (Announcement<i32>, Option<i32>) {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
if should_drop {
drop(announcer);
(announcement, listener.try_listen())
} else {
announcer.announce(42).unwrap();
(announcement, listener.try_listen())
}
}
// Test both paths
let (_ann1, result1) = maybe_announce(true);
assert_eq!(result1, None);
let (_ann2, result2) = maybe_announce(false);
assert_eq!(result2, Some(42));
}
/// Test: Announcer dropped in destructor of another type
///
/// Verifies: Announcer dropped in destructor of another type
/// Category: drop
#[test]
fn test_announcer_dropped_in_destructor() {
struct AnnouncerHolder<T> {
announcer: Option<announcement::Announcer<T>>,
}
impl<T> Drop for AnnouncerHolder<T> {
fn drop(&mut self) {
// Announcer dropped here without announcing
self.announcer.take();
}
}
let (announcer, announcement) = Announcement::<String>::new();
let listener = announcement.listener();
{
let _holder = AnnouncerHolder {
announcer: Some(announcer),
};
// Holder dropped here, taking announcer with it
}
assert_eq!(listener.try_listen(), None);
}
/// Test: Thread panic while holding announcer
///
/// Verifies: Thread panic while holding announcer
/// Category: drop
#[test]
fn test_thread_panic_with_announcer() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
let handle = thread::spawn(move || {
let _announcer = announcer;
panic!("Intentional panic while holding announcer");
});
// Thread panicked, dropping announcer
assert!(handle.join().is_err());
assert_eq!(listener.try_listen(), None);
}
/// Test: Announcer dropped with recursive structure
///
/// Verifies: Announcer dropped with recursive structure
/// Category: drop
#[test]
fn test_dropped_announcer_recursive() {
#[derive(Clone, Debug, PartialEq)]
struct Node {
value: i32,
next: Option<Box<Node>>,
}
let (announcer, announcement) = Announcement::<Node>::new();
let listener = announcement.listener();
drop(announcer);
assert_eq!(listener.try_listen(), None);
}
/// Test: Drop test: document blocking behavior
///
/// Verifies: document blocking behavior
/// Category: drop
#[cfg(feature = "std")]
#[test]
fn test_document_blocking_behavior() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
thread::spawn(move || {
thread::sleep(Duration::from_millis(10));
drop(announcer); // Drop instead of announce
});
// This will timeout because announcer was dropped
let result = listener.listen_timeout_blocking(Duration::from_millis(100));
match result {
Err(ListenError::Timeout) => {
// Expected: timeout because announcer was dropped
}
Err(ListenError::Closed) => {
// Also acceptable: closed because announcer was dropped
}
Ok(_) => panic!("Should not receive value when announcer is dropped"),
}
}
/// Test: Multiple announcement channels with some dropped announcers
///
/// Verifies: Multiple announcement channels with some dropped announcers
/// Category: drop
#[test]
fn test_multiple_channels_some_dropped() {
let (a1, ann1) = Announcement::<i32>::new();
let (a2, ann2) = Announcement::<i32>::new();
let (a3, ann3) = Announcement::<i32>::new();
let l1 = ann1.listener();
let l2 = ann2.listener();
let l3 = ann3.listener();
// Announce on first, drop others
a1.announce(42).unwrap();
drop(a2);
drop(a3);
assert_eq!(l1.try_listen(), Some(42));
assert_eq!(l2.try_listen(), None);
assert_eq!(l3.try_listen(), None);
}
+2140
View File
File diff suppressed because it is too large. Load diff
+415
View File
@@ -0,0 +1,415 @@
use announcement::Announcement;
#[cfg(feature = "std")]
use announcement::ListenError;
use std::sync::Arc;
use std::time::Duration;
// =============================================================================
// Basic async tests
// =============================================================================
/// Test: Basic async listen operation
///
/// Verifies: Listener can wait asynchronously and receive announced value
/// Runtime: tokio
/// Category: integration
/// Coverage: Async listen path, task spawning, value reception
#[tokio::test]
async fn test_async_listen() {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(10)).await;
announcer.announce(42).unwrap();
});
let result = listener.listen().await.unwrap();
assert_eq!(result, 42);
}
/// Test: Multiple async listeners receive same broadcast value
///
/// Verifies: Broadcast semantics work correctly across multiple async tasks
/// Runtime: tokio
/// Category: integration
/// Coverage: Concurrent async listeners, task spawning, value cloning
#[tokio::test]
async fn test_multiple_async_listeners() {
let (announcer, announcement) = Announcement::new();
let l1 = announcement.listener();
let l2 = announcement.listener();
let l3 = announcement.listener();
let h1 = tokio::spawn(async move { l1.listen().await.unwrap() });
let h2 = tokio::spawn(async move { l2.listen().await.unwrap() });
let h3 = tokio::spawn(async move { l3.listen().await.unwrap() });
tokio::time::sleep(Duration::from_millis(10)).await;
announcer.announce(42).unwrap();
assert_eq!(h1.await.unwrap(), 42);
assert_eq!(h2.await.unwrap(), 42);
assert_eq!(h3.await.unwrap(), 42);
}
/// Test: Listener created after announce receives value immediately
///
/// Verifies: Fast path works - listeners get value immediately if already announced
/// Runtime: tokio
/// Category: integration
/// Coverage: Fast path optimization, post-announce listener creation
#[tokio::test]
async fn test_listen_after_announce() {
let (announcer, announcement) = Announcement::new();
announcer.announce(42).unwrap();
let listener = announcement.listener();
let result = listener.listen().await.unwrap(); // Returns immediately
assert_eq!(result, 42);
}
/// Test: Cloned announcement channels share the same value
///
/// Verifies: Cloning announcement preserves broadcast semantics
/// Runtime: tokio
/// Category: integration
/// Coverage: Announcement cloning, shared state, value propagation
#[tokio::test]
async fn test_clone_announcement() {
let (announcer, announcement) = Announcement::new();
let announcement2 = announcement.clone();
let l1 = announcement.listener();
let l2 = announcement2.listener();
announcer.announce(42).unwrap();
assert_eq!(l1.listen().await.unwrap(), 42);
assert_eq!(l2.listen().await.unwrap(), 42);
}
// =============================================================================
// Arc usage test with refcount verification
// =============================================================================
/// Test: Arc-wrapped values maintain correct refcount
///
/// Verifies: Arc reference counting works correctly with shared values
/// Runtime: tokio
/// Category: memory
/// Coverage: Arc refcount tracking, value sharing, memory efficiency
#[tokio::test]
async fn test_with_arc() {
#[derive(Clone, Debug, PartialEq)]
struct Data {
value: Vec<u8>,
}
let (announcer, announcement) = Announcement::new();
let data = Arc::new(Data {
value: vec![1, 2, 3],
});
let l1 = announcement.listener();
let l2 = announcement.listener();
announcer.announce(data.clone()).unwrap();
let r1 = l1.listen().await.unwrap();
let r2 = l2.listen().await.unwrap();
assert_eq!(r1, data);
assert_eq!(r2, data);
// Verify Arc refcount is 4 (original + announcement + r1 + r2)
assert_eq!(Arc::strong_count(&r1), 4);
}
// =============================================================================
// Blocking tests (requires std feature)
// =============================================================================
/// Test: Blocking listen with timeout succeeds when value is announced
///
/// Verifies: Blocking listen API works correctly in multi-threaded context
/// Category: integration
/// Coverage: Blocking listen path, thread spawning, timeout handling
#[cfg(feature = "std")]
#[test]
fn test_blocking_listen() {
use std::thread;
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
let handle =
thread::spawn(move || listener.listen_timeout_blocking(Duration::from_millis(100)));
thread::sleep(Duration::from_millis(10));
announcer.announce(42).unwrap();
assert_eq!(handle.join().unwrap(), Ok(42));
}
/// Test: Blocking listen with longer timeout succeeds
///
/// Verifies: Timeout parameter is respected and value is received before timeout
/// Category: integration
/// Coverage: Timeout configuration, successful receive path
#[cfg(feature = "std")]
#[test]
fn test_blocking_timeout_success() {
use std::thread;
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
let handle = thread::spawn(move || listener.listen_timeout_blocking(Duration::from_secs(1)));
thread::sleep(Duration::from_millis(10));
announcer.announce(42).unwrap();
assert_eq!(handle.join().unwrap(), Ok(42));
}
/// Test: Blocking listen detects closed channel immediately
///
/// Verifies: Closed detection works correctly in blocking mode
/// Category: integration
/// Coverage: Channel closure detection, error propagation
#[cfg(feature = "std")]
#[test]
fn test_blocking_timeout_failure() {
let (_, announcement) = Announcement::<i32>::new();
// Note: Announcer dropped immediately via _ pattern
let listener = announcement.listener();
// With closed detection, this returns Closed immediately, not Timeout
let result = listener.listen_timeout_blocking(Duration::from_millis(50));
assert!(matches!(result, Err(ListenError::Closed)));
assert!(announcement.is_closed());
}
// =============================================================================
// CRITICAL: Race condition protection test (TOCTOU)
// =============================================================================
/// Test: TOCTOU race condition protection with tight timing
///
/// Verifies: Double-check pattern prevents race between listen setup and announce
/// Runtime: tokio
/// Category: race
/// Coverage: TOCTOU protection, concurrent announce during listen setup
/// Note: Runs 1000 iterations to catch race conditions
#[tokio::test]
async fn test_race_condition_protection() {
// Test the TOCTOU protection in listen()
for _ in 0..1000 {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
let listen_handle = tokio::spawn(async move { listener.listen().await.unwrap() });
// Announce immediately (race with listen setup)
announcer.announce(42).unwrap();
assert_eq!(listen_handle.await.unwrap(), 42);
}
}
// =============================================================================
// Additional edge case tests
// =============================================================================
/// Test: Multiple listeners created after announce all receive value
///
/// Verifies: Fast path works for multiple post-announce listeners
/// Runtime: tokio
/// Category: edge
/// Coverage: Multiple fast-path retrievals, late listener creation
#[tokio::test]
async fn test_multiple_listeners_created_after_announce() {
let (announcer, announcement) = Announcement::new();
// Announce first
announcer.announce(42).unwrap();
// Create listeners after announcement
let l1 = announcement.listener();
let l2 = announcement.listener();
let l3 = announcement.listener();
// All should get the value immediately
assert_eq!(l1.listen().await.unwrap(), 42);
assert_eq!(l2.listen().await.unwrap(), 42);
assert_eq!(l3.listen().await.unwrap(), 42);
}
/// Test: Listener can be called multiple times with same result
///
/// Verifies: Listen operation is idempotent and reusable
/// Runtime: tokio
/// Category: edge
/// Coverage: Multiple listen calls on same listener, value stability
#[tokio::test]
async fn test_listener_multiple_calls() {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
announcer.announce(42).unwrap();
// Calling listen multiple times should return the same value
assert_eq!(listener.listen().await.unwrap(), 42);
assert_eq!(listener.listen().await.unwrap(), 42);
assert_eq!(listener.listen().await.unwrap(), 42);
}
/// Test: High concurrency with 100 simultaneous listeners
///
/// Verifies: Broadcast scales correctly with many concurrent listeners
/// Runtime: tokio
/// Category: stress
/// Coverage: High listener count, concurrent wakeup, value distribution
#[tokio::test]
async fn test_concurrent_listeners() {
let (announcer, announcement) = Announcement::new();
// Create many listeners
let mut handles = Vec::new();
for i in 0..100 {
let listener = announcement.listener();
handles.push(tokio::spawn(async move {
(i, listener.listen().await.unwrap())
}));
}
// Small delay to ensure listeners are waiting
tokio::time::sleep(Duration::from_millis(10)).await;
// Announce
announcer.announce(999).unwrap();
// All should receive the same value
for handle in handles {
let (_, value) = handle.await.unwrap();
assert_eq!(value, 999);
}
}
/// Test: Large data broadcast with Arc maintains efficient refcounting
///
/// Verifies: Large payloads work efficiently via Arc sharing without copying
/// Runtime: tokio
/// Category: memory
/// Coverage: Large data handling, Arc efficiency, memory usage
#[tokio::test]
async fn test_announcement_with_large_data() {
#[derive(Clone, Debug, PartialEq)]
struct LargeData {
data: Vec<u8>,
}
let (announcer, announcement) = Announcement::new();
let large_data = Arc::new(LargeData {
data: vec![42; 1_000_000], // 1MB of data
});
let listener = announcement.listener();
announcer.announce(large_data.clone()).unwrap();
let result = listener.listen().await.unwrap();
assert_eq!(result, large_data);
// With Arc, we should have 3 references (original + announcement storage + result)
assert_eq!(Arc::strong_count(&result), 3);
}
// =============================================================================
// Memory leak tests
// =============================================================================
/// Test: No memory leak when announcer drops without announcing
///
/// Verifies: Channel cleanup happens correctly without announcement
/// Category: memory
/// Coverage: Resource cleanup, drop behavior, leak prevention
/// Note: Use valgrind/miri to verify
#[test]
fn test_no_leak_when_announcer_dropped() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
drop(announcer); // Drop without announcing
drop(listener);
drop(announcement);
// All memory should be freed
// Use valgrind/miri to verify
}
/// Test: No memory leak when value is announced but no listeners exist
///
/// Verifies: Announced values are properly dropped when unused
/// Category: memory
/// Coverage: Unused value cleanup, memory management
#[test]
fn test_no_leak_when_no_listeners() {
let (announcer, announcement) = Announcement::<Vec<u8>>::new();
announcer.announce(vec![1; 1000]).unwrap();
drop(announcement);
// Value should be dropped
}
// =============================================================================
// smol tests
// =============================================================================
/// Test: Basic async operation with smol runtime
///
/// Verifies: Announcement works correctly with smol async runtime
/// Runtime: smol
/// Category: integration
/// Coverage: Smol runtime compatibility, async listen, task spawning
#[test]
fn test_smol_basic() {
smol::block_on(async {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
smol::spawn(async move {
smol::Timer::after(Duration::from_millis(10)).await;
announcer.announce(42).unwrap();
})
.detach();
let result = listener.listen().await.unwrap();
assert_eq!(result, 42);
});
}
/// Test: Multiple listeners with smol runtime
///
/// Verifies: Broadcast semantics work correctly with smol runtime
/// Runtime: smol
/// Category: integration
/// Coverage: Multiple smol tasks, concurrent listeners, value sharing
#[test]
fn test_smol_multiple_listeners() {
smol::block_on(async {
let (announcer, announcement) = Announcement::new();
let l1 = announcement.listener();
let l2 = announcement.listener();
let h1 = smol::spawn(async move { l1.listen().await.unwrap() });
let h2 = smol::spawn(async move { l2.listen().await.unwrap() });
announcer.announce(42).unwrap();
assert_eq!(h1.await, 42);
assert_eq!(h2.await, 42);
});
}
+213
View File
@@ -0,0 +1,213 @@
//! 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(feature = "std")]
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")]
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));
}
+541
View File
@@ -0,0 +1,541 @@
//! Tests for announcing with no receivers - critical edge cases
//!
//! These tests verify that the announcement system handles gracefully:
//! - Announcing with 0 listeners
//! - Listeners created but dropped before announce
//! - Ensuring no memory leaks or panics
use announcement::Announcement;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Instant;
/// Test: Announce with 0 listeners created at all
///
/// Verifies: Announce with 0 listeners created at all
/// Category: close
#[test]
fn test_announce_with_zero_listeners_created() {
let (announcer, _announcement) = Announcement::<i32>::new();
// No listeners created at all
// Should succeed without panic
announcer.announce(42).unwrap();
}
/// Test: Announce then drop announcement (no listeners exist)
///
/// Verifies: Announce then drop announcement (no listeners exist)
/// Category: close
#[test]
fn test_announce_then_drop_announcement_no_listeners() {
let (announcer, announcement) = Announcement::<i32>::new();
// Drop the announcement immediately
drop(announcement);
// Should still succeed
announcer.announce(42).unwrap();
}
/// Test: Announce with listeners created but all dropped before announce
///
/// Verifies: Announce with listeners created but all dropped before announce
/// Category: close
#[test]
fn test_all_listeners_dropped_before_announce() {
let (announcer, announcement) = Announcement::<String>::new();
// Create listeners then immediately drop them
{
let _listener1 = announcement.listener();
let _listener2 = announcement.listener();
let _listener3 = announcement.listener();
let _listener4 = announcement.listener();
let _listener5 = announcement.listener();
// All listeners dropped here
}
// Should still announce successfully
announcer.announce("test".to_string()).unwrap();
// Verify new listeners can still get the value
let new_listener = announcement.listener();
assert_eq!(new_listener.try_listen(), Some("test".to_string()));
}
/// Test: Announce with listeners created but all dropped after announce
///
/// Verifies: Announce with listeners created but all dropped after announce
/// Category: close
#[test]
fn test_all_listeners_dropped_after_announce() {
let (announcer, announcement) = Announcement::<Vec<u8>>::new();
let mut listeners = Vec::new();
for _ in 0..10 {
listeners.push(announcement.listener());
}
announcer.announce(vec![1, 2, 3]).unwrap();
// Drop all listeners after announce
drop(listeners);
// New listener should still get the value
let new_listener = announcement.listener();
assert_eq!(new_listener.try_listen(), Some(vec![1, 2, 3]));
}
/// Test: Verify no memory leaks when value should be dropped
///
/// Verifies: no memory leaks when value should be dropped
/// Category: close
#[test]
fn test_no_memory_leak_value_dropped_with_last_reference() {
static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
#[derive(Clone, Debug)]
struct DropTracker {
_value: i32,
}
impl Drop for DropTracker {
/// Test: No-receiver test: drop
///
/// Verifies: Verifies drop
/// Category: no-receiver
fn drop(&mut self) {
DROP_COUNT.fetch_add(1, Ordering::SeqCst);
}
}
DROP_COUNT.store(0, Ordering::SeqCst);
// Test 1: No listeners at all
{
let (announcer, announcement) = Announcement::new();
let tracker = DropTracker { _value: 1 };
announcer.announce(tracker).unwrap();
drop(announcement);
}
let drops_after_no_listeners = DROP_COUNT.load(Ordering::SeqCst);
assert!(
drops_after_no_listeners >= 1,
"DropTracker should be dropped when no references exist"
);
DROP_COUNT.store(0, Ordering::SeqCst);
// Test 2: Listeners created and dropped
{
let (announcer, announcement) = Announcement::new();
let tracker = DropTracker { _value: 2 };
let listener1 = announcement.listener();
let listener2 = announcement.listener();
announcer.announce(tracker).unwrap();
// Get values to ensure clones are made
let _val1 = listener1.try_listen();
let _val2 = listener2.try_listen();
// Drop everything
drop(listener1);
drop(listener2);
drop(announcement);
drop(_val1);
drop(_val2);
}
let drops_after_listeners = DROP_COUNT.load(Ordering::SeqCst);
assert!(
drops_after_listeners >= 1,
"All DropTracker instances should be dropped"
);
}
/// Test: Multiple announcements to nobody in a loop (stress test)
///
/// Verifies: Multiple announcements to nobody in a loop (stress test)
/// Category: close
#[test]
fn test_multiple_announcements_to_nobody_stress() {
for i in 0..1000 {
let (announcer, _announcement) = Announcement::<i32>::new();
announcer.announce(i).unwrap();
}
}
/// Test: Large data announced with no listeners (memory stress)
///
/// Verifies: Large data announced with no listeners (memory stress)
/// Category: close
#[test]
fn test_announce_large_data_no_listeners() {
let (announcer, _announcement) = Announcement::<Vec<u8>>::new();
// Announce 10MB of data with no listeners
let large_data = vec![0u8; 10 * 1024 * 1024];
announcer.announce(large_data).unwrap();
}
/// Test: Arc-wrapped data with no listeners
///
/// Verifies: Arc-wrapped data with no listeners
/// Category: close
#[test]
fn test_announce_arc_data_no_listeners() {
let (announcer, _announcement) = Announcement::<Arc<String>>::new();
let data = Arc::new("shared data".to_string());
let initial_count = Arc::strong_count(&data);
announcer.announce(data.clone()).unwrap();
// After announce with no listeners, Arc count should only increase by 1 (stored in OnceLock)
assert_eq!(Arc::strong_count(&data), initial_count + 1);
}
/// Test: Announcement dropped before announcer with no listeners
///
/// Verifies: Announcement dropped before announcer with no listeners
/// Category: close
#[test]
fn test_announcement_dropped_before_announcer_no_listeners() {
let (announcer, announcement) = Announcement::<i32>::new();
// Drop announcement first
drop(announcement);
// Announce should still succeed
announcer.announce(42).unwrap();
}
/// Test: Rapid creation and dropping of empty announcements
///
/// Verifies: Rapid creation and dropping of empty announcements
/// Category: close
#[test]
fn test_rapid_empty_announcement_creation() {
for _ in 0..10000 {
let (_announcer, announcement) = Announcement::<()>::new();
drop(announcement);
}
}
/// Test: Threaded announce with no listeners
///
/// Verifies: Threaded announce with no listeners
/// Category: close
#[test]
fn test_threaded_announce_no_listeners() {
let (announcer, announcement) = Announcement::<i32>::new();
let handle = thread::spawn(move || {
announcer.announce(42).unwrap();
});
// No listeners created
handle.join().unwrap();
// New listener after announce in different thread
let listener = announcement.listener();
assert_eq!(listener.try_listen(), Some(42));
}
/// Test: Many announcements with no listeners in parallel
///
/// Verifies: Many announcements with no listeners in parallel
/// Category: close
#[test]
fn test_parallel_announcements_no_listeners() {
let mut handles = vec![];
for i in 0..100 {
handles.push(thread::spawn(move || {
let (announcer, _announcement) = Announcement::<i32>::new();
announcer.announce(i).unwrap();
}));
}
for handle in handles {
handle.join().unwrap();
}
}
/// Test: Create and drop listeners in a tight loop, then announce
///
/// Verifies: Create and drop listeners in a tight loop, then announce
/// Category: close
#[test]
fn test_listener_churn_before_announce() {
let (announcer, announcement) = Announcement::<i32>::new();
// Create and immediately drop 100,000 listeners
for _ in 0..100_000 {
drop(announcement.listener());
}
// Should still work fine
announcer.announce(42).unwrap();
// New listener should get value
let listener = announcement.listener();
assert_eq!(listener.try_listen(), Some(42));
}
/// Test: Listeners created in threads, all dropped before announce
///
/// Verifies: Listeners created in threads, all dropped before announce
/// Category: close
#[test]
fn test_threaded_listeners_all_dropped() {
let (announcer, announcement) = Announcement::<String>::new();
let mut handles = vec![];
// Create listeners in 10 threads
for _ in 0..10 {
let ann = announcement.clone();
handles.push(thread::spawn(move || {
// Create 100 listeners and immediately drop them
for _ in 0..100 {
drop(ann.listener());
}
}));
}
// Wait for all threads to finish
for handle in handles {
handle.join().unwrap();
}
// Announce after all listeners are gone
announcer.announce("all dropped".to_string()).unwrap();
// New listener should work
let listener = announcement.listener();
assert_eq!(listener.try_listen(), Some("all dropped".to_string()));
}
/// Test: Close test: zero overhead no listeners
///
/// Verifies: zero overhead no listeners
/// Category: close
#[test]
fn test_zero_overhead_no_listeners() {
// Warm up
for _ in 0..10 {
let (announcer, _) = Announcement::<i32>::new();
announcer.announce(42).unwrap();
}
// Measure time for 1000 announcements with no listeners
let start = Instant::now();
for i in 0..1000 {
let (announcer, _) = Announcement::<i32>::new();
announcer.announce(i).unwrap();
}
let duration = start.elapsed();
// Should be very fast (sub-millisecond for 1000 announcements)
assert!(
duration.as_millis() < 10,
"Announcing to no listeners should be nearly zero overhead"
);
}
/// Test: Create 1M listeners, drop them all, then announce
///
/// Verifies: Create 1M listeners, drop them all, then announce
/// Category: close
#[test]
fn test_value_dropped_when_announcement_dropped() {
static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
#[derive(Clone, Debug)]
struct ValueDropTracker {
_data: Vec<u8>,
}
impl Drop for ValueDropTracker {
/// Test: No-receiver test: drop
///
/// Verifies: Verifies drop
/// Category: no-receiver
fn drop(&mut self) {
DROP_COUNT.fetch_add(1, Ordering::SeqCst);
}
}
DROP_COUNT.store(0, Ordering::SeqCst);
{
let (announcer, announcement) = Announcement::new();
let tracker = ValueDropTracker {
_data: vec![1, 2, 3],
};
announcer.announce(tracker).unwrap();
// No listeners, drop announcement
drop(announcement);
}
// Value should be dropped
let drops = DROP_COUNT.load(Ordering::SeqCst);
assert!(
drops >= 1,
"Value should be dropped when announcement is dropped with no references"
);
}
/// Test: Complex scenario - mixed listener lifecycle with no final listeners
///
/// Verifies: Complex scenario - mixed listener lifecycle with no final listeners
/// Category: close
#[test]
fn test_complex_no_listener_scenario() {
use std::collections::HashMap;
let (announcer, announcement) = Announcement::<HashMap<String, Vec<i32>>>::new();
// Phase 1: Create some listeners
let listeners: Vec<_> = (0..5).map(|_| announcement.listener()).collect();
// Phase 2: Drop half of them
drop(listeners);
// Phase 3: Create more and drop immediately
for _ in 0..10 {
drop(announcement.listener());
}
// Phase 4: Create listeners in a thread that exits immediately
let ann = announcement.clone();
thread::spawn(move || {
let _l1 = ann.listener();
let _l2 = ann.listener();
// Thread exits, dropping listeners
})
.join()
.unwrap();
// Phase 5: Announce with no active listeners
let mut data = HashMap::new();
data.insert("key".to_string(), vec![1, 2, 3]);
announcer.announce(data.clone()).unwrap();
// Phase 6: Verify new listener still works
let final_listener = announcement.listener();
assert_eq!(final_listener.try_listen(), Some(data));
}
/// Test: Announcer with custom Drop impl and no listeners
///
/// Verifies: Announcer with custom Drop impl and no listeners
/// Category: close
#[test]
fn test_custom_drop_type_no_listeners() {
static CUSTOM_DROP: AtomicUsize = AtomicUsize::new(0);
#[derive(Clone, Debug)]
struct CustomDrop {
id: usize,
}
impl Drop for CustomDrop {
/// Test: No-receiver test: drop
///
/// Verifies: Verifies drop
/// Category: no-receiver
fn drop(&mut self) {
CUSTOM_DROP.fetch_add(self.id, Ordering::SeqCst);
}
}
CUSTOM_DROP.store(0, Ordering::SeqCst);
{
let (announcer, announcement) = Announcement::new();
let custom = CustomDrop { id: 100 };
announcer.announce(custom).unwrap();
drop(announcement);
}
// Should have been dropped (at least the original + the clone in OnceLock)
assert!(
CUSTOM_DROP.load(Ordering::SeqCst) >= 100,
"CustomDrop should be dropped at least once"
);
}
/// Test: Close test: announce no listeners no blocking
///
/// Verifies: announce no listeners no blocking
/// Category: close
#[test]
fn test_announce_no_listeners_no_blocking() {
let (announcer, _announcement) = Announcement::<String>::new();
let start = Instant::now();
announcer.announce("immediate".to_string()).unwrap();
let elapsed = start.elapsed();
// Should complete in microseconds
assert!(
elapsed.as_micros() < 1000,
"Announce should not block when there are no listeners"
);
}
/// Test: Close test: async no listeners
///
/// Verifies: async no listeners
/// Category: close
#[tokio::test]
async fn test_async_no_listeners() {
let (announcer, _announcement) = Announcement::<i32>::new();
// Announce with no async listeners
announcer.announce(42).unwrap();
}
/// Test: Memory safety with no listeners and large recursive structure
///
/// Verifies: Memory safety with no listeners and large recursive structure
/// Category: close
#[test]
fn test_recursive_structure_no_listeners() {
#[derive(Clone, Debug)]
#[allow(dead_code)]
enum Tree {
Leaf(i32),
Node(Box<Tree>, Box<Tree>),
}
fn create_deep_tree(depth: usize) -> Tree {
if depth == 0 {
Tree::Leaf(42)
} else {
Tree::Node(
Box::new(create_deep_tree(depth - 1)),
Box::new(create_deep_tree(depth - 1)),
)
}
}
let (announcer, _announcement) = Announcement::new();
// Create a tree with depth 10 (1024 leaf nodes)
let tree = create_deep_tree(10);
// Announce with no listeners - should handle the recursive structure properly
announcer.announce(tree).unwrap();
}
+910
View File
@@ -0,0 +1,910 @@
//! Panic Safety Tests
//!
//! These tests verify that the library maintains safety invariants even when panics occur.
//! Panic safety means:
//! 1. No memory leaks
//! 2. No undefined behavior
//! 3. Data structures remain consistent
//! 4. Resources are properly cleaned up
//! 5. No deadlocks or hangs after panic recovery
use announcement::Announcement;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::Arc;
// =============================================================================
// Basic Panic Safety Tests
// =============================================================================
/// Test: Panic safety: panic during Drop implementation
///
/// Verifies: System remains safe when announced value panics during Drop
/// Category: panic
#[test]
fn test_panic_during_drop_in_value() {
// Test that panicking during Drop of the announced value doesn't break things
#[derive(Debug)]
struct PanicOnDrop {
should_panic: bool,
}
impl Drop for PanicOnDrop {
fn drop(&mut self) {
if self.should_panic {
panic!("Intentional panic in Drop");
}
}
}
// Create announcement with panic-on-drop value
let result = catch_unwind(AssertUnwindSafe(|| {
let (announcer, announcement) = Announcement::new();
let _listener = announcement.listener();
announcer
.announce(Arc::new(PanicOnDrop { should_panic: true }))
.unwrap();
// When Arc drops, it should panic, but we catch it
}));
// The panic should be caught
assert!(result.is_err());
// Create another announcement to verify system still works
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
announcer.announce(42).unwrap();
assert_eq!(listener.try_listen(), Some(42));
}
/// Test: Panic safety: panic during Clone/Drop interaction
///
/// Verifies: System remains safe when panic occurs during value cloning or dropping
/// Category: panic
#[test]
fn test_panic_in_clone_implementation() {
// Test panic during T::clone() doesn't break announcement
#[derive(Debug, Clone)]
struct PanicOnClone {
panic_count: Arc<std::sync::atomic::AtomicUsize>,
panic_on_nth: usize,
}
impl Drop for PanicOnClone {
fn drop(&mut self) {
let count = self
.panic_count
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
if count == self.panic_on_nth {
panic!("Panic on Nth drop");
}
}
}
let panic_count = Arc::new(std::sync::atomic::AtomicUsize::new(0));
let _result = catch_unwind(AssertUnwindSafe(|| {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
let value = PanicOnClone {
panic_count: panic_count.clone(),
panic_on_nth: 2, // Panic on 3rd drop
};
announcer.announce(value).unwrap();
// First try_listen - might panic
let _v1 = listener.try_listen();
}));
// System should still work after panic
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
announcer.announce(42).unwrap();
assert_eq!(listener.try_listen(), Some(42));
}
/// Test: Panic safety: explicit announcer drop
///
/// Verifies: Dropping announcer without announcing properly closes listeners
/// Category: panic
#[test]
fn test_announcer_drop_with_panic_guard() {
// Test that dropping announcer without announcing works
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
assert!(!listener.is_closed());
// Drop announcer explicitly
drop(announcer);
// Listener should see closed
assert!(listener.is_closed());
}
// =============================================================================
// Arc Reference Counting Under Panic
// =============================================================================
/// Test: Panic safety: Arc reference counting
///
/// Verifies: Arc refcounts remain correct when panics occur while holding references
/// Category: panic
#[test]
fn test_arc_refcount_with_panic() {
// Verify Arc refcounts are correct even when panics occur
let (announcer, announcement) = Announcement::new();
let data = Arc::new(vec![1, 2, 3, 4, 5]);
announcer.announce(data.clone()).unwrap();
// Create listeners
let l1 = announcement.listener();
let l2 = announcement.listener();
// Get values
let v1 = l1.try_listen().unwrap();
let _v2 = l2.try_listen().unwrap();
// Refcount should be: data + announcement storage + v1 + v2 = 4
assert_eq!(Arc::strong_count(&v1), 4);
// Cause panic while holding references
let result = catch_unwind(AssertUnwindSafe(|| {
let _v3 = l1.try_listen().unwrap();
panic!("Test panic");
}));
assert!(result.is_err());
// Refcount should still be correct
assert_eq!(Arc::strong_count(&v1), 4);
}
/// Test: Panic safety: announcement clone with panic
///
/// Verifies: Cloned announcements remain functional after panic in sibling clone
/// Category: panic
#[test]
fn test_announcement_clone_with_panic() {
let (announcer, announcement) = Announcement::new();
// Clone announcement
let announcement2 = announcement.clone();
// Panic while one is in scope
let result = catch_unwind(AssertUnwindSafe(|| {
let _l = announcement2.listener();
panic!("Test panic");
}));
assert!(result.is_err());
// Original announcement should still work
let listener = announcement.listener();
announcer.announce(42).unwrap();
assert_eq!(listener.try_listen(), Some(42));
}
/// Test: Panic safety: listener clone with panic
///
/// Verifies: Cloned listeners remain functional after panic in sibling clone
/// Category: panic
#[test]
fn test_listener_clone_with_panic() {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
// Clone listener
let listener2 = listener.clone();
// Announce value
announcer.announce(42).unwrap();
// Panic while using clone
let result = catch_unwind(AssertUnwindSafe(|| {
let _v = listener2.try_listen();
panic!("Test panic");
}));
assert!(result.is_err());
// Original listener should still work
assert_eq!(listener.try_listen(), Some(42));
}
// =============================================================================
// Multiple Panic Scenarios
// =============================================================================
/// Test: Panic safety: sequential panics
///
/// Verifies: System handles multiple panics in sequence without breaking
/// Category: panic
#[test]
fn test_sequential_panics() {
// Multiple panics in sequence shouldn't break the system
for i in 0..5 {
let result = catch_unwind(AssertUnwindSafe(|| {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
announcer.announce(i).unwrap();
if i % 2 == 0 {
panic!("Even panic");
}
assert_eq!(listener.try_listen(), Some(i));
}));
if i % 2 == 0 {
assert!(result.is_err());
} else {
assert!(result.is_ok());
}
}
}
/// Test: Panic safety: nested panic scenarios
///
/// Verifies: System handles panic within panic handler context
/// Category: panic
#[test]
fn test_nested_panics() {
// Panic within panic handler
let result = catch_unwind(AssertUnwindSafe(|| {
let (announcer, announcement) = Announcement::<i32>::new();
let _listener = announcement.listener();
// Drop announcer (will set closed flag)
drop(announcer);
// Now cause a panic
panic!("First panic");
}));
assert!(result.is_err());
// System should still work
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
announcer.announce(42).unwrap();
assert_eq!(listener.try_listen(), Some(42));
}
// =============================================================================
// Async Panic Safety Tests (requires async feature)
// =============================================================================
/// Test: Panic safety: panic during async listen
///
/// Verifies: System remains functional when async task panics after listen completes
/// Category: panic
#[tokio::test]
async fn test_panic_during_async_listen() {
use std::time::Duration;
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
// Spawn task that will panic
let handle = tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(10)).await;
let _value = listener.listen().await.unwrap();
panic!("Panic after listen");
});
// Announce value
announcer.announce(42).unwrap();
// Task should panic
let result = handle.await;
assert!(result.is_err());
// System should still work
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
announcer.announce(42).unwrap();
assert_eq!(listener.listen().await, Ok(42));
}
/// Test: Panic safety: panic before async listen completion
///
/// Verifies: System remains functional when async task panics before listen completes
/// Category: panic
#[tokio::test]
async fn test_panic_before_async_listen_completes() {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
// Start listening but panic before completion
let handle = tokio::spawn(async move {
tokio::select! {
_ = listener.listen() => {
panic!("Should not reach here");
}
_ = tokio::time::sleep(tokio::time::Duration::from_millis(5)) => {
panic!("Timeout panic");
}
}
});
// Task should panic
let result = handle.await;
assert!(result.is_err());
// Announcer should still work
announcer.announce(42).unwrap();
}
/// Test: Panic safety: multiple async tasks with selective panics
///
/// Verifies: Some async tasks can panic while others complete successfully
/// Category: panic
#[tokio::test]
async fn test_multiple_async_tasks_with_panics() {
let (announcer, announcement) = Announcement::new();
// Spawn multiple tasks, some will panic
let mut handles = vec![];
for i in 0..10 {
let listener = announcement.listener();
handles.push(tokio::spawn(async move {
let value = listener.listen().await.unwrap();
if i % 3 == 0 {
panic!("Task {} panic", i);
}
value
}));
}
// Announce value
announcer.announce(42).unwrap();
// Check results - some panic, some succeed
for (i, handle) in handles.into_iter().enumerate() {
let result = handle.await;
if i % 3 == 0 {
assert!(result.is_err(), "Task {} should have panicked", i);
} else {
assert_eq!(result.unwrap(), 42, "Task {} should have succeeded", i);
}
}
}
// =============================================================================
// Blocking Mode Panic Safety (requires std feature)
// =============================================================================
/// Test: Panic safety: panic during blocking listen
///
/// Verifies: System remains functional when blocking thread panics after listen
/// Category: panic
#[cfg(feature = "std")]
#[test]
fn test_panic_during_blocking_listen() {
use std::thread;
use std::time::Duration;
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
let handle = thread::spawn(move || {
let _value = listener.listen_timeout_blocking(Duration::from_millis(100));
panic!("Panic after blocking listen with timeout");
});
// Give thread time to start
std::thread::sleep(std::time::Duration::from_millis(10));
// Announce value
announcer.announce(42).unwrap();
// Thread should panic
let result = handle.join();
assert!(result.is_err());
}
/// Test: Panic safety: multiple blocking threads with selective panics
///
/// Verifies: Some blocking threads can panic while others complete successfully
/// Category: panic
#[cfg(feature = "std")]
#[test]
fn test_multiple_blocking_threads_with_panics() {
use std::thread;
use std::time::Duration;
let (announcer, announcement) = Announcement::new();
let mut handles = vec![];
for i in 0..5 {
let listener = announcement.listener();
handles.push(thread::spawn(move || {
let value = listener.listen_timeout_blocking(Duration::from_millis(100));
if i % 2 == 0 {
panic!("Thread {} panic", i);
}
value
}));
}
thread::sleep(Duration::from_millis(10));
announcer.announce(42).unwrap();
// Check results
for (i, handle) in handles.into_iter().enumerate() {
let result = handle.join();
if i % 2 == 0 {
assert!(result.is_err());
} else {
assert_eq!(result.unwrap(), Ok(42));
}
}
}
// =============================================================================
// Edge Cases and Stress Tests
// =============================================================================
/// Test: Panic safety: panic with many listeners
///
/// Verifies: Panicking while iterating over many listeners doesn't affect others
/// Category: panic
#[test]
fn test_panic_with_large_number_of_listeners() {
let (announcer, announcement) = Announcement::new();
// Create many listeners
let listeners: Vec<_> = (0..1000).map(|_| announcement.listener()).collect();
announcer.announce(42).unwrap();
// Panic while iterating
let result = catch_unwind(AssertUnwindSafe(|| {
for (i, listener) in listeners.iter().enumerate() {
let _value = listener.try_listen();
if i == 500 {
panic!("Panic at 500");
}
}
}));
assert!(result.is_err());
// Remaining listeners should still work
for listener in listeners.iter().skip(501) {
assert_eq!(listener.try_listen(), Some(42));
}
}
/// Test: Panic safety: panic during is_announced checks
///
/// Verifies: System remains functional when panic occurs during state checks
/// Category: panic
#[test]
fn test_panic_during_is_announced_checks() {
let (announcer, announcement) = Announcement::new();
let result = catch_unwind(AssertUnwindSafe(|| {
assert!(!announcement.is_announced());
panic!("Panic before announce");
}));
assert!(result.is_err());
// Should still be able to announce
announcer.announce(42).unwrap();
assert!(announcement.is_announced());
}
/// Test: Panic safety: panic during is_closed checks
///
/// Verifies: System remains functional when panic occurs during closure checks
/// Category: panic
#[test]
fn test_panic_during_is_closed_checks() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
let result = catch_unwind(AssertUnwindSafe(|| {
assert!(!announcement.is_closed());
panic!("Panic before close");
}));
assert!(result.is_err());
// Should still be able to close
announcer.close();
assert!(announcement.is_closed());
assert!(listener.is_closed());
}
/// Test: Panic safety: no memory leaks on panic
///
/// Verifies: Repeated panics during announcement don't cause memory leaks
/// Category: panic
#[test]
fn test_memory_not_leaked_on_panic() {
// Create and drop many announcements with panics
for _ in 0..100 {
let result = catch_unwind(AssertUnwindSafe(|| {
let (announcer, announcement) = Announcement::new();
let _listener = announcement.listener();
announcer.announce(vec![1; 1000]).unwrap();
panic!("Test panic");
}));
assert!(result.is_err());
}
// If there were memory leaks, this would show up in memory profilers
// For now, we just verify the test completes
}
/// Test: Panic safety: close with panic recovery
///
/// Verifies: System remains functional after panic following close operation
/// Category: panic
#[test]
fn test_announcer_close_with_panic_recovery() {
let result = catch_unwind(AssertUnwindSafe(|| {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
announcer.close();
assert!(listener.is_closed());
panic!("Panic after close");
}));
assert!(result.is_err());
// System should still work
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
announcer.announce(42).unwrap();
assert_eq!(listener.try_listen(), Some(42));
}
/// Test: Panic safety: UnwindSafe trait implementation
///
/// Verifies: All types implement UnwindSafe and RefUnwindSafe traits
/// Category: panic
#[test]
fn test_unwind_safe_trait_impl() {
// Verify types implement UnwindSafe and RefUnwindSafe
fn assert_unwind_safe<T: std::panic::UnwindSafe>() {}
fn assert_ref_unwind_safe<T: std::panic::RefUnwindSafe>() {}
assert_unwind_safe::<announcement::Announcement<i32>>();
assert_unwind_safe::<announcement::Announcer<i32>>();
assert_unwind_safe::<announcement::Listener<i32>>();
assert_ref_unwind_safe::<announcement::Announcement<i32>>();
assert_ref_unwind_safe::<announcement::Listener<i32>>();
}
// =============================================================================
// Complex Panic Scenarios
// =============================================================================
/// Test: Panic safety: panic in complex type Drop
///
/// Verifies: System handles panics during drop of complex nested types
/// Category: panic
#[test]
fn test_panic_in_complex_type_drop() {
#[derive(Debug)]
struct ComplexType {
_data: Vec<Vec<String>>,
should_panic: bool,
}
impl Drop for ComplexType {
fn drop(&mut self) {
if self.should_panic {
panic!("Complex drop panic");
}
}
}
let result = catch_unwind(AssertUnwindSafe(|| {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
let complex = ComplexType {
_data: vec![vec!["test".to_string(); 10]; 10],
should_panic: false,
};
announcer.announce(Arc::new(complex)).unwrap();
let value = listener.try_listen().unwrap();
// Modify to panic on drop
// (Actually, we can't modify Arc contents, so this tests Arc safety)
drop(value);
}));
assert!(result.is_ok());
}
/// Test: Panic safety: panic recovery in async loop
///
/// Verifies: Async system recovers from panics across multiple iterations
/// Category: panic
#[tokio::test]
async fn test_panic_recovery_in_async_loop() {
// Test that async system recovers from panics in loop
for i in 0..5 {
let result = catch_unwind(AssertUnwindSafe(|| {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
announcer.announce(i).unwrap();
if i == 2 {
panic!("Panic on iteration 2");
}
assert_eq!(listener.try_listen(), Some(i));
}));
if i == 2 {
assert!(result.is_err());
} else {
assert!(result.is_ok());
}
}
// Async system should still work
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
announcer.announce(42).unwrap();
assert_eq!(listener.listen().await, Ok(42));
}
// Note: Double-panic test removed because double panics abort the process by design.
// This is correct Rust behavior to prevent undefined behavior during unwinding.
// =============================================================================
// Additional Panic Safety Tests
// =============================================================================
/// Test: Panic safety: panic with cloned announcements
///
/// Verifies: Cloned announcements remain functional after panic in siblings
/// Category: panic
#[test]
fn test_panic_with_cloned_announcements() {
let (announcer, announcement) = Announcement::new();
let announcement2 = announcement.clone();
let announcement3 = announcement.clone();
let result = catch_unwind(AssertUnwindSafe(|| {
let _l1 = announcement.listener();
let _l2 = announcement2.listener();
panic!("Test panic with clones");
}));
assert!(result.is_err());
// announcement3 should still work
let listener = announcement3.listener();
announcer.announce(42).unwrap();
assert_eq!(listener.try_listen(), Some(42));
}
/// Test: Panic safety: panic during listener creation
///
/// Verifies: System handles panics occurring during listener creation process
/// Category: panic
#[test]
fn test_panic_during_listener_creation() {
for _ in 0..10 {
let result = catch_unwind(AssertUnwindSafe(|| {
let (_announcer, announcement) = Announcement::<i32>::new();
// Create some listeners
let _l1 = announcement.listener();
let _l2 = announcement.listener();
// Panic during creation
panic!("Panic during listener creation");
}));
assert!(result.is_err());
}
}
/// Test: Panic safety: panic with many clones
///
/// Verifies: System handles panic while iterating over many clones
/// Category: panic
#[test]
fn test_panic_with_many_clones() {
let (announcer, announcement) = Announcement::new();
// Create many clones
let clones: Vec<_> = (0..100).map(|_| announcement.clone()).collect();
let result = catch_unwind(AssertUnwindSafe(|| {
for (i, clone) in clones.iter().enumerate() {
let _listener = clone.listener();
if i == 50 {
panic!("Panic at clone 50");
}
}
}));
assert!(result.is_err());
// Original should still work
let listener = announcement.listener();
announcer.announce(42).unwrap();
assert_eq!(listener.try_listen(), Some(42));
}
/// Test: Panic safety: panic during concurrent announces
///
/// Verifies: Panics in one announcement don't affect concurrent announcements
/// Category: panic
#[tokio::test]
async fn test_panic_during_concurrent_announces() {
// Test that panics don't affect concurrent operations
for _ in 0..10 {
let (announcer1, announcement1) = Announcement::new();
let (announcer2, announcement2) = Announcement::new();
let l1 = announcement1.listener();
let l2 = announcement2.listener();
// Spawn task that will panic
let h1 = tokio::spawn(async move {
announcer1.announce(42).unwrap();
panic!("Panic after first announce");
});
// Spawn task that should succeed
let h2 = tokio::spawn(async move {
tokio::time::sleep(tokio::time::Duration::from_millis(5)).await;
announcer2.announce(100).unwrap();
});
// First task should panic
assert!(h1.await.is_err());
// Second task should succeed
assert!(h2.await.is_ok());
// Both values should be available
assert_eq!(l1.listen().await, Ok(42));
assert_eq!(l2.listen().await, Ok(100));
}
}
/// Test: Panic safety: panic with nested Arc structures
///
/// Verifies: System handles panics with complex nested Arc data structures
/// Category: panic
#[test]
fn test_panic_in_nested_structures() {
#[derive(Debug, Clone)]
struct Nested {
_data: Arc<Vec<Arc<String>>>,
}
let result = catch_unwind(AssertUnwindSafe(|| {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
let nested = Nested {
_data: Arc::new(vec![
Arc::new("a".to_string()),
Arc::new("b".to_string()),
Arc::new("c".to_string()),
]),
};
announcer.announce(nested).unwrap();
let _value = listener.try_listen();
panic!("Panic with nested structure");
}));
assert!(result.is_err());
}
/// Test: Panic safety: panic mixing blocking and async
///
/// Verifies: Panics in blocking threads don't affect async operations
/// Category: panic
#[cfg(feature = "std")]
#[test]
fn test_panic_mixing_blocking_and_async() {
use std::thread;
use std::time::Duration;
let (announcer, announcement) = Announcement::new();
let listener1 = announcement.listener();
let listener2 = announcement.listener();
// Blocking thread with timeout
let h1 = thread::spawn(move || {
let result = catch_unwind(AssertUnwindSafe(|| {
let _value = listener1.listen_timeout_blocking(Duration::from_millis(100));
panic!("Panic in blocking thread with timeout");
}));
assert!(result.is_err());
});
// Announce from main thread
thread::sleep(std::time::Duration::from_millis(10));
announcer.announce(42).unwrap();
h1.join().unwrap();
// Other listener should still work
let rt = tokio::runtime::Runtime::new().unwrap();
let result = rt.block_on(async { listener2.listen().await });
assert_eq!(result, Ok(42));
}
/// Test: Panic safety: panic after immediate announcer drop
///
/// Verifies: System handles panic occurring after announce and immediate drop
/// Category: panic
#[test]
fn test_panic_with_immediate_drop() {
// Test panic when announcer is dropped immediately after announce
for _ in 0..20 {
let result = catch_unwind(AssertUnwindSafe(|| {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
announcer.announce(42).unwrap();
// Announcer dropped here
assert_eq!(listener.try_listen(), Some(42));
panic!("Panic after announce and drop");
}));
assert!(result.is_err());
}
// System should still work
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
announcer.announce(100).unwrap();
assert_eq!(listener.try_listen(), Some(100));
}
@@ -0,0 +1,8 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc ffcff4ea6a5b0fe8eed6a43f2ed183508e5248b46fbc50bded768a61f071c177 # shrinks to value = -1909181462, delays = [4, 3]
cc e107508cad2bad5d0efbb2ce1c68d5aadec45bd310d1f602aa0f6e2ea8c84cfa # shrinks to value = -468334696, delays = [1, 5]
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+802
View File
@@ -0,0 +1,802 @@
//! 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");
}
+334
View File
@@ -0,0 +1,334 @@
// Skip all smol tests under Miri - it doesn't support timerfd_create syscall
#![cfg_attr(miri, allow(dead_code))]
use announcement::Announcement;
use std::sync::Arc;
use std::time::Duration;
// =============================================================================
// Smol runtime tests
// =============================================================================
#[test]
#[cfg_attr(
miri,
ignore = "Miri doesn't support timerfd_create syscall used by smol"
)]
/// Test: Runtime test: Smol runtime basic operations
///
/// Verifies: Basic announcement and listening works on Smol runtime
/// Category: runtime
fn test_smol_basic() {
smol::block_on(async {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
smol::spawn(async move {
smol::Timer::after(Duration::from_millis(10)).await;
announcer.announce(42).unwrap();
})
.detach();
let result = listener.listen().await.unwrap();
assert_eq!(result, 42);
});
}
#[test]
#[cfg_attr(
miri,
ignore = "Miri doesn't support timerfd_create syscall used by smol"
)]
/// Test: Runtime test: multiple concurrent listeners
///
/// Verifies: Multiple listeners all receive the same announced value on the runtime
/// Category: runtime
fn test_smol_multiple_listeners() {
smol::block_on(async {
let (announcer, announcement) = Announcement::new();
let l1 = announcement.listener();
let l2 = announcement.listener();
let l3 = announcement.listener();
let h1 = smol::spawn(async move { l1.listen().await.unwrap() });
let h2 = smol::spawn(async move { l2.listen().await.unwrap() });
let h3 = smol::spawn(async move { l3.listen().await.unwrap() });
// Small delay before announcing
smol::Timer::after(Duration::from_millis(10)).await;
announcer.announce(42).unwrap();
assert_eq!(h1.await, 42);
assert_eq!(h2.await, 42);
assert_eq!(h3.await, 42);
});
}
#[test]
#[cfg_attr(
miri,
ignore = "Miri doesn't support timerfd_create syscall used by smol"
)]
/// Test: Runtime test: Smol concurrent announce and listen
///
/// Verifies: Concurrent announce and listen operations work on Smol runtime
/// Category: runtime
fn test_smol_concurrent_announce_and_listen() {
smol::block_on(async {
let (announcer, announcement) = Announcement::new();
// Create many listeners concurrently
let mut handles = Vec::new();
for i in 0..50 {
let listener = announcement.listener();
handles.push(smol::spawn(
async move { (i, listener.listen().await.unwrap()) },
));
}
// Announce value concurrently
smol::spawn(async move {
smol::Timer::after(Duration::from_millis(5)).await;
announcer.announce(999).unwrap();
})
.detach();
// Verify all listeners get the value
for handle in handles {
let (_, value) = handle.await;
assert_eq!(value, 999);
}
});
}
#[test]
#[cfg_attr(
miri,
ignore = "Miri doesn't support timerfd_create syscall used by smol"
)]
/// Test: Runtime test: Smol listen after announce
///
/// Verifies: Listening after announce returns immediately on Smol runtime
/// Category: runtime
fn test_smol_listen_after_announce() {
smol::block_on(async {
let (announcer, announcement) = Announcement::new();
// Announce first
announcer.announce(42).unwrap();
// Create listeners after
let l1 = announcement.listener();
let l2 = announcement.listener();
// Should return immediately
assert_eq!(l1.listen().await.unwrap(), 42);
assert_eq!(l2.listen().await.unwrap(), 42);
});
}
#[test]
#[cfg_attr(
miri,
ignore = "Miri doesn't support timerfd_create syscall used by smol"
)]
/// Test: Runtime test: Smol with Arc-wrapped data
///
/// Verifies: Arc-wrapped data is handled correctly on Smol runtime
/// Category: runtime
fn test_smol_with_arc() {
smol::block_on(async {
#[derive(Clone, Debug, PartialEq)]
struct Data {
value: Vec<u8>,
}
let (announcer, announcement) = Announcement::new();
let data = Arc::new(Data {
value: vec![1, 2, 3, 4, 5],
});
let l1 = announcement.listener();
let l2 = announcement.listener();
announcer.announce(data.clone()).unwrap();
let r1 = l1.listen().await.unwrap();
let r2 = l2.listen().await.unwrap();
assert_eq!(r1, data);
assert_eq!(r2, data);
// Verify Arc refcount
assert_eq!(Arc::strong_count(&r1), 4);
});
}
#[test]
#[cfg_attr(
miri,
ignore = "Miri doesn't support timerfd_create syscall used by smol"
)]
/// Test: Runtime test: Smol race condition protection
///
/// Verifies: Race conditions are properly handled on Smol runtime
/// Category: runtime
fn test_smol_race_condition_protection() {
smol::block_on(async {
// Run multiple times to catch any race conditions
for i in 0..100 {
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
let handle = smol::spawn(async move { listener.listen().await.unwrap() });
// Announce immediately (potential race)
announcer.announce(i).unwrap();
assert_eq!(handle.await, i);
}
});
}
#[test]
#[cfg_attr(
miri,
ignore = "Miri doesn't support timerfd_create syscall used by smol"
)]
/// Test: Runtime test: Smol many concurrent tasks
///
/// Verifies: Many concurrent tasks all receive values correctly on Smol runtime
/// Category: runtime
fn test_smol_many_concurrent_tasks() {
smol::block_on(async {
let (announcer, announcement) = Announcement::new();
// Create 100 concurrent listeners
let mut handles = Vec::new();
for _ in 0..100 {
let listener = announcement.listener();
handles.push(smol::spawn(async move { listener.listen().await.unwrap() }));
}
// Wait a bit to ensure all are waiting
smol::Timer::after(Duration::from_millis(10)).await;
// Announce
announcer.announce(12345).unwrap();
// All should receive the same value
for handle in handles {
assert_eq!(handle.await, 12345);
}
});
}
#[test]
#[cfg_attr(
miri,
ignore = "Miri doesn't support timerfd_create syscall used by smol"
)]
/// Test: Runtime test: Smol dropped listener handling
///
/// Verifies: System works correctly when listeners are dropped on Smol runtime
/// Category: runtime
fn test_smol_listener_dropped() {
smol::block_on(async {
let (announcer, announcement) = Announcement::new();
// Create and immediately drop a listener
{
let _listener = announcement.listener();
// Dropped here
}
// Should still work with new listeners
let listener = announcement.listener();
announcer.announce(42).unwrap();
assert_eq!(listener.listen().await.unwrap(), 42);
});
}
#[test]
#[cfg_attr(
miri,
ignore = "Miri doesn't support timerfd_create syscall used by smol"
)]
/// Test: Runtime test: Smol announcement cloning
///
/// Verifies: Cloned announcements work correctly on Smol runtime
/// Category: runtime
fn test_smol_clone_announcement() {
smol::block_on(async {
let (announcer, announcement) = Announcement::new();
let announcement2 = announcement.clone();
let l1 = announcement.listener();
let l2 = announcement2.listener();
announcer.announce(42).unwrap();
assert_eq!(l1.listen().await.unwrap(), 42);
assert_eq!(l2.listen().await.unwrap(), 42);
});
}
#[test]
#[cfg_attr(
miri,
ignore = "Miri doesn't support timerfd_create syscall used by smol"
)]
/// Test: Runtime test: timeout behavior
///
/// Verifies: Runtime timeout mechanisms work correctly with announcements
/// Category: runtime
fn test_smol_timeout_behavior() {
use announcement::ListenError;
smol::block_on(async {
let (_, announcement) = Announcement::<i32>::new();
// Note: Announcer dropped immediately via _ pattern - sets closed flag
let listener = announcement.listener();
// With closed detection, listen() returns Closed immediately
let result = listener.listen().await;
assert_eq!(result, Err(ListenError::Closed));
assert!(announcement.is_closed());
});
}
#[test]
#[cfg_attr(
miri,
ignore = "Miri doesn't support timerfd_create syscall used by smol"
)]
/// Test: Runtime test: Smol complex data types
///
/// Verifies: Complex data types work correctly on Smol runtime
/// Category: runtime
fn test_smol_complex_types() {
smol::block_on(async {
#[derive(Clone, Debug, PartialEq)]
struct ComplexData {
name: String,
values: Vec<i32>,
nested: Option<Box<ComplexData>>,
}
let (announcer, announcement) = Announcement::new();
let listener = announcement.listener();
let data = ComplexData {
name: "test".to_string(),
values: vec![1, 2, 3, 4, 5],
nested: None,
};
announcer.announce(data.clone()).unwrap();
let result = listener.listen().await.unwrap();
assert_eq!(result, data);
});
}
+192
View File
@@ -0,0 +1,192 @@
//! 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);
}
+163
View File
@@ -0,0 +1,163 @@
//! Tracing coverage tests
//!
//! These tests ensure tracing statements are executed during coverage runs.
//! They set up a tracing subscriber and exercise all code paths that contain
//! tracing statements.
#[cfg(feature = "tracing")]
#[cfg(test)]
mod tracing_tests {
use announcement::Announcement;
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use tracing::Level;
use tracing_subscriber::fmt::format::FmtSpan;
/// Test: Tracing test: all tracing paths
///
/// Verifies: Verifies all tracing paths
/// Category: tracing
/// Test that exercises all tracing statements in src/lib.rs
#[test]
fn test_all_tracing_paths() {
// Set up tracing subscriber to capture all trace/debug messages
let subscriber = tracing_subscriber::fmt()
.with_max_level(Level::TRACE)
.with_span_events(FmtSpan::FULL)
.with_test_writer()
.finish();
tracing::subscriber::with_default(subscriber, || {
// Test 1: Announcement::new() - covers line 286-287
let (announcer, announcement) = Announcement::<i32>::new();
// Test 2: announce() - covers lines 422-423, 440, 444
let listener = announcement.listener();
announcer.announce(42).unwrap();
assert_eq!(listener.try_listen(), Some(42));
// Test 3: close() - covers lines 486, 492-493
let (announcer2, announcement2) = Announcement::<String>::new();
let _listener2 = announcement2.listener();
announcer2.close();
assert!(announcement2.is_closed());
// Test 4: Drop (auto-close) - covers lines 531-532, 538-539
let (announcer3, announcement3) = Announcement::<Vec<u8>>::new();
let _listener3 = announcement3.listener();
drop(announcer3); // Triggers Drop impl
assert!(announcement3.is_closed());
// Test 5: listen() async fast path - covers lines 653-654
let (announcer4, announcement4) = Announcement::<String>::new();
announcer4.announce("fast".to_string()).unwrap();
let listener4 = announcement4.listener();
let rt = tokio::runtime::Runtime::new().unwrap();
let result = rt.block_on(async { listener4.listen().await });
assert_eq!(result, Ok("fast".to_string()));
// Test 6: listen() async slow path - covers lines 660-661, 666-667, 675-677, 682-683, 691
let (announcer5, announcement5) = Announcement::<i32>::new();
let listener5 = announcement5.listener();
let rt2 = tokio::runtime::Runtime::new().unwrap();
rt2.block_on(async {
let listen_future = listener5.listen();
// Spawn a task to announce after a delay
let ann5_clone = announcer5;
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(10)).await;
ann5_clone.announce(99).unwrap();
});
let result = listen_future.await;
assert_eq!(result, Ok(99));
});
// Test 7: listen() async with closed announcer - covers lines 660-661, 666-667, 682-683
let (announcer6, announcement6) = Announcement::<String>::new();
let listener6 = announcement6.listener();
let rt3 = tokio::runtime::Runtime::new().unwrap();
rt3.block_on(async {
let listen_future = listener6.listen();
// Close the announcer
let ann6_clone = announcer6;
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(10)).await;
ann6_clone.close();
});
let result = listen_future.await;
assert!(result.is_err());
});
});
}
/// Test: Tracing test: blocking with tracing
///
/// Verifies: Verifies blocking with tracing
/// Category: tracing
/// Test blocking operations with tracing
#[cfg(feature = "std")]
#[test]
fn test_blocking_with_tracing() {
let subscriber = tracing_subscriber::fmt()
.with_max_level(Level::TRACE)
.with_test_writer()
.finish();
tracing::subscriber::with_default(subscriber, || {
// listen_blocking() - exercises tracing in blocking wait
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
thread::spawn(move || {
thread::sleep(Duration::from_millis(10));
announcer.announce(123).unwrap();
});
let result = listener.listen_blocking();
assert_eq!(result, Ok(123));
});
}
/// Test: Tracing test: concurrent tracing
///
/// Verifies: Verifies concurrent tracing
/// Category: tracing
/// Test concurrent tracing paths
#[test]
fn test_concurrent_tracing() {
let subscriber = tracing_subscriber::fmt()
.with_max_level(Level::TRACE)
.with_test_writer()
.finish();
tracing::subscriber::with_default(subscriber, || {
let (announcer, announcement) = Announcement::<Arc<String>>::new();
// Announce first
announcer
.announce(Arc::new("concurrent".to_string()))
.unwrap();
let mut handles = vec![];
// Create multiple listeners concurrently after announce
for _ in 0..10 {
let listener = announcement.listener();
handles.push(thread::spawn(move || listener.try_listen()));
}
// Verify all threads received the value
for handle in handles {
let result = handle.join().unwrap();
assert_eq!(result, Some(Arc::new("concurrent".to_string())));
}
});
}
}
+335
View File
@@ -0,0 +1,335 @@
//! Tests for trait implementations (Display, Error, Debug, etc.)
//!
//! These tests ensure that all trait implementations are properly covered
//! by the test suite, particularly for error types.
use announcement::{AnnounceError, Announcement, ListenError};
use std::error::Error;
// =============================================================================
// AnnounceError Trait Implementation Tests
// =============================================================================
/// Test: Trait test: announce error display
///
/// Verifies: Verifies announce error display
/// Category: trait
#[test]
fn test_announce_error_display() {
let err = AnnounceError::AlreadyAnnounced(42);
let display_str = format!("{}", err);
assert_eq!(display_str, "a value has already been announced");
}
/// Test: Trait test: announce error display string type
///
/// Verifies: Verifies announce error display string type
/// Category: trait
#[test]
fn test_announce_error_display_string_type() {
let err = AnnounceError::AlreadyAnnounced("test".to_string());
let display_str = format!("{}", err);
assert_eq!(display_str, "a value has already been announced");
}
/// Test: Trait test: announce error to string
///
/// Verifies: Verifies announce error to string
/// Category: trait
#[test]
fn test_announce_error_to_string() {
let err = AnnounceError::AlreadyAnnounced(99);
assert_eq!(err.to_string(), "a value has already been announced");
}
/// Test: Trait test: announce error std error trait
///
/// Verifies: Verifies announce error std error trait
/// Category: trait
#[test]
fn test_announce_error_std_error_trait() {
let err = AnnounceError::AlreadyAnnounced(42);
// Verify it implements std::error::Error by using it as a trait object
let _err_ref: &dyn Error = &err;
// Verify source() returns None (default implementation)
assert!(err.source().is_none());
}
/// Test: Trait test: announce error debug
///
/// Verifies: Verifies announce error debug
/// Category: trait
#[test]
fn test_announce_error_debug() {
let err = AnnounceError::AlreadyAnnounced(42);
let debug_str = format!("{:?}", err);
assert!(debug_str.contains("AlreadyAnnounced"));
assert!(debug_str.contains("42"));
}
// =============================================================================
// ListenError Trait Implementation Tests
// =============================================================================
/// Test: Trait test: listen error timeout display
///
/// Verifies: Verifies listen error timeout display
/// Category: trait
#[test]
fn test_listen_error_timeout_display() {
let err = ListenError::Timeout;
let display_str = format!("{}", err);
assert_eq!(
display_str,
"timeout elapsed while waiting for announcement"
);
}
/// Test: Trait test: listen error closed display
///
/// Verifies: Verifies listen error closed display
/// Category: trait
#[test]
fn test_listen_error_closed_display() {
let err = ListenError::Closed;
let display_str = format!("{}", err);
assert_eq!(display_str, "announcer was dropped without announcing");
}
/// Test: Trait test: listen error timeout to string
///
/// Verifies: Verifies listen error timeout to string
/// Category: trait
#[test]
fn test_listen_error_timeout_to_string() {
let err = ListenError::Timeout;
assert_eq!(
err.to_string(),
"timeout elapsed while waiting for announcement"
);
}
/// Test: Trait test: listen error closed to string
///
/// Verifies: Verifies listen error closed to string
/// Category: trait
#[test]
fn test_listen_error_closed_to_string() {
let err = ListenError::Closed;
assert_eq!(err.to_string(), "announcer was dropped without announcing");
}
/// Test: Trait test: listen error std error trait
///
/// Verifies: Verifies listen error std error trait
/// Category: trait
#[test]
fn test_listen_error_std_error_trait() {
let err = ListenError::Timeout;
// Verify it implements std::error::Error by using it as a trait object
let _err_ref: &dyn Error = &err;
// Verify source() returns None (default implementation)
assert!(err.source().is_none());
}
/// Test: Trait test: listen error debug
///
/// Verifies: Verifies listen error debug
/// Category: trait
#[test]
fn test_listen_error_debug() {
let timeout_err = ListenError::Timeout;
let closed_err = ListenError::Closed;
let timeout_debug = format!("{:?}", timeout_err);
let closed_debug = format!("{:?}", closed_err);
assert!(timeout_debug.contains("Timeout"));
assert!(closed_debug.contains("Closed"));
}
// =============================================================================
// Clone and PartialEq Tests for Error Types
// =============================================================================
/// Test: Trait test: announce error clone
///
/// Verifies: Verifies announce error clone
/// Category: trait
#[test]
fn test_announce_error_clone() {
let err1 = AnnounceError::AlreadyAnnounced(42);
let err2 = err1.clone();
assert_eq!(err1, err2);
}
/// Test: Trait test: announce error partial eq
///
/// Verifies: Verifies announce error partial eq
/// Category: trait
#[test]
fn test_announce_error_partial_eq() {
let err1 = AnnounceError::AlreadyAnnounced(42);
let err2 = AnnounceError::AlreadyAnnounced(42);
let err3 = AnnounceError::AlreadyAnnounced(99);
assert_eq!(err1, err2);
assert_ne!(err1, err3);
}
/// Test: Trait test: listen error clone
///
/// Verifies: Verifies listen error clone
/// Category: trait
#[test]
fn test_listen_error_clone() {
let err1 = ListenError::Timeout;
let err2 = err1; // Copy trait is used here, not Clone
assert_eq!(err1, err2);
}
/// Test: Trait test: listen error partial eq
///
/// Verifies: Verifies listen error partial eq
/// Category: trait
#[test]
fn test_listen_error_partial_eq() {
let timeout1 = ListenError::Timeout;
let timeout2 = ListenError::Timeout;
let closed = ListenError::Closed;
assert_eq!(timeout1, timeout2);
assert_ne!(timeout1, closed);
}
// =============================================================================
// Error Usage in Result Context Tests
// =============================================================================
/// Test: Trait test: announce error in result
///
/// Verifies: Verifies announce error in result
/// Category: trait
#[test]
fn test_announce_error_in_result() {
/// Test: Trait test: returns error
///
/// Verifies: Verifies returns error
/// Category: trait
fn returns_error() -> Result<(), AnnounceError<i32>> {
Err(AnnounceError::AlreadyAnnounced(42))
}
let result = returns_error();
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(format!("{}", err), "a value has already been announced");
}
/// Test: Trait test: listen error in result
///
/// Verifies: Verifies listen error in result
/// Category: trait
#[test]
fn test_listen_error_in_result() {
/// Test: Trait test: returns timeout
///
/// Verifies: Verifies returns timeout
/// Category: trait
fn returns_timeout() -> Result<i32, ListenError> {
Err(ListenError::Timeout)
}
/// Test: Trait test: returns closed
///
/// Verifies: Verifies returns closed
/// Category: trait
fn returns_closed() -> Result<i32, ListenError> {
Err(ListenError::Closed)
}
let timeout_result = returns_timeout();
assert!(timeout_result.is_err());
assert_eq!(
format!("{}", timeout_result.unwrap_err()),
"timeout elapsed while waiting for announcement"
);
let closed_result = returns_closed();
assert!(closed_result.is_err());
assert_eq!(
format!("{}", closed_result.unwrap_err()),
"announcer was dropped without announcing"
);
}
// =============================================================================
// Real-World Error Scenario Tests
// =============================================================================
/// Test: Trait test: listen error closed from dropped announcer
///
/// Verifies: Verifies listen error closed from dropped announcer
/// Category: trait
#[tokio::test]
async fn test_listen_error_closed_from_dropped_announcer() {
let (announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// Drop announcer to trigger Closed error
drop(announcer);
let result = listener.listen().await;
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err, ListenError::Closed);
assert_eq!(
format!("{}", err),
"announcer was dropped without announcing"
);
// Verify Error trait methods
assert!(err.source().is_none());
}
/// Test: Trait test: listen error timeout from timeout blocking
///
/// Verifies: Verifies listen error timeout from timeout blocking
/// Category: trait
#[cfg(feature = "std")]
#[test]
fn test_listen_error_timeout_from_timeout_blocking() {
use std::time::Duration;
let (_announcer, announcement) = Announcement::<i32>::new();
let listener = announcement.listener();
// Try to listen with a very short timeout (will timeout)
let result = listener.listen_timeout_blocking(Duration::from_millis(1));
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err, ListenError::Timeout);
assert_eq!(
format!("{}", err),
"timeout elapsed while waiting for announcement"
);
// Verify Error trait methods
assert!(err.source().is_none());
}
/// Test: Trait test: announce error into inner preserves value
///
/// Verifies: Verifies announce error into inner preserves value
/// Category: trait
#[test]
fn test_announce_error_into_inner_preserves_value() {
let original_value = vec![1, 2, 3, 4, 5];
let err = AnnounceError::AlreadyAnnounced(original_value.clone());
let recovered_value = err.into_inner();
assert_eq!(recovered_value, original_value);
}
+2122
View File
File diff suppressed because it is too large. Load diff