1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
|
#include "../src/limiter.h"
#include <catch2/catch.hpp>
#include "mocks.h"
using namespace datadog::opentracing;
TEST_CASE("limiter") {
// Starting calendar time 2007-03-12 00:00:00
std::tm start{};
start.tm_mday = 12;
start.tm_mon = 2;
start.tm_year = 107;
TimePoint time{std::chrono::system_clock::from_time_t(timegm(&start)),
std::chrono::steady_clock::time_point{}};
TimeProvider get_time = [&time]() { return time; }; // Mock clock.
SECTION("limits requests") {
Limiter lim(get_time, 1, 1.0, 1);
auto first = lim.allow();
auto second = lim.allow();
REQUIRE(first.allowed);
REQUIRE(!second.allowed);
}
SECTION("refreshes over time") {
Limiter lim(get_time, 1, 1.0, 1);
auto first = lim.allow();
auto second = lim.allow();
advanceTime(time, std::chrono::seconds(1));
auto third = lim.allow();
REQUIRE(first.allowed);
REQUIRE(!second.allowed);
REQUIRE(third.allowed);
}
SECTION("handles long intervals correctly") {
Limiter lim(get_time, 1, 1.0, 1);
auto first = lim.allow();
advanceTime(time, std::chrono::seconds(2));
auto second = lim.allow();
auto third = lim.allow();
REQUIRE(first.allowed);
REQUIRE(second.allowed);
REQUIRE(!third.allowed);
}
SECTION("calculates effective rate") {
// starts off at 1.0, and decreases if nothing happens
Limiter lim(get_time, 1, 1.0, 1);
auto first = lim.allow();
REQUIRE(first.allowed);
REQUIRE(first.effective_rate == 1.0);
auto second = lim.allow();
REQUIRE(!second.allowed);
REQUIRE(second.effective_rate == 0.95);
// if 10 seconds pass, then the effective rate gets reset, so it should be
// 9 seconds of 1.0 and one second of 1.0
advanceTime(time, std::chrono::seconds(10));
auto third = lim.allow();
REQUIRE(third.allowed);
REQUIRE(third.effective_rate == 1.0);
}
SECTION("updates tokens at sub-second intervals") {
Limiter lim(get_time, 5, 5.0, 1); // replace tokens @ 5.0 per second (i.e. every 0.2 seconds)
// consume all the tokens first
for (auto i = 0; i < 5; i++) {
auto result = lim.allow();
REQUIRE(result.allowed);
}
auto all_consumed = lim.allow();
REQUIRE(!all_consumed.allowed);
advanceTime(time, std::chrono::milliseconds(200));
auto first = lim.allow();
auto second = lim.allow();
REQUIRE(first.allowed);
REQUIRE(!second.allowed); // only one token after 0.2s
// refills to maximum, and can consume 5 tokens
advanceTime(time, std::chrono::seconds(1));
for (auto i = 0; i < 5; i++) {
auto result = lim.allow();
REQUIRE(result.allowed);
}
all_consumed = lim.allow();
REQUIRE(!all_consumed.allowed);
}
SECTION("updates tokens at multi-second intervals") {
Limiter lim(get_time, 1, 0.25, 1); // replace tokens @ 0.25 per second (i.e. every 4 seconds)
// 0 seconds (0s)
auto result = lim.allow();
REQUIRE(result.allowed);
for (int i = 0; i < 3; ++i) {
// 1s, 2s, 3s... still haven't released a token
advanceTime(time, std::chrono::seconds(1));
result = lim.allow();
REQUIRE(!result.allowed);
}
// 4s... one token was just released
advanceTime(time, std::chrono::seconds(1));
result = lim.allow();
REQUIRE(result.allowed);
// still 4s... and we used that token already
result = lim.allow();
REQUIRE(!result.allowed);
}
SECTION("dedicated constructor configures based on desired allowed-per-second") {
const double per_second = 23.97;
Limiter lim(get_time, per_second);
for (int i = 0; i < 24; ++i) {
auto result = lim.allow();
REQUIRE(result.allowed);
}
auto result = lim.allow();
REQUIRE(!result.allowed);
advanceTime(time, std::chrono::milliseconds(int(1 / per_second * 1000) + 1));
result = lim.allow();
REQUIRE(result.allowed);
result = lim.allow();
REQUIRE(!result.allowed);
}
}
|