Replace Spring Boot TestCompiler with Spring Framework's version

See gh-31266
pull/32569/head
Scott Frederick 2 years ago
parent 8b2fd6a05a
commit d25a99692f

@ -12,5 +12,6 @@ dependencies {
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testImplementation("org.assertj:assertj-core") testImplementation("org.assertj:assertj-core")
testImplementation("org.springframework:spring-core") testImplementation("org.springframework:spring-core")
testImplementation("org.springframework:spring-core-test")
testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("org.junit.jupiter:junit-jupiter")
} }

@ -16,145 +16,171 @@
package org.springframework.boot.autoconfigureprocessor; package org.springframework.boot.autoconfigureprocessor;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.util.Properties; import java.util.Properties;
import java.util.function.Consumer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.testsupport.compiler.TestCompiler; import org.springframework.core.test.tools.SourceFile;
import org.springframework.util.FileCopyUtils; import org.springframework.core.test.tools.TestCompiler;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.fail;
/** /**
* Tests for {@link AutoConfigureAnnotationProcessor}. * Tests for {@link AutoConfigureAnnotationProcessor}.
* *
* @author Madhura Bhave * @author Madhura Bhave
* @author Moritz Halbritter * @author Moritz Halbritter
* @author Scott Frederick
*/ */
class AutoConfigureAnnotationProcessorTests { class AutoConfigureAnnotationProcessorTests {
@TempDir
File tempDir;
private TestCompiler compiler;
@BeforeEach
void createCompiler() throws IOException {
this.compiler = new TestCompiler(this.tempDir);
}
@Test @Test
void annotatedClass() throws Exception { void annotatedClass() throws Exception {
Properties properties = compile(TestClassConfiguration.class); compile(TestClassConfiguration.class, (properties) -> {
assertThat(properties).hasSize(7); assertThat(properties).hasSize(7);
assertThat(properties).containsEntry( assertThat(properties).containsEntry(
"org.springframework.boot.autoconfigureprocessor.TestClassConfiguration.ConditionalOnClass", "org.springframework.boot.autoconfigureprocessor.TestClassConfiguration.ConditionalOnClass",
"java.io.InputStream,org.springframework.boot.autoconfigureprocessor." "java.io.InputStream,org.springframework.boot.autoconfigureprocessor."
+ "TestClassConfiguration$Nested,org.springframework.foo"); + "TestClassConfiguration$Nested,org.springframework.foo");
assertThat(properties).containsKey("org.springframework.boot.autoconfigureprocessor.TestClassConfiguration"); assertThat(properties)
assertThat(properties) .containsKey("org.springframework.boot.autoconfigureprocessor.TestClassConfiguration");
.containsKey("org.springframework.boot.autoconfigureprocessor.TestClassConfiguration$Nested"); assertThat(properties)
assertThat(properties).containsEntry( .containsKey("org.springframework.boot.autoconfigureprocessor.TestClassConfiguration$Nested");
"org.springframework.boot.autoconfigureprocessor.TestClassConfiguration.ConditionalOnBean", assertThat(properties).containsEntry(
"java.io.OutputStream"); "org.springframework.boot.autoconfigureprocessor.TestClassConfiguration.ConditionalOnBean",
assertThat(properties).containsEntry("org.springframework.boot.autoconfigureprocessor." "java.io.OutputStream");
+ "TestClassConfiguration.ConditionalOnSingleCandidate", "java.io.OutputStream"); assertThat(properties).containsEntry("org.springframework.boot.autoconfigureprocessor."
assertThat(properties).containsEntry("org.springframework.boot.autoconfigureprocessor." + "TestClassConfiguration.ConditionalOnSingleCandidate", "java.io.OutputStream");
+ "TestClassConfiguration.ConditionalOnWebApplication", "SERVLET"); assertThat(properties).containsEntry("org.springframework.boot.autoconfigureprocessor."
+ "TestClassConfiguration.ConditionalOnWebApplication", "SERVLET");
});
} }
@Test @Test
void annotatedClassWithOnlyAutoConfiguration() throws Exception { void annotatedClassWithOnlyAutoConfiguration() {
Properties properties = compile(TestAutoConfigurationOnlyConfiguration.class); compile(TestAutoConfigurationOnlyConfiguration.class, (properties) -> {
assertThat(properties).containsEntry( assertThat(properties).containsEntry(
"org.springframework.boot.autoconfigureprocessor.TestAutoConfigurationOnlyConfiguration", ""); "org.springframework.boot.autoconfigureprocessor.TestAutoConfigurationOnlyConfiguration", "");
assertThat(properties).doesNotContainEntry( assertThat(properties).doesNotContainEntry(
"org.springframework.boot.autoconfigureprocessor.TestAutoConfigurationOnlyConfiguration.AutoConfigureAfter", "org.springframework.boot.autoconfigureprocessor.TestAutoConfigurationOnlyConfiguration.AutoConfigureAfter",
""); "");
assertThat(properties).doesNotContainEntry( assertThat(properties).doesNotContainEntry(
"org.springframework.boot.autoconfigureprocessor.TestAutoConfigurationOnlyConfiguration.AutoConfigureBefore", "org.springframework.boot.autoconfigureprocessor.TestAutoConfigurationOnlyConfiguration.AutoConfigureBefore",
""); "");
});
} }
@Test @Test
void annotatedClassWithOnBeanThatHasName() throws Exception { void annotatedClassWithOnBeanThatHasName() {
Properties properties = compile(TestOnBeanWithNameClassConfiguration.class); compile(TestOnBeanWithNameClassConfiguration.class, (properties) -> {
assertThat(properties).hasSize(2); assertThat(properties).hasSize(2);
assertThat(properties).containsEntry( assertThat(properties).containsEntry(
"org.springframework.boot.autoconfigureprocessor.TestOnBeanWithNameClassConfiguration.ConditionalOnBean", "org.springframework.boot.autoconfigureprocessor.TestOnBeanWithNameClassConfiguration.ConditionalOnBean",
""); "");
});
} }
@Test @Test
void annotatedMethod() throws Exception { void annotatedMethod() {
Properties properties = compile(TestMethodConfiguration.class); process(TestMethodConfiguration.class, (properties) -> assertThat(properties).isNull());
assertThat(properties).isNull();
} }
@Test @Test
void annotatedClassWithOrder() throws Exception { void annotatedClassWithOrder() {
Properties properties = compile(TestOrderedClassConfiguration.class); compile(TestOrderedClassConfiguration.class, (properties) -> {
assertThat(properties).containsEntry( assertThat(properties).containsEntry(
"org.springframework.boot.autoconfigureprocessor.TestOrderedClassConfiguration.ConditionalOnClass", "org.springframework.boot.autoconfigureprocessor.TestOrderedClassConfiguration.ConditionalOnClass",
"java.io.InputStream,java.io.OutputStream"); "java.io.InputStream,java.io.OutputStream");
assertThat(properties).containsEntry("org.springframework.boot.autoconfigureprocessor." assertThat(properties).containsEntry("org.springframework.boot.autoconfigureprocessor."
+ "TestOrderedClassConfiguration.AutoConfigureBefore", "test.before1,test.before2"); + "TestOrderedClassConfiguration.AutoConfigureBefore", "test.before1,test.before2");
assertThat(properties).containsEntry( assertThat(properties).containsEntry(
"org.springframework.boot.autoconfigureprocessor.TestOrderedClassConfiguration.AutoConfigureAfter", "org.springframework.boot.autoconfigureprocessor.TestOrderedClassConfiguration.AutoConfigureAfter",
"java.io.ObjectInputStream"); "java.io.ObjectInputStream");
assertThat(properties).containsEntry( assertThat(properties).containsEntry(
"org.springframework.boot.autoconfigureprocessor.TestOrderedClassConfiguration.AutoConfigureOrder", "org.springframework.boot.autoconfigureprocessor.TestOrderedClassConfiguration.AutoConfigureOrder",
"123"); "123");
});
} }
@Test @Test
void annotatedClassWithAutoConfiguration() throws Exception { void annotatedClassWithAutoConfiguration() throws Exception {
Properties properties = compile(TestAutoConfigurationConfiguration.class); compile(TestAutoConfigurationConfiguration.class, (properties) -> {
assertThat(properties).containsEntry( assertThat(properties).containsEntry(
"org.springframework.boot.autoconfigureprocessor.TestAutoConfigurationConfiguration", ""); "org.springframework.boot.autoconfigureprocessor.TestAutoConfigurationConfiguration", "");
assertThat(properties).containsEntry( assertThat(properties).containsEntry(
"org.springframework.boot.autoconfigureprocessor.TestAutoConfigurationConfiguration.AutoConfigureBefore", "org.springframework.boot.autoconfigureprocessor.TestAutoConfigurationConfiguration.AutoConfigureBefore",
"java.io.InputStream,test.before1,test.before2"); "java.io.InputStream,test.before1,test.before2");
assertThat(properties).containsEntry( assertThat(properties).containsEntry(
"org.springframework.boot.autoconfigureprocessor.TestAutoConfigurationConfiguration.AutoConfigureAfter", "org.springframework.boot.autoconfigureprocessor.TestAutoConfigurationConfiguration.AutoConfigureAfter",
"java.io.OutputStream,test.after1,test.after2"); "java.io.OutputStream,test.after1,test.after2");
});
} }
@Test @Test
void annotatedClassWithAutoConfigurationMerged() throws Exception { void annotatedClassWithAutoConfigurationMerged() throws Exception {
Properties properties = compile(TestMergedAutoConfigurationConfiguration.class); compile(TestMergedAutoConfigurationConfiguration.class, (properties) -> {
assertThat(properties).containsEntry( assertThat(properties).containsEntry(
"org.springframework.boot.autoconfigureprocessor.TestMergedAutoConfigurationConfiguration", ""); "org.springframework.boot.autoconfigureprocessor.TestMergedAutoConfigurationConfiguration", "");
assertThat(properties).containsEntry( assertThat(properties).containsEntry(
"org.springframework.boot.autoconfigureprocessor.TestMergedAutoConfigurationConfiguration.AutoConfigureBefore", "org.springframework.boot.autoconfigureprocessor.TestMergedAutoConfigurationConfiguration.AutoConfigureBefore",
"java.io.InputStream,test.before1,test.before2,java.io.ObjectInputStream,test.before3,test.before4"); "java.io.InputStream,test.before1,test.before2,java.io.ObjectInputStream,test.before3,test.before4");
assertThat(properties).containsEntry( assertThat(properties).containsEntry(
"org.springframework.boot.autoconfigureprocessor.TestMergedAutoConfigurationConfiguration.AutoConfigureAfter", "org.springframework.boot.autoconfigureprocessor.TestMergedAutoConfigurationConfiguration.AutoConfigureAfter",
"java.io.OutputStream,test.after1,test.after2,java.io.ObjectOutputStream,test.after3,test.after4"); "java.io.OutputStream,test.after1,test.after2,java.io.ObjectOutputStream,test.after3,test.after4");
});
} }
@Test // gh-19370 @Test // gh-19370
void propertiesAreFullRepeatable() throws Exception { void propertiesAreFullRepeatable() throws Exception {
String first = new String( process(TestOrderedClassConfiguration.class, (firstFile) -> {
FileCopyUtils.copyToByteArray(process(TestOrderedClassConfiguration.class).getWrittenFile())); String first = getFileContents(firstFile);
String second = new String( process(TestOrderedClassConfiguration.class, (secondFile) -> {
FileCopyUtils.copyToByteArray(process(TestOrderedClassConfiguration.class).getWrittenFile())); String second = getFileContents(secondFile);
assertThat(first).isEqualTo(second).doesNotContain("#"); assertThat(first).isEqualTo(second).doesNotContain("#");
});
});
}
private void compile(Class<?> type, Consumer<Properties> consumer) {
process(type, (writtenFile) -> consumer.accept(getWrittenProperties(writtenFile)));
}
private void process(Class<?> type, Consumer<InputStream> consumer) {
TestAutoConfigureAnnotationProcessor processor = new TestAutoConfigureAnnotationProcessor();
SourceFile sourceFile = SourceFile.forTestClass(type);
TestCompiler compiler = TestCompiler.forSystem().withProcessors(processor).withSources(sourceFile);
compiler.compile((compiled) -> {
InputStream propertiesFile = compiled.getClassLoader()
.getResourceAsStream(AutoConfigureAnnotationProcessor.PROPERTIES_PATH);
consumer.accept(propertiesFile);
});
} }
private Properties compile(Class<?>... types) throws IOException { private Properties getWrittenProperties(InputStream inputStream) {
return process(types).getWrittenProperties(); try {
Properties properties = new Properties();
properties.load(inputStream);
return properties;
}
catch (IOException ex) {
fail("Error reading properties", ex);
}
return null;
} }
private TestAutoConfigureAnnotationProcessor process(Class<?>... types) { private String getFileContents(InputStream inputStream) {
TestAutoConfigureAnnotationProcessor processor = new TestAutoConfigureAnnotationProcessor( try {
this.compiler.getOutputLocation()); return new String(inputStream.readAllBytes());
this.compiler.getTask(types).call(processor); }
return processor; catch (IOException ex) {
fail("Error reading contents of properties file", ex);
}
return null;
} }
} }

