/*
* Copyright 2012-2017 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.condition;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport.ConditionAndOutcomes;
import org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.boot.testutil.Matched;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationCondition;
import org.springframework.context.annotation.Import;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ClassUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.nullValue;
/**
* Tests for {@link ConditionEvaluationReport}.
*
* @author Greg Turnquist
* @author Phillip Webb
*/
public class ConditionEvaluationReportTests {
private DefaultListableBeanFactory beanFactory;
private ConditionEvaluationReport report;
@Mock
private Condition condition1;
@Mock
private Condition condition2;
@Mock
private Condition condition3;
private ConditionOutcome outcome1;
private ConditionOutcome outcome2;
private ConditionOutcome outcome3;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.beanFactory = new DefaultListableBeanFactory();
this.report = ConditionEvaluationReport.get(this.beanFactory);
}
@Test
public void get() throws Exception {
assertThat(this.report).isNotEqualTo(nullValue());
assertThat(this.report).isSameAs(ConditionEvaluationReport.get(this.beanFactory));
}
@Test
public void parent() throws Exception {
this.beanFactory.setParentBeanFactory(new DefaultListableBeanFactory());
ConditionEvaluationReport.get((ConfigurableListableBeanFactory) this.beanFactory
.getParentBeanFactory());
assertThat(this.report).isSameAs(ConditionEvaluationReport.get(this.beanFactory));
assertThat(this.report).isNotEqualTo(nullValue());
assertThat(this.report.getParent()).isNotEqualTo(nullValue());
ConditionEvaluationReport.get((ConfigurableListableBeanFactory) this.beanFactory
.getParentBeanFactory());
assertThat(this.report).isSameAs(ConditionEvaluationReport.get(this.beanFactory));
assertThat(this.report.getParent()).isSameAs(ConditionEvaluationReport
.get((ConfigurableListableBeanFactory) this.beanFactory
.getParentBeanFactory()));
}
@Test
public void parentBottomUp() throws Exception {
this.beanFactory = new DefaultListableBeanFactory(); // NB: overrides setup
this.beanFactory.setParentBeanFactory(new DefaultListableBeanFactory());
ConditionEvaluationReport.get((ConfigurableListableBeanFactory) this.beanFactory
.getParentBeanFactory());
this.report = ConditionEvaluationReport.get(this.beanFactory);
assertThat(this.report).isNotNull();
assertThat(this.report).isNotSameAs(this.report.getParent());
assertThat(this.report.getParent()).isNotNull();
assertThat(this.report.getParent().getParent()).isNull();
}
@Test
public void recordConditionEvaluations() throws Exception {
this.outcome1 = new ConditionOutcome(false, "m1");
this.outcome2 = new ConditionOutcome(false, "m2");
this.outcome3 = new ConditionOutcome(false, "m3");
this.report.recordConditionEvaluation("a", this.condition1, this.outcome1);
this.report.recordConditionEvaluation("a", this.condition2, this.outcome2);
this.report.recordConditionEvaluation("b", this.condition3, this.outcome3);
Map<String, ConditionAndOutcomes> map = this.report
.getConditionAndOutcomesBySource();
assertThat(map.size()).isEqualTo(2);
Iterator<ConditionAndOutcome> iterator = map.get("a").iterator();
ConditionAndOutcome conditionAndOutcome = iterator.next();
assertThat(conditionAndOutcome.getCondition()).isEqualTo(this.condition1);
assertThat(conditionAndOutcome.getOutcome()).isEqualTo(this.outcome1);
conditionAndOutcome = iterator.next();
assertThat(conditionAndOutcome.getCondition()).isEqualTo(this.condition2);
assertThat(conditionAndOutcome.getOutcome()).isEqualTo(this.outcome2);
assertThat(iterator.hasNext()).isFalse();
iterator = map.get("b").iterator();
conditionAndOutcome = iterator.next();
assertThat(conditionAndOutcome.getCondition()).isEqualTo(this.condition3);
assertThat(conditionAndOutcome.getOutcome()).isEqualTo(this.outcome3);
assertThat(iterator.hasNext()).isFalse();
}
@Test
public void fullMatch() throws Exception {
prepareMatches(true, true, true);
assertThat(this.report.getConditionAndOutcomesBySource().get("a").isFullMatch())
.isTrue();
}
@Test
public void notFullMatch() throws Exception {
prepareMatches(true, false, true);
assertThat(this.report.getConditionAndOutcomesBySource().get("a").isFullMatch())
.isFalse();
}
private void prepareMatches(boolean m1, boolean m2, boolean m3) {
this.outcome1 = new ConditionOutcome(m1, "m1");
this.outcome2 = new ConditionOutcome(m2, "m2");
this.outcome3 = new ConditionOutcome(m3, "m3");
this.report.recordConditionEvaluation("a", this.condition1, this.outcome1);
this.report.recordConditionEvaluation("a", this.condition2, this.outcome2);
this.report.recordConditionEvaluation("a", this.condition3, this.outcome3);
}
@Test
@SuppressWarnings("resource")
public void springBootConditionPopulatesReport() throws Exception {
ConditionEvaluationReport report = ConditionEvaluationReport.get(
new AnnotationConfigApplicationContext(Config.class).getBeanFactory());
assertThat(report.getConditionAndOutcomesBySource().size()).isNotEqualTo(0);
}
@Test
public void testDuplicateConditionAndOutcomes() {
ConditionAndOutcome outcome1 = new ConditionAndOutcome(this.condition1,
new ConditionOutcome(true, "Message 1"));
ConditionAndOutcome outcome2 = new ConditionAndOutcome(this.condition2,
new ConditionOutcome(true, "Message 2"));
ConditionAndOutcome outcome3 = new ConditionAndOutcome(this.condition3,
new ConditionOutcome(true, "Message 2"));
assertThat(outcome1).isEqualTo(outcome1);
assertThat(outcome1).isNotEqualTo(outcome2);
assertThat(outcome2).isEqualTo(outcome3);
ConditionAndOutcomes outcomes = new ConditionAndOutcomes();
outcomes.add(this.condition1, new ConditionOutcome(true, "Message 1"));
outcomes.add(this.condition2, new ConditionOutcome(true, "Message 2"));
outcomes.add(this.condition3, new ConditionOutcome(true, "Message 2"));
assertThat(getNumberOfOutcomes(outcomes)).isEqualTo(2);
}
@Test
public void duplicateOutcomes() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
DuplicateConfig.class);
ConditionEvaluationReport report = ConditionEvaluationReport
.get(context.getBeanFactory());
String autoconfigKey = MultipartAutoConfiguration.class.getName();
ConditionAndOutcomes outcomes = report.getConditionAndOutcomesBySource()
.get(autoconfigKey);
assertThat(outcomes).isNotEqualTo(nullValue());
assertThat(getNumberOfOutcomes(outcomes)).isEqualTo(2);
List<String> messages = new ArrayList<>();
for (ConditionAndOutcome outcome : outcomes) {
messages.add(outcome.getOutcome().getMessage());
}
assertThat(messages).areAtLeastOne(
Matched.by(containsString("@ConditionalOnClass found required classes "
+ "'javax.servlet.Servlet', 'org.springframework.web.multipart."
+ "support.StandardServletMultipartResolver', "
+ "'javax.servlet.MultipartConfigElement'")));
context.close();
}
@Test
public void negativeOuterPositiveInnerBean() throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
EnvironmentTestUtils.addEnvironment(context, "test.present=true");
context.register(NegativeOuterConfig.class);
context.refresh();
ConditionEvaluationReport report = ConditionEvaluationReport
.get(context.getBeanFactory());
Map<String, ConditionAndOutcomes> sourceOutcomes = report
.getConditionAndOutcomesBySource();
assertThat(context.containsBean("negativeOuterPositiveInnerBean")).isFalse();
String negativeConfig = NegativeOuterConfig.class.getName();
assertThat(sourceOutcomes.get(negativeConfig).isFullMatch()).isFalse();
String positiveConfig = NegativeOuterConfig.PositiveInnerConfig.class.getName();
assertThat(sourceOutcomes.get(positiveConfig).isFullMatch()).isFalse();
}
private int getNumberOfOutcomes(ConditionAndOutcomes outcomes) {
Iterator<ConditionAndOutcome> iterator = outcomes.iterator();
int numberOfOutcomesAdded = 0;
while (iterator.hasNext()) {
numberOfOutcomesAdded++;
iterator.next();
}
return numberOfOutcomesAdded;
}
@Configuration
@Import(WebMvcAutoConfiguration.class)
static class Config {
}
@Configuration
@Import(MultipartAutoConfiguration.class)
static class DuplicateConfig {
}
@Configuration
@Conditional({ ConditionEvaluationReportTests.MatchParseCondition.class,
ConditionEvaluationReportTests.NoMatchBeanCondition.class })
public static class NegativeOuterConfig {
@Configuration
@Conditional({ ConditionEvaluationReportTests.MatchParseCondition.class })
public static class PositiveInnerConfig {
@Bean
public String negativeOuterPositiveInnerBean() {
return "negativeOuterPositiveInnerBean";
}
}
}
static class TestMatchCondition extends SpringBootCondition
implements ConfigurationCondition {
private final ConfigurationPhase phase;
private final boolean match;
TestMatchCondition(ConfigurationPhase phase, boolean match) {
this.phase = phase;
this.match = match;
}
@Override
public ConfigurationPhase getConfigurationPhase() {
return this.phase;
}
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
return new ConditionOutcome(this.match, ClassUtils.getShortName(getClass()));
}
}
static class MatchParseCondition extends TestMatchCondition {
MatchParseCondition() {
super(ConfigurationPhase.PARSE_CONFIGURATION, true);
}
}
static class MatchBeanCondition extends TestMatchCondition {
MatchBeanCondition() {
super(ConfigurationPhase.REGISTER_BEAN, true);
}
}
static class NoMatchParseCondition extends TestMatchCondition {
NoMatchParseCondition() {
super(ConfigurationPhase.PARSE_CONFIGURATION, false);
}
}
static class NoMatchBeanCondition extends TestMatchCondition {
NoMatchBeanCondition() {
super(ConfigurationPhase.REGISTER_BEAN, false);
}
}
}