Register runtime hints for TestContext framework classes and annotations
This commit introduces TestContextRuntimeHints which is a RuntimeHintsRegistrar implementation that makes core types and annotations from the Spring TestContext Framework available at runtime within a GraalVM native image. TestContextRuntimeHints is registered automatically via the "META-INF/spring/aot.factories" file in spring-test. This commit also modifies TestContextAotGeneratorTests to assert the expected runtime hints registered by TestContextRuntimeHints as well as runtime hints for TestExecutionListener and ContextCustomizerFactory implementations registered by SpringFactoriesLoaderRuntimeHints. Closes gh-29028 Closes gh-29044
This commit is contained in:
@@ -16,23 +16,27 @@
|
||||
|
||||
package org.springframework.test.context.aot;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.generate.DefaultGenerationContext;
|
||||
import org.springframework.aot.generate.GeneratedFiles.Kind;
|
||||
import org.springframework.aot.generate.InMemoryGeneratedFiles;
|
||||
import org.springframework.aot.hint.JdkProxyHint;
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.ReflectionHints;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
import org.springframework.aot.test.generator.compile.CompileWithTargetClassAccess;
|
||||
import org.springframework.aot.test.generator.compile.TestCompiler;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.annotation.SynthesizedAnnotation;
|
||||
import org.springframework.javapoet.ClassName;
|
||||
import org.springframework.test.context.MergedContextConfiguration;
|
||||
import org.springframework.test.context.aot.samples.basic.BasicSpringJupiterSharedConfigTests;
|
||||
@@ -52,6 +56,11 @@ import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import static java.util.Comparator.comparing;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
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;
|
||||
import static org.springframework.aot.hint.MemberCategory.INVOKE_PUBLIC_METHODS;
|
||||
import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.reflection;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
@@ -83,10 +92,7 @@ class TestContextAotGeneratorTests extends AbstractAotTests {
|
||||
|
||||
generator.processAheadOfTime(testClasses.stream().sorted(comparing(Class::getName)));
|
||||
|
||||
ReflectionHints reflectionHints = generator.getRuntimeHints().reflection();
|
||||
assertThat(reflectionHints.getTypeHint(TypeReference.of(AotTestMappings.GENERATED_MAPPINGS_CLASS_NAME)))
|
||||
.satisfies(typeHint ->
|
||||
assertThat(typeHint.getMemberCategories()).containsExactly(MemberCategory.INVOKE_PUBLIC_METHODS));
|
||||
assertRuntimeHints(generator.getRuntimeHints());
|
||||
|
||||
List<String> sourceFiles = generatedFiles.getGeneratedFiles(Kind.SOURCE).keySet().stream().toList();
|
||||
assertThat(sourceFiles).containsExactlyInAnyOrder(expectedSourceFilesForBasicSpringTests);
|
||||
@@ -105,6 +111,101 @@ class TestContextAotGeneratorTests extends AbstractAotTests {
|
||||
}));
|
||||
}
|
||||
|
||||
private static void assertRuntimeHints(RuntimeHints runtimeHints) {
|
||||
assertReflectionRegistered(runtimeHints, AotTestMappings.GENERATED_MAPPINGS_CLASS_NAME, INVOKE_PUBLIC_METHODS);
|
||||
|
||||
Set.of(
|
||||
org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.class,
|
||||
org.springframework.test.context.support.DefaultBootstrapContext.class,
|
||||
org.springframework.test.context.support.DelegatingSmartContextLoader.class,
|
||||
org.springframework.test.context.web.WebDelegatingSmartContextLoader.class
|
||||
).forEach(type -> assertReflectionRegistered(runtimeHints, type, INVOKE_PUBLIC_CONSTRUCTORS));
|
||||
|
||||
Set.of(
|
||||
org.springframework.test.context.support.DefaultTestContextBootstrapper.class,
|
||||
org.springframework.test.context.web.WebTestContextBootstrapper.class,
|
||||
org.springframework.test.context.support.GenericGroovyXmlContextLoader.class,
|
||||
org.springframework.test.context.web.GenericGroovyXmlWebContextLoader.class
|
||||
).forEach(type -> assertReflectionRegistered(runtimeHints, type, INVOKE_DECLARED_CONSTRUCTORS));
|
||||
|
||||
Set.of(
|
||||
// Legacy and JUnit 4
|
||||
org.springframework.test.annotation.Commit.class,
|
||||
org.springframework.test.annotation.DirtiesContext.class,
|
||||
org.springframework.test.annotation.IfProfileValue.class,
|
||||
org.springframework.test.annotation.ProfileValueSourceConfiguration.class,
|
||||
org.springframework.test.annotation.Repeat.class,
|
||||
org.springframework.test.annotation.Rollback.class,
|
||||
org.springframework.test.annotation.Timed.class,
|
||||
|
||||
// Core TestContext framework
|
||||
org.springframework.test.context.ActiveProfiles.class,
|
||||
org.springframework.test.context.BootstrapWith.class,
|
||||
org.springframework.test.context.ContextConfiguration.class,
|
||||
org.springframework.test.context.ContextHierarchy.class,
|
||||
org.springframework.test.context.DynamicPropertySource.class,
|
||||
org.springframework.test.context.NestedTestConfiguration.class,
|
||||
org.springframework.test.context.TestConstructor.class,
|
||||
org.springframework.test.context.TestExecutionListeners.class,
|
||||
org.springframework.test.context.TestPropertySource.class,
|
||||
org.springframework.test.context.TestPropertySources.class,
|
||||
|
||||
// Application Events
|
||||
org.springframework.test.context.event.RecordApplicationEvents.class,
|
||||
|
||||
// JUnit Jupiter
|
||||
org.springframework.test.context.junit.jupiter.EnabledIf.class,
|
||||
org.springframework.test.context.junit.jupiter.DisabledIf.class,
|
||||
org.springframework.test.context.junit.jupiter.SpringJUnitConfig.class,
|
||||
org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig.class,
|
||||
|
||||
// Web
|
||||
org.springframework.test.context.web.WebAppConfiguration.class
|
||||
).forEach(type -> assertAnnotationRegistered(runtimeHints, type));
|
||||
|
||||
// TestExecutionListener
|
||||
Set.of(
|
||||
org.springframework.test.context.event.ApplicationEventsTestExecutionListener.class,
|
||||
org.springframework.test.context.event.EventPublishingTestExecutionListener.class,
|
||||
org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener.class,
|
||||
org.springframework.test.context.support.DependencyInjectionTestExecutionListener.class,
|
||||
org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener.class,
|
||||
org.springframework.test.context.support.DirtiesContextTestExecutionListener.class,
|
||||
org.springframework.test.context.transaction.TransactionalTestExecutionListener.class,
|
||||
org.springframework.test.context.web.ServletTestExecutionListener.class
|
||||
).forEach(type -> assertReflectionRegistered(runtimeHints, type, INVOKE_DECLARED_CONSTRUCTORS));
|
||||
|
||||
// ContextCustomizerFactory
|
||||
Set.of(
|
||||
"org.springframework.test.context.support.DynamicPropertiesContextCustomizerFactory",
|
||||
"org.springframework.test.context.web.socket.MockServerContainerContextCustomizerFactory"
|
||||
).forEach(type -> assertReflectionRegistered(runtimeHints, type, INVOKE_DECLARED_CONSTRUCTORS));
|
||||
}
|
||||
|
||||
private static void assertReflectionRegistered(RuntimeHints runtimeHints, String type, MemberCategory memberCategory) {
|
||||
assertThat(reflection().onType(TypeReference.of(type)).withMemberCategory(memberCategory))
|
||||
.as("Reflection hint for %s with category %s", type, memberCategory)
|
||||
.accepts(runtimeHints);
|
||||
}
|
||||
|
||||
private static void assertReflectionRegistered(RuntimeHints runtimeHints, Class<?> type, MemberCategory memberCategory) {
|
||||
assertThat(reflection().onType(type).withMemberCategory(memberCategory))
|
||||
.as("Reflection hint for %s with category %s", type.getSimpleName(), memberCategory)
|
||||
.accepts(runtimeHints);
|
||||
}
|
||||
|
||||
private static void assertAnnotationRegistered(RuntimeHints runtimeHints, Class<? extends Annotation> annotationType) {
|
||||
assertReflectionRegistered(runtimeHints, annotationType, INVOKE_DECLARED_METHODS);
|
||||
assertThat(runtimeHints.proxies().jdkProxies())
|
||||
.as("Proxy hint for annotation @%s", annotationType.getSimpleName())
|
||||
.anySatisfy(annotationProxy(annotationType));
|
||||
}
|
||||
|
||||
private static Consumer<JdkProxyHint> annotationProxy(Class<? extends Annotation> type) {
|
||||
return jdkProxyHint -> assertThat(jdkProxyHint.getProxiedInterfaces())
|
||||
.containsExactly(TypeReference.of(type), TypeReference.of(SynthesizedAnnotation.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void processAheadOfTimeWithBasicTests() {
|
||||
// We cannot parameterize with the test classes, since @CompileWithTargetClassAccess
|
||||
|
||||
Reference in New Issue
Block a user