JUnit 5 Log Level Adjuster
* Fix copyright, javadoc.
This commit is contained in:
committed by
Artem Bilan
parent
a4f7412cf5
commit
69dd180d0c
@@ -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
|
||||
|
||||
@@ -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 "";
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user