#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::significant_drop_tightening
)]
use cuengine::CueEngineError;
use cuengine::retry::{RetryConfig, with_retry};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
#[test]
fn test_retry_success_first_attempt() {
let config = RetryConfig::default();
let mut attempt_count = 0;
let result = with_retry(&config, || {
attempt_count += 1;
Ok::<String, CueEngineError>("success".to_string())
});
assert!(result.is_ok());
assert_eq!(result.unwrap(), "success");
assert_eq!(attempt_count, 1); }
#[test]
fn test_retry_eventual_success() {
let config = RetryConfig {
max_attempts: 3,
initial_delay: Duration::from_millis(10),
max_delay: Duration::from_secs(1),
exponential_base: 2.0,
};
let attempt_count = Arc::new(Mutex::new(0));
let attempt_count_clone = attempt_count.clone();
let result = with_retry(&config, || {
let mut count = attempt_count_clone.lock().unwrap();
*count += 1;
if *count < 3 {
Err(CueEngineError::configuration("temporary failure"))
} else {
Ok("success after retries".to_string())
}
});
assert!(result.is_ok());
assert_eq!(result.unwrap(), "success after retries");
assert_eq!(*attempt_count.lock().unwrap(), 3);
}
#[test]
fn test_retry_max_attempts_exceeded() {
let config = RetryConfig {
max_attempts: 2,
initial_delay: Duration::from_millis(10),
max_delay: Duration::from_secs(1),
exponential_base: 2.0,
};
let attempt_count = Arc::new(Mutex::new(0));
let attempt_count_clone = attempt_count.clone();
let result = with_retry(&config, || {
let mut count = attempt_count_clone.lock().unwrap();
*count += 1;
Err::<String, CueEngineError>(CueEngineError::configuration("persistent failure"))
});
assert!(result.is_err());
assert_eq!(*attempt_count.lock().unwrap(), 2); }
#[test]
fn test_retry_exponential_backoff() {
let config = RetryConfig {
max_attempts: 4,
initial_delay: Duration::from_millis(50),
max_delay: Duration::from_millis(500),
exponential_base: 2.0,
};
let attempt_times = Arc::new(Mutex::new(Vec::new()));
let attempt_times_clone = attempt_times.clone();
let start = Instant::now();
let _ = with_retry(&config, || {
let mut times = attempt_times_clone.lock().unwrap();
times.push(start.elapsed());
Err::<String, CueEngineError>(CueEngineError::configuration("failure"))
});
let times = attempt_times.lock().unwrap();
assert_eq!(times.len(), 4);
assert!(times[0] < Duration::from_millis(10));
if times.len() > 1 {
assert!(times[1] >= Duration::from_millis(40)); }
if times.len() > 2 {
assert!(times[2] >= Duration::from_millis(90)); }
if times.len() > 3 {
assert!(times[3] >= Duration::from_millis(190)); }
}
#[test]
fn test_retry_max_delay_capping() {
let config = RetryConfig {
max_attempts: 5,
initial_delay: Duration::from_millis(100),
max_delay: Duration::from_millis(150), exponential_base: 10.0, };
let attempt_times = Arc::new(Mutex::new(Vec::new()));
let attempt_times_clone = attempt_times.clone();
let start = Instant::now();
let _ = with_retry(&config, || {
let mut times = attempt_times_clone.lock().unwrap();
times.push(start.elapsed());
Err::<String, CueEngineError>(CueEngineError::configuration("failure"))
});
let times = attempt_times.lock().unwrap();
if times.len() >= 5 {
let delay_3_to_4 = times[3].saturating_sub(times[2]);
assert!(delay_3_to_4 >= Duration::from_millis(100)); assert!(delay_3_to_4 <= Duration::from_millis(400));
let delay_4_to_5 = times[4].saturating_sub(times[3]);
assert!(delay_4_to_5 >= Duration::from_millis(100)); assert!(delay_4_to_5 <= Duration::from_millis(400)); }
}
#[test]
fn test_retry_config_default() {
let config = RetryConfig::default();
assert_eq!(config.max_attempts, 3);
assert_eq!(config.initial_delay, Duration::from_millis(100));
assert_eq!(config.max_delay, Duration::from_secs(10));
assert!((config.exponential_base - 2.0).abs() < f32::EPSILON);
}
#[test]
fn test_retry_with_different_error_types() {
let config = RetryConfig {
max_attempts: 2,
initial_delay: Duration::from_millis(10),
max_delay: Duration::from_secs(1),
exponential_base: 2.0,
};
let result = with_retry(&config, || {
Err::<String, CueEngineError>(CueEngineError::validation("validation error"))
});
assert!(result.is_err());
let result = with_retry(&config, || {
Err::<String, CueEngineError>(CueEngineError::ffi("test_fn", "ffi error"))
});
assert!(result.is_err());
}
#[test]
fn test_retry_immediate_success_no_delay() {
let config = RetryConfig {
max_attempts: 3,
initial_delay: Duration::from_secs(10), max_delay: Duration::from_secs(100),
exponential_base: 2.0,
};
let start = Instant::now();
let result = with_retry(&config, || {
Ok::<String, CueEngineError>("immediate success".to_string())
});
let elapsed = start.elapsed();
assert!(result.is_ok());
assert_eq!(result.unwrap(), "immediate success");
assert!(elapsed < Duration::from_millis(100));
}