mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-27 17:49:02 +00:00
Introduce Builder API and factory methods for RetryPolicy
Prior to this commit, we had three concrete RetryPolicy implementations.
- MaxRetryAttemptsPolicy
- MaxDurationAttemptsPolicy
- PredicateRetryPolicy
However, there was no way to combine the behavior of those policies.
Furthermore, the PredicateRetryPolicy was practically useless as a
standalone policy, since it did not have a way to end an infinite loop
for a Retryable that continually throws an exception which matches the
predicate.
This commit therefore replaces the current built-in RetryPolicy
implementations with a fluent Builder API and dedicated factory methods
for common use cases.
In addition, this commit also introduces built-in support for
specifying include/exclude lists.
Examples:
new MaxRetryAttemptsPolicy(5) -->
RetryPolicy.withMaxAttempts(5)
new MaxDurationAttemptsPolicy(Duration.ofSeconds(5)) -->
RetryPolicy.withMaxDuration(Duration.ofSeconds(5))
new PredicateRetryPolicy(IOException.class::isInstance) -->
RetryPolicy.builder()
.maxAttempts(3)
.predicate(IOException.class::isInstance)
.build();
The following example demonstrates all supported features of the builder.
RetryPolicy.builder()
.maxAttempts(5)
.maxDuration(Duration.ofMillis(100))
.includes(IOException.class)
.excludes(FileNotFoundException.class)
.predicate(t -> t.getMessage().contains("Unexpected failure"))
.build();
Closes gh-35058
This commit is contained in:
+188
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.retry;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystemException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Max attempts tests for {@link DefaultRetryPolicy} and its {@link RetryExecution}.
|
||||
*
|
||||
* @author Mahmoud Ben Hassine
|
||||
* @author Sam Brannen
|
||||
* @since 7.0
|
||||
*/
|
||||
class MaxAttemptsDefaultRetryPolicyTests {
|
||||
|
||||
@Test
|
||||
void invalidMaxAttempts() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> RetryPolicy.withMaxAttempts(0))
|
||||
.withMessage("Max attempts must be greater than zero");
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> RetryPolicy.withMaxAttempts(-1))
|
||||
.withMessage("Max attempts must be greater than zero");
|
||||
}
|
||||
|
||||
@Test
|
||||
void maxAttempts() {
|
||||
var retryPolicy = RetryPolicy.withMaxAttempts(2);
|
||||
var retryExecution = retryPolicy.start();
|
||||
var throwable = mock(Throwable.class);
|
||||
|
||||
assertThat(retryExecution.shouldRetry(throwable)).isTrue();
|
||||
assertThat(retryExecution.shouldRetry(throwable)).isTrue();
|
||||
|
||||
assertThat(retryExecution.shouldRetry(throwable)).isFalse();
|
||||
assertThat(retryExecution.shouldRetry(throwable)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void maxAttemptsAndPredicate() {
|
||||
var retryPolicy = RetryPolicy.builder()
|
||||
.maxAttempts(4)
|
||||
.predicate(NumberFormatException.class::isInstance)
|
||||
.build();
|
||||
|
||||
var retryExecution = retryPolicy.start();
|
||||
|
||||
// 4 retries
|
||||
assertThat(retryExecution.shouldRetry(new NumberFormatException())).isTrue();
|
||||
assertThat(retryExecution.shouldRetry(new IllegalStateException())).isFalse();
|
||||
assertThat(retryExecution.shouldRetry(new IllegalStateException())).isFalse();
|
||||
assertThat(retryExecution.shouldRetry(new CustomNumberFormatException())).isTrue();
|
||||
|
||||
// After policy exhaustion
|
||||
assertThat(retryExecution.shouldRetry(new NumberFormatException())).isFalse();
|
||||
assertThat(retryExecution.shouldRetry(new IllegalStateException())).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void maxAttemptsWithIncludesAndExcludes() {
|
||||
var policy = RetryPolicy.builder()
|
||||
.maxAttempts(6)
|
||||
.includes(RuntimeException.class, IOException.class)
|
||||
.excludes(FileNotFoundException.class, CustomFileSystemException.class)
|
||||
.build();
|
||||
|
||||
var retryExecution = policy.start();
|
||||
|
||||
// 6 retries
|
||||
assertThat(retryExecution.shouldRetry(new IOException())).isTrue();
|
||||
assertThat(retryExecution.shouldRetry(new RuntimeException())).isTrue();
|
||||
assertThat(retryExecution.shouldRetry(new FileNotFoundException())).isFalse();
|
||||
assertThat(retryExecution.shouldRetry(new FileSystemException("file"))).isTrue();
|
||||
assertThat(retryExecution.shouldRetry(new CustomFileSystemException("file"))).isFalse();
|
||||
assertThat(retryExecution.shouldRetry(new IOException())).isTrue();
|
||||
|
||||
// After policy exhaustion
|
||||
assertThat(retryExecution.shouldRetry(new IOException())).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringImplementations() {
|
||||
var policy = RetryPolicy.builder()
|
||||
.maxAttempts(6)
|
||||
.includes(RuntimeException.class, IOException.class)
|
||||
.excludes(FileNotFoundException.class, CustomFileSystemException.class)
|
||||
.build();
|
||||
|
||||
assertThat(policy).asString().isEqualTo("""
|
||||
DefaultRetryPolicy[\
|
||||
maxAttempts=6, \
|
||||
includes=[java.lang.RuntimeException, java.io.IOException], \
|
||||
excludes=[java.io.FileNotFoundException, \
|
||||
org.springframework.core.retry.MaxAttemptsDefaultRetryPolicyTests.CustomFileSystemException]]""");
|
||||
|
||||
var template = """
|
||||
DefaultRetryPolicyExecution[\
|
||||
maxAttempts=6, \
|
||||
retryCount=%d, \
|
||||
includes=[java.lang.RuntimeException, java.io.IOException], \
|
||||
excludes=[java.io.FileNotFoundException, \
|
||||
org.springframework.core.retry.MaxAttemptsDefaultRetryPolicyTests.CustomFileSystemException]]""";
|
||||
var retryExecution = policy.start();
|
||||
var count = 0;
|
||||
|
||||
assertThat(retryExecution).asString().isEqualTo(template, count++);
|
||||
retryExecution.shouldRetry(new IOException());
|
||||
assertThat(retryExecution).asString().isEqualTo(template, count++);
|
||||
retryExecution.shouldRetry(new IOException());
|
||||
assertThat(retryExecution).asString().isEqualTo(template, count++);
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringImplementationsWithPredicateAsClass() {
|
||||
var policy = RetryPolicy.builder()
|
||||
.maxAttempts(1)
|
||||
.predicate(new NumberFormatExceptionMatcher())
|
||||
.build();
|
||||
assertThat(policy).asString()
|
||||
.isEqualTo("DefaultRetryPolicy[maxAttempts=1, predicate=NumberFormatExceptionMatcher]");
|
||||
|
||||
var retryExecution = policy.start();
|
||||
assertThat(retryExecution).asString()
|
||||
.isEqualTo("DefaultRetryPolicyExecution[maxAttempts=1, retryCount=0, predicate=NumberFormatExceptionMatcher]");
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringImplementationsWithPredicateAsLambda() {
|
||||
var policy = RetryPolicy.builder()
|
||||
.maxAttempts(2)
|
||||
.predicate(NumberFormatException.class::isInstance)
|
||||
.build();
|
||||
assertThat(policy).asString()
|
||||
.matches("DefaultRetryPolicy\\[maxAttempts=2, predicate=MaxAttemptsDefaultRetryPolicyTests.+?Lambda.+?]");
|
||||
|
||||
var retryExecution = policy.start();
|
||||
assertThat(retryExecution).asString()
|
||||
.matches("DefaultRetryPolicyExecution\\[maxAttempts=2, retryCount=0, predicate=MaxAttemptsDefaultRetryPolicyTests.+?Lambda.+?]");
|
||||
|
||||
retryExecution.shouldRetry(new NumberFormatException());
|
||||
assertThat(retryExecution).asString()
|
||||
.matches("DefaultRetryPolicyExecution\\[maxAttempts=2, retryCount=1, predicate=MaxAttemptsDefaultRetryPolicyTests.+?Lambda.+?]");
|
||||
|
||||
retryExecution.shouldRetry(new NumberFormatException());
|
||||
assertThat(retryExecution).asString()
|
||||
.matches("DefaultRetryPolicyExecution\\[maxAttempts=2, retryCount=2, predicate=MaxAttemptsDefaultRetryPolicyTests.+?Lambda.+?]");
|
||||
|
||||
retryExecution.shouldRetry(new NumberFormatException());
|
||||
assertThat(retryExecution).asString()
|
||||
.matches("DefaultRetryPolicyExecution\\[maxAttempts=2, retryCount=3, predicate=MaxAttemptsDefaultRetryPolicyTests.+?Lambda.+?]");
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class CustomNumberFormatException extends NumberFormatException {
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class CustomFileSystemException extends FileSystemException {
|
||||
|
||||
CustomFileSystemException(String file) {
|
||||
super(file);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.retry;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static java.time.Duration.ofSeconds;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Max duration tests for {@link DefaultRetryPolicy} and its {@link RetryExecution}.
|
||||
*
|
||||
* @author Mahmoud Ben Hassine
|
||||
* @author Sam Brannen
|
||||
* @since 7.0
|
||||
*/
|
||||
class MaxDurationDefaultRetryPolicyTests {
|
||||
|
||||
@Test
|
||||
void invalidMaxDuration() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> RetryPolicy.withMaxDuration(Duration.ZERO))
|
||||
.withMessage("Max duration must be positive");
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> RetryPolicy.withMaxDuration(ofSeconds(-1)))
|
||||
.withMessage("Max duration must be positive");
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringImplementations() {
|
||||
var policy1 = RetryPolicy.withMaxDuration(ofSeconds(3));
|
||||
var policy2 = RetryPolicy.builder()
|
||||
.maxDuration(ofSeconds(1))
|
||||
.predicate(new NumberFormatExceptionMatcher())
|
||||
.build();
|
||||
|
||||
assertThat(policy1).asString()
|
||||
.isEqualTo("DefaultRetryPolicy[maxDuration=3000ms]");
|
||||
assertThat(policy2).asString()
|
||||
.isEqualTo("DefaultRetryPolicy[maxDuration=1000ms, predicate=NumberFormatExceptionMatcher]");
|
||||
|
||||
assertThat(policy1.start()).asString()
|
||||
.matches("DefaultRetryPolicyExecution\\[maxDuration=3000ms, retryStartTime=.+]");
|
||||
assertThat(policy2.start()).asString()
|
||||
.matches("DefaultRetryPolicyExecution\\[maxDuration=1000ms, retryStartTime=.+, predicate=NumberFormatExceptionMatcher]");
|
||||
}
|
||||
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.retry;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Predicate that matches {@link NumberFormatException}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 7.0
|
||||
*/
|
||||
class NumberFormatExceptionMatcher implements Predicate<Throwable> {
|
||||
|
||||
@Override
|
||||
public boolean test(Throwable throwable) {
|
||||
return (throwable instanceof NumberFormatException);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.core.retry;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@@ -26,6 +28,7 @@ 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.assertj.core.api.InstanceOfAssertFactories.array;
|
||||
|
||||
/**
|
||||
* Tests for {@link RetryTemplate}.
|
||||
@@ -74,10 +77,10 @@ class RetryTemplateTests {
|
||||
|
||||
@Test
|
||||
void retryWithExhaustedPolicy() {
|
||||
AtomicInteger invocationCount = new AtomicInteger();
|
||||
RuntimeException exception = new RuntimeException("Boom!");
|
||||
var invocationCount = new AtomicInteger();
|
||||
var exception = new RuntimeException("Boom!");
|
||||
|
||||
Retryable<String> retryable = new Retryable<>() {
|
||||
var retryable = new Retryable<>() {
|
||||
@Override
|
||||
public String execute() {
|
||||
invocationCount.incrementAndGet();
|
||||
@@ -100,11 +103,11 @@ class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void retryWithFailingRetryableAndCustomRetryPolicy() {
|
||||
AtomicInteger invocationCount = new AtomicInteger();
|
||||
RuntimeException exception = new NumberFormatException();
|
||||
void retryWithFailingRetryableAndCustomRetryPolicyWithMultiplePredicates() {
|
||||
var invocationCount = new AtomicInteger();
|
||||
var exception = new NumberFormatException("Boom!");
|
||||
|
||||
Retryable<String> retryable = new Retryable<>() {
|
||||
var retryable = new Retryable<>() {
|
||||
@Override
|
||||
public String execute() {
|
||||
invocationCount.incrementAndGet();
|
||||
@@ -117,20 +120,111 @@ class RetryTemplateTests {
|
||||
}
|
||||
};
|
||||
|
||||
AtomicInteger retryCount = new AtomicInteger();
|
||||
// Custom RetryPolicy that only retries for a NumberFormatException and max 5 retry attempts.
|
||||
RetryPolicy retryPolicy = () -> throwable -> (retryCount.incrementAndGet() <= 5 && throwable instanceof NumberFormatException);
|
||||
var retryPolicy = RetryPolicy.builder()
|
||||
.maxAttempts(5)
|
||||
.maxDuration(Duration.ofMillis(100))
|
||||
.predicate(NumberFormatException.class::isInstance)
|
||||
.predicate(t -> t.getMessage().equals("Boom!"))
|
||||
.build();
|
||||
|
||||
retryTemplate.setRetryPolicy(retryPolicy);
|
||||
|
||||
assertThat(invocationCount).hasValue(0);
|
||||
assertThat(retryCount).hasValue(0);
|
||||
assertThatExceptionOfType(RetryException.class)
|
||||
.isThrownBy(() -> retryTemplate.execute(retryable))
|
||||
.withMessage("Retry policy for operation 'always fails' exhausted; aborting execution")
|
||||
.withCause(exception);
|
||||
// 6 = 1 initial invocation + 5 retry attempts
|
||||
// 6 = 1 initial invocation + 5 retry attempts
|
||||
assertThat(invocationCount).hasValue(6);
|
||||
assertThat(retryCount).hasValue(6);
|
||||
}
|
||||
|
||||
@Test
|
||||
void retryWithExceptionIncludes() throws Exception {
|
||||
var invocationCount = new AtomicInteger();
|
||||
|
||||
var retryable = new Retryable<>() {
|
||||
@Override
|
||||
public String execute() throws Exception {
|
||||
return switch (invocationCount.incrementAndGet()) {
|
||||
case 1 -> throw new FileNotFoundException();
|
||||
case 2 -> throw new IOException();
|
||||
case 3 -> throw new IllegalStateException();
|
||||
default -> "success";
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "test";
|
||||
}
|
||||
};
|
||||
|
||||
var retryPolicy = RetryPolicy.builder()
|
||||
.maxAttempts(Integer.MAX_VALUE)
|
||||
.includes(IOException.class)
|
||||
.build();
|
||||
|
||||
retryTemplate.setRetryPolicy(retryPolicy);
|
||||
|
||||
assertThat(invocationCount).hasValue(0);
|
||||
assertThatExceptionOfType(RetryException.class)
|
||||
.isThrownBy(() -> retryTemplate.execute(retryable))
|
||||
.withMessage("Retry policy for operation 'test' exhausted; aborting execution")
|
||||
.withCauseExactlyInstanceOf(IllegalStateException.class)
|
||||
.extracting(Throwable::getSuppressed, array(Throwable[].class))
|
||||
.satisfiesExactly(
|
||||
suppressed1 -> assertThat(suppressed1).isExactlyInstanceOf(IOException.class),
|
||||
suppressed2 -> assertThat(suppressed2).isExactlyInstanceOf(FileNotFoundException.class)
|
||||
);
|
||||
// 3 = 1 initial invocation + 2 retry attempts
|
||||
assertThat(invocationCount).hasValue(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void retryWithExceptionExcludes() throws Exception {
|
||||
var invocationCount = new AtomicInteger();
|
||||
|
||||
var retryable = new Retryable<>() {
|
||||
@Override
|
||||
public String execute() throws Exception {
|
||||
return switch (invocationCount.incrementAndGet()) {
|
||||
case 1 -> throw new IOException();
|
||||
case 2 -> throw new IOException();
|
||||
case 3 -> throw new CustomFileNotFoundException();
|
||||
default -> "success";
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "test";
|
||||
}
|
||||
};
|
||||
|
||||
var retryPolicy = RetryPolicy.builder()
|
||||
.maxAttempts(Integer.MAX_VALUE)
|
||||
.includes(IOException.class)
|
||||
.excludes(FileNotFoundException.class)
|
||||
.build();
|
||||
|
||||
retryTemplate.setRetryPolicy(retryPolicy);
|
||||
|
||||
assertThat(invocationCount).hasValue(0);
|
||||
assertThatExceptionOfType(RetryException.class)
|
||||
.isThrownBy(() -> retryTemplate.execute(retryable))
|
||||
.withMessage("Retry policy for operation 'test' exhausted; aborting execution")
|
||||
.withCauseExactlyInstanceOf(CustomFileNotFoundException.class)
|
||||
.extracting(Throwable::getSuppressed, array(Throwable[].class))
|
||||
.satisfiesExactly(
|
||||
suppressed1 -> assertThat(suppressed1).isExactlyInstanceOf(IOException.class),
|
||||
suppressed2 -> assertThat(suppressed2).isExactlyInstanceOf(IOException.class)
|
||||
);
|
||||
// 3 = 1 initial invocation + 2 retry attempts
|
||||
assertThat(invocationCount).hasValue(3);
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class CustomFileNotFoundException extends FileNotFoundException {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-114
@@ -1,114 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.retry.support;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.retry.RetryExecution;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link MaxRetryAttemptsPolicy} and its {@link RetryExecution}.
|
||||
*
|
||||
* @author Mahmoud Ben Hassine
|
||||
* @author Sam Brannen
|
||||
* @since 7.0
|
||||
*/
|
||||
class MaxRetryAttemptsPolicyTests {
|
||||
|
||||
@Test
|
||||
void defaultMaxRetryAttempts() {
|
||||
// given
|
||||
MaxRetryAttemptsPolicy retryPolicy = new MaxRetryAttemptsPolicy();
|
||||
Throwable throwable = mock();
|
||||
|
||||
// when
|
||||
RetryExecution retryExecution = retryPolicy.start();
|
||||
|
||||
// then
|
||||
assertThat(retryExecution.shouldRetry(throwable)).isTrue();
|
||||
assertThat(retryExecution.shouldRetry(throwable)).isTrue();
|
||||
assertThat(retryExecution.shouldRetry(throwable)).isTrue();
|
||||
|
||||
assertThat(retryExecution.shouldRetry(throwable)).isFalse();
|
||||
assertThat(retryExecution.shouldRetry(throwable)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void customMaxRetryAttempts() {
|
||||
// given
|
||||
MaxRetryAttemptsPolicy retryPolicy = new MaxRetryAttemptsPolicy(2);
|
||||
Throwable throwable = mock();
|
||||
|
||||
// when
|
||||
RetryExecution retryExecution = retryPolicy.start();
|
||||
|
||||
// then
|
||||
assertThat(retryExecution.shouldRetry(throwable)).isTrue();
|
||||
assertThat(retryExecution.shouldRetry(throwable)).isTrue();
|
||||
|
||||
assertThat(retryExecution.shouldRetry(throwable)).isFalse();
|
||||
assertThat(retryExecution.shouldRetry(throwable)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidMaxRetryAttempts() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new MaxRetryAttemptsPolicy(0))
|
||||
.withMessage("Max retry attempts must be greater than zero");
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new MaxRetryAttemptsPolicy(-1))
|
||||
.withMessage("Max retry attempts must be greater than zero");
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringImplementations() {
|
||||
MaxRetryAttemptsPolicy policy1 = new MaxRetryAttemptsPolicy();
|
||||
MaxRetryAttemptsPolicy policy2 = new MaxRetryAttemptsPolicy(1);
|
||||
|
||||
assertThat(policy1).asString().isEqualTo("MaxRetryAttemptsPolicy[maxRetryAttempts=3]");
|
||||
assertThat(policy2).asString().isEqualTo("MaxRetryAttemptsPolicy[maxRetryAttempts=1]");
|
||||
|
||||
RetryExecution retryExecution = policy1.start();
|
||||
assertThat(retryExecution).asString()
|
||||
.isEqualTo("MaxRetryAttemptsPolicyExecution[retryAttempts=0, maxRetryAttempts=3]");
|
||||
|
||||
assertThat(retryExecution.shouldRetry(mock())).isTrue();
|
||||
assertThat(retryExecution).asString()
|
||||
.isEqualTo("MaxRetryAttemptsPolicyExecution[retryAttempts=1, maxRetryAttempts=3]");
|
||||
|
||||
assertThat(retryExecution.shouldRetry(mock())).isTrue();
|
||||
assertThat(retryExecution).asString()
|
||||
.isEqualTo("MaxRetryAttemptsPolicyExecution[retryAttempts=2, maxRetryAttempts=3]");
|
||||
|
||||
assertThat(retryExecution.shouldRetry(mock())).isTrue();
|
||||
assertThat(retryExecution).asString()
|
||||
.isEqualTo("MaxRetryAttemptsPolicyExecution[retryAttempts=3, maxRetryAttempts=3]");
|
||||
|
||||
assertThat(retryExecution.shouldRetry(mock())).isFalse();
|
||||
assertThat(retryExecution).asString()
|
||||
.isEqualTo("MaxRetryAttemptsPolicyExecution[retryAttempts=4, maxRetryAttempts=3]");
|
||||
|
||||
assertThat(retryExecution.shouldRetry(mock())).isFalse();
|
||||
assertThat(retryExecution).asString()
|
||||
.isEqualTo("MaxRetryAttemptsPolicyExecution[retryAttempts=5, maxRetryAttempts=3]");
|
||||
}
|
||||
|
||||
}
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.retry.support;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.retry.RetryExecution;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link MaxRetryDurationPolicy} and its {@link RetryExecution}.
|
||||
*
|
||||
* @author Mahmoud Ben Hassine
|
||||
* @author Sam Brannen
|
||||
* @since 7.0
|
||||
*/
|
||||
class MaxRetryDurationPolicyTests {
|
||||
|
||||
@Test
|
||||
void invalidMaxRetryDuration() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new MaxRetryDurationPolicy(Duration.ZERO))
|
||||
.withMessage("Max retry duration must be positive");
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringImplementations() {
|
||||
MaxRetryDurationPolicy policy1 = new MaxRetryDurationPolicy();
|
||||
MaxRetryDurationPolicy policy2 = new MaxRetryDurationPolicy(Duration.ofSeconds(1));
|
||||
|
||||
assertThat(policy1).asString().isEqualTo("MaxRetryDurationPolicy[maxRetryDuration=3000ms]");
|
||||
assertThat(policy2).asString().isEqualTo("MaxRetryDurationPolicy[maxRetryDuration=1000ms]");
|
||||
|
||||
assertThat(policy1.start()).asString()
|
||||
.matches("MaxRetryDurationPolicyExecution\\[retryStartTime=.+, maxRetryDuration=3000ms\\]");
|
||||
assertThat(policy2.start()).asString()
|
||||
.matches("MaxRetryDurationPolicyExecution\\[retryStartTime=.+, maxRetryDuration=1000ms\\]");
|
||||
}
|
||||
|
||||
}
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.retry.support;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.retry.RetryExecution;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link PredicateRetryPolicy} and its {@link RetryExecution}.
|
||||
*
|
||||
* @author Mahmoud Ben Hassine
|
||||
* @author Sam Brannen
|
||||
* @since 7.0
|
||||
*/
|
||||
class PredicateRetryPolicyTests {
|
||||
|
||||
@Test
|
||||
void predicateRetryPolicy() {
|
||||
Predicate<Throwable> predicate = NumberFormatException.class::isInstance;
|
||||
PredicateRetryPolicy retryPolicy = new PredicateRetryPolicy(predicate);
|
||||
|
||||
RetryExecution retryExecution = retryPolicy.start();
|
||||
|
||||
assertThat(retryExecution.shouldRetry(new NumberFormatException())).isTrue();
|
||||
assertThat(retryExecution.shouldRetry(new IllegalStateException())).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringImplementations() {
|
||||
PredicateRetryPolicy policy1 = new PredicateRetryPolicy(NumberFormatException.class::isInstance);
|
||||
PredicateRetryPolicy policy2 = new PredicateRetryPolicy(new NumberFormatExceptionMatcher());
|
||||
|
||||
assertThat(policy1).asString().matches("PredicateRetryPolicy\\[predicate=PredicateRetryPolicyTests.+?Lambda.+?\\]");
|
||||
assertThat(policy2).asString().isEqualTo("PredicateRetryPolicy[predicate=NumberFormatExceptionMatcher]");
|
||||
|
||||
assertThat(policy1.start()).asString()
|
||||
.matches("PredicateRetryPolicyExecution\\[predicate=PredicateRetryPolicyTests.+?Lambda.+?\\]");
|
||||
assertThat(policy2.start()).asString()
|
||||
.isEqualTo("PredicateRetryPolicyExecution[predicate=NumberFormatExceptionMatcher]");
|
||||
}
|
||||
|
||||
|
||||
private static class NumberFormatExceptionMatcher implements Predicate<Throwable> {
|
||||
|
||||
@Override
|
||||
public boolean test(Throwable throwable) {
|
||||
return (throwable instanceof NumberFormatException);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user