1919
2020#include " iceberg/util/retry_util.h"
2121
22+ #include < chrono>
23+ #include < thread>
24+
2225#include < gtest/gtest.h>
2326
2427#include " iceberg/result.h"
2528#include " iceberg/test/matchers.h"
2629
2730namespace iceberg {
2831
29- // --------------------------------------------------------------------------
30- // Test: Successful on first attempt — no retries
31- // --------------------------------------------------------------------------
3232TEST (RetryRunnerTest, SuccessOnFirstAttempt) {
3333 int call_count = 0 ;
3434 int32_t attempts = 0 ;
@@ -50,9 +50,6 @@ TEST(RetryRunnerTest, SuccessOnFirstAttempt) {
5050 EXPECT_EQ (attempts, 1 );
5151}
5252
53- // --------------------------------------------------------------------------
54- // Test: Retry once then succeed
55- // --------------------------------------------------------------------------
5653TEST (RetryRunnerTest, RetryOnceThenSucceed) {
5754 int call_count = 0 ;
5855 int32_t attempts = 0 ;
@@ -77,9 +74,6 @@ TEST(RetryRunnerTest, RetryOnceThenSucceed) {
7774 EXPECT_EQ (attempts, 2 );
7875}
7976
80- // --------------------------------------------------------------------------
81- // Test: Max attempts exhausted
82- // --------------------------------------------------------------------------
8377TEST (RetryRunnerTest, MaxAttemptsExhausted) {
8478 int call_count = 0 ;
8579 int32_t attempts = 0 ;
@@ -96,13 +90,10 @@ TEST(RetryRunnerTest, MaxAttemptsExhausted) {
9690 &attempts);
9791
9892 EXPECT_THAT (result, IsError (ErrorKind::kCommitFailed ));
99- EXPECT_EQ (call_count, 3 ); // 1 initial + 2 retries
93+ EXPECT_EQ (call_count, 3 );
10094 EXPECT_EQ (attempts, 3 );
10195}
10296
103- // --------------------------------------------------------------------------
104- // Test: OnlyRetryOn filters correctly
105- // --------------------------------------------------------------------------
10697TEST (RetryRunnerTest, OnlyRetryOnFilter) {
10798 int call_count = 0 ;
10899 int32_t attempts = 0 ;
@@ -115,20 +106,15 @@ TEST(RetryRunnerTest, OnlyRetryOnFilter) {
115106 .Run (
116107 [&]() -> Result<int > {
117108 ++call_count;
118- // Return a non-retryable error
119109 return ValidationFailed (" schema conflict" );
120110 },
121111 &attempts);
122112
123- // Should NOT retry because ValidationFailed is not in the retry list
124113 EXPECT_THAT (result, IsError (ErrorKind::kValidationFailed ));
125114 EXPECT_EQ (call_count, 1 );
126115 EXPECT_EQ (attempts, 1 );
127116}
128117
129- // --------------------------------------------------------------------------
130- // Test: OnlyRetryOn retries matching error
131- // --------------------------------------------------------------------------
132118TEST (RetryRunnerTest, OnlyRetryOnMatchingError) {
133119 int call_count = 0 ;
134120 int32_t attempts = 0 ;
@@ -150,13 +136,10 @@ TEST(RetryRunnerTest, OnlyRetryOnMatchingError) {
150136
151137 EXPECT_THAT (result, IsOk ());
152138 EXPECT_EQ (*result, 100 );
153- EXPECT_EQ (call_count, 3 ); // 2 failures + 1 success
139+ EXPECT_EQ (call_count, 3 );
154140 EXPECT_EQ (attempts, 3 );
155141}
156142
157- // --------------------------------------------------------------------------
158- // Test: StopRetryOn stops on matching error
159- // --------------------------------------------------------------------------
160143TEST (RetryRunnerTest, StopRetryOnMatchingError) {
161144 int call_count = 0 ;
162145 int32_t attempts = 0 ;
@@ -178,9 +161,6 @@ TEST(RetryRunnerTest, StopRetryOnMatchingError) {
178161 EXPECT_EQ (attempts, 1 );
179162}
180163
181- // --------------------------------------------------------------------------
182- // Test: Zero retries means only one attempt
183- // --------------------------------------------------------------------------
184164TEST (RetryRunnerTest, ZeroRetries) {
185165 int call_count = 0 ;
186166 int32_t attempts = 0 ;
@@ -201,19 +181,40 @@ TEST(RetryRunnerTest, ZeroRetries) {
201181 EXPECT_EQ (attempts, 1 );
202182}
203183
204- // --------------------------------------------------------------------------
205- // Test: MakeCommitRetryRunner has correct configuration
206- // --------------------------------------------------------------------------
184+ TEST (RetryRunnerTest, TotalTimeoutStopsBeforeStartingAnotherAttempt) {
185+ int call_count = 0 ;
186+ int32_t attempts = 0 ;
187+
188+ auto result = RetryRunner (RetryConfig{.num_retries = 3 ,
189+ .min_wait_ms = 20 ,
190+ .max_wait_ms = 20 ,
191+ .total_timeout_ms = 15 })
192+ .Run (
193+ [&]() -> Result<int > {
194+ ++call_count;
195+ // The first failure consumes most of the 15 ms budget, so the
196+ // next 20 ms backoff should prevent another attempt from
197+ // starting.
198+ if (call_count == 1 ) {
199+ std::this_thread::sleep_for (std::chrono::milliseconds (10 ));
200+ }
201+ return CommitFailed (" retry budget exhausted" );
202+ },
203+ &attempts);
204+
205+ EXPECT_THAT (result, IsError (ErrorKind::kCommitFailed ));
206+ EXPECT_EQ (call_count, 1 );
207+ EXPECT_EQ (attempts, 1 );
208+ }
209+
207210TEST (RetryRunnerTest, MakeCommitRetryRunnerConfig) {
208211 int call_count = 0 ;
209212 int32_t attempts = 0 ;
210213
211- // MakeCommitRetryRunner should only retry on kCommitFailed
212214 auto result = MakeCommitRetryRunner (2 , 1 , 10 , 5000 )
213215 .Run (
214216 [&]() -> Result<int > {
215217 ++call_count;
216- // ValidationFailed should not be retried
217218 return ValidationFailed (" not retryable" );
218219 },
219220 &attempts);
@@ -223,9 +224,6 @@ TEST(RetryRunnerTest, MakeCommitRetryRunnerConfig) {
223224 EXPECT_EQ (attempts, 1 );
224225}
225226
226- // --------------------------------------------------------------------------
227- // Test: MakeCommitRetryRunner retries CommitFailed
228- // --------------------------------------------------------------------------
229227TEST (RetryRunnerTest, MakeCommitRetryRunnerRetriesCommitFailed) {
230228 int call_count = 0 ;
231229 int32_t attempts = 0 ;
@@ -247,9 +245,6 @@ TEST(RetryRunnerTest, MakeCommitRetryRunnerRetriesCommitFailed) {
247245 EXPECT_EQ (attempts, 3 );
248246}
249247
250- // --------------------------------------------------------------------------
251- // Test: OnlyRetryOn with multiple error kinds
252- // --------------------------------------------------------------------------
253248TEST (RetryRunnerTest, OnlyRetryOnMultipleErrorKinds) {
254249 int call_count = 0 ;
255250 int32_t attempts = 0 ;
@@ -279,9 +274,6 @@ TEST(RetryRunnerTest, OnlyRetryOnMultipleErrorKinds) {
279274 EXPECT_EQ (attempts, 3 );
280275}
281276
282- // --------------------------------------------------------------------------
283- // Test: Default retry (no filter) retries all errors
284- // --------------------------------------------------------------------------
285277TEST (RetryRunnerTest, DefaultRetryAllErrors) {
286278 int call_count = 0 ;
287279 int32_t attempts = 0 ;
0 commit comments