From 0c365319effb4f0ae051dade5c675df08b3f116a Mon Sep 17 00:00:00 2001 From: Raphael Vullriede Date: Tue, 6 Oct 2026 07:06:40 +0200 Subject: [PATCH 1/2] Prevent pausing of the logging system When the test context framework pauses a cached context, the logging lifecycle's stop() cleans up the logging system. With Logback this uninstalls the JUL bridge handler for the whole JVM and nothing reinstalls it when the context is restarted, so JUL output is lost in later tests that reuse the context. This commit makes the lifecycle non-pauseable so that cleanup only happens on close, as it already does for the web server lifecycles. See gh-51959 Signed-off-by: Raphael Vullriede --- core/spring-boot-test/build.gradle | 1 + ...onListenerTcfCacheContextPausingTests.java | 93 +++++++++++++++++++ .../logging/LoggingApplicationListener.java | 5 + 3 files changed, 99 insertions(+) create mode 100644 core/spring-boot-test/src/test/java/org/springframework/boot/test/context/LoggingApplicationListenerTcfCacheContextPausingTests.java diff --git a/core/spring-boot-test/build.gradle b/core/spring-boot-test/build.gradle index 5f8f7156e70..33f9c7827de 100644 --- a/core/spring-boot-test/build.gradle +++ b/core/spring-boot-test/build.gradle @@ -61,6 +61,7 @@ dependencies { testRuntimeOnly("org.aspectj:aspectjrt") testRuntimeOnly("org.aspectj:aspectjweaver") testRuntimeOnly("org.junit.vintage:junit-vintage-engine") + testRuntimeOnly("org.slf4j:jul-to-slf4j") } tasks.named("compileTestJava") { diff --git a/core/spring-boot-test/src/test/java/org/springframework/boot/test/context/LoggingApplicationListenerTcfCacheContextPausingTests.java b/core/spring-boot-test/src/test/java/org/springframework/boot/test/context/LoggingApplicationListenerTcfCacheContextPausingTests.java new file mode 100644 index 00000000000..64e607ee507 --- /dev/null +++ b/core/spring-boot-test/src/test/java/org/springframework/boot/test/context/LoggingApplicationListenerTcfCacheContextPausingTests.java @@ -0,0 +1,93 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.boot.test.context; + +import java.util.logging.Logger; + +import org.junit.jupiter.api.ClassOrderer; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestClassOrder; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.context.logging.LoggingApplicationListener; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.TestPropertySource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link LoggingApplicationListener} when the test context framework pauses a + * context while it's in the cache. + * + * @author Raphael Vullriede + */ +@SpringBootTest +@ExtendWith(OutputCaptureExtension.class) +@TestClassOrder(ClassOrderer.ClassName.class) +class LoggingApplicationListenerTcfCacheContextPausingTests { + + private static void assertJulOutputIsLogged(CapturedOutput output) { + Logger.getLogger("test").severe("Hello from JUL"); + assertThat(output).contains("Hello from JUL"); + } + + @Nested + @Import(TestConfig.class) + @TestPropertySource(properties = "context=one") + class ContextOne { + + @Test + void test(CapturedOutput output) { + assertJulOutputIsLogged(output); + } + + } + + @Nested + @Import(TestConfig.class) + @TestPropertySource(properties = "context=two") + class ContextTwo { + + @Test + void test(CapturedOutput output) { + assertJulOutputIsLogged(output); + } + + } + + @Nested + @Import(TestConfig.class) + @TestPropertySource(properties = "context=one") + class ReuseContextOne { + + @Test + void test(CapturedOutput output) { + assertJulOutputIsLogged(output); + } + + } + + @SpringBootConfiguration(proxyBeanMethods = false) + static class TestConfig { + + } + +} diff --git a/core/spring-boot/src/main/java/org/springframework/boot/context/logging/LoggingApplicationListener.java b/core/spring-boot/src/main/java/org/springframework/boot/context/logging/LoggingApplicationListener.java index d5b60317135..bb199a37c31 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/context/logging/LoggingApplicationListener.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/context/logging/LoggingApplicationListener.java @@ -488,6 +488,11 @@ public class LoggingApplicationListener implements GenericApplicationListener { return Integer.MIN_VALUE + 1; } + @Override + public boolean isPauseable() { + return false; + } + } } From 5d720a2138c260348196a0184faadf929b726002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Wed, 7 Oct 2026 16:15:28 +0200 Subject: [PATCH 2/2] Polish "Prevent pausing of the logging system" See gh-51959 --- ...ApplicationListenerWithContextPausingTests.java} | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) rename core/spring-boot-test/src/test/java/org/springframework/boot/test/context/{LoggingApplicationListenerTcfCacheContextPausingTests.java => LoggingApplicationListenerWithContextPausingTests.java} (87%) diff --git a/core/spring-boot-test/src/test/java/org/springframework/boot/test/context/LoggingApplicationListenerTcfCacheContextPausingTests.java b/core/spring-boot-test/src/test/java/org/springframework/boot/test/context/LoggingApplicationListenerWithContextPausingTests.java similarity index 87% rename from core/spring-boot-test/src/test/java/org/springframework/boot/test/context/LoggingApplicationListenerTcfCacheContextPausingTests.java rename to core/spring-boot-test/src/test/java/org/springframework/boot/test/context/LoggingApplicationListenerWithContextPausingTests.java index 64e607ee507..36ff16e6f2d 100644 --- a/core/spring-boot-test/src/test/java/org/springframework/boot/test/context/LoggingApplicationListenerTcfCacheContextPausingTests.java +++ b/core/spring-boot-test/src/test/java/org/springframework/boot/test/context/LoggingApplicationListenerWithContextPausingTests.java @@ -24,11 +24,11 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestClassOrder; import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.context.logging.LoggingApplicationListener; +import org.springframework.boot.test.context.LoggingApplicationListenerWithContextPausingTests.TestConfig; import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; -import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Configuration; import org.springframework.test.context.TestPropertySource; import static org.assertj.core.api.Assertions.assertThat; @@ -39,10 +39,10 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Raphael Vullriede */ -@SpringBootTest +@SpringBootTest(classes = TestConfig.class) @ExtendWith(OutputCaptureExtension.class) @TestClassOrder(ClassOrderer.ClassName.class) -class LoggingApplicationListenerTcfCacheContextPausingTests { +class LoggingApplicationListenerWithContextPausingTests { private static void assertJulOutputIsLogged(CapturedOutput output) { Logger.getLogger("test").severe("Hello from JUL"); @@ -50,7 +50,6 @@ class LoggingApplicationListenerTcfCacheContextPausingTests { } @Nested - @Import(TestConfig.class) @TestPropertySource(properties = "context=one") class ContextOne { @@ -62,7 +61,6 @@ class LoggingApplicationListenerTcfCacheContextPausingTests { } @Nested - @Import(TestConfig.class) @TestPropertySource(properties = "context=two") class ContextTwo { @@ -74,7 +72,6 @@ class LoggingApplicationListenerTcfCacheContextPausingTests { } @Nested - @Import(TestConfig.class) @TestPropertySource(properties = "context=one") class ReuseContextOne { @@ -85,7 +82,7 @@ class LoggingApplicationListenerTcfCacheContextPausingTests { } - @SpringBootConfiguration(proxyBeanMethods = false) + @Configuration(proxyBeanMethods = false) static class TestConfig { }