This is an automated email from the ASF dual-hosted git repository.

pkarwasz pushed a commit to branch fix/3770_LoggerContext_start
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit 869c5b75a33138f8bb944613463a6645530fd128
Author: Piotr P. Karwasz <pkarwasz-git...@apache.org>
AuthorDate: Sat Jun 21 11:50:03 2025 +0200

    feat: add tests for `LoggerContext.start` behavior
    
    Add test verifying expected behavior of 
`LoggerContext.start(Configuration)` to ensure backward compatibility:
    
    - The configuration must always be replaced, even if the context has 
already started.
    - Only the first configuration should register the shutdown hook.
---
 .../logging/log4j/core/LoggerContextTest.java      | 26 ++++++++
 .../logging/log4j/core/ShutdownDisabledTest.java   | 73 +++++++++++++++++++---
 2 files changed, 92 insertions(+), 7 deletions(-)

diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerContextTest.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerContextTest.java
index e14bc11a2e..e6d58f66c7 100644
--- 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerContextTest.java
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/LoggerContextTest.java
@@ -17,7 +17,9 @@
 package org.apache.logging.log4j.core;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.withSettings;
 
 import java.util.Collection;
 import java.util.concurrent.ExecutorService;
@@ -25,11 +27,16 @@ import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
+import org.apache.logging.log4j.core.config.AbstractConfiguration;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.ConfigurationSource;
+import org.apache.logging.log4j.core.config.DefaultConfiguration;
 import org.apache.logging.log4j.message.MessageFactory;
 import org.apache.logging.log4j.message.MessageFactory2;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.TestInfo;
+import org.junitpioneer.jupiter.Issue;
 
 class LoggerContextTest {
 
@@ -75,4 +82,23 @@ class LoggerContextTest {
             executorService.shutdown();
         }
     }
+
+    @Test
+    @Issue("https://github.com/apache/logging-log4j2/issues/3770";)
+    void start_should_fallback_on_reconfigure_if_context_already_started(final 
TestInfo testInfo) {
+        final String testName = testInfo.getDisplayName();
+        try (final LoggerContext loggerContext = new LoggerContext(testName)) {
+            loggerContext.start();
+            assertThat(loggerContext.isStarted()).isTrue();
+            
assertThat(loggerContext.getConfiguration()).isInstanceOf(DefaultConfiguration.class);
+            // Start
+            Configuration configuration = mock(
+                    AbstractConfiguration.class,
+                    withSettings()
+                            .useConstructor(null, 
ConfigurationSource.NULL_SOURCE)
+                            .defaultAnswer(CALLS_REAL_METHODS));
+            loggerContext.start(configuration);
+            
assertThat(loggerContext.getConfiguration()).isSameAs(configuration);
+        }
+    }
 }
diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/ShutdownDisabledTest.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/ShutdownDisabledTest.java
index 2e576d226b..483308f406 100644
--- 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/ShutdownDisabledTest.java
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/ShutdownDisabledTest.java
@@ -16,25 +16,84 @@
  */
 package org.apache.logging.log4j.core;
 
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.apache.logging.log4j.core.util.ReflectionUtil.getFieldValue;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
 
 import java.lang.reflect.Field;
+import org.apache.logging.log4j.core.config.AbstractConfiguration;
 import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.ConfigurationSource;
 import org.apache.logging.log4j.core.test.junit.LoggerContextSource;
-import org.apache.logging.log4j.core.util.ReflectionUtil;
 import org.apache.logging.log4j.test.junit.SetTestProperty;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
 
 @SetTestProperty(key = "log4j2.isWebapp", value = "false")
 @LoggerContextSource("log4j-test3.xml")
 class ShutdownDisabledTest {
 
+    private static final Field shutdownCallbackField;
+
+    static {
+        try {
+            shutdownCallbackField = 
LoggerContext.class.getDeclaredField("shutdownCallback");
+        } catch (NoSuchFieldException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     @Test
     void testShutdownFlag(final Configuration config, final LoggerContext ctx) 
throws NoSuchFieldException {
-        Field shutdownCallback = 
LoggerContext.class.getDeclaredField("shutdownCallback");
-        Object fieldValue = ReflectionUtil.getFieldValue(shutdownCallback, 
ctx);
-        assertFalse(config.isShutdownHookEnabled(), "Shutdown hook is 
enabled");
-        assertNull(fieldValue, "Shutdown callback");
+        assertThat(config.isShutdownHookEnabled())
+                .as("Shutdown hook is enabled")
+                .isFalse();
+        assertThat(getFieldValue(shutdownCallbackField, ctx))
+                .as("Shutdown callback")
+                .isNull();
+    }
+
+    @Test
+    void whenLoggerContextInitialized_respectsShutdownDisabled(TestInfo 
testInfo) {
+        Configuration configuration = mockConfiguration();
+        when(configuration.isShutdownHookEnabled()).thenReturn(false);
+        try (final LoggerContext ctx = new 
LoggerContext(testInfo.getDisplayName())) {
+            ctx.start(configuration);
+            assertThat(ctx.isStarted()).isTrue();
+            assertThat(ctx.getConfiguration()).isSameAs(configuration);
+            assertThat(getFieldValue(shutdownCallbackField, ctx))
+                    .as("Shutdown callback")
+                    .isNull();
+        }
+    }
+
+    @Test
+    void whenLoggerContextStarted_ignoresShutdownDisabled(TestInfo testInfo) {
+        // Traditional behavior: during reconfiguration, the shutdown hook is 
not removed.
+        Configuration initialConfiguration = mockConfiguration();
+        when(initialConfiguration.isShutdownHookEnabled()).thenReturn(true);
+        Configuration configuration = mockConfiguration();
+        when(configuration.isShutdownHookEnabled()).thenReturn(false);
+        try (final LoggerContext ctx = new 
LoggerContext(testInfo.getDisplayName())) {
+            ctx.start(initialConfiguration);
+            assertThat(ctx.isStarted()).isTrue();
+            Object shutdownCallback = getFieldValue(shutdownCallbackField, 
ctx);
+            assertThat(shutdownCallback).as("Shutdown callback").isNotNull();
+            ctx.start(configuration);
+            assertThat(getFieldValue(shutdownCallbackField, ctx))
+                    .as("Shutdown callback")
+                    .isSameAs(shutdownCallback);
+        }
+    }
+
+    private static Configuration mockConfiguration() {
+        return mock(
+                AbstractConfiguration.class,
+                withSettings()
+                        .useConstructor(null, ConfigurationSource.NULL_SOURCE)
+                        .defaultAnswer(CALLS_REAL_METHODS));
     }
 }

Reply via email to