Skip to content

Commit

Permalink
Convert more tests to JUnit5
Browse files Browse the repository at this point in the history
- some low-hanging fruit
- speed up admin tests with declaration failures with a `NoBackoffPolicy` or remove retry altogether
- speed up some tests that were waiting 2 seconds for no message expected

- Polishing Queue.clone()

- decouple `LongRunningIntegrationTestCondition` from JUnit 4

- Add JUnit 5 log level adjuster; find @RabbitAvailable etc on test superclasses

- Support @LogLevels at the method level

- Make JUnit 4 an optional dependency

- Remove annotation from method level context store

- Fix typo in class name

- Fix @SInCE

Javadoc

- Switch to the new Log4j2 logic from Spring Integration
  • Loading branch information
garyrussell authored and artembilan committed Aug 15, 2019
1 parent a22a1ce commit 2c5d037
Show file tree
Hide file tree
Showing 50 changed files with 836 additions and 637 deletions.
7 changes: 4 additions & 3 deletions build.gradle
Expand Up @@ -345,18 +345,18 @@ project('spring-rabbit-junit') {
dependencies { // no spring-amqp dependencies allowed

compile "org.springframework:spring-core:$springVersion"
compile "junit:junit:$junit4Version"
compile ("junit:junit:$junit4Version", optional)
compile "com.rabbitmq:amqp-client:$rabbitmqVersion"
compile ("com.rabbitmq:http-client:$rabbitmqHttpClientVersion") {
exclude group: 'org.springframework', module: 'spring-web'
}
compile "org.springframework:spring-web:$springVersion"
compile ("org.junit.jupiter:junit-jupiter-api:$junitJupiterVersion", optional)
compile "org.assertj:assertj-core:$assertjVersion"
compile ("ch.qos.logback:logback-classic:$logbackVersion", optional)
compile ("org.apache.logging.log4j:log4j-core:$log4jVersion", optional)
compileOnly 'org.apiguardian:apiguardian-api:1.0.0'

testRuntime "ch.qos.logback:logback-classic:$logbackVersion"

}

}
Expand All @@ -368,6 +368,7 @@ project('spring-rabbit-test') {

compile project(":spring-rabbit")
compile ("junit:junit:$junit4Version") {
optional(it)
exclude group: 'org.hamcrest', module: 'hamcrest-core'
}
compile "org.hamcrest:hamcrest-all:$hamcrestVersion"
Expand Down
Expand Up @@ -30,7 +30,7 @@
* @author Gary Russell
* @see AmqpAdmin
*/
public class Queue extends AbstractDeclarable {
public class Queue extends AbstractDeclarable implements Cloneable {

/**
* Argument key for the master locator.
Expand Down Expand Up @@ -175,6 +175,14 @@ public final void setMasterLocator(@Nullable String locator) {
}
}

@Override
public Object clone() {
Queue queue = new Queue(this.name, this.durable, this.exclusive, this.autoDelete,
new HashMap<>(this.arguments));
queue.setActualName(this.actualName);
return queue;
}

@Override
public String toString() {
return "Queue [name=" + this.name + ", durable=" + this.durable + ", autoDelete=" + this.autoDelete
Expand Down
Expand Up @@ -510,11 +510,31 @@ public void removeTestQueues(String... additionalQueues) {
}
}

/**
* Delete and re-declare all the configured queues. Can be used between tests when
* a test might leave stale data and multiple tests use the same queue.
*/
public void purgeTestQueues() {
removeTestQueues();
Connection connection = null; // NOSONAR (closeResources())
Channel channel = null;
try {
connection = getConnection(getConnectionFactory());
channel = createQueues(connection);
}
catch (Exception e) {
logger.warn("Failed to re-declare queues during purge: " + e.getMessage());
}
finally {
closeResources(connection, channel);
}
}

/**
* Delete arbitrary queues from the broker.
* @param queues the queues to delete.
* @param queuesToDelete the queues to delete.
*/
public void deleteQueues(String... queues) {
public void deleteQueues(String... queuesToDelete) {
Connection connection = null; // NOSONAR (closeResources())
Channel channel = null;

Expand All @@ -523,7 +543,7 @@ public void deleteQueues(String... queues) {
connection.setId(generateId() + ".queueDelete");
channel = connection.createChannel();

for (String queue : queues) {
for (String queue : queuesToDelete) {
channel.queueDelete(queue);
}
}
Expand Down Expand Up @@ -603,15 +623,15 @@ private void closeResources(Connection connection, Channel channel) {
try {
channel.close();
}
catch (IOException | TimeoutException e) {
catch (@SuppressWarnings("unused") IOException | TimeoutException e) {
// Ignore
}
}
if (connection != null) {
try {
connection.close();
}
catch (IOException e) {
catch (@SuppressWarnings("unused") IOException e) {
// Ignore
}
}
Expand Down
@@ -0,0 +1,170 @@
/*
* Copyright 2019 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.amqp.rabbit.junit;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.slf4j.LoggerFactory;

/**
* Utility methods for JUnit rules and conditions.
*
* @author Gary Russell
* @since 2.2
*
*/
public final class JUnitUtils {

private static final Log LOGGER = LogFactory.getLog(JUnitUtils.class);

private JUnitUtils() {
super();
}

/**
* Return the parsed value if the provided property exists in the environment or system properties.
* @param property the property name.
* @return the parsed property value if it exists, false otherwise.
*/
public static boolean parseBooleanProperty(String property) {
for (String value: new String[] { System.getenv(property), System.getProperty(property) }) {
if (Boolean.parseBoolean(value)) {
return true;
}
}
return false;
}

public static LevelsContainer adjustLogLevels(String methodName, List<Class<?>> classes, List<String> categories,
Level level) {

LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
Configuration config = ctx.getConfiguration();

Map<Class<?>, Level> classLevels = new HashMap<>();
for (Class<?> cls : classes) {
String className = cls.getName();
LoggerConfig loggerConfig = config.getLoggerConfig(className);
LoggerConfig specificConfig = loggerConfig;

// We need a specific configuration for this logger,
// otherwise we would change the level of all other loggers
// having the original configuration as parent as well

if (!loggerConfig.getName().equals(className)) {
specificConfig = new LoggerConfig(className, loggerConfig.getLevel(), true);
specificConfig.setParent(loggerConfig);
config.addLogger(className, specificConfig);
}

classLevels.put(cls, specificConfig.getLevel());
specificConfig.setLevel(level);
}

Map<String, Level> categoryLevels = new HashMap<>();
for (String category : categories) {
LoggerConfig loggerConfig = config.getLoggerConfig(category);
LoggerConfig specificConfig = loggerConfig;

// We need a specific configuration for this logger,
// otherwise we would change the level of all other loggers
// having the original configuration as parent as well

if (!loggerConfig.getName().equals(category)) {
specificConfig = new LoggerConfig(category, loggerConfig.getLevel(), true);
specificConfig.setParent(loggerConfig);
config.addLogger(category, specificConfig);
}

categoryLevels.put(category, specificConfig.getLevel());
specificConfig.setLevel(level);
}

ctx.updateLoggers();

Map<String, ch.qos.logback.classic.Level> oldLbLevels = new HashMap<>();
categories.stream()
.forEach(cat -> {
ch.qos.logback.classic.Logger lbLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(cat);
oldLbLevels.put(cat, lbLogger.getLevel());
lbLogger.setLevel(ch.qos.logback.classic.Level.toLevel(level.name()));
});
LOGGER.info("++++++++++++++++++++++++++++ "
+ "Overridden log level setting for: "
+ classes.stream()
.map(Class::getSimpleName)
.collect(Collectors.toList())
+ " and " + categories.toString()
+ " for test " + methodName);
return new LevelsContainer(classLevels, categoryLevels, oldLbLevels);
}

public static void revertLevels(String methodName, LevelsContainer container) {
LOGGER.info("++++++++++++++++++++++++++++ "
+ "Restoring log level setting for test " + methodName);
container.oldCatLevels.entrySet()
.stream()
.forEach(entry -> {
if (!entry.getKey().contains("BrokerRunning")) {
((Logger) LogManager.getLogger(entry.getKey())).setLevel(entry.getValue());
}
});
container.oldLevels.entrySet()
.stream()
.forEach(entry -> {
if (!entry.getKey().equals(BrokerRunning.class)) {
((Logger) LogManager.getLogger(entry.getKey())).setLevel(entry.getValue());
}
});
container.oldLbLevels.entrySet()
.stream()
.forEach(entry -> {
((ch.qos.logback.classic.Logger) LoggerFactory.getLogger(entry.getKey()))
.setLevel(entry.getValue());
});
}

public static class LevelsContainer {

final Map<Class<?>, Level> oldLevels;

final Map<String, Level> oldCatLevels;

final Map<String, ch.qos.logback.classic.Level> oldLbLevels;

public LevelsContainer(Map<Class<?>, Level> oldLevels, Map<String, Level> oldCatLevels,
Map<String, ch.qos.logback.classic.Level> oldLbLevels) {

this.oldLevels = oldLevels;
this.oldCatLevels = oldCatLevels;
this.oldLbLevels = oldLbLevels;
}

}

}
@@ -0,0 +1,87 @@
/*
* Copyright 2002-2019 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.amqp.rabbit.junit;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.apache.logging.log4j.Level;
import org.junit.rules.MethodRule;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;

import org.springframework.amqp.rabbit.junit.JUnitUtils.LevelsContainer;

/**
* A JUnit method &#064;Rule that changes the logger level for a set of classes
* while a test method is running. Useful for performance or scalability tests
* where we don't want to generate a large log in a tight inner loop.
*
* As well as adjusting Log4j, this also adjusts loggers for logback. The amqp-client
* library now uses slf4j. Since we have logback on the CP (for the appender)
* we can't add the slf4j-log4j bridge as well.
*
* @author Dave Syer
* @author Artem Bilan
* @author Gary Russell
*
*/
public class LogLevelAdjuster implements MethodRule {

private final List<Class<?>> classes;

private List<String> categories;

private final Level level;

public LogLevelAdjuster(Level level, Class<?>... classes) {
this.level = level;
this.classes = new ArrayList<>(Arrays.asList(classes));
this.classes.add(getClass());
this.categories = Collections.emptyList();
}

public LogLevelAdjuster categories(String... categoriesToAdjust) {
this.categories = new ArrayList<>(Arrays.asList(categoriesToAdjust));
return this;
}

@Override
public Statement apply(final Statement base, FrameworkMethod method, Object target) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
LevelsContainer container = null;
try {
container = JUnitUtils.adjustLogLevels(method.getName(),
LogLevelAdjuster.this.classes, LogLevelAdjuster.this.categories,
LogLevelAdjuster.this.level);
base.evaluate();
}
finally {
if (container != null) {
JUnitUtils.revertLevels(method.getName(), container);
}
}
}

};
}

}

0 comments on commit 2c5d037

Please sign in to comment.