Skip to content

Commit 7e9e33b

Browse files
authored
Introduce fixTestNativeImageRun gradle task. (#787)
1 parent e597c5d commit 7e9e33b

File tree

11 files changed

+484
-10
lines changed

11 files changed

+484
-10
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ on:
77
- master
88
paths:
99
- 'ci.json'
10+
- 'java/org/graalvm/internal/tck/MetadataFilesCheckerTask.java'
1011

1112
concurrency:
1213
group: "workflow = ${{ github.workflow }}, ref = ${{ github.event.ref }}, pr = ${{ github.event.pull_request.id }}"

AGENTS.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
- Be assertive in code.
2020
- Write type annotations in all functions and most variables.
2121
- Document code without being too verbose.
22-
- In java, always import classes and use them without qualified names.
22+
- In Java and Groovy, always import classes and use them without qualified names.
2323

2424
## Testing individual components
2525

@@ -47,6 +47,17 @@
4747
- ./gradlew checkMetadataFiles -Pcoordinates=1/64
4848
- ./gradlew test -Pcoordinates=1/64
4949

50+
### Generating Metadata
51+
- Generate metadata for a certain library version:
52+
- ./gradlew generateMetadata -Pcoordinates=com.hazelcast:hazelcast:5.2.1
53+
- Generate metadata for a certain library version and create or update the user-code-filter.json:
54+
- ./gradlew generateMetadata -Pcoordinates=org.postgresql:postgresql:42.7.3 --agentAllowedPackages=org.example.app,com.acme.service
55+
56+
### Fix failing tasks
57+
58+
- Generates new metadata for library's new version which is failing native-image run:
59+
- ./gradlew fixTestNativeImageRun -PtestLibraryCoordinates=org.postgresql:postgresql:42.7.3 -PnewLibraryVersion=42.7.4
60+
5061
## Docker Image Vulnerability Scanning
5162
- Changed images between commits:
5263
- ./gradlew checkAllowedDockerImages --baseCommit=$(git rev-parse origin/master) --newCommit=$(git rev-parse HEAD)

build.gradle

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
* You should have received a copy of the CC0 legalcode along with this
55
* work. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
66
*/
7+
import groovy.json.JsonSlurper
8+
79
import java.util.concurrent.Executors
810
import java.util.concurrent.TimeUnit
911

@@ -150,6 +152,157 @@ static List<Map> testAllCommands(String gradleCmd, String target) {
150152
}
151153
}
152154

