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;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Stream;
|
||||
|
|
@ -67,6 +66,9 @@ public class TestContextAotGenerator {
|
|||
|
||||
private final AotServices<TestRuntimeHintsRegistrar> testRuntimeHintsRegistrars;
|
||||
|
||||
private final MergedContextConfigurationRuntimeHints mergedConfigRuntimeHints =
|
||||
new MergedContextConfigurationRuntimeHints();
|
||||
|
||||
private final AtomicInteger sequence = new AtomicInteger();
|
||||
|
||||
private final GeneratedFiles generatedFiles;
|
||||
|
|
@ -115,7 +117,14 @@ public class TestContextAotGenerator {
|
|||
resetAotFactories();
|
||||
|
||||
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);
|
||||
|
||||
generateTestAotMappings(initializerClassMappings);
|
||||
|
|
@ -137,9 +146,6 @@ public class TestContextAotGenerator {
|
|||
logger.debug(LogMessage.format("Generating AOT artifacts for test classes %s",
|
||||
testClasses.stream().map(Class::getName).toList()));
|
||||
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.
|
||||
Class<?> testClass = testClasses.get(0);
|
||||
DefaultGenerationContext generationContext = createGenerationContext(testClass);
|
||||
|
|
|
|||
|
|
@ -16,10 +16,7 @@
|
|||
|
||||
package org.springframework.test.context.aot;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.test.context.MergedContextConfiguration;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* {@link org.springframework.aot.hint.RuntimeHintsRegistrar RuntimeHintsRegistrar}
|
||||
* API. If you need to register global hints for testing support that are not
|
||||
* specific to a particular test class or {@link MergedContextConfiguration}, favor
|
||||
* implementing {@code RuntimeHintsRegistrar} over this API.
|
||||
* specific to particular test classes, favor implementing {@code RuntimeHintsRegistrar}
|
||||
* over this API.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 6.0
|
||||
|
|
@ -45,13 +42,10 @@ public interface TestRuntimeHintsRegistrar {
|
|||
|
||||
/**
|
||||
* 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 testClass the test class to process
|
||||
* @param classLoader the classloader to use
|
||||
*/
|
||||
void registerHints(MergedContextConfiguration mergedConfig, List<Class<?>> testClasses,
|
||||
RuntimeHints runtimeHints, ClassLoader classLoader);
|
||||
void registerHints(RuntimeHints runtimeHints, Class<?> testClass, ClassLoader classLoader);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,23 +16,15 @@
|
|||
|
||||
package org.springframework.test.context.aot.hint;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
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.aot.TestRuntimeHintsRegistrar;
|
||||
import org.springframework.test.context.web.WebMergedContextConfiguration;
|
||||
|
||||
import static org.springframework.aot.hint.MemberCategory.INVOKE_DECLARED_CONSTRUCTORS;
|
||||
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
|
||||
|
|
@ -44,43 +36,8 @@ import static org.springframework.util.ResourceUtils.CLASSPATH_URL_PREFIX;
|
|||
*/
|
||||
class StandardTestRuntimeHints implements TestRuntimeHintsRegistrar {
|
||||
|
||||
private static final String SLASH = "/";
|
||||
|
||||
|
||||
@Override
|
||||
public void registerHints(MergedContextConfiguration mergedConfig, List<Class<?>> testClasses,
|
||||
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) {
|
||||
public void registerHints(RuntimeHints runtimeHints, Class<?> testClass, ClassLoader classLoader) {
|
||||
// @ActiveProfiles(resolver = ...)
|
||||
MergedAnnotations.search(TYPE_HIERARCHY)
|
||||
.withEnclosingClasses(TestContextAnnotationUtils::searchEnclosingClass)
|
||||
|
|
@ -95,26 +52,4 @@ class StandardTestRuntimeHints implements TestRuntimeHintsRegistrar {
|
|||
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