@ -16,12 +16,8 @@
package org.springframework.boot.autoconfigureprocessor; package org.springframework.boot.autoconfigureprocessor;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Properties;
import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedAnnotationTypes;
@ -29,6 +25,7 @@ import javax.annotation.processing.SupportedAnnotationTypes;
* Version of {@link AutoConfigureAnnotationProcessor} used for testing. * Version of {@link AutoConfigureAnnotationProcessor} used for testing.
* *
* @author Madhura Bhave * @author Madhura Bhave
* @author Scott Frederick
*/ */
@SupportedAnnotationTypes({ "org.springframework.boot.autoconfigureprocessor.TestConditionalOnClass", @SupportedAnnotationTypes({ "org.springframework.boot.autoconfigureprocessor.TestConditionalOnClass",
"org.springframework.boot.autoconfigureprocessor.TestConditionalOnBean", "org.springframework.boot.autoconfigureprocessor.TestConditionalOnBean",
@ -40,10 +37,7 @@ import javax.annotation.processing.SupportedAnnotationTypes;
"org.springframework.boot.autoconfigureprocessor.TestAutoConfiguration" }) "org.springframework.boot.autoconfigureprocessor.TestAutoConfiguration" })
public class TestAutoConfigureAnnotationProcessor extends AutoConfigureAnnotationProcessor { public class TestAutoConfigureAnnotationProcessor extends AutoConfigureAnnotationProcessor {
private final File outputLocation; public TestAutoConfigureAnnotationProcessor() {
public TestAutoConfigureAnnotationProcessor(File outputLocation) {
this.outputLocation = outputLocation;
} }
@Override @Override
@ -69,20 +63,4 @@ public class TestAutoConfigureAnnotationProcessor extends AutoConfigureAnnotatio
return generators; return generators;
} }
public Properties getWrittenProperties() throws IOException {
File file = getWrittenFile();
if (!file.exists()) {
return null;
}
try (FileInputStream inputStream = new FileInputStream(file)) {
Properties properties = new Properties();
properties.load(inputStream);
return properties;
}
}
public File getWrittenFile() {
return new File(this.outputLocation, PROPERTIES_PATH);
}
} }

