diff --git a/spring-test/src/main/java/org/springframework/test/context/aot/AotTestAttributes.java b/spring-test/src/main/java/org/springframework/test/context/aot/AotTestAttributes.java
new file mode 100644
index 00000000000..0e2897497e8
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/aot/AotTestAttributes.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2002-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.test.context.aot;
+
+import org.springframework.aot.AotDetector;
+import org.springframework.lang.Nullable;
+
+/**
+ * Holder for metadata specific to ahead-of-time (AOT) support in the Spring
+ * TestContext Framework.
+ *
+ *
AOT test attributes are supported in two modes of operation: build-time
+ * and run-time. At build time, test components can {@linkplain #setAttribute contribute}
+ * attributes during the AOT processing phase. At run time, test components can
+ * {@linkplain #getString(String) retrieve} attributes that were contributed at
+ * build time. If {@link AotDetector#useGeneratedArtifacts()} returns {@code true},
+ * run-time mode applies.
+ *
+ *
For example, if a test component computes something at build time that
+ * cannot be computed at run time, the result of the build-time computation can
+ * be stored as an AOT attribute and retrieved at run time without repeating the
+ * computation.
+ *
+ *
An {@link AotContextLoader} would typically contribute an attribute in
+ * {@link AotContextLoader#loadContextForAotProcessing loadContextForAotProcessing()};
+ * whereas, an {@link AotTestExecutionListener} would typically contribute an attribute
+ * in {@link AotTestExecutionListener#processAheadOfTime processAheadOfTime()}.
+ * Any other test component — such as a
+ * {@link org.springframework.test.context.TestContextBootstrapper TestContextBootstrapper}
+ * — can choose to contribute an attribute at any point in time. Note that
+ * contributing an attribute during standard JVM test execution will not have any
+ * adverse side effect since AOT attributes will be ignored in that scenario. In
+ * any case, you should use {@link AotDetector#useGeneratedArtifacts()} to determine
+ * if invocations of {@link #setAttribute(String, String)} and
+ * {@link #removeAttribute(String)} are permitted.
+ *
+ * @author Sam Brannen
+ * @since 6.0
+ */
+public interface AotTestAttributes {
+
+ /**
+ * Get the current instance of {@code AotTestAttributes} to use.
+ *
See the class-level {@link AotTestAttributes Javadoc} for details on
+ * the two supported modes.
+ */
+ static AotTestAttributes getInstance() {
+ return new DefaultAotTestAttributes(AotTestAttributesFactory.getAttributes());
+ }
+
+
+ /**
+ * Set a {@code String} attribute for later retrieval during AOT run-time execution.
+ *
In general, users should take care to prevent overlaps with other
+ * metadata attributes by using fully-qualified names, perhaps using a
+ * class or package name as a prefix.
+ * @param name the unique attribute name
+ * @param value the associated attribute value
+ * @throws UnsupportedOperationException if invoked during
+ * {@linkplain AotDetector#useGeneratedArtifacts() AOT run-time execution}
+ * @throws IllegalArgumentException if the provided value is {@code null} or
+ * if an attempt is made to override an existing attribute
+ * @see #setAttribute(String, boolean)
+ * @see #removeAttribute(String)
+ * @see AotDetector#useGeneratedArtifacts()
+ */
+ void setAttribute(String name, String value);
+
+ /**
+ * Set a {@code boolean} attribute for later retrieval during AOT run-time execution.
+ *
In general, users should take care to prevent overlaps with other
+ * metadata attributes by using fully-qualified names, perhaps using a
+ * class or package name as a prefix.
+ * @param name the unique attribute name
+ * @param value the associated attribute value
+ * @throws UnsupportedOperationException if invoked during
+ * {@linkplain AotDetector#useGeneratedArtifacts() AOT run-time execution}
+ * @throws IllegalArgumentException if an attempt is made to override an
+ * existing attribute
+ * @see #setAttribute(String, String)
+ * @see #removeAttribute(String)
+ * @see Boolean#toString(boolean)
+ * @see AotDetector#useGeneratedArtifacts()
+ */
+ default void setAttribute(String name, boolean value) {
+ setAttribute(name, Boolean.toString(value));
+ }
+
+ /**
+ * Remove the attribute stored under the provided name.
+ * @param name the unique attribute name
+ * @throws UnsupportedOperationException if invoked during
+ * {@linkplain AotDetector#useGeneratedArtifacts() AOT run-time execution}
+ * @see AotDetector#useGeneratedArtifacts()
+ * @see #setAttribute(String, String)
+ */
+ void removeAttribute(String name);
+
+ /**
+ * Retrieve the attribute value for the given name as a {@link String}.
+ * @param name the unique attribute name
+ * @return the associated attribute value, or {@code null} if not found
+ * @see #getBoolean(String)
+ * @see #setAttribute(String, String)
+ */
+ @Nullable
+ String getString(String name);
+
+ /**
+ * Retrieve the attribute value for the given name as a {@code boolean}.
+ * @param name the unique attribute name
+ * @return {@code true} if the attribute is set to "true" (ignoring case),
+ * {@code} false otherwise
+ * @see #getString(String)
+ * @see #setAttribute(String, String)
+ * @see Boolean#parseBoolean(String)
+ */
+ default boolean getBoolean(String name) {
+ return Boolean.parseBoolean(getString(name));
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/aot/AotTestAttributesCodeGenerator.java b/spring-test/src/main/java/org/springframework/test/context/aot/AotTestAttributesCodeGenerator.java
new file mode 100644
index 00000000000..1feace365f8
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/aot/AotTestAttributesCodeGenerator.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2002-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.test.context.aot;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.lang.model.element.Modifier;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.aot.generate.GeneratedClass;
+import org.springframework.aot.generate.GeneratedClasses;
+import org.springframework.core.log.LogMessage;
+import org.springframework.javapoet.CodeBlock;
+import org.springframework.javapoet.MethodSpec;
+import org.springframework.javapoet.ParameterizedTypeName;
+import org.springframework.javapoet.TypeName;
+import org.springframework.javapoet.TypeSpec;
+
+/**
+ * Internal code generator for {@link AotTestAttributes}.
+ *
+ * @author Sam Brannen
+ * @since 6.0
+ */
+class AotTestAttributesCodeGenerator {
+
+ private static final Log logger = LogFactory.getLog(AotTestAttributesCodeGenerator.class);
+
+ // Map
+ private static final TypeName MAP_TYPE = ParameterizedTypeName.get(Map.class, String.class, String.class);
+
+ private static final String GENERATED_SUFFIX = "Generated";
+
+ static final String GENERATED_ATTRIBUTES_CLASS_NAME = AotTestAttributes.class.getName() + "__" + GENERATED_SUFFIX;
+
+ static final String GENERATED_ATTRIBUTES_METHOD_NAME = "getAttributes";
+
+
+ private final Map attributes;
+
+ private final GeneratedClass generatedClass;
+
+
+ AotTestAttributesCodeGenerator(Map attributes, GeneratedClasses generatedClasses) {
+ this.attributes = attributes;
+ this.generatedClass = generatedClasses.addForFeature(GENERATED_SUFFIX, this::generateType);
+ }
+
+
+ GeneratedClass getGeneratedClass() {
+ return this.generatedClass;
+ }
+
+ private void generateType(TypeSpec.Builder type) {
+ logger.debug(LogMessage.format("Generating AOT test attributes in %s",
+ this.generatedClass.getName().reflectionName()));
+ type.addJavadoc("Generated map for {@link $T}.", AotTestAttributes.class);
+ type.addModifiers(Modifier.PUBLIC);
+ type.addMethod(generateMethod());
+ }
+
+ private MethodSpec generateMethod() {
+ MethodSpec.Builder method = MethodSpec.methodBuilder(GENERATED_ATTRIBUTES_METHOD_NAME);
+ method.addModifiers(Modifier.PUBLIC, Modifier.STATIC);
+ method.returns(MAP_TYPE);
+ method.addCode(generateCode());
+ return method.build();
+ }
+
+ private CodeBlock generateCode() {
+ CodeBlock.Builder code = CodeBlock.builder();
+ code.addStatement("$T map = new $T<>()", MAP_TYPE, HashMap.class);
+ this.attributes.forEach((key, value) -> {
+ logger.trace(LogMessage.format("Storing AOT test attribute: %s = %s", key, value));
+ code.addStatement("map.put($S, $S)", key, value);
+ });
+ code.addStatement("return map");
+ return code.build();
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/aot/AotTestAttributesFactory.java b/spring-test/src/main/java/org/springframework/test/context/aot/AotTestAttributesFactory.java
new file mode 100644
index 00000000000..5f86413a3e6
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/aot/AotTestAttributesFactory.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2002-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.test.context.aot;
+
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.springframework.aot.AotDetector;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ReflectionUtils;
+
+/**
+ * Factory for {@link AotTestAttributes}.
+ *
+ * @author Sam Brannen
+ * @since 6.0
+ */
+final class AotTestAttributesFactory {
+
+ @Nullable
+ private static volatile Map attributes;
+
+
+ private AotTestAttributesFactory() {
+ }
+
+ /**
+ * Get the underlying attributes map.
+ * If the map is not already loaded, this method loads the map from the
+ * generated class when running in {@linkplain AotDetector#useGeneratedArtifacts()
+ * AOT execution mode} and otherwise creates a new map for storing attributes
+ * during the AOT processing phase.
+ */
+ static Map getAttributes() {
+ Map attrs = attributes;
+ if (attrs == null) {
+ synchronized (AotTestAttributesFactory.class) {
+ attrs = attributes;
+ if (attrs == null) {
+ attrs = (AotDetector.useGeneratedArtifacts() ? loadAttributesMap() : new ConcurrentHashMap<>());
+ attributes = attrs;
+ }
+ }
+ }
+ return attrs;
+ }
+
+ /**
+ * Reset AOT test attributes.
+ * Only for internal use.
+ */
+ static void reset() {
+ synchronized (AotTestAttributesFactory.class) {
+ attributes = null;
+ }
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ private static Map loadAttributesMap() {
+ String className = AotTestAttributesCodeGenerator.GENERATED_ATTRIBUTES_CLASS_NAME;
+ String methodName = AotTestAttributesCodeGenerator.GENERATED_ATTRIBUTES_METHOD_NAME;
+ try {
+ Class> clazz = ClassUtils.forName(className, null);
+ Method method = ReflectionUtils.findMethod(clazz, methodName);
+ Assert.state(method != null, () -> "No %s() method found in %s".formatted(methodName, clazz.getName()));
+ Map attributes = (Map) ReflectionUtils.invokeMethod(method, null);
+ return Collections.unmodifiableMap(attributes);
+ }
+ catch (IllegalStateException ex) {
+ throw ex;
+ }
+ catch (Exception ex) {
+ throw new IllegalStateException("Failed to invoke %s() method on %s".formatted(methodName, className), ex);
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/aot/DefaultAotTestAttributes.java b/spring-test/src/main/java/org/springframework/test/context/aot/DefaultAotTestAttributes.java
new file mode 100644
index 00000000000..2c5cee5c4dd
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/context/aot/DefaultAotTestAttributes.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2002-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.test.context.aot;
+
+import java.util.Map;
+
+import org.springframework.aot.AotDetector;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+
+/**
+ * Default implementation of {@link AotTestAttributes} backed by a {@link Map}.
+ *
+ * @author Sam Brannen
+ * @since 6.0
+ */
+class DefaultAotTestAttributes implements AotTestAttributes {
+
+ private final Map attributes;
+
+
+ DefaultAotTestAttributes(Map attributes) {
+ this.attributes = attributes;
+ }
+
+
+ @Override
+ public void setAttribute(String name, String value) {
+ assertNotInAotRuntime();
+ Assert.notNull(value, "'value' must not be null");
+ Assert.isTrue(!this.attributes.containsKey(name),
+ () -> "AOT attributes cannot be overridden. Name '%s' is already in use.".formatted(name));
+ this.attributes.put(name, value);
+ }
+
+ @Override
+ public void removeAttribute(String name) {
+ assertNotInAotRuntime();
+ this.attributes.remove(name);
+ }
+
+ @Override
+ @Nullable
+ public String getString(String name) {
+ return this.attributes.get(name);
+ }
+
+
+ private static void assertNotInAotRuntime() {
+ if (AotDetector.useGeneratedArtifacts()) {
+ throw new UnsupportedOperationException(
+ "AOT attributes cannot be modified during AOT run-time execution");
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java b/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java
index 4d0f9467750..185cd908a91 100644
--- a/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java
+++ b/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java
@@ -17,6 +17,7 @@
package org.springframework.test.context.aot;
import java.util.Collections;
+import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
@@ -108,10 +109,21 @@ public class TestContextAotGenerator {
* @throws TestContextAotException if an error occurs during AOT processing
*/
public void processAheadOfTime(Stream> testClasses) throws TestContextAotException {
- MultiValueMap> mergedConfigMappings = new LinkedMultiValueMap<>();
- testClasses.forEach(testClass -> mergedConfigMappings.add(buildMergedContextConfiguration(testClass), testClass));
- MultiValueMap> initializerClassMappings = processAheadOfTime(mergedConfigMappings);
- generateTestAotMappings(initializerClassMappings);
+ try {
+ // Make sure AOT attributes are cleared before processing
+ AotTestAttributesFactory.reset();
+
+ MultiValueMap> mergedConfigMappings = new LinkedMultiValueMap<>();
+ testClasses.forEach(testClass -> mergedConfigMappings.add(buildMergedContextConfiguration(testClass), testClass));
+ MultiValueMap> initializerClassMappings = processAheadOfTime(mergedConfigMappings);
+
+ generateTestAotMappings(initializerClassMappings);
+ generateAotTestAttributes();
+ }
+ finally {
+ // Clear AOT attributes after processing
+ AotTestAttributesFactory.reset();
+ }
}
private MultiValueMap> processAheadOfTime(MultiValueMap> mergedConfigMappings) {
@@ -240,6 +252,20 @@ public class TestContextAotGenerator {
registerPublicMethods(className);
}
+ private void generateAotTestAttributes() {
+ ClassNameGenerator classNameGenerator = new ClassNameGenerator(AotTestAttributes.class);
+ DefaultGenerationContext generationContext =
+ new DefaultGenerationContext(classNameGenerator, this.generatedFiles, this.runtimeHints);
+ GeneratedClasses generatedClasses = generationContext.getGeneratedClasses();
+
+ Map attributes = AotTestAttributesFactory.getAttributes();
+ AotTestAttributesCodeGenerator codeGenerator =
+ new AotTestAttributesCodeGenerator(attributes, generatedClasses);
+ generationContext.writeGeneratedContent();
+ String className = codeGenerator.getGeneratedClass().getName().reflectionName();
+ registerPublicMethods(className);
+ }
+
private void registerPublicMethods(String className) {
this.runtimeHints.reflection().registerType(TypeReference.of(className), INVOKE_PUBLIC_METHODS);
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/AbstractAotTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/AbstractAotTests.java
index ea98ff704ef..142142949e0 100644
--- a/spring-test/src/test/java/org/springframework/test/context/aot/AbstractAotTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/aot/AbstractAotTests.java
@@ -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",
diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorTests.java
index faec46178ce..8b7a33bd83b 100644
--- a/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorTests.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 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 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",
diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicSpringVintageTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicSpringVintageTests.java
index 13f298c6207..517fe0a7086 100644
--- a/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicSpringVintageTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/aot/samples/basic/BasicSpringVintageTests.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;
+ }
+
}
}