JUnit 5 Log Level Adjuster

* Fix copyright, javadoc.
This commit is contained in:
Gary Russell
2019-08-16 10:36:23 -04:00
committed by Artem Bilan
parent a4f7412cf5
commit 69dd180d0c
5 changed files with 290 additions and 70 deletions

View File

@@ -17,9 +17,15 @@
package org.springframework.integration.transformer;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import org.apache.commons.logging.Log;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -28,6 +34,8 @@ import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.test.condition.LogLevels;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.integration.transformer.support.AvroHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.PollableChannel;
@@ -40,11 +48,15 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
*
*/
@SpringJUnitConfig
@LogLevels(categories = "foo", level = "DEBUG")
public class AvroTests {
@Test
@LogLevels(classes = DirectChannel.class, categories = "bar", level = "DEBUG")
void testTransformers(@Autowired Config config) {
AvroTestClass1 test = new AvroTestClass1("baz", "fiz");
Log spied = spy(TestUtils.getPropertyValue(config.in1(), "logger", Log.class));
new DirectFieldAccessor(config.in1()).setPropertyValue("logger", spied);
config.in1().send(new GenericMessage<>(test));
assertThat(config.tapped().receive(0))
.isNotNull()
@@ -57,6 +69,10 @@ public class AvroTests {
.isEqualTo(test)
.isNotSameAs(test);
assertThat(received.getHeaders().get("flow")).isEqualTo("flow1");
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(spied, atLeastOnce()).debug(captor.capture());
assertThat(captor.getAllValues()).anyMatch(s -> s.contains("preSend on channel"));
assertThat(captor.getAllValues()).anyMatch(s -> s.contains("postSend (sent=true) on channel"));
}
@Test

View File

@@ -0,0 +1,60 @@
/*
* 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.integration.test.condition;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.extension.ExtendWith;
/**
* Test classes annotated with this will change logging levels between tests. It can also
* be applied to individual test methods. If both class-level and method-level annotations
* are present, the method-level annotation is used.
*
* @author Gary Russell
* @since 5.2
*
*/
@ExtendWith(LogLevelsCondition.class)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogLevels {
/**
* Classes representing Log4j categories to change.
* @return the classes.
*/
Class<?>[] classes() default {};
/**
* Category names representing Log4j categories to change.
* @return the names.
*/
String[] categories() default {};
/**
* The Log4j level name to switch the categories to during the test.
* @return the level.
*/
String level() default "";
}

View File

@@ -0,0 +1,108 @@
/*
* 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.integration.test.condition;
import java.lang.reflect.AnnotatedElement;
import java.util.Arrays;
import java.util.Optional;
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.BeforeEachCallback;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.integration.test.util.TestUtils.LevelsContainer;
/**
* JUnit condition that adjusts and reverts log levels before/after each test.
*
* @author Gary Russell
* @since 5.2
*
*/
public class LogLevelsCondition
implements ExecutionCondition, BeforeEachCallback, AfterEachCallback, AfterAllCallback {
private static final String STORE_ANNOTATION_KEY = "logLevelsAnnotation";
private static final String STORE_CONTAINER_KEY = "logLevelsContainer";
private static final ConditionEvaluationResult ENABLED =
ConditionEvaluationResult.enabled("@LogLevels always enabled");
@Override
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
Optional<AnnotatedElement> element = context.getElement();
MergedAnnotations annotations = MergedAnnotations.from(element.get(),
MergedAnnotations.SearchStrategy.TYPE_HIERARCHY);
MergedAnnotation<LogLevels> mergedAnnotation = annotations.get(LogLevels.class);
if (mergedAnnotation.isPresent()) {
LogLevels loglevels = mergedAnnotation.synthesize();
Store store = context.getStore(Namespace.create(getClass(), context));
store.put(STORE_ANNOTATION_KEY, loglevels);
}
return ENABLED;
}
@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) {
ExtensionContext parent = context.getParent().get();
store = parent.getStore(Namespace.create(getClass(), parent));
logLevels = store.get(STORE_ANNOTATION_KEY, LogLevels.class);
}
store.put(STORE_CONTAINER_KEY, TestUtils.adjustLogLevels(context.getDisplayName(),
Arrays.asList((logLevels.classes())),
Arrays.asList(logLevels.categories()),
Level.toLevel(logLevels.level())));
}
@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;
}
TestUtils.revertLogLevels(context.getDisplayName(), container);
store.remove(STORE_CONTAINER_KEY);
if (!parentStore) {
store.remove(STORE_ANNOTATION_KEY);
}
}
@Override
public void afterAll(ExtensionContext context) {
Store store = context.getStore(Namespace.create(getClass(), context));
store.remove(STORE_ANNOTATION_KEY);
}
}

