2142 lines
58 KiB
Rust
2142 lines
58 KiB
Rust
//! Extreme edge case tests for the announcement crate.
|
|
//! Testing 100+ edge cases that could break the implementation.
|
|
|
|
use announcement::{AnnounceError, Announcement, Listener};
|
|
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
|
use std::mem;
|
|
use std::sync::{Arc, Mutex, RwLock};
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
|
|
/// Test: Edge case: Announce at the exact moment of listener creation
|
|
///
|
|
/// Verifies: Handles edge case: announce at the exact moment of listener creation
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_announce_during_listener_creation() {
|
|
for _ in 0..1000 {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let announcement_clone = announcement.clone();
|
|
|
|
let handle = thread::spawn(move || {
|
|
announcer.announce(42).unwrap();
|
|
});
|
|
|
|
let listener = announcement_clone.listener();
|
|
handle.join().unwrap();
|
|
|
|
// Should always receive the value
|
|
assert!(listener.try_listen().is_some());
|
|
}
|
|
}
|
|
|
|
/// Test: Edge case: Rapid creation and dropping of listeners
|
|
///
|
|
/// Verifies: Handles edge case: rapid creation and dropping of listeners
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_rapid_listener_churn() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
|
|
// Create and drop 10000 listeners rapidly
|
|
for _ in 0..10000 {
|
|
let _ = announcement.listener();
|
|
}
|
|
|
|
let final_listener = announcement.listener();
|
|
announcer.announce(999).unwrap();
|
|
assert_eq!(final_listener.try_listen(), Some(999));
|
|
}
|
|
|
|
/// Test: Edge case: Announcer dropped without announcing
|
|
///
|
|
/// Verifies: Handles edge case: announcer dropped without announcing
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_announcer_dropped_without_announce() {
|
|
let (announcer, announcement) = Announcement::<i32>::new();
|
|
let listener = announcement.listener();
|
|
|
|
drop(announcer);
|
|
|
|
// Listener should never receive a value
|
|
assert_eq!(listener.try_listen(), None);
|
|
}
|
|
|
|
/// Test: Edge case: All listeners dropped before announcing
|
|
///
|
|
/// Verifies: Handles edge case: all listeners dropped before announcing
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_all_listeners_dropped_before_announce() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
|
|
{
|
|
let _l1 = announcement.listener();
|
|
let _l2 = announcement.listener();
|
|
let _l3 = announcement.listener();
|
|
// All listeners dropped here
|
|
}
|
|
|
|
// Should still succeed
|
|
announcer.announce(42).unwrap();
|
|
|
|
// New listener should get the value
|
|
let new_listener = announcement.listener();
|
|
assert_eq!(new_listener.try_listen(), Some(42));
|
|
}
|
|
|
|
/// Test: Edge case: Zero-sized types
|
|
///
|
|
/// Verifies: Handles edge case: zero-sized types
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_zero_sized_type() {
|
|
let (announcer, announcement) = Announcement::<()>::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(()));
|
|
}
|
|
|
|
/// Test: Edge case: Very large types (1MB+)
|
|
///
|
|
/// Verifies: Handles edge case: very large types (1mb+)
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_very_large_type() {
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
struct LargeType {
|
|
data: Vec<u8>,
|
|
}
|
|
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let large = LargeType {
|
|
data: vec![0u8; 1024 * 1024], // 1MB
|
|
};
|
|
|
|
announcer.announce(large.clone()).unwrap();
|
|
|
|
let received = listener.try_listen().unwrap();
|
|
assert_eq!(received.data.len(), 1024 * 1024);
|
|
}
|
|
|
|
/// Test: Edge case: Recursive types with Arc
|
|
///
|
|
/// Verifies: Handles edge case: recursive types with arc
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_recursive_type_with_arc() {
|
|
#[derive(Clone, Debug)]
|
|
struct Node {
|
|
value: i32,
|
|
next: Option<Arc<Node>>,
|
|
}
|
|
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let node = Arc::new(Node {
|
|
value: 42,
|
|
next: Some(Arc::new(Node {
|
|
value: 43,
|
|
next: None,
|
|
})),
|
|
});
|
|
|
|
announcer.announce(node.clone()).unwrap();
|
|
|
|
let received = listener.try_listen().unwrap();
|
|
assert_eq!(received.value, 42);
|
|
assert_eq!(received.next.as_ref().unwrap().value, 43);
|
|
}
|
|
|
|
/// Test: Edge case: Empty announcements (unit type)
|
|
///
|
|
/// Verifies: Handles edge case: empty announcements (unit type)
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_empty_announcement() {
|
|
let (announcer, announcement) = Announcement::<()>::new();
|
|
let listeners: Vec<_> = (0..100).map(|_| announcement.listener()).collect();
|
|
|
|
announcer.announce(()).unwrap();
|
|
|
|
for listener in &listeners {
|
|
assert_eq!(listener.try_listen(), Some(()));
|
|
}
|
|
}
|
|
|
|
/// Test: Edge case: Nested announcement channels - using Arc to share announcer
|
|
///
|
|
/// Verifies: Handles edge case: nested announcement channels - using arc to share announcer
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_nested_announcements() {
|
|
let (outer_announcer, outer) = Announcement::<i32>::new();
|
|
let (_inner_announcer, inner) = Announcement::<i32>::new();
|
|
|
|
let outer_listener = outer.listener();
|
|
let _inner_listener: announcement::Listener<i32> = inner.listener();
|
|
|
|
// Announce a simple value instead of trying to pass the announcer
|
|
outer_announcer.announce(42).unwrap();
|
|
|
|
let value = outer_listener.try_listen().unwrap();
|
|
|
|
// Create a new announcer for the inner channel
|
|
let (inner_announcer2, inner2) = Announcement::<i32>::new();
|
|
let inner_listener2 = inner2.listener();
|
|
|
|
inner_announcer2.announce(value).unwrap();
|
|
assert_eq!(inner_listener2.try_listen(), Some(42));
|
|
}
|
|
|
|
/// Test: Edge case: Announcement with HashMap
|
|
///
|
|
/// Verifies: Handles edge case: announcement with hashmap
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_announcement_with_hashmap() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let mut map = HashMap::new();
|
|
map.insert("key1", vec![1, 2, 3]);
|
|
map.insert("key2", vec![4, 5, 6]);
|
|
|
|
announcer.announce(map.clone()).unwrap();
|
|
|
|
let received = listener.try_listen().unwrap();
|
|
assert_eq!(received["key1"], vec![1, 2, 3]);
|
|
assert_eq!(received["key2"], vec![4, 5, 6]);
|
|
}
|
|
|
|
/// Test: Edge case: Announcement with HashSet
|
|
///
|
|
/// Verifies: Handles edge case: announcement with hashset
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_announcement_with_hashset() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let mut set = HashSet::new();
|
|
set.insert(1);
|
|
set.insert(2);
|
|
set.insert(3);
|
|
|
|
announcer.announce(set.clone()).unwrap();
|
|
|
|
let received = listener.try_listen().unwrap();
|
|
assert_eq!(received.len(), 3);
|
|
assert!(received.contains(&1));
|
|
}
|
|
|
|
/// Test: Edge case: Announcement with BTreeMap
|
|
///
|
|
/// Verifies: Handles edge case: announcement with btreemap
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_announcement_with_btreemap() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let mut map = BTreeMap::new();
|
|
map.insert(3, "three");
|
|
map.insert(1, "one");
|
|
map.insert(2, "two");
|
|
|
|
announcer.announce(map.clone()).unwrap();
|
|
|
|
let received = listener.try_listen().unwrap();
|
|
assert_eq!(received[&1], "one");
|
|
}
|
|
|
|
/// Test: Edge case: Announcement with BTreeSet
|
|
///
|
|
/// Verifies: Handles edge case: announcement with btreeset
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_announcement_with_btreeset() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let mut set = BTreeSet::new();
|
|
set.insert("apple");
|
|
set.insert("banana");
|
|
set.insert("cherry");
|
|
|
|
announcer.announce(set.clone()).unwrap();
|
|
|
|
let received = listener.try_listen().unwrap();
|
|
assert_eq!(received.len(), 3);
|
|
}
|
|
|
|
/// Test: Edge case: Option<T> types
|
|
///
|
|
/// Verifies: Handles edge case: option<t> types
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_option_none() {
|
|
let (announcer, announcement) = Announcement::<Option<i32>>::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(None).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(None));
|
|
}
|
|
|
|
/// Test: Option type: option some
|
|
///
|
|
/// Verifies: Handles Option::Some values
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_option_some() {
|
|
let (announcer, announcement) = Announcement::<Option<String>>::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(Some("Hello".to_string())).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(Some("Hello".to_string())));
|
|
}
|
|
|
|
/// Test: Edge case: Result<T, E> types
|
|
///
|
|
/// Verifies: Handles edge case: result<t, e> types
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_result_ok() {
|
|
let (announcer, announcement) = Announcement::<Result<i32, String>>::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(Ok(42)).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(Ok(42)));
|
|
}
|
|
|
|
/// Test: Result type: result err
|
|
///
|
|
/// Verifies: Handles Result::Err values
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_result_err() {
|
|
let (announcer, announcement) = Announcement::<Result<i32, String>>::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(Err("Error".to_string())).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(Err("Error".to_string())));
|
|
}
|
|
|
|
/// Test: Edge case: Box<T> types
|
|
///
|
|
/// Verifies: Handles edge case: box<t> types
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_boxed_type() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let boxed: Box<Vec<i32>> = Box::new(vec![1, 2, 3, 4, 5]);
|
|
|
|
announcer.announce(boxed.clone()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(boxed));
|
|
}
|
|
|
|
/// Test: Edge case: Arc with interior mutability
|
|
///
|
|
/// Verifies: Handles edge case: arc with interior mutability
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_arc_mutex() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let data = Arc::new(Mutex::new(42));
|
|
|
|
announcer.announce(data.clone()).unwrap();
|
|
|
|
let received = listener.try_listen().unwrap();
|
|
assert_eq!(*received.lock().unwrap(), 42);
|
|
}
|
|
|
|
/// Test: Interior mutability: Arc<Rwlock<T>>
|
|
///
|
|
/// Verifies: Handles Arc-wrapped rwlock correctly
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_arc_rwlock() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let data = Arc::new(RwLock::new(vec![1, 2, 3]));
|
|
|
|
announcer.announce(data.clone()).unwrap();
|
|
|
|
let received = listener.try_listen().unwrap();
|
|
assert_eq!(*received.read().unwrap(), vec![1, 2, 3]);
|
|
}
|
|
|
|
/// Test: Edge case: Empty strings and vectors
|
|
///
|
|
/// Verifies: Handles edge case: empty strings and vectors
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_empty_string() {
|
|
let (announcer, announcement) = Announcement::<String>::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(String::new()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(String::new()));
|
|
}
|
|
|
|
/// Test: Empty collection: empty vec
|
|
///
|
|
/// Verifies: Handles empty vec correctly
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_empty_vec() {
|
|
let (announcer, announcement) = Announcement::<Vec<i32>>::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(Vec::new()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(Vec::new()));
|
|
}
|
|
|
|
/// Test: Edge case: Very long strings
|
|
///
|
|
/// Verifies: Handles edge case: very long strings
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_very_long_string() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let long_string = "x".repeat(10_000_000); // 10MB string
|
|
|
|
announcer.announce(long_string.clone()).unwrap();
|
|
|
|
let received = listener.try_listen().unwrap();
|
|
assert_eq!(received.len(), 10_000_000);
|
|
}
|
|
|
|
/// Test: Edge case: Arrays of different sizes
|
|
///
|
|
/// Verifies: Handles edge case: arrays of different sizes
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_zero_sized_array() {
|
|
let (announcer, announcement) = Announcement::<[u8; 0]>::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce([]).unwrap();
|
|
assert_eq!(listener.try_listen(), Some([]));
|
|
}
|
|
|
|
/// Test: Array: small fixed-size array
|
|
///
|
|
/// Verifies: Handles small fixed-size arrays
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_small_array() {
|
|
let (announcer, announcement) = Announcement::<[i32; 3]>::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce([1, 2, 3]).unwrap();
|
|
assert_eq!(listener.try_listen(), Some([1, 2, 3]));
|
|
}
|
|
|
|
/// Test: Array: large fixed-size array (1KB)
|
|
///
|
|
/// Verifies: Handles large fixed-size arrays
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_large_array() {
|
|
let (announcer, announcement) = Announcement::<[u8; 1024]>::new();
|
|
let listener = announcement.listener();
|
|
|
|
let array = [42u8; 1024];
|
|
announcer.announce(array).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(array));
|
|
}
|
|
|
|
/// Test: Edge case: Tuples
|
|
///
|
|
/// Verifies: Handles edge case: tuples
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_simple_tuple() {
|
|
let (announcer, announcement) = Announcement::<(i32, i32)>::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce((1, 2)).unwrap();
|
|
assert_eq!(listener.try_listen(), Some((1, 2)));
|
|
}
|
|
|
|
/// Test: Tuple: complex tuple
|
|
///
|
|
/// Verifies: Handles complex tuple correctly
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_complex_tuple() {
|
|
let (announcer, announcement) = Announcement::<(String, Vec<u8>, i32, Option<bool>)>::new();
|
|
let listener = announcement.listener();
|
|
|
|
let tuple = ("hello".to_string(), vec![1, 2, 3], 42, Some(true));
|
|
announcer.announce(tuple.clone()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(tuple));
|
|
}
|
|
|
|
/// Test: Edge case: Custom struct with Drop
|
|
///
|
|
/// Verifies: Handles edge case: custom struct with drop
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_struct_with_drop() {
|
|
static DROP_COUNT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
|
|
|
|
#[derive(Clone, Debug)]
|
|
#[allow(dead_code)]
|
|
struct DropCounter {
|
|
value: i32,
|
|
}
|
|
|
|
impl Drop for DropCounter {
|
|
fn drop(&mut self) {
|
|
DROP_COUNT.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
|
}
|
|
}
|
|
|
|
DROP_COUNT.store(0, std::sync::atomic::Ordering::SeqCst);
|
|
|
|
{
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let counter = DropCounter { value: 42 };
|
|
announcer.announce(counter).unwrap();
|
|
|
|
let _received = listener.try_listen();
|
|
}
|
|
|
|
// Check that drops happened
|
|
assert!(DROP_COUNT.load(std::sync::atomic::Ordering::SeqCst) > 0);
|
|
}
|
|
|
|
/// Test: Edge case: Enums
|
|
///
|
|
/// Verifies: Handles edge case: enums
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_simple_enum() {
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
#[allow(dead_code)]
|
|
enum SimpleEnum {
|
|
A,
|
|
B,
|
|
C,
|
|
}
|
|
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(SimpleEnum::B).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(SimpleEnum::B));
|
|
}
|
|
|
|
/// Test: Enum: complex enum
|
|
///
|
|
/// Verifies: Handles complex enum with variants correctly
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_complex_enum() {
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
#[allow(dead_code)]
|
|
enum ComplexEnum {
|
|
Empty,
|
|
Value(i32),
|
|
Tuple(String, Vec<u8>),
|
|
Struct { x: f64, y: f64 },
|
|
}
|
|
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let val = ComplexEnum::Tuple("test".to_string(), vec![1, 2, 3]);
|
|
announcer.announce(val.clone()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(val));
|
|
}
|
|
|
|
/// Test: Edge case: PhantomData
|
|
///
|
|
/// Verifies: Handles edge case: phantomdata
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_phantom_data() {
|
|
use std::marker::PhantomData;
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct Phantom<T> {
|
|
value: i32,
|
|
_phantom: PhantomData<T>,
|
|
}
|
|
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let phantom: Phantom<String> = Phantom {
|
|
value: 42,
|
|
_phantom: PhantomData,
|
|
};
|
|
|
|
announcer.announce(phantom).unwrap();
|
|
|
|
let received = listener.try_listen().unwrap();
|
|
assert_eq!(received.value, 42);
|
|
}
|
|
|
|
/// Test: Edge case: Function pointers
|
|
///
|
|
/// Verifies: Handles edge case: function pointers
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_function_pointer() {
|
|
fn example_fn() -> i32 {
|
|
42
|
|
}
|
|
|
|
let (announcer, announcement) = Announcement::<fn() -> i32>::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(example_fn).unwrap();
|
|
|
|
let received = listener.try_listen().unwrap();
|
|
assert_eq!(received(), 42);
|
|
}
|
|
|
|
/// Test: Edge case: Trait objects
|
|
///
|
|
/// Verifies: Handles edge case: trait objects
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_trait_object() {
|
|
trait MyTrait: Send + Sync {
|
|
fn value(&self) -> i32;
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct MyStruct(i32);
|
|
|
|
impl MyTrait for MyStruct {
|
|
fn value(&self) -> i32 {
|
|
self.0
|
|
}
|
|
}
|
|
|
|
let (announcer, announcement) = Announcement::<Arc<dyn MyTrait>>::new();
|
|
let listener = announcement.listener();
|
|
|
|
let obj: Arc<dyn MyTrait> = Arc::new(MyStruct(42));
|
|
let _ = announcer.announce(obj); // Can't use unwrap because dyn MyTrait doesn't implement Debug
|
|
|
|
let received = listener.try_listen().unwrap();
|
|
assert_eq!(received.value(), 42);
|
|
}
|
|
|
|
/// Test: Edge case: Deeply nested structures
|
|
///
|
|
/// Verifies: Handles edge case: deeply nested structures
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_deeply_nested_structure() {
|
|
#[derive(Clone, Debug)]
|
|
struct Level1(Level2);
|
|
#[derive(Clone, Debug)]
|
|
struct Level2(Level3);
|
|
#[derive(Clone, Debug)]
|
|
struct Level3(Level4);
|
|
#[derive(Clone, Debug)]
|
|
struct Level4(Level5);
|
|
#[derive(Clone, Debug)]
|
|
struct Level5(Level6);
|
|
#[derive(Clone, Debug)]
|
|
struct Level6(Level7);
|
|
#[derive(Clone, Debug)]
|
|
struct Level7(Level8);
|
|
#[derive(Clone, Debug)]
|
|
struct Level8(Level9);
|
|
#[derive(Clone, Debug)]
|
|
struct Level9(Level10);
|
|
#[derive(Clone, Debug)]
|
|
struct Level10(i32);
|
|
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let nested = Level1(Level2(Level3(Level4(Level5(Level6(Level7(Level8(
|
|
Level9(Level10(42)),
|
|
))))))));
|
|
|
|
announcer.announce(nested).unwrap();
|
|
|
|
let received = listener.try_listen().unwrap();
|
|
assert_eq!(received.0 .0 .0 .0 .0 .0 .0 .0 .0 .0, 42);
|
|
}
|
|
|
|
/// Test: Edge case: Listener created after drop of announcer
|
|
///
|
|
/// Verifies: Handles edge case: listener created after drop of announcer
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_listener_after_announcer_drop() {
|
|
let announcement = {
|
|
let (announcer, announcement) = Announcement::<i32>::new();
|
|
drop(announcer);
|
|
announcement
|
|
};
|
|
|
|
let listener = announcement.listener();
|
|
assert_eq!(listener.try_listen(), None);
|
|
}
|
|
|
|
/// Test: Edge case: Clone announcement after announce
|
|
///
|
|
/// Verifies: Handles edge case: clone announcement after announce
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_clone_announcement_after_announce() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
|
|
announcer.announce(42).unwrap();
|
|
|
|
let cloned = announcement.clone();
|
|
let listener = cloned.listener();
|
|
|
|
assert_eq!(listener.try_listen(), Some(42));
|
|
}
|
|
|
|
/// Test: Edge case: Many threads creating listeners simultaneously
|
|
///
|
|
/// Verifies: Handles edge case: many threads creating listeners simultaneously
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_concurrent_listener_creation() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let mut handles = vec![];
|
|
|
|
for _ in 0..100 {
|
|
let announcement_clone = announcement.clone();
|
|
handles.push(thread::spawn(move || announcement_clone.listener()));
|
|
}
|
|
|
|
let listeners: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
|
|
|
|
announcer.announce(42).unwrap();
|
|
|
|
for listener in &listeners {
|
|
assert_eq!(listener.try_listen(), Some(42));
|
|
}
|
|
}
|
|
|
|
/// Test: Edge case: Maximum value sizes
|
|
///
|
|
/// Verifies: Handles edge case: maximum value sizes
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_max_u128() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(u128::MAX).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(u128::MAX));
|
|
}
|
|
|
|
/// Test: Numeric boundary: i128::MIN
|
|
///
|
|
/// Verifies: Handles minimum signed 128-bit integer value
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_min_i128() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(i128::MIN).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(i128::MIN));
|
|
}
|
|
|
|
/// Test: Edge case: NaN and infinity for floats
|
|
///
|
|
/// Verifies: Handles edge case: nan and infinity for floats
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_nan_f64() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(f64::NAN).unwrap();
|
|
|
|
let received = listener.try_listen().unwrap();
|
|
assert!(received.is_nan());
|
|
}
|
|
|
|
/// Test: Special value: floating point infinity
|
|
///
|
|
/// Verifies: Handles positive/negative infinity correctly
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_infinity_f32() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(f32::INFINITY).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(f32::INFINITY));
|
|
}
|
|
|
|
/// Test: Special value: floating point infinity
|
|
///
|
|
/// Verifies: Handles positive/negative infinity correctly
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_neg_infinity_f64() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(f64::NEG_INFINITY).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(f64::NEG_INFINITY));
|
|
}
|
|
|
|
/// Test: Edge case: Unicode strings
|
|
///
|
|
/// Verifies: Handles edge case: unicode strings
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_unicode_emoji() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let emoji_string = "Hello 👋 World 🌍 🦀".to_string();
|
|
announcer.announce(emoji_string.clone()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(emoji_string));
|
|
}
|
|
|
|
/// Test: Unicode: unicode rtl
|
|
///
|
|
/// Verifies: Handles unicode rtl correctly
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_unicode_rtl() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let rtl_string = "مرحبا بالعالم".to_string();
|
|
announcer.announce(rtl_string.clone()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(rtl_string));
|
|
}
|
|
|
|
/// Test: Unicode: unicode combining
|
|
///
|
|
/// Verifies: Handles unicode combining correctly
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_unicode_combining() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let combining = "e\u{0301}".to_string(); // é as e + combining accent
|
|
announcer.announce(combining.clone()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(combining));
|
|
}
|
|
|
|
/// Test: Edge case: Memory size checks
|
|
///
|
|
/// Verifies: Handles edge case: memory size checks
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_announcement_memory_size() {
|
|
let size = mem::size_of::<Announcement<i32>>();
|
|
|
|
// Should be relatively small (just Arc pointers)
|
|
// Two Arc pointers on 64-bit: inner + event
|
|
assert!(size <= 16);
|
|
}
|
|
|
|
/// Test: Memory efficiency: Listener<T> size
|
|
///
|
|
/// Verifies: Verifies Listener type has minimal memory footprint
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_listener_memory_size() {
|
|
let size = mem::size_of::<Listener<i32>>();
|
|
|
|
// Should be same as Announcement
|
|
// Two Arc pointers on 64-bit: inner + event
|
|
assert!(size <= 16);
|
|
}
|
|
|
|
/// Test: Edge case: Listener dropped immediately after creation
|
|
///
|
|
/// Verifies: Handles edge case: listener dropped immediately after creation
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_listener_immediate_drop() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
|
|
for _ in 0..1000 {
|
|
drop(announcement.listener());
|
|
}
|
|
|
|
announcer.announce(42).unwrap();
|
|
|
|
// Should still work with new listener
|
|
let listener = announcement.listener();
|
|
assert_eq!(listener.try_listen(), Some(42));
|
|
}
|
|
|
|
/// Test: Edge case: Announcement with closure
|
|
///
|
|
/// Verifies: Handles edge case: announcement with closure
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_announcement_with_closure() {
|
|
let (announcer, announcement) = Announcement::<Arc<dyn Fn() -> i32 + Send + Sync>>::new();
|
|
let listener = announcement.listener();
|
|
|
|
let closure = Arc::new(|| 42) as Arc<dyn Fn() -> i32 + Send + Sync>;
|
|
let _ = announcer.announce(closure); // Can't use unwrap because dyn Fn doesn't implement Debug
|
|
|
|
let received = listener.try_listen().unwrap();
|
|
assert_eq!(received(), 42);
|
|
}
|
|
|
|
/// Test: Edge case: Announcement channel dropped before listeners
|
|
///
|
|
/// Verifies: Handles edge case: announcement channel dropped before listeners
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_announcement_dropped_before_listeners() {
|
|
let listener = {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(42).unwrap();
|
|
drop(announcement);
|
|
|
|
listener
|
|
};
|
|
|
|
assert_eq!(listener.try_listen(), Some(42));
|
|
}
|
|
|
|
/// Test: Edge case: Multiple announcement channels at once
|
|
///
|
|
/// Verifies: Handles edge case: multiple announcement channels at once
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_multiple_channels() {
|
|
let (a1, ann1) = Announcement::<i32>::new();
|
|
let (a2, ann2) = Announcement::<String>::new();
|
|
let (a3, ann3) = Announcement::<Vec<u8>>::new();
|
|
|
|
let l1 = ann1.listener();
|
|
let l2 = ann2.listener();
|
|
let l3 = ann3.listener();
|
|
|
|
a1.announce(42).unwrap();
|
|
a2.announce("hello".to_string()).unwrap();
|
|
a3.announce(vec![1, 2, 3]).unwrap();
|
|
|
|
assert_eq!(l1.try_listen(), Some(42));
|
|
assert_eq!(l2.try_listen(), Some("hello".to_string()));
|
|
assert_eq!(l3.try_listen(), Some(vec![1, 2, 3]));
|
|
}
|
|
|
|
/// Test: Edge case: Char boundary cases
|
|
///
|
|
/// Verifies: Handles edge case: char boundary cases
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_char_boundaries() {
|
|
let (announcer, announcement) = Announcement::<char>::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce('\0').unwrap();
|
|
assert_eq!(listener.try_listen(), Some('\0'));
|
|
|
|
let (announcer2, announcement2) = Announcement::<char>::new();
|
|
let listener2 = announcement2.listener();
|
|
|
|
announcer2.announce('\u{10FFFF}').unwrap();
|
|
assert_eq!(listener2.try_listen(), Some('\u{10FFFF}'));
|
|
}
|
|
|
|
/// Test: Edge case: Bool values
|
|
///
|
|
/// Verifies: Handles edge case: bool values
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_bool_true() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(true).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(true));
|
|
}
|
|
|
|
/// Test: Boolean: bool false
|
|
///
|
|
/// Verifies: Handles boolean false value
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_bool_false() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(false).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(false));
|
|
}
|
|
|
|
/// Test: Edge case: Nested Options
|
|
///
|
|
/// Verifies: Handles edge case: nested options
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_nested_options() {
|
|
type NestedOption = Option<Option<Option<i32>>>;
|
|
|
|
let (announcer, announcement) = Announcement::<NestedOption>::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(Some(Some(Some(42)))).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(Some(Some(Some(42)))));
|
|
}
|
|
|
|
/// Test: Edge case: Nested Results
|
|
///
|
|
/// Verifies: Handles edge case: nested results
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_nested_results() {
|
|
type NestedResult = Result<Result<i32, String>, Vec<u8>>;
|
|
|
|
let (announcer, announcement) = Announcement::<NestedResult>::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(Ok(Ok(42))).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(Ok(Ok(42))));
|
|
}
|
|
|
|
/// Test: Edge case: Empty collections
|
|
///
|
|
/// Verifies: Handles edge case: empty collections
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_empty_hashmap() {
|
|
let (announcer, announcement) = Announcement::<HashMap<String, i32>>::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(HashMap::new()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(HashMap::new()));
|
|
}
|
|
|
|
/// Test: Empty collection: empty btreemap
|
|
///
|
|
/// Verifies: Handles empty btreemap correctly
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_empty_btreemap() {
|
|
let (announcer, announcement) = Announcement::<BTreeMap<i32, String>>::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(BTreeMap::new()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(BTreeMap::new()));
|
|
}
|
|
|
|
/// Test: Edge case: Single element collections
|
|
///
|
|
/// Verifies: Handles edge case: single element collections
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_single_element_vec() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(vec![42]).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(vec![42]));
|
|
}
|
|
|
|
/// Test: Collection boundary: single element collections
|
|
///
|
|
/// Verifies: Handles collections with exactly one element
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_single_element_hashset() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let mut set = HashSet::new();
|
|
set.insert("only");
|
|
|
|
announcer.announce(set.clone()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(set));
|
|
}
|
|
|
|
/// Test: Edge case: Reference counting edge cases
|
|
///
|
|
/// Verifies: Handles edge case: reference counting edge cases
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_arc_refcount_after_announce() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
|
|
let data = Arc::new(42);
|
|
let initial_count = Arc::strong_count(&data);
|
|
|
|
let l1 = announcement.listener();
|
|
let l2 = announcement.listener();
|
|
|
|
announcer.announce(data.clone()).unwrap();
|
|
|
|
let r1 = l1.try_listen().unwrap();
|
|
let _r2 = l2.try_listen().unwrap();
|
|
|
|
// Should be: original + internal + r1 + r2
|
|
assert_eq!(Arc::strong_count(&r1), initial_count + 3);
|
|
}
|
|
|
|
/// Test: Edge case: Path-like strings
|
|
///
|
|
/// Verifies: Handles edge case: path-like strings
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_path_strings() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let path = "/usr/local/bin/../../lib".to_string();
|
|
announcer.announce(path.clone()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(path));
|
|
}
|
|
|
|
/// Test: Edge case: SQL injection attempt strings
|
|
///
|
|
/// Verifies: Handles edge case: sql injection attempt strings
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_sql_injection_string() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let sql = "'; DROP TABLE users; --".to_string();
|
|
announcer.announce(sql.clone()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(sql));
|
|
}
|
|
|
|
/// Test: Edge case: Listener cloned multiple times
|
|
///
|
|
/// Verifies: Handles edge case: listener cloned multiple times
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_listener_clone_chain() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let l1 = announcement.listener();
|
|
let l2 = l1.clone();
|
|
let l3 = l2.clone();
|
|
let l4 = l3.clone();
|
|
|
|
announcer.announce(42).unwrap();
|
|
|
|
assert_eq!(l1.try_listen(), Some(42));
|
|
assert_eq!(l2.try_listen(), Some(42));
|
|
assert_eq!(l3.try_listen(), Some(42));
|
|
assert_eq!(l4.try_listen(), Some(42));
|
|
}
|
|
|
|
/// Test: Edge case: Announcement cloned multiple times
|
|
///
|
|
/// Verifies: Handles edge case: announcement cloned multiple times
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_announcement_clone_chain() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let a2 = announcement.clone();
|
|
let a3 = a2.clone();
|
|
let a4 = a3.clone();
|
|
|
|
let l1 = announcement.listener();
|
|
let l2 = a2.listener();
|
|
let l3 = a3.listener();
|
|
let l4 = a4.listener();
|
|
|
|
announcer.announce(42).unwrap();
|
|
|
|
assert_eq!(l1.try_listen(), Some(42));
|
|
assert_eq!(l2.try_listen(), Some(42));
|
|
assert_eq!(l3.try_listen(), Some(42));
|
|
assert_eq!(l4.try_listen(), Some(42));
|
|
}
|
|
|
|
/// Test: Edge case: Mixed listener creation timing
|
|
///
|
|
/// Verifies: Handles edge case: mixed listener creation timing
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_mixed_timing_listeners() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
|
|
let before1 = announcement.listener();
|
|
let before2 = announcement.listener();
|
|
|
|
announcer.announce(42).unwrap();
|
|
|
|
let after1 = announcement.listener();
|
|
let after2 = announcement.listener();
|
|
|
|
assert_eq!(before1.try_listen(), Some(42));
|
|
assert_eq!(before2.try_listen(), Some(42));
|
|
assert_eq!(after1.try_listen(), Some(42));
|
|
assert_eq!(after2.try_listen(), Some(42));
|
|
}
|
|
|
|
/// Test: Edge case: Thread-local storage interaction
|
|
///
|
|
/// Verifies: Handles edge case: thread-local storage interaction
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_thread_local_interaction() {
|
|
thread_local! {
|
|
static COUNTER: std::cell::Cell<i32> = const { std::cell::Cell::new(0) };
|
|
}
|
|
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
COUNTER.with(|c| c.set(100));
|
|
|
|
announcer.announce(42).unwrap();
|
|
|
|
assert_eq!(listener.try_listen(), Some(42));
|
|
assert_eq!(COUNTER.with(|c| c.get()), 100);
|
|
}
|
|
|
|
/// Test: Edge case: Panic safety - announcer panics before announce
|
|
///
|
|
/// Verifies: Handles edge case: panic safety - announcer panics before announce
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_panic_before_announce() {
|
|
let result = std::panic::catch_unwind(|| {
|
|
let (_announcer, announcement) = Announcement::<i32>::new();
|
|
let _listener = announcement.listener();
|
|
panic!("Intentional panic");
|
|
#[allow(unreachable_code)]
|
|
_announcer.announce(42).unwrap();
|
|
});
|
|
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
/// Test: Edge case: Maximum listeners on single announcement
|
|
///
|
|
/// Verifies: Handles edge case: maximum listeners on single announcement
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_max_listeners_single_announcement() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
|
|
let mut listeners = Vec::new();
|
|
for _ in 0..10000 {
|
|
listeners.push(announcement.listener());
|
|
}
|
|
|
|
announcer.announce(42).unwrap();
|
|
|
|
// Sample check
|
|
assert_eq!(listeners[0].try_listen(), Some(42));
|
|
assert_eq!(listeners[5000].try_listen(), Some(42));
|
|
assert_eq!(listeners[9999].try_listen(), Some(42));
|
|
}
|
|
|
|
/// Test: Edge case: Alternating operations
|
|
///
|
|
/// Verifies: Handles edge case: alternating operations
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_alternating_operations() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
|
|
let _l1 = announcement.listener();
|
|
assert!(!announcement.is_announced());
|
|
|
|
let _l2 = announcement.listener();
|
|
assert!(!announcement.is_announced());
|
|
|
|
let _l3 = announcement.listener();
|
|
assert!(!announcement.is_announced());
|
|
|
|
announcer.announce(42).unwrap();
|
|
assert!(announcement.is_announced());
|
|
|
|
let l4 = announcement.listener();
|
|
assert!(announcement.is_announced());
|
|
assert_eq!(l4.try_listen(), Some(42));
|
|
}
|
|
|
|
/// Test: Edge case: Error type validation
|
|
///
|
|
/// Verifies: Handles edge case: error type validation
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_error_types() {
|
|
let err = AnnounceError::AlreadyAnnounced(42);
|
|
assert_eq!(format!("{}", err), "a value has already been announced");
|
|
assert_eq!(format!("{:?}", err), "AlreadyAnnounced(42)");
|
|
}
|
|
|
|
/// Test: Edge case: Boundary timing test
|
|
///
|
|
/// Verifies: Handles edge case: boundary timing test
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_announce_exactly_with_try_listen() {
|
|
for _ in 0..100 {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let announcement2 = announcement.clone();
|
|
|
|
let handle = thread::spawn(move || announcement2.listener().try_listen());
|
|
|
|
// Try to announce at nearly the same time
|
|
announcer.announce(42).unwrap();
|
|
|
|
let result = handle.join().unwrap();
|
|
// Could be Some or None depending on exact timing
|
|
assert!(result.is_none() || result == Some(42));
|
|
}
|
|
}
|
|
|
|
/// Test: Edge case: String with null bytes
|
|
///
|
|
/// Verifies: Handles edge case: string with null bytes
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_string_with_nulls() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let s = "hello\0world\0test".to_string();
|
|
announcer.announce(s.clone()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(s));
|
|
}
|
|
|
|
/// Test: Edge case: Maximum tuple size
|
|
///
|
|
/// Verifies: Handles edge case: maximum tuple size
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_12_tuple() {
|
|
type BigTuple = (i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32);
|
|
|
|
let (announcer, announcement) = Announcement::<BigTuple>::new();
|
|
let listener = announcement.listener();
|
|
|
|
let tuple = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
|
|
announcer.announce(tuple).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(tuple));
|
|
}
|
|
|
|
/// Test: Edge case: Negative array indices simulation
|
|
///
|
|
/// Verifies: Handles edge case: negative array indices simulation
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_vec_with_negative_pattern() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
// Simulate negative indexing pattern
|
|
let vec = vec![-1, -2, -3, 0, 1, 2, 3];
|
|
announcer.announce(vec.clone()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(vec));
|
|
}
|
|
|
|
/// Test: Edge case: Floating point special values
|
|
///
|
|
/// Verifies: Handles edge case: floating point special values
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_float_special_values() {
|
|
let (announcer, announcement) = Announcement::<Vec<f64>>::new();
|
|
let listener = announcement.listener();
|
|
|
|
let specials = vec![
|
|
0.0,
|
|
-0.0,
|
|
f64::MIN_POSITIVE,
|
|
f64::MAX,
|
|
f64::MIN,
|
|
f64::EPSILON,
|
|
];
|
|
|
|
announcer.announce(specials.clone()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(specials));
|
|
}
|
|
|
|
/// Test: Edge case: UTF-8 boundary test
|
|
///
|
|
/// Verifies: Handles edge case: utf-8 boundary test
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_utf8_boundaries() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
// Various UTF-8 sequences
|
|
let s = "Aಇ👍🏿Z".to_string();
|
|
announcer.announce(s.clone()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(s));
|
|
}
|
|
|
|
/// Test: Edge case: Lifetime boundary with static
|
|
///
|
|
/// Verifies: Handles edge case: lifetime boundary with static
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_static_str() {
|
|
let (announcer, announcement) = Announcement::<&'static str>::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce("static string").unwrap();
|
|
assert_eq!(listener.try_listen(), Some("static string"));
|
|
}
|
|
|
|
/// Test: Edge case: Very fast repeated checks
|
|
///
|
|
/// Verifies: Handles edge case: very fast repeated checks
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_spin_wait_pattern() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let announcement_clone = announcement.clone();
|
|
thread::spawn(move || {
|
|
thread::sleep(Duration::from_millis(1));
|
|
announcer.announce(42).unwrap();
|
|
});
|
|
|
|
// Spin wait
|
|
while listener.try_listen().is_none() {
|
|
std::hint::spin_loop();
|
|
}
|
|
|
|
assert_eq!(listener.try_listen(), Some(42));
|
|
assert!(announcement_clone.is_announced());
|
|
}
|
|
|
|
/// Test: Edge case: Memory ordering stress
|
|
///
|
|
/// Verifies: Handles edge case: memory ordering stress
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_memory_ordering_stress() {
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
|
|
let (announcer, announcement) = Announcement::new();
|
|
let flag = Arc::new(AtomicBool::new(false));
|
|
|
|
let listener = announcement.listener();
|
|
let flag_clone = flag.clone();
|
|
|
|
thread::spawn(move || {
|
|
flag_clone.store(true, Ordering::Release);
|
|
announcer.announce(42).unwrap();
|
|
});
|
|
|
|
while !flag.load(Ordering::Acquire) {
|
|
thread::yield_now();
|
|
}
|
|
|
|
// Flag is set, announcement should be visible soon
|
|
thread::sleep(Duration::from_millis(1));
|
|
assert_eq!(listener.try_listen(), Some(42));
|
|
}
|
|
|
|
/// Test: Edge case: Drop order testing
|
|
///
|
|
/// Verifies: Handles edge case: drop order testing
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_drop_order() {
|
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
|
|
static DROP_ORDER: AtomicUsize = AtomicUsize::new(0);
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct DropOrderTest(usize);
|
|
|
|
impl Drop for DropOrderTest {
|
|
fn drop(&mut self) {
|
|
DROP_ORDER.fetch_add(self.0, Ordering::SeqCst);
|
|
}
|
|
}
|
|
|
|
DROP_ORDER.store(0, Ordering::SeqCst);
|
|
|
|
{
|
|
let (announcer, announcement) = Announcement::new();
|
|
let _listener = announcement.listener();
|
|
|
|
announcer.announce(DropOrderTest(1)).unwrap();
|
|
}
|
|
|
|
assert!(DROP_ORDER.load(Ordering::SeqCst) > 0);
|
|
}
|
|
|
|
/// Test: Edge case: Const array patterns
|
|
///
|
|
/// Verifies: Handles edge case: const array patterns
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_const_array() {
|
|
const ARRAY: [i32; 5] = [1, 2, 3, 4, 5];
|
|
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(ARRAY).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(ARRAY));
|
|
}
|
|
|
|
/// Test: Edge case: Mixed collection types
|
|
///
|
|
/// Verifies: Handles edge case: mixed collection types
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_vec_of_hashmaps() {
|
|
let (announcer, announcement) = Announcement::<Vec<HashMap<String, i32>>>::new();
|
|
let listener = announcement.listener();
|
|
|
|
let mut map1 = HashMap::new();
|
|
map1.insert("a".to_string(), 1);
|
|
|
|
let mut map2 = HashMap::new();
|
|
map2.insert("b".to_string(), 2);
|
|
|
|
let vec = vec![map1, map2];
|
|
|
|
announcer.announce(vec.clone()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(vec));
|
|
}
|
|
|
|
/// Test: Edge case: Hashmap with complex values
|
|
///
|
|
/// Verifies: Handles edge case: hashmap with complex values
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_hashmap_complex_values() {
|
|
let (announcer, announcement) = Announcement::<HashMap<String, Vec<Option<i32>>>>::new();
|
|
let listener = announcement.listener();
|
|
|
|
let mut map = HashMap::new();
|
|
map.insert("key1".to_string(), vec![Some(1), None, Some(2)]);
|
|
map.insert("key2".to_string(), vec![]);
|
|
|
|
announcer.announce(map.clone()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(map));
|
|
}
|
|
|
|
/// Test: Edge case: Maximum safe integer boundaries
|
|
///
|
|
/// Verifies: Handles edge case: maximum safe integer boundaries
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_safe_integer_boundaries() {
|
|
// JavaScript MAX_SAFE_INTEGER equivalent
|
|
const MAX_SAFE: i64 = 9_007_199_254_740_991;
|
|
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(MAX_SAFE).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(MAX_SAFE));
|
|
}
|
|
|
|
/// Test: Edge case: Bitwise patterns
|
|
///
|
|
/// Verifies: Handles edge case: bitwise patterns
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_bitwise_patterns() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let pattern: u64 = 0xDEADBEEF_CAFEBABE;
|
|
announcer.announce(pattern).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(pattern));
|
|
}
|
|
|
|
/// Test: Edge case: String escaping patterns
|
|
///
|
|
/// Verifies: Handles edge case: string escaping patterns
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_string_escaping() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let s = "\"Hello\"\n\r\t\\World\\".to_string();
|
|
announcer.announce(s.clone()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(s));
|
|
}
|
|
|
|
/// Test: Edge case: Recursive enum
|
|
///
|
|
/// Verifies: Handles edge case: recursive enum
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_recursive_enum() {
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
enum Tree {
|
|
Leaf(i32),
|
|
Node(Box<Tree>, Box<Tree>),
|
|
}
|
|
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let tree = Tree::Node(
|
|
Box::new(Tree::Leaf(1)),
|
|
Box::new(Tree::Node(Box::new(Tree::Leaf(2)), Box::new(Tree::Leaf(3)))),
|
|
);
|
|
|
|
announcer.announce(tree.clone()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(tree));
|
|
}
|
|
|
|
/// Test: Edge case: Type with lifetime parameter via Arc
|
|
///
|
|
/// Verifies: Handles edge case: type with lifetime parameter via arc
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_type_with_lifetime() {
|
|
#[derive(Clone, Debug)]
|
|
struct WithData {
|
|
text: String,
|
|
number: i32,
|
|
}
|
|
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let data = Arc::new(WithData {
|
|
text: "test".to_string(),
|
|
number: 42,
|
|
});
|
|
|
|
announcer.announce(data.clone()).unwrap();
|
|
|
|
let received = listener.try_listen().unwrap();
|
|
assert_eq!(received.text, "test");
|
|
assert_eq!(received.number, 42);
|
|
}
|
|
|
|
/// Test: Edge case: Mixed sync primitives
|
|
///
|
|
/// Verifies: Handles edge case: mixed sync primitives
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_mixed_sync_primitives() {
|
|
use std::sync::RwLock;
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct MixedSync {
|
|
mutex_data: Arc<Mutex<i32>>,
|
|
rwlock_data: Arc<RwLock<String>>,
|
|
}
|
|
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let mixed = MixedSync {
|
|
mutex_data: Arc::new(Mutex::new(42)),
|
|
rwlock_data: Arc::new(RwLock::new("test".to_string())),
|
|
};
|
|
|
|
announcer.announce(mixed.clone()).unwrap();
|
|
|
|
let received = listener.try_listen().unwrap();
|
|
assert_eq!(*received.mutex_data.lock().unwrap(), 42);
|
|
assert_eq!(*received.rwlock_data.read().unwrap(), "test");
|
|
}
|
|
|
|
/// Test: Edge case: Whitespace strings
|
|
///
|
|
/// Verifies: Handles edge case: whitespace strings
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_whitespace_strings() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let whitespace = " \t\n\r\u{00A0}\u{2003} ".to_string();
|
|
announcer.announce(whitespace.clone()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(whitespace));
|
|
}
|
|
|
|
/// Test: Edge case: Binary data patterns
|
|
///
|
|
/// Verifies: Handles edge case: binary data patterns
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_binary_patterns() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let binary = vec![0x00, 0xFF, 0xAA, 0x55, 0x0F, 0xF0];
|
|
announcer.announce(binary.clone()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(binary));
|
|
}
|
|
|
|
/// Test: Edge case: HashMap with collision-prone keys
|
|
///
|
|
/// Verifies: Handles edge case: hashmap with collision-prone keys
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_hashmap_collision_keys() {
|
|
let (announcer, announcement) = Announcement::<HashMap<i32, String>>::new();
|
|
let listener = announcement.listener();
|
|
|
|
let mut map = HashMap::new();
|
|
// These might cause collisions in some hash implementations
|
|
for i in 0..100 {
|
|
map.insert(i * 65536, format!("value_{}", i));
|
|
}
|
|
|
|
announcer.announce(map.clone()).unwrap();
|
|
|
|
let received = listener.try_listen().unwrap();
|
|
assert_eq!(received.len(), 100);
|
|
}
|
|
|
|
/// Test: Edge case: Partial move semantics
|
|
///
|
|
/// Verifies: Handles edge case: partial move semantics
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_partial_struct() {
|
|
#[derive(Clone, Debug)]
|
|
struct Partial {
|
|
owned: String,
|
|
reference: i32,
|
|
}
|
|
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let partial = Partial {
|
|
owned: "owned data".to_string(),
|
|
reference: 42,
|
|
};
|
|
|
|
announcer.announce(partial.clone()).unwrap();
|
|
|
|
let received = listener.try_listen().unwrap();
|
|
assert_eq!(received.owned, "owned data");
|
|
assert_eq!(received.reference, 42);
|
|
}
|
|
|
|
/// Test: Edge case: Extreme thread contention
|
|
///
|
|
/// Verifies: Handles edge case: extreme thread contention
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_extreme_thread_contention() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
|
|
let mut handles = vec![];
|
|
|
|
// 50 threads all trying to check at once
|
|
for _ in 0..50 {
|
|
let ann = announcement.clone();
|
|
handles.push(thread::spawn(move || {
|
|
for _ in 0..1000 {
|
|
let _ = ann.is_announced();
|
|
}
|
|
}));
|
|
}
|
|
|
|
thread::sleep(Duration::from_millis(10));
|
|
announcer.announce(42).unwrap();
|
|
|
|
for handle in handles {
|
|
handle.join().unwrap();
|
|
}
|
|
|
|
assert!(announcement.is_announced());
|
|
}
|
|
|
|
/// Test: Edge case: Cross-thread announcement verification
|
|
///
|
|
/// Verifies: Handles edge case: cross-thread announcement verification
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_cross_thread_verification() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
|
|
let ann1 = announcement.clone();
|
|
let ann2 = announcement.clone();
|
|
|
|
let h1 = thread::spawn(move || ann1.is_announced());
|
|
|
|
let h2 = thread::spawn(move || {
|
|
announcer.announce(42).unwrap();
|
|
});
|
|
|
|
h2.join().unwrap();
|
|
thread::sleep(Duration::from_millis(1));
|
|
|
|
let h3 = thread::spawn(move || ann2.is_announced());
|
|
|
|
let _ = h1.join().unwrap(); // May or may not see it
|
|
assert!(h3.join().unwrap()); // Must see it
|
|
}
|
|
|
|
/// Test: Edge case: Zero capacity collections
|
|
///
|
|
/// Verifies: Handles edge case: zero capacity collections
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_zero_capacity_vec() {
|
|
let (announcer, announcement) = Announcement::<Vec<i32>>::new();
|
|
let listener = announcement.listener();
|
|
|
|
let vec = Vec::with_capacity(0);
|
|
announcer.announce(vec.clone()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(vec));
|
|
}
|
|
|
|
/// Test: Edge case: Enum with many variants
|
|
///
|
|
/// Verifies: Handles edge case: enum with many variants
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_large_enum() {
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
#[allow(dead_code)]
|
|
enum LargeEnum {
|
|
V1,
|
|
V2,
|
|
V3,
|
|
V4,
|
|
V5,
|
|
V6,
|
|
V7,
|
|
V8,
|
|
V9,
|
|
V10,
|
|
V11,
|
|
V12,
|
|
V13,
|
|
V14,
|
|
V15,
|
|
V16,
|
|
V17,
|
|
V18,
|
|
V19,
|
|
V20,
|
|
V21(i32),
|
|
V22(String),
|
|
V23(Vec<u8>),
|
|
}
|
|
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(LargeEnum::V23(vec![1, 2, 3])).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(LargeEnum::V23(vec![1, 2, 3])));
|
|
}
|
|
|
|
/// Test: Edge case: Thread priority interactions
|
|
///
|
|
/// Verifies: Handles edge case: thread priority interactions
|
|
/// Category: edge
|
|
#[test]
|
|
#[cfg(not(target_os = "macos"))]
|
|
fn test_thread_yield_pattern() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
thread::spawn(move || {
|
|
for _ in 0..100 {
|
|
thread::yield_now();
|
|
}
|
|
announcer.announce(42).unwrap();
|
|
});
|
|
|
|
thread::sleep(Duration::from_millis(50));
|
|
assert_eq!(listener.try_listen(), Some(42));
|
|
}
|
|
|
|
/// Test: Edge case: Poisoned mutex in announced value
|
|
///
|
|
/// Verifies: Handles edge case: poisoned mutex in announced value
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_poisoned_mutex() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let mutex = Arc::new(Mutex::new(42));
|
|
|
|
// Poison the mutex
|
|
let mutex_clone = mutex.clone();
|
|
let _ = thread::spawn(move || {
|
|
let _guard = mutex_clone.lock().unwrap();
|
|
panic!("Intentional panic to poison mutex");
|
|
})
|
|
.join();
|
|
|
|
announcer.announce(mutex.clone()).unwrap();
|
|
|
|
let received = listener.try_listen().unwrap();
|
|
assert!(received.lock().is_err()); // Should be poisoned
|
|
}
|
|
|
|
/// Test: Edge case: Special filesystem characters in strings
|
|
///
|
|
/// Verifies: Handles edge case: special filesystem characters in strings
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_filesystem_special_chars() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let special = "../../../etc/passwd\0.txt".to_string();
|
|
announcer.announce(special.clone()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(special));
|
|
}
|
|
|
|
/// Test: Edge case: Integer overflow patterns
|
|
///
|
|
/// Verifies: Handles edge case: integer overflow patterns
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_overflow_patterns() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let values = vec![u32::MAX, u32::MAX - 1, 0, 1, u32::MAX / 2];
|
|
|
|
announcer.announce(values.clone()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(values));
|
|
}
|
|
|
|
/// Test: Edge case: Negative zero float
|
|
///
|
|
/// Verifies: Handles edge case: negative zero float
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_negative_zero() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
let neg_zero = -0.0_f64;
|
|
announcer.announce(neg_zero).unwrap();
|
|
|
|
let received = listener.try_listen().unwrap();
|
|
assert!(received.is_sign_negative());
|
|
assert_eq!(received, 0.0);
|
|
}
|
|
|
|
/// Test: Edge case: Complex generic nesting
|
|
///
|
|
/// Verifies: Handles edge case: complex generic nesting
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_complex_generics() {
|
|
type Complex = HashMap<String, Vec<Option<Result<i32, String>>>>;
|
|
|
|
let (announcer, announcement) = Announcement::<Complex>::new();
|
|
let listener = announcement.listener();
|
|
|
|
let mut complex = HashMap::new();
|
|
complex.insert(
|
|
"key".to_string(),
|
|
vec![Some(Ok(1)), None, Some(Err("error".to_string()))],
|
|
);
|
|
|
|
announcer.announce(complex.clone()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(complex));
|
|
}
|
|
|
|
/// Test: Edge case: Many small announcements sequentially
|
|
///
|
|
/// Verifies: Handles edge case: many small announcements sequentially
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_sequential_small_announcements() {
|
|
for i in 0..100 {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(i).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(i));
|
|
}
|
|
}
|
|
|
|
/// Test: Edge case: Listener after multiple is_announced checks
|
|
///
|
|
/// Verifies: Handles edge case: listener after multiple is_announced checks
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_listener_after_many_checks() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
|
|
for _ in 0..1000 {
|
|
assert!(!announcement.is_announced());
|
|
}
|
|
|
|
let listener = announcement.listener();
|
|
|
|
announcer.announce(42).unwrap();
|
|
|
|
for _ in 0..1000 {
|
|
assert!(announcement.is_announced());
|
|
}
|
|
|
|
assert_eq!(listener.try_listen(), Some(42));
|
|
}
|
|
|
|
/// Test: Edge case: Type aliases
|
|
///
|
|
/// Verifies: Handles edge case: type aliases
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_type_aliases() {
|
|
type MyString = String;
|
|
type MyVec = Vec<MyString>;
|
|
|
|
let (announcer, announcement) = Announcement::<MyVec>::new();
|
|
let listener = announcement.listener();
|
|
|
|
let data: MyVec = vec!["a".to_string(), "b".to_string()];
|
|
announcer.announce(data.clone()).unwrap();
|
|
assert_eq!(listener.try_listen(), Some(data));
|
|
}
|
|
|
|
/// Test: Edge case: Final edge case - announcement reuse attempt
|
|
///
|
|
/// Verifies: Handles edge case: final edge case - announcement reuse attempt
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_cannot_reuse_consumed_announcer() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
let _listener = announcement.listener();
|
|
|
|
// Announcer is consumed here
|
|
announcer.announce(42).unwrap();
|
|
|
|
// Cannot use announcer again - it's been consumed
|
|
// This is enforced at compile time by Rust's ownership system
|
|
|
|
// Create a new one to verify the pattern
|
|
let (announcer2, announcement2) = Announcement::new();
|
|
let listener2 = announcement2.listener();
|
|
|
|
announcer2.announce(99).unwrap();
|
|
assert_eq!(listener2.try_listen(), Some(99));
|
|
}
|
|
|
|
/// Test: Edge case: Extreme rapid fire operations
|
|
///
|
|
/// Verifies: Handles edge case: extreme rapid fire operations
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_rapid_fire_all_operations() {
|
|
let (announcer, announcement) = Announcement::new();
|
|
|
|
let ann1 = announcement.clone();
|
|
let ann2 = announcement.clone();
|
|
let ann3 = announcement.clone();
|
|
|
|
let h1 = thread::spawn(move || {
|
|
for _ in 0..10000 {
|
|
let _ = ann1.listener();
|
|
}
|
|
});
|
|
|
|
let h2 = thread::spawn(move || {
|
|
for _ in 0..10000 {
|
|
let _ = ann2.is_announced();
|
|
}
|
|
});
|
|
|
|
let h3 = thread::spawn(move || {
|
|
for _ in 0..10000 {
|
|
let l = ann3.listener();
|
|
let _ = l.try_listen();
|
|
}
|
|
});
|
|
|
|
thread::sleep(Duration::from_millis(10));
|
|
announcer.announce(42).unwrap();
|
|
|
|
h1.join().unwrap();
|
|
h2.join().unwrap();
|
|
h3.join().unwrap();
|
|
}
|
|
|
|
// Additional edge cases for zero-listener scenarios
|
|
|
|
/// Test: Edge case: Edge case - announce to 0 listeners with ~0ns overhead
|
|
///
|
|
/// Verifies: Handles edge case: edge case - announce to 0 listeners with ~0ns overhead
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_announce_zero_listeners_minimal_overhead() {
|
|
use std::time::Instant;
|
|
|
|
// Warm up
|
|
for _ in 0..100 {
|
|
let (announcer, _) = Announcement::<i32>::new();
|
|
announcer.announce(42).unwrap();
|
|
}
|
|
|
|
// Measure single announcement to no listeners
|
|
let start = Instant::now();
|
|
let (announcer, _announcement) = Announcement::<i32>::new();
|
|
announcer.announce(42).unwrap();
|
|
let duration = start.elapsed();
|
|
|
|
// Should be extremely fast (nanoseconds)
|
|
assert!(
|
|
duration.as_nanos() < 10_000,
|
|
"Single announcement to 0 listeners should have near-zero overhead, was {:?}",
|
|
duration
|
|
);
|
|
}
|
|
|
|
/// Test: Edge case: Stress test - alternating announce/no-listeners pattern
|
|
///
|
|
/// Verifies: Handles edge case: stress test - alternating announce/no-listeners pattern
|
|
/// Category: edge
|
|
#[test]
|
|
fn test_edge_case_alternating_listener_pattern() {
|
|
for i in 0..1000 {
|
|
let (announcer, announcement) = Announcement::<i32>::new();
|
|
|
|
if i % 2 == 0 {
|
|
// Even: create listeners then drop them before announce
|
|
let listeners: Vec<_> = (0..10).map(|_| announcement.listener()).collect();
|
|
drop(listeners);
|
|
}
|
|
// Odd: no listeners at all
|
|
|
|
announcer.announce(i).unwrap();
|
|
|
|
// Verify late listener still works
|
|
if i % 3 == 0 {
|
|
let late_listener = announcement.listener();
|
|
assert_eq!(late_listener.try_listen(), Some(i));
|
|
}
|
|
}
|
|
}
|