Introduce AotTestAttributes mechanism in the TestContext framework
For certain use cases it is beneficial to be able to compute something during AOT build-time processing and then retrieve the result of that computation during AOT run-time execution, without having to deal with code generation on your own. To support such use cases, this commit introduces an AotTestAttributes mechanism in the Spring TestContext Framework with the following feature set. - conceptually similar to org.springframework.core.AttributeAccessor in the sense that attributes are generic metadata - allows an AOT-aware test component to contribute a key-value pair during the AOT build-time processing phase, where the key is a String and the value is a String - provides convenience methods for storing and retrieving attributes as boolean values - generates the necessary source code during the AOT build-time processing phase in the TestContext framework to create a persistent map of the attributes - provides a mechanism for accessing the stored attributes during AOT run-time execution Closes gh-29100
This commit is contained in:
@@ -32,6 +32,7 @@ abstract class AbstractAotTests {
|
||||
static final String[] expectedSourceFilesForBasicSpringTests = {
|
||||
// Global
|
||||
"org/springframework/test/context/aot/TestAotMappings__Generated.java",
|
||||
"org/springframework/test/context/aot/AotTestAttributes__Generated.java",
|
||||
// BasicSpringJupiterSharedConfigTests
|
||||
"org/springframework/context/event/DefaultEventListenerFactory__TestContext001_BeanDefinitions.java",
|
||||
"org/springframework/context/event/EventListenerMethodProcessor__TestContext001_BeanDefinitions.java",
|
||||
|
||||
@@ -26,6 +26,7 @@ import javax.sql.DataSource;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.AotDetector;
|
||||
import org.springframework.aot.generate.DefaultGenerationContext;
|
||||
import org.springframework.aot.generate.GeneratedFiles.Kind;
|
||||
import org.springframework.aot.generate.InMemoryGeneratedFiles;
|
||||
@@ -60,6 +61,7 @@ import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import static java.util.Comparator.comparing;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.springframework.aot.hint.MemberCategory.INVOKE_DECLARED_CONSTRUCTORS;
|
||||
import static org.springframework.aot.hint.MemberCategory.INVOKE_DECLARED_METHODS;
|
||||
import static org.springframework.aot.hint.MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS;
|
||||
@@ -73,7 +75,7 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppC
|
||||
|
||||
/**
|
||||
* Tests for {@link TestContextAotGenerator}, {@link TestAotMappings},
|
||||
* {@link AotContextLoader}, and run-time hints.
|
||||
* {@link AotTestAttributes}, {@link AotContextLoader}, and run-time hints.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 6.0
|
||||
@@ -109,29 +111,50 @@ class TestContextAotGeneratorTests extends AbstractAotTests {
|
||||
assertThat(sourceFiles).containsExactlyInAnyOrder(expectedSourceFiles);
|
||||
|
||||
TestCompiler.forSystem().withFiles(generatedFiles).compile(ThrowingConsumer.of(compiled -> {
|
||||
TestAotMappings aotTestMappings = new TestAotMappings();
|
||||
for (Class<?> testClass : testClasses) {
|
||||
MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
|
||||
ApplicationContextInitializer<ConfigurableApplicationContext> contextInitializer =
|
||||
aotTestMappings.getContextInitializer(testClass);
|
||||
assertThat(contextInitializer).isNotNull();
|
||||
ApplicationContext context = ((AotContextLoader) mergedConfig.getContextLoader())
|
||||
.loadContextForAotRuntime(mergedConfig, contextInitializer);
|
||||
if (context instanceof WebApplicationContext wac) {
|
||||
assertContextForWebTests(wac);
|
||||
}
|
||||
else if (testClass.getPackageName().contains("jdbc")) {
|
||||
assertContextForJdbcTests(context);
|
||||
}
|
||||
else {
|
||||
assertContextForBasicTests(context);
|
||||
try {
|
||||
System.setProperty(AotDetector.AOT_ENABLED, "true");
|
||||
AotTestAttributesFactory.reset();
|
||||
|
||||
AotTestAttributes aotAttributes = AotTestAttributes.getInstance();
|
||||
assertThatExceptionOfType(UnsupportedOperationException.class)
|
||||
.isThrownBy(() -> aotAttributes.setAttribute("foo", "bar"))
|
||||
.withMessage("AOT attributes cannot be modified during AOT run-time execution");
|
||||
String key = "@SpringBootConfiguration-" + BasicSpringVintageTests.class.getName();
|
||||
assertThat(aotAttributes.getString(key)).isEqualTo("org.example.Main");
|
||||
assertThat(aotAttributes.getBoolean(key + "-active1")).isTrue();
|
||||
assertThat(aotAttributes.getBoolean(key + "-active2")).isTrue();
|
||||
assertThat(aotAttributes.getString("bogus")).isNull();
|
||||
assertThat(aotAttributes.getBoolean("bogus")).isFalse();
|
||||
|
||||
TestAotMappings testAotMappings = new TestAotMappings();
|
||||
for (Class<?> testClass : testClasses) {
|
||||
MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
|
||||
ApplicationContextInitializer<ConfigurableApplicationContext> contextInitializer =
|
||||
testAotMappings.getContextInitializer(testClass);
|
||||
assertThat(contextInitializer).isNotNull();
|
||||
ApplicationContext context = ((AotContextLoader) mergedConfig.getContextLoader())
|
||||
.loadContextForAotRuntime(mergedConfig, contextInitializer);
|
||||
if (context instanceof WebApplicationContext wac) {
|
||||
assertContextForWebTests(wac);
|
||||
}
|
||||
else if (testClass.getPackageName().contains("jdbc")) {
|
||||
assertContextForJdbcTests(context);
|
||||
}
|
||||
else {
|
||||
assertContextForBasicTests(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
System.clearProperty(AotDetector.AOT_ENABLED);
|
||||
AotTestAttributesFactory.reset();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private static void assertRuntimeHints(RuntimeHints runtimeHints) {
|
||||
assertReflectionRegistered(runtimeHints, TestAotMappings.GENERATED_MAPPINGS_CLASS_NAME, INVOKE_PUBLIC_METHODS);
|
||||
assertReflectionRegistered(runtimeHints, AotTestAttributesCodeGenerator.GENERATED_ATTRIBUTES_CLASS_NAME, INVOKE_PUBLIC_METHODS);
|
||||
|
||||
Stream.of(
|
||||
org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.class,
|
||||
@@ -334,6 +357,7 @@ class TestContextAotGeneratorTests extends AbstractAotTests {
|
||||
private static final String[] expectedSourceFiles = {
|
||||
// Global
|
||||
"org/springframework/test/context/aot/TestAotMappings__Generated.java",
|
||||
"org/springframework/test/context/aot/AotTestAttributes__Generated.java",
|
||||
// BasicSpringJupiterSharedConfigTests
|
||||
"org/springframework/context/event/DefaultEventListenerFactory__TestContext001_BeanDefinitions.java",
|
||||
"org/springframework/context/event/EventListenerMethodProcessor__TestContext001_BeanDefinitions.java",
|
||||
|
||||
@@ -18,13 +18,16 @@ package org.springframework.test.context.aot.samples.basic;
|
||||
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.aot.AotDetector;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.test.context.BootstrapWith;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.ContextLoader;
|
||||
import org.springframework.test.context.MergedContextConfiguration;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.aot.AotTestAttributes;
|
||||
import org.springframework.test.context.aot.samples.basic.BasicSpringVintageTests.CustomXmlBootstrapper;
|
||||
import org.springframework.test.context.aot.samples.common.MessageService;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
@@ -63,10 +66,38 @@ public class BasicSpringVintageTests {
|
||||
}
|
||||
|
||||
public static class CustomXmlBootstrapper extends DefaultTestContextBootstrapper {
|
||||
|
||||
@Override
|
||||
protected Class<? extends ContextLoader> getDefaultContextLoaderClass(Class<?> testClass) {
|
||||
return GenericXmlContextLoader.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MergedContextConfiguration processMergedContextConfiguration(MergedContextConfiguration mergedConfig) {
|
||||
String stringKey = "@SpringBootConfiguration-" + mergedConfig.getTestClass().getName();
|
||||
String booleanKey1 = "@SpringBootConfiguration-" + mergedConfig.getTestClass().getName() + "-active1";
|
||||
String booleanKey2 = "@SpringBootConfiguration-" + mergedConfig.getTestClass().getName() + "-active2";
|
||||
AotTestAttributes aotAttributes = AotTestAttributes.getInstance();
|
||||
if (AotDetector.useGeneratedArtifacts()) {
|
||||
assertThat(aotAttributes.getString(stringKey))
|
||||
.as("AOT String attribute must already be present during AOT run-time execution")
|
||||
.isEqualTo("org.example.Main");
|
||||
assertThat(aotAttributes.getBoolean(booleanKey1))
|
||||
.as("AOT boolean attribute 1 must already be present during AOT run-time execution")
|
||||
.isTrue();
|
||||
assertThat(aotAttributes.getBoolean(booleanKey2))
|
||||
.as("AOT boolean attribute 2 must already be present during AOT run-time execution")
|
||||
.isTrue();
|
||||
}
|
||||
else {
|
||||
// Set AOT attributes during AOT build-time processing
|
||||
aotAttributes.setAttribute(stringKey, "org.example.Main");
|
||||
aotAttributes.setAttribute(booleanKey1, "TrUe");
|
||||
aotAttributes.setAttribute(booleanKey2, true);
|
||||
}
|
||||
return mergedConfig;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user