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:
		
							parent
							
								
									bf0ac84a05
								
							
						
					
					
						commit
						cbb30153fb
					
				| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -34,6 +34,7 @@ import org.springframework.util.ClassUtils;
 | 
			
		|||
 *
 | 
			
		||||
 * @author Sam Brannen
 | 
			
		||||
 * @since 6.0
 | 
			
		||||
 * @see StandardTestRuntimeHints
 | 
			
		||||
 */
 | 
			
		||||
class TestContextRuntimeHints implements RuntimeHintsRegistrar {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
		Loading…
	
		Reference in New Issue