use qubit_retry::{
AbortEvent, FailureEvent, RetryBuilder, RetryDelayStrategy, RetryError, RetryEvent,
SuccessEvent,
};
use std::error::Error;
use std::sync::{Arc, Mutex};
use std::time::Duration;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct TestData {
value: String,
}
#[derive(Debug)]
struct TestError(String);
impl std::fmt::Display for TestError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "TestError: {}", self.0)
}
}
impl Error for TestError {}
#[test]
fn test_check_max_duration_exceeded_with_none_max_duration() {
let executor = RetryBuilder::<String>::new()
.set_max_attempts(3)
.set_max_duration(None) .set_delay_strategy(RetryDelayStrategy::None)
.build();
let result = executor.run(|| Ok::<String, Box<dyn Error + Send + Sync>>("SUCCESS".to_string()));
assert!(result.is_ok());
assert_eq!(result.unwrap(), "SUCCESS");
}
#[test]
fn test_check_max_duration_exceeded_with_some_max_duration_not_exceeded() {
let executor = RetryBuilder::<String>::new()
.set_max_attempts(3)
.set_max_duration(Some(Duration::from_secs(5))) .set_delay_strategy(RetryDelayStrategy::None)
.build();
let result = executor.run(|| {
Ok::<String, Box<dyn Error + Send + Sync>>("SUCCESS".to_string())
});
assert!(result.is_ok());
assert_eq!(result.unwrap(), "SUCCESS");
}
#[test]
fn test_check_max_duration_exceeded_with_some_max_duration_exceeded() {
let executor = RetryBuilder::<String>::new()
.set_max_attempts(100)
.set_max_duration(Some(Duration::from_millis(100))) .set_delay_strategy(RetryDelayStrategy::Fixed {
delay: Duration::from_millis(50),
})
.build();
let result = executor.run(|| {
std::thread::sleep(Duration::from_millis(10));
Err(Box::new(TestError("Always fail".to_string())) as Box<dyn Error + Send + Sync>)
});
assert!(result.is_err());
match result {
Err(RetryError::MaxDurationExceeded { .. }) => {
}
_ => panic!("Expected MaxDurationExceeded error"),
}
}
#[test]
fn test_check_max_duration_exceeded_with_none_failure_listener() {
let executor = RetryBuilder::<String>::new()
.set_max_attempts(100)
.set_max_duration(Some(Duration::from_millis(100)))
.set_delay_strategy(RetryDelayStrategy::Fixed {
delay: Duration::from_millis(50),
})
.build();
let result = executor.run(|| {
std::thread::sleep(Duration::from_millis(10));
Err(Box::new(TestError("Always fail".to_string())) as Box<dyn Error + Send + Sync>)
});
assert!(result.is_err());
match result {
Err(RetryError::MaxDurationExceeded { .. }) => {
}
_ => panic!("Expected MaxDurationExceeded error"),
}
}
#[test]
fn test_check_max_duration_exceeded_with_some_failure_listener() {
let failure_count = Arc::new(Mutex::new(0));
let failure_count_clone = failure_count.clone();
let executor = RetryBuilder::<String>::new()
.set_max_attempts(100)
.set_max_duration(Some(Duration::from_millis(100)))
.set_delay_strategy(RetryDelayStrategy::Fixed {
delay: Duration::from_millis(50),
})
.on_failure(move |_event: &FailureEvent<String>| {
*failure_count_clone.lock().unwrap() += 1;
})
.build();
let result = executor.run(|| {
std::thread::sleep(Duration::from_millis(10));
Err(Box::new(TestError("Always fail".to_string())) as Box<dyn Error + Send + Sync>)
});
assert!(result.is_err());
match result {
Err(RetryError::MaxDurationExceeded { .. }) => {
}
_ => panic!("Expected MaxDurationExceeded error"),
}
assert_eq!(*failure_count.lock().unwrap(), 1);
}
#[test]
fn test_check_operation_timeout_with_none_timeout() {
let executor = RetryBuilder::<String>::new()
.set_max_attempts(3)
.set_operation_timeout(None) .build();
let result = executor.run(|| {
std::thread::sleep(Duration::from_millis(200));
Ok::<String, Box<dyn Error + Send + Sync>>("SUCCESS".to_string())
});
assert!(result.is_ok());
assert_eq!(result.unwrap(), "SUCCESS");
}
#[test]
fn test_check_operation_timeout_with_some_timeout_not_exceeded() {
let executor = RetryBuilder::<String>::new()
.set_max_attempts(3)
.set_operation_timeout(Some(Duration::from_millis(200))) .build();
let result = executor.run(|| {
std::thread::sleep(Duration::from_millis(50));
Ok::<String, Box<dyn Error + Send + Sync>>("SUCCESS".to_string())
});
assert!(result.is_ok());
assert_eq!(result.unwrap(), "SUCCESS");
}
#[test]
fn test_check_operation_timeout_with_some_timeout_exceeded() {
let executor = RetryBuilder::<String>::new()
.set_max_attempts(3)
.set_operation_timeout(Some(Duration::from_millis(50))) .set_delay_strategy(RetryDelayStrategy::None)
.build();
let result = executor.run(|| {
std::thread::sleep(Duration::from_millis(150));
Ok::<String, Box<dyn Error + Send + Sync>>("SUCCESS".to_string())
});
assert!(result.is_err());
match result {
Err(RetryError::MaxAttemptsExceeded { .. }) => {
}
_ => panic!("Expected MaxAttemptsExceeded error"),
}
}
#[test]
fn test_handle_success_with_none_listener() {
let executor = RetryBuilder::<String>::new()
.set_max_attempts(3)
.build();
let result = executor.run(|| Ok::<String, Box<dyn Error + Send + Sync>>("SUCCESS".to_string()));
assert!(result.is_ok());
assert_eq!(result.unwrap(), "SUCCESS");
}
#[test]
fn test_handle_success_with_some_listener() {
let success_count = Arc::new(Mutex::new(0));
let success_count_clone = success_count.clone();
let executor = RetryBuilder::<String>::new()
.set_max_attempts(3)
.on_success(move |event: &SuccessEvent<String>| {
*success_count_clone.lock().unwrap() += 1;
assert_eq!(event.result(), "SUCCESS");
})
.build();
let result = executor.run(|| Ok::<String, Box<dyn Error + Send + Sync>>("SUCCESS".to_string()));
assert!(result.is_ok());
assert_eq!(result.unwrap(), "SUCCESS");
assert_eq!(*success_count.lock().unwrap(), 1);
}
#[test]
fn test_handle_abort_with_none_listener() {
let abort_result = TestData {
value: "ABORT".to_string(),
};
let executor = RetryBuilder::<TestData>::new()
.set_max_attempts(3)
.abort_on_results(vec![abort_result.clone()])
.build();
let result =
executor.run(|| Ok::<TestData, Box<dyn Error + Send + Sync>>(abort_result.clone()));
assert!(result.is_err());
match result {
Err(RetryError::Aborted { .. }) => {
}
_ => panic!("Expected Aborted error"),
}
}
#[test]
fn test_handle_abort_with_some_listener() {
let abort_count = Arc::new(Mutex::new(0));
let abort_count_clone = abort_count.clone();
let abort_result = TestData {
value: "ABORT".to_string(),
};
let executor = RetryBuilder::<TestData>::new()
.set_max_attempts(3)
.abort_on_results(vec![abort_result.clone()])
.on_abort(move |_event: &AbortEvent<TestData>| {
*abort_count_clone.lock().unwrap() += 1;
})
.build();
let result =
executor.run(|| Ok::<TestData, Box<dyn Error + Send + Sync>>(abort_result.clone()));
assert!(result.is_err());
match result {
Err(RetryError::Aborted { .. }) => {
}
_ => panic!("Expected Aborted error"),
}
assert_eq!(*abort_count.lock().unwrap(), 1);
}
#[test]
fn test_handle_max_attempts_exceeded_with_none_failure_listener() {
let executor = RetryBuilder::<String>::new()
.set_max_attempts(3)
.set_delay_strategy(RetryDelayStrategy::None)
.build();
let result = executor.run(|| {
Err(Box::new(TestError("Always fail".to_string())) as Box<dyn Error + Send + Sync>)
});
assert!(result.is_err());
match result {
Err(RetryError::MaxAttemptsExceeded { attempts, .. }) => {
assert_eq!(attempts, 3);
}
_ => panic!("Expected MaxAttemptsExceeded error"),
}
}
#[test]
fn test_handle_max_attempts_exceeded_with_some_failure_listener() {
let failure_count = Arc::new(Mutex::new(0));
let failure_count_clone = failure_count.clone();
let executor = RetryBuilder::<String>::new()
.set_max_attempts(3)
.set_delay_strategy(RetryDelayStrategy::None)
.on_failure(move |event: &FailureEvent<String>| {
*failure_count_clone.lock().unwrap() += 1;
assert_eq!(event.attempt_count(), 3);
})
.build();
let result = executor.run(|| {
Err(Box::new(TestError("Always fail".to_string())) as Box<dyn Error + Send + Sync>)
});
assert!(result.is_err());
match result {
Err(RetryError::MaxAttemptsExceeded { attempts, .. }) => {
assert_eq!(attempts, 3);
}
_ => panic!("Expected MaxAttemptsExceeded error"),
}
assert_eq!(*failure_count.lock().unwrap(), 1);
}
#[test]
fn test_handle_max_attempts_exceeded_with_result_failure() {
let failure_count = Arc::new(Mutex::new(0));
let failure_count_clone = failure_count.clone();
let failed_result = TestData {
value: "FAIL".to_string(),
};
let executor = RetryBuilder::<TestData>::new()
.set_max_attempts(3)
.set_delay_strategy(RetryDelayStrategy::None)
.failed_on_results(vec![failed_result.clone()])
.on_failure(move |event: &FailureEvent<TestData>| {
*failure_count_clone.lock().unwrap() += 1;
assert!(event.last_result().is_some());
assert_eq!(event.last_result().unwrap().value, "FAIL");
})
.build();
let result =
executor.run(|| Ok::<TestData, Box<dyn Error + Send + Sync>>(failed_result.clone()));
assert!(result.is_err());
match result {
Err(RetryError::MaxAttemptsExceeded { attempts, .. }) => {
assert_eq!(attempts, 3);
}
_ => panic!("Expected MaxAttemptsExceeded error"),
}
assert_eq!(*failure_count.lock().unwrap(), 1);
}
#[test]
fn test_trigger_retry_and_wait_with_none_listener() {
let attempt_count = Arc::new(Mutex::new(0));
let attempt_count_clone = attempt_count.clone();
let executor = RetryBuilder::<String>::new()
.set_max_attempts(3)
.set_delay_strategy(RetryDelayStrategy::Fixed {
delay: Duration::from_millis(10),
})
.build();
let result = executor.run(|| {
let mut count = attempt_count_clone.lock().unwrap();
*count += 1;
let current = *count;
drop(count);
if current < 2 {
Err(Box::new(TestError("Temporary failure".to_string()))
as Box<dyn Error + Send + Sync>)
} else {
Ok::<String, Box<dyn Error + Send + Sync>>("SUCCESS".to_string())
}
});
assert!(result.is_ok());
assert_eq!(result.unwrap(), "SUCCESS");
assert_eq!(*attempt_count.lock().unwrap(), 2);
}
#[test]
fn test_trigger_retry_and_wait_with_some_listener() {
let retry_count = Arc::new(Mutex::new(0));
let retry_count_clone = retry_count.clone();
let attempt_count = Arc::new(Mutex::new(0));
let attempt_count_clone = attempt_count.clone();
let executor = RetryBuilder::<String>::new()
.set_max_attempts(3)
.set_delay_strategy(RetryDelayStrategy::Fixed {
delay: Duration::from_millis(10),
})
.on_retry(move |event: &RetryEvent<String>| {
*retry_count_clone.lock().unwrap() += 1;
assert!(event.attempt_count() > 0);
})
.build();
let result = executor.run(|| {
let mut count = attempt_count_clone.lock().unwrap();
*count += 1;
let current = *count;
drop(count);
if current < 2 {
Err(Box::new(TestError("Temporary failure".to_string()))
as Box<dyn Error + Send + Sync>)
} else {
Ok::<String, Box<dyn Error + Send + Sync>>("SUCCESS".to_string())
}
});
assert!(result.is_ok());
assert_eq!(result.unwrap(), "SUCCESS");
assert_eq!(*attempt_count.lock().unwrap(), 2);
assert_eq!(*retry_count.lock().unwrap(), 1);
}
#[test]
fn test_trigger_retry_and_wait_with_zero_delay() {
let retry_count = Arc::new(Mutex::new(0));
let retry_count_clone = retry_count.clone();
let attempt_count = Arc::new(Mutex::new(0));
let attempt_count_clone = attempt_count.clone();
let executor = RetryBuilder::<String>::new()
.set_max_attempts(3)
.set_delay_strategy(RetryDelayStrategy::None) .on_retry(move |_event: &RetryEvent<String>| {
*retry_count_clone.lock().unwrap() += 1;
})
.build();
let result = executor.run(|| {
let mut count = attempt_count_clone.lock().unwrap();
*count += 1;
let current = *count;
drop(count);
if current < 2 {
Err(Box::new(TestError("Temporary failure".to_string()))
as Box<dyn Error + Send + Sync>)
} else {
Ok::<String, Box<dyn Error + Send + Sync>>("SUCCESS".to_string())
}
});
assert!(result.is_ok());
assert_eq!(result.unwrap(), "SUCCESS");
assert_eq!(*attempt_count.lock().unwrap(), 2);
assert_eq!(*retry_count.lock().unwrap(), 1);
}
#[tokio::test]
async fn test_trigger_retry_and_wait_async_with_none_listener() {
let attempt_count = Arc::new(Mutex::new(0));
let attempt_count_clone = attempt_count.clone();
let executor = RetryBuilder::<String>::new()
.set_max_attempts(3)
.set_delay_strategy(RetryDelayStrategy::Fixed {
delay: Duration::from_millis(10),
})
.build();
let result = executor
.run_async(|| async {
let mut count = attempt_count_clone.lock().unwrap();
*count += 1;
let current = *count;
drop(count);
if current < 2 {
Err(Box::new(TestError("Temporary failure".to_string()))
as Box<dyn Error + Send + Sync>)
} else {
Ok::<String, Box<dyn Error + Send + Sync>>("SUCCESS".to_string())
}
})
.await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "SUCCESS");
assert_eq!(*attempt_count.lock().unwrap(), 2);
}
#[tokio::test]
async fn test_trigger_retry_and_wait_async_with_some_listener() {
let retry_count = Arc::new(Mutex::new(0));
let retry_count_clone = retry_count.clone();
let attempt_count = Arc::new(Mutex::new(0));
let attempt_count_clone = attempt_count.clone();
let executor = RetryBuilder::<String>::new()
.set_max_attempts(3)
.set_delay_strategy(RetryDelayStrategy::Fixed {
delay: Duration::from_millis(10),
})
.on_retry(move |event: &RetryEvent<String>| {
*retry_count_clone.lock().unwrap() += 1;
assert!(event.attempt_count() > 0);
})
.build();
let result = executor
.run_async(|| async {
let mut count = attempt_count_clone.lock().unwrap();
*count += 1;
let current = *count;
drop(count);
if current < 2 {
Err(Box::new(TestError("Temporary failure".to_string()))
as Box<dyn Error + Send + Sync>)
} else {
Ok::<String, Box<dyn Error + Send + Sync>>("SUCCESS".to_string())
}
})
.await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "SUCCESS");
assert_eq!(*attempt_count.lock().unwrap(), 2);
assert_eq!(*retry_count.lock().unwrap(), 1);
}
#[tokio::test]
async fn test_trigger_retry_and_wait_async_with_zero_delay() {
let retry_count = Arc::new(Mutex::new(0));
let retry_count_clone = retry_count.clone();
let attempt_count = Arc::new(Mutex::new(0));
let attempt_count_clone = attempt_count.clone();
let executor = RetryBuilder::<String>::new()
.set_max_attempts(3)
.set_delay_strategy(RetryDelayStrategy::None) .on_retry(move |_event: &RetryEvent<String>| {
*retry_count_clone.lock().unwrap() += 1;
})
.build();
let result = executor
.run_async(|| async {
let mut count = attempt_count_clone.lock().unwrap();
*count += 1;
let current = *count;
drop(count);
if current < 2 {
Err(Box::new(TestError("Temporary failure".to_string()))
as Box<dyn Error + Send + Sync>)
} else {
Ok::<String, Box<dyn Error + Send + Sync>>("SUCCESS".to_string())
}
})
.await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "SUCCESS");
assert_eq!(*attempt_count.lock().unwrap(), 2);
assert_eq!(*retry_count.lock().unwrap(), 1);
}
#[tokio::test]
async fn test_execute_operation_async_with_none_timeout() {
let executor = RetryBuilder::<String>::new()
.set_max_attempts(3)
.set_operation_timeout(None) .build();
let result = executor
.run_async(|| async {
tokio::time::sleep(Duration::from_millis(200)).await;
Ok::<String, Box<dyn Error + Send + Sync>>("SUCCESS".to_string())
})
.await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "SUCCESS");
}
#[tokio::test]
async fn test_execute_operation_async_with_some_timeout_not_exceeded() {
let executor = RetryBuilder::<String>::new()
.set_max_attempts(3)
.set_operation_timeout(Some(Duration::from_millis(200))) .build();
let result = executor
.run_async(|| async {
tokio::time::sleep(Duration::from_millis(50)).await;
Ok::<String, Box<dyn Error + Send + Sync>>("SUCCESS".to_string())
})
.await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "SUCCESS");
}
#[tokio::test]
async fn test_execute_operation_async_with_some_timeout_exceeded() {
let executor = RetryBuilder::<String>::new()
.set_max_attempts(3)
.set_operation_timeout(Some(Duration::from_millis(50))) .set_delay_strategy(RetryDelayStrategy::None)
.build();
let result = executor
.run_async(|| async {
tokio::time::sleep(Duration::from_millis(200)).await;
Ok::<String, Box<dyn Error + Send + Sync>>("SUCCESS".to_string())
})
.await;
assert!(result.is_err());
match result {
Err(RetryError::MaxAttemptsExceeded { .. }) => {
}
_ => panic!("Expected MaxAttemptsExceeded error"),
}
}
#[test]
fn test_run_with_check_max_duration_returns_none() {
let executor = RetryBuilder::<String>::new()
.set_max_attempts(3)
.set_max_duration(None) .build();
let result = executor.run(|| Ok::<String, Box<dyn Error + Send + Sync>>("SUCCESS".to_string()));
assert!(result.is_ok());
assert_eq!(result.unwrap(), "SUCCESS");
}
#[test]
fn test_run_with_check_max_duration_returns_some() {
let executor = RetryBuilder::<String>::new()
.set_max_attempts(100)
.set_max_duration(Some(Duration::from_millis(100))) .set_delay_strategy(RetryDelayStrategy::Fixed {
delay: Duration::from_millis(50),
})
.build();
let result = executor.run(|| {
std::thread::sleep(Duration::from_millis(10));
Err(Box::new(TestError("Always fail".to_string())) as Box<dyn Error + Send + Sync>)
});
assert!(result.is_err());
match result {
Err(RetryError::MaxDurationExceeded { .. }) => {
}
_ => panic!("Expected MaxDurationExceeded error"),
}
}
#[test]
fn test_run_with_max_duration_not_exceeded_but_max_attempts_exceeded() {
let executor = RetryBuilder::<String>::new()
.set_max_attempts(3)
.set_max_duration(Some(Duration::from_secs(10))) .set_delay_strategy(RetryDelayStrategy::None)
.build();
let result = executor.run(|| {
Err(Box::new(TestError("Always fail".to_string())) as Box<dyn Error + Send + Sync>)
});
assert!(result.is_err());
match result {
Err(RetryError::MaxAttemptsExceeded { attempts, .. }) => {
assert_eq!(attempts, 3);
}
_ => panic!("Expected MaxAttemptsExceeded error"),
}
}
#[tokio::test]
async fn test_run_async_with_check_max_duration_returns_none() {
let executor = RetryBuilder::<String>::new()
.set_max_attempts(3)
.set_max_duration(None) .build();
let result = executor
.run_async(|| async { Ok::<String, Box<dyn Error + Send + Sync>>("SUCCESS".to_string()) })
.await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "SUCCESS");
}
#[tokio::test]
async fn test_run_async_with_check_max_duration_returns_some() {
let executor = RetryBuilder::<String>::new()
.set_max_attempts(100)
.set_max_duration(Some(Duration::from_millis(100))) .set_delay_strategy(RetryDelayStrategy::Fixed {
delay: Duration::from_millis(50),
})
.build();
let result = executor
.run_async(|| async {
tokio::time::sleep(Duration::from_millis(10)).await;
Err(Box::new(TestError("Always fail".to_string())) as Box<dyn Error + Send + Sync>)
})
.await;
assert!(result.is_err());
match result {
Err(RetryError::MaxDurationExceeded { .. }) => {
}
_ => panic!("Expected MaxDurationExceeded error"),
}
}
#[tokio::test]
async fn test_run_async_with_max_duration_not_exceeded_but_max_attempts_exceeded() {
let executor = RetryBuilder::<String>::new()
.set_max_attempts(3)
.set_max_duration(Some(Duration::from_secs(10))) .set_delay_strategy(RetryDelayStrategy::None)
.build();
let result = executor
.run_async(|| async {
Err(Box::new(TestError("Always fail".to_string())) as Box<dyn Error + Send + Sync>)
})
.await;
assert!(result.is_err());
match result {
Err(RetryError::MaxAttemptsExceeded { attempts, .. }) => {
assert_eq!(attempts, 3);
}
_ => panic!("Expected MaxAttemptsExceeded error"),
}
}
#[test]
fn test_all_listeners_triggered_in_sequence() {
let retry_count = Arc::new(Mutex::new(0));
let retry_count_clone = retry_count.clone();
let success_count = Arc::new(Mutex::new(0));
let success_count_clone = success_count.clone();
let attempt_count = Arc::new(Mutex::new(0));
let attempt_count_clone = attempt_count.clone();
let executor = RetryBuilder::<String>::new()
.set_max_attempts(3)
.set_delay_strategy(RetryDelayStrategy::Fixed {
delay: Duration::from_millis(10),
})
.on_retry(move |_event: &RetryEvent<String>| {
*retry_count_clone.lock().unwrap() += 1;
})
.on_success(move |_event: &SuccessEvent<String>| {
*success_count_clone.lock().unwrap() += 1;
})
.build();
let result = executor.run(|| {
let mut count = attempt_count_clone.lock().unwrap();
*count += 1;
let current = *count;
drop(count);
if current < 2 {
Err(Box::new(TestError("Temporary failure".to_string()))
as Box<dyn Error + Send + Sync>)
} else {
Ok::<String, Box<dyn Error + Send + Sync>>("SUCCESS".to_string())
}
});
assert!(result.is_ok());
assert_eq!(result.unwrap(), "SUCCESS");
assert_eq!(*attempt_count.lock().unwrap(), 2);
assert_eq!(*retry_count.lock().unwrap(), 1); assert_eq!(*success_count.lock().unwrap(), 1); }
#[test]
fn test_no_listeners_all_branches() {
let attempt_count = Arc::new(Mutex::new(0));
let attempt_count_clone = attempt_count.clone();
let executor = RetryBuilder::<String>::new()
.set_max_attempts(3)
.set_delay_strategy(RetryDelayStrategy::Fixed {
delay: Duration::from_millis(10),
})
.build();
let result = executor.run(|| {
let mut count = attempt_count_clone.lock().unwrap();
*count += 1;
let current = *count;
drop(count);
if current < 2 {
Err(Box::new(TestError("Temporary failure".to_string()))
as Box<dyn Error + Send + Sync>)
} else {
Ok::<String, Box<dyn Error + Send + Sync>>("SUCCESS".to_string())
}
});
assert!(result.is_ok());
assert_eq!(result.unwrap(), "SUCCESS");
assert_eq!(*attempt_count.lock().unwrap(), 2);
}