Lines
98.94 %
Functions
100 %
Branches
use super::*;
use std::cell::Cell;
/// A clock the test drives by hand, so window behaviour is asserted rather
/// than slept through.
struct MockClock {
now: Cell<Instant>,
}
impl MockClock {
fn new() -> Self {
Self {
now: Cell::new(Instant::now()),
fn advance(&self, by: Duration) {
self.now.set(self.now.get() + by);
impl Clock for &MockClock {
fn now(&self) -> Instant {
self.now.get()
const WINDOW: Duration = Duration::from_secs(60);
fn limits(max_attempts: u32, capacity: usize) -> Limits {
Limits {
max_attempts,
window: WINDOW,
capacity,
#[test]
fn attempts_within_the_limit_are_allowed_and_the_next_is_not() {
let clock = MockClock::new();
let mut bucket = Bucket::with_clock(limits(3, 16), &clock);
assert_eq!(bucket.check(&"a"), Decision::Allow);
assert_eq!(bucket.check(&"a"), Decision::Deny);
fn the_window_slides_so_old_attempts_stop_counting() {
let mut bucket = Bucket::with_clock(limits(2, 16), &clock);
clock.advance(WINDOW + Duration::from_secs(1));
assert_eq!(
bucket.check(&"a"),
Decision::Allow,
"attempts older than the window must not count against a client forever"
);
fn a_partly_elapsed_window_only_forgives_the_attempts_that_aged_out() {
clock.advance(Duration::from_secs(50));
// Only the first attempt has aged out.
clock.advance(Duration::from_secs(11));
fn keys_are_counted_separately() {
let mut bucket = Bucket::with_clock(limits(1, 16), &clock);
assert_eq!(bucket.check(&"b"), Decision::Allow);
fn the_map_never_grows_past_its_capacity() {
let mut bucket = Bucket::with_clock(limits(5, 4), &clock);
for key in 0..100 {
bucket.check(&key);
assert!(
bucket.tracked() <= 4,
"an unbounded map is a memory DoS: tracked {} keys",
bucket.tracked()
fn a_full_map_evicts_the_stalest_key_rather_than_refusing_everyone() {
let mut bucket = Bucket::with_clock(limits(1, 2), &clock);
bucket.check(&"stale");
clock.advance(Duration::from_secs(1));
bucket.check(&"recent");
// A newcomer must get in; refusing here would let anyone who can fill the
// map deny service to everyone else.
assert_eq!(bucket.check(&"newcomer"), Decision::Allow);
bucket.check(&"recent"),
Decision::Deny,
"the more recently seen key must be the one that survived"
fn expired_keys_are_reclaimed_before_anything_is_evicted() {
bucket.check(&"a");
bucket.check(&"b");
assert_eq!(bucket.tracked(), 2);
bucket.check(&"c");
bucket.tracked(),
1,
"keys whose window has fully elapsed should be dropped, not evicted one at a time"
fn decision_reports_denial() {
assert!(Decision::Deny.is_denied());
assert!(!Decision::Allow.is_denied());
fn the_system_clock_moves_forward() {
let first = SystemClock.now();
assert!(SystemClock.now() >= first);