Skip to content

Commit 019785a

Browse files
committed
Introduce CLI for triggering AOT test context processing
This commit introduces TestAotProcessor which replaces ProcessTestsAheadOfTimeCommand, removes the dependency on picocli, and aligns with the implementation of the AotProcessor in Spring Boot. TestAotProcessor is a command-line application (CLI) that scans the provided classpath roots for Spring integration test classes and then generates AOT artifacts for those test classes in the provided output directories. This CLI is only intended to be used by build tools. Closes gh-28825
1 parent 5115deb commit 019785a

File tree

5 files changed

+264
-162
lines changed

5 files changed

+264
-162
lines changed

spring-test/src/main/java/org/springframework/test/context/aot/ProcessTestsAheadOfTimeCommand.java

Lines changed: 0 additions & 91 deletions
This file was deleted.
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.context.aot;
18+
19+
import java.io.File;
20+
import java.io.IOException;
21+
import java.io.UncheckedIOException;
22+
import java.nio.file.Path;
23+
import java.nio.file.Paths;
24+
import java.util.Arrays;
25+
import java.util.Set;
26+
import java.util.stream.Stream;
27+
28+
import org.springframework.aot.generate.FileSystemGeneratedFiles;
29+
import org.springframework.aot.generate.GeneratedFiles;
30+
import org.springframework.aot.generate.GeneratedFiles.Kind;
31+
import org.springframework.aot.hint.RuntimeHints;
32+
import org.springframework.aot.nativex.FileNativeConfigurationWriter;
33+
import org.springframework.util.Assert;
34+
import org.springframework.util.FileSystemUtils;
35+
36+
/**
37+
* Command-line application that scans the provided classpath roots for Spring
38+
* integration test classes and then generates AOT artifacts for those test
39+
* classes in the provided output directories.
40+
*
41+
* <p><strong>For internal use only.</strong>
42+
*
43+
* @author Sam Brannen
44+
* @author Stephane Nicoll
45+
* @author Andy Wilkinson
46+
* @author Phillip Webb
47+
* @since 6.0
48+
* @see TestClassScanner
49+
* @see TestContextAotGenerator
50+
* @see FileNativeConfigurationWriter
51+
* @see org.springframework.boot.AotProcessor
52+
*/
53+
public class TestAotProcessor {
54+
55+
private final Path[] classpathRoots;
56+
57+
private final Path sourceOutput;
58+
59+
private final Path resourceOutput;
60+
61+
private final Path classOutput;
62+
63+
private final String groupId;
64+
65+
private final String artifactId;
66+
67+
68+
/**
69+
* Create a new processor for the specified test classpath roots and
70+
* general settings.
71+
*
72+
* @param classpathRoots the classpath roots to scan for test classes
73+
* @param sourceOutput the location of generated sources
74+
* @param resourceOutput the location of generated resources
75+
* @param classOutput the location of generated classes
76+
* @param groupId the group ID of the application, used to locate
77+
* {@code native-image.properties}
78+
* @param artifactId the artifact ID of the application, used to locate
79+
* {@code native-image.properties}
80+
*/
81+
public TestAotProcessor(Path[] classpathRoots, Path sourceOutput, Path resourceOutput, Path classOutput,
82+
String groupId, String artifactId) {
83+
84+
this.classpathRoots = classpathRoots;
85+
this.sourceOutput = sourceOutput;
86+
this.resourceOutput = resourceOutput;
87+
this.classOutput = classOutput;
88+
this.groupId = groupId;
89+
this.artifactId = artifactId;
90+
}
91+
92+
93+
/**
94+
* Trigger processing of the test classes in the configured classpath roots.
95+
*/
96+
public void process() {
97+
deleteExistingOutput();
98+
performAotProcessing();
99+
}
100+
101+
private void deleteExistingOutput() {
102+
deleteExistingOutput(this.sourceOutput, this.resourceOutput, this.classOutput);
103+
}
104+
105+
private void deleteExistingOutput(Path... paths) {
106+
for (Path path : paths) {
107+
try {
108+
FileSystemUtils.deleteRecursively(path);
109+
}
110+
catch (IOException ex) {
111+
throw new UncheckedIOException("Failed to delete existing output in '%s'".formatted(path), ex);
112+
}
113+
}
114+
}
115+
116+
private void performAotProcessing() {
117+
TestClassScanner scanner = new TestClassScanner(Set.of(this.classpathRoots));
118+
Stream<Class<?>> testClasses = scanner.scan();
119+
120+
GeneratedFiles generatedFiles = new FileSystemGeneratedFiles(this::getRoot);
121+
TestContextAotGenerator generator = new TestContextAotGenerator(generatedFiles);
122+
generator.processAheadOfTime(testClasses);
123+
124+
writeHints(generator.getRuntimeHints());
125+
}
126+
127+
private Path getRoot(Kind kind) {
128+
return switch (kind) {
129+
case SOURCE -> this.sourceOutput;
130+
case RESOURCE -> this.resourceOutput;
131+
case CLASS -> this.classOutput;
132+
};
133+
}
134+
135+
private void writeHints(RuntimeHints hints) {
136+
FileNativeConfigurationWriter writer =
137+
new FileNativeConfigurationWriter(this.resourceOutput, this.groupId, this.artifactId);
138+
writer.write(hints);
139+
}
140+
141+
142+
public static void main(String[] args) {
143+
int requiredArgs = 6;
144+
Assert.isTrue(args.length >= requiredArgs, () ->
145+
"Usage: %s <classpathRoots> <sourceOutput> <resourceOutput> <classOutput> <groupId> <artifactId>"
146+
.formatted(TestAotProcessor.class.getName()));
147+
Path[] classpathRoots = Arrays.stream(args[0].split(File.pathSeparator)).map(Paths::get).toArray(Path[]::new);
148+
Path sourceOutput = Paths.get(args[1]);
149+
Path resourceOutput = Paths.get(args[2]);
150+
Path classOutput = Paths.get(args[3]);
151+
String groupId = args[4];
152+
String artifactId = args[5];
153+
new TestAotProcessor(classpathRoots, sourceOutput, resourceOutput, classOutput, groupId, artifactId).process();
154+
}
155+
156+
}

spring-test/src/test/java/org/springframework/test/context/aot/AbstractAotTests.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,13 @@ Path classpathRoot() {
8888
}
8989
}
9090

91+
Path classpathRoot(Class<?> clazz) {
92+
try {
93+
return Paths.get(clazz.getProtectionDomain().getCodeSource().getLocation().toURI());
94+
}
95+
catch (Exception ex) {
96+
throw new RuntimeException(ex);
97+
}
98+
}
99+
91100
}

spring-test/src/test/java/org/springframework/test/context/aot/ProcessTestsAheadOfTimeCommandTests.java

Lines changed: 0 additions & 71 deletions
This file was deleted.

0 commit comments

Comments
 (0)