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
This commit is contained in:
Sam Brannen 2022-09-04 17:44:19 +02:00
parent bf0ac84a05
commit cbb30153fb
4 changed files with 110 additions and 51 deletions

View File

@ -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.ContextLoader;
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 @@ import static org.springframework.util.ResourceUtils.CLASSPATH_URL_PREFIX;
*/
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 @@ public class TestContextAotGenerator {
mergedConfigMappings.forEach((mergedConfig, testClasses) -> {
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 @@ public class TestContextAotGenerator {
.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);

View File

@ -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 <em>Spring TestContext Framework</em>.
*
* @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<Class<?>> 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;
}
}

View File

@ -34,6 +34,7 @@ import org.springframework.util.ClassUtils;
*
* @author Sam Brannen
* @since 6.0
* @see StandardTestRuntimeHints
*/
class TestContextRuntimeHints implements RuntimeHintsRegistrar {

View File

@ -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