Retain existing feature name as prefix in test AOT processing

Prior to this commit, test AOT processing failed if a feature name for
generated class names was used for more than one ApplicationContext.

For example, when generating code for org.example.MessageService with a
"Management" feature name, the "BeanDefinitions" class was named as
follows (without a uniquely identifying TestContext###_ feature name
prefix).

org/example/MessageService__ManagementBeanDefinitions.java

When another attempt was made to generate code for the MessageService
using the same "Management" feature name, a FileAlreadyExistsException
was thrown denoting that the class/file name was already in use.

To avoid such naming collisions, this commit introduces a
TestContextGenerationContext which provides a custom implementation of
withName(String) that prepends an existing feature name (if present) to
a new feature name, thereby treating any existing feature name as a
prefix to a new, nested feature name.

Consequently, code generation for the above example now results in
unique class/file names like the following (which retain the uniquely
identifying TestContext###_ prefixes).

org/example/MessageService__TestContext002_ManagementBeanDefinitions.java
org/example/MessageService__TestContext003_ManagementBeanDefinitions.java

Closes gh-30861
This commit is contained in:
Sam Brannen
2023-07-15 10:51:56 +02:00
parent 317c6fbec2
commit f5db8bd1e8
11 changed files with 272 additions and 11 deletions

View File

@@ -324,8 +324,8 @@ public class TestContextAotGenerator {
DefaultGenerationContext createGenerationContext(Class<?> testClass) {
ClassNameGenerator classNameGenerator = new ClassNameGenerator(ClassName.get(testClass));
DefaultGenerationContext generationContext =
new DefaultGenerationContext(classNameGenerator, this.generatedFiles, this.runtimeHints);
TestContextGenerationContext generationContext =
new TestContextGenerationContext(classNameGenerator, this.generatedFiles, this.runtimeHints);
return generationContext.withName(nextTestContextId());
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2002-2023 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 org.springframework.aot.generate.ClassNameGenerator;
import org.springframework.aot.generate.DefaultGenerationContext;
import org.springframework.aot.generate.GeneratedClasses;
import org.springframework.aot.generate.GeneratedFiles;
import org.springframework.aot.hint.RuntimeHints;
/**
* Extension of {@link DefaultGenerationContext} with a custom implementation of
* {@link #withName(String)} that is specific to the <em>Spring TestContext Framework</em>.
*
* @author Sam Brannen
* @since 6.0.12
*/
class TestContextGenerationContext extends DefaultGenerationContext {
private final String featureName;
/**
* Create a new {@link TestContextGenerationContext} instance backed by the
* specified {@link ClassNameGenerator}, {@link GeneratedFiles}, and
* {@link RuntimeHints}.
* @param classNameGenerator the naming convention to use for generated class names
* @param generatedFiles the generated files
* @param runtimeHints the runtime hints
*/
TestContextGenerationContext(ClassNameGenerator classNameGenerator, GeneratedFiles generatedFiles,
RuntimeHints runtimeHints) {
super(classNameGenerator, generatedFiles, runtimeHints);
this.featureName = null;
}
/**
* Create a new {@link TestContextGenerationContext} instance backed by the
* specified {@link GeneratedClasses}, {@link GeneratedFiles}, and
* {@link RuntimeHints}.
* @param generatedClasses the generated classes
* @param generatedFiles the generated files
* @param runtimeHints the runtime hints
*/
private TestContextGenerationContext(GeneratedClasses generatedClasses, GeneratedFiles generatedFiles,
RuntimeHints runtimeHints, String featureName) {
super(generatedClasses, generatedFiles, runtimeHints);
this.featureName = featureName;
}
/**
* Create a new {@link TestContextGenerationContext} instance using the specified
* feature name to qualify generated assets for a dedicated round of code generation.
* <p>If <em>this</em> {@code TestContextGenerationContext} has a configured feature
* name, the supplied feature name will be appended to the existing feature name
* in order to avoid naming collisions.
* @param featureName the feature name to use
* @return a specialized {@link TestContextGenerationContext} for the specified
* feature name
*/
@Override
public TestContextGenerationContext withName(String featureName) {
if (this.featureName != null) {
featureName = this.featureName + featureName;
}
GeneratedClasses generatedClasses = getGeneratedClasses().withFeatureNamePrefix(featureName);
return new TestContextGenerationContext(generatedClasses, getGeneratedFiles(), getRuntimeHints(), featureName);
}
}