View File

@@ -17,21 +17,17 @@
package org.springframework.integration.test.rule;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
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.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.junit.rules.MethodRule;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.integration.test.util.TestUtils.LevelsContainer;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
@@ -81,75 +77,18 @@ public final class Log4j2LevelAdjuster implements MethodRule {
@Override
public void evaluate() throws Throwable {
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
Configuration config = ctx.getConfiguration();
Map<Class<?>, Level> classLevels = new HashMap<>();
for (Class<?> cls : Log4j2LevelAdjuster.this.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, Log4j2LevelAdjuster.this.level, true);
specificConfig.setParent(loggerConfig);
config.addLogger(className, specificConfig);
}
classLevels.put(cls, specificConfig.getLevel());
specificConfig.setLevel(Log4j2LevelAdjuster.this.level);
}
Map<String, Level> categoryLevels = new HashMap<>();
for (String category : Log4j2LevelAdjuster.this.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, Log4j2LevelAdjuster.this.level, true);
specificConfig.setParent(loggerConfig);
config.addLogger(category, specificConfig);
}
categoryLevels.put(category, specificConfig.getLevel());
specificConfig.setLevel(Log4j2LevelAdjuster.this.level);
}
ctx.updateLoggers();
logger.debug("++++++++++++++++++++++++++++ "
+ "Overridden log level setting for: " + Arrays.toString(Log4j2LevelAdjuster.this.classes)
+ " and " + Arrays.toString(Log4j2LevelAdjuster.this.categories)
+ " for test " + method.getName());
LevelsContainer container = null;
try {
container = TestUtils.adjustLogLevels(method.getName(),
Arrays.asList(Log4j2LevelAdjuster.this.classes),
Arrays.asList(Log4j2LevelAdjuster.this.categories),
Log4j2LevelAdjuster.this.level);
base.evaluate();
}
finally {
logger.debug("++++++++++++++++++++++++++++ "
+ "Restoring log level setting for: " + Arrays.toString(Log4j2LevelAdjuster.this.classes)
+ " and " + Arrays.toString(Log4j2LevelAdjuster.this.categories)
+ " for test " + method.getName());
for (Class<?> cls : Log4j2LevelAdjuster.this.classes) {
LoggerConfig loggerConfig = config.getLoggerConfig(cls.getName());
loggerConfig.setLevel(classLevels.get(cls));
if (container != null) {
TestUtils.revertLogLevels(method.getName(), container);
}
for (String category : Log4j2LevelAdjuster.this.categories) {
LoggerConfig loggerConfig = config.getLoggerConfig(category);
loggerConfig.setLevel(categoryLevels.get(category));
}
ctx.updateLoggers();
}
}
}

View File

@@ -18,7 +18,9 @@ package org.springframework.integration.test.util;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy;
@@ -26,6 +28,11 @@ import java.util.concurrent.atomic.AtomicReference;
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.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.BeanFactory;
@@ -301,4 +308,94 @@ public abstract class TestUtils {
}
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();
LOGGER.warn("++++++++++++++++++++++++++++ "
+ "Overridden log level setting for: " + classes
+ " and " + categories
+ " for test " + methodName);
return new LevelsContainer(classLevels, categoryLevels);
}
public static void revertLogLevels(String methodName, LevelsContainer container) {
LOGGER.warn("++++++++++++++++++++++++++++ "
+ "Restoring log level setting for: " + container.classLevels.keySet()
+ " and " + container.categoryLevels.keySet()
+ " for test " + methodName);
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
Configuration config = ctx.getConfiguration();
container.classLevels.entrySet().forEach(entry -> {
LoggerConfig loggerConfig = config.getLoggerConfig(entry.getKey().getName());
loggerConfig.setLevel(entry.getValue());
});
container.categoryLevels.entrySet().forEach(entry -> {
LoggerConfig loggerConfig = config.getLoggerConfig(entry.getKey());
loggerConfig.setLevel(entry.getValue());
});
ctx.updateLoggers();
}
public static class LevelsContainer {
final Map<Class<?>, Level> classLevels;
final Map<String, Level> categoryLevels;
public LevelsContainer(Map<Class<?>, Level> classLevels, Map<String, Level> categoryLevels) {
this.classLevels = classLevels;
this.categoryLevels = categoryLevels;
}
}
}