use std::sync::Arc;
use tracing::{error, info};
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::Layer;
use tracing_throttle::infrastructure::mocks::MockCaptureLayer;
use tracing_throttle::{EvictionStrategy, Policy, TracingRateLimitLayer};
#[test]
fn test_lru_eviction_with_max_signatures() {
let capture = MockCaptureLayer::new();
let layer = TracingRateLimitLayer::builder()
.with_policy(Policy::token_bucket(1000.0, 100.0).unwrap())
.with_max_signatures(5) .build()
.unwrap();
let subscriber =
tracing_subscriber::registry().with(capture.clone().with_filter(layer.clone()));
tracing::subscriber::with_default(subscriber, || {
info!("Message 0");
info!("Message 1");
info!("Message 2");
info!("Message 3");
info!("Message 4");
info!("Message 5");
info!("Message 6");
info!("Message 7");
info!("Message 8");
info!("Message 9");
});
let evictions = layer.metrics().signatures_evicted();
assert!(
(4..=6).contains(&evictions),
"Expected ~5 evictions, got {}",
evictions
);
assert_eq!(layer.signature_count(), 5);
}
#[test]
fn test_priority_eviction_keeps_high_priority() {
let capture = MockCaptureLayer::new();
let layer = TracingRateLimitLayer::builder()
.with_policy(Policy::token_bucket(1000.0, 100.0).unwrap())
.with_max_signatures(3)
.with_eviction_strategy(EvictionStrategy::Priority {
max_entries: 3,
priority_fn: Arc::new(|_sig, state| {
match state.metadata.as_ref().map(|m| m.level.as_str()) {
Some("ERROR") => 100,
Some("WARN") => 50,
Some("INFO") => 10,
_ => 5,
}
}),
})
.build()
.unwrap();
let subscriber =
tracing_subscriber::registry().with(capture.clone().with_filter(layer.clone()));
tracing::subscriber::with_default(subscriber, || {
error!("Error message 1");
error!("Error message 2");
info!("Info message 1");
assert_eq!(layer.signature_count(), 3);
info!("Info message 2");
info!("Info message 3");
});
assert_eq!(layer.signature_count(), 3);
let evictions = layer.metrics().signatures_evicted();
assert!(
evictions >= 2,
"Expected at least 2 evictions (INFO messages), got {}",
evictions
);
}
#[test]
fn test_memory_based_eviction() {
let capture = MockCaptureLayer::new();
let layer = TracingRateLimitLayer::builder()
.with_policy(Policy::token_bucket(1000.0, 100.0).unwrap())
.with_eviction_strategy(EvictionStrategy::Memory {
max_bytes: 800, })
.build()
.unwrap();
let subscriber =
tracing_subscriber::registry().with(capture.clone().with_filter(layer.clone()));
tracing::subscriber::with_default(subscriber, || {
info!("Msg 0");
info!("Msg 1");
info!("Msg 2");
info!("Msg 3");
info!("Msg 4");
info!("Msg 5");
info!("Msg 6");
info!("Msg 7");
info!("Msg 8");
info!("Msg 9");
});
let evictions = layer.metrics().signatures_evicted();
assert!(
evictions > 0,
"Expected evictions due to memory limit, got 0"
);
let sig_count = layer.signature_count();
assert!(
sig_count < 10,
"Expected memory limit to constrain signatures, got {}",
sig_count
);
}
#[test]
fn test_priority_with_memory_combined() {
let capture = MockCaptureLayer::new();
let layer = TracingRateLimitLayer::builder()
.with_policy(Policy::token_bucket(1000.0, 100.0).unwrap())
.with_eviction_strategy(EvictionStrategy::PriorityWithMemory {
max_entries: 10,
priority_fn: Arc::new(|_sig, state| {
match state.metadata.as_ref().map(|m| m.level.as_str()) {
Some("ERROR") => 100,
Some("WARN") => 50,
Some("INFO") => 10,
_ => 5,
}
}),
max_bytes: 1000, })
.build()
.unwrap();
let subscriber =
tracing_subscriber::registry().with(capture.clone().with_filter(layer.clone()));
tracing::subscriber::with_default(subscriber, || {
error!("Err A");
error!("Err B");
error!("Err C");
info!("Info A");
info!("Info B");
info!("Info C");
info!("Info D");
info!("Info E");
});
let evictions = layer.metrics().signatures_evicted();
assert!(evictions > 0, "Expected evictions, got 0");
}
#[test]
fn test_eviction_strategy_tracks_memory() {
let lru = EvictionStrategy::Lru { max_entries: 100 };
assert!(!lru.tracks_memory());
let priority = EvictionStrategy::Priority {
max_entries: 100,
priority_fn: Arc::new(|_, _| 1),
};
assert!(!priority.tracks_memory());
let memory = EvictionStrategy::Memory { max_bytes: 1000 };
assert!(memory.tracks_memory());
let combined = EvictionStrategy::PriorityWithMemory {
max_entries: 100,
priority_fn: Arc::new(|_, _| 1),
max_bytes: 1000,
};
assert!(combined.tracks_memory());
}
#[test]
fn test_eviction_strategy_memory_limit() {
let lru = EvictionStrategy::Lru { max_entries: 100 };
assert_eq!(lru.memory_limit(), None);
let memory = EvictionStrategy::Memory { max_bytes: 5000 };
assert_eq!(memory.memory_limit(), Some(5000));
let combined = EvictionStrategy::PriorityWithMemory {
max_entries: 100,
priority_fn: Arc::new(|_, _| 1),
max_bytes: 10000,
};
assert_eq!(combined.memory_limit(), Some(10000));
}
#[test]
fn test_eviction_strategy_uses_priority() {
let lru = EvictionStrategy::Lru { max_entries: 100 };
assert!(!lru.uses_priority());
let priority = EvictionStrategy::Priority {
max_entries: 100,
priority_fn: Arc::new(|_, _| 1),
};
assert!(priority.uses_priority());
let memory = EvictionStrategy::Memory { max_bytes: 1000 };
assert!(!memory.uses_priority());
let combined = EvictionStrategy::PriorityWithMemory {
max_entries: 100,
priority_fn: Arc::new(|_, _| 1),
max_bytes: 1000,
};
assert!(combined.uses_priority());
}
#[test]
fn test_no_eviction_with_unlimited_signatures() {
let capture = MockCaptureLayer::new();
let layer = TracingRateLimitLayer::builder()
.with_policy(Policy::token_bucket(1000.0, 100.0).unwrap())
.with_unlimited_signatures()
.build()
.unwrap();
let subscriber =
tracing_subscriber::registry().with(capture.clone().with_filter(layer.clone()));
tracing::subscriber::with_default(subscriber, || {
info!("Unique 0");
info!("Unique 1");
info!("Unique 2");
info!("Unique 3");
info!("Unique 4");
info!("Unique 5");
info!("Unique 6");
info!("Unique 7");
info!("Unique 8");
info!("Unique 9");
});
assert_eq!(layer.metrics().signatures_evicted(), 0);
assert_eq!(layer.signature_count(), 10);
}
#[test]
fn test_lru_evicts_oldest_accessed() {
let capture = MockCaptureLayer::new();
let layer = TracingRateLimitLayer::builder()
.with_policy(Policy::token_bucket(1000.0, 100.0).unwrap())
.with_max_signatures(3)
.build()
.unwrap();
let subscriber =
tracing_subscriber::registry().with(capture.clone().with_filter(layer.clone()));
tracing::subscriber::with_default(subscriber, || {
info!("Message A");
info!("Message B");
info!("Message C");
assert_eq!(layer.signature_count(), 3);
info!("Message A");
info!("Message B");
info!("Message D");
info!("Message E");
info!("Message F");
assert_eq!(layer.signature_count(), 3);
let evictions = layer.metrics().signatures_evicted();
assert!(
evictions >= 3,
"Expected at least 3 evictions, got {}",
evictions
);
});
}