155+
/*
156+
* Helper utilities for environment setup, and backup/restore
157+
* used by testMetadataGeneration.
158+
*/
159+
def withUnsetTckDir(List<String> baseArgs) {
160+
if (System.getProperty("os.name").toLowerCase().contains("windows")) {
161+
["cmd.exe", "/d", "/c", "set GVM_TCK_TCKDIR= && " + baseArgs.join(' ')]
162+
} else {
163+
["/usr/bin/env", "-u", "GVM_TCK_TCKDIR"] + baseArgs
164+
}
165+
}
166+
167+
def backupDirIfExists(File srcDir, File backupDir) {
168+
boolean had = srcDir?.isDirectory()
169+
if (had) {
170+
backupDir.parentFile?.mkdirs()
171+
project.copy {
172+
from(srcDir)
173+
into(backupDir)
174+
}
175+
}
176+
had
177+
}
178+
179+
def restoreDir(boolean had, File srcDir, File backupDir) {
180+
if (had) {
181+
project.delete(srcDir)
182+
srcDir.mkdirs()
183+
project.copy {
184+
from(backupDir)
185+
into(srcDir)
186+
}
187+
} else {
188+
project.delete(srcDir)
189+
}
190+
}
191+
def backupFileIfExists(File srcFile, File backupFile) {
192+
boolean had = srcFile?.isFile()
193+
if (had) {
194+
backupFile.parentFile?.mkdirs()
195+
backupFile.text = srcFile.getText('UTF-8')
196+
}
197+
had
198+
}
199+
def restoreFileIfBackedUp(boolean had, File srcFile, File backupFile) {
200+
if (had && backupFile?.isFile()) {
201+
srcFile.text = backupFile.getText('UTF-8')
202+
}
203+
}
204+
205+
// Test metadata generation tasks
206+
def testMetadataGeneration(String coordinate, String gradleCmd, File logsDir, String newVersion) {
207+
208+
def fileSuffix = coordinate.replaceAll(File.separator, "-")
209+
210+
// Compute paths and snapshot current state to allow revert after the test
211+
def parts = coordinate.split(":")
212+
if (parts.length != 3) {
213+
throw new GradleException("Invalid coordinates '${coordinate}'. Expected 'group:artifact:version'.")
214+
}
215+
def group = parts[0]
216+
def artifact = parts[1]
217+
def version = parts[2]
218+
def gaPath = "${group}/${artifact}"
219+
def versionPath = "${gaPath}/${version}"
220+
221+
def versionDir = new File(tck.metadataRoot.get().asFile, versionPath)
222+
def testsDir = new File(tck.testRoot.get().asFile, versionPath)
223+
224+
def backupRoot = layout.buildDirectory.dir("gm-backups/${fileSuffix}").get().asFile
225+
def backupMetadataDir = new File(backupRoot, "metadata-backup")
226+
def backupTestsDir = new File(backupRoot, "tests-backup")
227+
backupRoot.mkdirs()
228+
boolean hadVersionDir = backupDirIfExists(versionDir, backupMetadataDir)
229+
boolean hadTestsDir = backupDirIfExists(testsDir, backupTestsDir)
230+
231+
try {
232+
// Build base args once, then prepend an OS-specific unset of GVM_TCK_TCKDIR
233+
def baseGenArgs = [gradleCmd, "generateMetadata", "-Pcoordinates=${coordinate}", "--agentAllowedPackages=org.postgresql"]
234+
List<String> genArgs = withUnsetTckDir(baseGenArgs)
235+
def genLogFile = new File(logsDir, "generateMetadata-${fileSuffix}.log")
236+
int genExit = project.ext.runLoggedCommand(genArgs, "generateMetadata (${coordinate})", genLogFile, project.rootDir)
237+
if (genExit != 0) {
238+
throw new GradleException("generateMetadata failed with exit code ${genExit} (see ${genLogFile})")
239+
}
240+
241+
// Verify output directory and index.json for the specific version
242+
if (!versionDir.isDirectory()) {
243+
throw new GradleException("Version directory not found: ${versionDir}")
244+
}
245+
def indexFile = new File(versionDir, "index.json")
246+
if (!indexFile.isFile()) {
247+
throw new GradleException("Missing index.json in ${versionDir}")
248+
}
249+
250+
def actualFiles = versionDir.listFiles()?.findAll { it.isFile() && it.name != 'index.json' }?.collect { it.name }?.sort() ?: []
251+
if (actualFiles.isEmpty()) {
252+
throw new GradleException("No metadata files found in ${versionDir} (besides index.json)")
253+
}
254+
255+
def indexList = (List) new JsonSlurper().parse(indexFile)
256+
def sortedIndex = indexList.collect { it.toString() }.sort()
257+
if (!sortedIndex.equals(actualFiles)) {
258+
println("==== BEGIN index.json mismatch (${coordinate})")
259+
println("index.json: ${sortedIndex}")
260+
println("actual: ${actualFiles}")
261+
println("==== END index.json mismatch (${coordinate})")
262+
throw new GradleException("index.json does not match files in ${versionDir}")
263+
}
264+
println("Verified generated metadata for ${coordinate}: ${versionDir}")
265+
} finally {
266+
// Revert metadata directory changes
267+
restoreDir(hadVersionDir, versionDir, backupMetadataDir)
268+
// Revert tests directory changes (including build.gradle and user-code-filter.json)
269+
restoreDir(hadTestsDir, testsDir, backupTestsDir)
270+
// Cleanup backups
271+
project.delete(backupRoot)
272+
}
273+
274+
275+
def newVersionDir = new File(tck.metadataRoot.get().asFile, "${gaPath}/${newVersion}")
276+
277+
def moduleIndexFile = new File(tck.metadataRoot.get().asFile, "${gaPath}/index.json")
278+
279+
def fixBackupRoot = layout.buildDirectory.dir("fixnir-backups/${fileSuffix}").get().asFile
280+
def backupNewMetadataDir = new File(fixBackupRoot, "new-metadata-backup")
281+
def fixBackupTestsDir = new File(fixBackupRoot, "new-metadata-backup")
282+
def backupModuleIndexFile = new File(fixBackupRoot, "module-index.json.bak")
283+
fixBackupRoot.mkdirs()
284+
hadTestsDir = backupDirIfExists(testsDir, fixBackupTestsDir)
285+
def hadNewVersionDir = backupDirIfExists(newVersionDir, backupNewMetadataDir)
286+
def hadModuleIndexFile = backupFileIfExists(moduleIndexFile, backupModuleIndexFile)
287+
288+
try {
289+
def baseFixArgs = [gradleCmd, "fixTestNativeImageRun", "-PtestLibraryCoordinates=${coordinate}", "-PnewLibraryVersion=${newVersion}"]
290+
List<String> fixArgs = withUnsetTckDir(baseFixArgs)
291+
def fixLogFile = new File(logsDir, "fixTestNativeImageRun-${fileSuffix}.log")
292+
int fixExit = project.ext.runLoggedCommand(fixArgs, "fixTestNativeImageRun (${coordinate} -> ${newVersion})", fixLogFile, project.rootDir)
293+
if (fixExit != 0) {
294+
throw new GradleException("fixTestNativeImageRun failed with exit code ${fixExit} (see ${fixLogFile})")
295+
}
296+
} finally {
297+
// Revert changes
298+
restoreDir(hadNewVersionDir, newVersionDir, backupNewMetadataDir)
299+
restoreDir(hadTestsDir, testsDir, fixBackupTestsDir)
300+
restoreFileIfBackedUp(hadModuleIndexFile, moduleIndexFile, backupModuleIndexFile)
301+
// Cleanup backups
302+
project.delete(fixBackupRoot)
303+
}
304+
}
305+
153306
tasks.register('testAllParallel') { t ->
154307
t.group = "verification"
155308
t.setDescription("For each target, run clean and pullAllowedDockerImages first (sequentially), then run style checks and individual testing stages (${testAllCommands('', '').collect { it.name }.join(', ')}) concurrently with isolated logs. Options: -Pcoordinates to specify a single coordinate or 1/64 for the pull step; -Pparallelism to control the number of concurrent tasks.")
@@ -244,9 +397,11 @@ tasks.register('testAllParallel') { t ->
244397
println("All commands succeeded for coordinates=${coordinate}. Logs in: ${logsDir}")
245398
}
246399
}
400+
def newVersionForFix = "42.7.4"
247401

