#[cfg(all(feature = "std-time", feature = "tokio-time"))]
mod std_time_async_tests {
use rate_guard::{Nanos, Millis, StdTimeSource, RateLimit};
use rate_guard::limits::{
TokenBucketBuilder, FixedWindowCounterBuilder,
SlidingWindowCounterBuilder, ApproximateSlidingWindowBuilder
};
use std::time::Duration;
use tokio::time::sleep;
#[tokio::test]
async fn test_token_bucket_with_std_time_async() {
let bucket = TokenBucketBuilder::builder()
.capacity(100)
.refill_amount(10)
.refill_every(Duration::from_millis(100))
.with_time(StdTimeSource::new())
.with_precision::<Millis>()
.build()
.unwrap();
assert!(bucket.try_acquire(50).is_ok());
assert_eq!(bucket.capacity_remaining().unwrap(), 50);
sleep(Duration::from_millis(150)).await;
let remaining = bucket.capacity_remaining().unwrap();
assert!(remaining > 50, "Expected refill, got {} tokens", remaining);
assert!(remaining <= 60, "Expected at most 60 tokens, got {}", remaining);
}
#[tokio::test]
async fn test_async_retry_pattern() {
let bucket = TokenBucketBuilder::builder()
.capacity(10)
.refill_amount(5)
.refill_every(Duration::from_millis(100))
.with_time(StdTimeSource::new())
.with_precision::<Millis>()
.build()
.unwrap();
assert!(bucket.try_acquire(10).is_ok());
assert_eq!(bucket.capacity_remaining().unwrap(), 0);
assert!(bucket.try_acquire(1).is_err());
let result = async_acquire_with_retry(&bucket, 3, 3).await;
assert!(result.is_ok(), "Should eventually succeed after retry");
}
#[tokio::test]
async fn test_concurrent_async_tasks() {
use std::sync::Arc;
let bucket = Arc::new(
TokenBucketBuilder::builder()
.capacity(100)
.refill_amount(20)
.refill_every(Duration::from_millis(50))
.with_time(StdTimeSource::new())
.with_precision::<Millis>()
.build()
.unwrap()
);
let mut tasks = vec![];
for _ in 0..5 {
let bucket_clone = Arc::clone(&bucket);
let task = tokio::spawn(async move {
let mut acquired = 0;
for _ in 0..10 {
if bucket_clone.try_acquire(2).is_ok() {
acquired += 1;
}
tokio::time::sleep(Duration::from_millis(25)).await;
}
acquired
});
tasks.push(task);
}
let mut total = 0;
for task in tasks {
total += task.await.unwrap();
}
println!("Total acquisitions across all tasks: {}", total);
assert!(total > 0, "Should have acquired some tokens");
}
#[tokio::test]
async fn test_all_limiters_with_async() {
let time_source = StdTimeSource::new();
let token_bucket = TokenBucketBuilder::builder()
.capacity(50)
.refill_amount(10)
.refill_every(Duration::from_millis(100))
.with_time(time_source.clone())
.with_precision::<Millis>()
.build()
.unwrap();
let fixed_window = FixedWindowCounterBuilder::builder()
.capacity(50)
.window_duration(Duration::from_millis(200))
.with_time(time_source.clone())
.with_precision::<Millis>()
.build()
.unwrap();
let sliding_window = SlidingWindowCounterBuilder::builder()
.capacity(50)
.bucket_duration(Duration::from_millis(50))
.bucket_count(4)
.with_time(time_source.clone())
.with_precision::<Millis>()
.build()
.unwrap();
let approx_window = ApproximateSlidingWindowBuilder::builder()
.capacity(50)
.window_duration(Duration::from_millis(200))
.with_time(time_source)
.with_precision::<Millis>()
.build()
.unwrap();
assert!(token_bucket.try_acquire(20).is_ok());
assert!(fixed_window.try_acquire(20).is_ok());
assert!(sliding_window.try_acquire(20).is_ok());
assert!(approx_window.try_acquire(20).is_ok());
sleep(Duration::from_millis(250)).await;
assert!(token_bucket.capacity_remaining().unwrap() > 30);
assert_eq!(fixed_window.capacity_remaining().unwrap(), 50);
assert!(sliding_window.capacity_remaining().unwrap() >= 30);
assert!(approx_window.capacity_remaining().unwrap() > 30);
}
#[tokio::test]
async fn test_high_frequency_async_usage() {
let bucket = TokenBucketBuilder::builder()
.capacity(1000)
.refill_amount(100)
.refill_every(Duration::from_millis(10))
.with_time(StdTimeSource::new())
.with_precision::<Nanos>()
.build()
.unwrap();
let mut successful_acquisitions = 0;
let total_attempts = 100;
for _ in 0..total_attempts {
if bucket.try_acquire(5).is_ok() {
successful_acquisitions += 1;
}
sleep(Duration::from_millis(1)).await;
}
println!("Successful acquisitions: {}/{}", successful_acquisitions, total_attempts);
assert!(successful_acquisitions > 0, "Should have some successful acquisitions");
}
async fn async_acquire_with_retry<R: RateLimit>(
limiter: &R,
tokens: rate_guard::types::Uint,
max_retries: usize,
) -> Result<(), rate_guard::error::RateLimitError> {
for attempt in 0..max_retries {
match limiter.try_acquire_verbose(tokens) {
Ok(()) => return Ok(()),
Err(rate_guard::error::RateLimitError::InsufficientCapacity { retry_after, .. }) => {
if attempt < max_retries - 1 {
sleep(retry_after).await;
}
}
Err(e) => return Err(e),
}
}
limiter.try_acquire_verbose(tokens)
}
}
#[cfg(not(all(feature = "std-time", feature = "tokio-time")))]
mod async_tests_disabled {
#[tokio::test]
async fn test_async_features_disabled() {
println!("Async StdTimeSource tests skipped - required features not enabled");
println!("Enable both 'std-time' and 'tokio-time' features to run async tests");
}
}