Skip to content

Commit 8454626

Browse files
committed
Introduce fixTestNIRun gradle task.
1 parent dbf4f2a commit 8454626

File tree

4 files changed

+225
-0
lines changed

4 files changed

+225
-0
lines changed

tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-harness.gradle

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import org.graalvm.internal.tck.ContributionTask
1414
import org.graalvm.internal.tck.DockerTask
1515
import org.graalvm.internal.tck.ConfigFilesChecker
1616
import org.graalvm.internal.tck.ScaffoldTask
17+
import org.graalvm.internal.tck.FixTestNativeImageRun
1718
import org.graalvm.internal.tck.GrypeTask
1819
import org.graalvm.internal.tck.GenerateMetadataTask
1920
import org.graalvm.internal.tck.TestedVersionUpdaterTask
@@ -223,6 +224,11 @@ tasks.register("generateMetadata", GenerateMetadataTask.class) { task ->
223224
task.setDescription("Generates metadata based on provided tests.")
224225
task.setGroup(METADATA_GROUP)
225226
}
227+
// gradle fixTestNIRun --testLibraryCoordinates=<maven-coordinates> --newLibraryVersion=<library version which needs fix>
228+
tasks.register("fixTestNIRun", FixTestNativeImageRun.class) { task ->
229+
task.setDescription("Generates metadata and prepares pull request for contibuting on metadata repository based on provided tests.")
230+
task.setGroup(METADATA_GROUP)
231+
}
226232

