From cbb30153fbfc8462aeb7564b7c343fe0ae52e1ec Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sun, 4 Sep 2022 17:44:19 +0200 Subject: [PATCH] Introduce StandardTestRuntimeHints in the TestContext framework This commit introduces StandardTestRuntimeHints and migrates existing hint registration from TestContextAotGenerator to this new class. In addition, this commit removes a package cycle between context.aot and context.web that was introduced in commit dc7c7ac22a. See gh-29026, gh-29069 --- .../context/aot/TestContextAotGenerator.java | 51 --------- .../aot/hint/StandardTestRuntimeHints.java | 106 ++++++++++++++++++ .../aot/hint/TestContextRuntimeHints.java | 1 + .../resources/META-INF/spring/aot.factories | 3 + 4 files changed, 110 insertions(+), 51 deletions(-) create mode 100644 spring-test/src/main/java/org/springframework/test/context/aot/hint/StandardTestRuntimeHints.java 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 eff97bda8b3b..fefd5d9b520a 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 @@ -16,7 +16,6 @@ package org.springframework.test.context.aot; -import java.util.Arrays; import java.util.Collections; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; @@ -44,14 +43,12 @@ import org.springframework.test.context.MergedContextConfiguration; import org.springframework.test.context.SmartContextLoader; import org.springframework.test.context.TestContextBootstrapper; -import org.springframework.test.context.web.WebMergedContextConfiguration; import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import static org.springframework.aot.hint.MemberCategory.INVOKE_DECLARED_CONSTRUCTORS; import static org.springframework.aot.hint.MemberCategory.INVOKE_PUBLIC_METHODS; -import static org.springframework.util.ResourceUtils.CLASSPATH_URL_PREFIX; /** * {@code TestContextAotGenerator} generates AOT artifacts for integration tests @@ -63,8 +60,6 @@ */ public class TestContextAotGenerator { - private static final String SLASH = "/"; - private static final Log logger = LogFactory.getLog(TestContextAotGenerator.class); private final ApplicationContextAotGenerator aotGenerator = new ApplicationContextAotGenerator(); @@ -124,7 +119,6 @@ private void processAheadOfTime(MultiValueMap { logger.debug(LogMessage.format("Generating AOT artifacts for test classes %s", testClasses.stream().map(Class::getName).toList())); - registerHintsForMergedConfig(mergedConfig); try { this.testRuntimeHintsRegistrars.forEach(registrar -> registrar.registerHints(this.runtimeHints, mergedConfig, Collections.unmodifiableList(testClasses), getClass().getClassLoader())); @@ -247,51 +241,6 @@ private void generateAotTestMappings(MultiValueMap> initiali .registerType(TypeReference.of(className), INVOKE_PUBLIC_METHODS); } - private void registerHintsForMergedConfig(MergedContextConfiguration mergedConfig) { - // @ContextConfiguration(loader = ...) - ContextLoader contextLoader = mergedConfig.getContextLoader(); - if (contextLoader != null) { - registerDeclaredConstructors(contextLoader.getClass()); - } - - // @ContextConfiguration(initializers = ...) - mergedConfig.getContextInitializerClasses().forEach(this::registerDeclaredConstructors); - - // @ContextConfiguration(locations = ...) - registerHintsForClasspathResources(mergedConfig.getLocations()); - - // @TestPropertySource(locations = ... ) - registerHintsForClasspathResources(mergedConfig.getPropertySourceLocations()); - - if (mergedConfig instanceof WebMergedContextConfiguration webMergedConfig) { - String resourceBasePath = webMergedConfig.getResourceBasePath(); - if (resourceBasePath.startsWith(CLASSPATH_URL_PREFIX)) { - String pattern = resourceBasePath.substring(CLASSPATH_URL_PREFIX.length()); - if (!pattern.startsWith(SLASH)) { - pattern = SLASH + pattern; - } - if (!pattern.endsWith(SLASH)) { - pattern += SLASH; - } - pattern += "*"; - this.runtimeHints.resources().registerPattern(pattern); - } - } - } - - private void registerHintsForClasspathResources(String... locations) { - Arrays.stream(locations) - .filter(location -> location.startsWith(CLASSPATH_URL_PREFIX)) - .map(location -> { - location = location.substring(CLASSPATH_URL_PREFIX.length()); - if (!location.startsWith(SLASH)) { - location = SLASH + location; - } - return location; - }) - .forEach(this.runtimeHints.resources()::registerPattern); - } - private void registerDeclaredConstructors(Class type) { ReflectionHints reflectionHints = this.runtimeHints.reflection(); reflectionHints.registerType(type, INVOKE_DECLARED_CONSTRUCTORS); diff --git a/spring-test/src/main/java/org/springframework/test/context/aot/hint/StandardTestRuntimeHints.java b/spring-test/src/main/java/org/springframework/test/context/aot/hint/StandardTestRuntimeHints.java new file mode 100644 index 000000000000..4f93602ed3c1 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/context/aot/hint/StandardTestRuntimeHints.java @@ -0,0 +1,106 @@ +/* + * 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.hint; + +import java.util.Arrays; +import java.util.List; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.test.context.ContextLoader; +import org.springframework.test.context.MergedContextConfiguration; +import org.springframework.test.context.aot.TestRuntimeHintsRegistrar; +import org.springframework.test.context.web.WebMergedContextConfiguration; + +import static org.springframework.aot.hint.MemberCategory.INVOKE_DECLARED_CONSTRUCTORS; +import static org.springframework.util.ResourceUtils.CLASSPATH_URL_PREFIX; + +/** + * {@link TestRuntimeHintsRegistrar} implementation that registers run-time hints + * for standard functionality in the Spring TestContext Framework. + * + * @author Sam Brannen + * @since 6.0 + * @see TestContextRuntimeHints + */ +class StandardTestRuntimeHints implements TestRuntimeHintsRegistrar { + + private static final String SLASH = "/"; + + + @Override + public void registerHints(RuntimeHints runtimeHints, MergedContextConfiguration mergedConfig, + List> testClasses, ClassLoader classLoader) { + + registerHintsForMergedContextConfiguration(runtimeHints, mergedConfig); + } + + private void registerHintsForMergedContextConfiguration( + RuntimeHints runtimeHints, MergedContextConfiguration mergedConfig) { + + // @ContextConfiguration(loader = ...) + ContextLoader contextLoader = mergedConfig.getContextLoader(); + if (contextLoader != null) { + registerDeclaredConstructors(runtimeHints, contextLoader.getClass()); + } + + // @ContextConfiguration(initializers = ...) + mergedConfig.getContextInitializerClasses() + .forEach(clazz -> registerDeclaredConstructors(runtimeHints, clazz)); + + // @ContextConfiguration(locations = ...) + registerClasspathResources(runtimeHints, mergedConfig.getLocations()); + + // @TestPropertySource(locations = ... ) + registerClasspathResources(runtimeHints, mergedConfig.getPropertySourceLocations()); + + // @WebAppConfiguration(value = ...) + if (mergedConfig instanceof WebMergedContextConfiguration webConfig) { + registerClasspathResourceDirectoryStructure(runtimeHints, webConfig.getResourceBasePath()); + } + } + + private void registerDeclaredConstructors(RuntimeHints runtimeHints, Class type) { + runtimeHints.reflection().registerType(type, INVOKE_DECLARED_CONSTRUCTORS); + } + + private void registerClasspathResources(RuntimeHints runtimeHints, String... locations) { + Arrays.stream(locations) + .filter(location -> location.startsWith(CLASSPATH_URL_PREFIX)) + .map(this::cleanClasspathResource) + .forEach(runtimeHints.resources()::registerPattern); + } + + private void registerClasspathResourceDirectoryStructure(RuntimeHints runtimeHints, String directory) { + if (directory.startsWith(CLASSPATH_URL_PREFIX)) { + String pattern = cleanClasspathResource(directory); + if (!pattern.endsWith(SLASH)) { + pattern += SLASH; + } + pattern += "*"; + runtimeHints.resources().registerPattern(pattern); + } + } + + private String cleanClasspathResource(String location) { + location = location.substring(CLASSPATH_URL_PREFIX.length()); + if (!location.startsWith(SLASH)) { + location = SLASH + location; + } + return location; + } + +} 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 d1ceaa6b54d8..58f34cc1b8f4 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 @@ -34,6 +34,7 @@ * * @author Sam Brannen * @since 6.0 + * @see StandardTestRuntimeHints */ class TestContextRuntimeHints implements RuntimeHintsRegistrar { diff --git a/spring-test/src/main/resources/META-INF/spring/aot.factories b/spring-test/src/main/resources/META-INF/spring/aot.factories index 3a0242dec41d..dcedd2c64e25 100644 --- a/spring-test/src/main/resources/META-INF/spring/aot.factories +++ b/spring-test/src/main/resources/META-INF/spring/aot.factories @@ -1,2 +1,5 @@ org.springframework.aot.hint.RuntimeHintsRegistrar=\ org.springframework.test.context.aot.hint.TestContextRuntimeHints + +org.springframework.test.context.aot.TestRuntimeHintsRegistrar=\ +org.springframework.test.context.aot.hint.StandardTestRuntimeHints \ No newline at end of file