From 2a405c94a89c4413546603bad253d5d277433824 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Fri, 3 Jun 2022 17:41:29 +0200 Subject: [PATCH] Add missing test Closes gh-31249 --- ...actoryInitializationAotProcessorTests.java | 565 ++++++++++++++++++ 1 file changed, 565 insertions(+) create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests.java diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests.java new file mode 100644 index 0000000000..6cafb0da70 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests.java @@ -0,0 +1,565 @@ +/* + * Copyright 2012-2022 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.context.properties; + +import java.lang.reflect.Constructor; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Test; + +import org.springframework.aot.generate.DefaultGenerationContext; +import org.springframework.aot.generate.GenerationContext; +import org.springframework.aot.generate.InMemoryGeneratedFiles; +import org.springframework.aot.hint.ExecutableHint; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.TypeHint; +import org.springframework.aot.hint.TypeReference; +import org.springframework.beans.factory.aot.AotFactoriesLoader; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; +import org.springframework.beans.factory.aot.BeanFactoryInitializationCode; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.EnvironmentAware; +import org.springframework.core.env.Environment; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link ConfigurationPropertiesBeanFactoryInitializationAotProcessor}. + * + * @author Stephane Nicoll + */ +class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests { + + private final ConfigurationPropertiesBeanFactoryInitializationAotProcessor processor = new ConfigurationPropertiesBeanFactoryInitializationAotProcessor(); + + @Test + void configurationPropertiesBeanFactoryInitializationAotProcessorIsRegistered() { + assertThat(new AotFactoriesLoader(new DefaultListableBeanFactory()) + .load(BeanFactoryInitializationAotProcessor.class)) + .anyMatch(ConfigurationPropertiesBeanFactoryInitializationAotProcessor.class::isInstance); + + } + + @Test + void processNoMatchesReturnsNullContribution() { + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + beanFactory.registerBeanDefinition("test", new RootBeanDefinition(String.class)); + assertThat(this.processor.processAheadOfTime(beanFactory)).isNull(); + } + + @Test + void processJavaBeanConfigurationProperties() { + RuntimeHints runtimeHints = process(SampleProperties.class); + assertThat(runtimeHints.reflection().typeHints()).singleElement() + .satisfies(javaBeanBinding(SampleProperties.class)); + } + + @Test + void processJavaBeanConfigurationPropertiesWithSeveralConstructors() throws NoSuchMethodException { + RuntimeHints runtimeHints = process(SamplePropertiesWithSeveralConstructors.class); + assertThat(runtimeHints.reflection().typeHints()).singleElement() + .satisfies(javaBeanBinding(SamplePropertiesWithSeveralConstructors.class, + SamplePropertiesWithSeveralConstructors.class.getDeclaredConstructor())); + } + + @Test + void processJavaBeanConfigurationPropertiesWithMapOfPojo() { + RuntimeHints runtimeHints = process(SamplePropertiesWithMap.class); + List typeHints = runtimeHints.reflection().typeHints().toList(); + assertThat(typeHints).anySatisfy(javaBeanBinding(SamplePropertiesWithMap.class)); + assertThat(typeHints).anySatisfy(javaBeanBinding(Address.class)); + assertThat(typeHints).hasSize(2); + } + + @Test + void processJavaBeanConfigurationPropertiesWithListOfPojo() { + RuntimeHints runtimeHints = process(SamplePropertiesWithList.class); + List typeHints = runtimeHints.reflection().typeHints().toList(); + assertThat(typeHints).anySatisfy(javaBeanBinding(SamplePropertiesWithList.class)); + assertThat(typeHints).anySatisfy(javaBeanBinding(Address.class)); + assertThat(typeHints).hasSize(2); + } + + @Test + void processJavaBeanConfigurationPropertiesWitArrayOfPojo() { + RuntimeHints runtimeHints = process(SamplePropertiesWithArray.class); + List typeHints = runtimeHints.reflection().typeHints().toList(); + assertThat(typeHints).anySatisfy(javaBeanBinding(SamplePropertiesWithArray.class)); + assertThat(typeHints).anySatisfy(javaBeanBinding(Address.class)); + assertThat(typeHints).hasSize(2); + } + + @Test + void processJavaBeanConfigurationPropertiesWithListOfJavaType() { + RuntimeHints runtimeHints = process(SamplePropertiesWithSimpleList.class); + List typeHints = runtimeHints.reflection().typeHints().toList(); + assertThat(typeHints).anySatisfy(javaBeanBinding(SamplePropertiesWithSimpleList.class)); + assertThat(typeHints).hasSize(1); + } + + @Test + void processValueObjectConfigurationProperties() { + RuntimeHints runtimeHints = process(SampleImmutableProperties.class); + List typeHints = runtimeHints.reflection().typeHints().toList(); + assertThat(typeHints).anySatisfy(valueObjectBinding(SampleImmutableProperties.class, + SampleImmutableProperties.class.getDeclaredConstructors()[0])); + assertThat(typeHints).hasSize(1); + } + + @Test + void processValueObjectConfigurationPropertiesWithSpecificConstructor() throws NoSuchMethodException { + RuntimeHints runtimeHints = process(SampleImmutablePropertiesWithSeveralConstructors.class); + List typeHints = runtimeHints.reflection().typeHints().toList(); + assertThat(typeHints).anySatisfy(valueObjectBinding(SampleImmutablePropertiesWithSeveralConstructors.class, + SampleImmutablePropertiesWithSeveralConstructors.class.getDeclaredConstructor(String.class))); + assertThat(typeHints).hasSize(1); + } + + @Test + void processValueObjectConfigurationPropertiesWithSeveralLayersOfPojo() { + RuntimeHints runtimeHints = process(SampleImmutablePropertiesWithList.class); + List typeHints = runtimeHints.reflection().typeHints().toList(); + assertThat(typeHints).anySatisfy(valueObjectBinding(SampleImmutablePropertiesWithList.class, + SampleImmutablePropertiesWithList.class.getDeclaredConstructors()[0])); + assertThat(typeHints).anySatisfy(valueObjectBinding(Person.class, Person.class.getDeclaredConstructors()[0])); + assertThat(typeHints).anySatisfy(valueObjectBinding(Address.class, Address.class.getDeclaredConstructors()[0])); + assertThat(typeHints).hasSize(3); + } + + @Test + void processConfigurationPropertiesWithNestedTypeNotUsedIsIgnored() { + RuntimeHints runtimeHints = process(SamplePropertiesWithNested.class); + assertThat(runtimeHints.reflection().typeHints()).singleElement() + .satisfies(javaBeanBinding(SamplePropertiesWithNested.class)); + } + + @Test + void processConfigurationPropertiesWithNestedExternalType() { + RuntimeHints runtimeHints = process(SamplePropertiesWithExternalNested.class); + assertThat(runtimeHints.reflection().typeHints()) + .anySatisfy(javaBeanBinding(SamplePropertiesWithExternalNested.class)) + .anySatisfy(javaBeanBinding(SampleType.class)).anySatisfy(javaBeanBinding(SampleType.Nested.class)) + .hasSize(3); + } + + @Test + void processConfigurationPropertiesWithRecursiveType() { + RuntimeHints runtimeHints = process(SamplePropertiesWithRecursive.class); + assertThat(runtimeHints.reflection().typeHints()) + .anySatisfy(javaBeanBinding(SamplePropertiesWithRecursive.class)) + .anySatisfy(javaBeanBinding(Recursive.class)).hasSize(2); + } + + @Test + void processValueObjectConfigurationPropertiesWithRecursiveType() { + RuntimeHints runtimeHints = process(SampleImmutablePropertiesWithRecursive.class); + assertThat(runtimeHints.reflection().typeHints()) + .anySatisfy(valueObjectBinding(SampleImmutablePropertiesWithRecursive.class, + SampleImmutablePropertiesWithRecursive.class.getDeclaredConstructors()[0])) + .anySatisfy(valueObjectBinding(ImmutableRecursive.class, + ImmutableRecursive.class.getDeclaredConstructors()[0])) + .hasSize(2); + } + + @Test + void processConfigurationPropertiesWithWellKnownTypes() { + RuntimeHints runtimeHints = process(SamplePropertiesWithWellKnownTypes.class); + assertThat(runtimeHints.reflection().typeHints()) + .anySatisfy(javaBeanBinding(SamplePropertiesWithWellKnownTypes.class)) + // TODO + .hasSize(1); + } + + @Test + void processConfigurationPropertiesWithCrossReference() { + RuntimeHints runtimeHints = process(SamplePropertiesWithCrossReference.class); + assertThat(runtimeHints.reflection().typeHints()) + .anySatisfy(javaBeanBinding(SamplePropertiesWithCrossReference.class)) + .anySatisfy(javaBeanBinding(CrossReferenceA.class)).anySatisfy(javaBeanBinding(CrossReferenceB.class)) + .hasSize(3); + } + + @Test + void processConfigurationPropertiesWithUnresolvedGeneric() { + RuntimeHints runtimeHints = process(SamplePropertiesWithGeneric.class); + assertThat(runtimeHints.reflection().typeHints()).anySatisfy(javaBeanBinding(SamplePropertiesWithGeneric.class)) + .anySatisfy(javaBeanBinding(GenericObject.class)); + } + + private Consumer javaBeanBinding(Class type) { + return javaBeanBinding(type, type.getDeclaredConstructors()[0]); + } + + private Consumer javaBeanBinding(Class type, Constructor constructor) { + return (entry) -> { + assertThat(entry.getType()).isEqualTo(TypeReference.of(type)); + assertThat(entry.constructors()).singleElement().satisfies(match(constructor)); + assertThat(entry.getMemberCategories()).containsOnly(MemberCategory.INVOKE_DECLARED_METHODS, + MemberCategory.INVOKE_PUBLIC_METHODS); + }; + } + + private Consumer valueObjectBinding(Class type, Constructor constructor) { + return (entry) -> { + assertThat(entry.getType()).isEqualTo(TypeReference.of(type)); + assertThat(entry.constructors()).singleElement().satisfies(match(constructor)); + assertThat(entry.getMemberCategories()).containsOnly(MemberCategory.INVOKE_DECLARED_METHODS, + MemberCategory.INVOKE_PUBLIC_METHODS); + }; + } + + private Consumer match(Constructor constructor) { + return (executableHint) -> { + assertThat(executableHint.getName()).isEqualTo(""); + assertThat(Arrays.stream(constructor.getParameterTypes()).map(TypeReference::of).toList()) + .isEqualTo(executableHint.getParameterTypes()); + }; + } + + private RuntimeHints process(Class... types) { + DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); + for (Class type : types) { + beanFactory.registerBeanDefinition(type.getName(), new RootBeanDefinition(type)); + } + BeanFactoryInitializationAotContribution contribution = this.processor.processAheadOfTime(beanFactory); + assertThat(contribution).isNotNull(); + GenerationContext generationContext = new DefaultGenerationContext(new InMemoryGeneratedFiles()); + contribution.applyTo(generationContext, mock(BeanFactoryInitializationCode.class)); + return generationContext.getRuntimeHints(); + } + + @ConfigurationProperties("test") + static class SampleProperties { + + } + + @ConfigurationProperties("test") + public static class SamplePropertiesWithSeveralConstructors { + + SamplePropertiesWithSeveralConstructors() { + } + + SamplePropertiesWithSeveralConstructors(String ignored) { + } + + } + + @ConfigurationProperties("test") + public static class SamplePropertiesWithMap { + + public Map getAddresses() { + return Collections.emptyMap(); + } + + } + + @ConfigurationProperties("test") + public static class SamplePropertiesWithList { + + public List
getAllAddresses() { + return Collections.emptyList(); + } + + } + + @ConfigurationProperties("test") + public static class SamplePropertiesWithSimpleList { + + public List getNames() { + return Collections.emptyList(); + } + + } + + @ConfigurationProperties("test") + public static class SamplePropertiesWithArray { + + public Address[] getAllAddresses() { + return new Address[0]; + } + + } + + @ConfigurationProperties + public static class SampleImmutableProperties { + + private final String name; + + SampleImmutableProperties(String name) { + this.name = name; + } + + } + + @ConfigurationProperties + public static class SampleImmutablePropertiesWithSeveralConstructors { + + private final String name; + + @ConstructorBinding + SampleImmutablePropertiesWithSeveralConstructors(String name) { + this.name = name; + } + + SampleImmutablePropertiesWithSeveralConstructors() { + this("test"); + } + + } + + @ConfigurationProperties + public static class SampleImmutablePropertiesWithList { + + private final List family; + + SampleImmutablePropertiesWithList(List family) { + this.family = family; + } + + } + + @ConfigurationProperties("nested") + public static class SamplePropertiesWithNested { + + static class OneLevelDown { + + } + + } + + @ConfigurationProperties("nested") + public static class SamplePropertiesWithExternalNested { + + private String name; + + @NestedConfigurationProperty + private SampleType sampleType; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public SampleType getSampleType() { + return this.sampleType; + } + + public void setSampleType(SampleType sampleType) { + this.sampleType = sampleType; + } + + } + + @ConfigurationProperties("recursive") + public static class SamplePropertiesWithRecursive { + + @NestedConfigurationProperty + private Recursive recursive; + + public Recursive getRecursive() { + return this.recursive; + } + + public void setRecursive(Recursive recursive) { + this.recursive = recursive; + } + + } + + @ConfigurationProperties + public static class SampleImmutablePropertiesWithRecursive { + + @NestedConfigurationProperty + private ImmutableRecursive recursive; + + SampleImmutablePropertiesWithRecursive(ImmutableRecursive recursive) { + this.recursive = recursive; + } + + } + + @ConfigurationProperties("wellKnownTypes") + public static class SamplePropertiesWithWellKnownTypes implements ApplicationContextAware, EnvironmentAware { + + private ApplicationContext applicationContext; + + private Environment environment; + + public ApplicationContext getApplicationContext() { + return this.applicationContext; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + public Environment getEnvironment() { + return this.environment; + } + + @Override + public void setEnvironment(Environment environment) { + this.environment = environment; + } + + } + + public static class SampleType { + + private final Nested nested = new Nested(); + + public Nested getNested() { + return this.nested; + } + + static class Nested { + + } + + } + + public static class Address { + + } + + public static class Person { + + private final String firstName; + + private final String lastName; + + @NestedConfigurationProperty + private final Address address; + + Person(String firstName, String lastName, Address address) { + this.firstName = firstName; + this.lastName = lastName; + this.address = address; + } + + } + + public static class Recursive { + + private Recursive recursive; + + public Recursive getRecursive() { + return this.recursive; + } + + public void setRecursive(Recursive recursive) { + this.recursive = recursive; + } + + } + + public static class ImmutableRecursive { + + private ImmutableRecursive recursive; + + ImmutableRecursive(ImmutableRecursive recursive) { + this.recursive = recursive; + } + + } + + @ConfigurationProperties("crossreference") + public static class SamplePropertiesWithCrossReference { + + @NestedConfigurationProperty + private CrossReferenceA crossReferenceA; + + public void setCrossReferenceA(CrossReferenceA crossReferenceA) { + this.crossReferenceA = crossReferenceA; + } + + public CrossReferenceA getCrossReferenceA() { + return this.crossReferenceA; + } + + } + + public static class CrossReferenceA { + + @NestedConfigurationProperty + private CrossReferenceB crossReferenceB; + + public void setCrossReferenceB(CrossReferenceB crossReferenceB) { + this.crossReferenceB = crossReferenceB; + } + + public CrossReferenceB getCrossReferenceB() { + return this.crossReferenceB; + } + + } + + public static class CrossReferenceB { + + private CrossReferenceA crossReferenceA; + + public void setCrossReferenceA(CrossReferenceA crossReferenceA) { + this.crossReferenceA = crossReferenceA; + } + + public CrossReferenceA getCrossReferenceA() { + return this.crossReferenceA; + } + + } + + @ConfigurationProperties(prefix = "generic") + public static class SamplePropertiesWithGeneric { + + @NestedConfigurationProperty + private GenericObject generic; + + public GenericObject getGeneric() { + return this.generic; + } + + } + + public static final class GenericObject { + + private final T value; + + GenericObject(T value) { + this.value = value; + } + + public T getValue() { + return this.value; + } + + } + +}