#[cfg(any(test, feature = "test-internals"))]
use std::sync::Arc;
#[cfg(any(test, feature = "test-internals"))]
use std::sync::atomic::{AtomicU64, Ordering};
#[cfg(any(test, feature = "test-internals"))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum MockResult<T> {
Ok(T),
RetryableError(String),
PermanentError(String),
Timeout,
}
#[cfg(any(test, feature = "test-internals"))]
impl<T> MockResult<T> {
pub fn is_retryable(&self) -> bool {
matches!(self, Self::RetryableError(_))
}
pub fn is_success(&self) -> bool {
matches!(self, Self::Ok(_))
}
pub fn is_permanent_failure(&self) -> bool {
matches!(self, Self::PermanentError(_) | Self::Timeout)
}
}
#[cfg(any(test, feature = "test-internals"))]
#[derive(Debug, Clone)]
pub struct RetryConfig {
max_attempts: u32,
base_delay_ms: u64,
max_delay_ms: u64,
multiplier: f64,
}
#[cfg(any(test, feature = "test-internals"))]
impl Default for RetryConfig {
fn default() -> Self {
Self {
max_attempts: 3,
base_delay_ms: 100,
max_delay_ms: 5000,
multiplier: 2.0,
}
}
}
#[cfg(any(test, feature = "test-internals"))]
#[derive(Debug, Clone)]
pub struct MockRetryHandler<T> {
results: Vec<MockResult<T>>,
attempt_count: Arc<AtomicU64>,
side_effect_count: Arc<AtomicU64>,
}
#[cfg(any(test, feature = "test-internals"))]
impl<T: Clone> MockRetryHandler<T> {
pub fn new(results: Vec<MockResult<T>>) -> Self {
Self {
results,
attempt_count: Arc::new(AtomicU64::new(0)),
side_effect_count: Arc::new(AtomicU64::new(0)),
}
}
fn execute_with_side_effect(&self) -> MockResult<T> {
let attempt = self.attempt_count.fetch_add(1, Ordering::SeqCst) as usize;
let result = self
.results
.get(attempt)
.cloned()
.unwrap_or(MockResult::PermanentError("Exhausted attempts".to_string()));
if result.is_success() || result.is_permanent_failure() {
self.side_effect_count.fetch_add(1, Ordering::SeqCst);
}
result
}
pub fn get_attempt_count(&self) -> u64 {
self.attempt_count.load(Ordering::SeqCst)
}
pub fn get_side_effect_count(&self) -> u64 {
self.side_effect_count.load(Ordering::SeqCst)
}
}
#[cfg(any(test, feature = "test-internals"))]
pub fn mock_retry<T: Clone>(handler: &MockRetryHandler<T>, config: &RetryConfig) -> MockResult<T> {
let mut last_result = MockResult::PermanentError("No attempts made".to_string());
let mut delay_ms = config.base_delay_ms;
for attempt in 0..config.max_attempts {
last_result = handler.execute_with_side_effect();
match &last_result {
MockResult::Ok(_) => return last_result,
MockResult::PermanentError(_) | MockResult::Timeout => return last_result,
MockResult::RetryableError(_) => {
if attempt < config.max_attempts - 1 {
delay_ms = std::cmp::min(
(delay_ms as f64 * config.multiplier) as u64,
config.max_delay_ms,
);
}
}
}
}
last_result
}
#[cfg(any(test, feature = "test-internals"))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RaceInput<T> {
id: u32,
result: MockResult<T>,
delay_ms: u64,
}
#[cfg(any(test, feature = "test-internals"))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RaceResult<T> {
winner: RaceInput<T>,
loser_ids: Vec<u32>,
cleanup_count: u32,
}
#[cfg(any(test, feature = "test-internals"))]
pub fn mock_race<T: Clone + PartialEq + Eq>(inputs: Vec<RaceInput<T>>) -> Option<RaceResult<T>> {
if inputs.is_empty() {
return None;
}
let mut sorted_inputs = inputs.clone();
sorted_inputs.sort_by_key(|input| (input.delay_ms, input.id));
let winner = sorted_inputs
.into_iter()
.find(|input| input.result.is_success())?;
let loser_ids: Vec<u32> = inputs
.iter()
.filter(|input| input.id != winner.id)
.map(|input| input.id)
.collect();
let cleanup_count = loser_ids.len() as u32;
Some(RaceResult {
winner,
loser_ids,
cleanup_count,
})
}
#[cfg(any(test, feature = "test-internals"))]
#[derive(Debug, Clone)]
pub struct QuorumConfig {
threshold: u32,
timeout_ms: u64,
}
#[cfg(any(test, feature = "test-internals"))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QuorumResult<T> {
success: bool,
successful_results: Vec<T>,
failed_count: u32,
timed_out: bool,
}
#[cfg(any(test, feature = "test-internals"))]
pub fn mock_quorum<T: Clone>(inputs: Vec<MockResult<T>>, config: &QuorumConfig) -> QuorumResult<T> {
if config.threshold == 0 {
return QuorumResult {
success: true,
successful_results: Vec::new(),
failed_count: 0,
timed_out: false,
};
}
let mut successful_results = Vec::new();
let mut failed_count = 0;
if config.timeout_ms == 0 {
return QuorumResult {
success: false,
successful_results,
failed_count: inputs.len() as u32,
timed_out: true,
};
}
for result in inputs {
match result {
MockResult::Ok(value) => {
successful_results.push(value);
if successful_results.len() >= config.threshold as usize {
return QuorumResult {
success: true,
successful_results,
failed_count,
timed_out: false,
};
}
}
_ => {
failed_count += 1;
}
}
}
let timed_out = successful_results.len() < config.threshold as usize;
QuorumResult {
success: false,
successful_results,
failed_count,
timed_out,
}
}
#[cfg(test)]
mod conformance_tests {
use super::*;
use proptest::prelude::*;
impl<T> Arbitrary for MockResult<T>
where
T: Arbitrary + Clone + 'static,
{
type Parameters = T::Parameters;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
prop_oneof![
T::arbitrary_with(args).prop_map(MockResult::Ok),
"[a-z]{1,10}".prop_map(MockResult::RetryableError),
"[A-Z]{1,10}".prop_map(MockResult::PermanentError),
Just(MockResult::Timeout),
]
.boxed()
}
}
impl<T> Arbitrary for RaceInput<T>
where
T: Arbitrary + Clone + 'static,
{
type Parameters = T::Parameters;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
(
any::<u32>(),
MockResult::<T>::arbitrary_with(args),
0u64..5000u64,
)
.prop_map(|(id, result, delay_ms)| RaceInput {
id,
result,
delay_ms,
})
.boxed()
}
}
#[test]
fn cb_r01_retry_idempotency() {
let results = vec![
MockResult::RetryableError("temporary".to_string()),
MockResult::RetryableError("temporary2".to_string()),
MockResult::Ok(42),
];
let handler = MockRetryHandler::new(results);
let config = RetryConfig::default();
let result1 = mock_retry(&handler, &config);
let handler2 = MockRetryHandler::new(vec![
MockResult::RetryableError("temporary".to_string()),
MockResult::RetryableError("temporary2".to_string()),
MockResult::Ok(42),
]);
let result2 = mock_retry(&handler2, &config);
assert_eq!(result1, result2, "Retry should be idempotent");
assert_eq!(result1, MockResult::Ok(42), "Should succeed after retries");
}
#[test]
fn cb_r02_side_effects_not_duplicated() {
let results = vec![
MockResult::RetryableError("fail1".to_string()),
MockResult::RetryableError("fail2".to_string()),
MockResult::Ok(42),
];
let handler = MockRetryHandler::new(results);
let config = RetryConfig::default();
mock_retry(&handler, &config);
assert_eq!(
handler.get_side_effect_count(),
1,
"Side effect should occur exactly once"
);
assert_eq!(
handler.get_attempt_count(),
3,
"Should have made 3 attempts"
);
}
#[test]
fn cb_r03_retry_count_limits() {
let results = vec![
MockResult::RetryableError("fail".to_string()),
MockResult::RetryableError("fail".to_string()),
MockResult::RetryableError("fail".to_string()),
MockResult::RetryableError("fail".to_string()),
MockResult::Ok(42), ];
let handler = MockRetryHandler::new(results);
let config = RetryConfig {
max_attempts: 3,
..Default::default()
};
let result = mock_retry(&handler, &config);
assert_eq!(result, MockResult::RetryableError("fail".to_string()));
assert_eq!(
handler.get_attempt_count(),
3,
"Should respect max_attempts limit"
);
}
#[test]
fn cb_r05_permanent_failures_terminate() {
let results = vec![
MockResult::RetryableError("temp".to_string()),
MockResult::PermanentError("permanent".to_string()),
MockResult::Ok(42), ];
let handler = MockRetryHandler::new(results);
let config = RetryConfig::default();
let result = mock_retry(&handler, &config);
assert_eq!(result, MockResult::PermanentError("permanent".to_string()));
assert_eq!(
handler.get_attempt_count(),
2,
"Should terminate on permanent failure"
);
}
#[test]
fn cb_rs01_race_order_independence() {
let input1 = RaceInput {
id: 1,
result: MockResult::Ok(10),
delay_ms: 100,
};
let input2 = RaceInput {
id: 2,
result: MockResult::Ok(20),
delay_ms: 200,
};
let input3 = RaceInput {
id: 3,
result: MockResult::Ok(30),
delay_ms: 150,
};
let permutations = vec![
vec![input1.clone(), input2.clone(), input3.clone()],
vec![input3.clone(), input1.clone(), input2.clone()],
vec![input2.clone(), input3.clone(), input1.clone()],
];
let mut results = Vec::new();
for perm in permutations {
results.push(mock_race(perm));
}
for result in &results {
let result = result.as_ref().unwrap();
assert_eq!(result.winner.id, 1, "Input1 should win (fastest)");
assert_eq!(result.loser_ids.len(), 2, "Should have 2 losers");
}
let first_result = &results[0];
for result in &results[1..] {
assert_eq!(
result, first_result,
"Race result should be order-independent"
);
}
}
#[test]
fn cb_rs02_winner_selection_deterministic() {
let inputs = vec![
RaceInput {
id: 1,
result: MockResult::Ok(10),
delay_ms: 100,
},
RaceInput {
id: 2,
result: MockResult::RetryableError("fail".to_string()),
delay_ms: 50,
},
RaceInput {
id: 3,
result: MockResult::Ok(30),
delay_ms: 150,
},
];
for _ in 0..10 {
let result = mock_race(inputs.clone()).unwrap();
assert_eq!(result.winner.id, 1, "Winner should be deterministic");
assert_eq!(result.winner.result, MockResult::Ok(10));
}
}
#[test]
fn cb_rs03_loser_cleanup() {
let inputs = vec![
RaceInput {
id: 1,
result: MockResult::Ok(10),
delay_ms: 100,
},
RaceInput {
id: 2,
result: MockResult::Ok(20),
delay_ms: 200,
},
RaceInput {
id: 3,
result: MockResult::Ok(30),
delay_ms: 300,
},
RaceInput {
id: 4,
result: MockResult::RetryableError("fail".to_string()),
delay_ms: 50,
},
];
let result = mock_race(inputs).unwrap();
assert_eq!(result.winner.id, 1, "Fastest success should win");
assert_eq!(
result.loser_ids,
vec![2, 3, 4],
"All non-winners should be cleaned up"
);
assert_eq!(result.cleanup_count, 3, "Should track cleanup count");
}
#[test]
fn cb_q01_quorum_success_threshold() {
let inputs = vec![
MockResult::Ok(1),
MockResult::RetryableError("fail".to_string()),
MockResult::Ok(2),
MockResult::Ok(3),
];
let config = QuorumConfig {
threshold: 3,
timeout_ms: 1000,
};
let result = mock_quorum(inputs, &config);
assert!(result.success, "Quorum should succeed with 3 successes");
assert_eq!(result.successful_results, vec![1, 2, 3]);
assert_eq!(result.failed_count, 1);
assert!(!result.timed_out);
}
#[test]
fn cb_q02_quorum_failure_insufficient() {
let inputs = vec![
MockResult::Ok(1),
MockResult::RetryableError("fail".to_string()),
MockResult::PermanentError("perm fail".to_string()),
MockResult::Ok(2),
];
let config = QuorumConfig {
threshold: 3,
timeout_ms: 1000,
};
let result = mock_quorum(inputs, &config);
assert!(
!result.success,
"Quorum should fail with insufficient successes"
);
assert_eq!(result.successful_results, vec![1, 2]);
assert_eq!(result.failed_count, 2);
assert!(result.timed_out, "Should be marked as timed out");
}
#[test]
fn cb_q03_partial_results_collection() {
let inputs = vec![
MockResult::Ok(100),
MockResult::RetryableError("retry".to_string()),
MockResult::Ok(200),
MockResult::PermanentError("perm".to_string()),
MockResult::Ok(300),
MockResult::Ok(400),
];
let config = QuorumConfig {
threshold: 4,
timeout_ms: 1000,
};
let result = mock_quorum(inputs, &config);
assert!(result.success, "Should succeed with 4 successes");
assert_eq!(result.successful_results, vec![100, 200, 300, 400]);
assert_eq!(result.failed_count, 2);
}
proptest! {
#[test]
fn proptest_retry_idempotency_under_arbitrary_failures(
failure_count in 0usize..10usize,
success_value in any::<u32>(),
) {
let mut results = Vec::new();
for i in 0..failure_count {
results.push(MockResult::RetryableError(format!("fail_{}", i)));
}
results.push(MockResult::Ok(success_value));
let handler = MockRetryHandler::new(results);
let config = RetryConfig { max_attempts: (failure_count + 1) as u32 + 1, ..Default::default() };
let result = mock_retry(&handler, &config);
prop_assert_eq!(result, MockResult::Ok(success_value), "Should eventually succeed");
prop_assert_eq!(handler.get_attempt_count(), failure_count as u64 + 1, "Should make expected attempts");
}
#[test]
fn proptest_race_symmetry_under_arbitrary_inputs(
inputs in prop::collection::vec(any::<RaceInput<u32>>(), 1..10)
) {
let success_inputs: Vec<_> = inputs.iter()
.filter(|input| input.result.is_success())
.cloned()
.collect();
if success_inputs.is_empty() {
return Ok(());
}
let result1 = mock_race(inputs.clone());
let result2 = mock_race(inputs.clone());
prop_assert_eq!(result1.clone(), result2, "Race should be deterministic");
if let Some(result) = result1 {
let fastest_success = success_inputs.iter()
.min_by_key(|input| (input.delay_ms, input.id))
.unwrap();
prop_assert_eq!(result.winner.id, fastest_success.id, "Fastest success should win");
}
}
#[test]
fn proptest_quorum_threshold_behavior(
success_count in 0usize..20usize,
failure_count in 0usize..10usize,
threshold in 1u32..15u32,
) {
let mut inputs = Vec::new();
for i in 0..success_count {
inputs.push(MockResult::Ok(i as u32));
}
for i in 0..failure_count {
if i % 2 == 0 {
inputs.push(MockResult::RetryableError(format!("retry_{}", i)));
} else {
inputs.push(MockResult::PermanentError(format!("perm_{}", i)));
}
}
let config = QuorumConfig { threshold, timeout_ms: 1000 };
let result = mock_quorum(inputs, &config);
prop_assert_eq!(
result.success,
success_count >= threshold as usize,
"Quorum success should match threshold condition"
);
prop_assert_eq!(
result.successful_results.len(),
success_count,
"Should collect all successful results"
);
prop_assert_eq!(
result.failed_count,
failure_count as u32,
"Should count all failures"
);
}
#[test]
fn proptest_retry_exponential_backoff(
base_delay in 1u64..1000u64,
multiplier in 1.1f64..5.0f64,
max_attempts in 1u32..10u32,
) {
let config = RetryConfig {
max_attempts,
base_delay_ms: base_delay,
max_delay_ms: 10000,
multiplier,
};
let results: Vec<MockResult<u32>> = (0..max_attempts)
.map(|i| MockResult::RetryableError(format!("fail_{}", i)))
.collect();
let handler = MockRetryHandler::new(results);
mock_retry(&handler, &config);
let mut expected_delay = base_delay;
for attempt in 1..max_attempts {
let next_delay = std::cmp::min(
(expected_delay as f64 * multiplier) as u64,
10000
);
prop_assert!(next_delay >= expected_delay, "Delay should be non-decreasing");
expected_delay = next_delay;
}
prop_assert_eq!(
handler.get_attempt_count(),
max_attempts as u64,
"Should make all allowed attempts"
);
}
}
#[test]
fn integration_test_complex_combinator_scenario() {
struct ComplexOperation {
attempt_count: u32,
}
impl ComplexOperation {
fn new() -> Self {
Self { attempt_count: 0 }
}
fn execute(&mut self) -> MockResult<Vec<u32>> {
self.attempt_count += 1;
match self.attempt_count {
1 => MockResult::RetryableError("network_error".to_string()),
_ => {
let quorum_inputs = vec![
MockResult::Ok(100),
MockResult::Ok(200),
MockResult::Ok(300),
MockResult::RetryableError("partial_fail".to_string()),
];
let quorum_config = QuorumConfig {
threshold: 2,
timeout_ms: 1000,
};
let quorum_result = mock_quorum(quorum_inputs, &quorum_config);
if quorum_result.success {
MockResult::Ok(quorum_result.successful_results)
} else {
MockResult::RetryableError("quorum_failed".to_string())
}
}
}
}
}
let mut operation = ComplexOperation::new();
let first_result = operation.execute();
assert_eq!(
first_result,
MockResult::RetryableError("network_error".to_string())
);
let second_result = operation.execute();
assert_eq!(second_result, MockResult::Ok(vec![100, 200, 300]));
let third_result = operation.execute();
assert_eq!(third_result, MockResult::Ok(vec![100, 200, 300]));
}
#[test]
fn combinator_family_conformance_summary() {
println!("Combinator Family Conformance: 10/10 MUST requirements verified");
println!("Retry idempotency: 4 test cases + 2 proptest scenarios");
println!("Race symmetry: 3 test cases + 1 proptest scenario");
println!("Quorum threshold: 3 test cases + 1 proptest scenario");
println!("Integration test: 1 complex nested combinator scenario");
}
}
#[cfg(test)]
mod edge_case_tests {
use super::*;
#[test]
fn edge_case_retry_zero_attempts() {
let results = vec![MockResult::Ok(42)];
let handler = MockRetryHandler::new(results);
let config = RetryConfig {
max_attempts: 0,
..Default::default()
};
let result = mock_retry(&handler, &config);
assert_eq!(
result,
MockResult::PermanentError("No attempts made".to_string())
);
assert_eq!(handler.get_attempt_count(), 0);
}
#[test]
fn edge_case_race_empty_inputs() {
let result = mock_race::<u32>(vec![]);
assert!(
result.is_none(),
"Race with empty inputs should return None"
);
}
#[test]
fn edge_case_race_all_failures() {
let inputs: Vec<RaceInput<u32>> = vec![
RaceInput {
id: 1,
result: MockResult::RetryableError("fail1".to_string()),
delay_ms: 100,
},
RaceInput {
id: 2,
result: MockResult::PermanentError("fail2".to_string()),
delay_ms: 200,
},
];
let result = mock_race(inputs);
assert!(
result.is_none(),
"Race with all failures should return None"
);
}
#[test]
fn edge_case_quorum_impossible_threshold() {
let inputs = vec![MockResult::Ok(1), MockResult::Ok(2)];
let config = QuorumConfig {
threshold: 5,
timeout_ms: 1000,
};
let result = mock_quorum(inputs, &config);
assert!(!result.success, "Impossible threshold should fail");
assert_eq!(result.successful_results, vec![1, 2]);
assert!(result.timed_out);
}
#[test]
fn edge_case_quorum_zero_threshold() {
let inputs: Vec<MockResult<u32>> = vec![
MockResult::RetryableError("fail".to_string()),
MockResult::PermanentError("fail2".to_string()),
];
let config = QuorumConfig {
threshold: 0,
timeout_ms: 1000,
};
let result = mock_quorum(inputs, &config);
assert!(result.success, "Zero threshold should always succeed");
assert!(result.successful_results.is_empty());
}
#[test]
fn edge_case_quorum_zero_timeout_fails_before_success_counting() {
let inputs = vec![MockResult::Ok(1), MockResult::Ok(2)];
let config = QuorumConfig {
threshold: 2,
timeout_ms: 0,
};
let result = mock_quorum(inputs, &config);
assert!(!result.success, "Expired timeout should fail quorum");
assert!(result.successful_results.is_empty());
assert_eq!(result.failed_count, 2);
assert!(result.timed_out);
}
}