Register runtime hints for ContextLoaders automatically in the TCF

This commit moves the registration of runtime hints for ContextLoaders
from TestContextRuntimeHints to TestContextAotGenerator.

This allows for automatic registration of any ContextLoader used,
including custom loaders declared via the `loader` attribute in
@ContextConfiguration.

See gh-29021
This commit is contained in:
Sam Brannen
2022-09-02 14:02:58 +02:00
parent 41b6f4e351
commit 51daeac86b
4 changed files with 72 additions and 20 deletions

View File

@@ -28,6 +28,7 @@ import org.springframework.aot.generate.GeneratedClasses;
import org.springframework.aot.generate.GeneratedFiles;
import org.springframework.aot.generate.GenerationContext;
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.context.ApplicationContext;
@@ -173,6 +174,8 @@ public class TestContextAotGenerator {
Consider annotating test class [%s] with @ContextConfiguration or \
@ContextHierarchy.""".formatted(testClass.getName()));
registerDeclaredConstructors(contextLoader.getClass());
if (contextLoader instanceof AotContextLoader aotContextLoader) {
try {
ApplicationContext context = aotContextLoader.loadContextForAotProcessing(mergedConfig);
@@ -224,4 +227,9 @@ public class TestContextAotGenerator {
.registerType(TypeReference.of(className), MemberCategory.INVOKE_PUBLIC_METHODS);
}
private void registerDeclaredConstructors(Class<?> type) {
ReflectionHints reflectionHints = this.runtimeHints.reflection();
reflectionHints.registerType(type, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
}
}

View File

@@ -48,9 +48,7 @@ class TestContextRuntimeHints implements RuntimeHintsRegistrar {
// Loaded reflectively in BootstrapUtils
org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.class,
// Loaded reflectively in BootstrapUtils
org.springframework.test.context.support.DefaultBootstrapContext.class,
// Loaded reflectively in AbstractTestContextBootstrapper#resolveContextLoader()
org.springframework.test.context.support.DelegatingSmartContextLoader.class
org.springframework.test.context.support.DefaultBootstrapContext.class
);
registerDeclaredConstructors(reflectionHints,
@@ -59,10 +57,6 @@ class TestContextRuntimeHints implements RuntimeHintsRegistrar {
);
if (servletPresent) {
registerPublicConstructors(reflectionHints,
// Loaded reflectively in AbstractTestContextBootstrapper#resolveContextLoader()
"org.springframework.test.context.web.WebDelegatingSmartContextLoader"
);
registerDeclaredConstructors(reflectionHints,
// Loaded reflectively in BootstrapUtils
"org.springframework.test.context.web.WebTestContextBootstrapper"
@@ -92,10 +86,6 @@ class TestContextRuntimeHints implements RuntimeHintsRegistrar {
registerPublicConstructors(reflectionHints, TypeReference.listOf(types));
}
private static void registerPublicConstructors(ReflectionHints reflectionHints, String... classNames) {
registerPublicConstructors(reflectionHints, listOf(classNames));
}
private static void registerPublicConstructors(ReflectionHints reflectionHints, Iterable<TypeReference> types) {
reflectionHints.registerTypes(types, TypeHint.builtWith(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS));
}

View File

@@ -82,7 +82,8 @@ class TestContextAotGeneratorTests extends AbstractAotTests {
BasicSpringJupiterTests.class,
BasicSpringJupiterTests.NestedTests.class,
BasicSpringTestNGTests.class,
BasicSpringVintageTests.class);
BasicSpringVintageTests.class,
WebSpringJupiterTests.class);
InMemoryGeneratedFiles generatedFiles = new InMemoryGeneratedFiles();
TestContextAotGenerator generator = new TestContextAotGenerator(generatedFiles);
@@ -92,7 +93,7 @@ class TestContextAotGeneratorTests extends AbstractAotTests {
assertRuntimeHints(generator.getRuntimeHints());
List<String> sourceFiles = generatedFiles.getGeneratedFiles(Kind.SOURCE).keySet().stream().toList();
assertThat(sourceFiles).containsExactlyInAnyOrder(expectedSourceFilesForBasicSpringTests);
assertThat(sourceFiles).containsExactlyInAnyOrder(expectedSourceFiles);
TestCompiler.forSystem().withFiles(generatedFiles).compile(ThrowingConsumer.of(compiled -> {
AotTestMappings aotTestMappings = new AotTestMappings();
@@ -103,7 +104,12 @@ class TestContextAotGeneratorTests extends AbstractAotTests {
assertThat(contextInitializer).isNotNull();
ApplicationContext context = ((AotContextLoader) mergedConfig.getContextLoader())
.loadContextForAotRuntime(mergedConfig, contextInitializer);
assertContextForBasicTests(context);
if (context instanceof WebApplicationContext wac) {
assertContextForBasicWebTests(wac);
}
else {
assertContextForBasicTests(context);
}
}
}));
}
@@ -113,16 +119,17 @@ class TestContextAotGeneratorTests extends AbstractAotTests {
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
org.springframework.test.context.support.DefaultBootstrapContext.class
).forEach(type -> assertReflectionRegistered(runtimeHints, type, INVOKE_PUBLIC_CONSTRUCTORS));
Set.of(
org.springframework.test.context.support.AnnotationConfigContextLoader.class,
org.springframework.test.context.support.DefaultTestContextBootstrapper.class,
org.springframework.test.context.web.WebTestContextBootstrapper.class,
org.springframework.test.context.support.DelegatingSmartContextLoader.class,
org.springframework.test.context.support.GenericGroovyXmlContextLoader.class,
org.springframework.test.context.web.GenericGroovyXmlWebContextLoader.class
org.springframework.test.context.web.GenericGroovyXmlWebContextLoader.class,
org.springframework.test.context.web.WebDelegatingSmartContextLoader.class,
org.springframework.test.context.web.WebTestContextBootstrapper.class
).forEach(type -> assertReflectionRegistered(runtimeHints, type, INVOKE_DECLARED_CONSTRUCTORS));
Set.of(
@@ -186,6 +193,13 @@ class TestContextAotGeneratorTests extends AbstractAotTests {
assertThat(messageService.generateMessage()).isEqualTo("Hello, AOT!");
}
private void assertContextForBasicWebTests(WebApplicationContext wac) throws Exception {
assertThat(wac.getEnvironment().getProperty("test.engine")).as("Environment").isNotNull();
MockMvc mockMvc = webAppContextSetup(wac).build();
mockMvc.perform(get("/hello")).andExpectAll(status().isOk(), content().string("Hello, AOT!"));
}
@Test
void processAheadOfTimeWithXmlTests() {
// We cannot parameterize with the test classes, since @CompileWithTargetClassAccess
@@ -258,4 +272,43 @@ class TestContextAotGeneratorTests extends AbstractAotTests {
record Mapping(MergedContextConfiguration mergedConfig, ClassName className) {
}
private static final String[] expectedSourceFiles = {
// Global
"org/springframework/test/context/aot/AotTestMappings__Generated.java",
// BasicSpringJupiterSharedConfigTests
"org/springframework/context/event/DefaultEventListenerFactory__TestContext001_BeanDefinitions.java",
"org/springframework/context/event/EventListenerMethodProcessor__TestContext001_BeanDefinitions.java",
"org/springframework/test/context/aot/samples/basic/BasicSpringJupiterSharedConfigTests__TestContext001_ApplicationContextInitializer.java",
"org/springframework/test/context/aot/samples/basic/BasicSpringJupiterSharedConfigTests__TestContext001_BeanFactoryRegistrations.java",
"org/springframework/test/context/aot/samples/basic/BasicTestConfiguration__TestContext001_BeanDefinitions.java",
// BasicSpringJupiterTests -- not generated b/c already generated for BasicSpringJupiterSharedConfigTests.
// BasicSpringJupiterTests.NestedTests
"org/springframework/context/event/DefaultEventListenerFactory__TestContext002_BeanDefinitions.java",
"org/springframework/context/event/EventListenerMethodProcessor__TestContext002_BeanDefinitions.java",
"org/springframework/test/context/aot/samples/basic/BasicSpringJupiterTests_NestedTests__TestContext002_ApplicationContextInitializer.java",
"org/springframework/test/context/aot/samples/basic/BasicSpringJupiterTests_NestedTests__TestContext002_BeanFactoryRegistrations.java",
"org/springframework/test/context/aot/samples/basic/BasicTestConfiguration__TestContext002_BeanDefinitions.java",
// BasicSpringTestNGTests
"org/springframework/context/event/DefaultEventListenerFactory__TestContext003_BeanDefinitions.java",
"org/springframework/context/event/EventListenerMethodProcessor__TestContext003_BeanDefinitions.java",
"org/springframework/test/context/aot/samples/basic/BasicSpringTestNGTests__TestContext003_ApplicationContextInitializer.java",
"org/springframework/test/context/aot/samples/basic/BasicSpringTestNGTests__TestContext003_BeanFactoryRegistrations.java",
"org/springframework/test/context/aot/samples/basic/BasicTestConfiguration__TestContext003_BeanDefinitions.java",
// BasicSpringVintageTests
"org/springframework/context/event/DefaultEventListenerFactory__TestContext004_BeanDefinitions.java",
"org/springframework/context/event/EventListenerMethodProcessor__TestContext004_BeanDefinitions.java",
"org/springframework/test/context/aot/samples/basic/BasicSpringVintageTests__TestContext004_ApplicationContextInitializer.java",
"org/springframework/test/context/aot/samples/basic/BasicSpringVintageTests__TestContext004_BeanFactoryRegistrations.java",
"org/springframework/test/context/aot/samples/basic/BasicTestConfiguration__TestContext004_BeanDefinitions.java",
// WebSpringJupiterTests
"org/springframework/context/event/DefaultEventListenerFactory__TestContext005_BeanDefinitions.java",
"org/springframework/context/event/EventListenerMethodProcessor__TestContext005_BeanDefinitions.java",
"org/springframework/test/context/aot/samples/web/WebSpringJupiterTests__TestContext005_ApplicationContextInitializer.java",
"org/springframework/test/context/aot/samples/web/WebSpringJupiterTests__TestContext005_BeanFactoryRegistrations.java",
"org/springframework/test/context/aot/samples/web/WebTestConfiguration__TestContext005_BeanDefinitions.java",
"org/springframework/web/reactive/config/DelegatingWebFluxConfiguration__TestContext005_Autowiring.java",
"org/springframework/web/reactive/config/DelegatingWebFluxConfiguration__TestContext005_BeanDefinitions.java",
"org/springframework/web/reactive/config/WebFluxConfigurationSupport__TestContext005_BeanDefinitions.java"
};
}

View File

@@ -25,6 +25,7 @@ import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.aot.samples.common.MessageService;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import static org.assertj.core.api.Assertions.assertThat;
@@ -33,7 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* @since 6.0
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = BasicTestConfiguration.class)
@ContextConfiguration(classes = BasicTestConfiguration.class, loader = AnnotationConfigContextLoader.class)
@TestPropertySource(properties = "test.engine = vintage")
public class BasicSpringVintageTests {