From 10207931984fa41bce756e39a35cab001512a134 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 20 Sep 2024 17:09:34 +0100 Subject: [PATCH] Output condition evaluation report when app under test fails to start Closes gh-42185 --- ...ortApplicationContextFailureProcessor.java | 4 +- ...nditionReportContextCustomizerFactory.java | 92 +++++++++++++++++++ .../main/resources/META-INF/spring.factories | 6 +- ...plicationContextFailureProcessorTests.java | 3 + ...onReportContextCustomizerFactoryTests.java | 65 +++++++++++++ 5 files changed, 164 insertions(+), 6 deletions(-) create mode 100644 spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/OnFailureConditionReportContextCustomizerFactory.java create mode 100644 spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/OnFailureConditionReportContextCustomizerFactoryTests.java diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/ConditionReportApplicationContextFailureProcessor.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/ConditionReportApplicationContextFailureProcessor.java index b75649c1645..66e45638b8b 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/ConditionReportApplicationContextFailureProcessor.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/ConditionReportApplicationContextFailureProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2024 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. @@ -29,7 +29,9 @@ import org.springframework.test.context.ApplicationContextFailureProcessor; * @author Phillip Webb * @author Scott Frederick * @since 3.0.0 + * @deprecated in 3.2.11 for removal in 3.6.0 */ +@Deprecated(since = "3.2.11", forRemoval = true) public class ConditionReportApplicationContextFailureProcessor implements ApplicationContextFailureProcessor { @Override diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/OnFailureConditionReportContextCustomizerFactory.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/OnFailureConditionReportContextCustomizerFactory.java new file mode 100644 index 00000000000..d95ff503887 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/OnFailureConditionReportContextCustomizerFactory.java @@ -0,0 +1,92 @@ +/* + * Copyright 2012-2024 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.autoconfigure; + +import java.util.List; +import java.util.function.Supplier; + +import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; +import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportMessage; +import org.springframework.boot.context.event.ApplicationFailedEvent; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.test.context.ContextConfigurationAttributes; +import org.springframework.test.context.ContextCustomizer; +import org.springframework.test.context.ContextCustomizerFactory; +import org.springframework.test.context.MergedContextConfiguration; + +/** + * {@link ContextCustomizerFactory} that customizes the {@link ApplicationContext + * application context} such that a {@link ConditionEvaluationReport condition evaluation + * report} is output when the application under test {@link ApplicationFailedEvent fails + * to start}. + * + * @author Andy Wilkinson + */ +class OnFailureConditionReportContextCustomizerFactory implements ContextCustomizerFactory { + + @Override + public ContextCustomizer createContextCustomizer(Class testClass, + List configAttributes) { + return new OnFailureConditionReportContextCustomizer(); + } + + static class OnFailureConditionReportContextCustomizer implements ContextCustomizer { + + @Override + public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) { + Supplier reportSupplier; + if (context instanceof GenericApplicationContext) { + ConditionEvaluationReport report = ConditionEvaluationReport.get(context.getBeanFactory()); + reportSupplier = () -> report; + } + else { + reportSupplier = () -> ConditionEvaluationReport.get(context.getBeanFactory()); + } + context.addApplicationListener(new ApplicationFailureListener(reportSupplier)); + } + + @Override + public boolean equals(Object obj) { + return (obj != null) && (obj.getClass() == getClass()); + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } + + } + + private static final class ApplicationFailureListener implements ApplicationListener { + + private final Supplier reportSupplier; + + private ApplicationFailureListener(Supplier reportSupplier) { + this.reportSupplier = reportSupplier; + } + + @Override + public void onApplicationEvent(ApplicationFailedEvent event) { + System.err.println(new ConditionEvaluationReportMessage(this.reportSupplier.get())); + } + + } + +} diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories index dd35fa84a54..4a7ffc6de71 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories @@ -1,5 +1,6 @@ # Spring Test Context Customizer Factories org.springframework.test.context.ContextCustomizerFactory=\ +org.springframework.boot.test.autoconfigure.OnFailureConditionReportContextCustomizerFactory,\ org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory,\ org.springframework.boot.test.autoconfigure.actuate.observability.ObservabilityContextCustomizerFactory,\ org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizerFactory,\ @@ -13,8 +14,3 @@ org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerRese org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener,\ org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener,\ org.springframework.boot.test.autoconfigure.webservices.client.MockWebServiceServerTestExecutionListener - -# Spring Test ApplcationContext Failure Processors -org.springframework.test.context.ApplicationContextFailureProcessor=\ -org.springframework.boot.test.autoconfigure.ConditionReportApplicationContextFailureProcessor - diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/ConditionReportApplicationContextFailureProcessorTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/ConditionReportApplicationContextFailureProcessorTests.java index e5c1eea0427..21b7bda6d80 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/ConditionReportApplicationContextFailureProcessorTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/ConditionReportApplicationContextFailureProcessorTests.java @@ -35,8 +35,11 @@ import static org.assertj.core.api.Assertions.assertThat; * * @author Phillip Webb * @author Scott Frederick + * @deprecated since 3.2.11 for removal in 3.6.0 */ @ExtendWith(OutputCaptureExtension.class) +@Deprecated(since = "3.2.11", forRemoval = true) +@SuppressWarnings("removal") class ConditionReportApplicationContextFailureProcessorTests { @Test diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/OnFailureConditionReportContextCustomizerFactoryTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/OnFailureConditionReportContextCustomizerFactoryTests.java new file mode 100644 index 00000000000..87fc8ee8d85 --- /dev/null +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/OnFailureConditionReportContextCustomizerFactoryTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2012-2024 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.autoconfigure; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.TestContextManager; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; + +/** + * Tests for {@link OnFailureConditionReportContextCustomizerFactory}. + * + * @author Andy Wilkinson + */ +@ExtendWith(OutputCaptureExtension.class) +class OnFailureConditionReportContextCustomizerFactoryTests { + + @Test + void loadFailureShouldPrintReport(CapturedOutput output) { + assertThatIllegalStateException() + .isThrownBy(() -> new TestContextManager(FailingTests.class).getTestContext().getApplicationContext()); + assertThat(output).contains("JacksonAutoConfiguration matched"); + } + + @SpringBootTest + static class FailingTests { + + @Configuration(proxyBeanMethods = false) + @ImportAutoConfiguration(JacksonAutoConfiguration.class) + static class TestConfig { + + @Bean + String faultyBean() { + throw new IllegalStateException(); + } + + } + + } + +}