Skip to content

Commit 17b51fe

Browse files
committed
Test all in batches
1 parent ec0a91b commit 17b51fe

File tree

4 files changed

+224
-22
lines changed

4 files changed

+224
-22
lines changed

.github/workflows/test-all-metadata.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ jobs:
2727
- name: "🕸️ Populate matrix"
2828
id: set-matrix
2929
run: |
30-
./gradlew generateMatrixMatchingCoordinates -Pcoordinates=all
30+
./gradlew generateMatrixBatchedCoordinates -Pbatches=16
3131
3232
test-all-metadata:
3333
if: github.repository == 'oracle/graalvm-reachability-metadata'
3434
name: "🧪 ${{ matrix.coordinates }} (GraalVM for JDK ${{ matrix.version }} @ ${{ matrix.os }})"
3535
runs-on: ${{ matrix.os }}
36-
timeout-minutes: 20
36+
timeout-minutes: 120
3737
needs: get-all-metadata
3838
strategy:
3939
fail-fast: false
@@ -56,7 +56,7 @@ jobs:
5656
native-image-job-reports: 'true'
5757
- name: "Pull allowed docker images"
5858
run: |
59-
./gradlew pullAllowedDockerImages --coordinates=${{ matrix.coordinates }}
59+
./gradlew pullAllowedDockerImagesMatching -Pcoordinates=${{ matrix.coordinates }}
6060
- name: "Disable docker networking"
6161
run: bash ./.github/workflows/disable-docker.sh
6262
- name: "🧪 Run '${{ matrix.coordinates }}' tests"

