From a3641c7378408fa338e1e630dda16f2c9190c392 Mon Sep 17 00:00:00 2001 From: dimxy Date: Mon, 28 Sep 2026 13:46:56 +0300 Subject: [PATCH 1/3] build: added CI workflow --- .github/workflows/ci.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..86cafa4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: CI + +on: + pull_request: + branches: [main,dev] + push: + branches: [main,dev] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-and-test: + name: ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + + steps: + - uses: actions/checkout@v4 + + - name: Configure + run: cmake -B build -DCMAKE_BUILD_TYPE=Release + + - name: Build + run: cmake --build build --config Release --parallel + + - name: Test + run: ctest --test-dir build --build-config Release --output-on-failure From 8b023248093e7a772371478a5cfa455272345a4e Mon Sep 17 00:00:00 2001 From: dimxy Date: Mon, 28 Sep 2026 14:11:34 +0300 Subject: [PATCH 2/3] test: bad test to check ci --- tests/ratelimiter_test.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ratelimiter_test.cpp b/tests/ratelimiter_test.cpp index cc6658a..b956b76 100644 --- a/tests/ratelimiter_test.cpp +++ b/tests/ratelimiter_test.cpp @@ -11,7 +11,7 @@ long get_time_ms() { TEST(RateLimiterTest, AcquireWithinZeroLimit) { xflow1cpp::RateLimiter rl(0.0, 1000); - EXPECT_FALSE(rl.acquire(1)); + EXPECT_TRUE(rl.acquire(1)); } TEST(RateLimiterTest, AcquireWithinLimit) { From 8f69ffdf0ae5b84ec5f67b8b5afb53f0d3e4a641 Mon Sep 17 00:00:00 2001 From: dimxy Date: Mon, 28 Sep 2026 14:26:16 +0300 Subject: [PATCH 3/3] test: add time slack for long run on CI --- .github/workflows/ci.yml | 2 +- CMakeLists.txt | 2 ++ tests/ratelimiter_test.cpp | 61 +++++++++++++++++++++----------------- 3 files changed, 37 insertions(+), 28 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 86cafa4..86bb7dd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@v4 - name: Configure - run: cmake -B build -DCMAKE_BUILD_TYPE=Release + run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DTEST_TIME_SLACK_MS=500 - name: Build run: cmake --build build --config Release --parallel diff --git a/CMakeLists.txt b/CMakeLists.txt index efc5f34..5126922 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,8 @@ FetchContent_MakeAvailable(googletest) enable_testing() add_executable(unit_tests tests/ratelimiter_test.cpp) target_link_libraries(unit_tests PRIVATE GTest::gtest_main) +set(TEST_TIME_SLACK_MS 0 CACHE STRING "Extra ms allowed on timing test upper bounds (e.g. for CI runners)") +target_compile_definitions(unit_tests PRIVATE TEST_TIME_SLACK_MS=${TEST_TIME_SLACK_MS}) # target_compile_definitions(unit_tests PRIVATE RATELIMITER_DEBUG) include(GoogleTest) diff --git a/tests/ratelimiter_test.cpp b/tests/ratelimiter_test.cpp index b956b76..431bac0 100644 --- a/tests/ratelimiter_test.cpp +++ b/tests/ratelimiter_test.cpp @@ -3,6 +3,13 @@ #include #include "ratelimiter.h" +// Extra milliseconds allowed on the upper bound of timing checks, +// for slow or loaded machines such as shared CI runners +#ifndef TEST_TIME_SLACK_MS +#define TEST_TIME_SLACK_MS 0 +#endif +const long kTimeSlackMs = TEST_TIME_SLACK_MS; + long get_time_ms() { auto duration = std::chrono::steady_clock::now().time_since_epoch(); auto ms = std::chrono::duration_cast(duration).count(); @@ -11,7 +18,7 @@ long get_time_ms() { TEST(RateLimiterTest, AcquireWithinZeroLimit) { xflow1cpp::RateLimiter rl(0.0, 1000); - EXPECT_TRUE(rl.acquire(1)); + EXPECT_FALSE(rl.acquire(1)); } TEST(RateLimiterTest, AcquireWithinLimit) { @@ -19,7 +26,7 @@ TEST(RateLimiterTest, AcquireWithinLimit) { auto t0 = get_time_ms(); EXPECT_TRUE(rl.acquire(1)); auto t1 = get_time_ms(); - EXPECT_TRUE(t1 - t0 < 100); + EXPECT_LT(t1 - t0, 100 + kTimeSlackMs); } TEST(RateLimiterTest, RejectsNegativeRequests) { @@ -38,8 +45,8 @@ TEST(RateLimiterTest, AcquireWait2x) { EXPECT_TRUE(my_rl.acquire(8)); EXPECT_TRUE(my_rl.acquire(9)); auto t1 = get_time_ms(); - EXPECT_TRUE(t1 - t0 >= 17 / 10.0 * 1000); - EXPECT_TRUE(t1 - t0 < 18 / 10.0 * 1000); + EXPECT_GE(t1 - t0, 17 / 10.0 * 1000); + EXPECT_LT(t1 - t0, 18 / 10.0 * 1000 + kTimeSlackMs); } TEST(RateLimiterTest, AcquireWithinLimit3x) { @@ -49,7 +56,7 @@ TEST(RateLimiterTest, AcquireWithinLimit3x) { EXPECT_TRUE(my_rl.acquire(2)); EXPECT_TRUE(my_rl.acquire(2)); auto t1 = get_time_ms(); - assert(t1 - t0 < 100); + EXPECT_LT(t1 - t0, 100 + kTimeSlackMs); } /* This test is flaky: @@ -67,8 +74,8 @@ TEST(RateLimiterTest, AcquireWithinLimit3xNoBurst) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); EXPECT_TRUE(my_rl.acquire(9)); auto t1 = get_time_ms(); - EXPECT_TRUE(t1 - t0 >= 13 / 10.0 * 1000); - EXPECT_TRUE(t1 - t0 < 14 / 10.0 * 1000); + EXPECT_GE(t1 - t0, 13 / 10.0 * 1000); + EXPECT_LT(t1 - t0, 14 / 10.0 * 1000 + kTimeSlackMs); } TEST(RateLimiterTest, AcquireWithinLimit3xWithDelays) { @@ -80,8 +87,8 @@ TEST(RateLimiterTest, AcquireWithinLimit3xWithDelays) { std::this_thread::sleep_for(std::chrono::milliseconds(2000)); EXPECT_TRUE(my_rl.acquire(9)); auto t1 = get_time_ms(); - EXPECT_TRUE(t1 - t0 >= 4000); // no wait if sleep_for(2000) twice - EXPECT_TRUE(t1 - t0 < 4500); + EXPECT_GE(t1 - t0, 4000); // no wait if sleep_for(2000) twice + EXPECT_LT(t1 - t0, 4500 + kTimeSlackMs); } TEST(RateLimiterTest, RejectOverBurstInThread) { @@ -101,8 +108,8 @@ TEST(RateLimiterTest, InfiniteLoopFixWithCeil) { EXPECT_TRUE(my_rl.acquire(4)); EXPECT_TRUE(my_rl.acquire(3)); auto t1 = get_time_ms(); - EXPECT_TRUE(t1 - t0 >= 2333); // total 7 reqs when rps=3 - EXPECT_TRUE(t1 - t0 < 2500); + EXPECT_GE(t1 - t0, 2333); // total 7 reqs when rps=3 + EXPECT_LT(t1 - t0, 2500 + kTimeSlackMs); } TEST(RateLimiterTest, AcquireBigRequestOverLimit) { @@ -112,8 +119,8 @@ TEST(RateLimiterTest, AcquireBigRequestOverLimit) { std::this_thread::sleep_for(std::chrono::milliseconds(2)); EXPECT_TRUE(my_rl.acquire(1)); auto t1 = get_time_ms(); - EXPECT_TRUE(t1 - t0 >= 100 / 3.0 * 1000); // total 100 reqs when rps=3 - EXPECT_TRUE(t1 - t0 < 101 / 3.0 * 1000); + EXPECT_GE(t1 - t0, 100 / 3.0 * 1000); // total 100 reqs when rps=3 + EXPECT_LT(t1 - t0, 101 / 3.0 * 1000 + kTimeSlackMs); } TEST(RateLimiterTest, AcquireMultipleRequestsOverLimit) { @@ -141,8 +148,8 @@ TEST(RateLimiterTest, AcquireMultipleRequestsOverLimit) { std::this_thread::sleep_for(std::chrono::milliseconds(2)); EXPECT_TRUE(my_rl.acquire(1)); auto t1 = get_time_ms(); - EXPECT_TRUE(t1 - t0 >= 11 / 3.0 * 1000); - EXPECT_TRUE(t1 - t0 < 12 / 3.0 * 1000); + EXPECT_GE(t1 - t0, 11 / 3.0 * 1000); + EXPECT_LT(t1 - t0, 12 / 3.0 * 1000 + kTimeSlackMs); } TEST(RateLimiterTest, AcquireMultipleRequestsEachOverLimit) { @@ -160,8 +167,8 @@ TEST(RateLimiterTest, AcquireMultipleRequestsEachOverLimit) { std::this_thread::sleep_for(std::chrono::milliseconds(2)); EXPECT_TRUE(my_rl.acquire(19)); auto t1 = get_time_ms(); - EXPECT_TRUE(t1 - t0 >= 114 / 3.0 * 1000); // total 100 reqs when rps=3 - EXPECT_TRUE(t1 - t0 < 115 / 3.0 * 1000); + EXPECT_GE(t1 - t0, 114 / 3.0 * 1000); // total 100 reqs when rps=3 + EXPECT_LT(t1 - t0, 115 / 3.0 * 1000 + kTimeSlackMs); } TEST(RateLimiterTest, AcquireWithinLimitInThreads) { @@ -177,8 +184,8 @@ TEST(RateLimiterTest, AcquireWithinLimitInThreads) { th1.join(); th2.join(); auto t1 = get_time_ms(); - EXPECT_TRUE(t1 - t0 >= 12 / 3.0 * 1000); - EXPECT_TRUE(t1 - t0 < 13 / 3.0 * 1000); + EXPECT_GE(t1 - t0, 12 / 3.0 * 1000); + EXPECT_LT(t1 - t0, 13 / 3.0 * 1000 + kTimeSlackMs); } TEST(RateLimiterTest, AcquireWithinLimitInThreadsWithDelay) { @@ -195,8 +202,8 @@ TEST(RateLimiterTest, AcquireWithinLimitInThreadsWithDelay) { th1.join(); th2.join(); auto t1 = get_time_ms(); - EXPECT_TRUE(t1 - t0 >= 12 / 3.0 * 1000); - EXPECT_TRUE(t1 - t0 < 13 / 3.0 * 1000); + EXPECT_GE(t1 - t0, 12 / 3.0 * 1000); + EXPECT_LT(t1 - t0, 13 / 3.0 * 1000 + kTimeSlackMs); } TEST(RateLimiterTest, AcquireWaitForLimitInThreadsWithDelay) { @@ -213,8 +220,8 @@ TEST(RateLimiterTest, AcquireWaitForLimitInThreadsWithDelay) { th1.join(); th2.join(); auto t1 = get_time_ms(); - EXPECT_TRUE(t1 - t0 >= 30 / 10.0 * 1000); - EXPECT_TRUE(t1 - t0 < 32 / 10.0 * 1000); // '32' for extra time to cover sleep_for + EXPECT_GE(t1 - t0, 30 / 10.0 * 1000); + EXPECT_LT(t1 - t0, 32 / 10.0 * 1000 + kTimeSlackMs); // '32' for extra time to cover sleep_for } TEST(RateLimiterTest, AcquireInThreadsWithDelayedStart) { @@ -240,8 +247,8 @@ TEST(RateLimiterTest, AcquireInThreadsWithDelayedStart) { th1.join(); th2.join(); auto t1 = get_time_ms(); - EXPECT_TRUE(t1 - t0 >= 7400); - EXPECT_TRUE(t1 - t0 < 7900); // approx upper bound + EXPECT_GE(t1 - t0, 7400); + EXPECT_LT(t1 - t0, 7900 + kTimeSlackMs); // approx upper bound } } @@ -254,6 +261,6 @@ TEST(RateLimiterTest, AcquireWithTotalReqsDecayTotalReqs) { EXPECT_TRUE(my_rl.acquire(9)); EXPECT_TRUE(my_rl.acquire(9)); auto t1 = get_time_ms(); - EXPECT_TRUE(t1 - t0 >= 45 / 3.0 * 1000); // sleep_for(2000) not included - EXPECT_TRUE(t1 - t0 < 46 / 3.0 * 1000); + EXPECT_GE(t1 - t0, 45 / 3.0 * 1000); // sleep_for(2000) not included + EXPECT_LT(t1 - t0, 46 / 3.0 * 1000 + kTimeSlackMs); }