diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/logging/AutoConfigurationReportLoggingInitializer.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/logging/AutoConfigurationReportLoggingInitializer.java index 423cd3dc699..2fcb080b5c7 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/logging/AutoConfigurationReportLoggingInitializer.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/logging/AutoConfigurationReportLoggingInitializer.java @@ -16,19 +16,10 @@ package org.springframework.boot.autoconfigure.logging; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; -import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcome; -import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcomes; import org.springframework.boot.context.event.ApplicationFailedEvent; import org.springframework.boot.logging.LogLevel; import org.springframework.context.ApplicationContextInitializer; @@ -40,8 +31,6 @@ import org.springframework.context.event.GenericApplicationListener; import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.Ordered; import org.springframework.core.ResolvableType; -import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; /** * {@link ApplicationContextInitializer} that writes the {@link ConditionEvaluationReport} @@ -114,99 +103,11 @@ public class AutoConfigurationReportLoggingInitializer + "debug logging (start with --debug)%n%n")); } if (this.logger.isDebugEnabled()) { - this.logger.debug(getLogMessage(this.report)); + this.logger.debug(new ConditionEvalutionReportMessage(this.report)); } } } - private StringBuilder getLogMessage(ConditionEvaluationReport report) { - StringBuilder message = new StringBuilder(); - message.append(String.format("%n%n%n")); - message.append(String.format("=========================%n")); - message.append(String.format("AUTO-CONFIGURATION REPORT%n")); - message.append(String.format("=========================%n%n%n")); - message.append(String.format("Positive matches:%n")); - message.append(String.format("-----------------%n")); - Map shortOutcomes = orderByName( - report.getConditionAndOutcomesBySource()); - for (Map.Entry entry : shortOutcomes.entrySet()) { - if (entry.getValue().isFullMatch()) { - addLogMessage(message, entry.getKey(), entry.getValue()); - } - } - message.append(String.format("%n%n")); - message.append(String.format("Negative matches:%n")); - message.append(String.format("-----------------%n")); - for (Map.Entry entry : shortOutcomes.entrySet()) { - if (!entry.getValue().isFullMatch()) { - addLogMessage(message, entry.getKey(), entry.getValue()); - } - } - message.append(String.format("%n%n")); - message.append(String.format("Exclusions:%n")); - message.append(String.format("-----------%n")); - if (report.getExclusions().isEmpty()) { - message.append(String.format("%n None%n")); - } - else { - for (String exclusion : report.getExclusions()) { - message.append(String.format("%n %s%n", exclusion)); - } - } - message.append(String.format("%n%n")); - message.append(String.format("Unconditional classes:%n")); - message.append(String.format("----------------------%n")); - if (report.getUnconditionalClasses().isEmpty()) { - message.append(String.format("%n None%n")); - } - else { - for (String unconditionalClass : report.getUnconditionalClasses()) { - message.append(String.format("%n %s%n", unconditionalClass)); - } - } - message.append(String.format("%n%n")); - return message; - } - - private Map orderByName( - Map outcomes) { - Map result = new LinkedHashMap(); - List names = new ArrayList(); - Map classNames = new HashMap(); - for (String name : outcomes.keySet()) { - String shortName = ClassUtils.getShortName(name); - names.add(shortName); - classNames.put(shortName, name); - } - Collections.sort(names); - for (String shortName : names) { - result.put(shortName, outcomes.get(classNames.get(shortName))); - } - return result; - } - - private void addLogMessage(StringBuilder message, String source, - ConditionAndOutcomes conditionAndOutcomes) { - message.append(String.format("%n %s", source)); - message.append(conditionAndOutcomes.isFullMatch() ? " matched" : " did not match") - .append(String.format("%n")); - for (ConditionAndOutcome conditionAndOutcome : conditionAndOutcomes) { - message.append(" - "); - if (StringUtils.hasLength(conditionAndOutcome.getOutcome().getMessage())) { - message.append(conditionAndOutcome.getOutcome().getMessage()); - } - else { - message.append(conditionAndOutcome.getOutcome().isMatch() ? "matched" - : "did not match"); - } - message.append(" ("); - message.append(ClassUtils - .getShortName(conditionAndOutcome.getCondition().getClass())); - message.append(String.format(")%n")); - } - - } - private class AutoConfigurationReportListener implements GenericApplicationListener { @Override diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/logging/ConditionEvalutionReportMessage.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/logging/ConditionEvalutionReportMessage.java new file mode 100644 index 00000000000..8426c75fbfb --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/logging/ConditionEvalutionReportMessage.java @@ -0,0 +1,138 @@ +/* + * Copyright 2012-2016 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 + * + * 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.springframework.boot.autoconfigure.logging; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; +import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcome; +import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcomes; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; + +/** + * A condition evaluation report message that can logged or printed. + * + * @author Phillip Webb + * @since 1.4.0 + */ +public class ConditionEvalutionReportMessage { + + private StringBuilder message; + + public ConditionEvalutionReportMessage(ConditionEvaluationReport report) { + this.message = getLogMessage(report); + } + + private StringBuilder getLogMessage(ConditionEvaluationReport report) { + StringBuilder message = new StringBuilder(); + message.append(String.format("%n%n%n")); + message.append(String.format("=========================%n")); + message.append(String.format("AUTO-CONFIGURATION REPORT%n")); + message.append(String.format("=========================%n%n%n")); + message.append(String.format("Positive matches:%n")); + message.append(String.format("-----------------%n")); + Map shortOutcomes = orderByName( + report.getConditionAndOutcomesBySource()); + for (Map.Entry entry : shortOutcomes.entrySet()) { + if (entry.getValue().isFullMatch()) { + addLogMessage(message, entry.getKey(), entry.getValue()); + } + } + message.append(String.format("%n%n")); + message.append(String.format("Negative matches:%n")); + message.append(String.format("-----------------%n")); + for (Map.Entry entry : shortOutcomes.entrySet()) { + if (!entry.getValue().isFullMatch()) { + addLogMessage(message, entry.getKey(), entry.getValue()); + } + } + message.append(String.format("%n%n")); + message.append(String.format("Exclusions:%n")); + message.append(String.format("-----------%n")); + if (report.getExclusions().isEmpty()) { + message.append(String.format("%n None%n")); + } + else { + for (String exclusion : report.getExclusions()) { + message.append(String.format("%n %s%n", exclusion)); + } + } + message.append(String.format("%n%n")); + message.append(String.format("Unconditional classes:%n")); + message.append(String.format("----------------------%n")); + if (report.getUnconditionalClasses().isEmpty()) { + message.append(String.format("%n None%n")); + } + else { + for (String unconditionalClass : report.getUnconditionalClasses()) { + message.append(String.format("%n %s%n", unconditionalClass)); + } + } + message.append(String.format("%n%n")); + return message; + } + + private Map orderByName( + Map outcomes) { + Map result = new LinkedHashMap(); + List names = new ArrayList(); + Map classNames = new HashMap(); + for (String name : outcomes.keySet()) { + String shortName = ClassUtils.getShortName(name); + names.add(shortName); + classNames.put(shortName, name); + } + Collections.sort(names); + for (String shortName : names) { + result.put(shortName, outcomes.get(classNames.get(shortName))); + } + return result; + } + + private void addLogMessage(StringBuilder message, String source, + ConditionAndOutcomes conditionAndOutcomes) { + message.append(String.format("%n %s", source)); + message.append(conditionAndOutcomes.isFullMatch() ? " matched" : " did not match") + .append(String.format("%n")); + for (ConditionAndOutcome conditionAndOutcome : conditionAndOutcomes) { + message.append(" - "); + if (StringUtils.hasLength(conditionAndOutcome.getOutcome().getMessage())) { + message.append(conditionAndOutcome.getOutcome().getMessage()); + } + else { + message.append(conditionAndOutcome.getOutcome().isMatch() ? "matched" + : "did not match"); + } + message.append(" ("); + message.append(ClassUtils + .getShortName(conditionAndOutcome.getCondition().getClass())); + message.append(String.format(")%n")); + } + } + + @Override + public String toString() { + return this.message.toString(); + } + +} diff --git a/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/AutoConfigureReportTestExecutionListener.java b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/AutoConfigureReportTestExecutionListener.java new file mode 100644 index 00000000000..892db7cf025 --- /dev/null +++ b/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/AutoConfigureReportTestExecutionListener.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2016 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 + * + * 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.springframework.boot.test.autoconfigure; + +import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport; +import org.springframework.boot.autoconfigure.logging.ConditionEvalutionReportMessage; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.test.context.TestContext; +import org.springframework.test.context.TestExecutionListener; +import org.springframework.test.context.support.AbstractTestExecutionListener; +import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; + +/** + * {@link TestExecutionListener} to print the {@link ConditionEvaluationReport} when the + * context cannot be prepared. + * + * @author Phillip Webb + */ +class AutoConfigureReportTestExecutionListener extends AbstractTestExecutionListener { + + private DependencyInjectionTestExecutionListener delegate = new DependencyInjectionTestExecutionListener(); + + @Override + public int getOrder() { + return this.delegate.getOrder() - 1; + } + + @Override + public void prepareTestInstance(TestContext testContext) throws Exception { + try { + this.delegate.prepareTestInstance(testContext); + } + catch (Exception ex) { + ApplicationContext context = testContext.getApplicationContext(); + if (context instanceof ConfigurableApplicationContext) { + ConditionEvaluationReport report = ConditionEvaluationReport + .get(((ConfigurableApplicationContext) context).getBeanFactory()); + System.err.println(new ConditionEvalutionReportMessage(report)); + } + throw ex; + } + } + +} diff --git a/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/AutoConfigureReportTestExecutionListenerTests.java b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/AutoConfigureReportTestExecutionListenerTests.java new file mode 100644 index 00000000000..10cdf7a5113 --- /dev/null +++ b/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/AutoConfigureReportTestExecutionListenerTests.java @@ -0,0 +1,81 @@ +/* + * Copyright 2012-2016 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 + * + * 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.springframework.boot.test.autoconfigure; + +import org.junit.Rule; +import org.junit.Test; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.test.rule.OutputCapture; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.test.context.TestContext; +import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link AutoConfigureReportTestExecutionListener}. + * + * @author Phillip Webb + */ +public class AutoConfigureReportTestExecutionListenerTests { + + @Rule + public OutputCapture out = new OutputCapture(); + + private AutoConfigureReportTestExecutionListener reportListener = new AutoConfigureReportTestExecutionListener(); + + @Test + public void orderShouldBeBeforeDependencyInjectionTestExecutionListener() + throws Exception { + Ordered injectionListener = new DependencyInjectionTestExecutionListener(); + assertThat(this.reportListener.getOrder()) + .isLessThan(injectionListener.getOrder()); + } + + @Test + public void prepareFialingTestInstanceShouldPrintReport() throws Exception { + TestContext testContext = mock(TestContext.class); + given(testContext.getTestInstance()).willThrow(new IllegalStateException()); + SpringApplication application = new SpringApplication(Config.class); + application.setWebEnvironment(false); + ConfigurableApplicationContext applicationContext = application.run(); + given(testContext.getApplicationContext()).willReturn(applicationContext); + try { + this.reportListener.prepareTestInstance(testContext); + } + catch (IllegalStateException ex) { + // Expected + } + this.out.expect(containsString("AUTO-CONFIGURATION REPORT")); + this.out.expect(containsString("Positive matches")); + this.out.expect(containsString("Negative matches")); + } + + @Configuration + @ImportAutoConfiguration(JacksonAutoConfiguration.class) + static class Config { + } + +}