ci.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"generateMatrixMatchingCoordinates": {
3-
"java": ["17", "latest-ea"],
2+
"generateMatrixBatchedCoordinates": {
3+
"java": ["17", "21", "25", "latest-ea"],
44
"os": ["ubuntu-latest"]
55
},
66
"generateChangedCoordinatesMatrix": {
@@ -10,5 +10,9 @@
1010
"generateInfrastructureChangedCoordinatesMatrix": {
1111
"java": ["17", "21", "25", "latest-ea"],
1212
"os": ["ubuntu-latest"]
13+
},
14+
"generateMatrixMatchingCoordinates": {
15+
"java": ["17", "latest-ea"],
16+
"os": ["ubuntu-latest"]
1317
}
1418
}

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

Lines changed: 162 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ plugins {
1010
}
1111

1212
import groovy.json.JsonOutput
13+
import groovy.json.JsonSlurper
1314
import org.graalvm.internal.tck.ContributionTask
1415
import org.graalvm.internal.tck.DockerTask
1516
import org.graalvm.internal.tck.ConfigFilesChecker
17+
import org.graalvm.internal.tck.DockerUtils
18+
import org.graalvm.internal.tck.PullImagesFromFileTask
1619
import org.graalvm.internal.tck.ScaffoldTask
1720
import org.graalvm.internal.tck.GrypeTask
1821
import org.graalvm.internal.tck.TestedVersionUpdaterTask
@@ -42,7 +45,47 @@ def writeGithubOutput(String key, String value) {
4245
}
4346

4447
String coordinateFilter = Objects.requireNonNullElse(project.findProperty("coordinates"), "")
45-
List<String> matchingCoordinates = tck.getMatchingCoordinates(coordinateFilter)
48+
49+
// Support fractional batching coordinates in the form "k/n" (e.g., "1/16")
50+
boolean isFractionalBatch(String s) {
51+
return s != null && (s ==~ /\d+\/\d+/)
52+
}
53+
54+
List<Integer> parseFraction(String s) {
55+
def m = s =~ /(\d+)\/(\d+)/
56+
if (!m.matches()) return null
57+
int k = (m[0][1] as int)
58+
int n = (m[0][2] as int)
59+
return [k, n]
60+
}
61+
62+
List<String> computeBatchedCoordinates(List<String> allCoords, int k, int n) {
63+
if (n <= 0) throw new GradleException("Invalid batches denominator: ${n}")
64+
if (k < 1 || k > n) throw new GradleException("Invalid batch index: ${k}/${n}")
65+
def sorted = new ArrayList<String>(allCoords)
66+
java.util.Collections.sort(sorted)
67+
int target = (k - 1)
68+
def result = []
69+
int i = 0
70+
for (String c : sorted) {
71+
if ((i % n) == target) {
72+
result.add(c)
73+
}
74+
i++
75+
}
76+
return result
77+
}
78+
79+
List<String> matchingCoordinates
80+
if (isFractionalBatch(coordinateFilter)) {
81+
def frac = parseFraction(coordinateFilter)
82+
int k = frac[0]
83+
int n = frac[1]
84+
List<String> all = tck.getMatchingCoordinates("all")
85+
matchingCoordinates = computeBatchedCoordinates(all, k, n)
86+
} else {
87+
matchingCoordinates = tck.getMatchingCoordinates(coordinateFilter)
88+
}
4689

4790
// gradle test -Pcoordinates=<maven-coordinates>
4891
Provider<Task> test = tasks.register("test", DefaultTask) { task ->
@@ -78,6 +121,79 @@ tasks.named("check").configure {
78121
dependsOn(checkstyle)
79122
}
80123

124+
final String METADATA_GROUP = "Metadata"
125+
126+
Provider<Task> pullAllowedDockerImagesMatching = tasks.register("pullAllowedDockerImagesMatching", DefaultTask) { task ->
127+
task.setDescription("Pull allowed docker images for all matching coordinates")
128+
task.setGroup(METADATA_GROUP)
129+
task.doFirst {
130+
if (matchingCoordinates == null || matchingCoordinates.isEmpty()) {
131+
throw new GradleException("No matching coordinates found for property 'coordinates'. Provide -Pcoordinates=<filter> or a fractional batch 'k/n'.")
132+
}
133+
println "Pulling allowed docker images for ${matchingCoordinates.size()} coordinate(s):"
134+
matchingCoordinates.each { c -> println(" - ${c}") }
135+
}
136+
}
137+
138+
// Collect unique required images across matching coordinates and filter by allowed list
139+
tasks.register("collectRequiredAllowedDockerImagesMatching", DefaultTask) { task ->
140+
task.setDescription("Collect unique allowed docker images required by matching coordinates into a file")
141+
task.setGroup(METADATA_GROUP)
142+
File out = new File(buildDir, "required-docker-images-union.txt")
143+
// Declare the output so Gradle can track it
144+
task.outputs.file(out)
145+
task.doFirst {
146+
if (matchingCoordinates == null || matchingCoordinates.isEmpty()) {
147+
throw new GradleException("No matching coordinates found for property 'coordinates'. Provide -Pcoordinates=<filter> or a fractional batch 'k/n'.")
148+
}
149+
Set<String> unionRequired = new LinkedHashSet<>()
150+
matchingCoordinates.each { c ->
151+
def parts = c.split(":")
152+
if (parts.length < 3) {
153+
logger.warn("Skipping invalid coordinates: ${c}")
154+
return
155+
}
156+
def group = parts[0]
157+
def artifact = parts[1]
158+
def version = parts[2]
159+
File f = project.file("tests/src/${group}/${artifact}/${version}/required-docker-images.txt")
160+
if (f.exists()) {
161+
f.readLines()
162+
.collect { it?.trim() }
163+
.findAll { it && !it.startsWith("#") }
164+
.each { unionRequired.add(it) }
165+
}
166+
}
167+
168+
Set<String> allowed = DockerUtils.getAllAllowedImages()
169+
def notAllowed = unionRequired.findAll { !allowed.contains(it) }
170+
if (!notAllowed.isEmpty()) {
171+
throw new GradleException("The following images are not in the allowed list: ${notAllowed}. " +
172+
"If you need them, add Dockerfiles under tests/tck-build-logic/src/main/resources/allowed-docker-images " +
173+
"per CONTRIBUTING.md, or adjust required-docker-images.txt files.")
174+
}
175+
176+
out.parentFile.mkdirs()
177+
def finalList = unionRequired.findAll { allowed.contains(it) }.toList()
178+
out.text = finalList.join(System.lineSeparator())
179+
println "Collected ${finalList.size()} required allowed image(s):"
180+
finalList.each { println(" - ${it}") }
181+
}
182+
}
183+
184+
// Pull the collected unique images (once)
185+
tasks.register("pullRequiredAllowedDockerImagesMatching", PullImagesFromFileTask.class) { task ->
186+
task.setDescription("Pull unique allowed docker images required by matching coordinates")
187+
task.setGroup(METADATA_GROUP)
188+
task.dependsOn("collectRequiredAllowedDockerImagesMatching")
189+
task.imagesFile.set(new File(buildDir, "required-docker-images-union.txt"))
190+
}
191+
192+
// Rewire the aggregate to depend on the unique pull task
193+
pullAllowedDockerImagesMatching.configure {
194+
dependsOn("pullRequiredAllowedDockerImagesMatching")
195+
}
196+
81197
// Here we want to configure all test and checkstyle tasks for all filtered subprojects
82198
for (String coordinates in matchingCoordinates) {
83199
String testTaskName = generateTaskName("test", coordinates)
@@ -127,6 +243,13 @@ for (String coordinates in matchingCoordinates) {
127243
javaTest.configure {
128244
dependsOn(javaTestTaskName)
129245
}
246+
247+
String pullDockerTaskName = generateTaskName("pullAllowedDockerImages", coordinates)
248+
if ((!tasks.getNames().contains(pullDockerTaskName))) {
249+
tasks.register(pullDockerTaskName, DockerTask.class) { t ->
250+
t.setCoordinates(coordinates)
251+
}
252+
}
130253
}
131254

132255
// gradle diff -PbaseCommit=<base-commit> -PnewCommit=<new-commit>
@@ -157,15 +280,23 @@ if (project.hasProperty("baseCommit")) {
157280
}
158281
}
159282

160-
def matrixDefault = [
161-
"version": [
162-
"17",
163-
"latest-ea"
164-
],
165-
"os" : ["ubuntu-latest"]
166-
]
283+
Map<String, Object> loadCi() {
284+
File f = project.file("ci.json")
285+
if (!f.exists()) {
286+
throw new GradleException("Missing ci.json in repo root")
287+
}
288+
new JsonSlurper().parse(f) as Map<String, Object>
289+
}
167290

168-
final String METADATA_GROUP = "Metadata"
291+
Map<String, List<String>> matrixDefaultsFor(String section) {
292+
def ci = loadCi()
293+
def sec = ci[section]
294+
if (sec == null) throw new GradleException("Missing '${section}' in ci.json")
295+
[
296+
"version": (sec["java"] as List<String>),
297+
"os": (sec["os"] as List<String>)
298+
]
299+
}
169300

170301
// gradle generateMatrixMatchingCoordinates -Pcoordinates=<maven-coordinates>
171302
Provider<Task> generateMatrixMatchingCoordinates = tasks.register("generateMatrixMatchingCoordinates", DefaultTask) { task ->
@@ -175,7 +306,25 @@ Provider<Task> generateMatrixMatchingCoordinates = tasks.register("generateMatri
175306
def matrix = [
176307
"coordinates": matchingCoordinates
177308
]
178-
matrix.putAll(matrixDefault)
309+
matrix.putAll(matrixDefaultsFor("generateMatrixMatchingCoordinates"))
310+
writeGithubOutput("matrix", JsonOutput.toJson(matrix))
311+
}
312+
}
313+
314+
// gradle generateMatrixBatchedCoordinates [-Pbatches=<n>]
315+
Provider<Task> generateMatrixBatchedCoordinates = tasks.register("generateMatrixBatchedCoordinates", DefaultTask) { task ->
316+
task.setDescription("Returns matrix definition populated with fractional batch coordinates (k/n)")
317+
task.setGroup(METADATA_GROUP)
318+
task.doFirst {
319+
int batches = project.hasProperty("batches") ? Integer.parseInt(project.findProperty("batches").toString()) : 16
320+
if (batches <= 0) {
321+
throw new GradleException("Invalid 'batches' value: ${batches}")
322+
}
323+
List<String> coords = (1..batches).collect { i -> "${i}/${batches}" }
324+
def matrix = [
325+
"coordinates": coords
326+
]
327+
matrix.putAll(matrixDefaultsFor("generateMatrixBatchedCoordinates"))
179328
writeGithubOutput("matrix", JsonOutput.toJson(matrix))
180329
}
181330
}
@@ -193,13 +342,11 @@ Provider<Task> generateChangedCoordinatesMatrix = tasks.register("generateChange
193342
def matrix = [
194343
"coordinates": noneFound ? [] : diffCoordinates
195344
]
196-
matrix.putAll(matrixDefault)
345+
matrix.putAll(matrixDefaultsFor("generateChangedCoordinatesMatrix"))
197346
if (noneFound) {
198347
println "No changed coordinates were found!"
199348
}
200349

201-
matrix.version.add("21")
202-
matrix.version.add("25")
203350

204351
writeGithubOutput("matrix", JsonOutput.toJson(matrix))
205352
writeGithubOutput("none-found", noneFound.toString())
@@ -215,7 +362,7 @@ Provider<Task> generateInfrastructureChangedCoordinatesMatrix = tasks.register("
215362
boolean infraChanged = true
216363
// Pre-selected modules; keep stable for infra verification
217364
List<String> preselectedModules = [
218-
'org.bouncycastle:bcpkix-jdk15on',
365+
'ch.qos.logback:logback-classic',
219366
'org.bouncycastle:bcpkix-jdk18on',
220367
'io.netty:netty-common',
221368
'io.grpc:grpc-core',
@@ -243,10 +390,8 @@ Provider<Task> generateInfrastructureChangedCoordinatesMatrix = tasks.register("
243390
def matrix = [
244391
"coordinates": selected
245392
]
246-
matrix.putAll(matrixDefault)
393+
matrix.putAll(matrixDefaultsFor("generateInfrastructureChangedCoordinatesMatrix"))
247394

248-
matrix.version.add("21")
249-
matrix.version.add("25")
250395

251396
writeGithubOutput("matrix", JsonOutput.toJson(matrix))
252397
writeGithubOutput("none-found", "false")
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package org.graalvm.internal.tck;
2+
3+
import org.gradle.api.DefaultTask;
4+
import org.gradle.api.file.RegularFileProperty;
5+
import org.gradle.api.tasks.InputFile;
6+
import org.gradle.api.tasks.TaskAction;
7+
import org.gradle.process.ExecOperations;
8+
import org.gradle.api.GradleException;
9+
10+
import javax.inject.Inject;
11+
import java.io.File;
12+
import java.io.IOException;
13+
import java.nio.file.Files;
14+
import java.util.List;
15+
16+
/**
17+
* Pulls docker images listed in a text file (one image per line).
18+
* Fails the task immediately if any image pull fails to ensure batch execution fails on single error.
19+
*/
20+
public abstract class PullImagesFromFileTask extends DefaultTask {
21+
22+
@InputFile
23+
public abstract RegularFileProperty getImagesFile();
24+
25+
@Inject
26+
protected abstract ExecOperations getExecOperations();
27+
28+
@TaskAction
29+
public void run() throws IOException {
30+
File inFile = getImagesFile().get().getAsFile();
31+
if (!inFile.exists()) {
32+
getLogger().lifecycle("No required docker images collected: {}", inFile.getAbsolutePath());
33+
return;
34+
}
35+
36+
List<String> images = Files.readAllLines(inFile.toPath()).stream()
37+
.map(String::trim)
38+
.filter(s -> !s.isEmpty() && !s.startsWith("#"))
39+
.toList();
40+
41+
for (String image : images) {
42+
getLogger().lifecycle("Pulling image {}...", image);
43+
try {
44+
getExecOperations().exec(spec -> {
45+
spec.setExecutable("docker");
46+
spec.args("pull", image);
47+
});
48+
} catch (Exception e) {
49+
throw new GradleException("Failed to pull image " + image + ": " + e.getMessage(), e);
50+
}
51+
}
52+
}
53+
}

0 commit comments

Comments
 (0)