Simplify TestRuntimeHintsRegistrar API
Prior to this commit, the TestRuntimeHintsRegistrar API combined processing of MergedContextConfiguration and test classes. However, it appears that only spring-test internals have a need for registering hints based on the MergedContextConfiguration. For example, Spring Boot's AOT testing support has not had such a need, and it is assumed that third parties likely will not have such a need. In light of that, this commit simplifies the TestRuntimeHintsRegistrar API so that it focuses on processing of a single test class. In addition, this commit moves the hint registration code specific to MergedContextConfiguration to an internal mechanism. Closes gh-29264
This commit is contained in:
parent
7ad65b8dff
commit
935265048a
|
|
@ -0,0 +1,135 @@
|
||||||
|
/*
|
||||||
|
* 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.Arrays;
|
||||||
|
|
||||||
|
import org.springframework.aot.hint.RuntimeHints;
|
||||||
|
import org.springframework.core.io.DefaultResourceLoader;
|
||||||
|
import org.springframework.test.context.ContextLoader;
|
||||||
|
import org.springframework.test.context.MergedContextConfiguration;
|
||||||
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
||||||
|
import static org.springframework.aot.hint.MemberCategory.INVOKE_DECLARED_CONSTRUCTORS;
|
||||||
|
import static org.springframework.util.ResourceUtils.CLASSPATH_URL_PREFIX;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code MergedContextConfigurationRuntimeHints} registers run-time hints for
|
||||||
|
* standard functionality in the <em>Spring TestContext Framework</em> based on
|
||||||
|
* {@link MergedContextConfiguration}.
|
||||||
|
*
|
||||||
|
* <p>This class interacts with {@code org.springframework.test.context.web.WebMergedContextConfiguration}
|
||||||
|
* via reflection to avoid a package cycle.
|
||||||
|
*
|
||||||
|
* @author Sam Brannen
|
||||||
|
* @since 6.0
|
||||||
|
*/
|
||||||
|
class MergedContextConfigurationRuntimeHints {
|
||||||
|
|
||||||
|
private static final String SLASH = "/";
|
||||||
|
|
||||||
|
private static final String WEB_MERGED_CONTEXT_CONFIGURATION_CLASS_NAME =
|
||||||
|
"org.springframework.test.context.web.WebMergedContextConfiguration";
|
||||||
|
|
||||||
|
private static final String GET_RESOURCE_BASE_PATH_METHOD_NAME = "getResourceBasePath";
|
||||||
|
|
||||||
|
private static final Class<?> webMergedContextConfigurationClass = loadWebMergedContextConfigurationClass();
|
||||||
|
|
||||||
|
private static final Method getResourceBasePathMethod = loadGetResourceBasePathMethod();
|
||||||
|
|
||||||
|
|
||||||
|
public void registerHints(RuntimeHints runtimeHints, MergedContextConfiguration mergedConfig, ClassLoader classLoader) {
|
||||||
|
// @ContextConfiguration(loader = ...)
|
||||||
|
ContextLoader contextLoader = mergedConfig.getContextLoader();
|
||||||
|
if (contextLoader != null) {
|
||||||
|
registerDeclaredConstructors(contextLoader.getClass(), runtimeHints);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ContextConfiguration(initializers = ...)
|
||||||
|
mergedConfig.getContextInitializerClasses()
|
||||||
|
.forEach(clazz -> registerDeclaredConstructors(clazz, runtimeHints));
|
||||||
|
|
||||||
|
// @ContextConfiguration(locations = ...)
|
||||||
|
registerClasspathResources(mergedConfig.getLocations(), runtimeHints, classLoader);
|
||||||
|
|
||||||
|
// @TestPropertySource(locations = ... )
|
||||||
|
registerClasspathResources(mergedConfig.getPropertySourceLocations(), runtimeHints, classLoader);
|
||||||
|
|
||||||
|
// @WebAppConfiguration(value = ...)
|
||||||
|
if (webMergedContextConfigurationClass.isInstance(mergedConfig)) {
|
||||||
|
String resourceBasePath = null;
|
||||||
|
try {
|
||||||
|
resourceBasePath = (String) getResourceBasePathMethod.invoke(mergedConfig);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Failed to invoke WebMergedContextConfiguration#getResourceBasePath()", ex);
|
||||||
|
}
|
||||||
|
registerClasspathResourceDirectoryStructure(resourceBasePath, runtimeHints);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerDeclaredConstructors(Class<?> type, RuntimeHints runtimeHints) {
|
||||||
|
runtimeHints.reflection().registerType(type, INVOKE_DECLARED_CONSTRUCTORS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerClasspathResources(String[] paths, RuntimeHints runtimeHints, ClassLoader classLoader) {
|
||||||
|
DefaultResourceLoader resourceLoader = new DefaultResourceLoader(classLoader);
|
||||||
|
Arrays.stream(paths)
|
||||||
|
.filter(path -> path.startsWith(CLASSPATH_URL_PREFIX))
|
||||||
|
.map(resourceLoader::getResource)
|
||||||
|
.forEach(runtimeHints.resources()::registerResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerClasspathResourceDirectoryStructure(String directory, RuntimeHints runtimeHints) {
|
||||||
|
if (directory.startsWith(CLASSPATH_URL_PREFIX)) {
|
||||||
|
String pattern = directory.substring(CLASSPATH_URL_PREFIX.length());
|
||||||
|
if (pattern.startsWith(SLASH)) {
|
||||||
|
pattern = pattern.substring(1);
|
||||||
|
}
|
||||||
|
if (!pattern.endsWith(SLASH)) {
|
||||||
|
pattern += SLASH;
|
||||||
|
}
|
||||||
|
pattern += "*";
|
||||||
|
runtimeHints.resources().registerPattern(pattern);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static Class<?> loadWebMergedContextConfigurationClass() {
|
||||||
|
try {
|
||||||
|
return ClassUtils.forName(WEB_MERGED_CONTEXT_CONFIGURATION_CLASS_NAME,
|
||||||
|
MergedContextConfigurationRuntimeHints.class.getClassLoader());
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException | LinkageError ex) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Failed to load class " + WEB_MERGED_CONTEXT_CONFIGURATION_CLASS_NAME, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Method loadGetResourceBasePathMethod() {
|
||||||
|
try {
|
||||||
|
return webMergedContextConfigurationClass.getMethod(GET_RESOURCE_BASE_PATH_METHOD_NAME);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Failed to load method WebMergedContextConfiguration#getResourceBasePath()", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
package org.springframework.test.context.aot;
|
package org.springframework.test.context.aot;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
@ -67,6 +66,9 @@ public class TestContextAotGenerator {
|
||||||
|
|
||||||
private final AotServices<TestRuntimeHintsRegistrar> testRuntimeHintsRegistrars;
|
private final AotServices<TestRuntimeHintsRegistrar> testRuntimeHintsRegistrars;
|
||||||
|
|
||||||
|
private final MergedContextConfigurationRuntimeHints mergedConfigRuntimeHints =
|
||||||
|
new MergedContextConfigurationRuntimeHints();
|
||||||
|
|
||||||
private final AtomicInteger sequence = new AtomicInteger();
|
private final AtomicInteger sequence = new AtomicInteger();
|
||||||
|
|
||||||
private final GeneratedFiles generatedFiles;
|
private final GeneratedFiles generatedFiles;
|
||||||
|
|
@ -115,7 +117,14 @@ public class TestContextAotGenerator {
|
||||||
resetAotFactories();
|
resetAotFactories();
|
||||||
|
|
||||||
MultiValueMap<MergedContextConfiguration, Class<?>> mergedConfigMappings = new LinkedMultiValueMap<>();
|
MultiValueMap<MergedContextConfiguration, Class<?>> mergedConfigMappings = new LinkedMultiValueMap<>();
|
||||||
testClasses.forEach(testClass -> mergedConfigMappings.add(buildMergedContextConfiguration(testClass), testClass));
|
ClassLoader classLoader = getClass().getClassLoader();
|
||||||
|
testClasses.forEach(testClass -> {
|
||||||
|
MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
|
||||||
|
mergedConfigMappings.add(mergedConfig, testClass);
|
||||||
|
this.testRuntimeHintsRegistrars.forEach(registrar ->
|
||||||
|
registrar.registerHints(this.runtimeHints, testClass, classLoader));
|
||||||
|
this.mergedConfigRuntimeHints.registerHints(this.runtimeHints, mergedConfig, classLoader);
|
||||||
|
});
|
||||||
MultiValueMap<ClassName, Class<?>> initializerClassMappings = processAheadOfTime(mergedConfigMappings);
|
MultiValueMap<ClassName, Class<?>> initializerClassMappings = processAheadOfTime(mergedConfigMappings);
|
||||||
|
|
||||||
generateTestAotMappings(initializerClassMappings);
|
generateTestAotMappings(initializerClassMappings);
|
||||||
|
|
@ -137,9 +146,6 @@ public class TestContextAotGenerator {
|
||||||
logger.debug(LogMessage.format("Generating AOT artifacts for test classes %s",
|
logger.debug(LogMessage.format("Generating AOT artifacts for test classes %s",
|
||||||
testClasses.stream().map(Class::getName).toList()));
|
testClasses.stream().map(Class::getName).toList()));
|
||||||
try {
|
try {
|
||||||
this.testRuntimeHintsRegistrars.forEach(registrar -> registrar.registerHints(mergedConfig,
|
|
||||||
Collections.unmodifiableList(testClasses), this.runtimeHints, getClass().getClassLoader()));
|
|
||||||
|
|
||||||
// Use first test class discovered for a given unique MergedContextConfiguration.
|
// Use first test class discovered for a given unique MergedContextConfiguration.
|
||||||
Class<?> testClass = testClasses.get(0);
|
Class<?> testClass = testClasses.get(0);
|
||||||
DefaultGenerationContext generationContext = createGenerationContext(testClass);
|
DefaultGenerationContext generationContext = createGenerationContext(testClass);
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,7 @@
|
||||||
|
|
||||||
package org.springframework.test.context.aot;
|
package org.springframework.test.context.aot;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.springframework.aot.hint.RuntimeHints;
|
import org.springframework.aot.hint.RuntimeHints;
|
||||||
import org.springframework.test.context.MergedContextConfiguration;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contract for registering {@link RuntimeHints} for integration tests run with
|
* Contract for registering {@link RuntimeHints} for integration tests run with
|
||||||
|
|
@ -34,8 +31,8 @@ import org.springframework.test.context.MergedContextConfiguration;
|
||||||
* <p>This API serves as a companion to the core
|
* <p>This API serves as a companion to the core
|
||||||
* {@link org.springframework.aot.hint.RuntimeHintsRegistrar RuntimeHintsRegistrar}
|
* {@link org.springframework.aot.hint.RuntimeHintsRegistrar RuntimeHintsRegistrar}
|
||||||
* API. If you need to register global hints for testing support that are not
|
* API. If you need to register global hints for testing support that are not
|
||||||
* specific to a particular test class or {@link MergedContextConfiguration}, favor
|
* specific to particular test classes, favor implementing {@code RuntimeHintsRegistrar}
|
||||||
* implementing {@code RuntimeHintsRegistrar} over this API.
|
* over this API.
|
||||||
*
|
*
|
||||||
* @author Sam Brannen
|
* @author Sam Brannen
|
||||||
* @since 6.0
|
* @since 6.0
|
||||||
|
|
@ -45,13 +42,10 @@ public interface TestRuntimeHintsRegistrar {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contribute hints to the given {@link RuntimeHints} instance.
|
* Contribute hints to the given {@link RuntimeHints} instance.
|
||||||
* @param mergedConfig the merged context configuration to process
|
|
||||||
* @param testClasses the test classes that share the supplied merged context
|
|
||||||
* configuration
|
|
||||||
* @param runtimeHints the {@code RuntimeHints} to use
|
* @param runtimeHints the {@code RuntimeHints} to use
|
||||||
|
* @param testClass the test class to process
|
||||||
* @param classLoader the classloader to use
|
* @param classLoader the classloader to use
|
||||||
*/
|
*/
|
||||||
void registerHints(MergedContextConfiguration mergedConfig, List<Class<?>> testClasses,
|
void registerHints(RuntimeHints runtimeHints, Class<?> testClass, ClassLoader classLoader);
|
||||||
RuntimeHints runtimeHints, ClassLoader classLoader);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,23 +16,15 @@
|
||||||
|
|
||||||
package org.springframework.test.context.aot.hint;
|
package org.springframework.test.context.aot.hint;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.springframework.aot.hint.RuntimeHints;
|
import org.springframework.aot.hint.RuntimeHints;
|
||||||
import org.springframework.core.annotation.MergedAnnotations;
|
import org.springframework.core.annotation.MergedAnnotations;
|
||||||
import org.springframework.core.io.DefaultResourceLoader;
|
|
||||||
import org.springframework.test.context.ActiveProfiles;
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
import org.springframework.test.context.ActiveProfilesResolver;
|
import org.springframework.test.context.ActiveProfilesResolver;
|
||||||
import org.springframework.test.context.ContextLoader;
|
|
||||||
import org.springframework.test.context.MergedContextConfiguration;
|
|
||||||
import org.springframework.test.context.TestContextAnnotationUtils;
|
import org.springframework.test.context.TestContextAnnotationUtils;
|
||||||
import org.springframework.test.context.aot.TestRuntimeHintsRegistrar;
|
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.aot.hint.MemberCategory.INVOKE_DECLARED_CONSTRUCTORS;
|
||||||
import static org.springframework.core.annotation.MergedAnnotations.SearchStrategy.TYPE_HIERARCHY;
|
import static org.springframework.core.annotation.MergedAnnotations.SearchStrategy.TYPE_HIERARCHY;
|
||||||
import static org.springframework.util.ResourceUtils.CLASSPATH_URL_PREFIX;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link TestRuntimeHintsRegistrar} implementation that registers run-time hints
|
* {@link TestRuntimeHintsRegistrar} implementation that registers run-time hints
|
||||||
|
|
@ -44,43 +36,8 @@ import static org.springframework.util.ResourceUtils.CLASSPATH_URL_PREFIX;
|
||||||
*/
|
*/
|
||||||
class StandardTestRuntimeHints implements TestRuntimeHintsRegistrar {
|
class StandardTestRuntimeHints implements TestRuntimeHintsRegistrar {
|
||||||
|
|
||||||
private static final String SLASH = "/";
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerHints(MergedContextConfiguration mergedConfig, List<Class<?>> testClasses,
|
public void registerHints(RuntimeHints runtimeHints, Class<?> testClass, ClassLoader classLoader) {
|
||||||
RuntimeHints runtimeHints, ClassLoader classLoader) {
|
|
||||||
|
|
||||||
registerHintsForMergedContextConfiguration(mergedConfig, runtimeHints, classLoader);
|
|
||||||
testClasses.forEach(testClass -> registerHintsForActiveProfilesResolvers(testClass, runtimeHints));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void registerHintsForMergedContextConfiguration(
|
|
||||||
MergedContextConfiguration mergedConfig, RuntimeHints runtimeHints, ClassLoader classLoader) {
|
|
||||||
|
|
||||||
// @ContextConfiguration(loader = ...)
|
|
||||||
ContextLoader contextLoader = mergedConfig.getContextLoader();
|
|
||||||
if (contextLoader != null) {
|
|
||||||
registerDeclaredConstructors(contextLoader.getClass(), runtimeHints);
|
|
||||||
}
|
|
||||||
|
|
||||||
// @ContextConfiguration(initializers = ...)
|
|
||||||
mergedConfig.getContextInitializerClasses()
|
|
||||||
.forEach(clazz -> registerDeclaredConstructors(clazz, runtimeHints));
|
|
||||||
|
|
||||||
// @ContextConfiguration(locations = ...)
|
|
||||||
registerClasspathResources(mergedConfig.getLocations(), runtimeHints, classLoader);
|
|
||||||
|
|
||||||
// @TestPropertySource(locations = ... )
|
|
||||||
registerClasspathResources(mergedConfig.getPropertySourceLocations(), runtimeHints, classLoader);
|
|
||||||
|
|
||||||
// @WebAppConfiguration(value = ...)
|
|
||||||
if (mergedConfig instanceof WebMergedContextConfiguration webConfig) {
|
|
||||||
registerClasspathResourceDirectoryStructure(webConfig.getResourceBasePath(), runtimeHints);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void registerHintsForActiveProfilesResolvers(Class<?> testClass, RuntimeHints runtimeHints) {
|
|
||||||
// @ActiveProfiles(resolver = ...)
|
// @ActiveProfiles(resolver = ...)
|
||||||
MergedAnnotations.search(TYPE_HIERARCHY)
|
MergedAnnotations.search(TYPE_HIERARCHY)
|
||||||
.withEnclosingClasses(TestContextAnnotationUtils::searchEnclosingClass)
|
.withEnclosingClasses(TestContextAnnotationUtils::searchEnclosingClass)
|
||||||
|
|
@ -95,26 +52,4 @@ class StandardTestRuntimeHints implements TestRuntimeHintsRegistrar {
|
||||||
runtimeHints.reflection().registerType(type, INVOKE_DECLARED_CONSTRUCTORS);
|
runtimeHints.reflection().registerType(type, INVOKE_DECLARED_CONSTRUCTORS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerClasspathResources(String[] paths, RuntimeHints runtimeHints, ClassLoader classLoader) {
|
|
||||||
DefaultResourceLoader resourceLoader = new DefaultResourceLoader(classLoader);
|
|
||||||
Arrays.stream(paths)
|
|
||||||
.filter(path -> path.startsWith(CLASSPATH_URL_PREFIX))
|
|
||||||
.map(resourceLoader::getResource)
|
|
||||||
.forEach(runtimeHints.resources()::registerResource);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void registerClasspathResourceDirectoryStructure(String directory, RuntimeHints runtimeHints) {
|
|
||||||
if (directory.startsWith(CLASSPATH_URL_PREFIX)) {
|
|
||||||
String pattern = directory.substring(CLASSPATH_URL_PREFIX.length());
|
|
||||||
if (pattern.startsWith(SLASH)) {
|
|
||||||
pattern = pattern.substring(1);
|
|
||||||
}
|
|
||||||
if (!pattern.endsWith(SLASH)) {
|
|
||||||
pattern += SLASH;
|
|
||||||
}
|
|
||||||
pattern += "*";
|
|
||||||
runtimeHints.resources().registerPattern(pattern);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue