Skip to content

Commit 9bfae53

Browse files
authored
SolrTestCase now supports @loglevel (#2869)
Refactored the functionality into a TestRule, and moved from STCJ4 up to SolrTestCase. Move reinstatement of the root log level to shutdown()
1 parent af2c0b3 commit 9bfae53

File tree

7 files changed

+126
-79
lines changed

7 files changed

+126
-79
lines changed

solr/CHANGES.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,9 @@ Other Changes
177177
* SOLR-17579: Remove unused code and other refactorings in ReplicationHandler and tests. Removed unused public
178178
LOCAL_ACTIVITY_DURING_REPLICATION variable. (Eric Pugh)
179179

180+
* GITHUB#2869: SolrTestCase now supports @LogLevel annotations (as SolrTestCaseJ4 has). Added LogLevelTestRule
181+
for encapsulation and reuse. (David Smiley)
182+
180183
================== 9.8.0 ==================
181184
New Features
182185
---------------------

solr/core/src/java/org/apache/solr/util/StartupLoggingUtils.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ public final class StartupLoggingUtils {
4343
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
4444
private static final ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
4545

46+
private static final String INITIAL_ROOT_LOG_LEVEL = getLogLevelString();
47+
4648
/** Checks whether mandatory log dir is given */
4749
public static void checkLogDir() {
4850
if (EnvUtils.getProperty("solr.log.dir") == null) {
@@ -151,6 +153,11 @@ public static void shutdown() {
151153
}
152154
flushAllLoggers();
153155
LogManager.shutdown(true);
156+
157+
// re-instate original log level.
158+
if (!INITIAL_ROOT_LOG_LEVEL.equals(getLogLevelString())) {
159+
changeLogLevel(INITIAL_ROOT_LOG_LEVEL);
160+
}
154161
}
155162

156163
/**

solr/test-framework/src/java/org/apache/solr/SolrTestCase.java

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222

2323
import com.carrotsearch.randomizedtesting.annotations.ThreadLeakFilters;
2424
import com.carrotsearch.randomizedtesting.annotations.ThreadLeakLingering;
25-
import com.carrotsearch.randomizedtesting.rules.StatementAdapter;
2625
import com.carrotsearch.randomizedtesting.rules.SystemPropertiesRestoreRule;
26+
import com.carrotsearch.randomizedtesting.rules.TestRuleAdapter;
2727
import java.io.File;
2828
import java.lang.invoke.MethodHandles;
2929
import java.util.List;
@@ -37,14 +37,17 @@
3737
import org.apache.solr.common.util.ObjectReleaseTracker;
3838
import org.apache.solr.servlet.SolrDispatchFilter;
3939
import org.apache.solr.util.ExternalPaths;
40+
import org.apache.solr.util.LogLevelTestRule;
4041
import org.apache.solr.util.RevertDefaultThreadHandlerRule;
4142
import org.apache.solr.util.StartupLoggingUtils;
4243
import org.hamcrest.Matcher;
4344
import org.hamcrest.MatcherAssert;
45+
import org.junit.AfterClass;
4446
import org.junit.Before;
4547
import org.junit.BeforeClass;
4648
import org.junit.ClassRule;
4749
import org.junit.ComparisonFailure;
50+
import org.junit.Rule;
4851
import org.junit.rules.RuleChain;
4952
import org.junit.rules.TestRule;
5053
import org.slf4j.Logger;
@@ -90,24 +93,23 @@ public class SolrTestCase extends LuceneTestCase {
9093
new VerifyTestClassNamingConvention(
9194
"org.apache.solr.ltr", NAMING_CONVENTION_TEST_PREFIX))
9295
.around(new RevertDefaultThreadHandlerRule())
96+
.around(new LogLevelTestRule())
9397
.around(
94-
(base, description) ->
95-
new StatementAdapter(base) {
96-
@Override
97-
protected void afterIfSuccessful() {
98-
// if the tests passed, make sure everything was closed / released
99-
String orr = ObjectReleaseTracker.clearObjectTrackerAndCheckEmpty();
100-
assertNull(orr, orr);
101-
}
102-
103-
@Override
104-
protected void afterAlways(List<Throwable> errors) {
105-
if (!errors.isEmpty()) {
106-
ObjectReleaseTracker.tryClose();
107-
}
108-
StartupLoggingUtils.shutdown();
109-
}
110-
});
98+
new TestRuleAdapter() {
99+
@Override
100+
protected void afterIfSuccessful() {
101+
// if the tests passed, make sure everything was closed / released
102+
String orr = ObjectReleaseTracker.clearObjectTrackerAndCheckEmpty();
103+
assertNull(orr, orr);
104+
}
105+
106+
@Override
107+
protected void afterAlways(List<Throwable> errors) {
108+
if (!errors.isEmpty()) {
109+
ObjectReleaseTracker.tryClose();
110+
}
111+
}
112+
});
111113

112114
/**
113115
* Sets the <code>solr.default.confdir</code> system property to the value of {@link
@@ -174,19 +176,27 @@ public static void checkSyspropForceBeforeClassAssumptionFailure() {
174176
assumeFalse(PROP + " == true", systemPropertyAsBoolean(PROP, false));
175177
}
176178

179+
@AfterClass
180+
public static void afterClassShutdownLogging() {
181+
StartupLoggingUtils.shutdown();
182+
}
183+
184+
@Rule public TestRule methodRules = new LogLevelTestRule();
185+
177186
/**
178187
* Special hook for sanity checking if any tests trigger failures when an Assumption failure
179-
* occures in a {@link Before} method
188+
* occurs in a {@link Before} method
180189
*
181190
* @lucene.internal
182191
*/
183192
@Before
184193
public void checkSyspropForceBeforeAssumptionFailure() {
185-
// ant test -Dargs="-Dtests.force.assumption.failure.before=true"
186194
final String PROP = "tests.force.assumption.failure.before";
187195
assumeFalse(PROP + " == true", systemPropertyAsBoolean(PROP, false));
188196
}
189197

198+
// UTILITY METHODS FOLLOW
199+
190200
public static void assertJSONEquals(String expected, String actual) {
191201
Object json1 = fromJSONString(expected);
192202
Object json2 = fromJSONString(actual);

solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
import java.lang.annotation.Target;
4242
import java.lang.invoke.MethodHandles;
4343
import java.lang.reflect.InvocationTargetException;
44-
import java.lang.reflect.Method;
4544
import java.net.URL;
4645
import java.nio.charset.StandardCharsets;
4746
import java.nio.file.Files;
@@ -75,7 +74,6 @@
7574
import java.util.stream.Collectors;
7675
import javax.xml.xpath.XPathExpressionException;
7776
import org.apache.http.client.HttpClient;
78-
import org.apache.logging.log4j.Level;
7977
import org.apache.lucene.index.IndexWriterConfig;
8078
import org.apache.lucene.tests.analysis.MockAnalyzer;
8179
import org.apache.lucene.tests.analysis.MockTokenizer;
@@ -109,7 +107,6 @@
109107
import org.apache.solr.common.util.ExecutorUtil;
110108
import org.apache.solr.common.util.IOUtils;
111109
import org.apache.solr.common.util.SolrNamedThreadFactory;
112-
import org.apache.solr.common.util.SuppressForbidden;
113110
import org.apache.solr.common.util.Utils;
114111
import org.apache.solr.common.util.XML;
115112
import org.apache.solr.core.CoreContainer;
@@ -138,18 +135,14 @@
138135
import org.apache.solr.util.DirectoryUtil;
139136
import org.apache.solr.util.ErrorLogMuter;
140137
import org.apache.solr.util.ExternalPaths;
141-
import org.apache.solr.util.LogLevel;
142138
import org.apache.solr.util.RandomizeSSL;
143139
import org.apache.solr.util.RandomizeSSL.SSLRandomizer;
144140
import org.apache.solr.util.RefCounted;
145141
import org.apache.solr.util.SSLTestConfig;
146-
import org.apache.solr.util.StartupLoggingUtils;
147142
import org.apache.solr.util.TestHarness;
148143
import org.apache.solr.util.TestInjection;
149144
import org.apache.zookeeper.KeeperException;
150-
import org.junit.After;
151145
import org.junit.AfterClass;
152-
import org.junit.Before;
153146
import org.junit.BeforeClass;
154147
import org.junit.Rule;
155148
import org.junit.rules.RuleChain;
@@ -187,8 +180,6 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
187180

188181
public static int DEFAULT_CONNECTION_TIMEOUT = 60000; // default socket connection timeout in ms
189182

190-
private static String initialRootLogLevel;
191-
192183
protected static volatile ExecutorService testExecutor;
193184

194185
protected void writeCoreProperties(Path coreDirectory, String coreName) throws IOException {
@@ -257,8 +248,6 @@ protected void assertExceptionThrownWithMessageContaining(
257248

258249
@BeforeClass
259250
public static void setupTestCases() {
260-
initialRootLogLevel = StartupLoggingUtils.getLogLevelString();
261-
initClassLogLevels();
262251
resetExceptionIgnores();
263252

264253
testExecutor =
@@ -348,10 +337,6 @@ public static void teardownTestCases() throws Exception {
348337
testSolrHome = null;
349338

350339
IpTables.unblockAllPorts();
351-
352-
LogLevel.Configurer.restoreLogLevels(savedClassLogLevels);
353-
savedClassLogLevels.clear();
354-
StartupLoggingUtils.changeLogLevel(initialRootLogLevel);
355340
}
356341
}
357342

@@ -391,38 +376,6 @@ public static void assumeWorkingMockito() {
391376
}
392377
}
393378

394-
@SuppressForbidden(reason = "Using the Level class from log4j2 directly")
395-
private static Map<String, Level> savedClassLogLevels = new HashMap<>();
396-
397-
public static void initClassLogLevels() {
398-
Class<?> currentClass = RandomizedContext.current().getTargetClass();
399-
LogLevel annotation = currentClass.getAnnotation(LogLevel.class);
400-
if (annotation == null) {
401-
return;
402-
}
403-
Map<String, Level> previousLevels = LogLevel.Configurer.setLevels(annotation.value());
404-
savedClassLogLevels.putAll(previousLevels);
405-
}
406-
407-
private Map<String, Level> savedMethodLogLevels = new HashMap<>();
408-
409-
@Before
410-
public void initMethodLogLevels() {
411-
Method method = RandomizedContext.current().getTargetMethod();
412-
LogLevel annotation = method.getAnnotation(LogLevel.class);
413-
if (annotation == null) {
414-
return;
415-
}
416-
Map<String, Level> previousLevels = LogLevel.Configurer.setLevels(annotation.value());
417-
savedMethodLogLevels.putAll(previousLevels);
418-
}
419-
420-
@After
421-
public void restoreMethodLogLevels() {
422-
LogLevel.Configurer.restoreLogLevels(savedMethodLogLevels);
423-
savedMethodLogLevels.clear();
424-
}
425-
426379
protected static boolean isSSLMode() {
427380
return sslConfig != null && sslConfig.isSSLMode();
428381
}

solr/test-framework/src/java/org/apache/solr/util/LogLevel.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
* like this: <code>
3939
* {@literal @}LogLevel("org.apache.solr=DEBUG;org.apache.solr.core=INFO")
4040
* </code>
41+
*
42+
* @see LogLevelTestRule
4143
*/
4244
@Documented
4345
@Inherited
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.solr.util;
19+
20+
import java.lang.annotation.Annotation;
21+
import java.util.Map;
22+
import java.util.Optional;
23+
import org.apache.logging.log4j.Level;
24+
import org.apache.solr.common.util.SuppressForbidden;
25+
import org.junit.rules.TestRule;
26+
import org.junit.runner.Description;
27+
import org.junit.runners.model.Statement;
28+
29+
/**
30+
* A JUnit {@link TestRule} that sets (and resets) the Log4j2 log level based on the {@code
31+
* LogLevel} annotation.
32+
*/
33+
public class LogLevelTestRule implements TestRule {
34+
35+
@Override
36+
public Statement apply(Statement base, Description description) {
37+
// loop over the annotations to find LogLevel
38+
final Optional<Annotation> annotationOpt =
39+
description.getAnnotations().stream()
40+
.filter(a -> a.annotationType().equals(LogLevel.class))
41+
.findAny();
42+
if (annotationOpt.isEmpty()) {
43+
return base;
44+
}
45+
final var annotation = (LogLevel) annotationOpt.get();
46+
return new LogLevelStatement(base, annotation);
47+
}
48+
49+
static class LogLevelStatement extends Statement {
50+
private final Statement delegate;
51+
private final LogLevel annotation;
52+
53+
protected LogLevelStatement(Statement delegate, LogLevel annotation) {
54+
this.delegate = delegate;
55+
this.annotation = annotation;
56+
}
57+
58+
@SuppressForbidden(reason = "Using the Level class from log4j2 directly")
59+
@Override
60+
public void evaluate() throws Throwable {
61+
Map<String, Level> savedLogLevels = LogLevel.Configurer.setLevels(annotation.value());
62+
try {
63+
delegate.evaluate();
64+
} finally {
65+
LogLevel.Configurer.restoreLogLevels(savedLogLevels);
66+
}
67+
}
68+
}
69+
}

solr/test-framework/src/test/org/apache/solr/TestLogLevelAnnotations.java renamed to solr/test-framework/src/test/org/apache/solr/util/TestLogLevelAnnotations.java

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,26 @@
1515
* limitations under the License.
1616
*/
1717

18-
package org.apache.solr;
18+
package org.apache.solr.util;
1919

2020
import java.util.Map;
2121
import org.apache.logging.log4j.Level;
2222
import org.apache.logging.log4j.LogManager;
2323
import org.apache.logging.log4j.core.LoggerContext;
2424
import org.apache.logging.log4j.core.config.Configuration;
25+
import org.apache.solr.SolrTestCase;
2526
import org.apache.solr.common.util.SuppressForbidden;
26-
import org.apache.solr.util.LogLevel;
2727
import org.junit.AfterClass;
2828
import org.junit.BeforeClass;
2929

30+
/**
31+
* @see LogLevel
32+
* @see LogLevelTestRule
33+
*/
3034
@SuppressForbidden(reason = "We need to use log4J2 classes to access the log levels")
3135
@LogLevel(
3236
"org.apache.solr.bogus_logger.ClassLogLevel=error;org.apache.solr.bogus_logger.MethodLogLevel=warn")
33-
public class TestLogLevelAnnotations extends SolrTestCaseJ4 {
37+
public class TestLogLevelAnnotations extends SolrTestCase {
3438

3539
private static final String bogus_logger_prefix = "org.apache.solr.bogus_logger";
3640

@@ -42,12 +46,12 @@ public class TestLogLevelAnnotations extends SolrTestCaseJ4 {
4246
* the test.
4347
*
4448
* <p>We also don't want to initialize this in a <code>@BeforeClass</code> method because that
45-
* will run <em>after</em> the <code>@BeforeClass</code> logic of our super class {@link
46-
* SolrTestCaseJ4} where the <code>@LogLevel</code> annotation on this class will be parsed and
47-
* evaluated -- modifying the log4j run time configuration. The <code>@LogLevel</code>
48-
* configuration of this class <em>should</em> not affect the "root" Logger, but setting this in
49-
* static class initialization protect us (as best we can) against the possibility that it
50-
* <em>might</em> due to an unforseen (future) bug.
49+
* will run <em>after</em> the <code>@BeforeClass</code> or {@code @ClassRule} logic of our super
50+
* class where the <code>@LogLevel</code> annotation on this class will be parsed and evaluated --
51+
* modifying the log4j run time configuration. The <code>@LogLevel</code> configuration of this
52+
* class <em>should</em> not affect the "root" Logger, but setting this in static class
53+
* initialization protect us (as best we can) against the possibility that it <em>might</em> due
54+
* to an unforseen (future) bug.
5155
*
5256
* @see #checkLogLevelsBeforeClass
5357
*/
@@ -93,9 +97,8 @@ public static void checkLogLevelsBeforeClass() {
9397
* Check that the expected log level <em>configurations</em> have been reset after the test
9498
*
9599
* <p><b>NOTE:</b> We only validate <code>@LogLevel</code> modifications made at the {@link
96-
* #testMethodLogLevels} level, not at the 'class' level, because of the lifecycle of junit
97-
* methods: This <code>@AfterClass</code> will run before the <code>SolrTestCaseJ4#@AfterClass
98-
* </code> method where the 'class' <code>@LogLevel</code> modifications will be reset.
100+
* #testMethodLogLevels} level, not at the 'class' level, because the lifecycle of junit methods
101+
* that activate this logic prevent us from doing so.
99102
*
100103
* @see #checkLogLevelsBeforeClass
101104
* @see #testWhiteBoxMethods

0 commit comments

Comments
 (0)