mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-23 14:40:00 +00:00
Redesign RetryPolicy to directly incorporate BackOff
After experimenting with our newly introduced core retry support (RetryPolicy, RetryTemplate, etc.) and @Retryable support, it became apparent that there are overlapping concerns between the current RetryPolicy and BackOff contracts. - RetryPolicy and BackOff both have stateful executions: RetryExecution and BackOffExecution. However, only one stateful execution is necessary. - FixedBackOff and ExponentialBackOff already incorporate "retry" logic in terms of max attempts, max elapsed time, etc. Thus, there is no need to duplicate such behavior in a RetryPolicy and its RetryExecution. - RetryTemplate currently accepts both a RetryPolicy and a BackOff in order to instrument the retry algorithm. However, users would probably rather focus on configuring all "retry" logic via a single mechanism. In light of the above, this commit directly incorporates BackOff in RetryPolicy as follows. - Remove the RetryExecution interface and move its shouldRetry() method to RetryPolicy, replacing the current RetryExecution start() method. - Introduce a default getBackOff() method in the RetryPolicy interface. - Introduce RetryPolicy.withDefaults() factory method. - Completely overhaul the RetryPolicy.Builder to provide support for configuring a BackOff strategy. - Remove BackOff configuration from RetryTemplate. - Revise the method signatures of callbacks in RetryListener. The collective result of these changes can be witnessed in the reworked implementation of AbstractRetryInterceptor. RetryPolicy retryPolicy = RetryPolicy.builder() .includes(spec.includes()) .excludes(spec.excludes()) .predicate(spec.predicate().forMethod(method)) .maxAttempts(spec.maxAttempts()) .delay(Duration.ofMillis(spec.delay())) .maxDelay(Duration.ofMillis(spec.maxDelay())) .jitter(Duration.ofMillis(spec.jitter())) .multiplier(spec.multiplier()) .build(); RetryTemplate retryTemplate = new RetryTemplate(retryPolicy); See gh-34716 See gh-34529 See gh-35058 Closes gh-35110
This commit is contained in:
-188
@@ -1,188 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright 2002-present 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 java.time.Duration;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.springframework.util.backoff.BackOffExecution.STOP;
|
||||
|
||||
/**
|
||||
* Max attempts {@link RetryPolicy} tests.
|
||||
*
|
||||
* @author Mahmoud Ben Hassine
|
||||
* @author Sam Brannen
|
||||
* @since 7.0
|
||||
*/
|
||||
class MaxAttemptsRetryPolicyTests {
|
||||
|
||||
@Test
|
||||
void maxAttempts() {
|
||||
var retryPolicy = RetryPolicy.builder().maxAttempts(2).delay(Duration.ofMillis(1)).build();
|
||||
var backOffExecution = retryPolicy.getBackOff().start();
|
||||
var throwable = mock(Throwable.class);
|
||||
|
||||
assertThat(retryPolicy.shouldRetry(throwable)).isTrue();
|
||||
assertThat(backOffExecution.nextBackOff()).isGreaterThan(0);
|
||||
assertThat(retryPolicy.shouldRetry(throwable)).isTrue();
|
||||
assertThat(backOffExecution.nextBackOff()).isGreaterThan(0);
|
||||
|
||||
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()
|
||||
.maxAttempts(4)
|
||||
.delay(Duration.ofMillis(1))
|
||||
.predicate(NumberFormatException.class::isInstance)
|
||||
.build();
|
||||
|
||||
var backOffExecution = retryPolicy.getBackOff().start();
|
||||
|
||||
// 4 retries
|
||||
assertThat(retryPolicy.shouldRetry(new NumberFormatException())).isTrue();
|
||||
assertThat(backOffExecution.nextBackOff()).isGreaterThan(0);
|
||||
assertThat(retryPolicy.shouldRetry(new IllegalStateException())).isFalse();
|
||||
assertThat(backOffExecution.nextBackOff()).isGreaterThan(0);
|
||||
assertThat(retryPolicy.shouldRetry(new IllegalStateException())).isFalse();
|
||||
assertThat(backOffExecution.nextBackOff()).isGreaterThan(0);
|
||||
assertThat(retryPolicy.shouldRetry(new CustomNumberFormatException())).isTrue();
|
||||
assertThat(backOffExecution.nextBackOff()).isGreaterThan(0);
|
||||
|
||||
// After policy exhaustion
|
||||
assertThat(retryPolicy.shouldRetry(new NumberFormatException())).isTrue();
|
||||
assertThat(backOffExecution.nextBackOff()).isEqualTo(STOP);
|
||||
assertThat(retryPolicy.shouldRetry(new IllegalStateException())).isFalse();
|
||||
assertThat(backOffExecution.nextBackOff()).isEqualTo(STOP);
|
||||
}
|
||||
|
||||
@Test
|
||||
void maxAttemptsWithIncludesAndExcludes() {
|
||||
var retryPolicy = RetryPolicy.builder()
|
||||
.maxAttempts(6)
|
||||
.includes(RuntimeException.class, IOException.class)
|
||||
.excludes(FileNotFoundException.class, CustomFileSystemException.class)
|
||||
.build();
|
||||
|
||||
var backOffExecution = retryPolicy.getBackOff().start();
|
||||
|
||||
// 6 retries
|
||||
assertThat(retryPolicy.shouldRetry(new IOException())).isTrue();
|
||||
assertThat(backOffExecution.nextBackOff()).isGreaterThan(0);
|
||||
assertThat(retryPolicy.shouldRetry(new RuntimeException())).isTrue();
|
||||
assertThat(backOffExecution.nextBackOff()).isGreaterThan(0);
|
||||
assertThat(retryPolicy.shouldRetry(new FileNotFoundException())).isFalse();
|
||||
assertThat(backOffExecution.nextBackOff()).isGreaterThan(0);
|
||||
assertThat(retryPolicy.shouldRetry(new FileSystemException("file"))).isTrue();
|
||||
assertThat(backOffExecution.nextBackOff()).isGreaterThan(0);
|
||||
assertThat(retryPolicy.shouldRetry(new CustomFileSystemException("file"))).isFalse();
|
||||
assertThat(backOffExecution.nextBackOff()).isGreaterThan(0);
|
||||
assertThat(retryPolicy.shouldRetry(new IOException())).isTrue();
|
||||
assertThat(backOffExecution.nextBackOff()).isGreaterThan(0);
|
||||
|
||||
// After policy exhaustion
|
||||
assertThat(retryPolicy.shouldRetry(new IOException())).isTrue();
|
||||
assertThat(backOffExecution.nextBackOff()).isEqualTo(STOP);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class CustomNumberFormatException extends NumberFormatException {
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class CustomFileSystemException extends FileSystemException {
|
||||
|
||||
CustomFileSystemException(String file) {
|
||||
super(file);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-65
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-present 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]");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,455 @@
|
||||
/*
|
||||
* Copyright 2002-present 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 java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import org.assertj.core.api.ThrowingConsumer;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.util.backoff.ExponentialBackOff;
|
||||
import org.springframework.util.backoff.FixedBackOff;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link RetryPolicy} and its builder.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 7.0
|
||||
* @see RetryTemplateTests
|
||||
*/
|
||||
class RetryPolicyTests {
|
||||
|
||||
@Nested
|
||||
class FactoryMethodTests {
|
||||
|
||||
@Test
|
||||
void withDefaults() {
|
||||
var policy = RetryPolicy.withDefaults();
|
||||
|
||||
assertThat(policy.shouldRetry(new AssertionError())).isTrue();
|
||||
assertThat(policy.shouldRetry(new IOException())).isTrue();
|
||||
|
||||
assertThat(policy.getBackOff())
|
||||
.asInstanceOf(type(FixedBackOff.class))
|
||||
.satisfies(backOff -> {
|
||||
assertThat(backOff.getMaxAttempts()).isEqualTo(3);
|
||||
assertThat(backOff.getInterval()).isEqualTo(1000);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void withMaxAttemptsPreconditions() {
|
||||
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 withMaxAttempts() {
|
||||
var policy = RetryPolicy.withMaxAttempts(5);
|
||||
|
||||
assertThat(policy.shouldRetry(new AssertionError())).isTrue();
|
||||
assertThat(policy.shouldRetry(new IOException())).isTrue();
|
||||
|
||||
assertThat(policy.getBackOff())
|
||||
.asInstanceOf(type(FixedBackOff.class))
|
||||
.satisfies(backOff -> {
|
||||
assertThat(backOff.getMaxAttempts()).isEqualTo(5);
|
||||
assertThat(backOff.getInterval()).isEqualTo(1000);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void withMaxElapsedTimePreconditions() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> RetryPolicy.withMaxElapsedTime(Duration.ofMillis(0)))
|
||||
.withMessage("Invalid duration (0ms): max elapsed time must be positive.");
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> RetryPolicy.withMaxElapsedTime(Duration.ofMillis(-1)))
|
||||
.withMessage("Invalid duration (-1ms): max elapsed time must be positive.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void withMaxElapsedTime() {
|
||||
var policy = RetryPolicy.withMaxElapsedTime(Duration.ofMillis(42));
|
||||
|
||||
assertThat(policy.shouldRetry(new AssertionError())).isTrue();
|
||||
assertThat(policy.shouldRetry(new IOException())).isTrue();
|
||||
|
||||
assertThat(policy.getBackOff())
|
||||
.asInstanceOf(type(ExponentialBackOff.class))
|
||||
.satisfies(hasDefaultMaxAttemptsAndDelay())
|
||||
.extracting(ExponentialBackOff::getMaxElapsedTime).isEqualTo(42L);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class BuilderTests {
|
||||
|
||||
@Test
|
||||
void backOffPlusConflictingConfig() {
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> RetryPolicy.builder().backOff(mock()).delay(Duration.ofMillis(10)).build())
|
||||
.withMessage("""
|
||||
The following configuration options are not supported with a custom BackOff strategy: \
|
||||
maxAttempts, delay, jitter, multiplier, maxDelay, or maxElapsedTime.""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void backOff() {
|
||||
var backOff = new FixedBackOff();
|
||||
var policy = RetryPolicy.builder().backOff(backOff).build();
|
||||
|
||||
assertThat(policy.getBackOff()).isEqualTo(backOff);
|
||||
|
||||
assertThat(policy).asString()
|
||||
.isEqualTo("DefaultRetryPolicy[backOff=FixedBackOff[interval=5000, maxAttempts=unlimited]]");
|
||||
}
|
||||
|
||||
@Test
|
||||
void maxAttemptsPreconditions() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> RetryPolicy.builder().maxAttempts(0))
|
||||
.withMessage("Max attempts must be greater than zero");
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> RetryPolicy.builder().maxAttempts(-1))
|
||||
.withMessage("Max attempts must be greater than zero");
|
||||
}
|
||||
|
||||
@Test
|
||||
void maxAttempts() {
|
||||
var policy = RetryPolicy.builder().maxAttempts(5).build();
|
||||
|
||||
assertThat(policy.getBackOff())
|
||||
.asInstanceOf(type(ExponentialBackOff.class))
|
||||
.satisfies(backOff -> {
|
||||
assertThat(backOff.getMaxAttempts()).isEqualTo(5);
|
||||
assertThat(backOff.getInitialInterval()).isEqualTo(1000);
|
||||
});
|
||||
|
||||
assertToString(policy, 1000, 0, 1, Long.MAX_VALUE, Long.MAX_VALUE, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void delayPreconditions() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> RetryPolicy.builder().delay(Duration.ofMillis(0)))
|
||||
.withMessage("Invalid duration (0ms): delay must be positive.");
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> RetryPolicy.builder().delay(Duration.ofMillis(-1)))
|
||||
.withMessage("Invalid duration (-1ms): delay must be positive.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void delay() {
|
||||
var policy = RetryPolicy.builder().delay(Duration.ofMillis(42)).build();
|
||||
|
||||
assertThat(policy.getBackOff())
|
||||
.asInstanceOf(type(ExponentialBackOff.class))
|
||||
.satisfies(backOff -> {
|
||||
assertThat(backOff.getInitialInterval()).isEqualTo(42);
|
||||
assertThat(backOff.getMaxAttempts()).isEqualTo(3);
|
||||
});
|
||||
|
||||
assertToString(policy, 42, 0, 1, Long.MAX_VALUE, Long.MAX_VALUE, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void jitterPreconditions() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> RetryPolicy.builder().jitter(Duration.ofMillis(-1)))
|
||||
.withMessage("Invalid jitter (-1ms): must be >= 0.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void jitter() {
|
||||
var policy = RetryPolicy.builder().jitter(Duration.ofMillis(42)).build();
|
||||
|
||||
assertThat(policy.getBackOff())
|
||||
.asInstanceOf(type(ExponentialBackOff.class))
|
||||
.satisfies(hasDefaultMaxAttemptsAndDelay())
|
||||
.extracting(ExponentialBackOff::getJitter).isEqualTo(42L);
|
||||
|
||||
assertToString(policy, 1000, 42, 1, Long.MAX_VALUE, Long.MAX_VALUE, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiplierPreconditions() {
|
||||
String template = """
|
||||
Invalid multiplier '%s': must be greater than or equal to 1. \
|
||||
A multiplier of 1 is equivalent to a fixed delay.""";
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> RetryPolicy.builder().multiplier(-1))
|
||||
.withMessage(template, "-1.0");
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> RetryPolicy.builder().multiplier(0))
|
||||
.withMessage(template, "0.0");
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> RetryPolicy.builder().multiplier(0.5))
|
||||
.withMessage(template, "0.5");
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiplier() {
|
||||
var policy = RetryPolicy.builder().multiplier(1.5).build();
|
||||
|
||||
assertThat(policy.getBackOff())
|
||||
.asInstanceOf(type(ExponentialBackOff.class))
|
||||
.satisfies(hasDefaultMaxAttemptsAndDelay())
|
||||
.extracting(ExponentialBackOff::getMultiplier).isEqualTo(1.5);
|
||||
|
||||
assertToString(policy, 1000, 0, 1.5, Long.MAX_VALUE, Long.MAX_VALUE, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void maxDelayPreconditions() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> RetryPolicy.builder().maxDelay(Duration.ofMillis(0)))
|
||||
.withMessage("Invalid duration (0ms): max delay must be positive.");
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> RetryPolicy.builder().maxDelay(Duration.ofMillis(-1)))
|
||||
.withMessage("Invalid duration (-1ms): max delay must be positive.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void maxDelay() {
|
||||
var policy = RetryPolicy.builder().maxDelay(Duration.ofMillis(42)).build();
|
||||
|
||||
assertThat(policy.getBackOff())
|
||||
.asInstanceOf(type(ExponentialBackOff.class))
|
||||
.satisfies(hasDefaultMaxAttemptsAndDelay())
|
||||
.extracting(ExponentialBackOff::getMaxInterval).isEqualTo(42L);
|
||||
|
||||
assertToString(policy, 1000, 0, 1, 42, Long.MAX_VALUE, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void maxElapsedTimePreconditions() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> RetryPolicy.builder().maxElapsedTime(Duration.ofMillis(0)))
|
||||
.withMessage("Invalid duration (0ms): max elapsed time must be positive.");
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> RetryPolicy.builder().maxElapsedTime(Duration.ofMillis(-1)))
|
||||
.withMessage("Invalid duration (-1ms): max elapsed time must be positive.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void maxElapsedTime() {
|
||||
var policy = RetryPolicy.builder().maxElapsedTime(Duration.ofMillis(42)).build();
|
||||
|
||||
assertThat(policy.getBackOff())
|
||||
.asInstanceOf(type(ExponentialBackOff.class))
|
||||
.satisfies(hasDefaultMaxAttemptsAndDelay())
|
||||
.extracting(ExponentialBackOff::getMaxElapsedTime).isEqualTo(42L);
|
||||
|
||||
assertToString(policy, 1000, 0, 1, Long.MAX_VALUE, 42, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void includes() {
|
||||
var policy = RetryPolicy.builder()
|
||||
.includes(FileNotFoundException.class, IllegalArgumentException.class)
|
||||
.includes(List.of(NumberFormatException.class, AssertionError.class))
|
||||
.build();
|
||||
|
||||
assertThat(policy.shouldRetry(new FileNotFoundException())).isTrue();
|
||||
assertThat(policy.shouldRetry(new IllegalArgumentException())).isTrue();
|
||||
assertThat(policy.shouldRetry(new NumberFormatException())).isTrue();
|
||||
assertThat(policy.shouldRetry(new AssertionError())).isTrue();
|
||||
|
||||
assertThat(policy.shouldRetry(new Throwable())).isFalse();
|
||||
assertThat(policy.shouldRetry(new FileSystemException("fs"))).isFalse();
|
||||
|
||||
assertThat(policy.getBackOff())
|
||||
.asInstanceOf(type(ExponentialBackOff.class))
|
||||
.satisfies(hasDefaultMaxAttemptsAndDelay());
|
||||
|
||||
String filters = "includes=" + names(FileNotFoundException.class, IllegalArgumentException.class,
|
||||
NumberFormatException.class, AssertionError.class) + ", ";
|
||||
assertToString(policy, filters, 1000, 0, 1, Long.MAX_VALUE, Long.MAX_VALUE, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void includesSubtypeMatching() {
|
||||
var policy = RetryPolicy.builder().includes(IOException.class).build();
|
||||
|
||||
assertThat(policy.shouldRetry(new FileNotFoundException())).isTrue();
|
||||
assertThat(policy.shouldRetry(new FileSystemException("fs"))).isTrue();
|
||||
|
||||
assertThat(policy.shouldRetry(new Throwable())).isFalse();
|
||||
assertThat(policy.shouldRetry(new AssertionError())).isFalse();
|
||||
|
||||
assertThat(policy.getBackOff())
|
||||
.asInstanceOf(type(ExponentialBackOff.class))
|
||||
.satisfies(hasDefaultMaxAttemptsAndDelay());
|
||||
|
||||
assertToString(policy, "includes=[java.io.IOException], ", 1000, 0, 1, Long.MAX_VALUE, Long.MAX_VALUE, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void excludes() {
|
||||
var policy = RetryPolicy.builder()
|
||||
.excludes(FileNotFoundException.class, IllegalArgumentException.class)
|
||||
.excludes(List.of(NumberFormatException.class, AssertionError.class))
|
||||
.build();
|
||||
|
||||
assertThat(policy.shouldRetry(new FileNotFoundException())).isFalse();
|
||||
assertThat(policy.shouldRetry(new IllegalArgumentException())).isFalse();
|
||||
assertThat(policy.shouldRetry(new NumberFormatException())).isFalse();
|
||||
assertThat(policy.shouldRetry(new AssertionError())).isFalse();
|
||||
|
||||
assertThat(policy.shouldRetry(new Throwable())).isTrue();
|
||||
assertThat(policy.shouldRetry(new FileSystemException("fs"))).isTrue();
|
||||
|
||||
assertThat(policy.getBackOff())
|
||||
.asInstanceOf(type(ExponentialBackOff.class))
|
||||
.satisfies(hasDefaultMaxAttemptsAndDelay());
|
||||
|
||||
String filters = "excludes=" + names(FileNotFoundException.class, IllegalArgumentException.class,
|
||||
NumberFormatException.class, AssertionError.class) + ", ";
|
||||
assertToString(policy, filters, 1000, 0, 1, Long.MAX_VALUE, Long.MAX_VALUE, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void excludesSubtypeMatching() {
|
||||
var policy = RetryPolicy.builder().excludes(IOException.class).build();
|
||||
|
||||
assertThat(policy.shouldRetry(new IOException("fs"))).isFalse();
|
||||
assertThat(policy.shouldRetry(new FileNotFoundException())).isFalse();
|
||||
assertThat(policy.shouldRetry(new FileSystemException("fs"))).isFalse();
|
||||
|
||||
assertThat(policy.shouldRetry(new Throwable())).isTrue();
|
||||
assertThat(policy.shouldRetry(new AssertionError())).isTrue();
|
||||
|
||||
assertToString(policy, "excludes=[java.io.IOException], ", 1000, 0, 1, Long.MAX_VALUE, Long.MAX_VALUE, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void predicate() {
|
||||
var policy = RetryPolicy.builder()
|
||||
.predicate(new NumberFormatExceptionMatcher())
|
||||
.build();
|
||||
|
||||
assertThat(policy.shouldRetry(new NumberFormatException())).isTrue();
|
||||
assertThat(policy.shouldRetry(new CustomNumberFormatException())).isTrue();
|
||||
|
||||
assertThat(policy.shouldRetry(new Throwable())).isFalse();
|
||||
assertThat(policy.shouldRetry(new Exception())).isFalse();
|
||||
|
||||
assertThat(policy.getBackOff())
|
||||
.asInstanceOf(type(ExponentialBackOff.class))
|
||||
.satisfies(hasDefaultMaxAttemptsAndDelay());
|
||||
|
||||
assertToString(policy, "predicate=NumberFormatExceptionMatcher, ",
|
||||
1000, 0, 1, Long.MAX_VALUE, Long.MAX_VALUE, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void predicatesCombined() {
|
||||
var BOOM = "Boom!";
|
||||
var policy = RetryPolicy.builder()
|
||||
.predicate(new NumberFormatExceptionMatcher())
|
||||
.predicate(throwable -> BOOM.equals(throwable.getMessage()))
|
||||
.build();
|
||||
|
||||
assertThat(policy.shouldRetry(new NumberFormatException(BOOM))).isTrue();
|
||||
assertThat(policy.shouldRetry(new CustomNumberFormatException(BOOM))).isTrue();
|
||||
|
||||
assertThat(policy.shouldRetry(new NumberFormatException())).isFalse();
|
||||
assertThat(policy.shouldRetry(new CustomNumberFormatException())).isFalse();
|
||||
assertThat(policy.shouldRetry(new Throwable())).isFalse();
|
||||
assertThat(policy.shouldRetry(new Exception())).isFalse();
|
||||
|
||||
assertThat(policy.getBackOff())
|
||||
.asInstanceOf(type(ExponentialBackOff.class))
|
||||
.satisfies(hasDefaultMaxAttemptsAndDelay());
|
||||
|
||||
assertThat(policy).asString()
|
||||
.matches("DefaultRetryPolicy\\[predicate=Predicate.+?Lambda.+?, backOff=ExponentialBackOff\\[.+?]]");
|
||||
}
|
||||
|
||||
private static void assertToString(RetryPolicy policy, long initialInterval, long jitter,
|
||||
double multiplier, long maxInterval, long maxElapsedTime, int maxAttempts) {
|
||||
|
||||
assertToString(policy, "", initialInterval, jitter, multiplier, maxInterval, maxElapsedTime, maxAttempts);
|
||||
}
|
||||
|
||||
private static void assertToString(RetryPolicy policy, String filters, long initialInterval, long jitter,
|
||||
double multiplier, long maxInterval, long maxElapsedTime, int maxAttempts) {
|
||||
|
||||
assertThat(policy).asString()
|
||||
.isEqualTo("""
|
||||
DefaultRetryPolicy[%sbackOff=ExponentialBackOff[\
|
||||
initialInterval=%d, \
|
||||
jitter=%d, \
|
||||
multiplier=%s, \
|
||||
maxInterval=%d, \
|
||||
maxElapsedTime=%d, \
|
||||
maxAttempts=%d\
|
||||
]]""",
|
||||
filters, initialInterval, jitter, multiplier, maxInterval, maxElapsedTime, maxAttempts);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
@SuppressWarnings("unchecked")
|
||||
private static String names(Class<? extends Throwable>... types) {
|
||||
StringJoiner result = new StringJoiner(", ", "[", "]");
|
||||
for (Class<? extends Throwable> type : types) {
|
||||
String name = type.getCanonicalName();
|
||||
result.add(name != null? name : type.getName());
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static ThrowingConsumer<? super ExponentialBackOff> hasDefaultMaxAttemptsAndDelay() {
|
||||
return backOff -> {
|
||||
assertThat(backOff.getMaxAttempts()).isEqualTo(3);
|
||||
assertThat(backOff.getInitialInterval()).isEqualTo(1000);
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
private static class CustomNumberFormatException extends NumberFormatException {
|
||||
|
||||
CustomNumberFormatException() {
|
||||
}
|
||||
|
||||
CustomNumberFormatException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -30,18 +30,17 @@ import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments.ArgumentSet;
|
||||
import org.junit.jupiter.params.provider.FieldSource;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Tests for {@link RetryTemplate}.
|
||||
* Integration tests for {@link RetryTemplate} and {@link RetryPolicy}.
|
||||
*
|
||||
* @author Mahmoud Ben Hassine
|
||||
* @author Sam Brannen
|
||||
* @since 7.0
|
||||
* @see RetryPolicyTests
|
||||
*/
|
||||
class RetryTemplateTests {
|
||||
|
||||
@@ -49,8 +48,13 @@ class RetryTemplateTests {
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void configureTemplate() {
|
||||
this.retryTemplate.setBackOffPolicy(new FixedBackOff(Duration.ofMillis(10)));
|
||||
void configureRetryTemplate() {
|
||||
var retryPolicy = RetryPolicy.builder()
|
||||
.maxAttempts(3)
|
||||
.delay(Duration.ofMillis(1))
|
||||
.build();
|
||||
|
||||
retryTemplate.setRetryPolicy(retryPolicy);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -109,7 +113,7 @@ class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void retryWithFailingRetryableAndCustomRetryPolicyWithMultiplePredicates() {
|
||||
void retryWithFailingRetryableAndMultiplePredicates() {
|
||||
var invocationCount = new AtomicInteger();
|
||||
var exception = new NumberFormatException("Boom!");
|
||||
|
||||
@@ -128,7 +132,7 @@ class RetryTemplateTests {
|
||||
|
||||
var retryPolicy = RetryPolicy.builder()
|
||||
.maxAttempts(5)
|
||||
.maxDuration(Duration.ofMillis(100))
|
||||
.delay(Duration.ofMillis(1))
|
||||
.predicate(NumberFormatException.class::isInstance)
|
||||
.predicate(t -> t.getMessage().equals("Boom!"))
|
||||
.build();
|
||||
@@ -167,6 +171,7 @@ class RetryTemplateTests {
|
||||
|
||||
var retryPolicy = RetryPolicy.builder()
|
||||
.maxAttempts(Integer.MAX_VALUE)
|
||||
.delay(Duration.ofMillis(1))
|
||||
.includes(IOException.class)
|
||||
.build();
|
||||
|
||||
@@ -189,11 +194,13 @@ class RetryTemplateTests {
|
||||
argumentSet("Excludes",
|
||||
RetryPolicy.builder()
|
||||
.maxAttempts(Integer.MAX_VALUE)
|
||||
.delay(Duration.ofMillis(1))
|
||||
.excludes(FileNotFoundException.class)
|
||||
.build()),
|
||||
argumentSet("Includes & Excludes",
|
||||
RetryPolicy.builder()
|
||||
.maxAttempts(Integer.MAX_VALUE)
|
||||
.delay(Duration.ofMillis(1))
|
||||
.includes(IOException.class)
|
||||
.excludes(FileNotFoundException.class)
|
||||
.build())
|
||||
@@ -201,7 +208,7 @@ class RetryTemplateTests {
|
||||
|
||||
@ParameterizedTest
|
||||
@FieldSource("includesAndExcludesRetryPolicies")
|
||||
void retryWithIncludesAndExcludesRetryPolicies(RetryPolicy retryPolicy) {
|
||||
void retryWithExceptionIncludesAndExcludes(RetryPolicy retryPolicy) {
|
||||
retryTemplate.setRetryPolicy(retryPolicy);
|
||||
|
||||
var invocationCount = new AtomicInteger();
|
||||
|
||||
+20
-18
@@ -21,8 +21,9 @@ import java.util.List;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.retry.RetryExecution;
|
||||
import org.springframework.core.retry.RetryListener;
|
||||
import org.springframework.core.retry.RetryPolicy;
|
||||
import org.springframework.core.retry.Retryable;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -39,7 +40,8 @@ class CompositeRetryListenerTests {
|
||||
private final RetryListener listener1 = mock();
|
||||
private final RetryListener listener2 = mock();
|
||||
private final RetryListener listener3 = mock();
|
||||
private final RetryExecution retryExecution = mock();
|
||||
private final RetryPolicy retryPolicy = mock();
|
||||
private final Retryable<?> retryable = mock();
|
||||
|
||||
private final CompositeRetryListener compositeRetryListener =
|
||||
new CompositeRetryListener(List.of(listener1, listener2));
|
||||
@@ -52,41 +54,41 @@ class CompositeRetryListenerTests {
|
||||
|
||||
@Test
|
||||
void beforeRetry() {
|
||||
compositeRetryListener.beforeRetry(retryExecution);
|
||||
compositeRetryListener.beforeRetry(retryPolicy, retryable);
|
||||
|
||||
verify(listener1).beforeRetry(retryExecution);
|
||||
verify(listener2).beforeRetry(retryExecution);
|
||||
verify(listener3).beforeRetry(retryExecution);
|
||||
verify(listener1).beforeRetry(retryPolicy, retryable);
|
||||
verify(listener2).beforeRetry(retryPolicy, retryable);
|
||||
verify(listener3).beforeRetry(retryPolicy, retryable);
|
||||
}
|
||||
|
||||
@Test
|
||||
void onRetrySuccess() {
|
||||
Object result = new Object();
|
||||
compositeRetryListener.onRetrySuccess(retryExecution, result);
|
||||
compositeRetryListener.onRetrySuccess(retryPolicy, retryable, result);
|
||||
|
||||
verify(listener1).onRetrySuccess(retryExecution, result);
|
||||
verify(listener2).onRetrySuccess(retryExecution, result);
|
||||
verify(listener3).onRetrySuccess(retryExecution, result);
|
||||
verify(listener1).onRetrySuccess(retryPolicy, retryable, result);
|
||||
verify(listener2).onRetrySuccess(retryPolicy, retryable, result);
|
||||
verify(listener3).onRetrySuccess(retryPolicy, retryable, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void onRetryFailure() {
|
||||
Exception exception = new Exception();
|
||||
compositeRetryListener.onRetryFailure(retryExecution, exception);
|
||||
compositeRetryListener.onRetryFailure(retryPolicy, retryable, exception);
|
||||
|
||||
verify(listener1).onRetryFailure(retryExecution, exception);
|
||||
verify(listener2).onRetryFailure(retryExecution, exception);
|
||||
verify(listener3).onRetryFailure(retryExecution, exception);
|
||||
verify(listener1).onRetryFailure(retryPolicy, retryable, exception);
|
||||
verify(listener2).onRetryFailure(retryPolicy, retryable, exception);
|
||||
verify(listener3).onRetryFailure(retryPolicy, retryable, exception);
|
||||
}
|
||||
|
||||
@Test
|
||||
void onRetryPolicyExhaustion() {
|
||||
Exception exception = new Exception();
|
||||
compositeRetryListener.onRetryPolicyExhaustion(retryExecution, exception);
|
||||
compositeRetryListener.onRetryPolicyExhaustion(retryPolicy, retryable, exception);
|
||||
|
||||
verify(listener1).onRetryPolicyExhaustion(retryExecution, exception);
|
||||
verify(listener2).onRetryPolicyExhaustion(retryExecution, exception);
|
||||
verify(listener3).onRetryPolicyExhaustion(retryExecution, exception);
|
||||
verify(listener1).onRetryPolicyExhaustion(retryPolicy, retryable, exception);
|
||||
verify(listener2).onRetryPolicyExhaustion(retryPolicy, retryable, exception);
|
||||
verify(listener3).onRetryPolicyExhaustion(retryPolicy, retryable, exception);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user