Convert repeated tests to JUnit 5 @RepeatableTest

- add lifecycle to `@LogLevels` to avoid adjusting the log on each iteration
- with `@RepeatableTest` there is a template context between the class and method contexts

* * Remove Lifecycle from `@LogLevels`
* Only apply levels once for a class-level annotation
* Log a new delimiter between tests when using a class-level annotation
* Only log one delimiter per test method (e.g. when `@RepeatedTest`)
This commit is contained in:
Gary Russell
2019-08-21 17:43:31 -04:00
committed by Artem Bilan
parent 28e91034d6
commit 4b19b61d1e
6 changed files with 158 additions and 164 deletions

View File

@@ -53,8 +53,8 @@ public @interface LogLevels {
/**
* The Log4j level name to switch the categories to during the test.
* @return the level (default DEBUG).
* @return the level (default Log4j {@code Levels.toLevel()} - currently, DEBUG).
*/
String level() default "DEBUG";
String level() default "";
}

View File

@@ -17,12 +17,17 @@
package org.springframework.amqp.rabbit.junit;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.LogFactory;
import org.apache.logging.log4j.Level;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition;
@@ -33,6 +38,7 @@ import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.springframework.amqp.rabbit.junit.JUnitUtils.LevelsContainer;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.log.LogAccessor;
/**
* JUnit condition that adjusts and reverts log levels before/after each test.
@@ -42,7 +48,9 @@ import org.springframework.core.annotation.MergedAnnotations;
*
*/
public class LogLevelsCondition
implements ExecutionCondition, BeforeEachCallback, AfterEachCallback, AfterAllCallback {
implements ExecutionCondition, BeforeEachCallback, AfterEachCallback, BeforeAllCallback, AfterAllCallback {
private static final LogAccessor logger = new LogAccessor(LogFactory.getLog(LogLevelsCondition.class));
private static final String STORE_ANNOTATION_KEY = "logLevelsAnnotation";
@@ -51,6 +59,8 @@ public class LogLevelsCondition
private static final ConditionEvaluationResult ENABLED =
ConditionEvaluationResult.enabled("@LogLevels always enabled");
private final Map<String, Boolean> loggedMethods = new ConcurrentHashMap<>();
@Override
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
Optional<AnnotatedElement> element = context.getElement();
@@ -66,14 +76,9 @@ public class LogLevelsCondition
}
@Override
public void beforeEach(ExtensionContext context) {
public void beforeAll(ExtensionContext context) {
Store store = context.getStore(Namespace.create(getClass(), context));
LogLevels logLevels = store.get(STORE_ANNOTATION_KEY, LogLevels.class);
if (logLevels == null) {
ExtensionContext parent = context.getParent().get();
store = parent.getStore(Namespace.create(getClass(), parent));
logLevels = store.get(STORE_ANNOTATION_KEY, LogLevels.class);
}
if (logLevels != null) {
store.put(STORE_CONTAINER_KEY, JUnitUtils.adjustLogLevels(context.getDisplayName(),
Arrays.asList((logLevels.classes())),
@@ -82,22 +87,36 @@ public class LogLevelsCondition
}
}
@Override
public void beforeEach(ExtensionContext context) {
Store store = context.getStore(Namespace.create(getClass(), context));
LogLevels logLevels = store.get(STORE_ANNOTATION_KEY, LogLevels.class);
if (logLevels != null) { // Method level annotation
if (store.get(STORE_CONTAINER_KEY) == null) {
store.put(STORE_CONTAINER_KEY, JUnitUtils.adjustLogLevels(context.getDisplayName(),
Arrays.asList((logLevels.classes())),
Arrays.asList(logLevels.categories()),
Level.toLevel(logLevels.level())));
}
}
else {
Optional<Method> testMethod = context.getTestMethod();
if (testMethod.isPresent()
&& this.loggedMethods.putIfAbsent(testMethod.get().getName(), Boolean.TRUE) == null) {
logger.info(() -> "+++++++++++++++++++++++++++++ Begin " + testMethod.get().getName());
}
}
}
@Override
public void afterEach(ExtensionContext context) {
Store store = context.getStore(Namespace.create(getClass(), context));
LevelsContainer container = store.get(STORE_CONTAINER_KEY, LevelsContainer.class);
boolean parentStore = false;
if (container == null) {
ExtensionContext parent = context.getParent().get();
store = parent.getStore(Namespace.create(getClass(), parent));
container = store.get(STORE_CONTAINER_KEY, LevelsContainer.class);
parentStore = true;
}
if (container != null) {
JUnitUtils.revertLevels(context.getDisplayName(), container);
store.remove(STORE_CONTAINER_KEY);
if (!parentStore) {
store.remove(STORE_ANNOTATION_KEY);
LogLevels logLevels = store.get(STORE_ANNOTATION_KEY, LogLevels.class);
if (logLevels != null) {
JUnitUtils.revertLevels(context.getDisplayName(), container);
store.remove(STORE_CONTAINER_KEY);
}
}
}
@@ -105,7 +124,13 @@ public class LogLevelsCondition
@Override
public void afterAll(ExtensionContext context) {
Store store = context.getStore(Namespace.create(getClass(), context));
store.remove(STORE_ANNOTATION_KEY);
LogLevels logLevels = store.remove(STORE_ANNOTATION_KEY, LogLevels.class);
if (logLevels != null) {
LevelsContainer container = store.get(STORE_CONTAINER_KEY, LevelsContainer.class);
JUnitUtils.revertLevels(context.getDisplayName(), container);
store.remove(STORE_CONTAINER_KEY);
}
this.loggedMethods.clear();
}
}

View File

@@ -18,19 +18,22 @@ package org.springframework.amqp.rabbit.core;
import static org.assertj.core.api.Assertions.assertThat;
import org.apache.logging.log4j.Level;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.RepeatedTest;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.junit.BrokerRunning;
import org.springframework.amqp.rabbit.junit.BrokerTestUtils;
import org.springframework.amqp.rabbit.junit.LogLevelAdjuster;
import org.springframework.amqp.rabbit.junit.LongRunningIntegrationTest;
import org.springframework.amqp.rabbit.test.RepeatProcessor;
import org.springframework.test.annotation.Repeat;
import org.springframework.amqp.rabbit.junit.LogLevels;
import org.springframework.amqp.rabbit.junit.RabbitAvailable;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
@@ -44,87 +47,77 @@ import org.springframework.transaction.support.TransactionTemplate;
* @since 1.0
*
*/
@RabbitAvailable(queues = RabbitTemplatePerformanceIntegrationTests.ROUTE)
@LogLevels(level = "ERROR", classes = RabbitTemplate.class)
public class RabbitTemplatePerformanceIntegrationTests {
private static final String ROUTE = "test.queue";
public static final String ROUTE = "test.queue.RabbitTemplatePerformanceIntegrationTests";
private final RabbitTemplate template = new RabbitTemplate();
private static final RabbitTemplate template = new RabbitTemplate();
@Rule
public LongRunningIntegrationTest longTests = new LongRunningIntegrationTest();
private static final ExecutorService exec = Executors.newFixedThreadPool(4);
@Rule
public RepeatProcessor repeat = new RepeatProcessor(4);
private static CachingConnectionFactory connectionFactory;
@Rule
// After the repeat processor, so it only runs once
public LogLevelAdjuster logLevels = new LogLevelAdjuster(Level.ERROR, RabbitTemplate.class);
@Rule
// After the repeat processor, so it only runs once
public BrokerRunning brokerIsRunning = BrokerRunning.isRunningWithEmptyQueues(ROUTE);
private CachingConnectionFactory connectionFactory;
@Before
public void declareQueue() {
if (repeat.isInitialized()) {
// Important to prevent concurrent re-initialization
return;
}
@BeforeAll
public static void declareQueue() {
connectionFactory = new CachingConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setChannelCacheSize(repeat.getConcurrency());
connectionFactory.setPort(BrokerTestUtils.getPort());
template.setConnectionFactory(connectionFactory);
}
@After
public void cleanUp() {
if (!repeat.isFinalizing()) {
return;
}
this.template.stop();
this.connectionFactory.destroy();
this.brokerIsRunning.removeTestQueues();
@AfterAll
public static void cleanUp() {
template.stop();
connectionFactory.destroy();
exec.shutdownNow();
}
@Test
@Repeat(200)
public void testSendAndReceive() throws Exception {
template.convertAndSend(ROUTE, "message");
String result = (String) template.receiveAndConvert(ROUTE);
int count = 5;
while (result == null && count-- > 0) {
/*
* Retry for the purpose of non-transacted case because channel operations are async in that case
*/
Thread.sleep(10L);
result = (String) template.receiveAndConvert(ROUTE);
}
assertThat(result).isEqualTo("message");
}
@Test
@Repeat(200)
public void testSendAndReceiveTransacted() throws Exception {
template.setChannelTransacted(true);
template.convertAndSend(ROUTE, "message");
String result = (String) template.receiveAndConvert(ROUTE);
assertThat(result).isEqualTo("message");
}
@Test
@Repeat(200)
public void testSendAndReceiveExternalTransacted() throws Exception {
template.setChannelTransacted(true);
new TransactionTemplate(new TestTransactionManager()).execute(status -> {
@RepeatedTest(50)
public void testSendAndReceive() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(4);
List<String> results = new ArrayList<>();
Stream.of(1, 2, 3, 4).forEach(i -> exec.execute(() -> {
template.convertAndSend(ROUTE, "message");
return null;
});
template.convertAndSend(ROUTE, "message");
String result = (String) template.receiveAndConvert(ROUTE);
assertThat(result).isEqualTo("message");
results.add((String) template.receiveAndConvert(ROUTE, 10_000L));
latch.countDown();
}));
assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
assertThat(results).contains("message", "message", "message", "message");
}
@RepeatedTest(50)
public void testSendAndReceiveTransacted() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(4);
List<String> results = new ArrayList<>();
template.setChannelTransacted(true);
Stream.of(1, 2, 3, 4).forEach(i -> exec.execute(() -> {
template.convertAndSend(ROUTE, "message");
results.add((String) template.receiveAndConvert(ROUTE, 10_000L));
latch.countDown();
}));
assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
assertThat(results).contains("message", "message", "message", "message");
}
@RepeatedTest(50)
public void testSendAndReceiveExternalTransacted() throws InterruptedException {
template.setChannelTransacted(true);
CountDownLatch latch = new CountDownLatch(4);
List<String> results = new ArrayList<>();
template.setChannelTransacted(true);
Stream.of(1, 2, 3, 4).forEach(i -> exec.execute(() -> {
new TransactionTemplate(new TestTransactionManager()).execute(status -> {
template.convertAndSend(ROUTE, "message");
return null;
});
template.convertAndSend(ROUTE, "message");
results.add((String) template.receiveAndConvert(ROUTE, 10_000L));
latch.countDown();
}));
assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
assertThat(results).contains("message", "message", "message", "message");
}
@SuppressWarnings("serial")

View File

@@ -27,10 +27,8 @@ import java.util.concurrent.atomic.AtomicInteger;
import org.aopalliance.aop.Advice;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.logging.log4j.Level;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.core.Queue;
@@ -39,18 +37,16 @@ import org.springframework.amqp.rabbit.config.StatefulRetryOperationsInterceptor
import org.springframework.amqp.rabbit.config.StatelessRetryOperationsInterceptorFactoryBean;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.junit.BrokerRunning;
import org.springframework.amqp.rabbit.junit.BrokerTestUtils;
import org.springframework.amqp.rabbit.junit.LogLevelAdjuster;
import org.springframework.amqp.rabbit.junit.LogLevels;
import org.springframework.amqp.rabbit.junit.RabbitAvailable;
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
import org.springframework.amqp.rabbit.test.RepeatProcessor;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.amqp.support.converter.SimpleMessageConverter;
import org.springframework.amqp.utils.SerializationUtils;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.retry.policy.MapRetryContextCache;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.test.annotation.Repeat;
/**
* @author Dave Syer
@@ -61,25 +57,17 @@ import org.springframework.test.annotation.Repeat;
* @since 1.0
*
*/
@RabbitAvailable(queues = MessageListenerContainerRetryIntegrationTests.TEST_QUEUE)
@LogLevels(level = "ERROR", classes = {
RabbitTemplate.class, SimpleMessageListenerContainer.class, BlockingQueueConsumer.class,
StatefulRetryOperationsInterceptorFactoryBean.class, MessageListenerContainerRetryIntegrationTests.class })
public class MessageListenerContainerRetryIntegrationTests {
public static final String TEST_QUEUE = "test.queue.MessageListenerContainerRetryIntegrationTests";
private static Log logger = LogFactory.getLog(MessageListenerContainerRetryIntegrationTests.class);
private static Queue queue = new Queue("test.queue");
@Rule
public BrokerRunning brokerIsRunning = BrokerRunning.isRunningWithEmptyQueues(queue.getName());
@Rule
public LogLevelAdjuster logLevels = new LogLevelAdjuster(Level.ERROR, RabbitTemplate.class,
SimpleMessageListenerContainer.class, BlockingQueueConsumer.class);
@Rule
public LogLevelAdjuster traceLevels = new LogLevelAdjuster(Level.ERROR,
StatefulRetryOperationsInterceptorFactoryBean.class, MessageListenerContainerRetryIntegrationTests.class);
@Rule
public RepeatProcessor repeats = new RepeatProcessor();
private static Queue queue = new Queue(TEST_QUEUE);
private RetryTemplate retryTemplate;
@@ -93,21 +81,14 @@ public class MessageListenerContainerRetryIntegrationTests {
connectionFactory.setPort(BrokerTestUtils.getPort());
template.setConnectionFactory(connectionFactory);
if (messageConverter == null) {
SimpleMessageConverter messageConverter = new SimpleMessageConverter();
messageConverter.setCreateMessageIds(true);
this.messageConverter = messageConverter;
SimpleMessageConverter converter = new SimpleMessageConverter();
converter.setCreateMessageIds(true);
this.messageConverter = converter;
}
template.setMessageConverter(messageConverter);
return template;
}
@After
public void tearDown() {
if (this.repeats.isFinalizing()) {
this.brokerIsRunning.removeTestQueues();
}
}
@Test
public void testStatefulRetryWithAllMessagesFailing() throws Exception {
@@ -153,8 +134,7 @@ public class MessageListenerContainerRetryIntegrationTests {
.hasMessageContaining("but was not.");
}
@Test
@Repeat(10)
@RepeatedTest(10)
public void testStatefulRetryWithTxSizeAndIntermittentFailure() throws Exception {
int messageCount = 10;

View File

@@ -25,11 +25,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.logging.log4j.Level;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.RepetitionInfo;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.core.Message;
@@ -37,15 +38,13 @@ import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.junit.BrokerRunning;
import org.springframework.amqp.rabbit.junit.BrokerTestUtils;
import org.springframework.amqp.rabbit.junit.LogLevelAdjuster;
import org.springframework.amqp.rabbit.junit.LogLevels;
import org.springframework.amqp.rabbit.junit.RabbitAvailable;
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.amqp.rabbit.listener.exception.FatalListenerExecutionException;
import org.springframework.amqp.rabbit.test.RepeatProcessor;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.test.annotation.Repeat;
import com.rabbitmq.client.Channel;
@@ -57,13 +56,19 @@ import com.rabbitmq.client.Channel;
* @author Gary Russell
*
*/
@RabbitAvailable(queues = MessageListenerRecoveryRepeatIntegrationTests.TEST_QUEUE, purgeAfterEach = false)
@LogLevels(level = "ERROR", classes = { RabbitTemplate.class,
ConditionalRejectingErrorHandler.class,
SimpleMessageListenerContainer.class, BlockingQueueConsumer.class,
MessageListenerRecoveryRepeatIntegrationTests.class })
@TestInstance(Lifecycle.PER_CLASS)
public class MessageListenerRecoveryRepeatIntegrationTests {
public static final String TEST_QUEUE = "test.queue.MessageListenerRecoveryRepeatIntegrationTests";
private static Log logger = LogFactory.getLog(MessageListenerRecoveryRepeatIntegrationTests.class);
private final Queue queue = new Queue("test.queue");
private final Queue sendQueue = new Queue("test.send");
private final Queue queue = new Queue(TEST_QUEUE);
private final int concurrentConsumers = 1;
@@ -77,33 +82,22 @@ public class MessageListenerRecoveryRepeatIntegrationTests {
private SimpleMessageListenerContainer container;
@Rule
public LogLevelAdjuster logLevels = new LogLevelAdjuster(Level.ERROR, RabbitTemplate.class,
ConditionalRejectingErrorHandler.class,
SimpleMessageListenerContainer.class, BlockingQueueConsumer.class, MessageListenerRecoveryRepeatIntegrationTests.class);
@Rule
public BrokerRunning brokerIsRunning = BrokerRunning.isRunningWithEmptyQueues(queue.getName(), sendQueue.getName());
@Rule
public RepeatProcessor repeatProcessor = new RepeatProcessor();
private CloseConnectionListener listener;
private ConnectionFactory connectionFactory;
@Before
public void init() {
if (!repeatProcessor.isInitialized()) {
@BeforeEach
public void init(RepetitionInfo info) {
if (info.getCurrentRepetition() == 1) {
logger.info("Initializing at start of test");
connectionFactory = createConnectionFactory();
listener = new CloseConnectionListener();
}
}
@After
public void clear() throws Exception {
if (repeatProcessor.isFinalizing()) {
@AfterEach
public void clear(RepetitionInfo info) throws Exception {
if (info.getCurrentRepetition() == info.getTotalRepetitions()) {
// Wait for broker communication to finish before trying to stop container
Thread.sleep(300L);
logger.info("Shutting down at end of test");
@@ -113,12 +107,10 @@ public class MessageListenerRecoveryRepeatIntegrationTests {
if (connectionFactory != null) {
((DisposableBean) connectionFactory).destroy();
}
this.brokerIsRunning.removeTestQueues();
}
}
@Test
@Repeat(1000)
@RepeatedTest(1000)
public void testListenerRecoversFromClosedConnection() throws Exception {
if (this.container == null) {
this.container = createContainer(queue.getName(), listener, connectionFactory);
@@ -163,6 +155,7 @@ public class MessageListenerRecoveryRepeatIntegrationTests {
container.setChannelTransacted(transactional);
container.setAcknowledgeMode(acknowledgeMode);
container.setTaskExecutor(Executors.newFixedThreadPool(concurrentConsumers));
container.setReceiveTimeout(100L);
container.afterPropertiesSet();
container.start();
return container;

View File

@@ -34,6 +34,7 @@ import org.junit.After;
import org.junit.Before;
import org.junit.internal.runners.statements.RunAfters;
import org.junit.internal.runners.statements.RunBefores;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.rules.MethodRule;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;
@@ -46,10 +47,12 @@ import org.springframework.test.annotation.Repeat;
* A JUnit method &#064;Rule that looks at Spring repeat annotations on methods and executes the test multiple times
* (without re-initializing the test case if necessary). To avoid re-initializing use the {@link #isInitialized()}
* method to protect the &#64;Before and &#64;After methods.
* @deprecated in favor of JUnit 5 {@link RepeatedTest}.
*
* @author Dave Syer
*
*/
@Deprecated
public class RepeatProcessor implements MethodRule {
private static final Log logger = LogFactory.getLog(RepeatProcessor.class);