227233
tasks.register("checkConfigFiles", ConfigFilesChecker.class) { task ->
228234
task.setDescription("Checks content of config files for a new library.")
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
package org.graalvm.internal.tck;
2+
3+
import org.graalvm.internal.tck.utils.InteractiveTaskUtils;
4+
import org.graalvm.internal.tck.utils.MetadataGenerationUtils;
5+
import com.fasterxml.jackson.annotation.JsonInclude;
6+
import com.fasterxml.jackson.core.type.TypeReference;
7+
import com.fasterxml.jackson.core.util.DefaultIndenter;
8+
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
9+
import com.fasterxml.jackson.databind.ObjectMapper;
10+
import com.fasterxml.jackson.databind.SerializationFeature;
11+
import org.graalvm.internal.tck.model.MetadataVersionsIndexEntry;
12+
import org.gradle.api.DefaultTask;
13+
import org.gradle.api.file.ProjectLayout;
14+
import org.gradle.api.tasks.Input;
15+
import org.gradle.api.tasks.TaskAction;
16+
import org.gradle.api.tasks.options.Option;
17+
import org.gradle.process.ExecOperations;
18+
import org.gradle.api.GradleException;
19+
20+
import javax.inject.Inject;
21+
import java.io.IOException;
22+
import java.io.File;
23+
import java.util.ArrayList;
24+
import java.util.List;
25+
import java.nio.file.Files;
26+
import java.nio.file.Path;
27+
import java.util.regex.Pattern;
28+
29+
public abstract class FixTestNativeImageRun extends DefaultTask {
30+
private static final String GRADLEW = "gradlew";
31+
32+
private String testLibraryCoordinates;
33+
private String newLibraryVersion;
34+
35+
@Inject
36+
protected abstract ProjectLayout getLayout();
37+
38+
@Inject
39+
protected abstract ExecOperations getExecOperations();
40+
41+
@Option(option = "newLibraryVersion", description = "New version of library that is failing")
42+
public void setNewLibraryVersion(String newLibraryVersion) {
43+
this.newLibraryVersion = newLibraryVersion;
44+
}
45+
46+
@Input
47+
public String getNewLibraryVersion() {
48+
return newLibraryVersion;
49+
}
50+
51+
@Option(option = "testLibraryCoordinates", description = "Coordinates in the form of group:artifact:version")
52+
public void setTestLibraryCoordinates(String testLibraryCoordinates) {
53+
this.testLibraryCoordinates = testLibraryCoordinates;
54+
}
55+
56+
@Input
57+
public String getTestLibraryCoordinates() {
58+
return testLibraryCoordinates;
59+
}
60+
61+
@TaskAction
62+
public void run() throws IOException {
63+
Path testsDirectory = MetadataGenerationUtils.computeTestsDirectory(getLayout(), testLibraryCoordinates);
64+
Path gradlewPath = MetadataGenerationUtils.getPathFromProject(getLayout(), GRADLEW);
65+
66+
Coordinates baseCoords = Coordinates.parse(testLibraryCoordinates);
67+
String newCoordsString = baseCoords.group() + ":" + baseCoords.artifact() + ":" + newLibraryVersion;
68+
Coordinates newCoords = Coordinates.parse(newCoordsString);
69+
updateIndexJson(newCoords);
70+
71+
Path metadataDirectory = MetadataGenerationUtils.computeMetadataDirectory(getLayout(), newCoordsString);
72+
Files.createDirectories(metadataDirectory);
73+
74+
// Ensure the tests build.gradle has an agent block; if not, create user-code-filter.json and add the agent block.
75+
Path buildFilePath = testsDirectory.resolve("build.gradle");
76+
if (!Files.isRegularFile(buildFilePath)) {
77+
throw new RuntimeException("Cannot find tests build file at: " + buildFilePath);
78+
}
79+
String buildGradle = Files.readString(buildFilePath, java.nio.charset.StandardCharsets.UTF_8);
80+
boolean hasAgentBlock = Pattern.compile("(?s)\\bagent\\s*\\{").matcher(buildGradle).find();
81+
if (!hasAgentBlock) {
82+
MetadataGenerationUtils.addUserCodeFilterFile(testsDirectory, java.util.List.of(baseCoords.group()));
83+
MetadataGenerationUtils.addAgentConfigBlock(testsDirectory);
84+
}
85+
86+
MetadataGenerationUtils.collectMetadata(getExecOperations(), testsDirectory, getLayout(), newCoordsString, gradlewPath, newLibraryVersion);
87+
88+
MetadataGenerationUtils.createIndexJsonSpecificVersion(metadataDirectory);
89+
90+
// At the end, attempt to run tests with the new library version.
91+
// If the build fails, it can be due agent's non-deterministic nature.
92+
InteractiveTaskUtils.printUserInfo("Running the test with updated metadata");
93+
var execOutput = new java.io.ByteArrayOutputStream();
94+
var testResult = runTestWithVersion(gradlewPath, testLibraryCoordinates, newLibraryVersion, execOutput);
95+
if (testResult.getExitValue() != 0) {
96+
InteractiveTaskUtils.printUserInfo("Test run failed, running agent again");
97+
MetadataGenerationUtils.collectMetadata(getExecOperations(), testsDirectory, getLayout(), newCoordsString, gradlewPath);
98+
testResult = runTestWithVersion(gradlewPath, testLibraryCoordinates, newLibraryVersion, execOutput);
99+
if (testResult.getExitValue() != 0) {
100+
throw new GradleException("Test run failed. See output:\n" + execOutput);
101+
}
102+
}
103+
}
104+
105+
106+
private org.gradle.process.ExecResult runTestWithVersion(Path gradlewPath, String coords, String version, java.io.ByteArrayOutputStream execOutput) {
107+
return getExecOperations().exec(execSpec -> {
108+
execSpec.setExecutable(gradlewPath.toString());
109+
execSpec.setArgs(java.util.List.of("clean", "test", "-Pcoordinates=" + coords));
110+
execSpec.environment("GVM_TCK_LV", version);
111+
execSpec.setStandardOutput(execOutput);
112+
});
113+
}
114+
115+
private void updateIndexJson(Coordinates newCoords) throws IOException {
116+
String indexPathTemplate = "metadata/$group$/$artifact$/index.json";
117+
File indexFile = MetadataGenerationUtils.getPathFromProject(getLayout(), CoordinateUtils.replace(indexPathTemplate, newCoords)).toFile();
118+
119+
ObjectMapper objectMapper = new ObjectMapper()
120+
.enable(SerializationFeature.INDENT_OUTPUT)
121+
.setSerializationInclusion(JsonInclude.Include.NON_NULL);
122+
123+
// Read existing entries if file exists, otherwise start fresh
124+
List<MetadataVersionsIndexEntry> entries = new ArrayList<>();
125+
if (indexFile.exists()) {
126+
entries = objectMapper.readValue(indexFile, new TypeReference<>() {});
127+
}
128+
129+
// Remove 'latest' flag from any existing latest entry
130+
for (int i = 0; i < entries.size(); i++) {
131+
MetadataVersionsIndexEntry entry = entries.get(i);
132+
if (Boolean.TRUE.equals(entry.latest())) {
133+
entries.set(i, new MetadataVersionsIndexEntry(
134+
null, // latest removed
135+
entry.override(),
136+
entry.module(),
137+
entry.defaultFor(),
138+
entry.metadataVersion(),
139+
entry.testedVersions()
140+
));
141+
}
142+
}
143+
144+
// Add the new entry and mark it as latest
145+
String moduleName = newCoords.group() + ":" + newCoords.artifact();
146+
List<String> testedVersions = new ArrayList<>();
147+
testedVersions.add(newCoords.version());
148+
149+
MetadataVersionsIndexEntry newEntry = new MetadataVersionsIndexEntry(
150+
Boolean.TRUE,
151+
null,
152+
moduleName,
153+
null,
154+
newCoords.version(),
155+
testedVersions
156+
);
157+
entries.add(newEntry);
158+
159+
DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();
160+
prettyPrinter.indentArraysWith(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE);
161+
String json = objectMapper.writer(prettyPrinter).writeValueAsString(entries);
162+
if (!json.endsWith(System.lineSeparator())) {
163+
json = json + System.lineSeparator();
164+
}
165+
Files.writeString(indexFile.toPath(), json, java.nio.charset.StandardCharsets.UTF_8);
166+
}
167+
}

tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/GenerateMetadataTask.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,7 @@ public void run() throws IOException {
6666
}
6767
MetadataGenerationUtils.addAgentConfigBlock(testsDirectory);
6868
MetadataGenerationUtils.collectMetadata(getExecOperations(), testsDirectory, getLayout(), coordinates, gradlewPath);
69+
Path metadataDirectory = MetadataGenerationUtils.computeMetadataDirectory(getLayout(), coordinates);
70+
MetadataGenerationUtils.createIndexJsonSpecificVersion(metadataDirectory);
6971
}
7072
}

tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/utils/MetadataGenerationUtils.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,22 @@ public static void collectMetadata(ExecOperations execOps, Path testsDirectory,
106106
invokeCommand(execOps, gradlew + " metadataCopy --task test --dir " + metadataDirectory, "Cannot perform metadata copy", testsDirectory);
107107
}
108108

109+
/**
110+
* Runs Gradle tasks to generate metadata using the agent with a specific GVM_TCK_LV
111+
* and then copies the results into the computed metadata directory for the given coordinates.
112+
*/
113+
public static void collectMetadata(ExecOperations execOps, Path testsDirectory, ProjectLayout layout, String coordinates, Path gradlew, String gvmTckLv) {
114+
Path metadataDirectory = MetadataGenerationUtils.computeMetadataDirectory(layout, coordinates);
115+
116+
Map<String, String> env = Map.of("GVM_TCK_LV", gvmTckLv);
117+
118+
InteractiveTaskUtils.printUserInfo("Generating metadata");
119+
invokeCommand(execOps, gradlew.toString(), List.of("-Pagent", "test"), env, "Cannot generate metadata", testsDirectory);
120+
121+
InteractiveTaskUtils.printUserInfo("Performing metadata copy");
122+
invokeCommand(execOps, gradlew.toString(), List.of("metadataCopy", "--task", "test", "--dir", metadataDirectory.toString()), env, "Cannot perform metadata copy", testsDirectory);
123+
}
124+
109125
public static void writeToFile(Path path, String content, StandardOpenOption writeOption) throws IOException {
110126
Files.createDirectories(path.getParent());
111127
Files.writeString(path, content, StandardCharsets.UTF_8, writeOption);
@@ -128,11 +144,22 @@ public static void invokeCommand(ExecOperations execOps, String command, String
128144
* Captures output and throws a RuntimeException if the exit code is non-zero.
129145
*/
130146
public static void invokeCommand(ExecOperations execOps, String executable, List<String> args, String errorMessage, Path workingDirectory) {
147+
invokeCommand(execOps, executable, args, null, errorMessage, workingDirectory);
148+
}
149+
150+
/**
151+
* Executes the given executable with arguments in an optional working directory via Gradle ExecOperations,
152+
* allowing custom environment variables.
153+
*/
154+
public static void invokeCommand(ExecOperations execOps, String executable, List<String> args, Map<String, String> env, String errorMessage, Path workingDirectory) {
131155
ByteArrayOutputStream execOutput = new ByteArrayOutputStream();
132156
var result = execOps.exec(execSpec -> {
133157
if (workingDirectory != null) {
134158
execSpec.setWorkingDir(workingDirectory);
135159
}
160+
if (env != null && !env.isEmpty()) {
161+
execSpec.environment(env);
162+
}
136163
execSpec.setExecutable(executable);
137164
execSpec.setArgs(args);
138165
execSpec.setStandardOutput(execOutput);
@@ -150,4 +177,27 @@ public static Path computeTestsDirectory(ProjectLayout layout, String coordinate
150177
public static Path computeMetadataDirectory(ProjectLayout layout, String coordinates) {
151178
return getPathFromProject(layout, CoordinateUtils.replace("metadata/$group$/$artifact$/$version$", CoordinateUtils.fromString(coordinates)));
152179
}
180+
181+
/**
182+
* After metadata is collected, create index.json inside the given version directory,
183+
* listing all files present in that directory.
184+
*/
185+
public static void createIndexJsonSpecificVersion(Path metadataDirectory) throws IOException {
186+
try (java.util.stream.Stream<Path> paths = Files.list(metadataDirectory)) {
187+
List<String> files = paths
188+
.filter(Files::isRegularFile)
189+
.map(p -> p.getFileName().toString())
190+
.filter(name -> !"index.json".equals(name))
191+
.sorted()
192+
.toList();
193+
194+
DefaultPrettyPrinter printer = new DefaultPrettyPrinter();
195+
printer.indentArraysWith(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE);
196+
String jsonVersionIndex = objectMapper.writer(printer).writeValueAsString(files);
197+
if (!jsonVersionIndex.endsWith(System.lineSeparator())) {
198+
jsonVersionIndex = jsonVersionIndex + System.lineSeparator();
199+
}
200+
Files.writeString(metadataDirectory.resolve("index.json"), jsonVersionIndex, StandardCharsets.UTF_8);
201+
}
202+
}
153203
}

0 commit comments

Comments
 (0)