From 51daeac86b31960829e132d8c5a104a50aabbe07 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 2 Sep 2022 14:02:58 +0200 Subject: [PATCH] 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 --- .../context/aot/TestContextAotGenerator.java | 8 +++ .../aot/hint/TestContextRuntimeHints.java | 12 +--- .../aot/TestContextAotGeneratorTests.java | 69 ++++++++++++++++--- .../basic/BasicSpringVintageTests.java | 3 +- 4 files changed, 72 insertions(+), 20 deletions(-) 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 b4eaf1e756..b53cca2ff4 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 @@ -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); + } + } diff --git a/spring-test/src/main/java/org/springframework/test/context/aot/hint/TestContextRuntimeHints.java b/spring-test/src/main/java/org/springframework/test/context/aot/hint/TestContextRuntimeHints.java index 31ac273464..86c03df0cf 100644 --- a/spring-test/src/main/java/org/springframework/test/context/aot/hint/TestContextRuntimeHints.java +++ b/spring-test/src/main/java/org/springframework/test/context/aot/hint/TestContextRuntimeHints.java @@ -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 types) { reflectionHints.registerTypes(types, TypeHint.builtWith(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS)); } 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 b5567fe9b2..7ddf70477e 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 @@ -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 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" + }; + } 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 fe5244440c..709d20fddc 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 @@ -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 {