This is an automated email from the ASF dual-hosted git repository.
dsmiley pushed a commit to branch branch_9x
in repository https://gitbox.apache.org/repos/asf/solr.git
The following commit(s) were added to refs/heads/branch_9x by this push:
new b2503745224 SolrTestCase now supports @LogLevel (#2869)
b2503745224 is described below
commit b2503745224b6fa10b0618538f5976983e1d45ff
Author: David Smiley <[email protected]>
AuthorDate: Tue Jan 7 17:38:41 2025 -0500
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()
(cherry picked from commit 9bfae53db358354f014339498faae7957bee1e27)
---
solr/CHANGES.txt | 3 +
.../org/apache/solr/util/StartupLoggingUtils.java | 7 +++
.../src/java/org/apache/solr/SolrTestCase.java | 50 +++++++++-------
.../src/java/org/apache/solr/SolrTestCaseJ4.java | 46 ---------------
.../src/java/org/apache/solr/util/LogLevel.java | 2 +
.../org/apache/solr/util/LogLevelTestRule.java | 69 ++++++++++++++++++++++
.../solr/{ => util}/TestLogLevelAnnotations.java | 27 +++++----
7 files changed, 126 insertions(+), 78 deletions(-)
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 90178303047..d9d22fb59c6 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -38,6 +38,9 @@ Other Changes
* SOLR-17579: Remove unused code and other refactorings in ReplicationHandler
and tests. Removed unused public
LOCAL_ACTIVITY_DURING_REPLICATION variable. (Eric Pugh)
+* GITHUB#2869: SolrTestCase now supports @LogLevel annotations (as
SolrTestCaseJ4 has). Added LogLevelTestRule
+ for encapsulation and reuse. (David Smiley)
+
================== 9.8.0 ==================
New Features
---------------------
diff --git a/solr/core/src/java/org/apache/solr/util/StartupLoggingUtils.java
b/solr/core/src/java/org/apache/solr/util/StartupLoggingUtils.java
index aeda7757593..79047a502e3 100644
--- a/solr/core/src/java/org/apache/solr/util/StartupLoggingUtils.java
+++ b/solr/core/src/java/org/apache/solr/util/StartupLoggingUtils.java
@@ -43,6 +43,8 @@ public final class StartupLoggingUtils {
private static final Logger log =
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final ILoggerFactory loggerFactory =
LoggerFactory.getILoggerFactory();
+ private static final String INITIAL_ROOT_LOG_LEVEL = getLogLevelString();
+
/** Checks whether mandatory log dir is given */
public static void checkLogDir() {
if (EnvUtils.getProperty("solr.log.dir") == null) {
@@ -151,6 +153,11 @@ public final class StartupLoggingUtils {
}
flushAllLoggers();
LogManager.shutdown(true);
+
+ // re-instate original log level.
+ if (!INITIAL_ROOT_LOG_LEVEL.equals(getLogLevelString())) {
+ changeLogLevel(INITIAL_ROOT_LOG_LEVEL);
+ }
}
/**
diff --git a/solr/test-framework/src/java/org/apache/solr/SolrTestCase.java
b/solr/test-framework/src/java/org/apache/solr/SolrTestCase.java
index c9fdb63fab9..73fc008ae8b 100644
--- a/solr/test-framework/src/java/org/apache/solr/SolrTestCase.java
+++ b/solr/test-framework/src/java/org/apache/solr/SolrTestCase.java
@@ -22,8 +22,8 @@ import static
org.apache.solr.common.util.Utils.fromJSONString;
import com.carrotsearch.randomizedtesting.annotations.ThreadLeakFilters;
import com.carrotsearch.randomizedtesting.annotations.ThreadLeakLingering;
-import com.carrotsearch.randomizedtesting.rules.StatementAdapter;
import com.carrotsearch.randomizedtesting.rules.SystemPropertiesRestoreRule;
+import com.carrotsearch.randomizedtesting.rules.TestRuleAdapter;
import java.io.File;
import java.lang.invoke.MethodHandles;
import java.util.List;
@@ -37,14 +37,17 @@ import org.apache.solr.common.util.EnvUtils;
import org.apache.solr.common.util.ObjectReleaseTracker;
import org.apache.solr.servlet.SolrDispatchFilter;
import org.apache.solr.util.ExternalPaths;
+import org.apache.solr.util.LogLevelTestRule;
import org.apache.solr.util.RevertDefaultThreadHandlerRule;
import org.apache.solr.util.StartupLoggingUtils;
import org.hamcrest.Matcher;
import org.hamcrest.MatcherAssert;
+import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.ComparisonFailure;
+import org.junit.Rule;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.slf4j.Logger;
@@ -93,24 +96,23 @@ public class SolrTestCase extends LuceneTestCase {
new VerifyTestClassNamingConvention(
"org.apache.solr.ltr", NAMING_CONVENTION_TEST_PREFIX))
.around(new RevertDefaultThreadHandlerRule())
+ .around(new LogLevelTestRule())
.around(
- (base, description) ->
- new StatementAdapter(base) {
- @Override
- protected void afterIfSuccessful() {
- // if the tests passed, make sure everything was closed
/ released
- String orr =
ObjectReleaseTracker.clearObjectTrackerAndCheckEmpty();
- assertNull(orr, orr);
- }
-
- @Override
- protected void afterAlways(List<Throwable> errors) {
- if (!errors.isEmpty()) {
- ObjectReleaseTracker.tryClose();
- }
- StartupLoggingUtils.shutdown();
- }
- });
+ new TestRuleAdapter() {
+ @Override
+ protected void afterIfSuccessful() {
+ // if the tests passed, make sure everything was closed /
released
+ String orr =
ObjectReleaseTracker.clearObjectTrackerAndCheckEmpty();
+ assertNull(orr, orr);
+ }
+
+ @Override
+ protected void afterAlways(List<Throwable> errors) {
+ if (!errors.isEmpty()) {
+ ObjectReleaseTracker.tryClose();
+ }
+ }
+ });
/**
* Sets the <code>solr.default.confdir</code> system property to the value
of {@link
@@ -177,19 +179,27 @@ public class SolrTestCase extends LuceneTestCase {
assumeFalse(PROP + " == true", systemPropertyAsBoolean(PROP, false));
}
+ @AfterClass
+ public static void afterClassShutdownLogging() {
+ StartupLoggingUtils.shutdown();
+ }
+
+ @Rule public TestRule methodRules = new LogLevelTestRule();
+
/**
* Special hook for sanity checking if any tests trigger failures when an
Assumption failure
- * occures in a {@link Before} method
+ * occurs in a {@link Before} method
*
* @lucene.internal
*/
@Before
public void checkSyspropForceBeforeAssumptionFailure() {
- // ant test -Dargs="-Dtests.force.assumption.failure.before=true"
final String PROP = "tests.force.assumption.failure.before";
assumeFalse(PROP + " == true", systemPropertyAsBoolean(PROP, false));
}
+ // UTILITY METHODS FOLLOW
+
public static void assertJSONEquals(String expected, String actual) {
Object json1 = fromJSONString(expected);
Object json2 = fromJSONString(actual);
diff --git a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
index 3db7c03ab5e..517a5793418 100644
--- a/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
+++ b/solr/test-framework/src/java/org/apache/solr/SolrTestCaseJ4.java
@@ -45,7 +45,6 @@ import java.lang.annotation.Target;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@@ -81,7 +80,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import javax.xml.xpath.XPathExpressionException;
import org.apache.http.client.HttpClient;
-import org.apache.logging.log4j.Level;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.tests.analysis.MockAnalyzer;
import org.apache.lucene.tests.analysis.MockTokenizer;
@@ -144,18 +142,14 @@ import org.apache.solr.util.BaseTestHarness;
import org.apache.solr.util.DirectoryUtil;
import org.apache.solr.util.ErrorLogMuter;
import org.apache.solr.util.ExternalPaths;
-import org.apache.solr.util.LogLevel;
import org.apache.solr.util.RandomizeSSL;
import org.apache.solr.util.RandomizeSSL.SSLRandomizer;
import org.apache.solr.util.RefCounted;
import org.apache.solr.util.SSLTestConfig;
-import org.apache.solr.util.StartupLoggingUtils;
import org.apache.solr.util.TestHarness;
import org.apache.solr.util.TestInjection;
import org.apache.zookeeper.KeeperException;
-import org.junit.After;
import org.junit.AfterClass;
-import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.rules.RuleChain;
@@ -193,8 +187,6 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
public static int DEFAULT_CONNECTION_TIMEOUT = 60000; // default socket
connection timeout in ms
- private static String initialRootLogLevel;
-
protected static volatile ExecutorService testExecutor;
protected void writeCoreProperties(Path coreDirectory, String coreName)
throws IOException {
@@ -263,8 +255,6 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
@BeforeClass
public static void setupTestCases() {
- initialRootLogLevel = StartupLoggingUtils.getLogLevelString();
- initClassLogLevels();
resetExceptionIgnores();
testExecutor =
@@ -386,10 +376,6 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
testSolrHome = null;
IpTables.unblockAllPorts();
-
- LogLevel.Configurer.restoreLogLevels(savedClassLogLevels);
- savedClassLogLevels.clear();
- StartupLoggingUtils.changeLogLevel(initialRootLogLevel);
}
}
@@ -429,38 +415,6 @@ public abstract class SolrTestCaseJ4 extends SolrTestCase {
}
}
- @SuppressForbidden(reason = "Using the Level class from log4j2 directly")
- private static Map<String, Level> savedClassLogLevels = new HashMap<>();
-
- public static void initClassLogLevels() {
- Class<?> currentClass = RandomizedContext.current().getTargetClass();
- LogLevel annotation = currentClass.getAnnotation(LogLevel.class);
- if (annotation == null) {
- return;
- }
- Map<String, Level> previousLevels =
LogLevel.Configurer.setLevels(annotation.value());
- savedClassLogLevels.putAll(previousLevels);
- }
-
- private Map<String, Level> savedMethodLogLevels = new HashMap<>();
-
- @Before
- public void initMethodLogLevels() {
- Method method = RandomizedContext.current().getTargetMethod();
- LogLevel annotation = method.getAnnotation(LogLevel.class);
- if (annotation == null) {
- return;
- }
- Map<String, Level> previousLevels =
LogLevel.Configurer.setLevels(annotation.value());
- savedMethodLogLevels.putAll(previousLevels);
- }
-
- @After
- public void restoreMethodLogLevels() {
- LogLevel.Configurer.restoreLogLevels(savedMethodLogLevels);
- savedMethodLogLevels.clear();
- }
-
protected static boolean isSSLMode() {
return sslConfig != null && sslConfig.isSSLMode();
}
diff --git a/solr/test-framework/src/java/org/apache/solr/util/LogLevel.java
b/solr/test-framework/src/java/org/apache/solr/util/LogLevel.java
index af7451944e9..4d4d0387df6 100644
--- a/solr/test-framework/src/java/org/apache/solr/util/LogLevel.java
+++ b/solr/test-framework/src/java/org/apache/solr/util/LogLevel.java
@@ -38,6 +38,8 @@ import org.apache.solr.common.util.SuppressForbidden;
* like this: <code>
* {@literal @}LogLevel("org.apache.solr=DEBUG;org.apache.solr.core=INFO")
* </code>
+ *
+ * @see LogLevelTestRule
*/
@Documented
@Inherited
diff --git
a/solr/test-framework/src/java/org/apache/solr/util/LogLevelTestRule.java
b/solr/test-framework/src/java/org/apache/solr/util/LogLevelTestRule.java
new file mode 100644
index 00000000000..99ce3d02665
--- /dev/null
+++ b/solr/test-framework/src/java/org/apache/solr/util/LogLevelTestRule.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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.apache.solr.util;
+
+import java.lang.annotation.Annotation;
+import java.util.Map;
+import java.util.Optional;
+import org.apache.logging.log4j.Level;
+import org.apache.solr.common.util.SuppressForbidden;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * A JUnit {@link TestRule} that sets (and resets) the Log4j2 log level based
on the {@code
+ * LogLevel} annotation.
+ */
+public class LogLevelTestRule implements TestRule {
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ // loop over the annotations to find LogLevel
+ final Optional<Annotation> annotationOpt =
+ description.getAnnotations().stream()
+ .filter(a -> a.annotationType().equals(LogLevel.class))
+ .findAny();
+ if (annotationOpt.isEmpty()) {
+ return base;
+ }
+ final var annotation = (LogLevel) annotationOpt.get();
+ return new LogLevelStatement(base, annotation);
+ }
+
+ static class LogLevelStatement extends Statement {
+ private final Statement delegate;
+ private final LogLevel annotation;
+
+ protected LogLevelStatement(Statement delegate, LogLevel annotation) {
+ this.delegate = delegate;
+ this.annotation = annotation;
+ }
+
+ @SuppressForbidden(reason = "Using the Level class from log4j2 directly")
+ @Override
+ public void evaluate() throws Throwable {
+ Map<String, Level> savedLogLevels =
LogLevel.Configurer.setLevels(annotation.value());
+ try {
+ delegate.evaluate();
+ } finally {
+ LogLevel.Configurer.restoreLogLevels(savedLogLevels);
+ }
+ }
+ }
+}
diff --git
a/solr/test-framework/src/test/org/apache/solr/TestLogLevelAnnotations.java
b/solr/test-framework/src/test/org/apache/solr/util/TestLogLevelAnnotations.java
similarity index 89%
rename from
solr/test-framework/src/test/org/apache/solr/TestLogLevelAnnotations.java
rename to
solr/test-framework/src/test/org/apache/solr/util/TestLogLevelAnnotations.java
index 83b2888910e..ebfad4ea63c 100644
--- a/solr/test-framework/src/test/org/apache/solr/TestLogLevelAnnotations.java
+++
b/solr/test-framework/src/test/org/apache/solr/util/TestLogLevelAnnotations.java
@@ -15,22 +15,26 @@
* limitations under the License.
*/
-package org.apache.solr;
+package org.apache.solr.util;
import java.util.Map;
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.solr.SolrTestCase;
import org.apache.solr.common.util.SuppressForbidden;
-import org.apache.solr.util.LogLevel;
import org.junit.AfterClass;
import org.junit.BeforeClass;
+/**
+ * @see LogLevel
+ * @see LogLevelTestRule
+ */
@SuppressForbidden(reason = "We need to use log4J2 classes to access the log
levels")
@LogLevel(
"org.apache.solr.bogus_logger.ClassLogLevel=error;org.apache.solr.bogus_logger.MethodLogLevel=warn")
-public class TestLogLevelAnnotations extends SolrTestCaseJ4 {
+public class TestLogLevelAnnotations extends SolrTestCase {
private static final String bogus_logger_prefix =
"org.apache.solr.bogus_logger";
@@ -42,12 +46,12 @@ public class TestLogLevelAnnotations extends SolrTestCaseJ4
{
* the test.
*
* <p>We also don't want to initialize this in a <code>@BeforeClass</code>
method because that
- * will run <em>after</em> the <code>@BeforeClass</code> logic of our super
class {@link
- * SolrTestCaseJ4} where the <code>@LogLevel</code> annotation on this class
will be parsed and
- * evaluated -- modifying the log4j run time configuration. The
<code>@LogLevel</code>
- * configuration of this class <em>should</em> not affect the "root" Logger,
but setting this in
- * static class initialization protect us (as best we can) against the
possibility that it
- * <em>might</em> due to an unforseen (future) bug.
+ * will run <em>after</em> the <code>@BeforeClass</code> or {@code
@ClassRule} logic of our super
+ * class where the <code>@LogLevel</code> annotation on this class will be
parsed and evaluated --
+ * modifying the log4j run time configuration. The <code>@LogLevel</code>
configuration of this
+ * class <em>should</em> not affect the "root" Logger, but setting this in
static class
+ * initialization protect us (as best we can) against the possibility that
it <em>might</em> due
+ * to an unforseen (future) bug.
*
* @see #checkLogLevelsBeforeClass
*/
@@ -93,9 +97,8 @@ public class TestLogLevelAnnotations extends SolrTestCaseJ4 {
* Check that the expected log level <em>configurations</em> have been reset
after the test
*
* <p><b>NOTE:</b> We only validate <code>@LogLevel</code> modifications
made at the {@link
- * #testMethodLogLevels} level, not at the 'class' level, because of the
lifecycle of junit
- * methods: This <code>@AfterClass</code> will run before the
<code>SolrTestCaseJ4#@AfterClass
- * </code> method where the 'class' <code>@LogLevel</code> modifications
will be reset.
+ * #testMethodLogLevels} level, not at the 'class' level, because the
lifecycle of junit methods
+ * that activate this logic prevent us from doing so.
*
* @see #checkLogLevelsBeforeClass
* @see #testWhiteBoxMethods