diff --git a/AGENTS.md b/AGENTS.md index 333a4e99a..dfeb813a7 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/TckExtension.java b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/TckExtension.java index c62423971..4614a749a 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 @@ -100,16 +100,38 @@ public Path getTestDir(String coordinates) { Objects.requireNonNull(artifactId, "Artifact ID must be specified"); Objects.requireNonNull(version, "Version must be specified"); - // First, let's try if we can find test directory from the new `tests/src/index.json` file. - List> index = (List>) readIndexFile(testRoot()); - for (Map entry : index) { - boolean found = ((List>) entry.get("libraries")).stream().anyMatch( - lib -> coordinatesMatch((String) lib.get("name"), groupId, artifactId) && - ((List) lib.get("versions")).contains(version) - ); - if (found) { - return testRoot().resolve((String) entry.get("test-project-path")); + // First, try to locate the test project via the aggregated tests/src/index.json (new layout). + try { + List> index = (List>) readIndexFile(testRoot()); + for (Map entry : index) { + boolean found = ((List>) entry.get("libraries")).stream().anyMatch( + lib -> coordinatesMatch((String) lib.get("name"), groupId, artifactId) && + ((List) lib.get("versions")).contains(version) + ); + if (found) { + return testRoot().resolve((String) entry.get("test-project-path")); + } + } + } catch (Exception ignored) { + // Fall through to conventional layout resolution below. + } + // 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/AbstractSubprojectTask.groovy b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/AbstractSubprojectTask.groovy deleted file mode 100644 index 89c6015ba..000000000 --- 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 000000000..a234291ce --- /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()); + } + } +} 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 dd5079bd1..000000000 --- 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 000000000..458c3db3c --- /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()); + } + } +} 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 3e4d13a67..000000000 --- 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 000000000..94d155079 --- /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/CheckstyleInvocationTask.groovy b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CheckstyleInvocationTask.groovy deleted file mode 100644 index 729bb8521..000000000 --- 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 000000000..ae47bb802 --- /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"; + } +} 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 398a107f1..000000000 --- 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 000000000..dfac65cd1 --- /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"; + } +} 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 4f6c04a55..000000000 --- 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 000000000..f7edbd9e0 --- /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"; + } +} 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 a4f3a6c4d..000000000 --- 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 000000000..bbd608046 --- /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); + } + } +} 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 89adeabbd..000000000 --- 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 and ".Final" 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" are treated as full releases of the base version. - */ - private static final Pattern VERSION_PATTERN = ~/(?i)^(\d+(?:\.\d+)*)(?:\.Final)?(?:[-.](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/groovy/org/graalvm/internal/tck/harness/tasks/FetchExistingLibrariesWithNewerVersionsTask.java new file mode 100644 index 000000000..e84c536c1 --- /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); + } + } +} 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 5aff54dda..817cf8223 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"; } } 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 80c3e5f91..12a518c8a 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"; } } 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 ddf1b9090..26d5f5650 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); } } 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 8865fce11..000000000 --- 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 000000000..f9c44e35e --- /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 (Exception 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("===================="); + } +} 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 7811f271d..2ee4a13d0 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);