248402
// Run parallel phases sequentially: first the selectedArtifact, then the selectedBatch
249403
runPhaseForCoordinate(selectedArtifact)
404+
testMetadataGeneration(selectedArtifact, gradleCmd, logsDir, newVersionForFix)
250405
runPhaseForCoordinate(selectedBatch)
251406
}
252407
}

docs/DEVELOPING.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,22 @@ Examples:
106106
./gradlew generateMetadata -Pcoordinates=org.postgresql:postgresql:42.7.3 --agentAllowedPackages=org.example.app,com.acme.service
107107
```
108108

109+
### Fix failing tasks
110+
111+
Use this when a library's new version causes native-image run test failures. The task will:
112+
- Update the module's metadata index.json to mark the new version as latest
113+
- Ensure the tests project has an agent block and a user-code-filter.json if missing
114+
- Run the agent to collect metadata, then re-run tests (with a retry if needed)
115+
116+
Required properties:
117+
- -PtestLibraryCoordinates=group:artifact:version (coordinates of an existing tested version whose tests you run)
118+
- -PnewLibraryVersion=version (the new upstream version number only; do not include group or artifact)
119+
120+
Example:
121+
```console
122+
./gradlew fixTestNativeImageRun -PtestLibraryCoordinates=org.postgresql:postgresql:42.7.3 -PnewLibraryVersion=42.7.4
123+
```
124+
109125
### Docker image vulnerability scanning
110126

111127
1. Scan only images affected in a commit range:
@@ -150,6 +166,7 @@ These tasks support the scheduled workflow that checks newer upstream library ve
150166
- Pull images (single lib): `./gradlew pullAllowedDockerImages -Pcoordinates=[group:artifact:version|k/n|all]`
151167
- Check metadata (single lib): `./gradlew checkMetadataFiles -Pcoordinates=[group:artifact:version|k/n|all]`
152168
- Generate metadata (single lib): `./gradlew generateMetadata -Pcoordinates=group:artifact:version`
169+
- Fix test that fails Native Image run for new library version: `./gradlew fixTestNativeImageRun -PtestLibraryCoordinates=group:artifact:version -PnewLibraryVersion=version`
153170
- Test (single lib): `./gradlew test -Pcoordinates=[group:artifact:version|k/n|all]`
154171
- Scan changed Docker images: `./gradlew checkAllowedDockerImages --baseCommit=<sha1> --newCommit=<sha2>`
155172
- Scan all Docker images: `./gradlew checkAllowedDockerImages`

tests/src/AGENTS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Instructions when developing tests
2+
3+
## Code Style
4+
- Tests should throw and assert against standard Java exceptions; do not use `GradleException` in test code.
5+
- The model may modify `buildArgs` in `build.gradle` only in `graalvmNative` and only to add `--add-opens` or `--add-exports arguments`.

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import org.graalvm.internal.tck.DockerTask
1616
import org.graalvm.internal.tck.MetadataFilesCheckerTask
1717
import org.graalvm.internal.tck.DockerUtils
1818
import org.graalvm.internal.tck.ScaffoldTask
19+
import org.graalvm.internal.tck.FixTestNativeImageRun
1920
import org.graalvm.internal.tck.GrypeTask
2021
import org.graalvm.internal.tck.GenerateMetadataTask
2122
import org.graalvm.internal.tck.TestedVersionUpdaterTask
@@ -309,7 +310,6 @@ tasks.register("pullAllowedDockerImages", ComputeAndPullAllowedDockerImagesTask.
309310
task.setGroup(METADATA_GROUP)
310311
}
311312

312-
313313
// contributing tasks
314314
tasks.register("scaffold", ScaffoldTask.class) { task ->
315315
task.setDescription("Creates a metadata and test scaffold for the given coordindates")
@@ -323,8 +323,14 @@ tasks.register("contribute", ContributionTask.class) { task ->
323323
task.setGroup(METADATA_GROUP)
324324
}
325325

326-
// gradle generateMetadata --coordinates=<maven-coordinates> [or -Pcoordinates=<maven-coordinates>] --agentAllowedPackages=<comma-separated list of packages>
326+
// gradle generateMetadata -Pcoordinates=<maven-coordinates> [or --coordinates=<maven-coordinates>] --agentAllowedPackages=<comma-separated list of packages>
327327
tasks.register("generateMetadata", GenerateMetadataTask.class) { task ->
328328
task.setDescription("Generates metadata based on provided tests.")
329329
task.setGroup(METADATA_GROUP)
330330
}
331+
332+
// gradle fixTestNIRun -PtestLibraryCoordinates=<maven-coordinates> -PnewLibraryVersion=<library version which needs fix>
333+
tasks.register("fixTestNativeImageRun", FixTestNativeImageRun.class) { task ->
334+
task.setDescription("Fix test that fails Native Image run for new library version.")
335+
task.setGroup(METADATA_GROUP)
336+
}

0 commit comments

Comments
 (0)