From ada0880f3c65497f96b4250ae1e051459ccb23a7 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Fri, 19 Aug 2022 14:21:57 +0200 Subject: [PATCH] Introduce AotTestMappings and AotTestMappingsCodeGenerator TestContextAotGenerator now uses AotTestMappingsCodeGenerator to generate a AotTestMappings__Generated.java class which is loaded in AotTestMappings via reflection in order to retrieve access to ApplicationContextIntializers generated during AOT processing. Furthermore, the processAheadOfTimeAndGenerateAotTestMappings() method in TestContextAotGeneratorTests now performs a rather extensive test including: - emulating TestClassScanner to find test classes - processing all test classes and generating ApplicationContextIntializers - generating mappings for AotTestMappings - compiling all generated code - loading AotTestMappings - using AotTestMappings to instantiate the generated ApplicationContextIntializer - using the AotContextLoader API to load the AOT-optimized ApplicationContext - asserting the behavior of the loaded ApplicationContext See gh-28205 Closes gh-28204 --- .../test/context/aot/AotTestMappings.java | 113 ++++++++++++++++++ .../aot/AotTestMappingsCodeGenerator.java | 113 ++++++++++++++++++ .../context/aot/TestContextAotGenerator.java | 65 ++++++---- .../test/context/aot/AbstractAotTests.java | 2 + .../test/context/aot/AotSmokeTests.java | 8 +- .../aot/TestContextAotGeneratorTests.java | 60 ++++++---- 6 files changed, 315 insertions(+), 46 deletions(-) create mode 100644 spring-test/src/main/java/org/springframework/test/context/aot/AotTestMappings.java create mode 100644 spring-test/src/main/java/org/springframework/test/context/aot/AotTestMappingsCodeGenerator.java diff --git a/spring-test/src/main/java/org/springframework/test/context/aot/AotTestMappings.java b/spring-test/src/main/java/org/springframework/test/context/aot/AotTestMappings.java new file mode 100644 index 000000000000..b07ad9d279ea --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/aot/AotTestMappings.java @@ -0,0 +1,113 @@ +/* + * 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.Map; +import java.util.function.Supplier; + +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +/** + * {@code AotTestMappings} provides mappings from test classes to AOT-optimized + * context initializers. + * + *

If a test class is not {@linkplain #isSupportedTestClass(Class) supported} in + * AOT mode, {@link #getContextInitializer(Class)} will return {@code null}. + * + *

Reflectively accesses {@link #GENERATED_MAPPINGS_CLASS_NAME} generated by + * the {@link TestContextAotGenerator} to retrieve the mappings generated during + * AOT processing. + * + * @author Sam Brannen + * @author Stephane Nicoll + * @since 6.0 + */ +public class AotTestMappings { + + // TODO Add support in ClassNameGenerator for supplying a predefined class name. + // There is a similar issue in Spring Boot where code relies on a generated name. + // Ideally we would generate a class named: org.springframework.test.context.aot.GeneratedAotTestMappings + static final String GENERATED_MAPPINGS_CLASS_NAME = AotTestMappings.class.getName() + "__Generated"; + + static final String GENERATED_MAPPINGS_METHOD_NAME = "getContextInitializers"; + + private final Map>> contextInitializers; + + + public AotTestMappings() { + this(GENERATED_MAPPINGS_CLASS_NAME); + } + + AotTestMappings(String initializerClassName) { + this(loadContextInitializersMap(initializerClassName)); + } + + AotTestMappings(Map>> contextInitializers) { + this.contextInitializers = contextInitializers; + } + + + /** + * Determine if the specified test class has an AOT-optimized application context + * initializer. + *

If this method returns {@code true}, {@link #getContextInitializer(Class)} + * should not return {@code null}. + */ + public boolean isSupportedTestClass(Class testClass) { + return this.contextInitializers.containsKey(testClass.getName()); + } + + /** + * Get the AOT {@link ApplicationContextInitializer} for the specified test class. + * @return the AOT context initializer, or {@code null} if there is no AOT context + * initializer for the specified test class + * @see #isSupportedTestClass(Class) + */ + public ApplicationContextInitializer getContextInitializer(Class testClass) { + Supplier> supplier = + this.contextInitializers.get(testClass.getName()); + return (supplier != null ? supplier.get() : null); + } + + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private static Map>> + loadContextInitializersMap(String className) { + + String methodName = GENERATED_MAPPINGS_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())); + return (Map>>) + ReflectionUtils.invokeMethod(method, null); + } + catch (IllegalStateException ex) { + throw ex; + } + catch (Exception ex) { + throw new IllegalStateException("Failed to invoke %s() method in %s".formatted(methodName, className), ex); + } + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/context/aot/AotTestMappingsCodeGenerator.java b/spring-test/src/main/java/org/springframework/test/context/aot/AotTestMappingsCodeGenerator.java new file mode 100644 index 000000000000..03997f82c2af --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/aot/AotTestMappingsCodeGenerator.java @@ -0,0 +1,113 @@ +/* + * 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.List; +import java.util.Map; +import java.util.function.Supplier; + +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.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.log.LogMessage; +import org.springframework.javapoet.ClassName; +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; +import org.springframework.javapoet.WildcardTypeName; +import org.springframework.util.MultiValueMap; + +/** + * Internal code generator for mappings used by {@link AotTestMappings}. + * + * @author Sam Brannen + * @since 6.0 + */ +class AotTestMappingsCodeGenerator { + + private static final Log logger = LogFactory.getLog(AotTestMappingsCodeGenerator.class); + + private static final ParameterizedTypeName CONTEXT_INITIALIZER = ParameterizedTypeName.get( + ClassName.get(ApplicationContextInitializer.class), + WildcardTypeName.subtypeOf(ConfigurableApplicationContext.class)); + + private static final ParameterizedTypeName CONTEXT_INITIALIZER_SUPPLIER = ParameterizedTypeName + .get(ClassName.get(Supplier.class), CONTEXT_INITIALIZER); + + // Map>> + private static final TypeName CONTEXT_SUPPLIER_MAP = ParameterizedTypeName + .get(ClassName.get(Map.class), ClassName.get(String.class), CONTEXT_INITIALIZER_SUPPLIER); + + + private final MultiValueMap> initializerClassMappings; + + private final GeneratedClass generatedClass; + + + AotTestMappingsCodeGenerator(MultiValueMap> initializerClassMappings, + GeneratedClasses generatedClasses) { + + this.initializerClassMappings = initializerClassMappings; + this.generatedClass = generatedClasses.addForFeature("Generated", this::generateType); + } + + + GeneratedClass getGeneratedClass() { + return this.generatedClass; + } + + private void generateType(TypeSpec.Builder type) { + logger.debug(LogMessage.format("Generating AOT test mappings in %s", + this.generatedClass.getName().reflectionName())); + type.addJavadoc("Generated mappings for {@link $T}.", AotTestMappings.class); + type.addModifiers(Modifier.PUBLIC); + type.addMethod(generateMappingMethod()); + } + + private MethodSpec generateMappingMethod() { + MethodSpec.Builder method = MethodSpec.methodBuilder(AotTestMappings.GENERATED_MAPPINGS_METHOD_NAME); + method.addModifiers(Modifier.PUBLIC, Modifier.STATIC); + method.returns(CONTEXT_SUPPLIER_MAP); + method.addCode(generateMappingCode()); + return method.build(); + } + + private CodeBlock generateMappingCode() { + CodeBlock.Builder code = CodeBlock.builder(); + code.addStatement("$T map = new $T<>()", CONTEXT_SUPPLIER_MAP, HashMap.class); + this.initializerClassMappings.forEach((className, testClasses) -> { + List testClassNames = testClasses.stream().map(Class::getName).toList(); + logger.debug(LogMessage.format( + "Generating mapping from AOT context initializer [%s] to test classes %s", + className.reflectionName(), testClassNames)); + testClassNames.forEach(testClassName -> + code.addStatement("map.put($S, () -> new $T())", testClassName, className)); + }); + code.addStatement("return map"); + return code.build(); + } + +} 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 2fd247c1a479..859aecdce201 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 @@ -24,13 +24,17 @@ import org.springframework.aot.generate.ClassNameGenerator; import org.springframework.aot.generate.DefaultGenerationContext; +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.RuntimeHints; +import org.springframework.aot.hint.TypeReference; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.aot.ApplicationContextAotGenerator; import org.springframework.context.support.GenericApplicationContext; +import org.springframework.core.log.LogMessage; import org.springframework.javapoet.ClassName; import org.springframework.test.context.BootstrapUtils; import org.springframework.test.context.ContextLoader; @@ -51,7 +55,7 @@ */ class TestContextAotGenerator { - private static final Log logger = LogFactory.getLog(TestClassScanner.class); + private static final Log logger = LogFactory.getLog(TestContextAotGenerator.class); private final ApplicationContextAotGenerator aotGenerator = new ApplicationContextAotGenerator(); @@ -97,30 +101,33 @@ public final RuntimeHints getRuntimeHints() { * @throws TestContextAotException if an error occurs during AOT processing */ public void processAheadOfTime(Stream> testClasses) throws TestContextAotException { - MultiValueMap> map = new LinkedMultiValueMap<>(); - testClasses.forEach(testClass -> map.add(buildMergedContextConfiguration(testClass), testClass)); - - map.forEach((mergedConfig, classes) -> { - // System.err.println(mergedConfig + " -> " + classes); - if (logger.isDebugEnabled()) { - logger.debug("Generating AOT artifacts for test classes [%s]" - .formatted(classes.stream().map(Class::getCanonicalName).toList())); - } + MultiValueMap> mergedConfigMappings = new LinkedMultiValueMap<>(); + testClasses.forEach(testClass -> mergedConfigMappings.add(buildMergedContextConfiguration(testClass), testClass)); + processAheadOfTime(mergedConfigMappings); + } + + private void processAheadOfTime(MultiValueMap> mergedConfigMappings) { + MultiValueMap> initializerClassMappings = new LinkedMultiValueMap<>(); + mergedConfigMappings.forEach((mergedConfig, testClasses) -> { + logger.debug(LogMessage.format("Generating AOT artifacts for test classes %s", + testClasses.stream().map(Class::getName).toList())); try { // Use first test class discovered for a given unique MergedContextConfiguration. - Class testClass = classes.get(0); + Class testClass = testClasses.get(0); DefaultGenerationContext generationContext = createGenerationContext(testClass); - ClassName className = processAheadOfTime(mergedConfig, generationContext); - // TODO Store ClassName in a map analogous to TestContextAotProcessor in Spring Native. + ClassName initializer = processAheadOfTime(mergedConfig, generationContext); + Assert.state(!initializerClassMappings.containsKey(initializer), + () -> "ClassName [%s] already encountered".formatted(initializer.reflectionName())); + initializerClassMappings.addAll(initializer, testClasses); generationContext.writeGeneratedContent(); } catch (Exception ex) { - if (logger.isWarnEnabled()) { - logger.warn("Failed to generate AOT artifacts for test classes [%s]" - .formatted(classes.stream().map(Class::getCanonicalName).toList()), ex); - } + logger.warn(LogMessage.format("Failed to generate AOT artifacts for test classes [%s]", + testClasses.stream().map(Class::getName).toList()), ex); } }); + + generateAotTestMappings(initializerClassMappings); } /** @@ -143,7 +150,7 @@ ClassName processAheadOfTime(MergedContextConfiguration mergedConfig, } catch (Throwable ex) { throw new TestContextAotException("Failed to process test class [%s] for AOT" - .formatted(mergedConfig.getTestClass().getCanonicalName()), ex); + .formatted(mergedConfig.getTestClass().getName()), ex); } } @@ -154,7 +161,7 @@ ClassName processAheadOfTime(MergedContextConfiguration mergedConfig, * create {@link GenericApplicationContext GenericApplicationContexts}. * @throws TestContextAotException if an error occurs while loading the application * context or if one of the prerequisites is not met - * @see SmartContextLoader#loadContextForAotProcessing(MergedContextConfiguration) + * @see AotContextLoader#loadContextForAotProcessing(MergedContextConfiguration) */ private GenericApplicationContext loadContextForAotProcessing( MergedContextConfiguration mergedConfig) throws TestContextAotException { @@ -164,7 +171,7 @@ private GenericApplicationContext loadContextForAotProcessing( Assert.notNull(contextLoader, """ Cannot load an ApplicationContext with a NULL 'contextLoader'. \ Consider annotating test class [%s] with @ContextConfiguration or \ - @ContextHierarchy.""".formatted(testClass.getCanonicalName())); + @ContextHierarchy.""".formatted(testClass.getName())); if (contextLoader instanceof AotContextLoader aotContextLoader) { try { @@ -176,13 +183,13 @@ private GenericApplicationContext loadContextForAotProcessing( catch (Exception ex) { throw new TestContextAotException( "Failed to load ApplicationContext for AOT processing for test class [%s]" - .formatted(testClass.getCanonicalName()), ex); + .formatted(testClass.getName()), ex); } } throw new TestContextAotException(""" Cannot generate AOT artifacts for test class [%s]. The configured \ ContextLoader [%s] must be an AotContextLoader and must create a \ - GenericApplicationContext.""".formatted(testClass.getCanonicalName(), + GenericApplicationContext.""".formatted(testClass.getName(), contextLoader.getClass().getName())); } @@ -203,4 +210,18 @@ private String nextTestContextId() { return "TestContext%03d_".formatted(this.sequence.incrementAndGet()); } + private void generateAotTestMappings(MultiValueMap> initializerClassMappings) { + ClassNameGenerator classNameGenerator = new ClassNameGenerator(AotTestMappings.class); + DefaultGenerationContext generationContext = + new DefaultGenerationContext(classNameGenerator, this.generatedFiles, this.runtimeHints); + GeneratedClasses generatedClasses = generationContext.getGeneratedClasses(); + + AotTestMappingsCodeGenerator codeGenerator = + new AotTestMappingsCodeGenerator(initializerClassMappings, generatedClasses); + generationContext.writeGeneratedContent(); + String className = codeGenerator.getGeneratedClass().getName().reflectionName(); + this.runtimeHints.reflection().registerType(TypeReference.of(className), + builder -> builder.withMembers(MemberCategory.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 89cdd4c8a7b6..1a2160f8b8fd 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 @@ -28,6 +28,8 @@ abstract class AbstractAotTests { static final String[] expectedSourceFilesForBasicSpringTests = { + // 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", diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/AotSmokeTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/AotSmokeTests.java index 508a25a4e61a..f3e762afc4d6 100644 --- a/spring-test/src/test/java/org/springframework/test/context/aot/AotSmokeTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/aot/AotSmokeTests.java @@ -49,9 +49,11 @@ void scanClassPathThenGenerateSourceFilesAndCompileThem() { List sourceFiles = generatedFiles.getGeneratedFiles(Kind.SOURCE).keySet().stream().toList(); assertThat(sourceFiles).containsExactlyInAnyOrder(expectedSourceFilesForBasicSpringTests); - TestCompiler.forSystem().withFiles(generatedFiles).compile(compiled -> { - // just make sure compilation completes without errors - }); + TestCompiler.forSystem().withFiles(generatedFiles) + // .printFiles(System.out) + .compile(compiled -> { + // just make sure compilation completes without errors + }); } } 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 b15d89aa0a42..775921c7f4a8 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 @@ -19,18 +19,20 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; -import java.util.stream.Stream; 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.MemberCategory; +import org.springframework.aot.hint.ReflectionHints; +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.support.GenericApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.javapoet.ClassName; import org.springframework.test.context.MergedContextConfiguration; import org.springframework.test.context.aot.samples.basic.BasicSpringJupiterSharedConfigTests; @@ -48,6 +50,7 @@ import org.springframework.util.function.ThrowingConsumer; import org.springframework.web.context.WebApplicationContext; +import static java.util.Comparator.comparing; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; @@ -67,8 +70,8 @@ class TestContextAotGeneratorTests extends AbstractAotTests { * @see AotSmokeTests#scanClassPathThenGenerateSourceFilesAndCompileThem() */ @Test - void generate() { - Stream> testClasses = Stream.of( + void processAheadOfTimeAndGenerateAotTestMappings() { + Set> testClasses = Set.of( BasicSpringJupiterSharedConfigTests.class, BasicSpringJupiterTests.class, BasicSpringJupiterTests.NestedTests.class, @@ -78,14 +81,28 @@ void generate() { InMemoryGeneratedFiles generatedFiles = new InMemoryGeneratedFiles(); TestContextAotGenerator generator = new TestContextAotGenerator(generatedFiles); - generator.processAheadOfTime(testClasses); + 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)); List sourceFiles = generatedFiles.getGeneratedFiles(Kind.SOURCE).keySet().stream().toList(); assertThat(sourceFiles).containsExactlyInAnyOrder(expectedSourceFilesForBasicSpringTests); - TestCompiler.forSystem().withFiles(generatedFiles).compile(compiled -> { - // just make sure compilation completes without errors - }); + TestCompiler.forSystem().withFiles(generatedFiles).compile(ThrowingConsumer.of(compiled -> { + AotTestMappings aotTestMappings = new AotTestMappings(); + for (Class testClass : testClasses) { + MergedContextConfiguration mergedConfig = generator.buildMergedContextConfiguration(testClass); + ApplicationContextInitializer contextInitializer = + aotTestMappings.getContextInitializer(testClass); + assertThat(contextInitializer).isNotNull(); + ApplicationContext context = ((AotContextLoader) mergedConfig.getContextLoader()) + .loadContextForAotRuntime(mergedConfig, contextInitializer); + assertContextForBasicTests(context); + } + })); } @Test @@ -99,13 +116,14 @@ void processAheadOfTimeWithBasicTests() { BasicSpringTestNGTests.class, BasicSpringVintageTests.class); - processAheadOfTime(testClasses, context -> { - assertThat(context.getEnvironment().getProperty("test.engine")) - .as("Environment").isNotNull(); + processAheadOfTime(testClasses, this::assertContextForBasicTests); + } - MessageService messageService = context.getBean(MessageService.class); - assertThat(messageService.generateMessage()).isEqualTo("Hello, AOT!"); - }); + private void assertContextForBasicTests(ApplicationContext context) { + assertThat(context.getEnvironment().getProperty("test.engine")).as("Environment").isNotNull(); + + MessageService messageService = context.getBean(MessageService.class); + assertThat(messageService.generateMessage()).isEqualTo("Hello, AOT!"); } @Test @@ -151,16 +169,16 @@ private void processAheadOfTime(Set> testClasses, ThrowingConsumer mappings = processAheadOfTime(generator, testClasses); - TestCompiler.forSystem().withFiles(generatedFiles).compile(compiled -> { - mappings.forEach(mapping -> { + TestCompiler.forSystem().withFiles(generatedFiles).compile(ThrowingConsumer.of(compiled -> { + for (Mapping mapping : mappings) { MergedContextConfiguration mergedConfig = mapping.mergedConfig(); - ApplicationContextInitializer contextInitializer = + ApplicationContextInitializer contextInitializer = compiled.getInstance(ApplicationContextInitializer.class, mapping.className().reflectionName()); - AotRuntimeContextLoader aotRuntimeContextLoader = new AotRuntimeContextLoader(); - GenericApplicationContext context = aotRuntimeContextLoader.loadContext(mergedConfig, contextInitializer); + ApplicationContext context = ((AotContextLoader) mergedConfig.getContextLoader()) + .loadContextForAotRuntime(mergedConfig, contextInitializer); result.accept(context); - }); - }); + } + })); } private List processAheadOfTime(TestContextAotGenerator generator, Set> testClasses) {