Skip to content

Commit

Permalink
Convert repeated tests to JUnit 5 @RepeatableTest
Browse files Browse the repository at this point in the history
- 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`)
  • Loading branch information
garyrussell authored and artembilan committed Aug 21, 2019
1 parent 28e9103 commit 4b19b61
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 158 deletions.
Expand Up @@ -53,8 +53,8 @@

/**
* 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 "";

}
Expand Up @@ -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;
Expand All @@ -33,6 +38,7 @@
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.
Expand All @@ -42,7 +48,9 @@
*
*/
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";

Expand All @@ -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();
Expand All @@ -66,14 +76,9 @@ public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext con
}

@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())),
Expand All @@ -82,30 +87,50 @@ public void beforeEach(ExtensionContext context) {
}
}

@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);
}
}
}

@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();
}

}
Expand Up @@ -18,19 +18,22 @@

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;
Expand All @@ -44,87 +47,77 @@
* @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");
@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");
results.add((String) template.receiveAndConvert(ROUTE, 10_000L));
latch.countDown();
}));
assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
assertThat(results).contains("message", "message", "message", "message");
}

@Test
@Repeat(200)
public void testSendAndReceiveTransacted() throws Exception {
@RepeatedTest(50)
public void testSendAndReceiveTransacted() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(4);
List<String> results = new ArrayList<>();
template.setChannelTransacted(true);
template.convertAndSend(ROUTE, "message");
String result = (String) template.receiveAndConvert(ROUTE);
assertThat(result).isEqualTo("message");
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");
}

@Test
@Repeat(200)
public void testSendAndReceiveExternalTransacted() throws Exception {
@RepeatedTest(50)
public void testSendAndReceiveExternalTransacted() throws InterruptedException {
template.setChannelTransacted(true);
CountDownLatch latch = new CountDownLatch(4);
List<String> results = new ArrayList<>();
template.setChannelTransacted(true);
new TransactionTemplate(new TestTransactionManager()).execute(status -> {
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");
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");
}

@SuppressWarnings("serial")
Expand Down

0 comments on commit 4b19b61

Please sign in to comment.