24
24
25
25
import org .springframework .aot .generate .ClassNameGenerator ;
26
26
import org .springframework .aot .generate .DefaultGenerationContext ;
27
+ import org .springframework .aot .generate .GeneratedClasses ;
27
28
import org .springframework .aot .generate .GeneratedFiles ;
28
29
import org .springframework .aot .generate .GenerationContext ;
30
+ import org .springframework .aot .hint .MemberCategory ;
29
31
import org .springframework .aot .hint .RuntimeHints ;
32
+ import org .springframework .aot .hint .TypeReference ;
30
33
import org .springframework .context .ApplicationContext ;
31
34
import org .springframework .context .ApplicationContextInitializer ;
32
35
import org .springframework .context .aot .ApplicationContextAotGenerator ;
33
36
import org .springframework .context .support .GenericApplicationContext ;
37
+ import org .springframework .core .log .LogMessage ;
34
38
import org .springframework .javapoet .ClassName ;
35
39
import org .springframework .test .context .BootstrapUtils ;
36
40
import org .springframework .test .context .ContextLoader ;
51
55
*/
52
56
class TestContextAotGenerator {
53
57
54
- private static final Log logger = LogFactory .getLog (TestClassScanner .class );
58
+ private static final Log logger = LogFactory .getLog (TestContextAotGenerator .class );
55
59
56
60
private final ApplicationContextAotGenerator aotGenerator = new ApplicationContextAotGenerator ();
57
61
@@ -97,30 +101,33 @@ public final RuntimeHints getRuntimeHints() {
97
101
* @throws TestContextAotException if an error occurs during AOT processing
98
102
*/
99
103
public void processAheadOfTime (Stream <Class <?>> testClasses ) throws TestContextAotException {
100
- MultiValueMap <MergedContextConfiguration , Class <?>> map = new LinkedMultiValueMap <>();
101
- testClasses .forEach (testClass -> map .add (buildMergedContextConfiguration (testClass ), testClass ));
102
-
103
- map .forEach ((mergedConfig , classes ) -> {
104
- // System.err.println(mergedConfig + " -> " + classes);
105
- if (logger .isDebugEnabled ()) {
106
- logger .debug ("Generating AOT artifacts for test classes [%s]"
107
- .formatted (classes .stream ().map (Class ::getCanonicalName ).toList ()));
108
- }
104
+ MultiValueMap <MergedContextConfiguration , Class <?>> mergedConfigMappings = new LinkedMultiValueMap <>();
105
+ testClasses .forEach (testClass -> mergedConfigMappings .add (buildMergedContextConfiguration (testClass ), testClass ));
106
+ processAheadOfTime (mergedConfigMappings );
107
+ }
108
+
109
+ private void processAheadOfTime (MultiValueMap <MergedContextConfiguration , Class <?>> mergedConfigMappings ) {
110
+ MultiValueMap <ClassName , Class <?>> initializerClassMappings = new LinkedMultiValueMap <>();
111
+ mergedConfigMappings .forEach ((mergedConfig , testClasses ) -> {
112
+ logger .debug (LogMessage .format ("Generating AOT artifacts for test classes %s" ,
113
+ testClasses .stream ().map (Class ::getName ).toList ()));
109
114
try {
110
115
// Use first test class discovered for a given unique MergedContextConfiguration.
111
- Class <?> testClass = classes .get (0 );
116
+ Class <?> testClass = testClasses .get (0 );
112
117
DefaultGenerationContext generationContext = createGenerationContext (testClass );
113
- ClassName className = processAheadOfTime (mergedConfig , generationContext );
114
- // TODO Store ClassName in a map analogous to TestContextAotProcessor in Spring Native.
118
+ ClassName initializer = processAheadOfTime (mergedConfig , generationContext );
119
+ Assert .state (!initializerClassMappings .containsKey (initializer ),
120
+ () -> "ClassName [%s] already encountered" .formatted (initializer .reflectionName ()));
121
+ initializerClassMappings .addAll (initializer , testClasses );
115
122
generationContext .writeGeneratedContent ();
116
123
}
117
124
catch (Exception ex ) {
118
- if (logger .isWarnEnabled ()) {
119
- logger .warn ("Failed to generate AOT artifacts for test classes [%s]"
120
- .formatted (classes .stream ().map (Class ::getCanonicalName ).toList ()), ex );
121
- }
125
+ logger .warn (LogMessage .format ("Failed to generate AOT artifacts for test classes [%s]" ,
126
+ testClasses .stream ().map (Class ::getName ).toList ()), ex );
122
127
}
123
128
});
129
+
130
+ generateAotTestMappings (initializerClassMappings );
124
131
}
125
132
126
133
/**
@@ -143,7 +150,7 @@ ClassName processAheadOfTime(MergedContextConfiguration mergedConfig,
143
150
}
144
151
catch (Throwable ex ) {
145
152
throw new TestContextAotException ("Failed to process test class [%s] for AOT"
146
- .formatted (mergedConfig .getTestClass ().getCanonicalName ()), ex );
153
+ .formatted (mergedConfig .getTestClass ().getName ()), ex );
147
154
}
148
155
}
149
156
@@ -154,7 +161,7 @@ ClassName processAheadOfTime(MergedContextConfiguration mergedConfig,
154
161
* create {@link GenericApplicationContext GenericApplicationContexts}.
155
162
* @throws TestContextAotException if an error occurs while loading the application
156
163
* context or if one of the prerequisites is not met
157
- * @see SmartContextLoader #loadContextForAotProcessing(MergedContextConfiguration)
164
+ * @see AotContextLoader #loadContextForAotProcessing(MergedContextConfiguration)
158
165
*/
159
166
private GenericApplicationContext loadContextForAotProcessing (
160
167
MergedContextConfiguration mergedConfig ) throws TestContextAotException {
@@ -164,7 +171,7 @@ private GenericApplicationContext loadContextForAotProcessing(
164
171
Assert .notNull (contextLoader , """
165
172
Cannot load an ApplicationContext with a NULL 'contextLoader'. \
166
173
Consider annotating test class [%s] with @ContextConfiguration or \
167
- @ContextHierarchy.""" .formatted (testClass .getCanonicalName ()));
174
+ @ContextHierarchy.""" .formatted (testClass .getName ()));
168
175
169
176
if (contextLoader instanceof AotContextLoader aotContextLoader ) {
170
177
try {
@@ -176,13 +183,13 @@ private GenericApplicationContext loadContextForAotProcessing(
176
183
catch (Exception ex ) {
177
184
throw new TestContextAotException (
178
185
"Failed to load ApplicationContext for AOT processing for test class [%s]"
179
- .formatted (testClass .getCanonicalName ()), ex );
186
+ .formatted (testClass .getName ()), ex );
180
187
}
181
188
}
182
189
throw new TestContextAotException ("""
183
190
Cannot generate AOT artifacts for test class [%s]. The configured \
184
191
ContextLoader [%s] must be an AotContextLoader and must create a \
185
- GenericApplicationContext.""" .formatted (testClass .getCanonicalName (),
192
+ GenericApplicationContext.""" .formatted (testClass .getName (),
186
193
contextLoader .getClass ().getName ()));
187
194
}
188
195
@@ -203,4 +210,18 @@ private String nextTestContextId() {
203
210
return "TestContext%03d_" .formatted (this .sequence .incrementAndGet ());
204
211
}
205
212
213
+ private void generateAotTestMappings (MultiValueMap <ClassName , Class <?>> initializerClassMappings ) {
214
+ ClassNameGenerator classNameGenerator = new ClassNameGenerator (AotTestMappings .class );
215
+ DefaultGenerationContext generationContext =
216
+ new DefaultGenerationContext (classNameGenerator , this .generatedFiles , this .runtimeHints );
217
+ GeneratedClasses generatedClasses = generationContext .getGeneratedClasses ();
218
+
219
+ AotTestMappingsCodeGenerator codeGenerator =
220
+ new AotTestMappingsCodeGenerator (initializerClassMappings , generatedClasses );
221
+ generationContext .writeGeneratedContent ();
222
+ String className = codeGenerator .getGeneratedClass ().getName ().reflectionName ();
223
+ this .runtimeHints .reflection ().registerType (TypeReference .of (className ),
224
+ builder -> builder .withMembers (MemberCategory .INVOKE_PUBLIC_METHODS ));
225
+ }
226
+
206
227
}
0 commit comments