diff --git a/driver-core/src/main/com/mongodb/internal/async/function/RetryState.java b/driver-core/src/main/com/mongodb/internal/async/function/RetryState.java index 74504a1d9b..f14f4d2948 100644 --- a/driver-core/src/main/com/mongodb/internal/async/function/RetryState.java +++ b/driver-core/src/main/com/mongodb/internal/async/function/RetryState.java @@ -132,7 +132,7 @@ private RetryState(final int retries, final boolean retryUntilTimeoutThrowsExcep * per attempt and only if all the following is true: * * The {@code retryPredicate} accepts this {@link RetryState} and the exception from the most recent attempt, * and may mutate the exception. The {@linkplain RetryState} advances to represent the state of a new attempt @@ -140,7 +140,7 @@ private RetryState(final int retries, final boolean retryUntilTimeoutThrowsExcep * @throws RuntimeException Iff any of the following is true: * @@ -187,24 +187,10 @@ private void doAdvanceOrThrow(final Throwable attemptException, } assertTrue(!isFirstAttempt() || previouslyChosenException == null); Throwable newlyChosenException = callOnAttemptFailureOperator(previouslyChosenException, attemptException, onlyRuntimeExceptions, onAttemptFailureOperator); - - /* - * A MongoOperationTimeoutException indicates that the operation timed out, either during command execution or server selection. - * The timeout for server selection is determined by the computedServerSelectionMS = min(serverSelectionTimeoutMS, timeoutMS). - * - * It is important to check if the exception is an instance of MongoOperationTimeoutException to detect a timeout. - */ - if (isLastAttempt() || attemptException instanceof MongoOperationTimeoutException) { + if (isLastAttempt(attemptException)) { previouslyChosenException = newlyChosenException; - /* - * The function of isLastIteration() is to indicate if retrying has - * been explicitly halted. Such a stop is not interpreted as - * a timeout exception but as a deliberate cessation of retry attempts. - */ - if (retryUntilTimeoutThrowsException && !loopState.isLastIteration()) { - previouslyChosenException = createMongoTimeoutException( - "Retry attempt exceeded the timeout limit.", - previouslyChosenException); + if (attemptException instanceof MongoOperationTimeoutException) { + previouslyChosenException = createMongoTimeoutException("Retry attempt exceeded the timeout limit.", previouslyChosenException); } throw previouslyChosenException; } else { @@ -365,27 +351,23 @@ public boolean isFirstAttempt() { * An attempt is known to be the last one iff any of the following applies: * * * @see #attempt() */ - public boolean isLastAttempt() { - if (loopState.isLastIteration()) { - return true; - } - if (retryUntilTimeoutThrowsException) { - return false; - } - return attempt() == attempts - 1; + private boolean isLastAttempt(final Throwable attemptException) { + boolean operationTimeout = retryUntilTimeoutThrowsException && attemptException instanceof MongoOperationTimeoutException; + boolean attemptLimit = attempt() == attempts - 1; + assertFalse(operationTimeout && attemptLimit); + return loopState.isLastIteration() || operationTimeout || attemptLimit; } /** * A 0-based attempt number. * * @see #isFirstAttempt() - * @see #isLastAttempt() */ public int attempt() { return loopState.iteration(); diff --git a/driver-core/src/main/com/mongodb/internal/async/function/RetryingAsyncCallbackSupplier.java b/driver-core/src/main/com/mongodb/internal/async/function/RetryingAsyncCallbackSupplier.java index 16f6f2e708..6ce08513aa 100644 --- a/driver-core/src/main/com/mongodb/internal/async/function/RetryingAsyncCallbackSupplier.java +++ b/driver-core/src/main/com/mongodb/internal/async/function/RetryingAsyncCallbackSupplier.java @@ -69,7 +69,7 @@ public final class RetryingAsyncCallbackSupplier implements AsyncCallbackSupp * per attempt and only if all the following is true: * * The {@code retryPredicate} accepts this {@link RetryState} and the exception from the most recent attempt, * and may mutate the exception. The {@linkplain RetryState} advances to represent the state of a new attempt diff --git a/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryStateTest.java b/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryStateTest.java index 40a2033249..e66e131522 100644 --- a/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryStateTest.java +++ b/driver-core/src/test/unit/com/mongodb/internal/async/function/RetryStateTest.java @@ -21,6 +21,7 @@ import com.mongodb.internal.TimeoutSettings; import com.mongodb.internal.async.function.LoopState.AttachmentKey; import com.mongodb.internal.operation.retry.AttachmentKeys; +import com.mongodb.lang.Nullable; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -28,11 +29,15 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import java.util.function.BiPredicate; +import java.util.function.BinaryOperator; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -73,41 +78,36 @@ static Stream noTimeout() { @ParameterizedTest @MethodSource({"infiniteTimeout", "noTimeout"}) void unlimitedAttemptsAndAdvance(final TimeoutContext timeoutContext) { + RuntimeException attemptException = new RuntimeException(); RetryState retryState = new RetryState(timeoutContext); assertAll( () -> assertTrue(retryState.isFirstAttempt()), - () -> assertEquals(0, retryState.attempt()), - () -> assertFalse(retryState.isLastAttempt()) + () -> assertEquals(0, retryState.attempt()) ); - advance(retryState); + retryState.advanceOrThrow(attemptException, (e1, e2) -> e2, (rs, e) -> true); assertAll( () -> assertFalse(retryState.isFirstAttempt()), - () -> assertEquals(1, retryState.attempt()), - () -> assertFalse(retryState.isLastAttempt()) + () -> assertEquals(1, retryState.attempt()) ); retryState.markAsLastAttempt(); assertAll( () -> assertFalse(retryState.isFirstAttempt()), () -> assertEquals(1, retryState.attempt()), - () -> assertTrue(retryState.isLastAttempt()) + () -> assertAdvanceOrThrow(attemptException, retryState, attemptException) ); } @Test void limitedAttemptsAndAdvance() { RetryState retryState = RetryState.withNonRetryableState(); - RuntimeException attemptException = new RuntimeException() { - }; + RuntimeException attemptException = new RuntimeException(); assertAll( () -> assertTrue(retryState.isFirstAttempt()), () -> assertEquals(0, retryState.attempt()), - () -> assertTrue(retryState.isLastAttempt()), - () -> assertThrows(attemptException.getClass(), () -> - retryState.advanceOrThrow(attemptException, (e1, e2) -> e2, (rs, e) -> true)), + () -> assertAdvanceOrThrow(attemptException, retryState, attemptException), // when there is only one attempt, it is both the first and the last one () -> assertTrue(retryState.isFirstAttempt()), - () -> assertEquals(0, retryState.attempt()), - () -> assertTrue(retryState.isLastAttempt()) + () -> assertEquals(0, retryState.attempt()) ); } @@ -116,11 +116,8 @@ void limitedAttemptsAndAdvance() { void markAsLastAttemptAdvanceWithRuntimeException(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); retryState.markAsLastAttempt(); - assertTrue(retryState.isLastAttempt()); - RuntimeException attemptException = new RuntimeException() { - }; - assertThrows(attemptException.getClass(), - () -> retryState.advanceOrThrow(attemptException, (e1, e2) -> e2, (rs, e) -> fail())); + RuntimeException attemptException = new RuntimeException(); + assertAdvanceOrThrow(attemptException, retryState, attemptException, (rs, e) -> fail()); } @ParameterizedTest(name = "should advance with non-retryable error when marked as last attempt and : ''{0}''") @@ -128,11 +125,8 @@ void markAsLastAttemptAdvanceWithRuntimeException(final TimeoutContext timeoutCo void markAsLastAttemptAdvanceWithError(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); retryState.markAsLastAttempt(); - assertTrue(retryState.isLastAttempt()); - Error attemptException = new Error() { - }; - assertThrows(attemptException.getClass(), - () -> retryState.advanceOrThrow(attemptException, (e1, e2) -> e2, (rs, e) -> fail())); + Error attemptException = new Error(); + assertAdvanceOrThrow(attemptException, retryState, attemptException, (rs, e) -> fail()); } @ParameterizedTest @@ -140,7 +134,7 @@ void markAsLastAttemptAdvanceWithError(final TimeoutContext timeoutContext) { void breakAndThrowIfRetryAndFirstAttempt(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); retryState.breakAndThrowIfRetryAnd(Assertions::fail); - assertFalse(retryState.isLastAttempt()); + assertAdvanceOrThrow(null, retryState, new RuntimeException()); } @ParameterizedTest @@ -149,26 +143,27 @@ void breakAndThrowIfRetryAndFalse(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); advance(retryState); retryState.breakAndThrowIfRetryAnd(() -> false); - assertFalse(retryState.isLastAttempt()); + assertAdvanceOrThrow(null, retryState, new RuntimeException()); } @ParameterizedTest @MethodSource({"infiniteTimeout", "noTimeout"}) - void breakAndThrowIfRetryAndTrue() { - RetryState retryState = new RetryState(TIMEOUT_CONTEXT_NO_GLOBAL_TIMEOUT); + void breakAndThrowIfRetryAndTrue(final TimeoutContext timeoutContext) { + RetryState retryState = new RetryState(timeoutContext); advance(retryState); assertThrows(RuntimeException.class, () -> retryState.breakAndThrowIfRetryAnd(() -> true)); - assertTrue(retryState.isLastAttempt()); + RuntimeException attemptException = new RuntimeException(); + assertAdvanceOrThrow(attemptException, retryState, attemptException); } @Test void breakAndThrowIfRetryAndTrueWithExpiredTimeout() { TimeoutContext tContextMock = mock(TimeoutContext.class); - RetryState retryState = new RetryState(tContextMock); advance(retryState); assertThrows(RuntimeException.class, () -> retryState.breakAndThrowIfRetryAnd(() -> true)); - assertTrue(retryState.isLastAttempt()); + RuntimeException attemptException = new RuntimeException(); + assertAdvanceOrThrow(attemptException, retryState, attemptException); } @ParameterizedTest @@ -176,12 +171,13 @@ void breakAndThrowIfRetryAndTrueWithExpiredTimeout() { void breakAndThrowIfRetryIfPredicateThrows(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); advance(retryState); - RuntimeException e = new RuntimeException() { - }; - assertThrows(e.getClass(), () -> retryState.breakAndThrowIfRetryAnd(() -> { - throw e; - })); - assertFalse(retryState.isLastAttempt()); + RuntimeException exception = new RuntimeException(); + assertEquals( + exception, + assertThrows(exception.getClass(), () -> retryState.breakAndThrowIfRetryAnd(() -> { + throw exception; + }))); + assertAdvanceOrThrow(null, retryState, exception); } @ParameterizedTest @@ -191,7 +187,7 @@ void breakAndCompleteIfRetryAndFirstAttempt(final TimeoutContext timeoutContext) SupplyingCallback callback = new SupplyingCallback<>(); assertFalse(retryState.breakAndCompleteIfRetryAnd(Assertions::fail, callback)); assertFalse(callback.completed()); - assertFalse(retryState.isLastAttempt()); + assertAdvanceOrThrow(null, retryState, new RuntimeException()); } @ParameterizedTest @@ -202,7 +198,7 @@ void breakAndCompleteIfRetryAndFalse(final TimeoutContext timeoutContext) { SupplyingCallback callback = new SupplyingCallback<>(); assertFalse(retryState.breakAndCompleteIfRetryAnd(() -> false, callback)); assertFalse(callback.completed()); - assertFalse(retryState.isLastAttempt()); + assertAdvanceOrThrow(null, retryState, new RuntimeException()); } @ParameterizedTest @@ -213,7 +209,8 @@ void breakAndCompleteIfRetryAndTrue(final TimeoutContext timeoutContext) { SupplyingCallback callback = new SupplyingCallback<>(); assertTrue(retryState.breakAndCompleteIfRetryAnd(() -> true, callback)); assertThrows(RuntimeException.class, callback::get); - assertTrue(retryState.isLastAttempt()); + RuntimeException attemptException = new RuntimeException(); + assertAdvanceOrThrow(attemptException, retryState, attemptException); } @ParameterizedTest @@ -221,23 +218,23 @@ void breakAndCompleteIfRetryAndTrue(final TimeoutContext timeoutContext) { void breakAndCompleteIfRetryAndPredicateThrows(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); advance(retryState); - Error e = new Error() { - }; + Error exception = new Error(); SupplyingCallback callback = new SupplyingCallback<>(); assertTrue(retryState.breakAndCompleteIfRetryAnd(() -> { - throw e; + throw exception; }, callback)); - assertThrows(e.getClass(), callback::get); - assertFalse(retryState.isLastAttempt()); + assertEquals( + exception, + assertThrows(exception.getClass(), callback::get)); + assertAdvanceOrThrow(null, retryState, exception); } @ParameterizedTest @MethodSource({"infiniteTimeout", "noTimeout"}) void advanceOrThrowPredicateFalse(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); - RuntimeException attemptException = new RuntimeException() { - }; - assertThrows(attemptException.getClass(), () -> retryState.advanceOrThrow(attemptException, (e1, e2) -> e2, (rs, e) -> false)); + RuntimeException attemptException = new RuntimeException(); + assertAdvanceOrThrow(attemptException, retryState, attemptException, (rs, e) -> false); } @ParameterizedTest @@ -247,34 +244,30 @@ void advanceReThrowDetectedTimeoutExceptionEvenIfTimeoutInRetryStateIsNotExpired RetryState retryState = new RetryState(timeoutContext); MongoOperationTimeoutException expectedTimeoutException = TimeoutContext.createMongoTimeoutException("Server selection failed"); - MongoOperationTimeoutException actualTimeoutException = - assertThrows(expectedTimeoutException.getClass(), () -> retryState.advanceOrThrow(expectedTimeoutException, - (e1, e2) -> expectedTimeoutException, - (rs, e) -> false)); - - Assertions.assertEquals(actualTimeoutException, expectedTimeoutException); + assertAdvanceOrThrow(expectedTimeoutException, retryState, expectedTimeoutException, + (e1, e2) -> expectedTimeoutException, + (rs, e) -> false); } @Test @DisplayName("should throw timeout exception from retry, when transformer swallows original timeout exception") void advanceThrowTimeoutExceptionWhenTransformerSwallowOriginalTimeoutException() { RetryState retryState = new RetryState(TIMEOUT_CONTEXT_INFINITE_GLOBAL_TIMEOUT); - RuntimeException previousAttemptException = new RuntimeException() { - }; - MongoOperationTimeoutException expectedTimeoutException = TimeoutContext.createMongoTimeoutException("Server selection failed"); + RuntimeException previousAttemptException = new RuntimeException(); + MongoOperationTimeoutException unexpectedTimeoutException = TimeoutContext.createMongoTimeoutException("Server selection failed"); retryState.advanceOrThrow(previousAttemptException, (e1, e2) -> previousAttemptException, (rs, e) -> true); MongoOperationTimeoutException actualTimeoutException = - assertThrows(expectedTimeoutException.getClass(), () -> retryState.advanceOrThrow(expectedTimeoutException, + assertThrows(unexpectedTimeoutException.getClass(), () -> retryState.advanceOrThrow(unexpectedTimeoutException, (e1, e2) -> previousAttemptException, (rs, e) -> false)); - Assertions.assertNotEquals(actualTimeoutException, expectedTimeoutException); - Assertions.assertEquals(EXPECTED_TIMEOUT_MESSAGE, actualTimeoutException.getMessage()); - Assertions.assertEquals(previousAttemptException, actualTimeoutException.getCause(), + assertNotEquals(unexpectedTimeoutException, actualTimeoutException); + assertEquals(EXPECTED_TIMEOUT_MESSAGE, actualTimeoutException.getMessage()); + assertEquals(previousAttemptException, actualTimeoutException.getCause(), "Retry timeout exception should have a cause if transformer returned non-timeout exception."); } @@ -283,8 +276,7 @@ void advanceThrowTimeoutExceptionWhenTransformerSwallowOriginalTimeoutException( @DisplayName("should throw original timeout exception from retry, when transformer returns original timeout exception") void advanceThrowOriginalTimeoutExceptionWhenTransformerReturnsOriginalTimeoutException() { RetryState retryState = new RetryState(TIMEOUT_CONTEXT_INFINITE_GLOBAL_TIMEOUT); - RuntimeException previousAttemptException = new RuntimeException() { - }; + RuntimeException previousAttemptException = new RuntimeException(); MongoOperationTimeoutException expectedTimeoutException = TimeoutContext .createMongoTimeoutException("Server selection failed"); @@ -292,44 +284,37 @@ void advanceThrowOriginalTimeoutExceptionWhenTransformerReturnsOriginalTimeoutEx (e1, e2) -> previousAttemptException, (rs, e) -> true); - MongoOperationTimeoutException actualTimeoutException = - assertThrows(expectedTimeoutException.getClass(), () -> retryState.advanceOrThrow(expectedTimeoutException, - (e1, e2) -> expectedTimeoutException, - (rs, e) -> false)); - - Assertions.assertEquals(actualTimeoutException, expectedTimeoutException); - Assertions.assertNull(actualTimeoutException.getCause(), - "Original timeout exception should not have a cause if transformer already returned timeout exception."); + assertAdvanceOrThrow(expectedTimeoutException, retryState, expectedTimeoutException, + (e1, e2) -> expectedTimeoutException, + (rs, e) -> false); } @Test void advanceOrThrowPredicateTrueAndLastAttempt() { RetryState retryState = RetryState.withNonRetryableState(); - Error attemptException = new Error() { - }; - assertThrows(attemptException.getClass(), () -> retryState.advanceOrThrow(attemptException, (e1, e2) -> e2, (rs, e) -> true)); + Error attemptException = new Error(); + assertAdvanceOrThrow(attemptException, retryState, attemptException); } @ParameterizedTest @MethodSource({"infiniteTimeout", "noTimeout"}) void advanceOrThrowPredicateThrowsAfterFirstAttempt(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); - RuntimeException predicateException = new RuntimeException() { - }; - RuntimeException attemptException = new RuntimeException() { - }; - assertThrows(predicateException.getClass(), () -> retryState.advanceOrThrow(attemptException, (e1, e2) -> e2, (rs, e) -> { - assertTrue(rs.isFirstAttempt()); - assertEquals(attemptException, e); - throw predicateException; - })); + RuntimeException predicateException = new RuntimeException(); + RuntimeException attemptException = new RuntimeException(); + assertAdvanceOrThrow(predicateException, retryState, attemptException, + (e1, e2) -> e2, + (rs, e) -> { + assertTrue(rs.isFirstAttempt()); + assertEquals(attemptException, e); + throw predicateException; + }); } @Test void advanceOrThrowPredicateThrowsTimeoutAfterFirstAttempt() { RetryState retryState = new RetryState(TIMEOUT_CONTEXT_EXPIRED_GLOBAL_TIMEOUT); - RuntimeException predicateException = new RuntimeException() { - }; + RuntimeException predicateException = new RuntimeException(); RuntimeException attemptException = new MongoOperationTimeoutException(EXPECTED_TIMEOUT_MESSAGE); MongoOperationTimeoutException mongoOperationTimeoutException = assertThrows(MongoOperationTimeoutException.class, () -> retryState.advanceOrThrow(attemptException, (e1, e2) -> e2, (rs, e) -> { @@ -346,58 +331,52 @@ void advanceOrThrowPredicateThrowsTimeoutAfterFirstAttempt() { @MethodSource({"infiniteTimeout", "noTimeout"}) void advanceOrThrowPredicateThrows(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); - RuntimeException firstAttemptException = new RuntimeException() { - }; + RuntimeException firstAttemptException = new RuntimeException(); retryState.advanceOrThrow(firstAttemptException, (e1, e2) -> e2, (rs, e) -> true); - RuntimeException secondAttemptException = new RuntimeException() { - }; - RuntimeException predicateException = new RuntimeException() { - }; - assertThrows(predicateException.getClass(), () -> retryState.advanceOrThrow(secondAttemptException, (e1, e2) -> e2, (rs, e) -> { - assertEquals(1, rs.attempt()); - assertEquals(secondAttemptException, e); - throw predicateException; - })); + RuntimeException secondAttemptException = new RuntimeException(); + RuntimeException predicateException = new RuntimeException(); + assertAdvanceOrThrow(predicateException, retryState, secondAttemptException, + (e1, e2) -> e2, + (rs, e) -> { + assertEquals(1, rs.attempt()); + assertEquals(secondAttemptException, e); + throw predicateException; + }); } @ParameterizedTest @MethodSource({"infiniteTimeout", "noTimeout", "expiredTimeout"}) void advanceOrThrowTransformerThrowsAfterFirstAttempt(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); - RuntimeException transformerException = new RuntimeException() { - }; - assertThrows(transformerException.getClass(), () -> retryState.advanceOrThrow(new AssertionError(), + RuntimeException transformerException = new RuntimeException(); + assertAdvanceOrThrow(transformerException, retryState, new AssertionError(), (e1, e2) -> { throw transformerException; }, - (rs, e) -> fail())); + (rs, e) -> fail()); } @ParameterizedTest - @MethodSource({"infiniteTimeout", "noTimeout"}) //TODO mock? + @MethodSource({"infiniteTimeout", "noTimeout"}) void advanceOrThrowTransformerThrows(final TimeoutContext timeoutContext) throws Throwable { RetryState retryState = new RetryState(timeoutContext); - Error firstAttemptException = new Error() { - }; + Error firstAttemptException = new Error(); retryState.advanceOrThrow(firstAttemptException, (e1, e2) -> e2, (rs, e) -> true); - RuntimeException transformerException = new RuntimeException() { - }; - assertThrows(transformerException.getClass(), () -> retryState.advanceOrThrow(new AssertionError(), + RuntimeException transformerException = new RuntimeException(); + assertAdvanceOrThrow(transformerException, retryState, new AssertionError(), (e1, e2) -> { throw transformerException; }, - (rs, e) -> fail())); + (rs, e) -> fail()); } @ParameterizedTest @MethodSource({"infiniteTimeout", "noTimeout"}) void advanceOrThrowTransformAfterFirstAttempt(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); - RuntimeException attemptException = new RuntimeException() { - }; - RuntimeException transformerResult = new RuntimeException() { - }; - assertThrows(transformerResult.getClass(), () -> retryState.advanceOrThrow(attemptException, + RuntimeException attemptException = new RuntimeException(); + RuntimeException transformerResult = new RuntimeException(); + assertAdvanceOrThrow(transformerResult, retryState, attemptException, (e1, e2) -> { assertNull(e1); assertEquals(attemptException, e2); @@ -406,7 +385,7 @@ void advanceOrThrowTransformAfterFirstAttempt(final TimeoutContext timeoutContex (rs, e) -> { assertEquals(attemptException, e); return false; - })); + }); } @Test @@ -436,14 +415,11 @@ void advanceOrThrowTransformThrowsTimeoutExceptionAfterFirstAttempt() { @MethodSource({"infiniteTimeout", "noTimeout"}) void advanceOrThrowTransform(final TimeoutContext timeoutContext) { RetryState retryState = new RetryState(timeoutContext); - RuntimeException firstAttemptException = new RuntimeException() { - }; + RuntimeException firstAttemptException = new RuntimeException(); retryState.advanceOrThrow(firstAttemptException, (e1, e2) -> e2, (rs, e) -> true); - RuntimeException secondAttemptException = new RuntimeException() { - }; - RuntimeException transformerResult = new RuntimeException() { - }; - assertThrows(transformerResult.getClass(), () -> retryState.advanceOrThrow(secondAttemptException, + RuntimeException secondAttemptException = new RuntimeException(); + RuntimeException transformerResult = new RuntimeException(); + assertAdvanceOrThrow(transformerResult, retryState, secondAttemptException, (e1, e2) -> { assertEquals(firstAttemptException, e1); assertEquals(secondAttemptException, e2); @@ -452,7 +428,7 @@ void advanceOrThrowTransform(final TimeoutContext timeoutContext) { (rs, e) -> { assertEquals(secondAttemptException, e); return false; - })); + }); } @ParameterizedTest @@ -475,4 +451,35 @@ void attachAndAttachment(final TimeoutContext timeoutContext) { private static void advance(final RetryState retryState) { retryState.advanceOrThrow(new RuntimeException(), (e1, e2) -> e2, (rs, e) -> true); } + + private static void assertAdvanceOrThrow( + @Nullable final Throwable expectedException, + final RetryState retryState, + final Throwable attemptException) { + assertAdvanceOrThrow(expectedException, retryState, attemptException, (rs, e) -> true); + } + + private static void assertAdvanceOrThrow( + @Nullable final Throwable expectedException, + final RetryState retryState, + final Throwable attemptException, + final BiPredicate retryPredicate) { + assertAdvanceOrThrow(expectedException, retryState, attemptException, (e1, e2) -> e2, retryPredicate); + } + + private static void assertAdvanceOrThrow( + @Nullable final Throwable expectedException, + final RetryState retryState, + final Throwable attemptException, + final BinaryOperator onAttemptFailureOperator, + final BiPredicate retryPredicate) { + if (expectedException == null) { + assertDoesNotThrow(() -> retryState.advanceOrThrow(attemptException, onAttemptFailureOperator, retryPredicate)); + } else { + assertEquals( + expectedException, + assertThrows(expectedException.getClass(), () -> + retryState.advanceOrThrow(attemptException, onAttemptFailureOperator, retryPredicate))); + } + } }