From 7ac3f234763690dfa361d255f915ed58c8c91388 Mon Sep 17 00:00:00 2001 From: Mihailo Markovic <96449436+kimeta@users.noreply.github.com> Date: Tue, 6 Jan 2026 15:24:43 +0100 Subject: [PATCH 01/29] Added metadata entry. (#911) --- .../liquibase-core/5.0.1/reflect-config.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/metadata/org.liquibase/liquibase-core/5.0.1/reflect-config.json b/metadata/org.liquibase/liquibase-core/5.0.1/reflect-config.json index 0f823530837..0d9674c95ea 100644 --- a/metadata/org.liquibase/liquibase-core/5.0.1/reflect-config.json +++ b/metadata/org.liquibase/liquibase-core/5.0.1/reflect-config.json @@ -8160,5 +8160,19 @@ }, "name": "liquibase.ui.LoggerUIService", "allPublicConstructors": true + }, + { + "condition": { + "typeReachable": "liquibase.configuration.LiquibaseConfiguration" + }, + "name": "liquibase.change.core.SQLFileChange", + "methods": [ + { + "name": "setPath", + "parameterTypes": [ + "java.lang.String" + ] + } + ] } ] From a3dcff17a412752560afa648c631a29fbdd09eca Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Thu, 11 Dec 2025 08:19:26 +0100 Subject: [PATCH 02/29] tck-build-logic: migrate CoordinatesAwareTask from Groovy to Java; preserve task behavior --- .../harness/tasks/CoordinatesAwareTask.groovy | 74 ------------------ .../harness/tasks/CoordinatesAwareTask.java | 77 +++++++++++++++++++ 2 files changed, 77 insertions(+), 74 deletions(-) delete mode 100644 tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CoordinatesAwareTask.groovy create mode 100644 tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CoordinatesAwareTask.java diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CoordinatesAwareTask.groovy b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CoordinatesAwareTask.groovy deleted file mode 100644 index a4f3a6c4d6a..00000000000 --- a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CoordinatesAwareTask.groovy +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright and related rights waived via CC0 - * - * You should have received a copy of the CC0 legalcode along with this - * work. If not, see . - */ -package org.graalvm.internal.tck.harness.tasks - -import org.gradle.api.DefaultTask -import org.gradle.api.provider.ListProperty -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.Optional -import org.graalvm.internal.tck.harness.TckExtension -import org.graalvm.internal.tck.utils.CoordinateUtils - -import javax.inject.Inject - -/** - * Base task providing unified coordinate resolution from -Pcoordinates and optional overrides. - * Supports: - * - single coordinate: group:artifact:version - * - filter by group/artifact - * - "all" - * - fractional batches "k/n" - * Always filters out "samples:". - */ -abstract class CoordinatesAwareTask extends DefaultTask { - - protected final TckExtension tckExtension - - @Inject - CoordinatesAwareTask() { - this.tckExtension = project.extensions.findByType(TckExtension) - // Default to no override - getCoordinatesOverride().convention(Collections.emptyList()) - } - - /** - * Allows tasks (e.g., diff) to override the set of coordinates to run on. - */ - @Input - @Optional - final ListProperty coordinatesOverride = project.objects.listProperty(String) - - ListProperty getCoordinatesOverride() { - return coordinatesOverride - } - - void setCoordinatesOverride(List coords) { - getCoordinatesOverride().set(coords) - } - - protected List resolveCoordinates() { - List override = getCoordinatesOverride().orNull - List coords - if (override != null && !override.isEmpty()) { - coords = override - } else { - String coordinateFilter = Objects.requireNonNullElse(project.findProperty("coordinates"), "") as String - coords = computeMatchingCoordinates(coordinateFilter) - } - return coords.findAll { !it.startsWith("samples:") } - } - - protected List computeMatchingCoordinates(String filter) { - if (CoordinateUtils.isFractionalBatch(filter)) { - int[] frac = CoordinateUtils.parseFraction(filter) - List all = tckExtension.getMatchingCoordinates("all") - return CoordinateUtils.computeBatchedCoordinates(all, frac[0], frac[1]) - } else { - return tckExtension.getMatchingCoordinates(filter) - } - } -} diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CoordinatesAwareTask.java b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CoordinatesAwareTask.java new file mode 100644 index 00000000000..bbd6080460a --- /dev/null +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CoordinatesAwareTask.java @@ -0,0 +1,77 @@ +/* + * Copyright and related rights waived via CC0 + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ +package org.graalvm.internal.tck.harness.tasks; + +import org.gradle.api.DefaultTask; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.Optional; +import org.graalvm.internal.tck.harness.TckExtension; +import org.graalvm.internal.tck.utils.CoordinateUtils; + +import javax.inject.Inject; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * Base task providing unified coordinate resolution from -Pcoordinates and optional overrides. + * Supports: + * - single coordinate: group:artifact:version + * - filter by group/artifact + * - "all" + * - fractional batches "k/n" + * Always filters out "samples:". + */ +public abstract class CoordinatesAwareTask extends DefaultTask { + + protected final TckExtension tckExtension; + + @Input + @Optional + private final ListProperty coordinatesOverride; + + @Inject + public CoordinatesAwareTask() { + this.tckExtension = getProject().getExtensions().findByType(TckExtension.class); + this.coordinatesOverride = getProject().getObjects().listProperty(String.class); + this.coordinatesOverride.convention(Collections.emptyList()); + } + + public ListProperty getCoordinatesOverride() { + return coordinatesOverride; + } + + public void setCoordinatesOverride(List coords) { + getCoordinatesOverride().set(coords); + } + + protected List resolveCoordinates() { + List override = getCoordinatesOverride().getOrNull(); + List coords; + if (override != null && !override.isEmpty()) { + coords = override; + } else { + String coordinateFilter = Objects.toString(getProject().findProperty("coordinates"), ""); + coords = computeMatchingCoordinates(coordinateFilter); + } + return coords.stream() + .filter(c -> !c.startsWith("samples:")) + .collect(Collectors.toList()); + } + + protected List computeMatchingCoordinates(String filter) { + if (CoordinateUtils.isFractionalBatch(filter)) { + int[] frac = CoordinateUtils.parseFraction(filter); + List all = tckExtension.getMatchingCoordinates("all"); + return CoordinateUtils.computeBatchedCoordinates(all, frac[0], frac[1]); + } else { + return tckExtension.getMatchingCoordinates(filter); + } + } +} From 0d2a42078165851f197e830830995ee3aaf68404 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Thu, 11 Dec 2025 08:53:34 +0100 Subject: [PATCH 03/29] tck-build-logic: migrate AllCoordinatesExecTask from Groovy to Java; preserve task behavior --- AGENTS.md | 2 + .../tasks/AllCoordinatesExecTask.groovy | 138 -------------- .../harness/tasks/AllCoordinatesExecTask.java | 179 ++++++++++++++++++ 3 files changed, 181 insertions(+), 138 deletions(-) delete mode 100644 tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/AllCoordinatesExecTask.groovy create mode 100644 tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/AllCoordinatesExecTask.java diff --git a/AGENTS.md b/AGENTS.md index 333a4e99aea..dfeb813a78f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,6 +20,8 @@ - Write type annotations in all functions and most variables. - Document code without being too verbose. - In Java and Groovy, always import classes and use them without qualified names. +- In Java use multi-line strings where possible. +- In Java use the markdown style for comments. ## Testing individual components diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/AllCoordinatesExecTask.groovy b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/AllCoordinatesExecTask.groovy deleted file mode 100644 index dd5079bd1ca..00000000000 --- a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/AllCoordinatesExecTask.groovy +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright and related rights waived via CC0 - * - * You should have received a copy of the CC0 legalcode along with this - * work. If not, see . - */ -package org.graalvm.internal.tck.harness.tasks - -import org.gradle.api.GradleException -import org.gradle.api.tasks.TaskAction -import org.gradle.process.ExecOperations -import org.gradle.process.ExecSpec -import org.graalvm.internal.tck.harness.TckExtension - -import javax.inject.Inject -import java.nio.charset.StandardCharsets -import java.nio.file.Path - -import static org.graalvm.internal.tck.Utils.coordinatesMatch -import static org.graalvm.internal.tck.Utils.readIndexFile -import static org.graalvm.internal.tck.Utils.splitCoordinates - -/** - * Base task that resolves coordinates (via CoordinatesAwareTask) and executes a command for each coordinate. - * Subclasses implement commandFor(String coordinates) and may override hooks for logging. - */ -abstract class AllCoordinatesExecTask extends CoordinatesAwareTask { - - @Inject - abstract ExecOperations getExecOperations() - - - /** - * Subclasses must return the command line to run for the given coordinates. - */ - abstract List commandFor(String coordinates) - - /** - * Customize error message. - */ - protected String errorMessageFor(String coordinates, int exitCode) { - return "Execution failed for ${coordinates} with exit code ${exitCode}" - } - - /** - * Hook invoked before executing each coordinate. - */ - protected void beforeEach(String coordinates, List command) { - // no-op - } - - /** - * Hook invoked after executing each coordinate on success. - */ - protected void afterEach(String coordinates) { - // no-op - } - - @TaskAction - final void runAll() { - List coords = resolveCoordinates() - if (coords.isEmpty()) { - getLogger().lifecycle("No matching coordinates found. Nothing to do.") - return - } - for (String c : coords) { - runSingle(c) - } - } - - private void runSingle(String coordinates) { - List command = commandFor(coordinates) - beforeEach(coordinates, command) - - def out = new ByteArrayOutputStream() - def err = new ByteArrayOutputStream() - - def execResult = getExecOperations().exec { ExecSpec spec -> - this.configureSpec(spec, coordinates, command) - spec.standardOutput = new TeeOutputStream(out, System.out) - spec.errorOutput = new TeeOutputStream(err, System.err) - } - - // write output file like AbstractSubprojectTask - String hash = command.join(",").md5() - File outputFile = project.layout.buildDirectory.file("tests/${coordinates}/${hash}.out").get().asFile - outputFile.parentFile.mkdirs() - outputFile.text = """Standard out ------ -${out.toString(StandardCharsets.UTF_8)} ------ -Standard err ----- -${err.toString(StandardCharsets.UTF_8)} ----- -""" - - int exitCode = execResult.exitValue - if (exitCode != 0) { - throw new GradleException(errorMessageFor(coordinates, exitCode)) - } - afterEach(coordinates) - } - - protected void configureSpec(ExecSpec spec, String coordinates, List command) { - def (String groupId, String artifactId, String version) = splitCoordinates(coordinates) - Path metadataDir = tckExtension.getMetadataDir(coordinates) - boolean override = false - - def metadataIndex = readIndexFile(metadataDir.parent) - for (def entry in metadataIndex) { - if (coordinatesMatch((String) entry["module"], groupId, artifactId) && ((List) entry["tested-versions"]).contains(version)) { - if (entry.containsKey("override")) { - override |= entry["override"] as boolean - } - break - } - } - - Path testDir = tckExtension.getTestDir(coordinates) - - Map env = new HashMap<>(System.getenv()) - env.put("GVM_TCK_LC", coordinates) - env.put("GVM_TCK_EXCLUDE", override.toString()) - if (System.getenv("GVM_TCK_LV") == null) { - env.put("GVM_TCK_LV", version) - } - env.put("GVM_TCK_MD", metadataDir.toAbsolutePath().toString()) - env.put("GVM_TCK_TCKDIR", tckExtension.getTckRoot().get().getAsFile().toPath().toAbsolutePath().toString()) - - spec.environment(env) - spec.commandLine(command) - spec.workingDir(testDir.toAbsolutePath().toFile()) - spec.setIgnoreExitValue(true) - spec.standardOutput = System.out - spec.errorOutput = System.err - } -} diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/AllCoordinatesExecTask.java b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/AllCoordinatesExecTask.java new file mode 100644 index 00000000000..458c3db3caf --- /dev/null +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/AllCoordinatesExecTask.java @@ -0,0 +1,179 @@ +/* + * Copyright and related rights waived via CC0 + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ +package org.graalvm.internal.tck.harness.tasks; + +import org.gradle.api.GradleException; +import org.gradle.api.tasks.TaskAction; +import org.gradle.process.ExecOperations; +import org.gradle.process.ExecSpec; + +import javax.inject.Inject; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Files; +import java.security.MessageDigest; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.graalvm.internal.tck.Utils.coordinatesMatch; +import static org.graalvm.internal.tck.Utils.readIndexFile; +import static org.graalvm.internal.tck.Utils.splitCoordinates; + +/** + * Base task that resolves coordinates (via CoordinatesAwareTask) and executes a command for each coordinate. + * Subclasses implement commandFor(String coordinates) and may override hooks for logging. + */ +@SuppressWarnings("unused") +public abstract class AllCoordinatesExecTask extends CoordinatesAwareTask { + + @Inject + public abstract ExecOperations getExecOperations(); + + /** + * Subclasses must return the command line to run for the given coordinates. + */ + public abstract List commandFor(String coordinates); + + /** + * Customize error message. + */ + protected String errorMessageFor(String coordinates, int exitCode) { + return "Execution failed for " + coordinates + " with exit code " + exitCode; + } + + /** + * Hook invoked before executing each coordinate. + */ + protected void beforeEach(String coordinates, List command) { + // no-op + } + + /** + * Hook invoked after executing each coordinate on success. + */ + protected void afterEach(String coordinates) { + // no-op + } + + @TaskAction + public final void runAll() { + List coords = resolveCoordinates(); + if (coords.isEmpty()) { + getLogger().lifecycle("No matching coordinates found. Nothing to do."); + return; + } + for (String c : coords) { + runSingle(c); + } + } + + private void runSingle(String coordinates) { + List command = commandFor(coordinates); + beforeEach(coordinates, command); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + + var execResult = getExecOperations().exec((ExecSpec spec) -> { + this.configureSpec(spec, coordinates, command); + spec.setStandardOutput(new TeeOutputStream(out, System.out)); + spec.setErrorOutput(new TeeOutputStream(err, System.err)); + }); + + // write output file like AbstractSubprojectTask + String hash = md5(String.join(",", command)); + File outputFile = getProject().getLayout().getBuildDirectory().file("tests/" + coordinates + "/" + hash + ".out").get().getAsFile(); + File parent = outputFile.getParentFile(); + if (parent != null && !parent.exists()) { + //noinspection ResultOfMethodCallIgnored + parent.mkdirs(); + } + String content = "Standard out\n" + + "-----\n" + + out.toString(StandardCharsets.UTF_8) + + "\n-----\n" + + "Standard err\n" + + "----\n" + + err.toString(StandardCharsets.UTF_8) + + "\n----\n"; + try { + Files.writeString(outputFile.toPath(), content, StandardCharsets.UTF_8); + } catch (java.io.IOException e) { + throw new GradleException("Failed to write test output to " + outputFile, e); + } + + int exitCode = execResult.getExitValue(); + if (exitCode != 0) { + throw new GradleException(errorMessageFor(coordinates, exitCode)); + } + afterEach(coordinates); + } + + protected void configureSpec(ExecSpec spec, String coordinates, List command) { + List parts = splitCoordinates(coordinates); + String groupId = parts.get(0); + String artifactId = parts.get(1); + String version = parts.get(2); + Path metadataDir = tckExtension.getMetadataDir(coordinates); + boolean override = false; + + var metadataIndex = readIndexFile(metadataDir.getParent()); + for (Object entryObj : (Iterable) metadataIndex) { + @SuppressWarnings("unchecked") + Map entry = (Map) entryObj; + if (coordinatesMatch((String) entry.get("module"), groupId, artifactId) && + ((List) entry.get("tested-versions")).contains(version)) { + if (entry.containsKey("override")) { + Object ov = entry.get("override"); + if (ov instanceof Boolean b) { + override |= b; + } else if (ov != null) { + override |= Boolean.parseBoolean(ov.toString()); + } + } + break; + } + } + + Path testDir = tckExtension.getTestDir(coordinates); + + Map env = new HashMap<>(System.getenv()); + env.put("GVM_TCK_LC", coordinates); + env.put("GVM_TCK_EXCLUDE", Boolean.toString(override)); + if (System.getenv("GVM_TCK_LV") == null) { + env.put("GVM_TCK_LV", version); + } + env.put("GVM_TCK_MD", metadataDir.toAbsolutePath().toString()); + env.put("GVM_TCK_TCKDIR", tckExtension.getTckRoot().get().getAsFile().toPath().toAbsolutePath().toString()); + + spec.environment(env); + spec.commandLine(command); + spec.workingDir(testDir.toAbsolutePath().toFile()); + spec.setIgnoreExitValue(true); + spec.setStandardOutput(System.out); + spec.setErrorOutput(System.err); + } + + private static String md5(String s) { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] dig = md.digest(s.getBytes(StandardCharsets.UTF_8)); + StringBuilder sb = new StringBuilder(dig.length * 2); + for (byte b : dig) { + sb.append(Character.forDigit((b & 0xF0) >> 4, 16)); + sb.append(Character.forDigit(b & 0x0F, 16)); + } + return sb.toString(); + } catch (Exception e) { + // Fallback to hex of hashCode to avoid failing the build because of MD5 unavailability + return Integer.toHexString(s.hashCode()); + } + } +} From 13718e8bf3ff8a89fa7e398cd674d9ee4801c7f6 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Thu, 11 Dec 2025 09:02:38 +0100 Subject: [PATCH 04/29] tck-build-logic: migrate SingleCoordinateTask from Groovy to Java; preserve task behavior --- ...teTask.groovy => SingleCoordinateTask.java} | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) rename tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/{SingleCoordinateTask.groovy => SingleCoordinateTask.java} (69%) diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/SingleCoordinateTask.groovy b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/SingleCoordinateTask.java similarity index 69% rename from tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/SingleCoordinateTask.groovy rename to tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/SingleCoordinateTask.java index ddf1b9090e7..26d5f565003 100644 --- a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/SingleCoordinateTask.groovy +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/SingleCoordinateTask.java @@ -4,28 +4,30 @@ * You should have received a copy of the CC0 legalcode along with this * work. If not, see . */ -package org.graalvm.internal.tck.harness.tasks +package org.graalvm.internal.tck.harness.tasks; -import org.gradle.api.GradleException +import org.gradle.api.GradleException; + +import java.util.List; /** * Base task for actions that must operate on exactly one coordinate. * Reuses the same unified resolution as CoordinatesAwareTask and enforces a single result. */ -abstract class SingleCoordinateTask extends CoordinatesAwareTask { +public abstract class SingleCoordinateTask extends CoordinatesAwareTask { /** * Resolves to exactly one coordinate or fails with a helpful error. */ protected String resolveSingleCoordinate() { - List coords = resolveCoordinates() + List coords = resolveCoordinates(); if (coords.isEmpty()) { - throw new GradleException("No matching coordinates found. Provide a concrete coordinate via -Pcoordinates=group:artifact:version") + throw new GradleException("No matching coordinates found. Provide a concrete coordinate via -Pcoordinates=group:artifact:version"); } if (coords.size() > 1) { - throw new GradleException("Multiple coordinates matched: ${coords}. This task requires a single concrete coordinate. " + - "Please specify an exact 'group:artifact:version' using -Pcoordinates.") + throw new GradleException("Multiple coordinates matched: " + coords + ". This task requires a single concrete coordinate. " + + "Please specify an exact 'group:artifact:version' using -Pcoordinates."); } - return coords.get(0) + return coords.get(0); } } From bda44a1b69c0785a8a24649616b8021cd24f82bd Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Thu, 11 Dec 2025 09:08:23 +0100 Subject: [PATCH 05/29] tck-build-logic: migrate AbstractSubprojectTask from Groovy to Java; preserve task behavior --- .../tasks/AbstractSubprojectTask.groovy | 174 ------------- .../harness/tasks/AbstractSubprojectTask.java | 230 ++++++++++++++++++ 2 files changed, 230 insertions(+), 174 deletions(-) delete mode 100644 tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/AbstractSubprojectTask.groovy create mode 100644 tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/AbstractSubprojectTask.java diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/AbstractSubprojectTask.groovy b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/AbstractSubprojectTask.groovy deleted file mode 100644 index 89c6015baa7..00000000000 --- a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/AbstractSubprojectTask.groovy +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright and related rights waived via CC0 - * - * You should have received a copy of the CC0 legalcode along with this - * work. If not, see . - */ -package org.graalvm.internal.tck.harness.tasks - -import groovy.transform.Internal -import org.graalvm.internal.tck.harness.TckExtension -import org.gradle.api.DefaultTask -import org.gradle.api.GradleException -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.PathSensitive -import org.gradle.api.tasks.PathSensitivity -import org.gradle.api.tasks.TaskAction -import org.gradle.process.ExecOperations -import org.gradle.process.ExecSpec - -import javax.inject.Inject -import java.nio.charset.StandardCharsets -import java.nio.file.Path -import java.util.stream.Collectors -import static org.graalvm.internal.tck.Utils.splitCoordinates; -import static org.graalvm.internal.tck.Utils.readIndexFile; -import static org.graalvm.internal.tck.Utils.coordinatesMatch; - -import static groovy.io.FileType.FILES - -/** - * Abstract task that is used to invoke test subprojects. - */ -@SuppressWarnings("unused") -abstract class AbstractSubprojectTask extends DefaultTask { - - protected final TckExtension tckExtension - private final String coordinates - - @Inject - abstract ExecOperations getExecOperations() - - @Input - abstract List getCommand(); - - @InputFiles - @PathSensitive(PathSensitivity.RELATIVE) - final Set getInputFiles() { - def inputFiles = project.objects.fileCollection() - def metadataDir = tckExtension.getMetadataDir(coordinates) - Path testDir = tckExtension.getTestDir(coordinates) - inputFiles.from(project.files(tckExtension.getMetadataFileList(metadataDir))) - def io = inputsFor(testDir) - def result = inputFiles.from(io).files - result - } - - @OutputFile - final File getOutputFile() { - String hash = command.join(",").md5() - def file = project.layout.buildDirectory.file("tests/${coordinates}/${hash}.out").get().asFile - file - } - - @Inject - AbstractSubprojectTask(String coordinates) { - this.tckExtension = project.extensions.findByType(TckExtension) - this.coordinates = coordinates - } - - - protected final configureSpec(ExecSpec spec) { - - def (String groupId, String artifactId, String version) = splitCoordinates(coordinates) - Path metadataDir = tckExtension.getMetadataDir(coordinates) - boolean override = false - - def metadataIndex = readIndexFile(metadataDir.parent) - for (def entry in metadataIndex) { - if (coordinatesMatch((String) entry["module"], groupId, artifactId) && ((List) entry["tested-versions"]).contains(version)) { - if (entry.containsKey("override")) { - override |= entry["override"] as boolean - } - break - } - } - - Path testDir = tckExtension.getTestDir(coordinates) - - Map env = new HashMap<>(System.getenv()) - // Environment variables for setting up TCK - env.put("GVM_TCK_LC", coordinates) - env.put("GVM_TCK_EXCLUDE", override.toString()) - if (System.getenv("GVM_TCK_LV") == null) { - // we only set this env variable if user didn't specify it manually - env.put("GVM_TCK_LV", version) - } - env.put("GVM_TCK_MD", metadataDir.toAbsolutePath().toString()) - env.put("GVM_TCK_TCKDIR", tckExtension.getTckRoot().get().getAsFile().toPath().toAbsolutePath().toString()) - spec.environment(env) - spec.commandLine(getCommand()) - spec.workingDir(testDir.toAbsolutePath().toFile()) - - spec.setIgnoreExitValue(true) - spec.standardOutput = System.out - spec.errorOutput = System.err - } - - /** - * Given project dir returns a tuple that contains a list of inputs. - * @return lists of input files - */ - @Internal - def inputsFor(Path projectDir) { - File dir = projectDir.toFile() - def excludedSubdirNames = [".gradle", ".mvn"] - - List excludedSubdirs = excludedSubdirNames.stream() - .map(name -> projectDir.resolve(name).toFile().getCanonicalPath() + File.separator) - .collect(Collectors.toList()) - - - def inputFiles = [] - - dir.traverse(type: FILES) { File file -> - if (excludedSubdirs.stream().noneMatch(curr -> file.getCanonicalPath().startsWith(curr))) { - inputFiles.add(file.toPath()) - } - } - - return Collections.unmodifiableList(inputFiles) - } - - protected void beforeExecute() { - // do nothing - } - - protected void afterExecute() { - // do nothing - } - - protected String getErrorMessage(int exitCode) { - "Execution of " + getCommand() + " failed." - } - - @TaskAction - final void execute() { - beforeExecute() - println "Command: $command" - def out = new ByteArrayOutputStream() - def err = new ByteArrayOutputStream() - def execResult = execOperations.exec { spec -> - configureSpec(spec) - spec.standardOutput = new TeeOutputStream(out, System.out) - spec.errorOutput = new TeeOutputStream(err, System.err) - } - outputFile.parentFile.mkdirs() - outputFile.text = """Standard out ------ -${out.toString(StandardCharsets.UTF_8)} ------ -Standard err ----- -${err.toString(StandardCharsets.UTF_8)} ----- -""" - def exitCode = execResult.exitValue - if (exitCode != 0) { - throw new GradleException(getErrorMessage(exitCode)) - } - afterExecute() - } -} diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/AbstractSubprojectTask.java b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/AbstractSubprojectTask.java new file mode 100644 index 00000000000..a234291ce13 --- /dev/null +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/AbstractSubprojectTask.java @@ -0,0 +1,230 @@ +/* + * Copyright and related rights waived via CC0 + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ +package org.graalvm.internal.tck.harness.tasks; + +import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.TaskAction; +import org.gradle.process.ExecOperations; +import org.gradle.process.ExecSpec; +import org.graalvm.internal.tck.harness.TckExtension; + +import javax.inject.Inject; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.util.*; +import java.util.stream.Collectors; + +import static org.graalvm.internal.tck.Utils.coordinatesMatch; +import static org.graalvm.internal.tck.Utils.readIndexFile; +import static org.graalvm.internal.tck.Utils.splitCoordinates; + +/** + * Abstract task that is used to invoke test subprojects. + */ +@SuppressWarnings("unused") +public abstract class AbstractSubprojectTask extends DefaultTask { + + protected final TckExtension tckExtension; + private final String coordinates; + + @Inject + public abstract ExecOperations getExecOperations(); + + @Input + public abstract List getCommand(); + + @InputFiles + @PathSensitive(PathSensitivity.RELATIVE) + public final Set getInputFiles() { + var inputFiles = getProject().getObjects().fileCollection(); + Path metadataDir = tckExtension.getMetadataDir(coordinates); + Path testDir = tckExtension.getTestDir(coordinates); + try { + inputFiles.from(getProject().files(tckExtension.getMetadataFileList(metadataDir))); + } catch (IOException e) { + throw new GradleException("Failed to list metadata files for " + metadataDir, e); + } + List io = inputsFor(testDir); + inputFiles.from(getProject().files(io)); + return inputFiles.getFiles(); + } + + @OutputFile + public final File getOutputFile() { + String hash = md5(String.join(",", getCommand())); + return getProject().getLayout().getBuildDirectory() + .file("tests/" + coordinates + "/" + hash + ".out") + .get().getAsFile(); + } + + @Inject + public AbstractSubprojectTask(String coordinates) { + this.tckExtension = getProject().getExtensions().findByType(TckExtension.class); + this.coordinates = coordinates; + } + + protected final void configureSpec(ExecSpec spec) { + List parts = splitCoordinates(coordinates); + String groupId = parts.get(0); + String artifactId = parts.get(1); + String version = parts.get(2); + Path metadataDir = tckExtension.getMetadataDir(coordinates); + boolean override = false; + + var metadataIndex = readIndexFile(metadataDir.getParent()); + for (Object entryObj : (Iterable) metadataIndex) { + @SuppressWarnings("unchecked") + Map entry = (Map) entryObj; + if (coordinatesMatch((String) entry.get("module"), groupId, artifactId) && + ((List) entry.get("tested-versions")).contains(version)) { + if (entry.containsKey("override")) { + Object ov = entry.get("override"); + if (ov instanceof Boolean b) { + override |= b; + } else if (ov != null) { + override |= Boolean.parseBoolean(ov.toString()); + } + } + break; + } + } + + Path testDir = tckExtension.getTestDir(coordinates); + + Map env = new HashMap<>(System.getenv()); + // Environment variables for setting up TCK + env.put("GVM_TCK_LC", coordinates); + env.put("GVM_TCK_EXCLUDE", Boolean.toString(override)); + if (System.getenv("GVM_TCK_LV") == null) { + // we only set this env variable if user didn't specify it manually + env.put("GVM_TCK_LV", version); + } + env.put("GVM_TCK_MD", metadataDir.toAbsolutePath().toString()); + env.put("GVM_TCK_TCKDIR", tckExtension.getTckRoot().get().getAsFile().toPath().toAbsolutePath().toString()); + spec.environment(env); + spec.commandLine(getCommand()); + spec.workingDir(testDir.toAbsolutePath().toFile()); + + spec.setIgnoreExitValue(true); + spec.setStandardOutput(System.out); + spec.setErrorOutput(System.err); + } + + /** + * Given project dir returns a list of inputs. + * @return list of input files + */ + @Internal + protected List inputsFor(Path projectDir) { + List excludedSubdirNames = List.of(".gradle", ".mvn"); + List excludedSubdirs = excludedSubdirNames.stream() + .map(name -> { + try { + return projectDir.resolve(name).toFile().getCanonicalPath() + File.separator; + } catch (IOException e) { + return projectDir.resolve(name).toFile().getAbsolutePath() + File.separator; + } + }) + .collect(Collectors.toList()); + + List inputFiles = new ArrayList<>(); + try { + Files.walk(projectDir) + .filter(Files::isRegularFile) + .filter(p -> { + try { + String canon = p.toFile().getCanonicalPath(); + return excludedSubdirs.stream().noneMatch(canon::startsWith); + } catch (IOException e) { + return false; + } + }) + .forEach(inputFiles::add); + } catch (IOException e) { + // If traversal fails treat as no inputs to avoid breaking configuration phase + return Collections.emptyList(); + } + + return Collections.unmodifiableList(inputFiles); + } + + protected void beforeExecute() { + // do nothing + } + + protected void afterExecute() { + // do nothing + } + + protected String getErrorMessage(int exitCode) { + return "Execution of " + getCommand() + " failed."; + } + + @TaskAction + public final void executeTask() { + beforeExecute(); + getLogger().lifecycle("Command: " + getCommand()); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + var execResult = getExecOperations().exec(spec -> { + configureSpec(spec); + spec.setStandardOutput(new TeeOutputStream(out, System.out)); + spec.setErrorOutput(new TeeOutputStream(err, System.err)); + }); + File of = getOutputFile(); + File parent = of.getParentFile(); + if (parent != null && !parent.exists()) { + //noinspection ResultOfMethodCallIgnored + parent.mkdirs(); + } + String content = "Standard out\n" + + "-----\n" + + out.toString(StandardCharsets.UTF_8) + + "\n-----\n" + + "Standard err\n" + + "----\n" + + err.toString(StandardCharsets.UTF_8) + + "\n----\n"; + try { + Files.writeString(of.toPath(), content, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new GradleException("Failed to write test output to " + of, e); + } + int exitCode = execResult.getExitValue(); + if (exitCode != 0) { + throw new GradleException(getErrorMessage(exitCode)); + } + afterExecute(); + } + + private static String md5(String s) { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] dig = md.digest(s.getBytes(StandardCharsets.UTF_8)); + StringBuilder sb = new StringBuilder(dig.length * 2); + for (byte b : dig) { + sb.append(Character.forDigit((b & 0xF0) >> 4, 16)); + sb.append(Character.forDigit(b & 0x0F, 16)); + } + return sb.toString(); + } catch (Exception e) { + return Integer.toHexString(s.hashCode()); + } + } +} From 80217049a121995f3470d652cae85f31574ebe23 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Thu, 11 Dec 2025 09:12:39 +0100 Subject: [PATCH 06/29] tck-build-logic: migrate CheckstyleInvocationTask from Groovy to Java; preserve task behavior --- .../tasks/CheckstyleInvocationTask.groovy | 28 ------------------ .../tasks/CheckstyleInvocationTask.java | 29 +++++++++++++++++++ 2 files changed, 29 insertions(+), 28 deletions(-) delete mode 100644 tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CheckstyleInvocationTask.groovy create mode 100644 tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CheckstyleInvocationTask.java diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CheckstyleInvocationTask.groovy b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CheckstyleInvocationTask.groovy deleted file mode 100644 index 729bb85216e..00000000000 --- a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CheckstyleInvocationTask.groovy +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright and related rights waived via CC0 - * - * You should have received a copy of the CC0 legalcode along with this - * work. If not, see . - */ -package org.graalvm.internal.tck.harness.tasks - -import org.gradle.api.tasks.Input - -import javax.inject.Inject -/** - * Task that is used to run checkstyle task on subprojects. - */ -@SuppressWarnings("unused") -abstract class CheckstyleInvocationTask extends AllCoordinatesExecTask { - - - List commandFor(String coordinates) { - return [tckExtension.repoRoot.get().asFile.toPath().resolve("gradlew").toString(), "checkstyle"] - } - - @Override - protected String errorMessageFor(String coordinates, int exitCode) { - "Checkstyle failed" - } - -} diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CheckstyleInvocationTask.java b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CheckstyleInvocationTask.java new file mode 100644 index 00000000000..ae47bb80205 --- /dev/null +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CheckstyleInvocationTask.java @@ -0,0 +1,29 @@ +/* + * Copyright and related rights waived via CC0 + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ +package org.graalvm.internal.tck.harness.tasks; + +import java.util.List; + +/** + * Task that is used to run checkstyle task on subprojects. + */ +@SuppressWarnings("unused") +public abstract class CheckstyleInvocationTask extends AllCoordinatesExecTask { + + @Override + public List commandFor(String coordinates) { + return List.of( + tckExtension.getRepoRoot().get().getAsFile().toPath().resolve("gradlew").toString(), + "checkstyle" + ); + } + + @Override + protected String errorMessageFor(String coordinates, int exitCode) { + return "Checkstyle failed"; + } +} From 5eed1c660329b9c9544586df88a17a57d62dd890 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Thu, 11 Dec 2025 09:14:30 +0100 Subject: [PATCH 07/29] tck-build-logic: migrate CleanInvocationTask from Groovy to Java; preserve task behavior --- .../harness/tasks/CleanInvocationTask.groovy | 27 ------------------- .../harness/tasks/CleanInvocationTask.java | 26 ++++++++++++++++++ 2 files changed, 26 insertions(+), 27 deletions(-) delete mode 100644 tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CleanInvocationTask.groovy create mode 100644 tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CleanInvocationTask.java diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CleanInvocationTask.groovy b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CleanInvocationTask.groovy deleted file mode 100644 index 398a107f12a..00000000000 --- a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CleanInvocationTask.groovy +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright and related rights waived via CC0 - * - * You should have received a copy of the CC0 legalcode along with this - * work. If not, see . - */ -package org.graalvm.internal.tck.harness.tasks - -import org.gradle.api.tasks.Input - -import javax.inject.Inject - -@SuppressWarnings('unused') -abstract class CleanInvocationTask extends AllCoordinatesExecTask { - - - @Override - List commandFor(String coordinates) { - return [tckExtension.repoRoot.get().asFile.toPath().resolve("gradlew").toString(), "clean"] - } - - @Override - protected String errorMessageFor(String coordinates, int exitCode) { - "Clean task failed" - } - -} diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CleanInvocationTask.java b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CleanInvocationTask.java new file mode 100644 index 00000000000..dfac65cd1d9 --- /dev/null +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CleanInvocationTask.java @@ -0,0 +1,26 @@ +/* + * Copyright and related rights waived via CC0 + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ +package org.graalvm.internal.tck.harness.tasks; + +import java.util.List; + +@SuppressWarnings("unused") +public abstract class CleanInvocationTask extends AllCoordinatesExecTask { + + @Override + public List commandFor(String coordinates) { + return List.of( + tckExtension.getRepoRoot().get().getAsFile().toPath().resolve("gradlew").toString(), + "clean" + ); + } + + @Override + protected String errorMessageFor(String coordinates, int exitCode) { + return "Clean task failed"; + } +} From ed1714753d1818c8756976cd0ddc9e8b1aa11693 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Thu, 11 Dec 2025 09:16:10 +0100 Subject: [PATCH 08/29] tck-build-logic: migrate CompileTestJavaInvocationTask from Groovy to Java; preserve task behavior --- .../CompileTestJavaInvocationTask.groovy | 24 --------------- .../tasks/CompileTestJavaInvocationTask.java | 29 +++++++++++++++++++ 2 files changed, 29 insertions(+), 24 deletions(-) delete mode 100644 tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CompileTestJavaInvocationTask.groovy create mode 100644 tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CompileTestJavaInvocationTask.java diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CompileTestJavaInvocationTask.groovy b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CompileTestJavaInvocationTask.groovy deleted file mode 100644 index 4f6c04a554e..00000000000 --- a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CompileTestJavaInvocationTask.groovy +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright and related rights waived via CC0 - * - * You should have received a copy of the CC0 legalcode along with this - * work. If not, see . - */ -package org.graalvm.internal.tck.harness.tasks - -/** - * Task that is used to compile subprojects with javac. - */ -@SuppressWarnings("unused") -abstract class CompileTestJavaInvocationTask extends AllCoordinatesExecTask { - - @Override - List commandFor(String coordinates) { - return [tckExtension.repoRoot.get().asFile.toPath().resolve("gradlew").toString(), "compileTestJava"] - } - - @Override - protected String errorMessageFor(String coordinates, int exitCode) { - "Compilation failed" - } -} diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CompileTestJavaInvocationTask.java b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CompileTestJavaInvocationTask.java new file mode 100644 index 00000000000..f7edbd9e0e2 --- /dev/null +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CompileTestJavaInvocationTask.java @@ -0,0 +1,29 @@ +/* + * Copyright and related rights waived via CC0 + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ +package org.graalvm.internal.tck.harness.tasks; + +import java.util.List; + +/** + * Task that is used to compile subprojects with javac. + */ +@SuppressWarnings("unused") +public abstract class CompileTestJavaInvocationTask extends AllCoordinatesExecTask { + + @Override + public List commandFor(String coordinates) { + return List.of( + tckExtension.getRepoRoot().get().getAsFile().toPath().resolve("gradlew").toString(), + "compileTestJava" + ); + } + + @Override + protected String errorMessageFor(String coordinates, int exitCode) { + return "Compilation failed"; + } +} From d0ea5b8bd7fd972f3b6c64fcd343b673d5d4b9ac Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Thu, 11 Dec 2025 09:18:03 +0100 Subject: [PATCH 09/29] tck-build-logic: migrate JavaTestInvocationTask from Groovy to Java; preserve task behavior --- ...ionTask.groovy => JavaTestInvocationTask.java} | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) rename tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/{JavaTestInvocationTask.groovy => JavaTestInvocationTask.java} (51%) diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/JavaTestInvocationTask.groovy b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/JavaTestInvocationTask.java similarity index 51% rename from tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/JavaTestInvocationTask.groovy rename to tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/JavaTestInvocationTask.java index 5aff54dda74..817cf8223ba 100644 --- a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/JavaTestInvocationTask.groovy +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/JavaTestInvocationTask.java @@ -4,21 +4,26 @@ * You should have received a copy of the CC0 legalcode along with this * work. If not, see . */ -package org.graalvm.internal.tck.harness.tasks +package org.graalvm.internal.tck.harness.tasks; + +import java.util.List; /** * Task that is used to run JVM tests (Gradle 'test') on subprojects. */ @SuppressWarnings("unused") -abstract class JavaTestInvocationTask extends AllCoordinatesExecTask { +public abstract class JavaTestInvocationTask extends AllCoordinatesExecTask { @Override - List commandFor(String coordinates) { - return [tckExtension.repoRoot.get().asFile.toPath().resolve("gradlew").toString(), "test"] + public List commandFor(String coordinates) { + return List.of( + tckExtension.getRepoRoot().get().getAsFile().toPath().resolve("gradlew").toString(), + "test" + ); } @Override protected String errorMessageFor(String coordinates, int exitCode) { - "Java tests failed" + return "Java tests failed"; } } From 91756ff131395774d0c929d5156b375e231e786a Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Thu, 11 Dec 2025 09:19:37 +0100 Subject: [PATCH 10/29] tck-build-logic: migrate NativeTestCompileInvocationTask from Groovy to Java; preserve task behavior --- ...roovy => NativeTestCompileInvocationTask.java} | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) rename tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/{NativeTestCompileInvocationTask.groovy => NativeTestCompileInvocationTask.java} (50%) diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/NativeTestCompileInvocationTask.groovy b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/NativeTestCompileInvocationTask.java similarity index 50% rename from tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/NativeTestCompileInvocationTask.groovy rename to tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/NativeTestCompileInvocationTask.java index 80c3e5f9187..12a518c8ae8 100644 --- a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/NativeTestCompileInvocationTask.groovy +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/NativeTestCompileInvocationTask.java @@ -4,21 +4,26 @@ * You should have received a copy of the CC0 legalcode along with this * work. If not, see . */ -package org.graalvm.internal.tck.harness.tasks +package org.graalvm.internal.tck.harness.tasks; + +import java.util.List; /** * Task that is used to compile native tests (Gradle 'nativeTestCompile') on subprojects. */ @SuppressWarnings("unused") -abstract class NativeTestCompileInvocationTask extends AllCoordinatesExecTask { +public abstract class NativeTestCompileInvocationTask extends AllCoordinatesExecTask { @Override - List commandFor(String coordinates) { - return [tckExtension.repoRoot.get().asFile.toPath().resolve("gradlew").toString(), "nativeTestCompile"] + public List commandFor(String coordinates) { + return List.of( + tckExtension.getRepoRoot().get().getAsFile().toPath().resolve("gradlew").toString(), + "nativeTestCompile" + ); } @Override protected String errorMessageFor(String coordinates, int exitCode) { - "Native test compilation failed" + return "Native test compilation failed"; } } From 9dd0c96c33cc8c3bcb06fb6790d737795bd6d198 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Thu, 11 Dec 2025 09:58:47 +0100 Subject: [PATCH 11/29] tck-build-logic: migrate FetchExistingLibrariesWithNewerVersionsTask from Groovy to Java; preserve task behavior --- .../tasks/CheckMetadataFilesAllTask.groovy | 53 ---- .../tasks/CheckMetadataFilesAllTask.java | 56 ++++ ...xistingLibrariesWithNewerVersionsTask.java | 249 ++++++++++++++++++ 3 files changed, 305 insertions(+), 53 deletions(-) delete mode 100644 tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CheckMetadataFilesAllTask.groovy create mode 100644 tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CheckMetadataFilesAllTask.java create mode 100644 tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/FetchExistingLibrariesWithNewerVersionsTask.java diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CheckMetadataFilesAllTask.groovy b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CheckMetadataFilesAllTask.groovy deleted file mode 100644 index 3e4d13a67d2..00000000000 --- a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CheckMetadataFilesAllTask.groovy +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright and related rights waived via CC0 - * - * You should have received a copy of the CC0 legalcode along with this - * work. If not, see . - */ -package org.graalvm.internal.tck.harness.tasks - -import org.gradle.api.GradleException -import org.gradle.api.tasks.TaskAction -import org.graalvm.internal.tck.MetadataFilesCheckerTask - -/** - * Executes MetadataFilesCheckerTask for all matching coordinates resolved via -Pcoordinates. - * This unifies handling so the task itself performs coordinate resolution and iteration. - */ -@SuppressWarnings('unused') -abstract class CheckMetadataFilesAllTask extends CoordinatesAwareTask { - - @TaskAction - void runAll() { - List coords = resolveCoordinates() - if (coords.isEmpty()) { - getLogger().lifecycle("No matching coordinates found for metadata checks. Nothing to do.") - return - } - - List failures = [] - coords.each { c -> - if (c.startsWith("samples:") || c.startsWith("org.example:")) { - return // skip samples/infrastructure - } - String tmpName = ("checkMetadataFiles_" + c.replace(":", "_") + "_" + System.nanoTime()) - def t = project.tasks.create(tmpName, MetadataFilesCheckerTask.class) - t.setCoordinates(c) - try { - t.run() - getLogger().lifecycle("Metadata files check passed for {}", c) - } catch (Throwable ex) { - failures.add("${c}: ${ex.message}") - getLogger().error("Metadata files check failed for {}: {}", c, ex.message) - } finally { - // Best effort cleanup to avoid cluttering the task graph - t.enabled = false - } - } - - if (!failures.isEmpty()) { - String msg = "Metadata files check failed for the following coordinates:\n - " + failures.join("\n - ") - throw new GradleException(msg) - } - } -} diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CheckMetadataFilesAllTask.java b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CheckMetadataFilesAllTask.java new file mode 100644 index 00000000000..94d1550793e --- /dev/null +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CheckMetadataFilesAllTask.java @@ -0,0 +1,56 @@ +/* + * Copyright and related rights waived via CC0 + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ +package org.graalvm.internal.tck.harness.tasks; + +import org.gradle.api.GradleException; +import org.gradle.api.tasks.TaskAction; +import org.graalvm.internal.tck.MetadataFilesCheckerTask; + +import java.util.ArrayList; +import java.util.List; + +/** + * Executes MetadataFilesCheckerTask for all matching coordinates resolved via -Pcoordinates. + * This unifies handling so the task itself performs coordinate resolution and iteration. + */ +@SuppressWarnings("unused") +public abstract class CheckMetadataFilesAllTask extends CoordinatesAwareTask { + + @TaskAction + public void runAll() { + List coords = resolveCoordinates(); + if (coords.isEmpty()) { + getLogger().lifecycle("No matching coordinates found for metadata checks. Nothing to do."); + return; + } + + List failures = new ArrayList<>(); + for (String c : coords) { + if (c.startsWith("samples:") || c.startsWith("org.example:")) { + continue; // skip samples/infrastructure + } + String tmpName = "checkMetadataFiles_" + c.replace(":", "_") + "_" + System.nanoTime(); + MetadataFilesCheckerTask t = getProject().getTasks().create(tmpName, MetadataFilesCheckerTask.class); + t.setCoordinates(c); + try { + t.run(); + getLogger().lifecycle("Metadata files check passed for {}", c); + } catch (Throwable ex) { + failures.add(c + ": " + ex.getMessage()); + getLogger().error("Metadata files check failed for {}: {}", c, ex.getMessage()); + } finally { + // Best effort cleanup to avoid cluttering the task graph + t.setEnabled(false); + } + } + + if (!failures.isEmpty()) { + String msg = "Metadata files check failed for the following coordinates:\n - " + String.join("\n - ", failures); + throw new GradleException(msg); + } + } +} diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/FetchExistingLibrariesWithNewerVersionsTask.java b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/FetchExistingLibrariesWithNewerVersionsTask.java new file mode 100644 index 00000000000..e84c536c167 --- /dev/null +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/FetchExistingLibrariesWithNewerVersionsTask.java @@ -0,0 +1,249 @@ +/* + * Copyright and related rights waived via CC0 + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ +package org.graalvm.internal.tck.harness.tasks; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import org.graalvm.internal.tck.model.MetadataVersionsIndexEntry; +import org.graalvm.internal.tck.model.SkippedVersionEntry; +import org.gradle.api.DefaultTask; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.TaskAction; +import org.gradle.util.internal.VersionNumber; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Lists existing libraries that have newer upstream versions available (not yet tested). + * Produces a JSON array of objects: [{"name": "group:artifact", "versions": ["x.y.z", ...]}, ...] + */ +@SuppressWarnings("unused") +public abstract class FetchExistingLibrariesWithNewerVersionsTask extends DefaultTask { + + @Input + public abstract ListProperty getAllLibraryCoordinates(); + + private static final List INFRASTRUCTURE_TESTS = List.of("samples", "org.example"); + + /** + * Identifies library versions, including optional pre-release and ".Final" suffixes. + * + * Pre-release identifiers (case-insensitive): alpha, beta, rc, cr, m, ea, b, preview, and pure numeric suffixes. + * Versions ending with ".Final" are treated as full releases of the base version. + */ + private static final Pattern VERSION_PATTERN = Pattern.compile("(?i)^(\\\\d+(?:\\\\.\\\\d+)*)" + + "(?:\\\\.Final)?" + + "(?:[-.](alpha\\\\d*|beta\\\\d*|rc\\\\d*|cr\\\\d*|m\\\\d+|ea\\\\d*|b\\\\d+|\\\\d+|preview)(?:[-.].*)?)?$"); + + @TaskAction + public void action() { + // Derive set of distinct library modules: group:artifact + Set libraries = new LinkedHashSet<>(); + for (String coord : getAllLibraryCoordinates().get()) { + int last = coord.lastIndexOf(':'); + if (last > 0) { + libraries.add(coord.substring(0, last)); + } + } + + // For each library, compute newer versions and filter out infrastructure modules + List newerVersions = new ArrayList<>(); + for (String libraryName : libraries) { + if (INFRASTRUCTURE_TESTS.stream().noneMatch(libraryName::startsWith)) { + List versions = getNewerVersionsFor(libraryName, getLatestLibraryVersion(libraryName)); + List skipped = getSkippedVersions(libraryName); + versions.removeAll(skipped); + for (String v : versions) { + newerVersions.add(libraryName + ":" + v); + } + } + } + + // Aggregate by library name to the requested structure + Map> grouped = new LinkedHashMap<>(); + for (String coord : newerVersions) { + String[] parts = coord.split(":", -1); + String key = parts[0] + ":" + parts[1]; + grouped.computeIfAbsent(key, k -> new ArrayList<>()).add(parts[2]); + } + + List> pairs = new ArrayList<>(); + for (Map.Entry> e : grouped.entrySet()) { + Map m = new LinkedHashMap<>(); + m.put("name", e.getKey()); + m.put("versions", e.getValue()); + pairs.add(m); + } + + try { + ObjectMapper om = new ObjectMapper() + .enable(SerializationFeature.INDENT_OUTPUT) + .setSerializationInclusion(JsonInclude.Include.NON_NULL); + System.out.println(om.writeValueAsString(pairs)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + static List getNewerVersionsFor(String library, String startingVersion) { + try { + String baseUrl = "https://repo1.maven.org/maven2"; + String[] libraryParts = library.split(":"); + String group = libraryParts[0].replace(".", "/"); + String artifact = libraryParts[1]; + String data = new String(new URL(baseUrl + "/" + group + "/" + artifact + "/maven-metadata.xml").openStream().readAllBytes()); + + List newerVersions = getNewerVersionsFromLibraryIndex(data, startingVersion, library); + + // filter out already tested versions + List testedVersions = getTestedVersions(library); + newerVersions.removeAll(testedVersions); + + // filter pre-release versions if full release exists + return filterPreReleases(newerVersions); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + static List getNewerVersionsFromLibraryIndex(String index, String startingVersion, String libraryName) { + Pattern pattern = Pattern.compile("(.*)"); + Matcher matcher = pattern.matcher(index); + List allVersions = new ArrayList<>(); + + if (matcher.groupCount() < 1) { + throw new RuntimeException("Cannot find versions in the given index file: " + libraryName); + } + + while (matcher.find()) { + allVersions.add(matcher.group(1)); + } + + int indexOfStartingVersion = allVersions.indexOf(startingVersion); + if (indexOfStartingVersion < 0) { + return new ArrayList<>(); + } + + allVersions = allVersions.subList(indexOfStartingVersion, allVersions.size()); + return new ArrayList<>(allVersions.subList(1, allVersions.size())); + } + + static List filterPreReleases(List versions) { + // Identify base versions that have a full release + Set releases = new HashSet<>(); + for (String v : versions) { + Matcher m = VERSION_PATTERN.matcher(v); + if (m.matches() && m.group(2) == null) { + releases.add(m.group(1)); + } + } + + List result = new ArrayList<>(); + for (String v : versions) { + Matcher m = VERSION_PATTERN.matcher(v); + if (m.matches()) { + String base = m.group(1); + String preSuffix = m.groupCount() > 1 ? m.group(2) : null; + if (preSuffix == null || !releases.contains(base)) { + result.add(v); + } + } else { + result.add(v); + } + } + return result; + } + + static String getLatestLibraryVersion(String libraryModule) { + try { + List testedVersions = getTestedVersions(libraryModule); + if (testedVersions.isEmpty()) { + throw new IllegalStateException("Cannot find any tested version for: " + libraryModule); + } + testedVersions.sort(Comparator.comparing(VersionNumber::parse)); + return testedVersions.get(testedVersions.size() - 1); + } catch (RuntimeException e) { + throw e; + } + } + + /** + * Reads the tested versions of a given library from its metadata index file. + */ + static List getTestedVersions(String libraryModule) { + try { + String[] coordinates = libraryModule.split(":"); + String group = coordinates[0]; + String artifact = coordinates[1]; + + File indexFile = new File("metadata/" + group + "/" + artifact + "/index.json"); + if (!indexFile.exists()) { + return Collections.emptyList(); + } + + ObjectMapper objectMapper = new ObjectMapper() + .enable(SerializationFeature.INDENT_OUTPUT) + .setSerializationInclusion(JsonInclude.Include.NON_NULL); + + List entries = objectMapper.readValue( + indexFile, new TypeReference>() {}); + List testedVersions = new ArrayList<>(); + for (MetadataVersionsIndexEntry entry : entries) { + testedVersions.addAll(entry.testedVersions()); + } + return testedVersions; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Returns all versions of a given library that are marked as skipped in the metadata index. + * + * For the provided Maven coordinates (groupId:artifactId), reads metadata///index.json + * and collects versions under "skipped-versions". + */ + static List getSkippedVersions(String libraryModule) { + try { + String[] coordinates = libraryModule.split(":"); + String group = coordinates[0]; + String artifact = coordinates[1]; + + File coordinatesMetadataIndex = new File("metadata/" + group + "/" + artifact + "/index.json"); + if (!coordinatesMetadataIndex.exists()) { + return Collections.emptyList(); + } + + ObjectMapper objectMapper = new ObjectMapper() + .enable(SerializationFeature.INDENT_OUTPUT) + .setSerializationInclusion(JsonInclude.Include.NON_NULL); + + List entries = objectMapper.readValue( + coordinatesMetadataIndex, new TypeReference>() {}); + + List skipped = new ArrayList<>(); + for (MetadataVersionsIndexEntry entry : entries) { + if (entry.skippedVersions() != null) { + for (SkippedVersionEntry sve : entry.skippedVersions()) { + skipped.add(sve.version()); + } + } + } + return skipped; + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} From 0ae63304b90291501a038b699c57008cedbe9e50 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Thu, 11 Dec 2025 10:02:28 +0100 Subject: [PATCH 12/29] tck-build-logic: migrate TestInvocationTask from Groovy to Java; preserve task behavior --- .../harness/tasks/TestInvocationTask.groovy | 92 ----------------- .../tck/harness/tasks/TestInvocationTask.java | 99 +++++++++++++++++++ 2 files changed, 99 insertions(+), 92 deletions(-) delete mode 100644 tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/TestInvocationTask.groovy create mode 100644 tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/TestInvocationTask.java diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/TestInvocationTask.groovy b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/TestInvocationTask.groovy deleted file mode 100644 index 8865fce114a..00000000000 --- a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/TestInvocationTask.groovy +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright and related rights waived via CC0 - * - * You should have received a copy of the CC0 legalcode along with this - * work. If not, see . - */ -package org.graalvm.internal.tck.harness.tasks - -import org.gradle.api.provider.ProviderFactory - -import javax.inject.Inject -import java.nio.file.Path -import java.util.stream.Collectors - -import static org.graalvm.internal.tck.Utils.readIndexFile -import static org.graalvm.internal.tck.Utils.splitCoordinates - -/** - * Task that is used to start subproject tests for matching coordinates. - * Coordinate resolution is unified and handled by the base class. - */ -@SuppressWarnings("unused") -abstract class TestInvocationTask extends AllCoordinatesExecTask { - - @Inject - abstract ProviderFactory getProviders() - - @Override - List commandFor(String coordinates) { - def defaultArgs = [tckExtension.repoRoot.get().asFile.toPath().resolve("gradlew").toString(), "nativeTest"] - def installPathsProperty = providers.environmentVariable("TCK_JDK_INSTALLATION_PATHS") - if (installPathsProperty.isPresent()) { - defaultArgs.addAll( - [ - "-Porg.gradle.java.installations.auto-detect=false", - "-Porg.gradle.java.installations.paths=${installPathsProperty.get()}" - ] - ) - } - try { - Map> testIndex = readIndexFile(tckExtension.getTestDir(coordinates)) as Map> - if (!testIndex.containsKey("test-command")) { - return defaultArgs - } - - Path metadataDir = tckExtension.getMetadataDir(coordinates) - return testIndex.get("test-command").stream() - .map(c -> processCommand(c, metadataDir, coordinates)) - .collect(Collectors.toList()) - } catch (FileNotFoundException ignored) { - return defaultArgs - } - } - - /** - * Fills in template parameters in the command invocation. - * Parameters are defined as in cmd. - * - * @param cmd command line with parameters - * @param metadataDir metadata directory location - * @param coordinates - * @return final command - */ - static String processCommand(String cmd, Path metadataDir, String coordinates) { - def (String groupId, String artifactId, String version) = splitCoordinates(coordinates) - return cmd.replace("", metadataDir.toAbsolutePath().toString()) - .replace("", groupId) - .replace("", artifactId) - .replace("", version) - } - - @Override - protected String errorMessageFor(String coordinates, int exitCode) { - "Test for ${coordinates} failed with exit code ${exitCode}." - } - - @Override - protected void beforeEach(String coordinates, List command) { - getLogger().lifecycle("====================") - getLogger().lifecycle("Testing library: {}", coordinates) - getLogger().lifecycle("Command: `{}`", String.join(" ", command)) - getLogger().lifecycle("Executing test...") - getLogger().lifecycle("-------") - } - - @Override - protected void afterEach(String coordinates) { - getLogger().lifecycle("-------") - getLogger().lifecycle("Test for {} passed.", coordinates) - getLogger().lifecycle("====================") - } -} diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/TestInvocationTask.java b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/TestInvocationTask.java new file mode 100644 index 00000000000..1d6478d4b96 --- /dev/null +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/TestInvocationTask.java @@ -0,0 +1,99 @@ +/* + * Copyright and related rights waived via CC0 + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ +package org.graalvm.internal.tck.harness.tasks; + +import org.gradle.api.provider.ProviderFactory; + +import javax.inject.Inject; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.graalvm.internal.tck.Utils.readIndexFile; +import static org.graalvm.internal.tck.Utils.splitCoordinates; + +/** + * Task that is used to start subproject tests for matching coordinates. + * Coordinate resolution is unified and handled by the base class. + */ +@SuppressWarnings("unused") +public abstract class TestInvocationTask extends AllCoordinatesExecTask { + + @Inject + public abstract ProviderFactory getProviders(); + + @Override + public List commandFor(String coordinates) { + List defaultArgs = new ArrayList<>(List.of( + tckExtension.getRepoRoot().get().getAsFile().toPath().resolve("gradlew").toString(), + "nativeTest" + )); + var installPathsProperty = getProviders().environmentVariable("TCK_JDK_INSTALLATION_PATHS"); + if (installPathsProperty.isPresent()) { + defaultArgs.add("-Porg.gradle.java.installations.auto-detect=false"); + defaultArgs.add("-Porg.gradle.java.installations.paths=" + installPathsProperty.get()); + } + try { + @SuppressWarnings("unchecked") + Map> testIndex = + (Map>) readIndexFile(tckExtension.getTestDir(coordinates)); + if (!testIndex.containsKey("test-command")) { + return defaultArgs; + } + + Path metadataDir = tckExtension.getMetadataDir(coordinates); + return testIndex.get("test-command").stream() + .map(c -> processCommand(c, metadataDir, coordinates)) + .collect(Collectors.toList()); + } catch (RuntimeException ignored) { + return defaultArgs; + } + } + + /** + * Fills in template parameters in the command invocation. + * Parameters are defined as in cmd. + * + * @param cmd command line with parameters + * @param metadataDir metadata directory location + * @param coordinates coordinates in form group:artifact:version + * @return final command + */ + public static String processCommand(String cmd, Path metadataDir, String coordinates) { + List parts = splitCoordinates(coordinates); + String groupId = parts.get(0); + String artifactId = parts.get(1); + String version = parts.get(2); + return cmd.replace("", metadataDir.toAbsolutePath().toString()) + .replace("", groupId) + .replace("", artifactId) + .replace("", version); + } + + @Override + protected String errorMessageFor(String coordinates, int exitCode) { + return "Test for " + coordinates + " failed with exit code " + exitCode + "."; + } + + @Override + protected void beforeEach(String coordinates, List command) { + getLogger().lifecycle("===================="); + getLogger().lifecycle("Testing library: {}", coordinates); + getLogger().lifecycle("Command: `{}`", String.join(" ", command)); + getLogger().lifecycle("Executing test..."); + getLogger().lifecycle("-------"); + } + + @Override + protected void afterEach(String coordinates) { + getLogger().lifecycle("-------"); + getLogger().lifecycle("Test for {} passed.", coordinates); + getLogger().lifecycle("===================="); + } +} From 1a5458559ff862a40007eae409f79c4351f55b44 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Thu, 11 Dec 2025 10:19:32 +0100 Subject: [PATCH 13/29] tck-build-logic: make test directory resolution robust; fallback to conventional layout and metadata-version; tolerate missing tests index.json in TestInvocationTask --- .../internal/tck/harness/TckExtension.java | 15 +++++++++++++++ .../tck/harness/tasks/TestInvocationTask.java | 2 +- .../internal/tck/MetadataFilesCheckerTask.java | 8 ++------ 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/TckExtension.java b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/TckExtension.java index 0ac5c2fe9b6..04cd2485a07 100644 --- a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/TckExtension.java +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/TckExtension.java @@ -139,11 +139,26 @@ public Path getTestDir(String coordinates) { } catch (Exception ignored) { // Fall through to conventional lookup } + // Fallback: conventional layout tests/src/// Path conventional = testRoot().resolve(groupId).resolve(artifactId).resolve(version); if (Files.isDirectory(conventional)) { return conventional; } + + // Secondary fallback: derive test dir from metadata "metadata-version" + try { + Path mdDir = getMetadataDir(coordinates); // .../metadata/// + Path mdVersion = mdDir.getFileName(); + if (mdVersion != null) { + Path testsForMetadataVersion = testRoot().resolve(groupId).resolve(artifactId).resolve(mdVersion.toString()); + if (Files.isDirectory(testsForMetadataVersion)) { + return testsForMetadataVersion; + } + } + } catch (Exception ignored) { + // ignore and fall through to error + } throw new RuntimeException("Missing test-directory for coordinates `" + coordinates + "`"); } diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/TestInvocationTask.java b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/TestInvocationTask.java index 1d6478d4b96..f9c44e35e12 100644 --- a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/TestInvocationTask.java +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/TestInvocationTask.java @@ -51,7 +51,7 @@ public List commandFor(String coordinates) { return testIndex.get("test-command").stream() .map(c -> processCommand(c, metadataDir, coordinates)) .collect(Collectors.toList()); - } catch (RuntimeException ignored) { + } catch (Exception ignored) { return defaultArgs; } } diff --git a/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/MetadataFilesCheckerTask.java b/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/MetadataFilesCheckerTask.java index 7811f271dd5..2ee4a13d0ae 100644 --- a/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/MetadataFilesCheckerTask.java +++ b/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/MetadataFilesCheckerTask.java @@ -48,7 +48,7 @@ public abstract class MetadataFilesCheckerTask extends DefaultTask { List allowedPackages; @Option(option = "coordinates", description = "Coordinates in the form of group:artifact:version") - void setCoordinates(String coords) { + public void setCoordinates(String coords) { extractCoordinates(coords); } @@ -78,11 +78,7 @@ private void extractCoordinates(String c) { } @TaskAction - void run() throws IllegalArgumentException, FileNotFoundException { - if (coordinates.group().equalsIgnoreCase("org.example") || coordinates.group().equalsIgnoreCase("samples")) { - return; - } - + public void run() throws IllegalArgumentException, FileNotFoundException { File coordinatesMetadataRoot = getMetadataRoot().get().getAsFile(); if (!coordinatesMetadataRoot.exists()) { throw new IllegalArgumentException("ERROR: Cannot find metadata directory for given coordinates: " + this.coordinates); From 509d02ec0c2ea9071a60628fac596e4f824b9cd6 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Wed, 7 Jan 2026 11:34:11 +0100 Subject: [PATCH 14/29] Use the same pattern for tested versions --- .../internal/tck/harness/TckExtension.java | 2 +- ...tchExistingLibrariesWithNewerVersionsTask.java | 15 +++------------ .../internal/tck/TestedVersionUpdaterTask.java | 2 +- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/TckExtension.java b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/TckExtension.java index 04cd2485a07..92960232cbb 100644 --- a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/TckExtension.java +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/TckExtension.java @@ -145,7 +145,7 @@ public Path getTestDir(String coordinates) { if (Files.isDirectory(conventional)) { return conventional; } - + // Secondary fallback: derive test dir from metadata "metadata-version" try { Path mdDir = getMetadataDir(coordinates); // .../metadata/// diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/FetchExistingLibrariesWithNewerVersionsTask.java b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/FetchExistingLibrariesWithNewerVersionsTask.java index e84c536c167..24527005f8e 100644 --- a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/FetchExistingLibrariesWithNewerVersionsTask.java +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/FetchExistingLibrariesWithNewerVersionsTask.java @@ -10,6 +10,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import org.graalvm.internal.tck.TestedVersionUpdaterTask; import org.graalvm.internal.tck.model.MetadataVersionsIndexEntry; import org.graalvm.internal.tck.model.SkippedVersionEntry; import org.gradle.api.DefaultTask; @@ -37,16 +38,6 @@ public abstract class FetchExistingLibrariesWithNewerVersionsTask extends Defaul private static final List INFRASTRUCTURE_TESTS = List.of("samples", "org.example"); - /** - * Identifies library versions, including optional pre-release and ".Final" suffixes. - * - * Pre-release identifiers (case-insensitive): alpha, beta, rc, cr, m, ea, b, preview, and pure numeric suffixes. - * Versions ending with ".Final" are treated as full releases of the base version. - */ - private static final Pattern VERSION_PATTERN = Pattern.compile("(?i)^(\\\\d+(?:\\\\.\\\\d+)*)" - + "(?:\\\\.Final)?" - + "(?:[-.](alpha\\\\d*|beta\\\\d*|rc\\\\d*|cr\\\\d*|m\\\\d+|ea\\\\d*|b\\\\d+|\\\\d+|preview)(?:[-.].*)?)?$"); - @TaskAction public void action() { // Derive set of distinct library modules: group:artifact @@ -144,7 +135,7 @@ static List filterPreReleases(List versions) { // Identify base versions that have a full release Set releases = new HashSet<>(); for (String v : versions) { - Matcher m = VERSION_PATTERN.matcher(v); + Matcher m = TestedVersionUpdaterTask.VERSION_PATTERN.matcher(v); if (m.matches() && m.group(2) == null) { releases.add(m.group(1)); } @@ -152,7 +143,7 @@ static List filterPreReleases(List versions) { List result = new ArrayList<>(); for (String v : versions) { - Matcher m = VERSION_PATTERN.matcher(v); + Matcher m = TestedVersionUpdaterTask.VERSION_PATTERN.matcher(v); if (m.matches()) { String base = m.group(1); String preSuffix = m.groupCount() > 1 ? m.group(2) : null; diff --git a/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/TestedVersionUpdaterTask.java b/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/TestedVersionUpdaterTask.java index dfbd37d661f..0fce76f2dac 100644 --- a/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/TestedVersionUpdaterTask.java +++ b/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/TestedVersionUpdaterTask.java @@ -55,7 +55,7 @@ public abstract class TestedVersionUpdaterTask extends DefaultTask { *

* Versions ending with ".Final" or `.RELEASE` are treated as full releases of the base version. */ - private static final Pattern VERSION_PATTERN = Pattern.compile( + public static final Pattern VERSION_PATTERN = Pattern.compile( "(?i)^(\\d+(?:\\.\\d+)*)(?:\\.Final|\\.RELEASE)?(?:[-.](alpha\\d*|beta\\d*|rc\\d*|cr\\d*|m\\d+|ea\\d*|b\\d+|\\d+|preview)(?:[-.].*)?)?$" ); From 25a7204f2b03fb1f3240fdf2a2005c910c14a9b9 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Wed, 7 Jan 2026 11:47:06 +0100 Subject: [PATCH 15/29] Remove secondary fallback, we will add it later --- .../graalvm/internal/tck/harness/TckExtension.java | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/TckExtension.java b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/TckExtension.java index 92960232cbb..57d5abd0064 100644 --- a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/TckExtension.java +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/TckExtension.java @@ -146,19 +146,6 @@ public Path getTestDir(String coordinates) { return conventional; } - // Secondary fallback: derive test dir from metadata "metadata-version" - try { - Path mdDir = getMetadataDir(coordinates); // .../metadata/// - Path mdVersion = mdDir.getFileName(); - if (mdVersion != null) { - Path testsForMetadataVersion = testRoot().resolve(groupId).resolve(artifactId).resolve(mdVersion.toString()); - if (Files.isDirectory(testsForMetadataVersion)) { - return testsForMetadataVersion; - } - } - } catch (Exception ignored) { - // ignore and fall through to error - } throw new RuntimeException("Missing test-directory for coordinates `" + coordinates + "`"); } From 15207fff743baa4d7643cbf79981b78a2a4b4848 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Wed, 7 Jan 2026 12:41:05 +0100 Subject: [PATCH 16/29] Fix fetch existing libraries with newer versions --- ...stingLibrariesWithNewerVersionsTask.groovy | 239 ------------------ ...xistingLibrariesWithNewerVersionsTask.java | 29 +-- 2 files changed, 1 insertion(+), 267 deletions(-) delete mode 100644 tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/FetchExistingLibrariesWithNewerVersionsTask.groovy rename tests/tck-build-logic/src/main/{groovy => java}/org/graalvm/internal/tck/harness/tasks/FetchExistingLibrariesWithNewerVersionsTask.java (85%) diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/FetchExistingLibrariesWithNewerVersionsTask.groovy b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/FetchExistingLibrariesWithNewerVersionsTask.groovy deleted file mode 100644 index f851246f4f5..00000000000 --- a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/FetchExistingLibrariesWithNewerVersionsTask.groovy +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright and related rights waived via CC0 - * - * You should have received a copy of the CC0 legalcode along with this - * work. If not, see . - */ -package org.graalvm.internal.tck.harness.tasks - - -import com.fasterxml.jackson.annotation.JsonInclude -import com.fasterxml.jackson.core.type.TypeReference -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.SerializationFeature -import groovy.json.JsonOutput -import org.graalvm.internal.tck.model.MetadataVersionsIndexEntry -import org.graalvm.internal.tck.model.SkippedVersionEntry -import org.gradle.api.DefaultTask -import org.gradle.api.provider.ListProperty -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.TaskAction -import org.gradle.util.internal.VersionNumber - -import java.util.regex.Matcher -import java.util.regex.Pattern - -@SuppressWarnings("unused") -abstract class FetchExistingLibrariesWithNewerVersionsTask extends DefaultTask { - - @Input - abstract ListProperty getAllLibraryCoordinates() - - private static final List INFRASTRUCTURE_TESTS = List.of("samples", "org.example") - - /** - * Identifies library versions, including optional pre-release, ".Final" and ".RELEASE" suffixes. - *

- * A version is considered a pre-release if it has a suffix (following the last '.' or '-') matching - * one of these case-insensitive patterns: - *

    - *
  • {@code alpha} followed by optional numbers (e.g., "alpha", "Alpha1", "alpha123")
  • - *
  • {@code beta} followed by optional numbers (e.g., "beta", "Beta2", "BETA45")
  • - *
  • {@code rc} followed by optional numbers (e.g., "rc", "RC1", "rc99")
  • - *
  • {@code cr} followed by optional numbers (e.g., "cr", "CR3", "cr10")
  • - *
  • {@code m} followed by REQUIRED numbers (e.g., "M1", "m23")
  • - *
  • {@code ea} followed by optional numbers (e.g., "ea", "ea2", "ea15")
  • - *
  • {@code b} followed by REQUIRED numbers (e.g., "b0244", "b5")
  • - *
  • {@code preview} followed by optional numbers (e.g., "preview", "preview1", "preview42")
  • - *
  • Numeric suffixes separated by '-' (e.g., "-1", "-123")
  • - *
- *

- * Versions ending with ".Final" or ".RELEASE" are treated as full releases of the base version. - */ - private static final Pattern VERSION_PATTERN = ~/(?i)^(\d+(?:\.\d+)*)(?:\.Final|\.RELEASE)?(?:[-.](alpha\d*|beta\d*|rc\d*|cr\d*|m\d+|ea\d*|b\d+|\d+|preview)(?:[-.].*)?)?$/ - - @TaskAction - void action() { - // get all existing libraries - Set libraries = [] - getAllLibraryCoordinates().get().forEach { - libraries.add(it.substring(0, it.lastIndexOf(":"))) - } - - // foreach existing library find newer versions than the latest one tested except for infrastructure tests - List newerVersions = new ArrayList<>() - libraries.forEach { - String libraryName = it - if (INFRASTRUCTURE_TESTS.stream().noneMatch(testName -> libraryName.startsWith(testName))) { - List versions = getNewerVersionsFor(libraryName, getLatestLibraryVersion(libraryName)) - List skipped = getSkippedVersions(libraryName) - - // filter out skipped versions - versions = versions.findAll { !skipped.contains(it) } - - versions.forEach { - newerVersions.add(libraryName.concat(":").concat(it)) - } - } - } - - def map = [:] - newerVersions.each { coord -> - def (group, artifact, version) = coord.tokenize(':') - def key = "${group}:${artifact}" - map[key] = (map[key] ?: []) + version - } - def pairs = map.collect { k, v -> [name: k, versions: v] } - - println JsonOutput.toJson(pairs) - } - - static List getNewerVersionsFor(String library, String startingVersion) { - def baseUrl = "https://repo1.maven.org/maven2" - String[] libraryParts = library.split(":") - String group = libraryParts[0].replace(".", "/") - String artifact = libraryParts[1] - def data = new URL(baseUrl + "/" + group + "/" + artifact + "/" + "maven-metadata.xml").getText() - - List newerVersions = getNewerVersionsFromLibraryIndex(data, startingVersion, library) - - // filter out already tested versions - List testedVersions = getTestedVersions(library); - newerVersions.removeAll(testedVersions); - - // filter pre-release versions if full release exists - return filterPreReleases(newerVersions) - } - - static List getNewerVersionsFromLibraryIndex(String index, String startingVersion, String libraryName) { - Pattern pattern = Pattern.compile("(.*)"); - Matcher matcher = pattern.matcher(index); - List allVersions = new ArrayList<>(); - - if (matcher.groupCount() < 1) { - throw new RuntimeException("Cannot find versions in the given index file: " + libraryName); - } - - while (matcher.find()) { - allVersions.add(matcher.group(1)); - } - - int indexOfStartingVersion = allVersions.indexOf(startingVersion); - if (indexOfStartingVersion < 0) { - return new ArrayList<>(); - } - - allVersions = allVersions.subList(indexOfStartingVersion, allVersions.size()); - - return allVersions.subList(1, allVersions.size()); - } - - static List filterPreReleases(List versions) { - // identify full releases - Set releases = versions.collect { v -> - def matcher = VERSION_PATTERN.matcher(v) - if (matcher.matches() && matcher.group(2) == null) { - return matcher.group(1) - } - return null - }.findAll { it != null } as Set - - // filter pre-releases if full release exists - return versions.findAll { v -> - def matcher = VERSION_PATTERN.matcher(v) - if (matcher.matches()) { - String base = matcher.group(1) - String preSuffix = matcher.groupCount() > 1 ? matcher.group(2) : null - return preSuffix == null || !releases.contains(base) - } - true - } - } - - static String getLatestLibraryVersion(String libraryModule) { - try { - List testedVersions = getTestedVersions(libraryModule); - if (testedVersions.isEmpty()) { - throw new IllegalStateException("Cannot find any tested version for: " + libraryModule); - } - - testedVersions.sort(Comparator.comparing(VersionNumber::parse)); - return testedVersions.get(testedVersions.size() - 1); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - /** - * Reads the tested versions of a given library from its metadata index file. - */ - static List getTestedVersions(String libraryModule) { - try { - String[] coordinates = libraryModule.split(":"); - String group = coordinates[0]; - String artifact = coordinates[1]; - - File indexFile = new File("metadata/" + group + "/" + artifact + "/index.json"); - if (!indexFile.exists()) { - return Collections.emptyList(); - } - - ObjectMapper objectMapper = new ObjectMapper() - .enable(SerializationFeature.INDENT_OUTPUT) - .setSerializationInclusion(JsonInclude.Include.NON_NULL); - - List entries = objectMapper.readValue(indexFile, - new TypeReference>() {}); - - List testedVersions = new ArrayList<>(); - for (MetadataVersionsIndexEntry entry : entries) { - testedVersions.addAll(entry.testedVersions()); - } - return testedVersions; - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - /** - * Returns all versions of a given library that are marked as skipped in the - * metadata index. - *

- * For the provided Maven coordinates (in the format {@code :}), - * this method reads the corresponding {@code index.json} file located under: - * {@code metadata///index.json} - * and collects all version entries listed under {@code skipped-versions}. - */ - static List getSkippedVersions(String libraryModule) { - try { - String[] coordinates = libraryModule.split(":"); - String group = coordinates[0]; - String artifact = coordinates[1]; - - File coordinatesMetadataIndex = new File("metadata/" + group + "/" + artifact + "/index.json"); - ObjectMapper objectMapper = new ObjectMapper() - .enable(SerializationFeature.INDENT_OUTPUT) - .setSerializationInclusion(JsonInclude.Include.NON_NULL); - - List entries = objectMapper.readValue( - coordinatesMetadataIndex, - new TypeReference>() {} - ); - - List skipped = new ArrayList<>(); - for (MetadataVersionsIndexEntry entry : entries) { - if (entry.skippedVersions() != null) { - skipped.addAll( - entry.skippedVersions().stream() - .map(SkippedVersionEntry::version) - .toList() - ); - } - } - - return skipped; - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/FetchExistingLibrariesWithNewerVersionsTask.java b/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/harness/tasks/FetchExistingLibrariesWithNewerVersionsTask.java similarity index 85% rename from tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/FetchExistingLibrariesWithNewerVersionsTask.java rename to tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/harness/tasks/FetchExistingLibrariesWithNewerVersionsTask.java index 24527005f8e..2a50c002c09 100644 --- a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/FetchExistingLibrariesWithNewerVersionsTask.java +++ b/tests/tck-build-logic/src/main/java/org/graalvm/internal/tck/harness/tasks/FetchExistingLibrariesWithNewerVersionsTask.java @@ -9,7 +9,6 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; import org.graalvm.internal.tck.TestedVersionUpdaterTask; import org.graalvm.internal.tck.model.MetadataVersionsIndexEntry; import org.graalvm.internal.tck.model.SkippedVersionEntry; @@ -26,10 +25,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -/** - * Lists existing libraries that have newer upstream versions available (not yet tested). - * Produces a JSON array of objects: [{"name": "group:artifact", "versions": ["x.y.z", ...]}, ...] - */ @SuppressWarnings("unused") public abstract class FetchExistingLibrariesWithNewerVersionsTask extends DefaultTask { @@ -40,7 +35,6 @@ public abstract class FetchExistingLibrariesWithNewerVersionsTask extends Defaul @TaskAction public void action() { - // Derive set of distinct library modules: group:artifact Set libraries = new LinkedHashSet<>(); for (String coord : getAllLibraryCoordinates().get()) { int last = coord.lastIndexOf(':'); @@ -49,7 +43,6 @@ public void action() { } } - // For each library, compute newer versions and filter out infrastructure modules List newerVersions = new ArrayList<>(); for (String libraryName : libraries) { if (INFRASTRUCTURE_TESTS.stream().noneMatch(libraryName::startsWith)) { @@ -62,7 +55,6 @@ public void action() { } } - // Aggregate by library name to the requested structure Map> grouped = new LinkedHashMap<>(); for (String coord : newerVersions) { String[] parts = coord.split(":", -1); @@ -80,7 +72,6 @@ public void action() { try { ObjectMapper om = new ObjectMapper() - .enable(SerializationFeature.INDENT_OUTPUT) .setSerializationInclusion(JsonInclude.Include.NON_NULL); System.out.println(om.writeValueAsString(pairs)); } catch (IOException e) { @@ -98,11 +89,9 @@ static List getNewerVersionsFor(String library, String startingVersion) List newerVersions = getNewerVersionsFromLibraryIndex(data, startingVersion, library); - // filter out already tested versions List testedVersions = getTestedVersions(library); newerVersions.removeAll(testedVersions); - // filter pre-release versions if full release exists return filterPreReleases(newerVersions); } catch (IOException e) { throw new RuntimeException(e); @@ -114,10 +103,6 @@ static List getNewerVersionsFromLibraryIndex(String index, String starti Matcher matcher = pattern.matcher(index); List allVersions = new ArrayList<>(); - if (matcher.groupCount() < 1) { - throw new RuntimeException("Cannot find versions in the given index file: " + libraryName); - } - while (matcher.find()) { allVersions.add(matcher.group(1)); } @@ -132,7 +117,6 @@ static List getNewerVersionsFromLibraryIndex(String index, String starti } static List filterPreReleases(List versions) { - // Identify base versions that have a full release Set releases = new HashSet<>(); for (String v : versions) { Matcher m = TestedVersionUpdaterTask.VERSION_PATTERN.matcher(v); @@ -170,9 +154,6 @@ static String getLatestLibraryVersion(String libraryModule) { } } - /** - * Reads the tested versions of a given library from its metadata index file. - */ static List getTestedVersions(String libraryModule) { try { String[] coordinates = libraryModule.split(":"); @@ -185,7 +166,6 @@ static List getTestedVersions(String libraryModule) { } ObjectMapper objectMapper = new ObjectMapper() - .enable(SerializationFeature.INDENT_OUTPUT) .setSerializationInclusion(JsonInclude.Include.NON_NULL); List entries = objectMapper.readValue( @@ -200,12 +180,6 @@ static List getTestedVersions(String libraryModule) { } } - /** - * Returns all versions of a given library that are marked as skipped in the metadata index. - * - * For the provided Maven coordinates (groupId:artifactId), reads metadata///index.json - * and collects versions under "skipped-versions". - */ static List getSkippedVersions(String libraryModule) { try { String[] coordinates = libraryModule.split(":"); @@ -214,11 +188,10 @@ static List getSkippedVersions(String libraryModule) { File coordinatesMetadataIndex = new File("metadata/" + group + "/" + artifact + "/index.json"); if (!coordinatesMetadataIndex.exists()) { - return Collections.emptyList(); + throw new RuntimeException("Missing index.json for " + libraryModule); } ObjectMapper objectMapper = new ObjectMapper() - .enable(SerializationFeature.INDENT_OUTPUT) .setSerializationInclusion(JsonInclude.Include.NON_NULL); List entries = objectMapper.readValue( From e533dccaff767cd25ad2b1280305a53f5b6cecb8 Mon Sep 17 00:00:00 2001 From: jvukicev Date: Thu, 8 Jan 2026 08:43:17 +0100 Subject: [PATCH 17/29] Add timeouts to test runs in run-consecutive-tests.sh --- .github/workflows/scripts/run-consecutive-tests.sh | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/scripts/run-consecutive-tests.sh b/.github/workflows/scripts/run-consecutive-tests.sh index 15ea47b6cdd..4335329d39d 100644 --- a/.github/workflows/scripts/run-consecutive-tests.sh +++ b/.github/workflows/scripts/run-consecutive-tests.sh @@ -6,6 +6,8 @@ set -u +TIMEOUT="10m" + if [ $# -ne 2 ]; then echo "Usage: $0 " exit 1 @@ -41,17 +43,23 @@ run_multiple_attempts() { echo "Re-running stage '$stage' (attempt $((attempt + 1))/$max_attempts)" fi - eval "$cmd_str" + timeout "$TIMEOUT" bash -c "$cmd_str" result=$? if [ "$result" -eq 0 ]; then return 0 fi + if [ "$result" -eq 124 ]; then + echo "⚠️ TIMEOUT: '$stage' for $VERSION took longer than $TIMEOUT." + else + echo "❌ ERROR: '$stage' for $VERSION failed with exit code $result." + fi + attempt=$((attempt + 1)) done - echo "FAILED [$stage][$VERSION][$cmd_str]" + echo "FAILED[$stage][$VERSION][$gradle_command]" return $result } From b69654febc18814611fad59c2c7c66e5f9fef2ca Mon Sep 17 00:00:00 2001 From: jvukicev Date: Thu, 8 Jan 2026 08:44:59 +0100 Subject: [PATCH 18/29] Fix 'latest' version retrieval --- ...rify-new-library-version-compatibility.yml | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/.github/workflows/verify-new-library-version-compatibility.yml b/.github/workflows/verify-new-library-version-compatibility.yml index 32626b9ac34..fb3887e15f3 100644 --- a/.github/workflows/verify-new-library-version-compatibility.yml +++ b/.github/workflows/verify-new-library-version-compatibility.yml @@ -119,8 +119,32 @@ jobs: id: extract-params run: | LIBRARY_PATH=$(echo "${{ matrix.item.name }}" | sed 's/:/\//g') - LATEST_VERSION=$(find tests/src/$LIBRARY_PATH/* -maxdepth 1 -type d | sort -V | tail -1 | cut -d '/' -f5) - TEST_PATH="$LIBRARY_PATH/$LATEST_VERSION" + INDEX_PATH="metadata/$LIBRARY_PATH/index.json" + + # 1. Determine the target version entry + # Try 'latest' tag first. If not found, sort all entries by version and take the highest. + TARGET_ENTRY=$(jq -c '.[] | select(.latest == true)' "$INDEX_PATH" 2>/dev/null) + + if [ -z "$TARGET_ENTRY" ]; then + HIGHEST_VERSION=$(jq -r '.[]."metadata-version"' "$INDEX_PATH" | sort -V | tail -1) + + # Now get the JSON entry for that specific version + TARGET_ENTRY=$(jq -c ".[] | select(.\"metadata-version\" == \"$HIGHEST_VERSION\")" "$INDEX_PATH") + fi + + # 2. Extract versions from the selected entry + if [ -n "$TARGET_ENTRY" ]; then + LATEST_VERSION=$(echo "$TARGET_ENTRY" | jq -r '."metadata-version"') + # Use test-version from this entry if it exists, otherwise use its metadata-version + TEST_VERSION_FOLDER=$(echo "$TARGET_ENTRY" | jq -r '."test-version" // ."metadata-version"') + else + # Fallback to filesystem parsing + LATEST_VERSION=$(jq -r '.[]."metadata-version"' "$INDEX_PATH" | sort -V | tail -1) + TEST_VERSION_FOLDER=$(find tests/src/$LIBRARY_PATH/* -maxdepth 0 -type d | sort -V | tail -1 | xargs basename) + fi + + # 3. Finalize paths + TEST_PATH="$LIBRARY_PATH/$TEST_VERSION_FOLDER" TEST_COORDINATES=$(echo "$TEST_PATH" | tr / :) echo "LATEST_VERSION=$LATEST_VERSION" >> ${GITHUB_ENV} From 674400a71b8c6dbb57b722076d999403924b7692 Mon Sep 17 00:00:00 2001 From: jvukicev Date: Thu, 8 Jan 2026 10:30:07 +0100 Subject: [PATCH 19/29] Revert "Fix 'latest' version retrieval" This reverts commit 3074d36d7620c26639e345304d297552f1a9e18c. --- ...rify-new-library-version-compatibility.yml | 28 ++----------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/.github/workflows/verify-new-library-version-compatibility.yml b/.github/workflows/verify-new-library-version-compatibility.yml index fb3887e15f3..32626b9ac34 100644 --- a/.github/workflows/verify-new-library-version-compatibility.yml +++ b/.github/workflows/verify-new-library-version-compatibility.yml @@ -119,32 +119,8 @@ jobs: id: extract-params run: | LIBRARY_PATH=$(echo "${{ matrix.item.name }}" | sed 's/:/\//g') - INDEX_PATH="metadata/$LIBRARY_PATH/index.json" - - # 1. Determine the target version entry - # Try 'latest' tag first. If not found, sort all entries by version and take the highest. - TARGET_ENTRY=$(jq -c '.[] | select(.latest == true)' "$INDEX_PATH" 2>/dev/null) - - if [ -z "$TARGET_ENTRY" ]; then - HIGHEST_VERSION=$(jq -r '.[]."metadata-version"' "$INDEX_PATH" | sort -V | tail -1) - - # Now get the JSON entry for that specific version - TARGET_ENTRY=$(jq -c ".[] | select(.\"metadata-version\" == \"$HIGHEST_VERSION\")" "$INDEX_PATH") - fi - - # 2. Extract versions from the selected entry - if [ -n "$TARGET_ENTRY" ]; then - LATEST_VERSION=$(echo "$TARGET_ENTRY" | jq -r '."metadata-version"') - # Use test-version from this entry if it exists, otherwise use its metadata-version - TEST_VERSION_FOLDER=$(echo "$TARGET_ENTRY" | jq -r '."test-version" // ."metadata-version"') - else - # Fallback to filesystem parsing - LATEST_VERSION=$(jq -r '.[]."metadata-version"' "$INDEX_PATH" | sort -V | tail -1) - TEST_VERSION_FOLDER=$(find tests/src/$LIBRARY_PATH/* -maxdepth 0 -type d | sort -V | tail -1 | xargs basename) - fi - - # 3. Finalize paths - TEST_PATH="$LIBRARY_PATH/$TEST_VERSION_FOLDER" + LATEST_VERSION=$(find tests/src/$LIBRARY_PATH/* -maxdepth 1 -type d | sort -V | tail -1 | cut -d '/' -f5) + TEST_PATH="$LIBRARY_PATH/$LATEST_VERSION" TEST_COORDINATES=$(echo "$TEST_PATH" | tr / :) echo "LATEST_VERSION=$LATEST_VERSION" >> ${GITHUB_ENV} From f30bd550d680dfebee14cfebd46e2ab9f12e8739 Mon Sep 17 00:00:00 2001 From: jvukicev Date: Thu, 8 Jan 2026 10:57:47 +0100 Subject: [PATCH 20/29] Add stacktrace printing when timeouts happen --- .github/workflows/scripts/run-consecutive-tests.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/scripts/run-consecutive-tests.sh b/.github/workflows/scripts/run-consecutive-tests.sh index 4335329d39d..1e7574a54c9 100644 --- a/.github/workflows/scripts/run-consecutive-tests.sh +++ b/.github/workflows/scripts/run-consecutive-tests.sh @@ -6,7 +6,7 @@ set -u -TIMEOUT="10m" +TIMEOUT="5m" if [ $# -ne 2 ]; then echo "Usage: $0 " @@ -43,15 +43,15 @@ run_multiple_attempts() { echo "Re-running stage '$stage' (attempt $((attempt + 1))/$max_attempts)" fi - timeout "$TIMEOUT" bash -c "$cmd_str" + timeout --signal=QUIT --kill-after=20s "$TIMEOUT" bash -c "$cmd_str" result=$? if [ "$result" -eq 0 ]; then return 0 fi - if [ "$result" -eq 124 ]; then - echo "⚠️ TIMEOUT: '$stage' for $VERSION took longer than $TIMEOUT." + if [ "$result" -eq 124 ] || [ "$result" -eq 131 ] || [ "$result" -eq 137 ]; then + echo "⚠️ TIMEOUT: '$stage' for $VERSION took longer than $TIMEOUT (exit code $result)." else echo "❌ ERROR: '$stage' for $VERSION failed with exit code $result." fi From fded8a341e1b0ba293b33b91cf46dfc07adae7c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jovan=20Vuki=C4=87evi=C4=87?= Date: Thu, 8 Jan 2026 15:17:29 +0100 Subject: [PATCH 21/29] Fix "latest" version retrieval in verify-new-library-version-compatibility.yml and improve index.json validation (#917) --- .github/workflows/index-file-validation.yml | 2 +- ...rify-new-library-version-compatibility.yml | 10 +- docs/DEVELOPING.md | 2 +- .../ch.qos.logback/logback-classic/index.json | 18 +-- .../org.graalvm.internal.tck-harness.gradle | 97 ++++++++++++++-- .../harness/tasks/ValidateIndexFilesTask.java | 108 +++++++++++++++++- 6 files changed, 203 insertions(+), 34 deletions(-) diff --git a/.github/workflows/index-file-validation.yml b/.github/workflows/index-file-validation.yml index 1e88f50caa2..afdb451d714 100644 --- a/.github/workflows/index-file-validation.yml +++ b/.github/workflows/index-file-validation.yml @@ -37,7 +37,7 @@ jobs: run: | ./gradlew generateChangedIndexFileCoordinatesList -PbaseCommit=${{ github.event.pull_request.base.sha }} -PnewCommit=${{ github.event.pull_request.head.sha }} - - name: "Check that the changed index.json files conform to their schemas" + - name: "Validate index file schema and version consistency" if: steps.filter.outputs.changed == 'true' run: | ./gradlew validateIndexFiles -Pcoordinates="${{ steps.get-coords.outputs.changed-coordinates }}" diff --git a/.github/workflows/verify-new-library-version-compatibility.yml b/.github/workflows/verify-new-library-version-compatibility.yml index 32626b9ac34..d89f71bbe84 100644 --- a/.github/workflows/verify-new-library-version-compatibility.yml +++ b/.github/workflows/verify-new-library-version-compatibility.yml @@ -117,15 +117,7 @@ jobs: - name: "Extract test path and library version" if: steps.check_existing_issue.outputs.skip != 'true' id: extract-params - run: | - LIBRARY_PATH=$(echo "${{ matrix.item.name }}" | sed 's/:/\//g') - LATEST_VERSION=$(find tests/src/$LIBRARY_PATH/* -maxdepth 1 -type d | sort -V | tail -1 | cut -d '/' -f5) - TEST_PATH="$LIBRARY_PATH/$LATEST_VERSION" - TEST_COORDINATES=$(echo "$TEST_PATH" | tr / :) - - echo "LATEST_VERSION=$LATEST_VERSION" >> ${GITHUB_ENV} - echo "TEST_PATH=$TEST_PATH" >> ${GITHUB_ENV} - echo "TEST_COORDINATES=$TEST_COORDINATES" >> ${GITHUB_ENV} + run: ./gradlew extractLibraryTestParams -Pcoordinates="${{ matrix.item.name }}" - name: "Pull allowed docker images" if: steps.check_existing_issue.outputs.skip != 'true' diff --git a/docs/DEVELOPING.md b/docs/DEVELOPING.md index f0e1f232435..669425d55ae 100644 --- a/docs/DEVELOPING.md +++ b/docs/DEVELOPING.md @@ -51,7 +51,7 @@ For a single coordinate, CI runs three steps in this order: ```console ./gradlew pullAllowedDockerImages -Pcoordinates=org.postgresql:postgresql:42.7.3 ``` -2. Validate index.json schemas: +2. Validate index.json file integrity and schemas: ```console ./gradlew validateIndexFiles -Pcoordinates=org.postgresql:postgresql:42.7.3 ``` diff --git a/metadata/ch.qos.logback/logback-classic/index.json b/metadata/ch.qos.logback/logback-classic/index.json index 3c370b5866d..6c40f094f34 100644 --- a/metadata/ch.qos.logback/logback-classic/index.json +++ b/metadata/ch.qos.logback/logback-classic/index.json @@ -5,15 +5,7 @@ "metadata-version" : "1.4.9", "test-version": "1.4.1", "tested-versions" : [ - "1.4.9" - ] - }, - { - "module" : "ch.qos.logback:logback-classic", - "default-for" : "1\\.4\\.[0-8]", - "metadata-version" : "1.4.1", - "tested-versions" : [ - "1.4.1", + "1.4.9", "1.4.10", "1.4.11", "1.4.12", @@ -28,6 +20,14 @@ "1.5.6" ] }, + { + "module" : "ch.qos.logback:logback-classic", + "default-for" : "1\\.4\\.[0-8]", + "metadata-version" : "1.4.1", + "tested-versions" : [ + "1.4.1" + ] + }, { "module" : "ch.qos.logback:logback-classic", "default-for" : "1\\.[0-3]\\..*", diff --git a/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-harness.gradle b/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-harness.gradle index 35e7512003c..0237c9d675d 100644 --- a/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-harness.gradle +++ b/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-harness.gradle @@ -50,6 +50,15 @@ def writeGithubOutput(String key, String value) { } } +def writeGithubEnv(String key, String value) { + def path = System.getenv("GITHUB_ENV") + if (path == null || path.trim().isEmpty()) { + println "${key}=${value}" + } else { + new File(path).append("${key}=${value}\n") + } +} + String coordinateFilter = Objects.requireNonNullElse(project.findProperty("coordinates"), "") @@ -64,31 +73,31 @@ if (CoordinateUtils.isFractionalBatch(coordinateFilter)) { matchingCoordinates = matchingCoordinates.findAll { !it.startsWith("samples:") } - // gradle checkstyle -Pcoordinates= +// gradle checkstyle -Pcoordinates= tasks.register("checkstyle", CheckstyleInvocationTask.class) { task -> task.setDescription("Runs checkstyle on all subprojects") task.setGroup(JavaBasePlugin.VERIFICATION_GROUP) } - // gradle compileTestJava -Pcoordinates= +// gradle compileTestJava -Pcoordinates= tasks.register("compileTestJava", CompileTestJavaInvocationTask.class) { task -> task.setDescription("Compiles sources (javac) for all subprojects") task.setGroup(LifecycleBasePlugin.BUILD_GROUP) } - // gradle javaTest -Pcoordinates= +// gradle javaTest -Pcoordinates= tasks.register("javaTest", JavaTestInvocationTask.class) { task -> task.setDescription("Runs JVM tests (Gradle 'test') for all subprojects") task.setGroup(JavaBasePlugin.VERIFICATION_GROUP) } - // gradle nativeTestCompile -Pcoordinates= +// gradle nativeTestCompile -Pcoordinates= tasks.register("nativeTestCompile", NativeTestCompileInvocationTask.class) { task -> task.setDescription("Compiles native tests (nativeTestCompile) for all subprojects") task.setGroup(LifecycleBasePlugin.BUILD_GROUP) } - // gradle clean -Pcoordinates= +// gradle clean -Pcoordinates= if (tasks.findByName("clean") == null) { tasks.register("clean", CleanInvocationTask.class) { t -> t.setDescription("Cleans all matching subprojects") @@ -102,7 +111,7 @@ if (tasks.findByName("clean") == null) { tasks.named("clean").configure { it.dependsOn("tckClean") } } - // gradle test -Pcoordinates= +// gradle test -Pcoordinates= if (tasks.findByName("test") == null) { tasks.register("test", TestInvocationTask.class) { t -> t.setDescription("Runs Native Image tests for all matching coordinates") @@ -169,8 +178,8 @@ Map> matrixDefaultsFor(String section) { def sec = ci[section] if (sec == null) throw new GradleException("Missing '${section}' in ci.json") [ - "version": (sec["java"] as List), - "os": (sec["os"] as List) + "version": (sec["java"] as List), + "os": (sec["os"] as List) ] } @@ -187,7 +196,7 @@ Provider generateMatrixMatchingCoordinates = tasks.register("generateMatri } } - // gradle generateMatrixBatchedCoordinates [-Pbatches=] +// gradle generateMatrixBatchedCoordinates [-Pbatches=] Provider generateMatrixBatchedCoordinates = tasks.register("generateMatrixBatchedCoordinates", DefaultTask) { task -> task.setDescription("Returns matrix definition populated with fractional batch coordinates (k/n)") task.setGroup(METADATA_GROUP) @@ -290,7 +299,7 @@ Provider generateInfrastructureChangedCoordinatesMatrix = tasks.register(" } def matrix = [ - "coordinates": selected + "coordinates": selected ] matrix.putAll(matrixDefaultsFor("generateInfrastructureChangedCoordinatesMatrix")) @@ -362,3 +371,71 @@ tasks.register("addLibraryAsLatestMetadataIndexJson", DefaultTask) { task -> MetadataGenerationUtils.makeVersionLatestInIndexJson(project.layout, coords, testVersion) } } + +// gradle extractLibraryTestParams -Pcoordinates= +tasks.register("extractLibraryTestParams", DefaultTask) { task -> + task.setDescription("Extracts TEST_PATH, LATEST_VERSION and TEST_COORDINATES for given coordinates (group:artifact[:version]). Writes them into GITHUB_ENV.") + task.setGroup(METADATA_GROUP) + task.doFirst { + if (!project.hasProperty("coordinates")) { + throw new GradleException("Missing 'coordinates' property! Rerun Gradle with -Pcoordinates=") + } + def coordsStr = project.findProperty("coordinates").toString() + def parts = coordsStr.split(":") + if (parts.length < 2) { + throw new GradleException("Invalid 'coordinates' value; expected group:artifact or group:artifact:version, got: ${coordsStr}") + } + String lib = parts[0] + ":" + parts[1] + + String libPath = lib.replace(":", "/") + File indexFile = project.file("metadata/${libPath}/index.json") + + Map latestEntry = null + if (indexFile.exists()) { + def entries = new JsonSlurper().parse(indexFile) as List + latestEntry = entries.find { e -> Boolean.TRUE.equals(e["latest"]) } + if (latestEntry == null && !entries.isEmpty()) { + latestEntry = entries.max { e -> VersionNumber.parse((e["metadata-version"] ?: "").toString()) } + } + } + + String latestVersion = null + String testVersionFolder = null + + if (latestEntry != null) { + latestVersion = (latestEntry["metadata-version"] ?: "").toString() + def tv = latestEntry["test-version"] + testVersionFolder = (tv != null && tv.toString().trim()) ? tv.toString() : latestVersion + } else { + if (!indexFile.exists()) { + throw new GradleException("Missing index.json at ${indexFile}. Cannot determine latest metadata version.") + } + def entries = new JsonSlurper().parse(indexFile) as List + if (entries.isEmpty()) { + throw new GradleException("Empty index.json at ${indexFile}.") + } + latestVersion = entries.collect { it["metadata-version"].toString() } + .max { v -> VersionNumber.parse(v) } + } + + if (testVersionFolder == null || testVersionFolder.trim().isEmpty()) { + File testsLibDir = project.file("tests/src/${libPath}") + if (!testsLibDir.exists()) { + throw new GradleException("No tests directory found for ${lib} at ${testsLibDir}") + } + File[] dirs = testsLibDir.listFiles() + List versionDirs = dirs == null ? [] : dirs.findAll { it.isDirectory() } + if (versionDirs.isEmpty()) { + throw new GradleException("No version subdirectories found in ${testsLibDir}") + } + testVersionFolder = versionDirs.max { d -> VersionNumber.parse(d.name) }.name + } + + String testPath = "${libPath}/${testVersionFolder}" + String testCoordinates = testPath.replace("/", ":") + + writeGithubEnv("LATEST_VERSION", latestVersion) + writeGithubEnv("TEST_PATH", testPath) + writeGithubEnv("TEST_COORDINATES", testCoordinates) + } +} diff --git a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/ValidateIndexFilesTask.java b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/ValidateIndexFilesTask.java index b55a5770473..08c60be2cff 100644 --- a/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/ValidateIndexFilesTask.java +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/ValidateIndexFilesTask.java @@ -19,6 +19,7 @@ import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.options.Option; import org.jetbrains.annotations.NotNull; +import org.gradle.util.internal.VersionNumber; import java.io.File; import java.util.ArrayList; @@ -137,15 +138,25 @@ private void executeValidation(Set targetFiles) { ); JsonNode json = mapper.readTree(jsonFile); - Set errors = schema.validate(json); + int beforeFailures = failures.size(); - if (errors.isEmpty()) { - getLogger().lifecycle("✅ " + filePath + ": Valid"); - } else { + // Schema validation + Set errors = schema.validate(json); + if (!errors.isEmpty()) { for (ValidationMessage err : errors) { failures.add("❌ " + filePath + ": " + err.getMessage()); } } + + // Additional semantic validations for library metadata index files + if (METADATA_PATTERN.matcher(filePath).matches()) { + checkLibraryIndexTestedVersions(json, filePath, failures); + } + + // Print success only if no new failures were added by schema or semantic checks + if (failures.size() == beforeFailures) { + getLogger().lifecycle("✅ " + filePath + ": Valid"); + } } catch (Exception e) { failures.add("💥 " + filePath + ": Parse Error (" + e.getMessage() + ")"); } @@ -157,6 +168,95 @@ private void executeValidation(Set targetFiles) { } } + /** + * Ensures "tested-versions" are mapped to the most appropriate metadata entry. + *

+ * Rule: A version in {@code tested-versions} must be strictly less than the + * next higher {@code metadata-version} available in the file. + *

+ * This prevents "stray" versions from being associated with obsolete metadata + * when a more recent metadata entry exists. + * + * @param json The JSON array of index entries. + * @param filePath Path for error reporting. + * @param failures Accumulator for validation errors. + */ + private static void checkLibraryIndexTestedVersions(JsonNode json, String filePath, List failures) { + if (json == null || !json.isArray()) { + return; + } + + // Collect unique metadata-version strings + java.util.Set metaStrings = new java.util.LinkedHashSet<>(); + for (JsonNode entry : json) { + JsonNode mv = entry.get("metadata-version"); + if (mv != null && mv.isTextual()) { + metaStrings.add(mv.asText()); + } + } + if (metaStrings.isEmpty()) { + return; + } + + // Parse and sort metadata versions + List metasSorted = new ArrayList<>(); + for (String s : metaStrings) { + try { + metasSorted.add(VersionNumber.parse(s)); + } catch (Exception ignore) { + // Ignore unparsable versions; schema should handle invalid shapes + } + } + if (metasSorted.isEmpty()) { + return; + } + metasSorted.sort(java.util.Comparator.naturalOrder()); + + // For each entry, enforce: tested-version < next(metadata-version), if next exists + for (JsonNode entry : json) { + String underMetaStr = entry.path("metadata-version").isTextual() ? entry.get("metadata-version").asText() : null; + if (underMetaStr == null) continue; + + VersionNumber underMeta; + try { + underMeta = VersionNumber.parse(underMetaStr); + } catch (Exception ignore) { + continue; + } + + // Locate index of current metadata-version in the sorted list + int idx = -1; + for (int i = 0; i < metasSorted.size(); i++) { + if (metasSorted.get(i).compareTo(underMeta) == 0) { + idx = i; + break; + } + } + if (idx == -1) continue; + + VersionNumber nextMeta = (idx < metasSorted.size() - 1) ? metasSorted.get(idx + 1) : null; + + JsonNode tvs = entry.get("tested-versions"); + if (tvs != null && tvs.isArray() && nextMeta != null) { + for (JsonNode tvNode : tvs) { + if (tvNode != null && tvNode.isTextual()) { + try { + VersionNumber tv = VersionNumber.parse(tvNode.asText()); + // Must be strictly less than the next metadata-version + if (tv.compareTo(nextMeta) >= 0) { + failures.add("❌ " + filePath + ": tested-versions contains version " + tv + + " not less than next metadata-version " + nextMeta + + " (under metadata-version " + underMetaStr + ")"); + } + } catch (Exception ignore) { + // ignore unparsable tested versions; schema should catch most cases + } + } + } + } + } + } + public static String mapToSchemaPath(String filePath) { if ("metadata/index.json".equals(filePath)) { return "schemas/metadata-root-index-schema-v1.0.0.json"; From bf7c2476e882bc36b420dbd3723f162f76dec667 Mon Sep 17 00:00:00 2001 From: Mihailo Date: Thu, 8 Jan 2026 22:40:44 +0100 Subject: [PATCH 22/29] Scaffold org.apache.santuario:xmlsec:4.0.1 --- .../xmlsec/4.0.1/.gitignore | 4 ++++ .../xmlsec/4.0.1/build.gradle | 16 ++++++++++++++++ .../xmlsec/4.0.1/gradle.properties | 2 ++ .../xmlsec/4.0.1/settings.gradle | 19 +++++++++++++++++++ .../xmlsec/XmlsecTest.java | 18 ++++++++++++++++++ 5 files changed, 59 insertions(+) create mode 100644 tests/src/org.apache.santuario/xmlsec/4.0.1/.gitignore create mode 100644 tests/src/org.apache.santuario/xmlsec/4.0.1/build.gradle create mode 100644 tests/src/org.apache.santuario/xmlsec/4.0.1/gradle.properties create mode 100644 tests/src/org.apache.santuario/xmlsec/4.0.1/settings.gradle create mode 100644 tests/src/org.apache.santuario/xmlsec/4.0.1/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java diff --git a/tests/src/org.apache.santuario/xmlsec/4.0.1/.gitignore b/tests/src/org.apache.santuario/xmlsec/4.0.1/.gitignore new file mode 100644 index 00000000000..c98c7875b0e --- /dev/null +++ b/tests/src/org.apache.santuario/xmlsec/4.0.1/.gitignore @@ -0,0 +1,4 @@ +gradlew.bat +gradlew +gradle/ +build/ diff --git a/tests/src/org.apache.santuario/xmlsec/4.0.1/build.gradle b/tests/src/org.apache.santuario/xmlsec/4.0.1/build.gradle new file mode 100644 index 00000000000..5a0808bbd82 --- /dev/null +++ b/tests/src/org.apache.santuario/xmlsec/4.0.1/build.gradle @@ -0,0 +1,16 @@ +/* + * Copyright and related rights waived via CC0 + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ +plugins { + id "org.graalvm.internal.tck" +} + +String libraryVersion = tck.testedLibraryVersion.get() + +dependencies { + testImplementation "org.apache.santuario:xmlsec:$libraryVersion" + testImplementation 'org.assertj:assertj-core:3.22.0' +} diff --git a/tests/src/org.apache.santuario/xmlsec/4.0.1/gradle.properties b/tests/src/org.apache.santuario/xmlsec/4.0.1/gradle.properties new file mode 100644 index 00000000000..d4a3da13cce --- /dev/null +++ b/tests/src/org.apache.santuario/xmlsec/4.0.1/gradle.properties @@ -0,0 +1,2 @@ +library.version = 4.0.1 +metadata.dir = org.apache.santuario/xmlsec/4.0.1/ diff --git a/tests/src/org.apache.santuario/xmlsec/4.0.1/settings.gradle b/tests/src/org.apache.santuario/xmlsec/4.0.1/settings.gradle new file mode 100644 index 00000000000..2556658e444 --- /dev/null +++ b/tests/src/org.apache.santuario/xmlsec/4.0.1/settings.gradle @@ -0,0 +1,19 @@ +/* + * Copyright and related rights waived via CC0 + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ +pluginManagement { + def tckPath = Objects.requireNonNullElse( + System.getenv("GVM_TCK_TCKDIR"), + "../../../../tck-build-logic" + ) + includeBuild(tckPath) +} + +plugins { + id "org.graalvm.internal.tck-settings" version "1.0.0-SNAPSHOT" +} + +rootProject.name = 'org.apache.santuario.xmlsec_tests' diff --git a/tests/src/org.apache.santuario/xmlsec/4.0.1/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java b/tests/src/org.apache.santuario/xmlsec/4.0.1/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java new file mode 100644 index 00000000000..c342d91481a --- /dev/null +++ b/tests/src/org.apache.santuario/xmlsec/4.0.1/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java @@ -0,0 +1,18 @@ +/* + * Copyright and related rights waived via CC0 + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ +package org_apache_santuario.xmlsec; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.fail; + +class XmlsecTest { + @Test + void test() throws Exception { + fail("TODO: Add test logic here"); + } +} From 7a011a703d0767abca2aab36e1bb12cbfe31c6e3 Mon Sep 17 00:00:00 2001 From: "Mihailo (aider)" Date: Thu, 8 Jan 2026 22:45:44 +0100 Subject: [PATCH 23/29] test: add xmlsec tests for RSA signature, excl c14n, and AES-128 --- .../xmlsec/XmlsecTest.java | 189 +++++++++++++++++- 1 file changed, 186 insertions(+), 3 deletions(-) diff --git a/tests/src/org.apache.santuario/xmlsec/4.0.1/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java b/tests/src/org.apache.santuario/xmlsec/4.0.1/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java index c342d91481a..ab2a6ab31bd 100644 --- a/tests/src/org.apache.santuario/xmlsec/4.0.1/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java +++ b/tests/src/org.apache.santuario/xmlsec/4.0.1/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java @@ -6,13 +6,196 @@ */ package org_apache_santuario.xmlsec; +import org.apache.xml.security.Init; +import org.apache.xml.security.algorithms.MessageDigestAlgorithm; +import org.apache.xml.security.c14n.Canonicalizer; +import org.apache.xml.security.encryption.XMLCipher; +import org.apache.xml.security.signature.XMLSignature; +import org.apache.xml.security.transforms.Transforms; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.w3c.dom.Document; +import org.w3c.dom.Element; -import static org.assertj.core.api.Assertions.fail; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.SecureRandom; + +import static org.assertj.core.api.Assertions.assertThat; class XmlsecTest { + + @BeforeAll + static void initXmlSec() { + // Initialize the XML Security library once for all tests. + Init.init(); + } + @Test - void test() throws Exception { - fail("TODO: Add test logic here"); + void signAndVerifyEnvelopedSignatureRsaSha256() throws Exception { + // Create a simple namespaced document + Document doc = newDocument(); + Element root = doc.createElementNS("urn:test", "t:root"); + root.setAttributeNS("urn:test", "t:attr", "value"); + Element child = doc.createElementNS("urn:test", "t:child"); + child.setTextContent("payload"); + root.appendChild(child); + doc.appendChild(root); + + // Generate RSA key pair for signing + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); + kpg.initialize(2048, new SecureRandom()); + KeyPair kp = kpg.generateKeyPair(); + + // Create XML Signature with Exclusive C14N (omit comments) + XMLSignature signature = new XMLSignature( + doc, + "", + XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA256, + Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS + ); + + // Append the Signature element into the document + root.appendChild(signature.getElement()); + + // Add reference to the whole document with the Enveloped Signature and Exclusive C14N transforms + Transforms transforms = new Transforms(doc); + transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE); + transforms.addTransform(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); + signature.addDocument("", transforms, MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA256); + + // Sign using the private key + signature.sign(kp.getPrivate()); + + // Ensure a Signature element is present + assertThat(root.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature").getLength()).isEqualTo(1); + + // Verify signature using the public key + XMLSignature parsedSignature = new XMLSignature(signature.getElement(), ""); + boolean valid = parsedSignature.checkSignatureValue(kp.getPublic()); + assertThat(valid).isTrue(); + + // Re-serialize and reparse to ensure the signature remains valid across parsing cycles + byte[] xmlBytes = toBytes(doc); + Document reparsed = parseBytes(xmlBytes); + Element sigElem = (Element) reparsed.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature").item(0); + XMLSignature reparsedSig = new XMLSignature(sigElem, ""); + assertThat(reparsedSig.checkSignatureValue(kp.getPublic())).isTrue(); + } + + @Test + void exclusiveCanonicalizationProducesStableOutput() throws Exception { + // Build two semantically equivalent documents with different prefix and attribute ordering + Document d1 = newDocument(); + Element r1 = d1.createElementNS("urn:test", "a:root"); + r1.setAttributeNS("urn:test", "a:z", "3"); + r1.setAttributeNS("urn:test", "a:a", "1"); + Element c1 = d1.createElementNS("urn:test", "a:child"); + c1.setTextContent("text"); + r1.appendChild(c1); + d1.appendChild(r1); + + Document d2 = newDocument(); + Element r2 = d2.createElementNS("urn:test", "b:root"); + r2.setAttributeNS("urn:test", "b:a", "1"); + r2.setAttributeNS("urn:test", "b:z", "3"); + Element c2 = d2.createElementNS("urn:test", "b:child"); + c2.setTextContent("text"); + r2.appendChild(c2); + d2.appendChild(r2); + + Canonicalizer canon = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); + byte[] out1 = canon.canonicalizeSubtree(r1); + byte[] out2 = canon.canonicalizeSubtree(r2); + + // The canonicalized outputs must match + assertThat(new String(out1, StandardCharsets.UTF_8)).isEqualTo(new String(out2, StandardCharsets.UTF_8)); + } + + @Test + void encryptAndDecryptElementWithAes128() throws Exception { + // Build a document with a secret element + Document doc = newDocument(); + Element root = doc.createElementNS("urn:test", "t:root"); + Element secret = doc.createElementNS("urn:test", "t:Secret"); + secret.setTextContent("super-secret"); + Element other = doc.createElementNS("urn:test", "t:Other"); + other.setTextContent("public"); + root.appendChild(secret); + root.appendChild(other); + doc.appendChild(root); + + // Generate an AES-128 key + KeyGenerator kg = KeyGenerator.getInstance("AES"); + kg.init(128, new SecureRandom()); + SecretKey key = kg.generateKey(); + + // Encrypt the Secret element (element encryption) + XMLCipher encCipher = XMLCipher.getInstance(XMLCipher.AES_128); + encCipher.init(XMLCipher.ENCRYPT_MODE, key); + encCipher.doFinal(doc, secret, false); + + // After encryption, there should be no "Secret" element under root + assertThat(root.getElementsByTagNameNS("urn:test", "Secret").getLength()).isEqualTo(0); + // And there should be exactly one EncryptedData element + assertThat(root.getElementsByTagNameNS("http://www.w3.org/2001/04/xmlenc#", "EncryptedData").getLength()).isEqualTo(1); + + // Decrypt back + Element encryptedData = (Element) root.getElementsByTagNameNS("http://www.w3.org/2001/04/xmlenc#", "EncryptedData").item(0); + XMLCipher decCipher = XMLCipher.getInstance(); + decCipher.init(XMLCipher.DECRYPT_MODE, key); + decCipher.doFinal(doc, encryptedData); + + // The Secret element should be restored with original content + Element restored = (Element) root.getElementsByTagNameNS("urn:test", "Secret").item(0); + assertThat(restored).isNotNull(); + assertThat(restored.getTextContent()).isEqualTo("super-secret"); + // The 'Other' element remains unchanged + assertThat(((Element) root.getElementsByTagNameNS("urn:test", "Other").item(0)).getTextContent()).isEqualTo("public"); + } + + // Utility methods + + private static Document newDocument() throws Exception { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + // Harden the parser a bit for safety in tests + try { + dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + } catch (Exception ignored) { + // If not supported by the JAXP implementation, ignore for tests + } + DocumentBuilder db = dbf.newDocumentBuilder(); + return db.newDocument(); + } + + private static byte[] toBytes(Document doc) throws Exception { + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer t = tf.newTransformer(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + t.transform(new DOMSource(doc), new StreamResult(baos)); + return baos.toByteArray(); + } + + private static Document parseBytes(byte[] data) throws Exception { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + try { + dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + } catch (Exception ignored) { + } + DocumentBuilder db = dbf.newDocumentBuilder(); + return db.parse(new ByteArrayInputStream(data)); } } From a28f9f1a9241aa5127ef30cd2f0492eacefe8e71 Mon Sep 17 00:00:00 2001 From: "Mihailo (aider)" Date: Thu, 8 Jan 2026 22:47:06 +0100 Subject: [PATCH 24/29] test: fix namespace and canonicalization API usage in XmlsecTest --- .../org_apache_santuario/xmlsec/XmlsecTest.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/src/org.apache.santuario/xmlsec/4.0.1/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java b/tests/src/org.apache.santuario/xmlsec/4.0.1/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java index ab2a6ab31bd..4df4e079fec 100644 --- a/tests/src/org.apache.santuario/xmlsec/4.0.1/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java +++ b/tests/src/org.apache.santuario/xmlsec/4.0.1/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java @@ -12,6 +12,7 @@ import org.apache.xml.security.encryption.XMLCipher; import org.apache.xml.security.signature.XMLSignature; import org.apache.xml.security.transforms.Transforms; +import org.apache.xml.security.utils.Constants; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.w3c.dom.Document; @@ -79,7 +80,7 @@ void signAndVerifyEnvelopedSignatureRsaSha256() throws Exception { signature.sign(kp.getPrivate()); // Ensure a Signature element is present - assertThat(root.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature").getLength()).isEqualTo(1); + assertThat(root.getElementsByTagNameNS(Constants.SignatureSpecNS, "Signature").getLength()).isEqualTo(1); // Verify signature using the public key XMLSignature parsedSignature = new XMLSignature(signature.getElement(), ""); @@ -89,7 +90,7 @@ void signAndVerifyEnvelopedSignatureRsaSha256() throws Exception { // Re-serialize and reparse to ensure the signature remains valid across parsing cycles byte[] xmlBytes = toBytes(doc); Document reparsed = parseBytes(xmlBytes); - Element sigElem = (Element) reparsed.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature").item(0); + Element sigElem = (Element) reparsed.getElementsByTagNameNS(Constants.SignatureSpecNS, "Signature").item(0); XMLSignature reparsedSig = new XMLSignature(sigElem, ""); assertThat(reparsedSig.checkSignatureValue(kp.getPublic())).isTrue(); } @@ -116,8 +117,14 @@ void exclusiveCanonicalizationProducesStableOutput() throws Exception { d2.appendChild(r2); Canonicalizer canon = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); - byte[] out1 = canon.canonicalizeSubtree(r1); - byte[] out2 = canon.canonicalizeSubtree(r2); + + ByteArrayOutputStream bos1 = new ByteArrayOutputStream(); + canon.canonicalizeSubtree(r1, bos1); + byte[] out1 = bos1.toByteArray(); + + ByteArrayOutputStream bos2 = new ByteArrayOutputStream(); + canon.canonicalizeSubtree(r2, bos2); + byte[] out2 = bos2.toByteArray(); // The canonicalized outputs must match assertThat(new String(out1, StandardCharsets.UTF_8)).isEqualTo(new String(out2, StandardCharsets.UTF_8)); From 49ad915493e3942538c5573bbf35c09e2af9ef27 Mon Sep 17 00:00:00 2001 From: "Mihailo (aider)" Date: Thu, 8 Jan 2026 22:49:11 +0100 Subject: [PATCH 25/29] test: declare xmlns:t and align prefixes to fix xmlsec tests --- .../xmlsec/XmlsecTest.java | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/tests/src/org.apache.santuario/xmlsec/4.0.1/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java b/tests/src/org.apache.santuario/xmlsec/4.0.1/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java index 4df4e079fec..fe6511d7cb9 100644 --- a/tests/src/org.apache.santuario/xmlsec/4.0.1/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java +++ b/tests/src/org.apache.santuario/xmlsec/4.0.1/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java @@ -20,6 +20,7 @@ import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; +import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.Transformer; @@ -48,6 +49,8 @@ void signAndVerifyEnvelopedSignatureRsaSha256() throws Exception { // Create a simple namespaced document Document doc = newDocument(); Element root = doc.createElementNS("urn:test", "t:root"); + // Explicitly declare the namespace prefix used in this subtree + root.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "xmlns:t", "urn:test"); root.setAttributeNS("urn:test", "t:attr", "value"); Element child = doc.createElementNS("urn:test", "t:child"); child.setTextContent("payload"); @@ -97,21 +100,23 @@ void signAndVerifyEnvelopedSignatureRsaSha256() throws Exception { @Test void exclusiveCanonicalizationProducesStableOutput() throws Exception { - // Build two semantically equivalent documents with different prefix and attribute ordering + // Build two semantically equivalent documents with same prefix and different attribute ordering Document d1 = newDocument(); - Element r1 = d1.createElementNS("urn:test", "a:root"); - r1.setAttributeNS("urn:test", "a:z", "3"); - r1.setAttributeNS("urn:test", "a:a", "1"); - Element c1 = d1.createElementNS("urn:test", "a:child"); + Element r1 = d1.createElementNS("urn:test", "t:root"); + r1.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "xmlns:t", "urn:test"); + r1.setAttributeNS("urn:test", "t:z", "3"); + r1.setAttributeNS("urn:test", "t:a", "1"); + Element c1 = d1.createElementNS("urn:test", "t:child"); c1.setTextContent("text"); r1.appendChild(c1); d1.appendChild(r1); Document d2 = newDocument(); - Element r2 = d2.createElementNS("urn:test", "b:root"); - r2.setAttributeNS("urn:test", "b:a", "1"); - r2.setAttributeNS("urn:test", "b:z", "3"); - Element c2 = d2.createElementNS("urn:test", "b:child"); + Element r2 = d2.createElementNS("urn:test", "t:root"); + r2.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "xmlns:t", "urn:test"); + r2.setAttributeNS("urn:test", "t:a", "1"); + r2.setAttributeNS("urn:test", "t:z", "3"); + Element c2 = d2.createElementNS("urn:test", "t:child"); c2.setTextContent("text"); r2.appendChild(c2); d2.appendChild(r2); @@ -126,7 +131,7 @@ void exclusiveCanonicalizationProducesStableOutput() throws Exception { canon.canonicalizeSubtree(r2, bos2); byte[] out2 = bos2.toByteArray(); - // The canonicalized outputs must match + // The canonicalized outputs must match despite different attribute insertion order assertThat(new String(out1, StandardCharsets.UTF_8)).isEqualTo(new String(out2, StandardCharsets.UTF_8)); } @@ -135,6 +140,8 @@ void encryptAndDecryptElementWithAes128() throws Exception { // Build a document with a secret element Document doc = newDocument(); Element root = doc.createElementNS("urn:test", "t:root"); + // Ensure the 't' prefix is declared so subtree serialization during encryption/decryption is valid + root.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "xmlns:t", "urn:test"); Element secret = doc.createElementNS("urn:test", "t:Secret"); secret.setTextContent("super-secret"); Element other = doc.createElementNS("urn:test", "t:Other"); From bca932d2f11f9f08c98aa6b67e5812c3b4f12184 Mon Sep 17 00:00:00 2001 From: "Mihailo (aider)" Date: Thu, 8 Jan 2026 22:51:20 +0100 Subject: [PATCH 26/29] test: add HMAC-SHA256 element-by-ID signature tamper detection --- .../xmlsec/XmlsecTest.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/src/org.apache.santuario/xmlsec/4.0.1/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java b/tests/src/org.apache.santuario/xmlsec/4.0.1/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java index fe6511d7cb9..be760356285 100644 --- a/tests/src/org.apache.santuario/xmlsec/4.0.1/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java +++ b/tests/src/org.apache.santuario/xmlsec/4.0.1/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java @@ -179,6 +179,54 @@ void encryptAndDecryptElementWithAes128() throws Exception { assertThat(((Element) root.getElementsByTagNameNS("urn:test", "Other").item(0)).getTextContent()).isEqualTo("public"); } + @Test + void hmacSha256SignatureOnElementById_isValidAndDetectsTampering() throws Exception { + // Build a document with a target element identified by ID + Document doc = newDocument(); + Element root = doc.createElementNS("urn:test", "t:root"); + root.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "xmlns:t", "urn:test"); + + Element data = doc.createElementNS("urn:test", "t:Data"); + data.setTextContent("important"); + data.setAttribute("Id", "elem-1"); + // Mark the attribute as type ID so same-document URI can resolve + data.setIdAttribute("Id", true); + + Element other = doc.createElementNS("urn:test", "t:Other"); + other.setTextContent("not-signed"); + + root.appendChild(data); + root.appendChild(other); + doc.appendChild(root); + + // Create an HMAC-SHA256 XML Signature (detached same-document reference) + XMLSignature signature = new XMLSignature( + doc, + "", + XMLSignature.ALGO_ID_MAC_HMAC_SHA256, + Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS + ); + root.appendChild(signature.getElement()); + + // Reference only the Data element via its ID and canonicalize it + Transforms transforms = new Transforms(doc); + transforms.addTransform(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); + signature.addDocument("#elem-1", transforms, MessageDigestAlgorithm.ALGO_ID_DIGEST_SHA256); + + // Generate an HMAC key and sign + SecretKey hmacKey = KeyGenerator.getInstance("HmacSHA256").generateKey(); + signature.sign(hmacKey); + + // Verify signature is valid with the same HMAC key + XMLSignature parsedSignature = new XMLSignature(signature.getElement(), ""); + assertThat(parsedSignature.checkSignatureValue(hmacKey)).isTrue(); + + // Tamper with the signed element content; verification must fail now + data.setTextContent("tampered"); + XMLSignature tamperedSig = new XMLSignature(signature.getElement(), ""); + assertThat(tamperedSig.checkSignatureValue(hmacKey)).isFalse(); + } + // Utility methods private static Document newDocument() throws Exception { From 3c92b15625df2a8f666478356964a7a2e3174278 Mon Sep 17 00:00:00 2001 From: "Mihailo (aider)" Date: Thu, 8 Jan 2026 22:53:49 +0100 Subject: [PATCH 27/29] test: add RSA-OAEP key wrapping test for AES element encryption --- .../xmlsec/XmlsecTest.java | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/tests/src/org.apache.santuario/xmlsec/4.0.1/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java b/tests/src/org.apache.santuario/xmlsec/4.0.1/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java index be760356285..a793ea70333 100644 --- a/tests/src/org.apache.santuario/xmlsec/4.0.1/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java +++ b/tests/src/org.apache.santuario/xmlsec/4.0.1/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java @@ -9,7 +9,9 @@ import org.apache.xml.security.Init; import org.apache.xml.security.algorithms.MessageDigestAlgorithm; import org.apache.xml.security.c14n.Canonicalizer; +import org.apache.xml.security.encryption.EncryptedKey; import org.apache.xml.security.encryption.XMLCipher; +import org.apache.xml.security.keys.KeyInfo; import org.apache.xml.security.signature.XMLSignature; import org.apache.xml.security.transforms.Transforms; import org.apache.xml.security.utils.Constants; @@ -227,6 +229,65 @@ void hmacSha256SignatureOnElementById_isValidAndDetectsTampering() throws Except assertThat(tamperedSig.checkSignatureValue(hmacKey)).isFalse(); } + @Test + void encryptElementWithAes128AndWrapKeyWithRsaOaep() throws Exception { + // Build a document that contains data to encrypt + Document doc = newDocument(); + Element root = doc.createElementNS("urn:test", "t:root"); + root.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "xmlns:t", "urn:test"); + Element sensitive = doc.createElementNS("urn:test", "t:Sensitive"); + sensitive.setTextContent("classified"); + Element publicElem = doc.createElementNS("urn:test", "t:Public"); + publicElem.setTextContent("public-info"); + root.appendChild(sensitive); + root.appendChild(publicElem); + doc.appendChild(root); + + // Generate RSA key pair for key transport (wrapping AES key) + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); + kpg.initialize(2048, new SecureRandom()); + KeyPair rsaKp = kpg.generateKeyPair(); + + // Generate a random AES-128 content encryption key + SecretKey aesKey = KeyGenerator.getInstance("AES").generateKey(); + + // Prepare data cipher for element encryption + XMLCipher dataCipher = XMLCipher.getInstance(XMLCipher.AES_128); + dataCipher.init(XMLCipher.ENCRYPT_MODE, aesKey); + + // Wrap AES key using RSA-OAEP and place it into EncryptedData/KeyInfo + XMLCipher keyCipher = XMLCipher.getInstance(XMLCipher.RSA_OAEP); + keyCipher.init(XMLCipher.WRAP_MODE, rsaKp.getPublic()); + EncryptedKey encryptedKey = keyCipher.encryptKey(doc, aesKey); + + KeyInfo ki = new KeyInfo(doc); + ki.add(encryptedKey); + dataCipher.getEncryptedData().setKeyInfo(ki); + + // Encrypt the element (element encryption, not content-only) + dataCipher.doFinal(doc, sensitive, false); + + // Assertions after encryption + assertThat(root.getElementsByTagNameNS("urn:test", "Sensitive").getLength()).isEqualTo(0); + Element encryptedData = (Element) root.getElementsByTagNameNS("http://www.w3.org/2001/04/xmlenc#", "EncryptedData").item(0); + assertThat(encryptedData).isNotNull(); + // Ensure an EncryptedKey is embedded via KeyInfo + assertThat(encryptedData.getElementsByTagNameNS("http://www.w3.org/2001/04/xmlenc#", "EncryptedKey").getLength()).isEqualTo(1); + + // Decrypt using the private RSA key to unwrap the AES key + XMLCipher decryptCipher = XMLCipher.getInstance(); + decryptCipher.init(XMLCipher.DECRYPT_MODE, null); + decryptCipher.setKEK(rsaKp.getPrivate()); + decryptCipher.doFinal(doc, encryptedData); + + // After decryption, original element and content must be restored + Element restored = (Element) root.getElementsByTagNameNS("urn:test", "Sensitive").item(0); + assertThat(restored).isNotNull(); + assertThat(restored.getTextContent()).isEqualTo("classified"); + // Unrelated element remains unchanged + assertThat(((Element) root.getElementsByTagNameNS("urn:test", "Public").item(0)).getTextContent()).isEqualTo("public-info"); + } + // Utility methods private static Document newDocument() throws Exception { From 1005bd11cf66c1fa676de721cbc8cb33af7bd63e Mon Sep 17 00:00:00 2001 From: Mihailo Date: Fri, 9 Jan 2026 12:12:15 +0100 Subject: [PATCH 28/29] Add support for org.apache.santuario:xmlsec:4.0.1 --- metadata/index.json | 400 +++++++++--------- .../xmlsec/4.0.1/jni-config.json | 2 + .../4.0.1/predefined-classes-config.json | 7 + .../xmlsec/4.0.1/proxy-config.json | 2 + .../xmlsec/4.0.1/reflect-config.json | 111 +++++ .../xmlsec/4.0.1/resource-config.json | 23 + .../xmlsec/4.0.1/serialization-config.json | 8 + .../org.apache.santuario/xmlsec/index.json | 10 + .../xmlsec/4.0.1/build.gradle | 11 + .../xmlsec/4.0.1/user-code-filter.json | 10 + 10 files changed, 386 insertions(+), 198 deletions(-) create mode 100644 metadata/org.apache.santuario/xmlsec/4.0.1/jni-config.json create mode 100644 metadata/org.apache.santuario/xmlsec/4.0.1/predefined-classes-config.json create mode 100644 metadata/org.apache.santuario/xmlsec/4.0.1/proxy-config.json create mode 100644 metadata/org.apache.santuario/xmlsec/4.0.1/reflect-config.json create mode 100644 metadata/org.apache.santuario/xmlsec/4.0.1/resource-config.json create mode 100644 metadata/org.apache.santuario/xmlsec/4.0.1/serialization-config.json create mode 100644 metadata/org.apache.santuario/xmlsec/index.json create mode 100644 tests/src/org.apache.santuario/xmlsec/4.0.1/user-code-filter.json diff --git a/metadata/index.json b/metadata/index.json index fc2f310a9a7..97ae7a58ab3 100644 --- a/metadata/index.json +++ b/metadata/index.json @@ -1,396 +1,400 @@ [ { - "allowed-packages" : [ "ch.qos.logback" ], "directory" : "ch.qos.logback.contrib/logback-jackson", - "module" : "ch.qos.logback.contrib:logback-jackson" + "module" : "ch.qos.logback.contrib:logback-jackson", + "allowed-packages" : [ "ch.qos.logback" ] }, { - "allowed-packages" : [ "ch.qos.logback" ], "directory" : "ch.qos.logback.contrib/logback-json-classic", - "module" : "ch.qos.logback.contrib:logback-json-classic" + "module" : "ch.qos.logback.contrib:logback-json-classic", + "allowed-packages" : [ "ch.qos.logback" ] }, { - "allowed-packages" : [ "ch.qos.logback" ], "directory" : "ch.qos.logback/logback-classic", - "module" : "ch.qos.logback:logback-classic" + "module" : "ch.qos.logback:logback-classic", + "allowed-packages" : [ "ch.qos.logback" ] }, { - "allowed-packages" : [ "com.ecwid" ], "directory" : "com.ecwid.consul/consul-api", - "module" : "com.ecwid.consul:consul-api" + "module" : "com.ecwid.consul:consul-api", + "allowed-packages" : [ "com.ecwid" ] }, { - "allowed-packages" : [ "com.fasterxml.jackson", "java.sql", "org.w3c.dom", "javax.xml" ], "directory" : "com.fasterxml.jackson.core/jackson-databind", - "module" : "com.fasterxml.jackson.core:jackson-databind" + "module" : "com.fasterxml.jackson.core:jackson-databind", + "allowed-packages" : [ "com.fasterxml.jackson", "java.sql", "org.w3c.dom", "javax.xml" ] }, { - "allowed-packages" : [ "com.github.benmanes.caffeine" ], "directory" : "com.github.ben-manes.caffeine/caffeine", - "module" : "com.github.ben-manes.caffeine:caffeine" + "module" : "com.github.ben-manes.caffeine:caffeine", + "allowed-packages" : [ "com.github.benmanes.caffeine" ] }, { - "allowed-packages" : [ "com.github.luben" ], "directory" : "com.github.luben/zstd-jni", - "module" : "com.github.luben:zstd-jni" + "module" : "com.github.luben:zstd-jni", + "allowed-packages" : [ "com.github.luben" ] }, { - "allowed-packages" : [ "com.google.protobuf" ], "directory" : "com.google.protobuf/protobuf-java-util", - "module" : "com.google.protobuf:protobuf-java-util" + "module" : "com.google.protobuf:protobuf-java-util", + "allowed-packages" : [ "com.google.protobuf" ] }, { - "allowed-packages" : [ "graphql" ], "directory" : "com.graphql-java/graphql-java", - "module" : "com.graphql-java:graphql-java" + "module" : "com.graphql-java:graphql-java", + "allowed-packages" : [ "graphql" ] }, { - "allowed-packages" : [ "graphql" ], "directory" : "com.graphql-java/graphql-java-extended-validation", "module" : "com.graphql-java:graphql-java-extended-validation", - "requires" : [ "com.graphql-java:graphql-java", "org.hibernate.validator:hibernate-validator" ] + "requires" : [ "com.graphql-java:graphql-java", "org.hibernate.validator:hibernate-validator" ], + "allowed-packages" : [ "graphql" ] }, { - "allowed-packages" : [ "org.h2" ], "directory" : "com.h2database/h2", - "module" : "com.h2database:h2" + "module" : "com.h2database:h2", + "allowed-packages" : [ "org.h2" ] }, { - "allowed-packages" : [ "com.hazelcast" ], "directory" : "com.hazelcast/hazelcast", - "module" : "com.hazelcast:hazelcast" + "module" : "com.hazelcast:hazelcast", + "allowed-packages" : [ "com.hazelcast" ] }, { - "allowed-packages" : [ "com.itextpdf" ], "directory" : "com.itextpdf/forms", - "module" : "com.itextpdf:forms" + "module" : "com.itextpdf:forms", + "allowed-packages" : [ "com.itextpdf" ] }, { - "allowed-packages" : [ "com.itextpdf" ], "directory" : "com.itextpdf/io", - "module" : "com.itextpdf:io" + "module" : "com.itextpdf:io", + "allowed-packages" : [ "com.itextpdf" ] }, { - "allowed-packages" : [ "com.itextpdf" ], "directory" : "com.itextpdf/kernel", - "module" : "com.itextpdf:kernel" + "module" : "com.itextpdf:kernel", + "allowed-packages" : [ "com.itextpdf" ] }, { - "allowed-packages" : [ "com.itextpdf" ], "directory" : "com.itextpdf/layout", - "module" : "com.itextpdf:layout" + "module" : "com.itextpdf:layout", + "allowed-packages" : [ "com.itextpdf" ] }, { - "allowed-packages" : [ "com.itextpdf" ], "directory" : "com.itextpdf/svg", - "module" : "com.itextpdf:svg" + "module" : "com.itextpdf:svg", + "allowed-packages" : [ "com.itextpdf" ] }, { - "allowed-packages" : [ "com.microsoft.sqlserver" ], "directory" : "com.microsoft.sqlserver/mssql-jdbc", - "module" : "com.microsoft.sqlserver:mssql-jdbc" + "module" : "com.microsoft.sqlserver:mssql-jdbc", + "allowed-packages" : [ "com.microsoft.sqlserver" ] }, { - "allowed-packages" : [ "com.mysql" ], "directory" : "com.mysql/mysql-connector-j", - "module" : "com.mysql:mysql-connector-j" + "module" : "com.mysql:mysql-connector-j", + "allowed-packages" : [ "com.mysql" ] }, { - "allowed-packages" : [ "com.sun.mail", "jakarta" ], "directory" : "com.sun.mail/jakarta.mail", - "module" : "com.sun.mail:jakarta.mail" + "module" : "com.sun.mail:jakarta.mail", + "allowed-packages" : [ "com.sun.mail", "jakarta" ] }, { - "allowed-packages" : [ "com.zaxxer", "jdk.internal" ], "directory" : "com.zaxxer/HikariCP", - "module" : "com.zaxxer:HikariCP" + "module" : "com.zaxxer:HikariCP", + "allowed-packages" : [ "com.zaxxer", "jdk.internal" ] }, { - "allowed-packages" : [ "org.apache.commons.logging" ], "directory" : "commons-logging/commons-logging", - "module" : "commons-logging:commons-logging" + "module" : "commons-logging:commons-logging", + "allowed-packages" : [ "org.apache.commons.logging" ] }, { - "allowed-packages" : [ "io.grpc" ], "directory" : "io.grpc/grpc-core", - "module" : "io.grpc:grpc-core" + "module" : "io.grpc:grpc-core", + "allowed-packages" : [ "io.grpc" ] }, { - "allowed-packages" : [ "io.grpc" ], "directory" : "io.grpc/grpc-netty", - "module" : "io.grpc:grpc-netty" + "module" : "io.grpc:grpc-netty", + "allowed-packages" : [ "io.grpc" ] }, { - "allowed-packages" : [ "io.jsonwebtoken" ], "directory" : "io.jsonwebtoken/jjwt-gson", - "module" : "io.jsonwebtoken:jjwt-gson" + "module" : "io.jsonwebtoken:jjwt-gson", + "allowed-packages" : [ "io.jsonwebtoken" ] }, { - "allowed-packages" : [ "io.jsonwebtoken" ], "directory" : "io.jsonwebtoken/jjwt-jackson", - "module" : "io.jsonwebtoken:jjwt-jackson" + "module" : "io.jsonwebtoken:jjwt-jackson", + "allowed-packages" : [ "io.jsonwebtoken" ] }, { - "allowed-packages" : [ "io.jsonwebtoken" ], "directory" : "io.jsonwebtoken/jjwt-orgjson", - "module" : "io.jsonwebtoken:jjwt-orgjson" + "module" : "io.jsonwebtoken:jjwt-orgjson", + "allowed-packages" : [ "io.jsonwebtoken" ] }, { - "allowed-packages" : [ "io.nats" ], "directory" : "io.nats/jnats", - "module" : "io.nats:jnats" + "module" : "io.nats:jnats", + "allowed-packages" : [ "io.nats" ] }, { - "allowed-packages" : [ "io.netty" ], "directory" : "io.netty/netty-common", - "module" : "io.netty:netty-common" + "module" : "io.netty:netty-common", + "allowed-packages" : [ "io.netty" ] }, { - "allowed-packages" : [ "io.netty" ], "directory" : "io.netty/netty-transport", - "module" : "io.netty:netty-transport" + "module" : "io.netty:netty-transport", + "allowed-packages" : [ "io.netty" ] }, { - "allowed-packages" : [ "io.opentelemetry" ], "directory" : "io.opentelemetry/opentelemetry-exporter-jaeger", - "module" : "io.opentelemetry:opentelemetry-exporter-jaeger" + "module" : "io.opentelemetry:opentelemetry-exporter-jaeger", + "allowed-packages" : [ "io.opentelemetry" ] }, { - "allowed-packages" : [ "io.opentelemetry" ], "directory" : "io.opentelemetry/opentelemetry-exporter-logging", - "module" : "io.opentelemetry:opentelemetry-exporter-logging" + "module" : "io.opentelemetry:opentelemetry-exporter-logging", + "allowed-packages" : [ "io.opentelemetry" ] }, { - "allowed-packages" : [ "io.opentelemetry" ], "directory" : "io.opentelemetry/opentelemetry-exporter-otlp", - "module" : "io.opentelemetry:opentelemetry-exporter-otlp" + "module" : "io.opentelemetry:opentelemetry-exporter-otlp", + "allowed-packages" : [ "io.opentelemetry" ] }, { - "allowed-packages" : [ "io.opentelemetry" ], "directory" : "io.opentelemetry/opentelemetry-exporter-zipkin", - "module" : "io.opentelemetry:opentelemetry-exporter-zipkin" + "module" : "io.opentelemetry:opentelemetry-exporter-zipkin", + "allowed-packages" : [ "io.opentelemetry" ] }, { - "allowed-packages" : [ "io.opentelemetry" ], "directory" : "io.opentelemetry/opentelemetry-sdk-metrics", - "module" : "io.opentelemetry:opentelemetry-sdk-metrics" + "module" : "io.opentelemetry:opentelemetry-sdk-metrics", + "allowed-packages" : [ "io.opentelemetry" ] }, { - "allowed-packages" : [ "io.opentelemetry" ], "directory" : "io.opentelemetry/opentelemetry-sdk-trace", - "module" : "io.opentelemetry:opentelemetry-sdk-trace" + "module" : "io.opentelemetry:opentelemetry-sdk-trace", + "allowed-packages" : [ "io.opentelemetry" ] }, { - "allowed-packages" : [ "io.undertow" ], "directory" : "io.undertow/undertow-core", - "module" : "io.undertow:undertow-core" + "module" : "io.undertow:undertow-core", + "allowed-packages" : [ "io.undertow" ] }, { - "allowed-packages" : [ "jakarta.servlet" ], "directory" : "jakarta.servlet/jakarta.servlet-api", - "module" : "jakarta.servlet:jakarta.servlet-api" + "module" : "jakarta.servlet:jakarta.servlet-api", + "allowed-packages" : [ "jakarta.servlet" ] }, { - "allowed-packages" : [ "javax.cache" ], "directory" : "javax.cache/cache-api", - "module" : "javax.cache:cache-api" + "module" : "javax.cache:cache-api", + "allowed-packages" : [ "javax.cache" ] }, { - "allowed-packages" : [ "org.apache.log4j" ], "directory" : "log4j/log4j", - "module" : "log4j:log4j" + "module" : "log4j:log4j", + "allowed-packages" : [ "org.apache.log4j" ] }, { - "allowed-packages" : [ "mysql" ], "directory" : "mysql/mysql-connector-java", - "module" : "mysql:mysql-connector-java" + "module" : "mysql:mysql-connector-java", + "allowed-packages" : [ "mysql" ] }, { - "allowed-packages" : [ "com.sun.jna" ], "directory" : "net.java.dev.jna/jna", - "module" : "net.java.dev.jna:jna" + "module" : "net.java.dev.jna:jna", + "allowed-packages" : [ "com.sun.jna" ] }, { - "allowed-packages" : [ "org.apache.activemq" ], "directory" : "org.apache.activemq/activemq-broker", - "module" : "org.apache.activemq:activemq-broker" + "module" : "org.apache.activemq:activemq-broker", + "allowed-packages" : [ "org.apache.activemq" ] }, { - "allowed-packages" : [ "org.apache.activemq" ], "directory" : "org.apache.activemq/activemq-client", - "module" : "org.apache.activemq:activemq-client" + "module" : "org.apache.activemq:activemq-client", + "allowed-packages" : [ "org.apache.activemq" ] }, { - "allowed-packages" : [ "org.apache.activemq" ], "directory" : "org.apache.activemq/artemis-jms-client", - "module" : "org.apache.activemq:artemis-jms-client" + "module" : "org.apache.activemq:artemis-jms-client", + "allowed-packages" : [ "org.apache.activemq" ] }, { - "allowed-packages" : [ "org.apache.commons" ], "directory" : "org.apache.commons/commons-compress", - "module" : "org.apache.commons:commons-compress" + "module" : "org.apache.commons:commons-compress", + "allowed-packages" : [ "org.apache.commons" ] }, { - "allowed-packages" : [ "org.apache.commons" ], "directory" : "org.apache.commons/commons-dbcp2", - "module" : "org.apache.commons:commons-dbcp2" + "module" : "org.apache.commons:commons-dbcp2", + "allowed-packages" : [ "org.apache.commons" ] }, { - "allowed-packages" : [ "org.apache.commons" ], "directory" : "org.apache.commons/commons-pool2", - "module" : "org.apache.commons:commons-pool2" + "module" : "org.apache.commons:commons-pool2", + "allowed-packages" : [ "org.apache.commons" ] }, { - "allowed-packages" : [ "org.apache.http" ], "directory" : "org.apache.httpcomponents/httpclient", - "module" : "org.apache.httpcomponents:httpclient" + "module" : "org.apache.httpcomponents:httpclient", + "allowed-packages" : [ "org.apache.http" ] }, { - "allowed-packages" : [ "org.apache.kafka" ], "directory" : "org.apache.kafka/kafka-clients", - "module" : "org.apache.kafka:kafka-clients" + "module" : "org.apache.kafka:kafka-clients", + "allowed-packages" : [ "org.apache.kafka" ] }, { - "allowed-packages" : [ "org.apache.kafka", "org.rocksdb" ], "directory" : "org.apache.kafka/kafka-streams", - "module" : "org.apache.kafka:kafka-streams" + "module" : "org.apache.kafka:kafka-streams", + "allowed-packages" : [ "org.apache.kafka", "org.rocksdb" ] +}, { + "directory" : "org.apache.santuario/xmlsec", + "module" : "org.apache.santuario:xmlsec", + "allowed-packages" : [ "org.apache.santuario" ] }, { - "allowed-packages" : [ "org.apache.tomcat", "org.apache.catalina" ], "directory" : "org.apache.tomcat.embed/tomcat-embed-core", - "module" : "org.apache.tomcat.embed:tomcat-embed-core" + "module" : "org.apache.tomcat.embed:tomcat-embed-core", + "allowed-packages" : [ "org.apache.tomcat", "org.apache.catalina" ] }, { - "allowed-packages" : [ "org.apache.tomcat.jdbc" ], "directory" : "org.apache.tomcat/tomcat-jdbc", - "module" : "org.apache.tomcat:tomcat-jdbc" + "module" : "org.apache.tomcat:tomcat-jdbc", + "allowed-packages" : [ "org.apache.tomcat.jdbc" ] }, { - "allowed-packages" : [ "org.bouncycastle" ], "directory" : "org.bouncycastle/bcpkix-jdk15on", - "module" : "org.bouncycastle:bcpkix-jdk15on" + "module" : "org.bouncycastle:bcpkix-jdk15on", + "allowed-packages" : [ "org.bouncycastle" ] }, { - "allowed-packages" : [ "org.bouncycastle" ], "directory" : "org.bouncycastle/bcpkix-jdk15to18", - "module" : "org.bouncycastle:bcpkix-jdk15to18" + "module" : "org.bouncycastle:bcpkix-jdk15to18", + "allowed-packages" : [ "org.bouncycastle" ] }, { - "allowed-packages" : [ "org.bouncycastle" ], "directory" : "org.bouncycastle/bcpkix-jdk18on", - "module" : "org.bouncycastle:bcpkix-jdk18on" -}, { - "allowed-packages" : [ "jakarta" ], + "module" : "org.bouncycastle:bcpkix-jdk18on", + "allowed-packages" : [ "org.bouncycastle" ] +}, { "directory" : "org.eclipse.angus/jakarta.mail", - "module" : "org.eclipse.angus:jakarta.mail" + "module" : "org.eclipse.angus:jakarta.mail", + "allowed-packages" : [ "jakarta" ] }, { - "allowed-packages" : [ "org.eclipse.jetty" ], "directory" : "org.eclipse.jetty/jetty-client", - "module" : "org.eclipse.jetty:jetty-client" + "module" : "org.eclipse.jetty:jetty-client", + "allowed-packages" : [ "org.eclipse.jetty" ] }, { - "allowed-packages" : [ "org.eclipse" ], "directory" : "org.eclipse.jetty/jetty-server", - "module" : "org.eclipse.jetty:jetty-server" + "module" : "org.eclipse.jetty:jetty-server", + "allowed-packages" : [ "org.eclipse" ] }, { - "allowed-packages" : [ "org.eclipse.jetty" ], "directory" : "org.eclipse.jetty/jetty-util", - "module" : "org.eclipse.jetty:jetty-util" + "module" : "org.eclipse.jetty:jetty-util", + "allowed-packages" : [ "org.eclipse.jetty" ] }, { - "allowed-packages" : [ "org.eclipse.jgit" ], "directory" : "org.eclipse.jgit/org.eclipse.jgit", - "module" : "org.eclipse.jgit:org.eclipse.jgit" + "module" : "org.eclipse.jgit:org.eclipse.jgit", + "allowed-packages" : [ "org.eclipse.jgit" ] }, { - "allowed-packages" : [ "org.eclipse.paho.client.mqttv3" ], "directory" : "org.eclipse.paho/org.eclipse.paho.client.mqttv3", - "module" : "org.eclipse.paho:org.eclipse.paho.client.mqttv3" + "module" : "org.eclipse.paho:org.eclipse.paho.client.mqttv3", + "allowed-packages" : [ "org.eclipse.paho.client.mqttv3" ] }, { - "allowed-packages" : [ "org.eclipse.paho.mqttv5" ], "directory" : "org.eclipse.paho/org.eclipse.paho.mqttv5.client", - "module" : "org.eclipse.paho:org.eclipse.paho.mqttv5.client" + "module" : "org.eclipse.paho:org.eclipse.paho.mqttv5.client", + "allowed-packages" : [ "org.eclipse.paho.mqttv5" ] }, { - "allowed-packages" : [ "org.ehcache" ], "directory" : "org.ehcache/ehcache", - "module" : "org.ehcache:ehcache" + "module" : "org.ehcache:ehcache", + "allowed-packages" : [ "org.ehcache" ] }, { - "allowed-packages" : [ "org.example" ], "directory" : "org.example/library", - "module" : "org.example:library" + "module" : "org.example:library", + "allowed-packages" : [ "org.example" ] }, { - "allowed-packages" : [ "org.flywaydb", "org.apache", "org.slf4j" ], "directory" : "org.flywaydb/flyway-core", - "module" : "org.flywaydb:flyway-core" + "module" : "org.flywaydb:flyway-core", + "allowed-packages" : [ "org.flywaydb", "org.apache", "org.slf4j" ] }, { - "allowed-packages" : [ "org.flywaydb" ], "directory" : "org.flywaydb/flyway-database-postgresql", - "module" : "org.flywaydb:flyway-database-postgresql" + "module" : "org.flywaydb:flyway-database-postgresql", + "allowed-packages" : [ "org.flywaydb" ] }, { - "allowed-packages" : [ "org.flywaydb" ], "directory" : "org.flywaydb/flyway-sqlserver", - "module" : "org.flywaydb:flyway-sqlserver" + "module" : "org.flywaydb:flyway-sqlserver", + "allowed-packages" : [ "org.flywaydb" ] }, { - "allowed-packages" : [ "org.freemarker" ], "directory" : "org.freemarker/freemarker", - "module" : "org.freemarker:freemarker" + "module" : "org.freemarker:freemarker", + "allowed-packages" : [ "org.freemarker" ] }, { - "allowed-packages" : [ "org.glassfish.jaxb", "jakarta.xml" ], "directory" : "org.glassfish.jaxb/jaxb-runtime", - "module" : "org.glassfish.jaxb:jaxb-runtime" + "module" : "org.glassfish.jaxb:jaxb-runtime", + "allowed-packages" : [ "org.glassfish.jaxb", "jakarta.xml" ] }, { - "allowed-packages" : [ "org.HdrHistogram" ], "directory" : "org.hdrhistogram/HdrHistogram", - "module" : "org.hdrhistogram:HdrHistogram" + "module" : "org.hdrhistogram:HdrHistogram", + "allowed-packages" : [ "org.HdrHistogram" ] }, { - "allowed-packages" : [ "org.hibernate", "jakarta", "org.h2.Driver", "org.postgresql.Driver", "com.mysql.cj.jdbc.Driver", "org.hsqldb.jdbc.JDBCDriver", "com.microsoft.sqlserver.jdbc.SQLServerDriver", "oracle.jdbc.driver.OracleDriver", "org.mariadb.jdbc.Driver" ], "directory" : "org.hibernate.orm/hibernate-core", - "module" : "org.hibernate.orm:hibernate-core" + "module" : "org.hibernate.orm:hibernate-core", + "allowed-packages" : [ "org.hibernate", "jakarta", "org.h2.Driver", "org.postgresql.Driver", "com.mysql.cj.jdbc.Driver", "org.hsqldb.jdbc.JDBCDriver", "com.microsoft.sqlserver.jdbc.SQLServerDriver", "oracle.jdbc.driver.OracleDriver", "org.mariadb.jdbc.Driver" ] }, { - "allowed-packages" : [ "org.hibernate", "jakarta" ], "directory" : "org.hibernate.orm/hibernate-envers", "module" : "org.hibernate.orm:hibernate-envers", - "requires" : [ "org.hibernate.orm:hibernate-core" ] + "requires" : [ "org.hibernate.orm:hibernate-core" ], + "allowed-packages" : [ "org.hibernate", "jakarta" ] }, { - "allowed-packages" : [ "org.hibernate.reactive" ], "directory" : "org.hibernate.reactive/hibernate-reactive-core", - "module" : "org.hibernate.reactive:hibernate-reactive-core" + "module" : "org.hibernate.reactive:hibernate-reactive-core", + "allowed-packages" : [ "org.hibernate.reactive" ] }, { - "allowed-packages" : [ "org.hibernate", "jakarta" ], "directory" : "org.hibernate.validator/hibernate-validator", "module" : "org.hibernate.validator:hibernate-validator", - "requires" : [ "org.jboss.logging:jboss-logging" ] + "requires" : [ "org.jboss.logging:jboss-logging" ], + "allowed-packages" : [ "org.hibernate", "jakarta" ] }, { - "allowed-packages" : [ "org.hibernate", "jakarta", "org.h2.Driver", "org.postgresql.Driver", "com.mysql.cj.jdbc.Driver", "org.hsqldb.jdbc.JDBCDriver", "com.microsoft.sqlserver.jdbc.SQLServerDriver", "oracle.jdbc.driver.OracleDriver", "org.mariadb.jdbc.Driver" ], "directory" : "org.hibernate/hibernate-core", - "module" : "org.hibernate:hibernate-core" + "module" : "org.hibernate:hibernate-core", + "allowed-packages" : [ "org.hibernate", "jakarta", "org.h2.Driver", "org.postgresql.Driver", "com.mysql.cj.jdbc.Driver", "org.hsqldb.jdbc.JDBCDriver", "com.microsoft.sqlserver.jdbc.SQLServerDriver", "oracle.jdbc.driver.OracleDriver", "org.mariadb.jdbc.Driver" ] }, { - "allowed-packages" : [ "org.hibernate" ], "directory" : "org.hibernate/hibernate-spatial", - "module" : "org.hibernate:hibernate-spatial" + "module" : "org.hibernate:hibernate-spatial", + "allowed-packages" : [ "org.hibernate" ] }, { - "allowed-packages" : [ "org.jboss" ], "directory" : "org.jboss.logging/jboss-logging", - "module" : "org.jboss.logging:jboss-logging" + "module" : "org.jboss.logging:jboss-logging", + "allowed-packages" : [ "org.jboss" ] }, { - "allowed-packages" : [ "org.jboss" ], "directory" : "org.jboss.spec.javax.servlet/jboss-servlet-api_4.0_spec", - "module" : "org.jboss.spec.javax.servlet:jboss-servlet-api_4.0_spec" + "module" : "org.jboss.spec.javax.servlet:jboss-servlet-api_4.0_spec", + "allowed-packages" : [ "org.jboss" ] }, { - "allowed-packages" : [ "org.jctools" ], "directory" : "org.jctools/jctools-core", - "module" : "org.jctools:jctools-core" + "module" : "org.jctools:jctools-core", + "allowed-packages" : [ "org.jctools" ] }, { - "allowed-packages" : [ "kotlin" ], "directory" : "org.jetbrains.kotlin/kotlin-reflect", - "module" : "org.jetbrains.kotlin:kotlin-reflect" + "module" : "org.jetbrains.kotlin:kotlin-reflect", + "allowed-packages" : [ "kotlin" ] }, { - "allowed-packages" : [ "kotlin" ], "directory" : "org.jetbrains.kotlin/kotlin-stdlib", - "module" : "org.jetbrains.kotlin:kotlin-stdlib" + "module" : "org.jetbrains.kotlin:kotlin-stdlib", + "allowed-packages" : [ "kotlin" ] }, { - "allowed-packages" : [ "org.jline", "org.fusesource" ], "directory" : "org.jline/jline", - "module" : "org.jline:jline" + "module" : "org.jline:jline", + "allowed-packages" : [ "org.jline", "org.fusesource" ] }, { - "allowed-packages" : [ "org.jline", "org.fusesource" ], "module" : "org.jline:jline-console", - "requires" : [ "org.jline:jline" ] + "requires" : [ "org.jline:jline" ], + "allowed-packages" : [ "org.jline", "org.fusesource" ] }, { - "allowed-packages" : [ "org.jline", "org.fusesource" ], "module" : "org.jline:jline-terminal", - "requires" : [ "org.jline:jline" ] + "requires" : [ "org.jline:jline" ], + "allowed-packages" : [ "org.jline", "org.fusesource" ] }, { - "allowed-packages" : [ "org.jooq", "org_jooq" ], "directory" : "org.jooq/jooq", - "module" : "org.jooq:jooq" + "module" : "org.jooq:jooq", + "allowed-packages" : [ "org.jooq", "org_jooq" ] }, { - "allowed-packages" : [ "org.liquibase", "liquibase" ], "directory" : "org.liquibase/liquibase-core", - "module" : "org.liquibase:liquibase-core" + "module" : "org.liquibase:liquibase-core", + "allowed-packages" : [ "org.liquibase", "liquibase" ] }, { - "allowed-packages" : [ "org.mariadb" ], "directory" : "org.mariadb.jdbc/mariadb-java-client", - "module" : "org.mariadb.jdbc:mariadb-java-client" + "module" : "org.mariadb.jdbc:mariadb-java-client", + "allowed-packages" : [ "org.mariadb" ] }, { - "allowed-packages" : [ "org.mariadb" ], "directory" : "org.mariadb/r2dbc-mariadb", - "module" : "org.mariadb:r2dbc-mariadb" + "module" : "org.mariadb:r2dbc-mariadb", + "allowed-packages" : [ "org.mariadb" ] }, { - "allowed-packages" : [ "org.mockito" ], "directory" : "org.mockito/mockito-core", - "module" : "org.mockito:mockito-core" + "module" : "org.mockito:mockito-core", + "allowed-packages" : [ "org.mockito" ] }, { - "allowed-packages" : [ "org.opengauss" ], "directory" : "org.opengauss/opengauss-jdbc", - "module" : "org.opengauss:opengauss-jdbc" + "module" : "org.opengauss:opengauss-jdbc", + "allowed-packages" : [ "org.opengauss" ] }, { - "allowed-packages" : [ "org.postgresql" ], "directory" : "org.postgresql/postgresql", - "module" : "org.postgresql:postgresql" + "module" : "org.postgresql:postgresql", + "allowed-packages" : [ "org.postgresql" ] }, { - "allowed-packages" : [ "org.quartz" ], "directory" : "org.quartz-scheduler/quartz", - "module" : "org.quartz-scheduler:quartz" + "module" : "org.quartz-scheduler:quartz", + "allowed-packages" : [ "org.quartz" ] }, { - "allowed-packages" : [ "org.testcontainers" ], "directory" : "org.testcontainers/testcontainers", - "module" : "org.testcontainers:testcontainers" + "module" : "org.testcontainers:testcontainers", + "allowed-packages" : [ "org.testcontainers" ] }, { - "allowed-packages" : [ "org.thymeleaf", "org.springframework" ], "directory" : "org.thymeleaf.extras/thymeleaf-extras-springsecurity6", - "module" : "org.thymeleaf.extras:thymeleaf-extras-springsecurity6" + "module" : "org.thymeleaf.extras:thymeleaf-extras-springsecurity6", + "allowed-packages" : [ "org.thymeleaf", "org.springframework" ] }, { - "allowed-packages" : [ "org.thymeleaf" ], "directory" : "org.thymeleaf/thymeleaf", - "module" : "org.thymeleaf:thymeleaf" + "module" : "org.thymeleaf:thymeleaf", + "allowed-packages" : [ "org.thymeleaf" ] }, { - "allowed-packages" : [ "org.thymeleaf" ], "directory" : "org.thymeleaf/thymeleaf-spring6", - "module" : "org.thymeleaf:thymeleaf-spring6" + "module" : "org.thymeleaf:thymeleaf-spring6", + "allowed-packages" : [ "org.thymeleaf" ] }, { - "allowed-packages" : [ "samples" ], "directory" : "samples/docker", - "module" : "samples:docker" -} ] + "module" : "samples:docker", + "allowed-packages" : [ "samples" ] +} ] \ No newline at end of file diff --git a/metadata/org.apache.santuario/xmlsec/4.0.1/jni-config.json b/metadata/org.apache.santuario/xmlsec/4.0.1/jni-config.json new file mode 100644 index 00000000000..32960f8ced3 --- /dev/null +++ b/metadata/org.apache.santuario/xmlsec/4.0.1/jni-config.json @@ -0,0 +1,2 @@ +[ +] \ No newline at end of file diff --git a/metadata/org.apache.santuario/xmlsec/4.0.1/predefined-classes-config.json b/metadata/org.apache.santuario/xmlsec/4.0.1/predefined-classes-config.json new file mode 100644 index 00000000000..847895071fb --- /dev/null +++ b/metadata/org.apache.santuario/xmlsec/4.0.1/predefined-classes-config.json @@ -0,0 +1,7 @@ +[ + { + "type":"agent-extracted", + "classes":[ + ] + } +] diff --git a/metadata/org.apache.santuario/xmlsec/4.0.1/proxy-config.json b/metadata/org.apache.santuario/xmlsec/4.0.1/proxy-config.json new file mode 100644 index 00000000000..32960f8ced3 --- /dev/null +++ b/metadata/org.apache.santuario/xmlsec/4.0.1/proxy-config.json @@ -0,0 +1,2 @@ +[ +] \ No newline at end of file diff --git a/metadata/org.apache.santuario/xmlsec/4.0.1/reflect-config.json b/metadata/org.apache.santuario/xmlsec/4.0.1/reflect-config.json new file mode 100644 index 00000000000..890c9185a6f --- /dev/null +++ b/metadata/org.apache.santuario/xmlsec/4.0.1/reflect-config.json @@ -0,0 +1,111 @@ +[ +{ + "condition":{"typeReachable":"org.apache.xml.security.encryption.XMLCipher"}, + "name":"com.sun.crypto.provider.AESCipher$General", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "condition":{"typeReachable":"org.apache.xml.security.algorithms.implementations.IntegrityHmac"}, + "name":"com.sun.crypto.provider.HmacCore$HmacSHA256", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "condition":{"typeReachable":"org.apache.xml.security.encryption.XMLCipher"}, + "name":"com.sun.crypto.provider.RSACipher", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "condition":{"typeReachable":"org.apache.xml.security.encryption.XMLCipher"}, + "name":"com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "condition":{"typeReachable":"org.apache.xml.security.parser.XMLParserImpl"}, + "name":"com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "condition":{"typeReachable":"org.apache.xml.security.stax.ext.XMLSecurityConstants"}, + "name":"com.sun.org.apache.xerces.internal.jaxp.datatype.DatatypeFactoryImpl", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "condition":{"typeReachable":"org.apache.xml.security.encryption.XMLCipher"}, + "name":"java.security.SecureRandomParameters" +}, +{ + "condition":{"typeReachable":"org.apache.xml.security.stax.ext.XMLSecurityConstants"}, + "name":"java.security.SecureRandomParameters" +}, +{ + "condition":{"typeReachable":"org.apache.xml.security.algorithms.SignatureAlgorithmSpi"}, + "name":"java.security.interfaces.RSAPrivateKey" +}, +{ + "condition":{"typeReachable":"org.apache.xml.security.encryption.XMLCipher"}, + "name":"java.security.interfaces.RSAPrivateKey" +}, +{ + "condition":{"typeReachable":"org.apache.xml.security.algorithms.SignatureAlgorithmSpi"}, + "name":"java.security.interfaces.RSAPublicKey" +}, +{ + "condition":{"typeReachable":"org.apache.xml.security.encryption.XMLCipher"}, + "name":"java.security.interfaces.RSAPublicKey" +}, +{ + "condition":{"typeReachable":"org.apache.xml.security.algorithms.SignatureAlgorithm"}, + "name":"org.apache.xml.security.algorithms.implementations.IntegrityHmac$IntegrityHmacSHA256", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "condition":{"typeReachable":"org.apache.xml.security.algorithms.SignatureAlgorithm"}, + "name":"org.apache.xml.security.algorithms.implementations.SignatureBaseRSA$SignatureRSASHA256", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "condition":{"typeReachable":"org.apache.xml.security.signature.SignedInfo"}, + "name":"org.apache.xml.security.c14n.implementations.Canonicalizer20010315ExclOmitComments", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "condition":{"typeReachable":"org.apache.xml.security.encryption.AbstractSerializer"}, + "name":"org.apache.xml.security.c14n.implementations.CanonicalizerPhysical", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "condition":{"typeReachable":"org.apache.xml.security.encryption.XMLCipher"}, + "name":"sun.security.provider.NativePRNG", + "methods":[{"name":"","parameterTypes":[] }, {"name":"","parameterTypes":["java.security.SecureRandomParameters"] }] +}, +{ + "condition":{"typeReachable":"org.apache.xml.security.stax.ext.XMLSecurityConstants"}, + "name":"sun.security.provider.NativePRNG", + "methods":[{"name":"","parameterTypes":[] }, {"name":"","parameterTypes":["java.security.SecureRandomParameters"] }] +}, +{ + "condition":{"typeReachable":"org.apache.xml.security.encryption.XMLCipher"}, + "name":"sun.security.provider.SHA", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "condition":{"typeReachable":"org.apache.xml.security.algorithms.MessageDigestAlgorithm"}, + "name":"sun.security.provider.SHA2$SHA256", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "condition":{"typeReachable":"org.apache.xml.security.algorithms.SignatureAlgorithmSpi"}, + "name":"sun.security.provider.SHA2$SHA256", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "condition":{"typeReachable":"org.apache.xml.security.algorithms.implementations.IntegrityHmac"}, + "name":"sun.security.provider.SHA2$SHA256", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "condition":{"typeReachable":"org.apache.xml.security.algorithms.SignatureAlgorithmSpi"}, + "name":"sun.security.rsa.RSASignature$SHA256withRSA", + "methods":[{"name":"","parameterTypes":[] }] +} +] \ No newline at end of file diff --git a/metadata/org.apache.santuario/xmlsec/4.0.1/resource-config.json b/metadata/org.apache.santuario/xmlsec/4.0.1/resource-config.json new file mode 100644 index 00000000000..21d0b93d784 --- /dev/null +++ b/metadata/org.apache.santuario/xmlsec/4.0.1/resource-config.json @@ -0,0 +1,23 @@ +{ + "resources":{ + "includes":[{ + "condition":{"typeReachable":"org.apache.xml.security.utils.I18n"}, + "pattern":"\\QMETA-INF/services/java.util.spi.ResourceBundleControlProvider\\E" + }, { + "condition":{"typeReachable":"org.apache.xml.security.stax.ext.XMLSecurityConstants"}, + "pattern":"\\QMETA-INF/services/javax.xml.datatype.DatatypeFactory\\E" + }, { + "condition":{"typeReachable":"org.apache.xml.security.parser.XMLParserImpl"}, + "pattern":"\\QMETA-INF/services/javax.xml.parsers.DocumentBuilderFactory\\E" + }, { + "condition":{"typeReachable":"org.apache.xml.security.stax.ext.XMLSecurityConstants"}, + "pattern":"\\QMETA-INF/services/javax.xml.stream.XMLOutputFactory\\E" + }, { + "condition":{"typeReachable":"org.apache.xml.security.encryption.XMLCipher"}, + "pattern":"\\QMETA-INF/services/javax.xml.transform.TransformerFactory\\E" + }]}, + "bundles":[{ + "name":"org/apache/xml/security/resource/xmlsecurity", + "locales":["en"] + }] +} \ No newline at end of file diff --git a/metadata/org.apache.santuario/xmlsec/4.0.1/serialization-config.json b/metadata/org.apache.santuario/xmlsec/4.0.1/serialization-config.json new file mode 100644 index 00000000000..d0304f2a1c7 --- /dev/null +++ b/metadata/org.apache.santuario/xmlsec/4.0.1/serialization-config.json @@ -0,0 +1,8 @@ +{ + "types":[ + ], + "lambdaCapturingTypes":[ + ], + "proxies":[ + ] +} \ No newline at end of file diff --git a/metadata/org.apache.santuario/xmlsec/index.json b/metadata/org.apache.santuario/xmlsec/index.json new file mode 100644 index 00000000000..2b3bf8078fa --- /dev/null +++ b/metadata/org.apache.santuario/xmlsec/index.json @@ -0,0 +1,10 @@ +[ + { + "latest": true, + "metadata-version": "4.0.1", + "module": "org.apache.santuario:xmlsec", + "tested-versions": [ + "4.0.1" + ] + } +] diff --git a/tests/src/org.apache.santuario/xmlsec/4.0.1/build.gradle b/tests/src/org.apache.santuario/xmlsec/4.0.1/build.gradle index 5a0808bbd82..89856474cad 100644 --- a/tests/src/org.apache.santuario/xmlsec/4.0.1/build.gradle +++ b/tests/src/org.apache.santuario/xmlsec/4.0.1/build.gradle @@ -14,3 +14,14 @@ dependencies { testImplementation "org.apache.santuario:xmlsec:$libraryVersion" testImplementation 'org.assertj:assertj-core:3.22.0' } + +graalvmNative { + agent { + defaultMode = "conditional" + modes { + conditional { + userCodeFilterPath = "user-code-filter.json" + } + } + } +} diff --git a/tests/src/org.apache.santuario/xmlsec/4.0.1/user-code-filter.json b/tests/src/org.apache.santuario/xmlsec/4.0.1/user-code-filter.json new file mode 100644 index 00000000000..588fa5ff149 --- /dev/null +++ b/tests/src/org.apache.santuario/xmlsec/4.0.1/user-code-filter.json @@ -0,0 +1,10 @@ +{ + "rules" : [ + { + "excludeClasses" : "**" + }, + { + "includeClasses" : "org.apache.**" + } + ] +} \ No newline at end of file From a1abf507ef300693008401d2958a8d18a5b59637 Mon Sep 17 00:00:00 2001 From: Mihailo Date: Fri, 9 Jan 2026 12:25:30 +0100 Subject: [PATCH 29/29] Add support for org.apache.santuario:xmlsec:4.0.4 --- .../xmlsec/4.0.1/jni-config.json | 2 - .../4.0.1/predefined-classes-config.json | 7 - .../xmlsec/4.0.1/proxy-config.json | 2 - .../xmlsec/4.0.1/reflect-config.json | 111 -------- .../xmlsec/4.0.1/resource-config.json | 23 -- .../xmlsec/4.0.1/serialization-config.json | 8 - .../xmlsec/4.0.4/reflect-config.json | 254 ++++++++++++++++++ .../xmlsec/4.0.4/resource-config.json | 38 +++ .../org.apache.santuario/xmlsec/index.json | 4 +- .../xmlsec/4.0.1/gradle.properties | 2 - .../xmlsec/{4.0.1 => 4.0.4}/.gitignore | 0 .../xmlsec/{4.0.1 => 4.0.4}/build.gradle | 0 .../xmlsec/4.0.4/gradle.properties | 2 + .../xmlsec/{4.0.1 => 4.0.4}/settings.gradle | 0 .../xmlsec/XmlsecTest.java | 0 .../{4.0.1 => 4.0.4}/user-code-filter.json | 0 16 files changed, 296 insertions(+), 157 deletions(-) delete mode 100644 metadata/org.apache.santuario/xmlsec/4.0.1/jni-config.json delete mode 100644 metadata/org.apache.santuario/xmlsec/4.0.1/predefined-classes-config.json delete mode 100644 metadata/org.apache.santuario/xmlsec/4.0.1/proxy-config.json delete mode 100644 metadata/org.apache.santuario/xmlsec/4.0.1/reflect-config.json delete mode 100644 metadata/org.apache.santuario/xmlsec/4.0.1/resource-config.json delete mode 100644 metadata/org.apache.santuario/xmlsec/4.0.1/serialization-config.json create mode 100644 metadata/org.apache.santuario/xmlsec/4.0.4/reflect-config.json create mode 100644 metadata/org.apache.santuario/xmlsec/4.0.4/resource-config.json delete mode 100644 tests/src/org.apache.santuario/xmlsec/4.0.1/gradle.properties rename tests/src/org.apache.santuario/xmlsec/{4.0.1 => 4.0.4}/.gitignore (100%) rename tests/src/org.apache.santuario/xmlsec/{4.0.1 => 4.0.4}/build.gradle (100%) create mode 100644 tests/src/org.apache.santuario/xmlsec/4.0.4/gradle.properties rename tests/src/org.apache.santuario/xmlsec/{4.0.1 => 4.0.4}/settings.gradle (100%) rename tests/src/org.apache.santuario/xmlsec/{4.0.1 => 4.0.4}/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java (100%) rename tests/src/org.apache.santuario/xmlsec/{4.0.1 => 4.0.4}/user-code-filter.json (100%) diff --git a/metadata/org.apache.santuario/xmlsec/4.0.1/jni-config.json b/metadata/org.apache.santuario/xmlsec/4.0.1/jni-config.json deleted file mode 100644 index 32960f8ced3..00000000000 --- a/metadata/org.apache.santuario/xmlsec/4.0.1/jni-config.json +++ /dev/null @@ -1,2 +0,0 @@ -[ -] \ No newline at end of file diff --git a/metadata/org.apache.santuario/xmlsec/4.0.1/predefined-classes-config.json b/metadata/org.apache.santuario/xmlsec/4.0.1/predefined-classes-config.json deleted file mode 100644 index 847895071fb..00000000000 --- a/metadata/org.apache.santuario/xmlsec/4.0.1/predefined-classes-config.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - "type":"agent-extracted", - "classes":[ - ] - } -] diff --git a/metadata/org.apache.santuario/xmlsec/4.0.1/proxy-config.json b/metadata/org.apache.santuario/xmlsec/4.0.1/proxy-config.json deleted file mode 100644 index 32960f8ced3..00000000000 --- a/metadata/org.apache.santuario/xmlsec/4.0.1/proxy-config.json +++ /dev/null @@ -1,2 +0,0 @@ -[ -] \ No newline at end of file diff --git a/metadata/org.apache.santuario/xmlsec/4.0.1/reflect-config.json b/metadata/org.apache.santuario/xmlsec/4.0.1/reflect-config.json deleted file mode 100644 index 890c9185a6f..00000000000 --- a/metadata/org.apache.santuario/xmlsec/4.0.1/reflect-config.json +++ /dev/null @@ -1,111 +0,0 @@ -[ -{ - "condition":{"typeReachable":"org.apache.xml.security.encryption.XMLCipher"}, - "name":"com.sun.crypto.provider.AESCipher$General", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "condition":{"typeReachable":"org.apache.xml.security.algorithms.implementations.IntegrityHmac"}, - "name":"com.sun.crypto.provider.HmacCore$HmacSHA256", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "condition":{"typeReachable":"org.apache.xml.security.encryption.XMLCipher"}, - "name":"com.sun.crypto.provider.RSACipher", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "condition":{"typeReachable":"org.apache.xml.security.encryption.XMLCipher"}, - "name":"com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "condition":{"typeReachable":"org.apache.xml.security.parser.XMLParserImpl"}, - "name":"com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "condition":{"typeReachable":"org.apache.xml.security.stax.ext.XMLSecurityConstants"}, - "name":"com.sun.org.apache.xerces.internal.jaxp.datatype.DatatypeFactoryImpl", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "condition":{"typeReachable":"org.apache.xml.security.encryption.XMLCipher"}, - "name":"java.security.SecureRandomParameters" -}, -{ - "condition":{"typeReachable":"org.apache.xml.security.stax.ext.XMLSecurityConstants"}, - "name":"java.security.SecureRandomParameters" -}, -{ - "condition":{"typeReachable":"org.apache.xml.security.algorithms.SignatureAlgorithmSpi"}, - "name":"java.security.interfaces.RSAPrivateKey" -}, -{ - "condition":{"typeReachable":"org.apache.xml.security.encryption.XMLCipher"}, - "name":"java.security.interfaces.RSAPrivateKey" -}, -{ - "condition":{"typeReachable":"org.apache.xml.security.algorithms.SignatureAlgorithmSpi"}, - "name":"java.security.interfaces.RSAPublicKey" -}, -{ - "condition":{"typeReachable":"org.apache.xml.security.encryption.XMLCipher"}, - "name":"java.security.interfaces.RSAPublicKey" -}, -{ - "condition":{"typeReachable":"org.apache.xml.security.algorithms.SignatureAlgorithm"}, - "name":"org.apache.xml.security.algorithms.implementations.IntegrityHmac$IntegrityHmacSHA256", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "condition":{"typeReachable":"org.apache.xml.security.algorithms.SignatureAlgorithm"}, - "name":"org.apache.xml.security.algorithms.implementations.SignatureBaseRSA$SignatureRSASHA256", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "condition":{"typeReachable":"org.apache.xml.security.signature.SignedInfo"}, - "name":"org.apache.xml.security.c14n.implementations.Canonicalizer20010315ExclOmitComments", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "condition":{"typeReachable":"org.apache.xml.security.encryption.AbstractSerializer"}, - "name":"org.apache.xml.security.c14n.implementations.CanonicalizerPhysical", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "condition":{"typeReachable":"org.apache.xml.security.encryption.XMLCipher"}, - "name":"sun.security.provider.NativePRNG", - "methods":[{"name":"","parameterTypes":[] }, {"name":"","parameterTypes":["java.security.SecureRandomParameters"] }] -}, -{ - "condition":{"typeReachable":"org.apache.xml.security.stax.ext.XMLSecurityConstants"}, - "name":"sun.security.provider.NativePRNG", - "methods":[{"name":"","parameterTypes":[] }, {"name":"","parameterTypes":["java.security.SecureRandomParameters"] }] -}, -{ - "condition":{"typeReachable":"org.apache.xml.security.encryption.XMLCipher"}, - "name":"sun.security.provider.SHA", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "condition":{"typeReachable":"org.apache.xml.security.algorithms.MessageDigestAlgorithm"}, - "name":"sun.security.provider.SHA2$SHA256", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "condition":{"typeReachable":"org.apache.xml.security.algorithms.SignatureAlgorithmSpi"}, - "name":"sun.security.provider.SHA2$SHA256", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "condition":{"typeReachable":"org.apache.xml.security.algorithms.implementations.IntegrityHmac"}, - "name":"sun.security.provider.SHA2$SHA256", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "condition":{"typeReachable":"org.apache.xml.security.algorithms.SignatureAlgorithmSpi"}, - "name":"sun.security.rsa.RSASignature$SHA256withRSA", - "methods":[{"name":"","parameterTypes":[] }] -} -] \ No newline at end of file diff --git a/metadata/org.apache.santuario/xmlsec/4.0.1/resource-config.json b/metadata/org.apache.santuario/xmlsec/4.0.1/resource-config.json deleted file mode 100644 index 21d0b93d784..00000000000 --- a/metadata/org.apache.santuario/xmlsec/4.0.1/resource-config.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "resources":{ - "includes":[{ - "condition":{"typeReachable":"org.apache.xml.security.utils.I18n"}, - "pattern":"\\QMETA-INF/services/java.util.spi.ResourceBundleControlProvider\\E" - }, { - "condition":{"typeReachable":"org.apache.xml.security.stax.ext.XMLSecurityConstants"}, - "pattern":"\\QMETA-INF/services/javax.xml.datatype.DatatypeFactory\\E" - }, { - "condition":{"typeReachable":"org.apache.xml.security.parser.XMLParserImpl"}, - "pattern":"\\QMETA-INF/services/javax.xml.parsers.DocumentBuilderFactory\\E" - }, { - "condition":{"typeReachable":"org.apache.xml.security.stax.ext.XMLSecurityConstants"}, - "pattern":"\\QMETA-INF/services/javax.xml.stream.XMLOutputFactory\\E" - }, { - "condition":{"typeReachable":"org.apache.xml.security.encryption.XMLCipher"}, - "pattern":"\\QMETA-INF/services/javax.xml.transform.TransformerFactory\\E" - }]}, - "bundles":[{ - "name":"org/apache/xml/security/resource/xmlsecurity", - "locales":["en"] - }] -} \ No newline at end of file diff --git a/metadata/org.apache.santuario/xmlsec/4.0.1/serialization-config.json b/metadata/org.apache.santuario/xmlsec/4.0.1/serialization-config.json deleted file mode 100644 index d0304f2a1c7..00000000000 --- a/metadata/org.apache.santuario/xmlsec/4.0.1/serialization-config.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "types":[ - ], - "lambdaCapturingTypes":[ - ], - "proxies":[ - ] -} \ No newline at end of file diff --git a/metadata/org.apache.santuario/xmlsec/4.0.4/reflect-config.json b/metadata/org.apache.santuario/xmlsec/4.0.4/reflect-config.json new file mode 100644 index 00000000000..b2f1fda9bf7 --- /dev/null +++ b/metadata/org.apache.santuario/xmlsec/4.0.4/reflect-config.json @@ -0,0 +1,254 @@ +[ + { + "condition": { + "typeReachable": "org.apache.xml.security.encryption.XMLCipher" + }, + "name": "com.sun.crypto.provider.AESCipher$General", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "condition": { + "typeReachable": "org.apache.xml.security.algorithms.implementations.IntegrityHmac" + }, + "name": "com.sun.crypto.provider.HmacCore$HmacSHA256", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "condition": { + "typeReachable": "org.apache.xml.security.encryption.XMLCipher" + }, + "name": "com.sun.crypto.provider.RSACipher", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "condition": { + "typeReachable": "org.apache.xml.security.encryption.XMLCipher" + }, + "name": "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "condition": { + "typeReachable": "org.apache.xml.security.parser.XMLParserImpl" + }, + "name": "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "condition": { + "typeReachable": "org.apache.xml.security.stax.ext.XMLSecurityConstants" + }, + "name": "com.sun.org.apache.xerces.internal.jaxp.datatype.DatatypeFactoryImpl", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "condition": { + "typeReachable": "org.apache.xml.security.encryption.XMLCipher" + }, + "name": "java.security.SecureRandomParameters" + }, + { + "condition": { + "typeReachable": "org.apache.xml.security.stax.ext.XMLSecurityConstants" + }, + "name": "java.security.SecureRandomParameters" + }, + { + "condition": { + "typeReachable": "org.apache.xml.security.algorithms.SignatureAlgorithmSpi" + }, + "name": "java.security.interfaces.RSAPrivateKey" + }, + { + "condition": { + "typeReachable": "org.apache.xml.security.encryption.XMLCipher" + }, + "name": "java.security.interfaces.RSAPrivateKey" + }, + { + "condition": { + "typeReachable": "org.apache.xml.security.algorithms.SignatureAlgorithmSpi" + }, + "name": "java.security.interfaces.RSAPublicKey" + }, + { + "condition": { + "typeReachable": "org.apache.xml.security.encryption.XMLCipher" + }, + "name": "java.security.interfaces.RSAPublicKey" + }, + { + "condition": { + "typeReachable": "org.apache.xml.security.algorithms.SignatureAlgorithm" + }, + "name": "org.apache.xml.security.algorithms.implementations.IntegrityHmac$IntegrityHmacSHA256", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "condition": { + "typeReachable": "org.apache.xml.security.algorithms.SignatureAlgorithm" + }, + "name": "org.apache.xml.security.algorithms.implementations.SignatureBaseRSA$SignatureRSASHA256", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "condition": { + "typeReachable": "org.apache.xml.security.signature.SignedInfo" + }, + "name": "org.apache.xml.security.c14n.implementations.Canonicalizer20010315ExclOmitComments", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "condition": { + "typeReachable": "org.apache.xml.security.encryption.AbstractSerializer" + }, + "name": "org.apache.xml.security.c14n.implementations.CanonicalizerPhysical", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "condition": { + "typeReachable": "org.apache.xml.security.encryption.XMLCipher" + }, + "name": "sun.security.provider.NativePRNG", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "", + "parameterTypes": [ + "java.security.SecureRandomParameters" + ] + } + ] + }, + { + "condition": { + "typeReachable": "org.apache.xml.security.stax.ext.XMLSecurityConstants" + }, + "name": "sun.security.provider.NativePRNG", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "", + "parameterTypes": [ + "java.security.SecureRandomParameters" + ] + } + ] + }, + { + "condition": { + "typeReachable": "org.apache.xml.security.encryption.XMLCipher" + }, + "name": "sun.security.provider.SHA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "condition": { + "typeReachable": "org.apache.xml.security.algorithms.MessageDigestAlgorithm" + }, + "name": "sun.security.provider.SHA2$SHA256", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "condition": { + "typeReachable": "org.apache.xml.security.algorithms.SignatureAlgorithmSpi" + }, + "name": "sun.security.provider.SHA2$SHA256", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "condition": { + "typeReachable": "org.apache.xml.security.algorithms.implementations.IntegrityHmac" + }, + "name": "sun.security.provider.SHA2$SHA256", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "condition": { + "typeReachable": "org.apache.xml.security.algorithms.SignatureAlgorithmSpi" + }, + "name": "sun.security.rsa.RSASignature$SHA256withRSA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + } +] diff --git a/metadata/org.apache.santuario/xmlsec/4.0.4/resource-config.json b/metadata/org.apache.santuario/xmlsec/4.0.4/resource-config.json new file mode 100644 index 00000000000..47023c2e22e --- /dev/null +++ b/metadata/org.apache.santuario/xmlsec/4.0.4/resource-config.json @@ -0,0 +1,38 @@ +{ + "resources": { + "includes": [ + { + "condition": { + "typeReachable": "org.apache.xml.security.stax.ext.XMLSecurityConstants" + }, + "pattern": "\\QMETA-INF/services/javax.xml.datatype.DatatypeFactory\\E" + }, + { + "condition": { + "typeReachable": "org.apache.xml.security.parser.XMLParserImpl" + }, + "pattern": "\\QMETA-INF/services/javax.xml.parsers.DocumentBuilderFactory\\E" + }, + { + "condition": { + "typeReachable": "org.apache.xml.security.stax.ext.XMLSecurityConstants" + }, + "pattern": "\\QMETA-INF/services/javax.xml.stream.XMLOutputFactory\\E" + }, + { + "condition": { + "typeReachable": "org.apache.xml.security.encryption.XMLCipher" + }, + "pattern": "\\QMETA-INF/services/javax.xml.transform.TransformerFactory\\E" + } + ] + }, + "bundles": [ + { + "name": "org/apache/xml/security/resource/xmlsecurity", + "locales": [ + "en" + ] + } + ] +} diff --git a/metadata/org.apache.santuario/xmlsec/index.json b/metadata/org.apache.santuario/xmlsec/index.json index 2b3bf8078fa..20d921c9be9 100644 --- a/metadata/org.apache.santuario/xmlsec/index.json +++ b/metadata/org.apache.santuario/xmlsec/index.json @@ -1,10 +1,10 @@ [ { "latest": true, - "metadata-version": "4.0.1", + "metadata-version": "4.0.4", "module": "org.apache.santuario:xmlsec", "tested-versions": [ - "4.0.1" + "4.0.4" ] } ] diff --git a/tests/src/org.apache.santuario/xmlsec/4.0.1/gradle.properties b/tests/src/org.apache.santuario/xmlsec/4.0.1/gradle.properties deleted file mode 100644 index d4a3da13cce..00000000000 --- a/tests/src/org.apache.santuario/xmlsec/4.0.1/gradle.properties +++ /dev/null @@ -1,2 +0,0 @@ -library.version = 4.0.1 -metadata.dir = org.apache.santuario/xmlsec/4.0.1/ diff --git a/tests/src/org.apache.santuario/xmlsec/4.0.1/.gitignore b/tests/src/org.apache.santuario/xmlsec/4.0.4/.gitignore similarity index 100% rename from tests/src/org.apache.santuario/xmlsec/4.0.1/.gitignore rename to tests/src/org.apache.santuario/xmlsec/4.0.4/.gitignore diff --git a/tests/src/org.apache.santuario/xmlsec/4.0.1/build.gradle b/tests/src/org.apache.santuario/xmlsec/4.0.4/build.gradle similarity index 100% rename from tests/src/org.apache.santuario/xmlsec/4.0.1/build.gradle rename to tests/src/org.apache.santuario/xmlsec/4.0.4/build.gradle diff --git a/tests/src/org.apache.santuario/xmlsec/4.0.4/gradle.properties b/tests/src/org.apache.santuario/xmlsec/4.0.4/gradle.properties new file mode 100644 index 00000000000..5fa2caac9dc --- /dev/null +++ b/tests/src/org.apache.santuario/xmlsec/4.0.4/gradle.properties @@ -0,0 +1,2 @@ +library.version = 4.0.4 +metadata.dir = org.apache.santuario/xmlsec/4.0.4/ diff --git a/tests/src/org.apache.santuario/xmlsec/4.0.1/settings.gradle b/tests/src/org.apache.santuario/xmlsec/4.0.4/settings.gradle similarity index 100% rename from tests/src/org.apache.santuario/xmlsec/4.0.1/settings.gradle rename to tests/src/org.apache.santuario/xmlsec/4.0.4/settings.gradle diff --git a/tests/src/org.apache.santuario/xmlsec/4.0.1/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java b/tests/src/org.apache.santuario/xmlsec/4.0.4/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java similarity index 100% rename from tests/src/org.apache.santuario/xmlsec/4.0.1/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java rename to tests/src/org.apache.santuario/xmlsec/4.0.4/src/test/java/org_apache_santuario/xmlsec/XmlsecTest.java diff --git a/tests/src/org.apache.santuario/xmlsec/4.0.1/user-code-filter.json b/tests/src/org.apache.santuario/xmlsec/4.0.4/user-code-filter.json similarity index 100% rename from tests/src/org.apache.santuario/xmlsec/4.0.1/user-code-filter.json rename to tests/src/org.apache.santuario/xmlsec/4.0.4/user-code-filter.json