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)); } }