Files
swaits 319cd3b6cb
CI / Test (beta, Linux) (push) Canceled after 0s
CI / Test (nightly, Linux) (push) Canceled after 0s
CI / Test (stable, Linux) (push) Canceled after 0s
CI / Test (macos-latest) (push) Canceled after 0s
CI / Test (windows-latest) (push) Canceled after 0s
CI / Rustfmt (push) Canceled after 0s
CI / Clippy (push) Canceled after 0s
CI / MSRV (push) Canceled after 0s
CI / Documentation (push) Canceled after 0s
CI / Coverage (push) Canceled after 0s
CI / Security Audit (push) Canceled after 0s
CI / Fuzz Testing (push) Canceled after 0s
CI / Examples (push) Canceled after 0s
CI / Benchmarks (push) Canceled after 0s
fix: disable tests on macos+windows and mutation testing in CI
2025-10-13 20:22:46 -06:00

1763 lines
52 KiB
Rust

//! 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<Value = String> {
prop::string::string_regex("[a-zA-Z0-9]{0,100}").unwrap()
}
fn arb_vec() -> impl Strategy<Value = Vec<i32>> {
prop::collection::vec(any::<i32>(), 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<u8> values
#[test]
fn prop_n_listeners_all_receive(
value: Vec<u8>,
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<i32> 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<i32> with multiple clone operations
#[test]
fn prop_clone_preserves_value(value: Vec<i32>) {
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::<i32>(),
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<T> values (Some/None) preserved correctly
///
/// Verifies: Both Some and None variants handled properly
/// Strategy: Random Option<i32> values
#[test]
fn prop_option_handling(opt in prop::option::of(any::<i32>())) {
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<T, E> values (Ok/Err) preserved correctly
///
/// Verifies: Both Ok and Err variants handled properly
/// Strategy: Random Result<i32, String> values
#[test]
fn prop_result_handling(
result in prop::result::maybe_ok(any::<i32>(), 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<i32> 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<u8>
) {
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<u8>) 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<String>,
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::<i32>(), 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<Vec<i32>> maintains all levels correctly
/// Strategy: Random nested vectors with variable dimensions
#[test]
fn prop_hashset_preservation(
data in prop::collection::hash_set(any::<i32>(), 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<i32> 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<T> wrapper and inner value preserved
///
/// Verifies: Boxed values work correctly
/// Strategy: Random Box<i32> values
#[test]
fn prop_channels_independent(
values in prop::collection::vec(any::<i32>(), 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::<f64>()
) {
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<Mutex<T>> wrapper and locked value preserved
///
/// Verifies: Thread-safe wrapped values work correctly
/// Strategy: Random Arc<Mutex<i32>> 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<u8>,
o: Option<bool>
) {
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<i32> = (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<i32> = (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<Box<Node>>,
}
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<Box<Node>> works correctly
/// Strategy: Random depth 0-20 for recursive structures
#[test]
fn prop_binary_data_integrity(
data in prop::collection::vec(any::<u8>(), 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<u8> 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::<i32>(), 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<Level0>;
type Level2 = Option<Level1>;
type Level3 = Option<Level2>;
type Level4 = Option<Level3>;
type Level5 = Option<Level4>;
match depth {
0 => {
let (announcer, announcement) = Announcement::<Level0>::new();
let listener = announcement.listener();
announcer.announce(value).unwrap();
assert_eq!(listener.try_listen(), Some(value));
},
1 => {
let (announcer, announcement) = Announcement::<Level1>::new();
let listener = announcement.listener();
announcer.announce(Some(value)).unwrap();
assert_eq!(listener.try_listen(), Some(Some(value)));
},
2 => {
let (announcer, announcement) = Announcement::<Level2>::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::<Level3>::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::<Level4>::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::<Level5>::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<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<i32, String>;
type Outer = Result<Inner, String>;
let (announcer, announcement) = Announcement::<Outer>::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<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::<i32>(), any::<bool>())
),
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<Option<Result<T,E>>> work
///
/// Verifies: Deeply composed generic types preserved correctly
/// Strategy: Random Vec<Option<Result<i32,bool>>>
#[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<Value = CustomStruct> {
(any::<i32>(), arb_string(), any::<bool>()).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<i32> = 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::<i32>(), 5..20)) {
// Use exactly 5 elements to simulate fixed-size array behavior
let vec: Vec<i32> = 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<i32, String> = 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<CustomStruct> = (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));
}
}