mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-25 00:09:02 +00:00
Expose RetryException#getRetryCount() and accept maxAttempts(0)
Closes gh-35351 Closes gh-35362
This commit is contained in:
+13
@@ -54,6 +54,18 @@ class MaxAttemptsRetryPolicyTests {
|
||||
assertThat(backOffExecution.nextBackOff()).isEqualTo(STOP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void maxAttemptsZero() {
|
||||
var retryPolicy = RetryPolicy.builder().maxAttempts(0).delay(Duration.ZERO).build();
|
||||
var backOffExecution = retryPolicy.getBackOff().start();
|
||||
var throwable = mock(Throwable.class);
|
||||
|
||||
assertThat(retryPolicy.shouldRetry(throwable)).isTrue();
|
||||
assertThat(backOffExecution.nextBackOff()).isEqualTo(STOP);
|
||||
assertThat(retryPolicy.shouldRetry(throwable)).isTrue();
|
||||
assertThat(backOffExecution.nextBackOff()).isEqualTo(STOP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void maxAttemptsAndPredicate() {
|
||||
var retryPolicy = RetryPolicy.builder()
|
||||
@@ -115,6 +127,7 @@ class MaxAttemptsRetryPolicyTests {
|
||||
private static class CustomNumberFormatException extends NumberFormatException {
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class CustomFileSystemException extends FileSystemException {
|
||||
|
||||
|
||||
@@ -65,12 +65,9 @@ class RetryPolicyTests {
|
||||
|
||||
@Test
|
||||
void withMaxAttemptsPreconditions() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> RetryPolicy.withMaxAttempts(0))
|
||||
.withMessage("Invalid maxAttempts (0): must be greater than zero.");
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> RetryPolicy.withMaxAttempts(-1))
|
||||
.withMessage("Invalid maxAttempts (-1): must be greater than zero.");
|
||||
.withMessageStartingWith("Invalid maxAttempts (-1)");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -115,12 +112,9 @@ class RetryPolicyTests {
|
||||
|
||||
@Test
|
||||
void maxAttemptsPreconditions() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> RetryPolicy.builder().maxAttempts(0))
|
||||
.withMessage("Invalid maxAttempts (0): must be greater than zero.");
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> RetryPolicy.builder().maxAttempts(-1))
|
||||
.withMessage("Invalid maxAttempts (-1): must be greater than zero.");
|
||||
.withMessageStartingWith("Invalid maxAttempts (-1)");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -32,9 +32,6 @@ import org.junit.jupiter.params.provider.Arguments.ArgumentSet;
|
||||
import org.junit.jupiter.params.provider.FieldSource;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import org.springframework.util.backoff.BackOff;
|
||||
import org.springframework.util.backoff.FixedBackOff;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.junit.jupiter.params.provider.Arguments.argumentSet;
|
||||
@@ -51,16 +48,13 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
*
|
||||
* @author Mahmoud Ben Hassine
|
||||
* @author Sam Brannen
|
||||
* @author Juergen Hoeller
|
||||
* @since 7.0
|
||||
* @see RetryPolicyTests
|
||||
*/
|
||||
class RetryTemplateTests {
|
||||
|
||||
private final RetryPolicy retryPolicy =
|
||||
RetryPolicy.builder()
|
||||
.maxAttempts(3)
|
||||
.delay(Duration.ZERO)
|
||||
.build();
|
||||
private final RetryPolicy retryPolicy = RetryPolicy.builder().maxAttempts(3).delay(Duration.ZERO).build();
|
||||
|
||||
private final RetryTemplate retryTemplate = new RetryTemplate(retryPolicy);
|
||||
|
||||
@@ -104,7 +98,8 @@ class RetryTemplateTests {
|
||||
.isThrownBy(() -> retryTemplate.execute(retryable))
|
||||
.withMessageMatching("Retry policy for operation '.+?' exhausted; aborting execution")
|
||||
.withCause(exception)
|
||||
.satisfies(throwable -> assertThat(throwable.getSuppressed()).isEmpty());
|
||||
.satisfies(throwable -> assertThat(throwable.getSuppressed()).isEmpty())
|
||||
.satisfies(throwable -> assertThat(throwable.getRetryCount()).isZero());
|
||||
|
||||
// RetryListener interactions:
|
||||
inOrder.verify(retryListener).onRetryPolicyExhaustion(retryPolicy, retryable, exception);
|
||||
@@ -112,19 +107,8 @@ class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void retryWithInitialFailureAndZeroRetriesBackOffPolicy() {
|
||||
RetryPolicy retryPolicy = new RetryPolicy() {
|
||||
|
||||
@Override
|
||||
public boolean shouldRetry(Throwable throwable) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BackOff getBackOff() {
|
||||
return new FixedBackOff(10, 0); // Zero retries
|
||||
}
|
||||
};
|
||||
void retryWithInitialFailureAndZeroRetriesFixedBackOffPolicy() {
|
||||
RetryPolicy retryPolicy = RetryPolicy.withMaxAttempts(0);
|
||||
|
||||
RetryTemplate retryTemplate = new RetryTemplate(retryPolicy);
|
||||
retryTemplate.setRetryListener(retryListener);
|
||||
@@ -137,7 +121,31 @@ class RetryTemplateTests {
|
||||
.isThrownBy(() -> retryTemplate.execute(retryable))
|
||||
.withMessageMatching("Retry policy for operation '.+?' exhausted; aborting execution")
|
||||
.withCause(exception)
|
||||
.satisfies(throwable -> assertThat(throwable.getSuppressed()).isEmpty());
|
||||
.satisfies(throwable -> assertThat(throwable.getSuppressed()).isEmpty())
|
||||
.satisfies(throwable -> assertThat(throwable.getRetryCount()).isZero());
|
||||
|
||||
// RetryListener interactions:
|
||||
inOrder.verify(retryListener).onRetryPolicyExhaustion(retryPolicy, retryable, exception);
|
||||
verifyNoMoreInteractions(retryListener);
|
||||
}
|
||||
|
||||
@Test
|
||||
void retryWithInitialFailureAndZeroRetriesBackOffPolicyFromBuilder() {
|
||||
RetryPolicy retryPolicy = RetryPolicy.builder().maxAttempts(0).build();
|
||||
|
||||
RetryTemplate retryTemplate = new RetryTemplate(retryPolicy);
|
||||
retryTemplate.setRetryListener(retryListener);
|
||||
Exception exception = new RuntimeException("Boom!");
|
||||
Retryable<String> retryable = () -> {
|
||||
throw exception;
|
||||
};
|
||||
|
||||
assertThatExceptionOfType(RetryException.class)
|
||||
.isThrownBy(() -> retryTemplate.execute(retryable))
|
||||
.withMessageMatching("Retry policy for operation '.+?' exhausted; aborting execution")
|
||||
.withCause(exception)
|
||||
.satisfies(throwable -> assertThat(throwable.getSuppressed()).isEmpty())
|
||||
.satisfies(throwable -> assertThat(throwable.getRetryCount()).isZero());
|
||||
|
||||
// RetryListener interactions:
|
||||
inOrder.verify(retryListener).onRetryPolicyExhaustion(retryPolicy, retryable, exception);
|
||||
@@ -282,7 +290,8 @@ class RetryTemplateTests {
|
||||
.satisfies(hasSuppressedExceptionsSatisfyingExactly(
|
||||
suppressed1 -> assertThat(suppressed1).isExactlyInstanceOf(FileNotFoundException.class),
|
||||
suppressed2 -> assertThat(suppressed2).isExactlyInstanceOf(IOException.class)
|
||||
));
|
||||
))
|
||||
.satisfies(throwable -> assertThat(throwable.getRetryCount()).isEqualTo(2));
|
||||
// 3 = 1 initial invocation + 2 retry attempts
|
||||
assertThat(invocationCount).hasValue(3);
|
||||
|
||||
@@ -344,7 +353,8 @@ class RetryTemplateTests {
|
||||
.satisfies(hasSuppressedExceptionsSatisfyingExactly(
|
||||
suppressed1 -> assertThat(suppressed1).isExactlyInstanceOf(IOException.class),
|
||||
suppressed2 -> assertThat(suppressed2).isExactlyInstanceOf(IOException.class)
|
||||
));
|
||||
))
|
||||
.satisfies(throwable -> assertThat(throwable.getRetryCount()).isEqualTo(2));
|
||||
// 3 = 1 initial invocation + 2 retry attempts
|
||||
assertThat(invocationCount).hasValue(3);
|
||||
|
||||
@@ -366,8 +376,9 @@ class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private static final Consumer<Throwable> hasSuppressedExceptionsSatisfyingExactly(
|
||||
private static Consumer<Throwable> hasSuppressedExceptionsSatisfyingExactly(
|
||||
ThrowingConsumer<? super Throwable>... requirements) {
|
||||
|
||||
return throwable -> assertThat(throwable.getSuppressed()).satisfiesExactly(requirements);
|
||||
}
|
||||
|
||||
@@ -376,6 +387,7 @@ class RetryTemplateTests {
|
||||
private static class CustomFileNotFoundException extends FileNotFoundException {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Custom {@link RuntimeException} that implements {@link #equals(Object)}
|
||||
* and {@link #hashCode()} for use in assertions that check for equality.
|
||||
|
||||
Reference in New Issue
Block a user