Skip to content

Commit 69dd180

Browse files
garyrussellartembilan
authored andcommitted
JUnit 5 Log Level Adjuster
* Fix copyright, javadoc.
1 parent a4f7412 commit 69dd180

File tree

5 files changed

+290
-70
lines changed

5 files changed

+290
-70
lines changed

spring-integration-core/src/test/java/org/springframework/integration/transformer/AvroTests.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,15 @@
1717
package org.springframework.integration.transformer;
1818

1919
import static org.assertj.core.api.Assertions.assertThat;
20+
import static org.mockito.Mockito.atLeastOnce;
21+
import static org.mockito.Mockito.spy;
22+
import static org.mockito.Mockito.verify;
2023

24+
import org.apache.commons.logging.Log;
2125
import org.junit.jupiter.api.Test;
26+
import org.mockito.ArgumentCaptor;
2227

28+
import org.springframework.beans.DirectFieldAccessor;
2329
import org.springframework.beans.factory.annotation.Autowired;
2430
import org.springframework.context.annotation.Bean;
2531
import org.springframework.context.annotation.Configuration;
@@ -28,6 +34,8 @@
2834
import org.springframework.integration.config.EnableIntegration;
2935
import org.springframework.integration.dsl.IntegrationFlow;
3036
import org.springframework.integration.dsl.IntegrationFlows;
37+
import org.springframework.integration.test.condition.LogLevels;
38+
import org.springframework.integration.test.util.TestUtils;
3139
import org.springframework.integration.transformer.support.AvroHeaders;
3240
import org.springframework.messaging.Message;
3341
import org.springframework.messaging.PollableChannel;
@@ -40,11 +48,15 @@
4048
*
4149
*/
4250
@SpringJUnitConfig
51+
@LogLevels(categories = "foo", level = "DEBUG")
4352
public class AvroTests {
4453

4554
@Test
55+
@LogLevels(classes = DirectChannel.class, categories = "bar", level = "DEBUG")
4656
void testTransformers(@Autowired Config config) {
4757
AvroTestClass1 test = new AvroTestClass1("baz", "fiz");
58+
Log spied = spy(TestUtils.getPropertyValue(config.in1(), "logger", Log.class));
59+
new DirectFieldAccessor(config.in1()).setPropertyValue("logger", spied);
4860
config.in1().send(new GenericMessage<>(test));
4961
assertThat(config.tapped().receive(0))
5062
.isNotNull()
@@ -57,6 +69,10 @@ void testTransformers(@Autowired Config config) {
5769
.isEqualTo(test)
5870
.isNotSameAs(test);
5971
assertThat(received.getHeaders().get("flow")).isEqualTo("flow1");
72+
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
73+
verify(spied, atLeastOnce()).debug(captor.capture());
74+
assertThat(captor.getAllValues()).anyMatch(s -> s.contains("preSend on channel"));
75+
assertThat(captor.getAllValues()).anyMatch(s -> s.contains("postSend (sent=true) on channel"));
6076
}
6177

6278
@Test
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.integration.test.condition;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
import org.junit.jupiter.api.extension.ExtendWith;
26+
27+
/**
28+
* Test classes annotated with this will change logging levels between tests. It can also
29+
* be applied to individual test methods. If both class-level and method-level annotations
30+
* are present, the method-level annotation is used.
31+
*
32+
* @author Gary Russell
33+
* @since 5.2
34+
*
35+
*/
36+
@ExtendWith(LogLevelsCondition.class)
37+
@Target({ ElementType.TYPE, ElementType.METHOD })
38+
@Retention(RetentionPolicy.RUNTIME)
39+
@Documented
40+
public @interface LogLevels {
41+
42+
/**
43+
* Classes representing Log4j categories to change.
44+
* @return the classes.
45+
*/
46+
Class<?>[] classes() default {};
47+
48+
/**
49+
* Category names representing Log4j categories to change.
50+
* @return the names.
51+
*/
52+
String[] categories() default {};
53+
54+
/**
55+
* The Log4j level name to switch the categories to during the test.
56+
* @return the level.
57+
*/
58+
String level() default "";
59+
60+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright 2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.integration.test.condition;
18+
19+
import java.lang.reflect.AnnotatedElement;
20+
import java.util.Arrays;
21+
import java.util.Optional;
22+
23+
import org.apache.logging.log4j.Level;
24+
import org.junit.jupiter.api.extension.AfterAllCallback;
25+
import org.junit.jupiter.api.extension.AfterEachCallback;
26+
import org.junit.jupiter.api.extension.BeforeEachCallback;
27+
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
28+
import org.junit.jupiter.api.extension.ExecutionCondition;
29+
import org.junit.jupiter.api.extension.ExtensionContext;
30+
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
31+
import org.junit.jupiter.api.extension.ExtensionContext.Store;
32+
33+
import org.springframework.core.annotation.MergedAnnotation;
34+
import org.springframework.core.annotation.MergedAnnotations;
35+
import org.springframework.integration.test.util.TestUtils;
36+
import org.springframework.integration.test.util.TestUtils.LevelsContainer;
37+
38+
/**
39+
* JUnit condition that adjusts and reverts log levels before/after each test.
40+
*
41+
* @author Gary Russell
42+
* @since 5.2
43+
*
44+
*/
45+
public class LogLevelsCondition
46+
implements ExecutionCondition, BeforeEachCallback, AfterEachCallback, AfterAllCallback {
47+
48+
private static final String STORE_ANNOTATION_KEY = "logLevelsAnnotation";
49+
50+
private static final String STORE_CONTAINER_KEY = "logLevelsContainer";
51+
52+
private static final ConditionEvaluationResult ENABLED =
53+
ConditionEvaluationResult.enabled("@LogLevels always enabled");
54+
55+
@Override
56+
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
57+
Optional<AnnotatedElement> element = context.getElement();
58+
MergedAnnotations annotations = MergedAnnotations.from(element.get(),
59+
MergedAnnotations.SearchStrategy.TYPE_HIERARCHY);
60+
MergedAnnotation<LogLevels> mergedAnnotation = annotations.get(LogLevels.class);
61+
if (mergedAnnotation.isPresent()) {
62+
LogLevels loglevels = mergedAnnotation.synthesize();
63+
Store store = context.getStore(Namespace.create(getClass(), context));
64+
store.put(STORE_ANNOTATION_KEY, loglevels);
65+
}
66+
return ENABLED;
67+
}
68+
69+
@Override
70+
public void beforeEach(ExtensionContext context) {
71+
Store store = context.getStore(Namespace.create(getClass(), context));
72+
LogLevels logLevels = store.get(STORE_ANNOTATION_KEY, LogLevels.class);
73+
if (logLevels == null) {
74+
ExtensionContext parent = context.getParent().get();
75+
store = parent.getStore(Namespace.create(getClass(), parent));
76+
logLevels = store.get(STORE_ANNOTATION_KEY, LogLevels.class);
77+
}
78+
store.put(STORE_CONTAINER_KEY, TestUtils.adjustLogLevels(context.getDisplayName(),
79+
Arrays.asList((logLevels.classes())),
80+
Arrays.asList(logLevels.categories()),
81+
Level.toLevel(logLevels.level())));
82+
}
83+
84+
@Override
85+
public void afterEach(ExtensionContext context) {
86+
Store store = context.getStore(Namespace.create(getClass(), context));
87+
LevelsContainer container = store.get(STORE_CONTAINER_KEY, LevelsContainer.class);
88+
boolean parentStore = false;
89+
if (container == null) {
90+
ExtensionContext parent = context.getParent().get();
91+
store = parent.getStore(Namespace.create(getClass(), parent));
92+
container = store.get(STORE_CONTAINER_KEY, LevelsContainer.class);
93+
parentStore = true;
94+
}
95+
TestUtils.revertLogLevels(context.getDisplayName(), container);
96+
store.remove(STORE_CONTAINER_KEY);
97+
if (!parentStore) {
98+
store.remove(STORE_ANNOTATION_KEY);
99+
}
100+
}
101+
102+
@Override
103+
public void afterAll(ExtensionContext context) {
104+
Store store = context.getStore(Namespace.create(getClass(), context));
105+
store.remove(STORE_ANNOTATION_KEY);
106+
}
107+
108+
}

spring-integration-test-support/src/main/java/org/springframework/integration/test/rule/Log4j2LevelAdjuster.java

Lines changed: 9 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,17 @@
1717
package org.springframework.integration.test.rule;
1818

1919
import java.util.Arrays;
20-
import java.util.HashMap;
21-
import java.util.Map;
2220
import java.util.stream.Stream;
2321

2422
import org.apache.commons.logging.Log;
2523
import org.apache.commons.logging.LogFactory;
2624
import org.apache.logging.log4j.Level;
27-
import org.apache.logging.log4j.LogManager;
28-
import org.apache.logging.log4j.core.LoggerContext;
29-
import org.apache.logging.log4j.core.config.Configuration;
30-
import org.apache.logging.log4j.core.config.LoggerConfig;
3125
import org.junit.rules.MethodRule;
3226
import org.junit.runners.model.FrameworkMethod;
3327
import org.junit.runners.model.Statement;
3428

29+
import org.springframework.integration.test.util.TestUtils;
30+
import org.springframework.integration.test.util.TestUtils.LevelsContainer;
3531
import org.springframework.util.Assert;
3632
import org.springframework.util.ObjectUtils;
3733

@@ -81,75 +77,18 @@ class AdjustingStatement extends Statement {
8177

8278
@Override
8379
public void evaluate() throws Throwable {
84-
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
85-
Configuration config = ctx.getConfiguration();
86-
87-
Map<Class<?>, Level> classLevels = new HashMap<>();
88-
for (Class<?> cls : Log4j2LevelAdjuster.this.classes) {
89-
String className = cls.getName();
90-
LoggerConfig loggerConfig = config.getLoggerConfig(className);
91-
LoggerConfig specificConfig = loggerConfig;
92-
93-
// We need a specific configuration for this logger,
94-
// otherwise we would change the level of all other loggers
95-
// having the original configuration as parent as well
96-
97-
if (!loggerConfig.getName().equals(className)) {
98-
specificConfig = new LoggerConfig(className, Log4j2LevelAdjuster.this.level, true);
99-
specificConfig.setParent(loggerConfig);
100-
config.addLogger(className, specificConfig);
101-
}
102-
103-
classLevels.put(cls, specificConfig.getLevel());
104-
specificConfig.setLevel(Log4j2LevelAdjuster.this.level);
105-
}
106-
107-
Map<String, Level> categoryLevels = new HashMap<>();
108-
for (String category : Log4j2LevelAdjuster.this.categories) {
109-
LoggerConfig loggerConfig = config.getLoggerConfig(category);
110-
LoggerConfig specificConfig = loggerConfig;
111-
112-
// We need a specific configuration for this logger,
113-
// otherwise we would change the level of all other loggers
114-
// having the original configuration as parent as well
115-
116-
if (!loggerConfig.getName().equals(category)) {
117-
specificConfig = new LoggerConfig(category, Log4j2LevelAdjuster.this.level, true);
118-
specificConfig.setParent(loggerConfig);
119-
config.addLogger(category, specificConfig);
120-
}
121-
122-
categoryLevels.put(category, specificConfig.getLevel());
123-
specificConfig.setLevel(Log4j2LevelAdjuster.this.level);
124-
}
125-
126-
ctx.updateLoggers();
127-
128-
logger.debug("++++++++++++++++++++++++++++ "
129-
+ "Overridden log level setting for: " + Arrays.toString(Log4j2LevelAdjuster.this.classes)
130-
+ " and " + Arrays.toString(Log4j2LevelAdjuster.this.categories)
131-
+ " for test " + method.getName());
132-
80+
LevelsContainer container = null;
13381
try {
82+
container = TestUtils.adjustLogLevels(method.getName(),
83+
Arrays.asList(Log4j2LevelAdjuster.this.classes),
84+
Arrays.asList(Log4j2LevelAdjuster.this.categories),
85+
Log4j2LevelAdjuster.this.level);
13486
base.evaluate();
13587
}
13688
finally {
137-
logger.debug("++++++++++++++++++++++++++++ "
138-
+ "Restoring log level setting for: " + Arrays.toString(Log4j2LevelAdjuster.this.classes)
139-
+ " and " + Arrays.toString(Log4j2LevelAdjuster.this.categories)
140-
+ " for test " + method.getName());
141-
142-
for (Class<?> cls : Log4j2LevelAdjuster.this.classes) {
143-
LoggerConfig loggerConfig = config.getLoggerConfig(cls.getName());
144-
loggerConfig.setLevel(classLevels.get(cls));
89+
if (container != null) {
90+
TestUtils.revertLogLevels(method.getName(), container);
14591
}
146-
147-
for (String category : Log4j2LevelAdjuster.this.categories) {
148-
LoggerConfig loggerConfig = config.getLoggerConfig(category);
149-
loggerConfig.setLevel(categoryLevels.get(category));
150-
}
151-
152-
ctx.updateLoggers();
15392
}
15493
}
15594
}

0 commit comments

Comments
 (0)