//! Property-based testing for the announcement crate. //! Uses proptest to generate random test cases and verify invariants. use announcement::Announcement; use proptest::prelude::*; use std::collections::{HashMap, HashSet}; use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; // Strategy for generating various types fn arb_string() -> impl Strategy { prop::string::string_regex("[a-zA-Z0-9]{0,100}").unwrap() } fn arb_vec() -> impl Strategy> { prop::collection::vec(any::(), 0..100) } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// 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 #[test] fn prop_all_listeners_receive_value(value: i32, listener_count in 1usize..100) { let (announcer, announcement) = Announcement::new(); let mut listeners = Vec::new(); for _ in 0..listener_count { listeners.push(announcement.listener()); } announcer.announce(value).unwrap(); for listener in &listeners { assert_eq!(listener.try_listen(), Some(value)); } } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: try_listen transitions from None to Some exactly once /// /// Verifies: State transition is clean and irreversible /// Strategy: Random string values #[test] fn prop_try_listen_none_then_some(value: String) { let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); assert_eq!(listener.try_listen(), None); announcer.announce(value.clone()).unwrap(); assert_eq!(listener.try_listen(), Some(value)); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: is_announced is monotonic - false then true, never reverses /// /// Verifies: Announcement state is permanent once set /// Strategy: Multiple checks before and after announce with u64 values #[test] fn prop_is_announced_monotonic(value: u64, checks_before in 1usize..50, checks_after in 1usize..50) { let (announcer, announcement) = Announcement::new(); // Check false before for _ in 0..checks_before { assert!(!announcement.is_announced()); } announcer.announce(value).unwrap(); // Check true after for _ in 0..checks_after { assert!(announcement.is_announced()); } } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Broadcast scales to large listener counts /// /// Verifies: All listeners receive value regardless of count (1-1000) /// Strategy: Variable listener counts with Vec values #[test] fn prop_n_listeners_all_receive( value: Vec, listener_count in 1usize..1000 ) { let (announcer, announcement) = Announcement::new(); let listeners: Vec<_> = (0..listener_count) .map(|_| announcement.listener()) .collect(); announcer.announce(value.clone()).unwrap(); for listener in &listeners { assert_eq!(listener.try_listen(), Some(value.clone())); } } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Arc refcount equals initial + storage + received refs /// /// Verifies: No reference leaks in Arc-wrapped values /// Strategy: 0-50 listeners with Arc values #[test] fn prop_arc_refcount_correct( value: i32, listener_count in 0usize..50 ) { let (announcer, announcement) = Announcement::new(); let arc_value = Arc::new(value); let initial_count = Arc::strong_count(&arc_value); let mut listeners = Vec::new(); for _ in 0..listener_count { listeners.push(announcement.listener()); } announcer.announce(arc_value.clone()).unwrap(); let mut received = Vec::new(); for listener in &listeners { received.push(listener.try_listen().unwrap()); } if listener_count > 0 { // initial + internal storage + all received refs let expected = initial_count + 1 + listener_count; assert_eq!(Arc::strong_count(&received[0]), expected); } } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Listener creation timing doesn't affect value received /// /// Verifies: Both early and late listeners get the same value /// Strategy: Variable pre/post-announce listener counts with strings #[test] fn prop_listeners_before_after_same( value: String, before_count in 0usize..20, after_count in 0usize..20 ) { let (announcer, announcement) = Announcement::new(); let before_listeners: Vec<_> = (0..before_count) .map(|_| announcement.listener()) .collect(); announcer.announce(value.clone()).unwrap(); let after_listeners: Vec<_> = (0..after_count) .map(|_| announcement.listener()) .collect(); for listener in &before_listeners { assert_eq!(listener.try_listen(), Some(value.clone())); } for listener in &after_listeners { assert_eq!(listener.try_listen(), Some(value.clone())); } } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Cloning listeners and announcements preserves value access /// /// Verifies: Clone semantics maintain broadcast behavior /// Strategy: Random Vec with multiple clone operations #[test] fn prop_clone_preserves_value(value: Vec) { let (announcer, announcement) = Announcement::new(); let original_listener = announcement.listener(); let cloned_listener = original_listener.clone(); let cloned_announcement = announcement.clone(); let listener_from_clone = cloned_announcement.listener(); announcer.announce(value.clone()).unwrap(); assert_eq!(original_listener.try_listen(), Some(value.clone())); assert_eq!(cloned_listener.try_listen(), Some(value.clone())); assert_eq!(listener_from_clone.try_listen(), Some(value)); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: try_listen is idempotent across multiple calls /// /// Verifies: Same value returned on every call without side effects /// Strategy: 1-100 calls with u32 values #[test] fn prop_try_listen_idempotent(value: u32, call_count in 1usize..100) { let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(value).unwrap(); for _ in 0..call_count { assert_eq!(listener.try_listen(), Some(value)); } } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: HashMap structure and contents preserved through broadcast /// /// Verifies: All keys, values, and length maintained /// Strategy: Random HashMaps with 0-50 string keys and i32 values #[test] fn prop_hashmap_integrity( data in prop::collection::hash_map( arb_string(), any::(), 0..50 ) ) { let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(data.clone()).unwrap(); let received = listener.try_listen().unwrap(); assert_eq!(received, data); assert_eq!(received.len(), data.len()); for (k, v) in &data { assert_eq!(received.get(k), Some(v)); } } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Option values (Some/None) preserved correctly /// /// Verifies: Both Some and None variants handled properly /// Strategy: Random Option values #[test] fn prop_option_handling(opt in prop::option::of(any::())) { let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(opt).unwrap(); assert_eq!(listener.try_listen(), Some(opt)); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Result values (Ok/Err) preserved correctly /// /// Verifies: Both Ok and Err variants handled properly /// Strategy: Random Result values #[test] fn prop_result_handling( result in prop::result::maybe_ok(any::(), arb_string()) ) { let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(result.clone()).unwrap(); assert_eq!(listener.try_listen(), Some(result)); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Thread safety holds despite random timing variations /// /// Verifies: Value delivery works across threads with variable delays /// Strategy: 2-10 threads with 0-50ms random delays #[test] fn prop_thread_safety_random_delays( value: i32, _thread_count in 2usize..10, delays in prop::collection::vec(0u64..50, 2..10) ) { let (announcer, announcement) = Announcement::new(); let mut handles = vec![]; for delay_ms in delays { let announcement_clone = announcement.clone(); handles.push(thread::spawn(move || { thread::sleep(Duration::from_millis(delay_ms)); announcement_clone.listener() })); } thread::sleep(Duration::from_millis(25)); announcer.announce(value).unwrap(); for handle in handles { let listener = handle.join().unwrap(); assert_eq!(listener.try_listen(), Some(value)); } } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Vector length and element order preserved /// /// Verifies: Vec structure maintained including first/last elements /// Strategy: Random Vec with 0-100 elements #[test] fn prop_vec_operations(vec in arb_vec()) { let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); let original_len = vec.len(); let first = vec.first().cloned(); let last = vec.last().cloned(); announcer.announce(vec.clone()).unwrap(); let received = listener.try_listen().unwrap(); assert_eq!(received.len(), original_len); assert_eq!(received.first().cloned(), first); assert_eq!(received.last().cloned(), last); assert_eq!(received, vec); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: String length and emptiness preserved /// /// Verifies: String contents and properties maintained /// Strategy: Random strings with any characters #[test] fn prop_string_operations(s in ".*") { let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); let original_len = s.len(); let is_empty = s.is_empty(); announcer.announce(s.clone()).unwrap(); let received = listener.try_listen().unwrap(); assert_eq!(received.len(), original_len); assert_eq!(received.is_empty(), is_empty); assert_eq!(received, s); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: String length and emptiness preserved /// /// Verifies: String contents and properties maintained /// Strategy: Random strings with any characters #[test] fn prop_tuple_preservation( a: i32, b: String, c: Vec ) { let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); let tuple = (a, b.clone(), c.clone()); announcer.announce(tuple.clone()).unwrap(); let received = listener.try_listen().unwrap(); assert_eq!(received.0, a); assert_eq!(received.1, b); assert_eq!(received.2, c); assert_eq!(received, tuple); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Tuple structure and elements preserved /// /// Verifies: All tuple components maintained with correct types /// Strategy: Random (i32, String, Vec) tuples #[test] fn prop_is_announced_thread_consistency( value: i32, thread_count in 2usize..20 ) { let (announcer, announcement) = Announcement::new(); announcer.announce(value).unwrap(); let mut handles = vec![]; for _ in 0..thread_count { let announcement_clone = announcement.clone(); handles.push(thread::spawn(move || { announcement_clone.is_announced() })); } for handle in handles { assert!(handle.join().unwrap()); } } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: is_announced returns consistent value across all threads /// /// Verifies: Thread-safe state visibility after announce /// Strategy: 2-20 concurrent threads checking state #[test] fn prop_listener_count_independent( value: Vec, counts in prop::collection::vec(1usize..10, 1..5) ) { for count in counts { let (announcer, announcement) = Announcement::new(); let listeners: Vec<_> = (0..count) .map(|_| announcement.listener()) .collect(); announcer.announce(value.clone()).unwrap(); for listener in &listeners { assert_eq!(listener.try_listen(), Some(value.clone())); } } } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Value received is independent of listener count /// /// Verifies: Same value delivered regardless of N /// Strategy: Multiple runs with varying listener counts 1-10 #[test] fn prop_nested_collections( data in prop::collection::vec( prop::collection::vec(any::(), 0..10), 0..10 ) ) { let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(data.clone()).unwrap(); let received = listener.try_listen().unwrap(); assert_eq!(received, data); assert_eq!(received.len(), data.len()); for (i, inner) in data.iter().enumerate() { assert_eq!(&received[i], inner); } } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Nested collection structure preserved /// /// Verifies: Vec> maintains all levels correctly /// Strategy: Random nested vectors with variable dimensions #[test] fn prop_hashset_preservation( data in prop::collection::hash_set(any::(), 0..50) ) { let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(data.clone()).unwrap(); let received = listener.try_listen().unwrap(); assert_eq!(received.len(), data.len()); for item in &data { assert!(received.contains(item)); } for item in &received { assert!(data.contains(item)); } } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: HashSet elements and cardinality preserved /// /// Verifies: All set elements present in both directions /// Strategy: Random HashSet with 0-50 elements #[test] fn prop_box_preservation(value: i32) { let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); let boxed = Box::new(value); announcer.announce(boxed.clone()).unwrap(); let received = listener.try_listen().unwrap(); assert_eq!(*received, value); assert_eq!(received, boxed); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Box wrapper and inner value preserved /// /// Verifies: Boxed values work correctly /// Strategy: Random Box values #[test] fn prop_channels_independent( values in prop::collection::vec(any::(), 1..10) ) { let mut channels = Vec::new(); for value in &values { let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); channels.push((Some(announcer), announcement, listener, *value)); } // Announce in order for (announcer, _, _, value) in channels.iter_mut() { if let Some(a) = announcer.take() { a.announce(*value).unwrap(); } } // Verify each channel independently for (_, _, listener, expected) in &channels { assert_eq!(listener.try_listen(), Some(*expected)); } } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Independent announcement channels don't interfere /// /// Verifies: Each channel maintains its own state independently /// Strategy: 1-10 separate channels with different values #[test] fn prop_float_special_values( use_nan: bool, use_infinity: bool, use_neg_infinity: bool, regular in any::() ) { let value = if use_nan { f64::NAN } else if use_infinity { f64::INFINITY } else if use_neg_infinity { f64::NEG_INFINITY } else { regular }; let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(value).unwrap(); let received = listener.try_listen().unwrap(); if value.is_nan() { assert!(received.is_nan()); } else { assert_eq!(received, value); } } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Float special values (NaN, infinity) handled correctly /// /// Verifies: NaN, INFINITY, NEG_INFINITY, and regular floats work /// Strategy: Random selection of special and regular f64 values #[test] fn prop_array_preservation(arr: [i32; 10]) { let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(arr).unwrap(); let received = listener.try_listen().unwrap(); assert_eq!(received, arr); for i in 0..10 { assert_eq!(received[i], arr[i]); } } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Fixed-size array structure preserved /// /// Verifies: All array elements maintained at correct indices /// Strategy: Random [i32; 10] arrays #[test] fn prop_arc_mutex_preservation(value: i32) { let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); let mutex_value = Arc::new(Mutex::new(value)); announcer.announce(mutex_value.clone()).unwrap(); let received = listener.try_listen().unwrap(); assert_eq!(*received.lock().unwrap(), value); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Arc> wrapper and locked value preserved /// /// Verifies: Thread-safe wrapped values work correctly /// Strategy: Random Arc> values #[test] fn prop_unicode_preservation(s in "\\PC*") { let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(s.clone()).unwrap(); let received = listener.try_listen().unwrap(); assert_eq!(received, s); assert_eq!(received.len(), s.len()); assert_eq!(received.chars().count(), s.chars().count()); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Unicode strings preserved with correct character counts /// /// Verifies: Multi-byte characters handled correctly /// Strategy: Random Unicode strings #[test] fn prop_enum_with_data( choice: bool, i_val: i32, s_val: String ) { #[derive(Clone, Debug, PartialEq)] enum TestEnum { Variant1(i32), Variant2(String), } let value = if choice { TestEnum::Variant1(i_val) } else { TestEnum::Variant2(s_val) }; let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(value.clone()).unwrap(); assert_eq!(listener.try_listen(), Some(value)); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Enum variants with data preserved correctly /// /// Verifies: Both enum variant and associated data maintained /// Strategy: Random choice between two enum variants with data #[test] fn prop_rapid_listener_creation( value: i32, creation_count in 10usize..100 ) { let (announcer, announcement) = Announcement::new(); let mut listeners = Vec::new(); for _ in 0..creation_count { listeners.push(announcement.listener()); // Some may be dropped immediately if listeners.len() > 10 { let _ = listeners.remove(0); } } announcer.announce(value).unwrap(); for listener in &listeners { assert_eq!(listener.try_listen(), Some(value)); } } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Rapid listener creation and dropping doesn't break system /// /// Verifies: High churn rate of listener lifecycle handled correctly /// Strategy: Create 10-100 listeners with some dropped before announce #[test] fn prop_mixed_tuple( i: i32, f: f64, s: String, v: Vec, o: Option ) { let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); let tuple = (i, f, s.clone(), v.clone(), o); announcer.announce(tuple.clone()).unwrap(); let received = listener.try_listen().unwrap(); assert_eq!(received.0, i); assert_eq!(received.1, f); assert_eq!(received.2, s); assert_eq!(received.3, v); assert_eq!(received.4, o); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Complex heterogeneous tuple preserved /// /// Verifies: Mixed-type tuple (i32, f64, String, Vec, Option) works /// Strategy: Random 5-element tuple with diverse types #[test] fn prop_bounded_integers( u8_val: u8, u16_val: u16, u32_val: u32, u64_val: u64, i8_val: i8, i16_val: i16, i32_val: i32, i64_val: i64 ) { let tuple = (u8_val, u16_val, u32_val, u64_val, i8_val, i16_val, i32_val, i64_val); let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(tuple).unwrap(); let received = listener.try_listen().unwrap(); assert_eq!(received, tuple); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: All integer types preserved correctly /// /// Verifies: Signed and unsigned integers of all sizes work /// Strategy: Random tuple of all integer types (u8-u64, i8-i64) #[test] fn prop_collection_size_preserved( vec_size in 0usize..1000, hash_size in 0usize..100, set_size in 0usize..100 ) { let vec: Vec = (0..vec_size as i32).collect(); let mut hash = HashMap::new(); for i in 0..hash_size { hash.insert(i, i * 2); } let set: HashSet = (0..set_size as i32).collect(); let data = (vec.clone(), hash.clone(), set.clone()); let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(data).unwrap(); let received = listener.try_listen().unwrap(); assert_eq!(received.0.len(), vec_size); assert_eq!(received.1.len(), hash_size); assert_eq!(received.2.len(), set_size); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Multiple collection sizes preserved independently /// /// Verifies: Vec, HashMap, and HashSet sizes all maintained /// Strategy: Random sizes for three different collection types #[test] #[cfg(target_os = "linux")] fn prop_listener_timing_independent( value: i32, delays in prop::collection::vec(0u64..10, 1..10) ) { let (announcer, announcement) = Announcement::new(); thread::spawn(move || { thread::sleep(Duration::from_millis(5)); announcer.announce(value).unwrap(); }); let mut listeners = Vec::new(); for delay in delays { thread::sleep(Duration::from_millis(delay)); listeners.push(announcement.listener()); } thread::sleep(Duration::from_millis(20)); for listener in &listeners { assert_eq!(listener.try_listen(), Some(value)); } } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Listener creation timing relative to announce doesn't matter /// /// Verifies: Variable delays don't affect value reception /// Strategy: Random delays 0-10ms for listener creation #[test] fn prop_error_message_consistency(value: i32) { use announcement::AnnounceError; let err = AnnounceError::AlreadyAnnounced(value); let message = err.to_string(); assert!(message.contains("already")); assert!(message.contains("announced")); let inner = err.into_inner(); assert_eq!(inner, value); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Error messages contain expected keywords /// /// Verifies: AnnounceError message format is consistent /// Strategy: Random i32 values for error construction #[test] fn prop_char_validity(c: char) { let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(c).unwrap(); let received = listener.try_listen().unwrap(); assert_eq!(received, c); assert!(received.is_ascii() == c.is_ascii()); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Character values and properties preserved /// /// Verifies: All valid char values including ASCII status maintained /// Strategy: Random char values #[test] fn prop_path_strings( components in prop::collection::vec( prop::string::string_regex("[a-zA-Z0-9_-]+").unwrap(), 1..10 ) ) { let path = components.join("/"); let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(path.clone()).unwrap(); let received = listener.try_listen().unwrap(); assert_eq!(received, path); assert_eq!(received.split('/').count(), components.len()); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Path-like strings with separators preserved /// /// Verifies: Component count and structure maintained /// Strategy: Random path strings with 1-10 components #[test] fn prop_recursive_depth(depth in 0usize..20) { #[derive(Clone, Debug)] #[allow(dead_code)] struct Node { value: i32, next: Option>, } let mut node = Node { value: depth as i32, next: None, }; for i in (0..depth).rev() { node = Node { value: i as i32, next: Some(Box::new(node)), }; } let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(node).unwrap(); let received = listener.try_listen().unwrap(); // Verify depth let mut current = &received; let mut count = 0; while let Some(ref next) = current.next { count += 1; current = next; } assert_eq!(count, depth); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Recursive linked structures preserved to arbitrary depth /// /// Verifies: Deep nesting via Option> works correctly /// Strategy: Random depth 0-20 for recursive structures #[test] fn prop_binary_data_integrity( data in prop::collection::vec(any::(), 0..1000) ) { let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); let checksum: u32 = data.iter().map(|&b| b as u32).sum(); announcer.announce(data.clone()).unwrap(); let received = listener.try_listen().unwrap(); let received_checksum: u32 = received.iter().map(|&b| b as u32).sum(); assert_eq!(received, data); assert_eq!(received_checksum, checksum); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Binary data integrity via checksum verification /// /// Verifies: All bytes preserved without corruption /// Strategy: Random Vec with 0-1000 bytes and checksum #[test] fn prop_thread_ordering( value: i32, thread_count in 2usize..10 ) { let (announcer, announcement) = Announcement::new(); let counter = Arc::new(Mutex::new(0)); let mut handles = vec![]; for i in 0..thread_count { let announcement_clone = announcement.clone(); let counter_clone = counter.clone(); handles.push(thread::spawn(move || { let listener = announcement_clone.listener(); *counter_clone.lock().unwrap() += 1; (i, listener) })); } thread::sleep(Duration::from_millis(10)); announcer.announce(value).unwrap(); for handle in handles { let (_, listener) = handle.join().unwrap(); assert_eq!(listener.try_listen(), Some(value)); } assert_eq!(*counter.lock().unwrap(), thread_count); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Thread spawning order doesn't affect correctness /// /// Verifies: All threads receive value regardless of spawn order /// Strategy: 2-10 threads with counter verification #[test] fn prop_empty_string_patterns( prefix in ".*", suffix in ".*", use_empty: bool ) { let value = if use_empty { String::new() } else { format!("{}{}", prefix, suffix) }; let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(value.clone()).unwrap(); let received = listener.try_listen().unwrap(); assert_eq!(received, value); assert_eq!(received.is_empty(), value.is_empty()); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Empty strings and string concatenations preserved /// /// Verifies: Empty string handling and is_empty() property maintained /// Strategy: Random empty or concatenated strings #[test] fn prop_hashmap_key_order_independent( keys in prop::collection::vec( prop::string::string_regex("[a-z]+").unwrap(), 1..20 ), values in prop::collection::vec(any::(), 1..20) ) { let len = keys.len().min(values.len()); let mut map = HashMap::new(); for i in 0..len { map.insert(keys[i].clone(), values[i]); } let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(map.clone()).unwrap(); let received = listener.try_listen().unwrap(); assert_eq!(received.len(), map.len()); for (k, v) in &map { assert_eq!(received.get(k), Some(v)); } } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: HashMap correctness independent of insertion order /// /// Verifies: All key-value pairs present regardless of order /// Strategy: Random key-value pairs with 1-20 entries #[test] fn prop_type_size_handling( small: u8, medium: [u8; 100], large_size in 1000usize..10000 ) { let large = vec![0u8; large_size]; let data = (small, medium, large.clone()); let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(data.clone()).unwrap(); let received = listener.try_listen().unwrap(); assert_eq!(received.0, small); assert_eq!(received.1, medium); assert_eq!(received.2, large); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Various data sizes (small, medium, large) handled correctly /// /// Verifies: Size doesn't affect correctness /// Strategy: Small (u8), medium (array), large (Vec) data #[test] fn prop_clone_chain_integrity( value: i32, chain_length in 1usize..20 ) { let (announcer, announcement) = Announcement::new(); let mut current = announcement.listener(); let mut listeners = vec![current.clone()]; for _ in 1..chain_length { current = current.clone(); listeners.push(current.clone()); } announcer.announce(value).unwrap(); for listener in &listeners { assert_eq!(listener.try_listen(), Some(value)); } } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Long clone chains maintain value access /// /// Verifies: Chained listener clones all work correctly /// Strategy: 1-20 length clone chains #[test] fn prop_concurrent_is_announced( value: i32, check_count in 100usize..1000 ) { let (announcer, announcement) = Announcement::new(); let ann_clone = announcement.clone(); let handle = thread::spawn(move || { let mut saw_false = false; let mut saw_true = false; for _ in 0..check_count { if ann_clone.is_announced() { saw_true = true; } else { saw_false = true; } } (saw_false, saw_true) }); thread::sleep(Duration::from_millis(1)); announcer.announce(value).unwrap(); let (saw_false, saw_true) = handle.join().unwrap(); // Should have seen at least one true (after announce) assert!(saw_true || saw_false); // At least one observation } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Concurrent is_announced checks see consistent state transition /// /// Verifies: Thread-safe state observation during announce /// Strategy: 100-1000 rapid checks concurrent with announce #[test] fn prop_option_chain( depth in 0usize..5, value: i32 ) { type Level0 = i32; type Level1 = Option; type Level2 = Option; type Level3 = Option; type Level4 = Option; type Level5 = Option; match depth { 0 => { let (announcer, announcement) = Announcement::::new(); let listener = announcement.listener(); announcer.announce(value).unwrap(); assert_eq!(listener.try_listen(), Some(value)); }, 1 => { let (announcer, announcement) = Announcement::::new(); let listener = announcement.listener(); announcer.announce(Some(value)).unwrap(); assert_eq!(listener.try_listen(), Some(Some(value))); }, 2 => { let (announcer, announcement) = Announcement::::new(); let listener = announcement.listener(); announcer.announce(Some(Some(value))).unwrap(); assert_eq!(listener.try_listen(), Some(Some(Some(value)))); }, 3 => { let (announcer, announcement) = Announcement::::new(); let listener = announcement.listener(); announcer.announce(Some(Some(Some(value)))).unwrap(); assert_eq!(listener.try_listen(), Some(Some(Some(Some(value))))); }, 4 => { let (announcer, announcement) = Announcement::::new(); let listener = announcement.listener(); announcer.announce(Some(Some(Some(Some(value))))).unwrap(); assert_eq!(listener.try_listen(), Some(Some(Some(Some(Some(value)))))); }, _ => { let (announcer, announcement) = Announcement::::new(); let listener = announcement.listener(); announcer.announce(Some(Some(Some(Some(Some(value)))))).unwrap(); assert_eq!(listener.try_listen(), Some(Some(Some(Some(Some(Some(value))))))); } }; } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Nested Option> chains preserved /// /// Verifies: Deep Option nesting works correctly /// Strategy: 0-5 levels of Option wrapping #[test] fn prop_result_chain( success: bool, value: i32, error: String ) { type Inner = Result; type Outer = Result; let (announcer, announcement) = Announcement::::new(); let listener = announcement.listener(); let result = if success { Ok(Ok(value)) } else { Ok(Err(error.clone())) }; announcer.announce(result.clone()).unwrap(); assert_eq!(listener.try_listen(), Some(result)); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Nested Result> chains preserved /// /// Verifies: Deep Result nesting maintains Ok/Err structure /// Strategy: Random nested Result with bool choice #[test] fn prop_complex_generics( data in prop::collection::vec( prop::option::of( prop::result::maybe_ok(any::(), any::()) ), 0..10 ) ) { let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(data.clone()).unwrap(); let received = listener.try_listen().unwrap(); assert_eq!(received, data); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Complex nested generics Vec>> work /// /// Verifies: Deeply composed generic types preserved correctly /// Strategy: Random Vec>> #[test] fn prop_string_whitespace(s in "\\s*\\S*\\s*") { let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(s.clone()).unwrap(); let received = listener.try_listen().unwrap(); assert_eq!(received, s); assert_eq!(received.trim(), s.trim()); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: String whitespace patterns preserved exactly /// /// Verifies: Leading/trailing whitespace maintained /// Strategy: Random strings with whitespace patterns #[test] fn prop_numeric_boundaries( add_to_max: bool, sub_from_min: bool ) { let value = if add_to_max { (u32::MAX, i32::MAX, u64::MAX) } else if sub_from_min { (0u32, i32::MIN, 0u64) } else { (u32::MAX / 2, 0, u64::MAX / 2) }; let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(value).unwrap(); let received = listener.try_listen().unwrap(); assert_eq!(received, value); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Numeric boundary values (MAX, MIN, mid) preserved /// /// Verifies: Extreme integer values handled correctly /// Strategy: Random choice of MAX, MIN, or mid-range values #[test] fn prop_capacity_preservation( initial_capacity in 0usize..100, actual_size in 0usize..50 ) { let actual_size = actual_size.min(initial_capacity); let mut vec = Vec::with_capacity(initial_capacity); for i in 0..actual_size { vec.push(i as i32); } let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(vec.clone()).unwrap(); let received = listener.try_listen().unwrap(); assert_eq!(received.len(), actual_size); assert_eq!(received, vec); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Vec length preserved regardless of capacity /// /// Verifies: Only length matters, not internal capacity /// Strategy: Random capacity 0-100, size 0-50 #[test] fn prop_thread_pool_saturation( value: i32, batch_size in 10usize..50 ) { let (announcer, announcement) = Announcement::new(); let mut handles = vec![]; // Create many threads quickly for _ in 0..batch_size { let ann = announcement.clone(); if let Ok(handle) = thread::Builder::new() .stack_size(32 * 1024) .spawn(move || { ann.listener() }) { handles.push(handle); } } announcer.announce(value).unwrap(); for handle in handles { let listener = handle.join().unwrap(); assert_eq!(listener.try_listen(), Some(value)); } } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: High thread count doesn't break system /// /// Verifies: System handles many concurrent threads gracefully /// Strategy: 10-50 threads spawned rapidly #[test] fn prop_memory_pattern( pattern: u8, size in 1usize..1000 ) { let data = vec![pattern; size]; let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(data.clone()).unwrap(); let received = listener.try_listen().unwrap(); assert_eq!(received.len(), size); assert!(received.iter().all(|&b| b == pattern)); } } // ===================================================================== // New tests ported from quickcheck to achieve proptest supremacy // ===================================================================== /// Property: All printable ASCII characters in string preserved /// /// Verifies: Full ASCII printable character set transmitted correctly /// Strategy: Generates string with all characters from ASCII 32-126 /// Category: unit #[test] fn prop_all_ascii_chars() { // Test with all printable ASCII characters let s: String = (32..127).map(|c| c as u8 as char).collect(); let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(s.clone()).unwrap(); assert_eq!(listener.try_listen(), Some(s)); } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Boolean values preserved exactly /// /// Verifies: Both true and false transmitted without modification /// Strategy: Random bool values /// Category: property #[test] fn prop_bool_preservation(b: bool) { let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(b).unwrap(); assert_eq!(listener.try_listen(), Some(b)); } } // Custom struct for testing with proptest Strategy #[derive(Clone, Debug, PartialEq, Eq)] struct CustomStruct { id: i32, name: String, active: bool, } // Strategy for generating CustomStruct fn arb_custom_struct() -> impl Strategy { (any::(), arb_string(), any::()).prop_map(|(id, name, active)| CustomStruct { id, name, active, }) } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Custom user-defined structs preserved with all fields /// /// Verifies: User-defined struct maintains all field values /// Strategy: Random CustomStruct with arbitrary fields /// Category: property #[test] fn prop_custom_struct_preservation(custom in arb_custom_struct()) { let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(custom.clone()).unwrap(); assert_eq!(listener.try_listen(), Some(custom)); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Empty and non-empty vectors handled correctly /// /// Verifies: Both empty and populated vectors preserved correctly /// Strategy: Boolean choice between empty vec and populated vec /// Category: property #[test] fn prop_empty_vec_handled(empty: bool) { let vec: Vec = if empty { Vec::new() } else { vec![1, 2, 3] }; let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(vec.clone()).unwrap(); let received = listener.try_listen().unwrap(); assert_eq!(received.is_empty(), vec.is_empty()); assert_eq!(received, vec); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Fixed-size vector simulation preserved /// /// Verifies: Vectors of exact length transmitted correctly /// Strategy: Generates vec, takes exactly 5 elements to simulate array /// Category: property #[test] fn prop_fixed_size_vec_preservation(values in prop::collection::vec(any::(), 5..20)) { // Use exactly 5 elements to simulate fixed-size array behavior let vec: Vec = values.into_iter().take(5).collect(); let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(vec.clone()).unwrap(); assert_eq!(listener.try_listen(), Some(vec)); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: is_announced consistent across multiple announcement clones /// /// Verifies: All cloned announcements show identical is_announced state /// Strategy: Random i32 value, checks 3 announcement clones /// Category: property #[test] fn prop_is_announced_consistent_across_clones(value: i32) { let (announcer, announcement) = Announcement::new(); let announcement2 = announcement.clone(); let announcement3 = announcement.clone(); announcer.announce(value).unwrap(); assert!(announcement.is_announced()); assert!(announcement2.is_announced()); assert!(announcement3.is_announced()); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Listeners dropped before announce don't prevent new ones /// /// Verifies: Listener lifecycle independence and cleanup correct /// Strategy: Random i32 value, drop listener then create new one /// Category: property #[test] fn prop_listener_dropped_before_announce(value: i32) { let (announcer, announcement) = Announcement::new(); // Create and drop listener { let _listener = announcement.listener(); } // Create new listener after drop let listener2 = announcement.listener(); announcer.announce(value).unwrap(); assert_eq!(listener2.try_listen(), Some(value)); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Nested custom type structures preserved /// /// Verifies: Nested user-defined types maintain structure and values /// Strategy: Random outer id and inner CustomStruct /// Category: property #[test] fn prop_nested_custom_types(outer_id: i32, inner in arb_custom_struct()) { #[derive(Clone, Debug, PartialEq)] struct Nested { id: i32, inner: CustomStruct, } let nested = Nested { id: outer_id, inner: inner.clone(), }; let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(nested.clone()).unwrap(); assert_eq!(listener.try_listen(), Some(nested)); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Option with explicit Some and None variants work /// /// Verifies: Both Some(value) and None cases handled correctly /// Strategy: Boolean choice and random i32 value /// Category: property #[test] fn prop_option_explicit_some_none(has_value: bool, value: i32) { let opt = if has_value { Some(value) } else { None }; let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(opt).unwrap(); assert_eq!(listener.try_listen(), Some(opt)); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Result with explicit Ok and Err variants work /// /// Verifies: Both Ok(value) and Err(error) cases handled correctly /// Strategy: Boolean choice, random i32 and String /// Category: property #[test] fn prop_result_explicit_ok_err(is_ok: bool, ok_val: i32, err_val in arb_string()) { let result: Result = if is_ok { Ok(ok_val) } else { Err(err_val.clone()) }; let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(result.clone()).unwrap(); assert_eq!(listener.try_listen(), Some(result)); } } proptest! { #![proptest_config(ProptestConfig::with_cases(1024))] /// Property: Vector of custom types preserved /// /// Verifies: Collections of user-defined types transmitted correctly /// Strategy: Random count 0-20, generates CustomStruct instances /// Category: property #[test] fn prop_vec_of_custom_types(count in 0usize..20) { let customs: Vec = (0..count) .map(|i| CustomStruct { id: i as i32, name: format!("item_{}", i), active: i % 2 == 0, }) .collect(); let (announcer, announcement) = Announcement::new(); let listener = announcement.listener(); announcer.announce(customs.clone()).unwrap(); assert_eq!(listener.try_listen(), Some(customs)); } }