@ -19,6 +19,7 @@ dependencies {
testCompileOnly("com.google.code.findbugs:jsr305:3.0.2") testCompileOnly("com.google.code.findbugs:jsr305:3.0.2")
testImplementation(enforcedPlatform(project(":spring-boot-project:spring-boot-dependencies"))) testImplementation(enforcedPlatform(project(":spring-boot-project:spring-boot-dependencies")))
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testImplementation("org.springframework:spring-core-test")
testImplementation("jakarta.validation:jakarta.validation-api") testImplementation("jakarta.validation:jakarta.validation-api")
testImplementation("org.assertj:assertj-core") testImplementation("org.assertj:assertj-core")
testImplementation("org.hamcrest:hamcrest-library") testImplementation("org.hamcrest:hamcrest-library")

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -36,6 +36,7 @@ import org.springframework.boot.configurationprocessor.metadata.JsonMarshaller;
* A {@code MetadataStore} is responsible for the storage of metadata on the filesystem. * A {@code MetadataStore} is responsible for the storage of metadata on the filesystem.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Scott Frederick
* @since 1.2.2 * @since 1.2.2
*/ */
public class MetadataStore { public class MetadataStore {
@ -76,7 +77,7 @@ public class MetadataStore {
} }
private ConfigurationMetadata readMetadata(InputStream in) throws IOException { private ConfigurationMetadata readMetadata(InputStream in) throws IOException {
try { try (in) {
return new JsonMarshaller().read(in); return new JsonMarshaller().read(in);
} }
catch (IOException ex) { catch (IOException ex) {
@ -87,9 +88,6 @@ public class MetadataStore {
"Invalid additional meta-data in '" + METADATA_PATH + "': " + ex.getMessage(), "Invalid additional meta-data in '" + METADATA_PATH + "': " + ex.getMessage(),
Diagnostic.Kind.ERROR); Diagnostic.Kind.ERROR);
} }
finally {
in.close();
}
} }
private FileObject getMetadataResource() throws IOException { private FileObject getMetadataResource() throws IOException {
@ -104,8 +102,26 @@ public class MetadataStore {
// Most build systems will have copied the file to the class output location // Most build systems will have copied the file to the class output location
FileObject fileObject = this.environment.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", FileObject fileObject = this.environment.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "",
ADDITIONAL_METADATA_PATH); ADDITIONAL_METADATA_PATH);
File file = locateAdditionalMetadataFile(new File(fileObject.toUri())); InputStream inputStream = getMetadataStream(fileObject);
return (file.exists() ? new FileInputStream(file) : fileObject.toUri().toURL().openStream()); if (inputStream != null) {
return inputStream;
}
try {
File file = locateAdditionalMetadataFile(new File(fileObject.toUri()));
return (file.exists() ? new FileInputStream(file) : fileObject.toUri().toURL().openStream());
}
catch (Exception ex) {
throw new FileNotFoundException();
}
}
private InputStream getMetadataStream(FileObject fileObject) {
try {
return fileObject.openInputStream();
}
catch (IOException ex) {
return null;
}
} }
File locateAdditionalMetadataFile(File standardLocation) throws IOException { File locateAdditionalMetadataFile(File standardLocation) throws IOException {

@ -16,50 +16,58 @@
package org.springframework.boot.configurationprocessor; package org.springframework.boot.configurationprocessor;
import java.io.File;
import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.BeforeEach; import java.util.concurrent.atomic.AtomicReference;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata; import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
import org.springframework.boot.configurationprocessor.test.CompiledMetadataReader;
import org.springframework.boot.configurationprocessor.test.TestConfigurationMetadataAnnotationProcessor; import org.springframework.boot.configurationprocessor.test.TestConfigurationMetadataAnnotationProcessor;
import org.springframework.boot.testsupport.compiler.TestCompiler; import org.springframework.core.test.tools.ResourceFile;
import org.springframework.core.test.tools.SourceFile;
import org.springframework.core.test.tools.TestCompiler;
/** /**
* Base test infrastructure for metadata generation tests. * Base test infrastructure for metadata generation tests.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Scott Frederick
*/ */
public abstract class AbstractMetadataGenerationTests { public abstract class AbstractMetadataGenerationTests {
@TempDir private static final String ADDITIONAL_METADATA_FILE = "META-INF/additional-spring-configuration-metadata.json";
File tempDir;
protected ConfigurationMetadata compile(Class<?>... types) {
TestCompiler compiler = TestCompiler.forSystem().withSources(sourceFilesOf(types));
return compile(compiler);
}
private TestCompiler compiler; protected ConfigurationMetadata compile(String additionalMetadata, Class<?> type, Class<?>... types) {
TestCompiler compiler = TestCompiler.forSystem().withSources(sourceFilesOf(type))
.withSources(sourceFilesOf(types))
.withResources(ResourceFile.of(ADDITIONAL_METADATA_FILE, additionalMetadata));
return compile(compiler);
}
@BeforeEach protected ConfigurationMetadata compile(String... source) {
void createCompiler() throws IOException { TestCompiler compiler = TestCompiler.forSystem().withSources(sourceFilesOf(source));
this.compiler = new TestCompiler(this.tempDir); return compile(compiler);
} }
protected TestCompiler getCompiler() { private ConfigurationMetadata compile(TestCompiler compiler) {
return this.compiler; TestConfigurationMetadataAnnotationProcessor processor = new TestConfigurationMetadataAnnotationProcessor();
compiler = compiler.withProcessors(processor);
AtomicReference<ConfigurationMetadata> configurationMetadata = new AtomicReference<>();
compiler.compile((compiled) -> configurationMetadata.set(CompiledMetadataReader.getMetadata(compiled)));
return configurationMetadata.get();
} }
protected ConfigurationMetadata compile(Class<?>... types) { private List<SourceFile> sourceFilesOf(Class<?>... types) {
TestConfigurationMetadataAnnotationProcessor processor = new TestConfigurationMetadataAnnotationProcessor( return Arrays.stream(types).map(SourceFile::forTestClass).toList();
this.compiler.getOutputLocation());
this.compiler.getTask(types).call(processor);
return processor.getMetadata();
} }
protected ConfigurationMetadata compile(File... sources) { private List<SourceFile> sourceFilesOf(String... content) {
TestConfigurationMetadataAnnotationProcessor processor = new TestConfigurationMetadataAnnotationProcessor( return Arrays.stream(content).map(SourceFile::of).toList();
this.compiler.getOutputLocation());
this.compiler.getTask(Arrays.asList(sources)).call(processor);
return processor.getMetadata();
} }
} }

@ -16,13 +16,9 @@
package org.springframework.boot.configurationprocessor; package org.springframework.boot.configurationprocessor;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata; import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata; import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
@ -60,9 +56,10 @@ import org.springframework.boot.configurationsample.specific.InvalidDefaultValue
import org.springframework.boot.configurationsample.specific.InvalidDoubleRegistrationProperties; import org.springframework.boot.configurationsample.specific.InvalidDoubleRegistrationProperties;
import org.springframework.boot.configurationsample.specific.SimplePojo; import org.springframework.boot.configurationsample.specific.SimplePojo;
import org.springframework.boot.configurationsample.specific.StaticAccessor; import org.springframework.boot.configurationsample.specific.StaticAccessor;
import org.springframework.core.test.tools.CompilationException;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/** /**
* Tests for {@link ConfigurationMetadataAnnotationProcessor}. * Tests for {@link ConfigurationMetadataAnnotationProcessor}.
@ -73,6 +70,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
* @author Kris De Volder * @author Kris De Volder
* @author Jonas Keßler * @author Jonas Keßler
* @author Pavel Anisimov * @author Pavel Anisimov
* @author Scott Frederick
*/ */
class ConfigurationMetadataAnnotationProcessorTests extends AbstractMetadataGenerationTests { class ConfigurationMetadataAnnotationProcessorTests extends AbstractMetadataGenerationTests {
@ -374,26 +372,30 @@ class ConfigurationMetadataAnnotationProcessorTests extends AbstractMetadataGene
@Test @Test
void invalidDoubleRegistration() { void invalidDoubleRegistration() {
assertThatIllegalStateException().isThrownBy(() -> compile(InvalidDoubleRegistrationProperties.class)) assertThatExceptionOfType(CompilationException.class)
.withMessageContaining("Compilation failed"); .isThrownBy(() -> compile(InvalidDoubleRegistrationProperties.class))
.withMessageContaining("Unable to compile source");
} }
@Test @Test
void constructorParameterPropertyWithInvalidDefaultValueOnNumber() { void constructorParameterPropertyWithInvalidDefaultValueOnNumber() {
assertThatIllegalStateException().isThrownBy(() -> compile(InvalidDefaultValueNumberProperties.class)) assertThatExceptionOfType(CompilationException.class)
.withMessageContaining("Compilation failed"); .isThrownBy(() -> compile(InvalidDefaultValueNumberProperties.class))
.withMessageContaining("Unable to compile source");
} }
@Test @Test
void constructorParameterPropertyWithInvalidDefaultValueOnFloatingPoint() { void constructorParameterPropertyWithInvalidDefaultValueOnFloatingPoint() {
assertThatIllegalStateException().isThrownBy(() -> compile(InvalidDefaultValueFloatingPointProperties.class)) assertThatExceptionOfType(CompilationException.class)
.withMessageContaining("Compilation failed"); .isThrownBy(() -> compile(InvalidDefaultValueFloatingPointProperties.class))
.withMessageContaining("Unable to compile source");
} }
@Test @Test
void constructorParameterPropertyWithInvalidDefaultValueOnCharacter() { void constructorParameterPropertyWithInvalidDefaultValueOnCharacter() {
assertThatIllegalStateException().isThrownBy(() -> compile(InvalidDefaultValueCharacterProperties.class)) assertThatExceptionOfType(CompilationException.class)
.withMessageContaining("Compilation failed"); .isThrownBy(() -> compile(InvalidDefaultValueCharacterProperties.class))
.withMessageContaining("Unable to compile source");
} }
@Test @Test
@ -411,31 +413,27 @@ class ConfigurationMetadataAnnotationProcessorTests extends AbstractMetadataGene
} }
@Test @Test
void recordProperties(@TempDir File temp) throws IOException { void recordProperties() throws IOException {
File exampleRecord = new File(temp, "ExampleRecord.java"); String source = """
try (PrintWriter writer = new PrintWriter(new FileWriter(exampleRecord))) { @org.springframework.boot.configurationsample.ConfigurationProperties("implicit")
writer.println("@org.springframework.boot.configurationsample.ConfigurationProperties(\"implicit\")"); public record ExampleRecord(String someString, Integer someInteger) {
writer.println("public record ExampleRecord(String someString, Integer someInteger) {"); }
writer.println("}"); """;
} ConfigurationMetadata metadata = compile(source);
ConfigurationMetadata metadata = compile(exampleRecord);
assertThat(metadata).has(Metadata.withProperty("implicit.some-string")); assertThat(metadata).has(Metadata.withProperty("implicit.some-string"));
assertThat(metadata).has(Metadata.withProperty("implicit.some-integer")); assertThat(metadata).has(Metadata.withProperty("implicit.some-integer"));
} }
@Test @Test
void recordPropertiesWithDefaultValues(@TempDir File temp) throws IOException { void recordPropertiesWithDefaultValues() throws IOException {
File exampleRecord = new File(temp, "ExampleRecord.java"); String source = """
try (PrintWriter writer = new PrintWriter(new FileWriter(exampleRecord))) { @org.springframework.boot.configurationsample.ConfigurationProperties("record.defaults")
writer.println( public record ExampleRecord(
"@org.springframework.boot.configurationsample.ConfigurationProperties(\"record.defaults\")"); @org.springframework.boot.configurationsample.DefaultValue("An1s9n") String someString,
writer.println("public record ExampleRecord("); @org.springframework.boot.configurationsample.DefaultValue("594") Integer someInteger) {
writer.println("@org.springframework.boot.configurationsample.DefaultValue(\"An1s9n\") String someString,"); }
writer.println("@org.springframework.boot.configurationsample.DefaultValue(\"594\") Integer someInteger"); """;
writer.println(") {"); ConfigurationMetadata metadata = compile(source);
writer.println("}");
}
ConfigurationMetadata metadata = compile(exampleRecord);
assertThat(metadata) assertThat(metadata)
.has(Metadata.withProperty("record.defaults.some-string", String.class).withDefaultValue("An1s9n")); .has(Metadata.withProperty("record.defaults.some-string", String.class).withDefaultValue("An1s9n"));
assertThat(metadata) assertThat(metadata)
@ -443,21 +441,20 @@ class ConfigurationMetadataAnnotationProcessorTests extends AbstractMetadataGene
} }
@Test @Test
void multiConstructorRecordProperties(@TempDir File temp) throws IOException { void multiConstructorRecordProperties() throws IOException {
File exampleRecord = new File(temp, "ExampleRecord.java"); String source = """
try (PrintWriter writer = new PrintWriter(new FileWriter(exampleRecord))) { @org.springframework.boot.configurationsample.ConfigurationProperties("multi")
writer.println("@org.springframework.boot.configurationsample.ConfigurationProperties(\"multi\")"); public record ExampleRecord(String someString, Integer someInteger) {
writer.println("public record ExampleRecord(String someString, Integer someInteger) {"); @org.springframework.boot.configurationsample.ConstructorBinding
writer.println(" @org.springframework.boot.configurationsample.ConstructorBinding"); public ExampleRecord(String someString) {
writer.println(" public ExampleRecord(String someString) {"); this(someString, 42);
writer.println(" this(someString, 42);"); }
writer.println(" }"); public ExampleRecord(Integer someInteger) {
writer.println(" public ExampleRecord(Integer someInteger) {"); this("someString", someInteger);
writer.println(" this(\"someString\", someInteger);"); }
writer.println(" }"); }
writer.println("}"); """;
} ConfigurationMetadata metadata = compile(source);
ConfigurationMetadata metadata = compile(exampleRecord);
assertThat(metadata).has(Metadata.withProperty("multi.some-string")); assertThat(metadata).has(Metadata.withProperty("multi.some-string"));
assertThat(metadata).doesNotHave(Metadata.withProperty("multi.some-integer")); assertThat(metadata).doesNotHave(Metadata.withProperty("multi.some-integer"));
} }

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -36,6 +36,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Metadata generation tests for Actuator endpoints. * Metadata generation tests for Actuator endpoints.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Scott Frederick
*/ */
class EndpointMetadataGenerationTests extends AbstractMetadataGenerationTests { class EndpointMetadataGenerationTests extends AbstractMetadataGenerationTests {
@ -96,8 +97,8 @@ class EndpointMetadataGenerationTests extends AbstractMetadataGenerationTests {
@Test @Test
void incrementalEndpointBuildChangeGeneralEnabledFlag() throws Exception { void incrementalEndpointBuildChangeGeneralEnabledFlag() throws Exception {
TestProject project = new TestProject(this.tempDir, IncrementalEndpoint.class); TestProject project = new TestProject(IncrementalEndpoint.class);
ConfigurationMetadata metadata = project.fullBuild(); ConfigurationMetadata metadata = project.compile();
assertThat(metadata) assertThat(metadata)
.has(Metadata.withGroup("management.endpoint.incremental").fromSource(IncrementalEndpoint.class)); .has(Metadata.withGroup("management.endpoint.incremental").fromSource(IncrementalEndpoint.class));
assertThat(metadata).has(enabledFlag("incremental", true)); assertThat(metadata).has(enabledFlag("incremental", true));
@ -105,7 +106,7 @@ class EndpointMetadataGenerationTests extends AbstractMetadataGenerationTests {
assertThat(metadata.getItems()).hasSize(3); assertThat(metadata.getItems()).hasSize(3);
project.replaceText(IncrementalEndpoint.class, "id = \"incremental\"", project.replaceText(IncrementalEndpoint.class, "id = \"incremental\"",
"id = \"incremental\", enableByDefault = false"); "id = \"incremental\", enableByDefault = false");
metadata = project.incrementalBuild(IncrementalEndpoint.class); metadata = project.compile();
assertThat(metadata) assertThat(metadata)
.has(Metadata.withGroup("management.endpoint.incremental").fromSource(IncrementalEndpoint.class)); .has(Metadata.withGroup("management.endpoint.incremental").fromSource(IncrementalEndpoint.class));
assertThat(metadata).has(enabledFlag("incremental", false)); assertThat(metadata).has(enabledFlag("incremental", false));
@ -115,15 +116,15 @@ class EndpointMetadataGenerationTests extends AbstractMetadataGenerationTests {
@Test @Test
void incrementalEndpointBuildChangeCacheFlag() throws Exception { void incrementalEndpointBuildChangeCacheFlag() throws Exception {
TestProject project = new TestProject(this.tempDir, IncrementalEndpoint.class); TestProject project = new TestProject(IncrementalEndpoint.class);
ConfigurationMetadata metadata = project.fullBuild(); ConfigurationMetadata metadata = project.compile();
assertThat(metadata) assertThat(metadata)
.has(Metadata.withGroup("management.endpoint.incremental").fromSource(IncrementalEndpoint.class)); .has(Metadata.withGroup("management.endpoint.incremental").fromSource(IncrementalEndpoint.class));
assertThat(metadata).has(enabledFlag("incremental", true)); assertThat(metadata).has(enabledFlag("incremental", true));
assertThat(metadata).has(cacheTtl("incremental")); assertThat(metadata).has(cacheTtl("incremental"));
assertThat(metadata.getItems()).hasSize(3); assertThat(metadata.getItems()).hasSize(3);
project.replaceText(IncrementalEndpoint.class, "@Nullable String param", "String param"); project.replaceText(IncrementalEndpoint.class, "@Nullable String param", "String param");
metadata = project.incrementalBuild(IncrementalEndpoint.class); metadata = project.compile();
assertThat(metadata) assertThat(metadata)
.has(Metadata.withGroup("management.endpoint.incremental").fromSource(IncrementalEndpoint.class)); .has(Metadata.withGroup("management.endpoint.incremental").fromSource(IncrementalEndpoint.class));
assertThat(metadata).has(enabledFlag("incremental", true)); assertThat(metadata).has(enabledFlag("incremental", true));
@ -132,14 +133,14 @@ class EndpointMetadataGenerationTests extends AbstractMetadataGenerationTests {
@Test @Test
void incrementalEndpointBuildEnableSpecificEndpoint() throws Exception { void incrementalEndpointBuildEnableSpecificEndpoint() throws Exception {
TestProject project = new TestProject(this.tempDir, SpecificEndpoint.class); TestProject project = new TestProject(SpecificEndpoint.class);
ConfigurationMetadata metadata = project.fullBuild(); ConfigurationMetadata metadata = project.compile();
assertThat(metadata).has(Metadata.withGroup("management.endpoint.specific").fromSource(SpecificEndpoint.class)); assertThat(metadata).has(Metadata.withGroup("management.endpoint.specific").fromSource(SpecificEndpoint.class));
assertThat(metadata).has(enabledFlag("specific", true)); assertThat(metadata).has(enabledFlag("specific", true));
assertThat(metadata).has(cacheTtl("specific")); assertThat(metadata).has(cacheTtl("specific"));
assertThat(metadata.getItems()).hasSize(3); assertThat(metadata.getItems()).hasSize(3);
project.replaceText(SpecificEndpoint.class, "enableByDefault = true", "enableByDefault = false"); project.replaceText(SpecificEndpoint.class, "enableByDefault = true", "enableByDefault = false");
metadata = project.incrementalBuild(SpecificEndpoint.class); metadata = project.compile();
assertThat(metadata).has(Metadata.withGroup("management.endpoint.specific").fromSource(SpecificEndpoint.class)); assertThat(metadata).has(Metadata.withGroup("management.endpoint.specific").fromSource(SpecificEndpoint.class));
assertThat(metadata).has(enabledFlag("specific", false)); assertThat(metadata).has(enabledFlag("specific", false));
assertThat(metadata).has(cacheTtl("specific")); assertThat(metadata).has(cacheTtl("specific"));

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2021 the original author or authors. * Copyright 2012-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -32,31 +32,30 @@ import static org.assertj.core.api.Assertions.assertThat;
* Metadata generation tests for incremental builds. * Metadata generation tests for incremental builds.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Scott Frederick
*/ */
class IncrementalBuildMetadataGenerationTests extends AbstractMetadataGenerationTests { class IncrementalBuildMetadataGenerationTests extends AbstractMetadataGenerationTests {
@Test @Test
void incrementalBuild() throws Exception { void incrementalBuild() throws Exception {
TestProject project = new TestProject(this.tempDir, FooProperties.class, BarProperties.class); TestProject project = new TestProject(FooProperties.class, BarProperties.class);
assertThat(project.getOutputFile(MetadataStore.METADATA_PATH).exists()).isFalse(); ConfigurationMetadata metadata = project.compile();
ConfigurationMetadata metadata = project.fullBuild();
assertThat(project.getOutputFile(MetadataStore.METADATA_PATH).exists()).isTrue();
assertThat(metadata) assertThat(metadata)
.has(Metadata.withProperty("foo.counter").fromSource(FooProperties.class).withDefaultValue(0)); .has(Metadata.withProperty("foo.counter").fromSource(FooProperties.class).withDefaultValue(0));
assertThat(metadata) assertThat(metadata)
.has(Metadata.withProperty("bar.counter").fromSource(BarProperties.class).withDefaultValue(0)); .has(Metadata.withProperty("bar.counter").fromSource(BarProperties.class).withDefaultValue(0));
metadata = project.incrementalBuild(BarProperties.class); metadata = project.compile();
assertThat(metadata) assertThat(metadata)
.has(Metadata.withProperty("foo.counter").fromSource(FooProperties.class).withDefaultValue(0)); .has(Metadata.withProperty("foo.counter").fromSource(FooProperties.class).withDefaultValue(0));
assertThat(metadata) assertThat(metadata)
.has(Metadata.withProperty("bar.counter").fromSource(BarProperties.class).withDefaultValue(0)); .has(Metadata.withProperty("bar.counter").fromSource(BarProperties.class).withDefaultValue(0));
project.addSourceCode(BarProperties.class, BarProperties.class.getResourceAsStream("BarProperties.snippet")); project.addSourceCode(BarProperties.class, BarProperties.class.getResourceAsStream("BarProperties.snippet"));
metadata = project.incrementalBuild(BarProperties.class); metadata = project.compile();
assertThat(metadata).has(Metadata.withProperty("bar.extra")); assertThat(metadata).has(Metadata.withProperty("bar.extra"));
assertThat(metadata).has(Metadata.withProperty("foo.counter").withDefaultValue(0)); assertThat(metadata).has(Metadata.withProperty("foo.counter").withDefaultValue(0));
assertThat(metadata).has(Metadata.withProperty("bar.counter").withDefaultValue(0)); assertThat(metadata).has(Metadata.withProperty("bar.counter").withDefaultValue(0));
project.revert(BarProperties.class); project.revert(BarProperties.class);
metadata = project.incrementalBuild(BarProperties.class); metadata = project.compile();
assertThat(metadata).isNotEqualTo(Metadata.withProperty("bar.extra")); assertThat(metadata).isNotEqualTo(Metadata.withProperty("bar.extra"));
assertThat(metadata).has(Metadata.withProperty("foo.counter").withDefaultValue(0)); assertThat(metadata).has(Metadata.withProperty("foo.counter").withDefaultValue(0));
assertThat(metadata).has(Metadata.withProperty("bar.counter").withDefaultValue(0)); assertThat(metadata).has(Metadata.withProperty("bar.counter").withDefaultValue(0));
@ -64,20 +63,21 @@ class IncrementalBuildMetadataGenerationTests extends AbstractMetadataGeneration
@Test @Test
void incrementalBuildAnnotationRemoved() throws Exception { void incrementalBuildAnnotationRemoved() throws Exception {
TestProject project = new TestProject(this.tempDir, FooProperties.class, BarProperties.class); TestProject project = new TestProject(FooProperties.class, BarProperties.class);
ConfigurationMetadata metadata = project.fullBuild(); ConfigurationMetadata metadata = project.compile();
assertThat(metadata).has(Metadata.withProperty("foo.counter").withDefaultValue(0)); assertThat(metadata).has(Metadata.withProperty("foo.counter").withDefaultValue(0));
assertThat(metadata).has(Metadata.withProperty("bar.counter").withDefaultValue(0)); assertThat(metadata).has(Metadata.withProperty("bar.counter").withDefaultValue(0));
project.replaceText(BarProperties.class, "@ConfigurationProperties", "//@ConfigurationProperties"); project.replaceText(BarProperties.class, "@ConfigurationProperties", "//@ConfigurationProperties");
metadata = project.incrementalBuild(BarProperties.class); project.replaceText(FooProperties.class, "@ConfigurationProperties", "//@ConfigurationProperties");
metadata = project.compile();
assertThat(metadata).isNull(); assertThat(metadata).isNull();
} }
@Test @Test
@Disabled("gh-26271") @Disabled("gh-26271")
void incrementalBuildTypeRenamed() throws Exception { void incrementalBuildTypeRenamed() throws Exception {
TestProject project = new TestProject(this.tempDir, FooProperties.class, BarProperties.class); TestProject project = new TestProject(FooProperties.class, BarProperties.class);
ConfigurationMetadata metadata = project.fullBuild(); ConfigurationMetadata metadata = project.compile();
assertThat(metadata) assertThat(metadata)
.has(Metadata.withProperty("foo.counter").fromSource(FooProperties.class).withDefaultValue(0)); .has(Metadata.withProperty("foo.counter").fromSource(FooProperties.class).withDefaultValue(0));
assertThat(metadata) assertThat(metadata)
@ -85,7 +85,7 @@ class IncrementalBuildMetadataGenerationTests extends AbstractMetadataGeneration
assertThat(metadata).doesNotHave(Metadata.withProperty("bar.counter").fromSource(RenamedBarProperties.class)); assertThat(metadata).doesNotHave(Metadata.withProperty("bar.counter").fromSource(RenamedBarProperties.class));
project.delete(BarProperties.class); project.delete(BarProperties.class);
project.add(RenamedBarProperties.class); project.add(RenamedBarProperties.class);
metadata = project.incrementalBuild(RenamedBarProperties.class); metadata = project.compile();
assertThat(metadata) assertThat(metadata)
.has(Metadata.withProperty("foo.counter").fromSource(FooProperties.class).withDefaultValue(0)); .has(Metadata.withProperty("foo.counter").fromSource(FooProperties.class).withDefaultValue(0));
assertThat(metadata) assertThat(metadata)
@ -96,9 +96,9 @@ class IncrementalBuildMetadataGenerationTests extends AbstractMetadataGeneration
@Test @Test
void incrementalBuildDoesNotDeleteItems() throws Exception { void incrementalBuildDoesNotDeleteItems() throws Exception {
TestProject project = new TestProject(this.tempDir, ClassWithNestedProperties.class, FooProperties.class); TestProject project = new TestProject(ClassWithNestedProperties.class, FooProperties.class);
ConfigurationMetadata initialMetadata = project.fullBuild(); ConfigurationMetadata initialMetadata = project.compile();
ConfigurationMetadata updatedMetadata = project.incrementalBuild(FooProperties.class); ConfigurationMetadata updatedMetadata = project.compile();
assertThat(initialMetadata.getItems()).isEqualTo(updatedMetadata.getItems()); assertThat(initialMetadata.getItems()).isEqualTo(updatedMetadata.getItems());
} }

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,8 +16,6 @@
package org.springframework.boot.configurationprocessor; package org.springframework.boot.configurationprocessor;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -37,15 +35,16 @@ import org.springframework.boot.configurationprocessor.metadata.TestJsonConverte
import org.springframework.boot.configurationsample.simple.DeprecatedSingleProperty; import org.springframework.boot.configurationsample.simple.DeprecatedSingleProperty;
import org.springframework.boot.configurationsample.simple.SimpleProperties; import org.springframework.boot.configurationsample.simple.SimpleProperties;
import org.springframework.boot.configurationsample.specific.SimpleConflictingProperties; import org.springframework.boot.configurationsample.specific.SimpleConflictingProperties;
import org.springframework.util.FileCopyUtils; import org.springframework.core.test.tools.CompilationException;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/** /**
* Metadata generation tests for merging additional metadata. * Metadata generation tests for merging additional metadata.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Scott Frederick
*/ */
class MergeMetadataGenerationTests extends AbstractMetadataGenerationTests { class MergeMetadataGenerationTests extends AbstractMetadataGenerationTests {
@ -53,8 +52,8 @@ class MergeMetadataGenerationTests extends AbstractMetadataGenerationTests {
void mergingOfAdditionalProperty() throws Exception { void mergingOfAdditionalProperty() throws Exception {
ItemMetadata property = ItemMetadata.newProperty(null, "foo", "java.lang.String", ItemMetadata property = ItemMetadata.newProperty(null, "foo", "java.lang.String",
AdditionalMetadata.class.getName(), null, null, null, null); AdditionalMetadata.class.getName(), null, null, null, null);
writeAdditionalMetadata(property); String additionalMetadata = buildAdditionalMetadata(property);
ConfigurationMetadata metadata = compile(SimpleProperties.class); ConfigurationMetadata metadata = compile(additionalMetadata, SimpleProperties.class);
assertThat(metadata).has(Metadata.withProperty("simple.comparator")); assertThat(metadata).has(Metadata.withProperty("simple.comparator"));
assertThat(metadata).has(Metadata.withProperty("foo", String.class).fromSource(AdditionalMetadata.class)); assertThat(metadata).has(Metadata.withProperty("foo", String.class).fromSource(AdditionalMetadata.class));
} }
@ -63,8 +62,8 @@ class MergeMetadataGenerationTests extends AbstractMetadataGenerationTests {
void mergingOfAdditionalPropertyMatchingGroup() throws Exception { void mergingOfAdditionalPropertyMatchingGroup() throws Exception {
ItemMetadata property = ItemMetadata.newProperty(null, "simple", "java.lang.String", null, null, null, null, ItemMetadata property = ItemMetadata.newProperty(null, "simple", "java.lang.String", null, null, null, null,
null); null);
writeAdditionalMetadata(property); String additionalMetadata = buildAdditionalMetadata(property);
ConfigurationMetadata metadata = compile(SimpleProperties.class); ConfigurationMetadata metadata = compile(additionalMetadata, SimpleProperties.class);
assertThat(metadata).has(Metadata.withGroup("simple").fromSource(SimpleProperties.class)); assertThat(metadata).has(Metadata.withGroup("simple").fromSource(SimpleProperties.class));
assertThat(metadata).has(Metadata.withProperty("simple", String.class)); assertThat(metadata).has(Metadata.withProperty("simple", String.class));
} }
@ -72,8 +71,8 @@ class MergeMetadataGenerationTests extends AbstractMetadataGenerationTests {
@Test @Test
void mergeExistingPropertyDefaultValue() throws Exception { void mergeExistingPropertyDefaultValue() throws Exception {
ItemMetadata property = ItemMetadata.newProperty("simple", "flag", null, null, null, null, true, null); ItemMetadata property = ItemMetadata.newProperty("simple", "flag", null, null, null, null, true, null);
writeAdditionalMetadata(property); String additionalMetadata = buildAdditionalMetadata(property);
ConfigurationMetadata metadata = compile(SimpleProperties.class); ConfigurationMetadata metadata = compile(additionalMetadata, SimpleProperties.class);
assertThat(metadata).has(Metadata.withProperty("simple.flag", Boolean.class).fromSource(SimpleProperties.class) assertThat(metadata).has(Metadata.withProperty("simple.flag", Boolean.class).fromSource(SimpleProperties.class)
.withDescription("A simple flag.").withDeprecation(null, null).withDefaultValue(true)); .withDescription("A simple flag.").withDeprecation(null, null).withDefaultValue(true));
assertThat(metadata.getItems()).hasSize(4); assertThat(metadata.getItems()).hasSize(4);
@ -83,8 +82,9 @@ class MergeMetadataGenerationTests extends AbstractMetadataGenerationTests {
void mergeExistingPropertyWithSeveralCandidates() throws Exception { void mergeExistingPropertyWithSeveralCandidates() throws Exception {
ItemMetadata property = ItemMetadata.newProperty("simple", "flag", Boolean.class.getName(), null, null, null, ItemMetadata property = ItemMetadata.newProperty("simple", "flag", Boolean.class.getName(), null, null, null,
true, null); true, null);
writeAdditionalMetadata(property); String additionalMetadata = buildAdditionalMetadata(property);
ConfigurationMetadata metadata = compile(SimpleProperties.class, SimpleConflictingProperties.class); ConfigurationMetadata metadata = compile(additionalMetadata, SimpleProperties.class,
SimpleConflictingProperties.class);
assertThat(metadata.getItems()).hasSize(6); assertThat(metadata.getItems()).hasSize(6);
List<ItemMetadata> items = metadata.getItems().stream().filter((item) -> item.getName().equals("simple.flag")) List<ItemMetadata> items = metadata.getItems().stream().filter((item) -> item.getName().equals("simple.flag"))
.collect(Collectors.toList()); .collect(Collectors.toList());
@ -107,8 +107,8 @@ class MergeMetadataGenerationTests extends AbstractMetadataGenerationTests {
void mergeExistingPropertyDescription() throws Exception { void mergeExistingPropertyDescription() throws Exception {
ItemMetadata property = ItemMetadata.newProperty("simple", "comparator", null, null, null, "A nice comparator.", ItemMetadata property = ItemMetadata.newProperty("simple", "comparator", null, null, null, "A nice comparator.",
null, null); null, null);
writeAdditionalMetadata(property); String additionalMetadata = buildAdditionalMetadata(property);
ConfigurationMetadata metadata = compile(SimpleProperties.class); ConfigurationMetadata metadata = compile(additionalMetadata, SimpleProperties.class);
assertThat(metadata).has(Metadata.withProperty("simple.comparator", "java.util.Comparator<?>") assertThat(metadata).has(Metadata.withProperty("simple.comparator", "java.util.Comparator<?>")
.fromSource(SimpleProperties.class).withDescription("A nice comparator.")); .fromSource(SimpleProperties.class).withDescription("A nice comparator."));
assertThat(metadata.getItems()).hasSize(4); assertThat(metadata.getItems()).hasSize(4);
@ -118,8 +118,8 @@ class MergeMetadataGenerationTests extends AbstractMetadataGenerationTests {
void mergeExistingPropertyDeprecation() throws Exception { void mergeExistingPropertyDeprecation() throws Exception {
ItemMetadata property = ItemMetadata.newProperty("simple", "comparator", null, null, null, null, null, ItemMetadata property = ItemMetadata.newProperty("simple", "comparator", null, null, null, null, null,
new ItemDeprecation("Don't use this.", "simple.complex-comparator", "error")); new ItemDeprecation("Don't use this.", "simple.complex-comparator", "error"));
writeAdditionalMetadata(property); String additionalMetadata = buildAdditionalMetadata(property);
ConfigurationMetadata metadata = compile(SimpleProperties.class); ConfigurationMetadata metadata = compile(additionalMetadata, SimpleProperties.class);
assertThat(metadata).has( assertThat(metadata).has(
Metadata.withProperty("simple.comparator", "java.util.Comparator<?>").fromSource(SimpleProperties.class) Metadata.withProperty("simple.comparator", "java.util.Comparator<?>").fromSource(SimpleProperties.class)
.withDeprecation("Don't use this.", "simple.complex-comparator", "error")); .withDeprecation("Don't use this.", "simple.complex-comparator", "error"));
@ -130,8 +130,8 @@ class MergeMetadataGenerationTests extends AbstractMetadataGenerationTests {
void mergeExistingPropertyDeprecationOverride() throws Exception { void mergeExistingPropertyDeprecationOverride() throws Exception {
ItemMetadata property = ItemMetadata.newProperty("singledeprecated", "name", null, null, null, null, null, ItemMetadata property = ItemMetadata.newProperty("singledeprecated", "name", null, null, null, null, null,
new ItemDeprecation("Don't use this.", "single.name")); new ItemDeprecation("Don't use this.", "single.name"));
writeAdditionalMetadata(property); String additionalMetadata = buildAdditionalMetadata(property);
ConfigurationMetadata metadata = compile(DeprecatedSingleProperty.class); ConfigurationMetadata metadata = compile(additionalMetadata, DeprecatedSingleProperty.class);
assertThat(metadata).has(Metadata.withProperty("singledeprecated.name", String.class.getName()) assertThat(metadata).has(Metadata.withProperty("singledeprecated.name", String.class.getName())
.fromSource(DeprecatedSingleProperty.class).withDeprecation("Don't use this.", "single.name")); .fromSource(DeprecatedSingleProperty.class).withDeprecation("Don't use this.", "single.name"));
assertThat(metadata.getItems()).hasSize(3); assertThat(metadata.getItems()).hasSize(3);
@ -141,8 +141,8 @@ class MergeMetadataGenerationTests extends AbstractMetadataGenerationTests {
void mergeExistingPropertyDeprecationOverrideLevel() throws Exception { void mergeExistingPropertyDeprecationOverrideLevel() throws Exception {
ItemMetadata property = ItemMetadata.newProperty("singledeprecated", "name", null, null, null, null, null, ItemMetadata property = ItemMetadata.newProperty("singledeprecated", "name", null, null, null, null, null,
new ItemDeprecation(null, null, "error")); new ItemDeprecation(null, null, "error"));
writeAdditionalMetadata(property); String additionalMetadata = buildAdditionalMetadata(property);
ConfigurationMetadata metadata = compile(DeprecatedSingleProperty.class); ConfigurationMetadata metadata = compile(additionalMetadata, DeprecatedSingleProperty.class);
assertThat(metadata).has(Metadata.withProperty("singledeprecated.name", String.class.getName()) assertThat(metadata).has(Metadata.withProperty("singledeprecated.name", String.class.getName())
.fromSource(DeprecatedSingleProperty.class) .fromSource(DeprecatedSingleProperty.class)
.withDeprecation("renamed", "singledeprecated.new-name", "error")); .withDeprecation("renamed", "singledeprecated.new-name", "error"));
@ -151,17 +151,17 @@ class MergeMetadataGenerationTests extends AbstractMetadataGenerationTests {
@Test @Test
void mergeOfInvalidAdditionalMetadata() throws IOException { void mergeOfInvalidAdditionalMetadata() throws IOException {
File additionalMetadataFile = createAdditionalMetadataFile(); String metadata = "Hello World";
FileCopyUtils.copy("Hello World", new FileWriter(additionalMetadataFile)); assertThatExceptionOfType(CompilationException.class)
assertThatIllegalStateException().isThrownBy(() -> compile(SimpleProperties.class)) .isThrownBy(() -> compile(metadata, SimpleProperties.class))
.withMessage("Compilation failed"); .withMessageContaining("Invalid additional meta-data");
} }
@Test @Test
void mergingOfSimpleHint() throws Exception { void mergingOfSimpleHint() throws Exception {
writeAdditionalHints(ItemHint.newHint("simple.the-name", new ItemHint.ValueHint("boot", "Bla bla"), String hints = buildAdditionalHints(ItemHint.newHint("simple.the-name",
new ItemHint.ValueHint("spring", null))); new ItemHint.ValueHint("boot", "Bla bla"), new ItemHint.ValueHint("spring", null)));
ConfigurationMetadata metadata = compile(SimpleProperties.class); ConfigurationMetadata metadata = compile(hints, SimpleProperties.class);
assertThat(metadata).has(Metadata.withProperty("simple.the-name", String.class) assertThat(metadata).has(Metadata.withProperty("simple.the-name", String.class)
.fromSource(SimpleProperties.class).withDescription("The name of this simple properties.") .fromSource(SimpleProperties.class).withDescription("The name of this simple properties.")
.withDefaultValue("boot").withDeprecation(null, null)); .withDefaultValue("boot").withDeprecation(null, null));
@ -171,8 +171,9 @@ class MergeMetadataGenerationTests extends AbstractMetadataGenerationTests {
@Test @Test
void mergingOfHintWithNonCanonicalName() throws Exception { void mergingOfHintWithNonCanonicalName() throws Exception {
writeAdditionalHints(ItemHint.newHint("simple.theName", new ItemHint.ValueHint("boot", "Bla bla"))); String hints = buildAdditionalHints(
ConfigurationMetadata metadata = compile(SimpleProperties.class); ItemHint.newHint("simple.theName", new ItemHint.ValueHint("boot", "Bla bla")));
ConfigurationMetadata metadata = compile(hints, SimpleProperties.class);
assertThat(metadata).has(Metadata.withProperty("simple.the-name", String.class) assertThat(metadata).has(Metadata.withProperty("simple.the-name", String.class)
.fromSource(SimpleProperties.class).withDescription("The name of this simple properties.") .fromSource(SimpleProperties.class).withDescription("The name of this simple properties.")
.withDefaultValue("boot").withDeprecation(null, null)); .withDefaultValue("boot").withDeprecation(null, null));
@ -181,10 +182,10 @@ class MergeMetadataGenerationTests extends AbstractMetadataGenerationTests {
@Test @Test
void mergingOfHintWithProvider() throws Exception { void mergingOfHintWithProvider() throws Exception {
writeAdditionalHints(new ItemHint("simple.theName", Collections.emptyList(), String hints = buildAdditionalHints(new ItemHint("simple.theName", Collections.emptyList(),
Arrays.asList(new ItemHint.ValueProvider("first", Collections.singletonMap("target", "org.foo")), Arrays.asList(new ItemHint.ValueProvider("first", Collections.singletonMap("target", "org.foo")),
new ItemHint.ValueProvider("second", null)))); new ItemHint.ValueProvider("second", null))));
ConfigurationMetadata metadata = compile(SimpleProperties.class); ConfigurationMetadata metadata = compile(hints, SimpleProperties.class);
assertThat(metadata).has(Metadata.withProperty("simple.the-name", String.class) assertThat(metadata).has(Metadata.withProperty("simple.the-name", String.class)
.fromSource(SimpleProperties.class).withDescription("The name of this simple properties.") .fromSource(SimpleProperties.class).withDescription("The name of this simple properties.")
.withDefaultValue("boot").withDeprecation(null, null)); .withDefaultValue("boot").withDeprecation(null, null));
@ -194,58 +195,48 @@ class MergeMetadataGenerationTests extends AbstractMetadataGenerationTests {
@Test @Test
void mergingOfAdditionalDeprecation() throws Exception { void mergingOfAdditionalDeprecation() throws Exception {
writePropertyDeprecation(ItemMetadata.newProperty("simple", "wrongName", "java.lang.String", null, null, null, String deprecations = buildPropertyDeprecations(ItemMetadata.newProperty("simple", "wrongName",
null, new ItemDeprecation("Lame name.", "simple.the-name"))); "java.lang.String", null, null, null, null, new ItemDeprecation("Lame name.", "simple.the-name")));
ConfigurationMetadata metadata = compile(SimpleProperties.class); ConfigurationMetadata metadata = compile(deprecations, SimpleProperties.class);
assertThat(metadata).has(Metadata.withProperty("simple.wrong-name", String.class).withDeprecation("Lame name.", assertThat(metadata).has(Metadata.withProperty("simple.wrong-name", String.class).withDeprecation("Lame name.",
"simple.the-name")); "simple.the-name"));
} }
@Test @Test
void mergingOfAdditionalMetadata() throws Exception { void mergingOfAdditionalMetadata() throws Exception {
File metaInfDirectory = new File(getCompiler().getOutputLocation(), "META-INF");
metaInfDirectory.mkdirs();
File additionalMetadataFile = new File(metaInfDirectory, "additional-spring-configuration-metadata.json");
additionalMetadataFile.createNewFile();
JSONObject property = new JSONObject(); JSONObject property = new JSONObject();
property.put("name", "foo"); property.put("name", "foo");
property.put("type", "java.lang.String"); property.put("type", "java.lang.String");
property.put("sourceType", AdditionalMetadata.class.getName()); property.put("sourceType", AdditionalMetadata.class.getName());
JSONArray properties = new JSONArray(); JSONArray properties = new JSONArray();
properties.put(property); properties.put(property);
JSONObject additionalMetadata = new JSONObject(); JSONObject json = new JSONObject();
additionalMetadata.put("properties", properties); json.put("properties", properties);
FileWriter writer = new FileWriter(additionalMetadataFile); String additionalMetadata = json.toString();
writer.append(additionalMetadata.toString(2)); ConfigurationMetadata metadata = compile(additionalMetadata, SimpleProperties.class);
writer.flush();
writer.close();
ConfigurationMetadata metadata = compile(SimpleProperties.class);
assertThat(metadata).has(Metadata.withProperty("simple.comparator")); assertThat(metadata).has(Metadata.withProperty("simple.comparator"));
assertThat(metadata).has(Metadata.withProperty("foo", String.class).fromSource(AdditionalMetadata.class)); assertThat(metadata).has(Metadata.withProperty("foo", String.class).fromSource(AdditionalMetadata.class));
} }
private void writeAdditionalMetadata(ItemMetadata... metadata) throws Exception { private String buildAdditionalMetadata(ItemMetadata... metadata) throws Exception {
TestJsonConverter converter = new TestJsonConverter(); TestJsonConverter converter = new TestJsonConverter();
File additionalMetadataFile = createAdditionalMetadataFile();
JSONObject additionalMetadata = new JSONObject(); JSONObject additionalMetadata = new JSONObject();
JSONArray properties = new JSONArray(); JSONArray properties = new JSONArray();
for (ItemMetadata itemMetadata : metadata) { for (ItemMetadata itemMetadata : metadata) {
properties.put(converter.toJsonObject(itemMetadata)); properties.put(converter.toJsonObject(itemMetadata));
} }
additionalMetadata.put("properties", properties); additionalMetadata.put("properties", properties);
writeMetadata(additionalMetadataFile, additionalMetadata); return additionalMetadata.toString();
} }
private void writeAdditionalHints(ItemHint... hints) throws Exception { private String buildAdditionalHints(ItemHint... hints) throws Exception {
TestJsonConverter converter = new TestJsonConverter(); TestJsonConverter converter = new TestJsonConverter();
File additionalMetadataFile = createAdditionalMetadataFile();
JSONObject additionalMetadata = new JSONObject(); JSONObject additionalMetadata = new JSONObject();
additionalMetadata.put("hints", converter.toJsonArray(Arrays.asList(hints))); additionalMetadata.put("hints", converter.toJsonArray(Arrays.asList(hints)));
writeMetadata(additionalMetadataFile, additionalMetadata); return additionalMetadata.toString();
} }
private void writePropertyDeprecation(ItemMetadata... items) throws Exception { private String buildPropertyDeprecations(ItemMetadata... items) throws Exception {
File additionalMetadataFile = createAdditionalMetadataFile();
JSONArray propertiesArray = new JSONArray(); JSONArray propertiesArray = new JSONArray();
for (ItemMetadata item : items) { for (ItemMetadata item : items) {
JSONObject jsonObject = new JSONObject(); JSONObject jsonObject = new JSONObject();
@ -269,21 +260,7 @@ class MergeMetadataGenerationTests extends AbstractMetadataGenerationTests {
} }
JSONObject additionalMetadata = new JSONObject(); JSONObject additionalMetadata = new JSONObject();
additionalMetadata.put("properties", propertiesArray); additionalMetadata.put("properties", propertiesArray);
writeMetadata(additionalMetadataFile, additionalMetadata); return additionalMetadata.toString();
}
private File createAdditionalMetadataFile() throws IOException {
File metaInfDirectory = new File(getCompiler().getOutputLocation(), "META-INF");
metaInfDirectory.mkdirs();
File additionalMetadataFile = new File(metaInfDirectory, "additional-spring-configuration-metadata.json");
additionalMetadataFile.createNewFile();
return additionalMetadataFile;
}
private void writeMetadata(File metadataFile, JSONObject metadata) throws Exception {
try (FileWriter writer = new FileWriter(metadataFile)) {
writer.append(metadata.toString(2));
}
} }
static class AdditionalMetadata { static class AdditionalMetadata {

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -36,6 +36,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Metadata generation tests for types defined by {@code @Bean} methods. * Metadata generation tests for types defined by {@code @Bean} methods.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Scott Frederick
*/ */
class MethodBasedMetadataGenerationTests extends AbstractMetadataGenerationTests { class MethodBasedMetadataGenerationTests extends AbstractMetadataGenerationTests {
@ -65,7 +66,7 @@ class MethodBasedMetadataGenerationTests extends AbstractMetadataGenerationTests
@Test @Test
void privateMethodConfig() { void privateMethodConfig() {
ConfigurationMetadata metadata = compile(PrivateMethodConfig.class); ConfigurationMetadata metadata = compile(PrivateMethodConfig.class);
assertThat(metadata).doesNotHave(Metadata.withGroup("foo")); assertThat(metadata).isNull();
} }
@Test @Test

@ -16,12 +16,11 @@
package org.springframework.boot.configurationprocessor; package org.springframework.boot.configurationprocessor;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -29,7 +28,6 @@ import java.util.stream.Stream;
import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeElement;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.configurationprocessor.metadata.ItemMetadata; import org.springframework.boot.configurationprocessor.metadata.ItemMetadata;
import org.springframework.boot.configurationprocessor.test.RoundEnvironmentTester; import org.springframework.boot.configurationprocessor.test.RoundEnvironmentTester;
@ -49,7 +47,8 @@ import org.springframework.boot.configurationsample.simple.HierarchicalPropertie
import org.springframework.boot.configurationsample.simple.HierarchicalPropertiesParent; import org.springframework.boot.configurationsample.simple.HierarchicalPropertiesParent;
import org.springframework.boot.configurationsample.simple.SimpleProperties; import org.springframework.boot.configurationsample.simple.SimpleProperties;
import org.springframework.boot.configurationsample.specific.TwoConstructorsExample; import org.springframework.boot.configurationsample.specific.TwoConstructorsExample;
import org.springframework.boot.testsupport.compiler.TestCompiler; import org.springframework.core.test.tools.SourceFile;
import org.springframework.core.test.tools.TestCompiler;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -57,12 +56,10 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link PropertyDescriptorResolver}. * Tests for {@link PropertyDescriptorResolver}.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Scott Frederick
*/ */
class PropertyDescriptorResolverTests { class PropertyDescriptorResolverTests {
@TempDir
File tempDir;
@Test @Test
void propertiesWithJavaBeanProperties() throws IOException { void propertiesWithJavaBeanProperties() throws IOException {
process(SimpleProperties.class, process(SimpleProperties.class,
@ -181,7 +178,7 @@ class PropertyDescriptorResolverTests {
} }
private void process(Class<?> target, Collection<Class<?>> additionalClasses, private void process(Class<?> target, Collection<Class<?>> additionalClasses,
BiConsumer<TypeElement, MetadataGenerationEnvironment> consumer) throws IOException { BiConsumer<TypeElement, MetadataGenerationEnvironment> consumer) {
BiConsumer<RoundEnvironmentTester, MetadataGenerationEnvironment> internalConsumer = (roundEnv, BiConsumer<RoundEnvironmentTester, MetadataGenerationEnvironment> internalConsumer = (roundEnv,
metadataEnv) -> { metadataEnv) -> {
TypeElement element = roundEnv.getRootElement(target); TypeElement element = roundEnv.getRootElement(target);
@ -189,11 +186,12 @@ class PropertyDescriptorResolverTests {
}; };
TestableAnnotationProcessor<MetadataGenerationEnvironment> processor = new TestableAnnotationProcessor<>( TestableAnnotationProcessor<MetadataGenerationEnvironment> processor = new TestableAnnotationProcessor<>(
internalConsumer, new MetadataGenerationEnvironmentFactory()); internalConsumer, new MetadataGenerationEnvironmentFactory());
TestCompiler compiler = new TestCompiler(this.tempDir); SourceFile targetSource = SourceFile.forTestClass(target);
ArrayList<Class<?>> allClasses = new ArrayList<>(); List<SourceFile> additionalSource = additionalClasses.stream().map(SourceFile::forTestClass).toList();
allClasses.add(target); TestCompiler compiler = TestCompiler.forSystem().withProcessors(processor).withSources(targetSource)
allClasses.addAll(additionalClasses); .withSources(additionalSource);
compiler.getTask(allClasses.toArray(new Class<?>[0])).call(processor); compiler.compile((compiled) -> {
});
} }
} }

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,8 +16,6 @@
package org.springframework.boot.configurationprocessor; package org.springframework.boot.configurationprocessor;
import java.io.File;
import java.io.IOException;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import javax.lang.model.element.Element; import javax.lang.model.element.Element;
@ -26,23 +24,20 @@ import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement; import javax.lang.model.element.VariableElement;
import javax.lang.model.util.ElementFilter; import javax.lang.model.util.ElementFilter;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.configurationprocessor.test.ItemMetadataAssert; import org.springframework.boot.configurationprocessor.test.ItemMetadataAssert;
import org.springframework.boot.configurationprocessor.test.RoundEnvironmentTester; import org.springframework.boot.configurationprocessor.test.RoundEnvironmentTester;
import org.springframework.boot.configurationprocessor.test.TestableAnnotationProcessor; import org.springframework.boot.configurationprocessor.test.TestableAnnotationProcessor;
import org.springframework.boot.testsupport.compiler.TestCompiler; import org.springframework.core.test.tools.SourceFile;
import org.springframework.core.test.tools.TestCompiler;
/** /**
* Base test infrastructure to test {@link PropertyDescriptor} implementations. * Base test infrastructure to test {@link PropertyDescriptor} implementations.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Scott Frederick
*/ */
public abstract class PropertyDescriptorTests { public abstract class PropertyDescriptorTests {
@TempDir
File tempDir;
protected String createAccessorMethodName(String prefix, String name) { protected String createAccessorMethodName(String prefix, String name) {
char[] chars = name.toCharArray(); char[] chars = name.toCharArray();
chars[0] = Character.toUpperCase(chars[0]); chars[0] = Character.toUpperCase(chars[0]);
@ -66,12 +61,14 @@ public abstract class PropertyDescriptorTests {
return new ItemMetadataAssert(property.resolveItemMetadata("test", metadataEnv)); return new ItemMetadataAssert(property.resolveItemMetadata("test", metadataEnv));
} }
protected void process(Class<?> target, BiConsumer<RoundEnvironmentTester, MetadataGenerationEnvironment> consumer) protected void process(Class<?> target,
throws IOException { BiConsumer<RoundEnvironmentTester, MetadataGenerationEnvironment> consumer) {
TestableAnnotationProcessor<MetadataGenerationEnvironment> processor = new TestableAnnotationProcessor<>( TestableAnnotationProcessor<MetadataGenerationEnvironment> processor = new TestableAnnotationProcessor<>(
consumer, new MetadataGenerationEnvironmentFactory()); consumer, new MetadataGenerationEnvironmentFactory());
TestCompiler compiler = new TestCompiler(this.tempDir); TestCompiler compiler = TestCompiler.forSystem().withProcessors(processor)
compiler.getTask(target).call(processor); .withSources(SourceFile.forTestClass(target));
compiler.compile((compiled) -> {
});
} }
} }

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,27 +16,22 @@
package org.springframework.boot.configurationprocessor; package org.springframework.boot.configurationprocessor;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.StringReader;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.List;
import java.util.LinkedHashSet; import java.util.concurrent.atomic.AtomicReference;
import java.util.Set;
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata; import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
import org.springframework.boot.configurationprocessor.test.CompiledMetadataReader;
import org.springframework.boot.configurationprocessor.test.TestConfigurationMetadataAnnotationProcessor; import org.springframework.boot.configurationprocessor.test.TestConfigurationMetadataAnnotationProcessor;
import org.springframework.boot.configurationsample.ConfigurationProperties; import org.springframework.boot.configurationsample.ConfigurationProperties;
import org.springframework.boot.configurationsample.NestedConfigurationProperty; import org.springframework.boot.configurationsample.NestedConfigurationProperty;
import org.springframework.boot.testsupport.compiler.TestCompiler; import org.springframework.core.test.tools.SourceFile;
import org.springframework.boot.testsupport.compiler.TestCompiler.TestCompilationTask; import org.springframework.core.test.tools.SourceFiles;
import org.springframework.core.test.tools.TestCompiler;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileCopyUtils;
import org.springframework.util.FileSystemUtils;
/** /**
* A TestProject contains a copy of a subset of test sample code. * A TestProject contains a copy of a subset of test sample code.
@ -46,85 +41,26 @@ import org.springframework.util.FileSystemUtils;
* original content itself. * original content itself.
* *
* @author Kris De Volder * @author Kris De Volder
* @author Scott Frederick
*/ */
public class TestProject { public class TestProject {
private static final Class<?>[] ALWAYS_INCLUDE = { ConfigurationProperties.class, private static final Class<?>[] ALWAYS_INCLUDE = { ConfigurationProperties.class,
NestedConfigurationProperty.class }; NestedConfigurationProperty.class };
/** private SourceFiles sources;
* Contains copies of the original source so we can modify it safely to test
* incremental builds.
*/
private File sourceDirectory;
private TestCompiler compiler;
private Set<File> sourceFiles = new LinkedHashSet<>();
public TestProject(File tempDirectory, Class<?>... classes) throws IOException {
this.sourceDirectory = new File(tempDirectory, "src");
this.compiler = new TestCompiler(new File(tempDirectory, "build")) {
@Override
protected File getSourceDirectory() {
return TestProject.this.sourceDirectory;
}
};
Set<Class<?>> contents = new HashSet<>(Arrays.asList(classes));
contents.addAll(Arrays.asList(ALWAYS_INCLUDE));
copySources(contents);
}
private void copySources(Set<Class<?>> contents) throws IOException {
for (Class<?> type : contents) {
copySources(type);
}
}
private void copySources(Class<?> type) throws IOException {
File original = getOriginalSourceFile(type);
File target = getSourceFile(type);
target.getParentFile().mkdirs();
FileCopyUtils.copy(original, target);
this.sourceFiles.add(target);
}
public File getSourceFile(Class<?> type) {
return new File(this.sourceDirectory, TestCompiler.sourcePathFor(type));
}
public ConfigurationMetadata fullBuild() {
TestConfigurationMetadataAnnotationProcessor processor = new TestConfigurationMetadataAnnotationProcessor(
this.compiler.getOutputLocation());
TestCompilationTask task = this.compiler.getTask(this.sourceFiles);
deleteDirectoryContents(this.compiler.getOutputLocation());
task.call(processor);
return processor.getMetadata();
}
public ConfigurationMetadata incrementalBuild(Class<?>... toRecompile) { public TestProject(Class<?>... classes) {
TestConfigurationMetadataAnnotationProcessor processor = new TestConfigurationMetadataAnnotationProcessor( this.sources = SourceFiles.none().and(sourceFilesOf(ALWAYS_INCLUDE)).and(sourceFilesOf(classes));
this.compiler.getOutputLocation());
TestCompilationTask task = this.compiler.getTask(toRecompile);
task.call(processor);
return processor.getMetadata();
} }
private void deleteDirectoryContents(File outputDirectory) { public ConfigurationMetadata compile() {
FileSystemUtils.deleteRecursively(outputDirectory); TestConfigurationMetadataAnnotationProcessor processor = new TestConfigurationMetadataAnnotationProcessor();
outputDirectory.mkdirs(); TestCompiler compiler = TestCompiler.forSystem().withProcessors(processor);
} AtomicReference<ConfigurationMetadata> configurationMetadata = new AtomicReference<>();
compiler.compile(this.sources,
/** (compiled) -> configurationMetadata.set(CompiledMetadataReader.getMetadata(compiled)));
* Retrieve File relative to project's output directory. return configurationMetadata.get();
* @param relativePath the relative path
* @return the output file
*/
public File getOutputFile(String relativePath) {
Assert.isTrue(!new File(relativePath).isAbsolute(), "'" + relativePath + "' was absolute");
return new File(this.compiler.getOutputLocation(), relativePath);
} }
/** /**
@ -134,12 +70,12 @@ public class TestProject {
* @throws Exception if the source cannot be added * @throws Exception if the source cannot be added
*/ */
public void addSourceCode(Class<?> target, InputStream snippetStream) throws Exception { public void addSourceCode(Class<?> target, InputStream snippetStream) throws Exception {
File targetFile = getSourceFile(target); SourceFile sourceFile = SourceFile.forTestClass(target);
String contents = getContents(targetFile); String contents = sourceFile.getContent();
int insertAt = contents.lastIndexOf('}'); int insertAt = contents.lastIndexOf('}');
String additionalSource = FileCopyUtils.copyToString(new InputStreamReader(snippetStream)); String additionalSource = FileCopyUtils.copyToString(new InputStreamReader(snippetStream));
contents = contents.substring(0, insertAt) + additionalSource + contents.substring(insertAt); contents = contents.substring(0, insertAt) + additionalSource + contents.substring(insertAt);
putContents(targetFile, contents); this.sources = this.sources.and(SourceFile.of(contents));
} }
/** /**
@ -147,53 +83,40 @@ public class TestProject {
* @param type the class to delete * @param type the class to delete
*/ */
public void delete(Class<?> type) { public void delete(Class<?> type) {
File target = getSourceFile(type); SourceFile[] newSources = this.sources.stream()
target.delete(); .filter((sourceFile) -> !sourceFile.getPath().equals(SourceFile.forTestClass(type).getPath()))
this.sourceFiles.remove(target); .toArray(SourceFile[]::new);
this.sources = SourceFiles.of(newSources);
} }
/** /**
* Restore source code of given class to its original contents. * Restore source code of given class to its original contents.
* @param type the class to revert * @param type the class to revert
* @throws IOException on IO error
*/ */
public void revert(Class<?> type) throws IOException { public void revert(Class<?> type) {
Assert.isTrue(getSourceFile(type).exists(), "Source file for type '" + type + "' does not exist"); Assert.isTrue(this.sources.stream().anyMatch((sourceFile) -> sourceFile.getClassName().equals(type.getName())),
copySources(type); "Source file for type '" + type + "' does not exist");
this.sources = this.sources.and(SourceFile.forTestClass(type));
} }
/** /**
* Add source code of given class to this project. * Add source code of given class to this project.
* @param type the class to add * @param type the class to add
* @throws IOException on IO error
*/
public void add(Class<?> type) throws IOException {
Assert.isTrue(!getSourceFile(type).exists(), "Source file for type '" + type + "' already exists");
copySources(type);
}
public void replaceText(Class<?> type, String find, String replace) throws Exception {
File target = getSourceFile(type);
String contents = getContents(target);
contents = contents.replace(find, replace);
putContents(target, contents);
}
/**
* Find the 'original' source code for given test class. Clients or subclasses should
* have no need to know about these. They should work only with the copied source
* code.
*/ */
private File getOriginalSourceFile(Class<?> type) { public void add(Class<?> type) {
return new File(TestCompiler.SOURCE_DIRECTORY, TestCompiler.sourcePathFor(type)); Assert.isTrue(this.sources.stream().noneMatch((sourceFile) -> sourceFile.getClassName().equals(type.getName())),
"Source file for type '" + type + "' already exists");
this.sources = this.sources.and(SourceFile.forTestClass(type));
} }
private static void putContents(File targetFile, String contents) throws IOException { public void replaceText(Class<?> type, String find, String replace) {
FileCopyUtils.copy(new StringReader(contents), new FileWriter(targetFile)); SourceFile sourceFile = SourceFile.forTestClass(type);
String contents = sourceFile.getContent().replace(find, replace);
this.sources = this.sources.and(SourceFile.of(contents));
} }
private static String getContents(File file) throws Exception { private List<SourceFile> sourceFilesOf(Class<?>... types) {
return FileCopyUtils.copyToString(new FileReader(file)); return Arrays.stream(types).map(SourceFile::forTestClass).toList();
} }
} }

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,13 +16,11 @@
package org.springframework.boot.configurationprocessor; package org.springframework.boot.configurationprocessor;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.time.Duration; import java.time.Duration;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.configurationprocessor.TypeUtils.TypeDescriptor; import org.springframework.boot.configurationprocessor.TypeUtils.TypeDescriptor;
import org.springframework.boot.configurationprocessor.test.RoundEnvironmentTester; import org.springframework.boot.configurationprocessor.test.RoundEnvironmentTester;
@ -30,7 +28,8 @@ import org.springframework.boot.configurationprocessor.test.TestableAnnotationPr
import org.springframework.boot.configurationsample.generic.AbstractGenericProperties; import org.springframework.boot.configurationsample.generic.AbstractGenericProperties;
import org.springframework.boot.configurationsample.generic.AbstractIntermediateGenericProperties; import org.springframework.boot.configurationsample.generic.AbstractIntermediateGenericProperties;
import org.springframework.boot.configurationsample.generic.SimpleGenericProperties; import org.springframework.boot.configurationsample.generic.SimpleGenericProperties;
import org.springframework.boot.testsupport.compiler.TestCompiler; import org.springframework.core.test.tools.SourceFile;
import org.springframework.core.test.tools.TestCompiler;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -38,12 +37,10 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link TypeUtils}. * Tests for {@link TypeUtils}.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Scott Frederick
*/ */
class TypeUtilsTests { class TypeUtilsTests {
@TempDir
File tempDir;
@Test @Test
void resolveTypeDescriptorOnConcreteClass() throws IOException { void resolveTypeDescriptorOnConcreteClass() throws IOException {
process(SimpleGenericProperties.class, (roundEnv, typeUtils) -> { process(SimpleGenericProperties.class, (roundEnv, typeUtils) -> {
@ -82,10 +79,12 @@ class TypeUtilsTests {
}); });
} }
private void process(Class<?> target, BiConsumer<RoundEnvironmentTester, TypeUtils> consumer) throws IOException { private void process(Class<?> target, BiConsumer<RoundEnvironmentTester, TypeUtils> consumer) {
TestableAnnotationProcessor<TypeUtils> processor = new TestableAnnotationProcessor<>(consumer, TypeUtils::new); TestableAnnotationProcessor<TypeUtils> processor = new TestableAnnotationProcessor<>(consumer, TypeUtils::new);
TestCompiler compiler = new TestCompiler(this.tempDir); TestCompiler compiler = TestCompiler.forSystem().withProcessors(processor)
compiler.getTask(target).call(processor); .withSources(SourceFile.forTestClass(target));
compiler.compile((compiled) -> {
});
} }
} }

@ -16,7 +16,6 @@
package org.springframework.boot.configurationprocessor.fieldvalues; package org.springframework.boot.configurationprocessor.fieldvalues;
import java.io.File;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -31,10 +30,10 @@ import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeElement;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.configurationsample.fieldvalues.FieldValues; import org.springframework.boot.configurationsample.fieldvalues.FieldValues;
import org.springframework.boot.testsupport.compiler.TestCompiler; import org.springframework.core.test.tools.SourceFile;
import org.springframework.core.test.tools.TestCompiler;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -46,16 +45,15 @@ import static org.assertj.core.api.Assertions.assertThat;
*/ */
public abstract class AbstractFieldValuesProcessorTests { public abstract class AbstractFieldValuesProcessorTests {
@TempDir
File tempDir;
protected abstract FieldValuesParser createProcessor(ProcessingEnvironment env); protected abstract FieldValuesParser createProcessor(ProcessingEnvironment env);
@Test @Test
void getFieldValues() throws Exception { void getFieldValues() throws Exception {
TestProcessor processor = new TestProcessor(); TestProcessor processor = new TestProcessor();
TestCompiler compiler = new TestCompiler(this.tempDir); TestCompiler compiler = TestCompiler.forSystem().withProcessors(processor)
compiler.getTask(FieldValues.class).call(processor); .withSources(SourceFile.forTestClass(FieldValues.class));
compiler.compile((compiled) -> {
});
Map<String, Object> values = processor.getValues(); Map<String, Object> values = processor.getValues();
assertThat(values.get("string")).isEqualTo("1"); assertThat(values.get("string")).isEqualTo("1");
assertThat(values.get("stringNone")).isNull(); assertThat(values.get("stringNone")).isNull();

@ -0,0 +1,53 @@
/*
* 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.configurationprocessor.test;
import java.io.InputStream;
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
import org.springframework.boot.configurationprocessor.metadata.JsonMarshaller;
import org.springframework.core.test.tools.Compiled;
import org.springframework.core.test.tools.TestCompiler;
/**
* Read the contents of metadata generated from the {@link TestCompiler}.
*
* @author Scott Frederick
*/
public final class CompiledMetadataReader {
private static final String METADATA_FILE = "META-INF/spring-configuration-metadata.json";
private CompiledMetadataReader() {
}
public static ConfigurationMetadata getMetadata(Compiled compiled) {
InputStream inputStream = compiled.getClassLoader().getResourceAsStream(METADATA_FILE);
try {
if (inputStream != null) {
return new JsonMarshaller().read(inputStream);
}
else {
return null;
}
}
catch (Exception ex) {
throw new RuntimeException("Failed to read metadata", ex);
}
}
}

@ -16,10 +16,6 @@
package org.springframework.boot.configurationprocessor.test; package org.springframework.boot.configurationprocessor.test;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@ -29,8 +25,6 @@ import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion; import javax.lang.model.SourceVersion;
import org.springframework.boot.configurationprocessor.ConfigurationMetadataAnnotationProcessor; import org.springframework.boot.configurationprocessor.ConfigurationMetadataAnnotationProcessor;
import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata;
import org.springframework.boot.configurationprocessor.metadata.JsonMarshaller;
/** /**
* Test {@link ConfigurationMetadataAnnotationProcessor}. * Test {@link ConfigurationMetadataAnnotationProcessor}.
@ -39,6 +33,7 @@ import org.springframework.boot.configurationprocessor.metadata.JsonMarshaller;
* @author Phillip Webb * @author Phillip Webb
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Kris De Volder * @author Kris De Volder
* @author Scott Frederick
*/ */
@SupportedAnnotationTypes({ TestConfigurationMetadataAnnotationProcessor.CONFIGURATION_PROPERTIES_ANNOTATION, @SupportedAnnotationTypes({ TestConfigurationMetadataAnnotationProcessor.CONFIGURATION_PROPERTIES_ANNOTATION,
TestConfigurationMetadataAnnotationProcessor.CONTROLLER_ENDPOINT_ANNOTATION, TestConfigurationMetadataAnnotationProcessor.CONTROLLER_ENDPOINT_ANNOTATION,
@ -79,12 +74,7 @@ public class TestConfigurationMetadataAnnotationProcessor extends ConfigurationM
public static final String NAME_ANNOTATION = "org.springframework.boot.configurationsample.Name"; public static final String NAME_ANNOTATION = "org.springframework.boot.configurationsample.Name";
private ConfigurationMetadata metadata; public TestConfigurationMetadataAnnotationProcessor() {
private final File outputLocation;
public TestConfigurationMetadataAnnotationProcessor(File outputLocation) {
this.outputLocation = outputLocation;
} }
@Override @Override
@ -133,28 +123,4 @@ public class TestConfigurationMetadataAnnotationProcessor extends ConfigurationM
return NAME_ANNOTATION; return NAME_ANNOTATION;
} }
@Override
protected ConfigurationMetadata writeMetadata() throws Exception {
super.writeMetadata();
try {
File metadataFile = new File(this.outputLocation, "META-INF/spring-configuration-metadata.json");
if (metadataFile.isFile()) {
try (InputStream input = new FileInputStream(metadataFile)) {
this.metadata = new JsonMarshaller().read(input);
}
}
else {
this.metadata = new ConfigurationMetadata();
}
return this.metadata;
}
catch (IOException ex) {
throw new RuntimeException("Failed to read metadata from disk", ex);
}
}
public ConfigurationMetadata getMetadata() {
return this.metadata;
}
} }

@ -15,6 +15,7 @@ dependencies {
testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.mockito:mockito-core") testImplementation("org.mockito:mockito-core")
testImplementation("org.springframework:spring-test") testImplementation("org.springframework:spring-test")
testImplementation("org.springframework:spring-core-test")
testRuntimeOnly("ch.qos.logback:logback-classic") testRuntimeOnly("ch.qos.logback:logback-classic")
testRuntimeOnly("org.bouncycastle:bcprov-jdk18on:1.71") testRuntimeOnly("org.bouncycastle:bcprov-jdk18on:1.71")

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -101,6 +101,7 @@ class JarLauncherTests extends AbstractExecutableArchiveLauncherTests {
} }
@Test @Test
@SuppressWarnings("removal")
void explodedJarDefinedPackagesIncludeManifestAttributes() throws Exception { void explodedJarDefinedPackagesIncludeManifestAttributes() throws Exception {
Manifest manifest = new Manifest(); Manifest manifest = new Manifest();
Attributes attributes = manifest.getMainAttributes(); Attributes attributes = manifest.getMainAttributes();

@ -36,6 +36,7 @@ dependencies {
implementation("org.hamcrest:hamcrest-library") implementation("org.hamcrest:hamcrest-library")
implementation("org.springframework:spring-core") implementation("org.springframework:spring-core")
implementation("org.springframework:spring-test") implementation("org.springframework:spring-test")
implementation("org.springframework:spring-core-test")
testImplementation("jakarta.servlet:jakarta.servlet-api") testImplementation("jakarta.servlet:jakarta.servlet-api")
testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("org.junit.jupiter:junit-jupiter")

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2022 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -37,7 +37,10 @@ import javax.tools.ToolProvider;
* @author Phillip Webb * @author Phillip Webb
* @author Andy Wilkinson * @author Andy Wilkinson
* @since 1.5.0 * @since 1.5.0
* @deprecated since 3.0.0 in favor of
* {@link org.springframework.core.test.tools.TestCompiler}
*/ */
@Deprecated(since = "3.0.0", forRemoval = true)
public class TestCompiler { public class TestCompiler {
/** /**

@ -102,6 +102,7 @@ dependencies {
optional("org.jetbrains.kotlin:kotlin-stdlib") optional("org.jetbrains.kotlin:kotlin-stdlib")
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support")) testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
testImplementation("org.springframework:spring-core-test")
testImplementation("com.ibm.db2:jcc") testImplementation("com.ibm.db2:jcc")
testImplementation("com.jayway.jsonpath:json-path") testImplementation("com.jayway.jsonpath:json-path")
testImplementation("com.microsoft.sqlserver:mssql-jdbc") testImplementation("com.microsoft.sqlserver:mssql-jdbc")

@ -16,36 +16,31 @@
package org.springframework.boot.context.properties.bind; package org.springframework.boot.context.properties.bind;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource; import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MockConfigurationPropertySource; import org.springframework.boot.context.properties.source.MockConfigurationPropertySource;
import org.springframework.boot.testsupport.compiler.TestCompiler;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.core.test.tools.SourceFile;
import org.springframework.core.test.tools.TestCompiler;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.junit.jupiter.api.Assertions.fail;
/** /**
* Tests for {@link ValueObjectBinder}. * Tests for {@link ValueObjectBinder}.
@ -368,26 +363,27 @@ class ValueObjectBinderTests {
} }
@Test @Test
void bindToRecordWithDefaultValue(@TempDir File tempDir) throws IOException, ClassNotFoundException { void bindToRecordWithDefaultValue() throws IOException {
MockConfigurationPropertySource source = new MockConfigurationPropertySource(); MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("test.record.property1", "value-from-config-1"); source.put("test.record.property1", "value-from-config-1");
this.sources.add(source); this.sources.add(source);
File recordProperties = new File(tempDir, "RecordProperties.java"); String recordProperties = """
try (PrintWriter writer = new PrintWriter(new FileWriter(recordProperties))) { public record RecordProperties(
writer.println("public record RecordProperties("); @org.springframework.boot.context.properties.bind.DefaultValue("default-value-1") String property1,
writer.println( @org.springframework.boot.context.properties.bind.DefaultValue("default-value-2") String property2) {
"@org.springframework.boot.context.properties.bind.DefaultValue(\"default-value-1\") String property1,"); }
writer.println( """;
"@org.springframework.boot.context.properties.bind.DefaultValue(\"default-value-2\") String property2"); TestCompiler.forSystem().withSources(SourceFile.of(recordProperties)).compile((compiled) -> {
writer.println(") {"); try {
writer.println("}"); ClassLoader cl = compiled.getClassLoader();
} Object bean = this.binder.bind("test.record", Class.forName("RecordProperties", true, cl)).get();
TestCompiler compiler = new TestCompiler(tempDir); assertThat(bean).hasFieldOrPropertyWithValue("property1", "value-from-config-1")
compiler.getTask(Arrays.asList(recordProperties)).call(); .hasFieldOrPropertyWithValue("property2", "default-value-2");
ClassLoader ucl = new URLClassLoader(new URL[] { tempDir.toURI().toURL() }); }
Object bean = this.binder.bind("test.record", Class.forName("RecordProperties", true, ucl)).get(); catch (ClassNotFoundException ex) {
assertThat(bean).hasFieldOrPropertyWithValue("property1", "value-from-config-1") fail("Expected generated class 'RecordProperties' not found", ex);
.hasFieldOrPropertyWithValue("property2", "default-value-2"); }
});
} }
private void noConfigurationProperty(BindException ex) { private void noConfigurationProperty(BindException ex) {

Loading…
Cancel
Save