use error_forge::recovery::{
Backoff, CircuitBreaker, CircuitBreakerConfig, CircuitState, ExponentialBackoff, FixedBackoff,
LinearBackoff, RetryPolicy,
};
use std::error::Error as StdError;
use std::fmt;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
#[test]
fn test_exponential_backoff() {
let backoff = ExponentialBackoff::new()
.with_initial_delay(100)
.with_max_delay(10000)
.with_factor(2.0);
let delay1 = backoff.next_delay(0);
let delay2 = backoff.next_delay(1);
let delay3 = backoff.next_delay(2);
assert_eq!(delay1.as_millis(), 100);
assert_eq!(delay2.as_millis(), 200);
assert_eq!(delay3.as_millis(), 400);
let delay_max = backoff.next_delay(10);
assert!(delay_max.as_millis() <= 10000);
}
#[test]
fn test_linear_backoff() {
let backoff = LinearBackoff::new()
.with_initial_delay(100)
.with_increment(50)
.with_max_delay(500);
let delay1 = backoff.next_delay(0);
let delay2 = backoff.next_delay(1);
let delay3 = backoff.next_delay(2);
assert_eq!(delay1.as_millis(), 100);
assert_eq!(delay2.as_millis(), 150);
assert_eq!(delay3.as_millis(), 200);
let delay_max = backoff.next_delay(10);
assert_eq!(delay_max.as_millis(), 500);
}
#[test]
fn test_fixed_backoff() {
let backoff = FixedBackoff::new(200);
assert_eq!(backoff.next_delay(0).as_millis(), 200);
assert_eq!(backoff.next_delay(1).as_millis(), 200);
assert_eq!(backoff.next_delay(10).as_millis(), 200);
}
#[derive(Debug)]
struct TestError(&'static str);
impl fmt::Display for TestError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "TestError: {}", self.0)
}
}
impl StdError for TestError {}
#[test]
fn test_circuit_breaker() {
let circuit = CircuitBreaker::with_config(
"test-circuit",
CircuitBreakerConfig {
failure_threshold: 2,
failure_window_ms: 1000,
reset_timeout_ms: 100,
},
);
assert_eq!(circuit.state(), CircuitState::Closed);
let result = circuit.execute(|| -> Result<(), TestError> { Err(TestError("error")) });
assert!(result.is_err());
assert_eq!(circuit.state(), CircuitState::Closed);
let result = circuit.execute(|| -> Result<(), TestError> { Err(TestError("error")) });
assert!(result.is_err());
assert_eq!(circuit.state(), CircuitState::Open);
let start = Instant::now();
let result = circuit.execute(|| -> Result<(), TestError> {
std::thread::sleep(Duration::from_millis(50));
Ok(())
});
assert!(result.is_err());
assert!(start.elapsed() < Duration::from_millis(10));
std::thread::sleep(Duration::from_millis(200));
let result = circuit.execute(|| -> Result<(), TestError> { Ok(()) });
assert!(result.is_ok());
assert_eq!(circuit.state(), CircuitState::Closed);
let result = circuit.execute(|| -> Result<(), TestError> { Ok(()) });
assert!(result.is_ok());
assert_eq!(circuit.state(), CircuitState::Closed);
}
#[test]
fn test_retry_policy() {
let counter = Arc::new(AtomicUsize::new(0));
let counter_clone = Arc::clone(&counter);
let policy = RetryPolicy::new_fixed(10).with_max_retries(3);
let result: Result<(), TestError> = policy.retry(|| {
let current = counter_clone.fetch_add(1, Ordering::SeqCst);
if current < 2 {
Err(TestError("not ready"))
} else {
Ok(())
}
});
assert!(result.is_ok());
assert_eq!(counter.load(Ordering::SeqCst), 3);
}
#[test]
fn test_retry_with_predicate() {
let counter = Arc::new(AtomicUsize::new(0));
let counter_clone = Arc::clone(&counter);
let executor = RetryPolicy::new_fixed(10)
.with_max_retries(5)
.executor::<TestError>()
.with_retry_if(|err| err.0 == "retry");
let result: Result<(), TestError> = executor.retry(|| {
counter_clone.fetch_add(1, Ordering::SeqCst);
Err(TestError("stop"))
});
assert!(result.is_err());
assert_eq!(counter.load(Ordering::SeqCst), 1);
counter.store(0, Ordering::SeqCst);
let result: Result<(), TestError> = executor.retry(|| {
counter_clone.fetch_add(1, Ordering::SeqCst);
Err(TestError("retry"))
});
assert!(result.is_err());
assert_eq!(counter.load(Ordering::SeqCst), 6); }