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

Reply via email to