diff --git a/.github/workflows/scripts/disable-docker.sh b/.github/workflows/scripts/disable-docker.sh index 4387fabf1..322608bad 100644 --- a/.github/workflows/scripts/disable-docker.sh +++ b/.github/workflows/scripts/disable-docker.sh @@ -1,5 +1,4 @@ #!/bin/bash - # Copyright and related rights waived via CC0 # # You should have received a copy of the CC0 legalcode along with this diff --git a/.github/workflows/scripts/run-consecutive-tests.sh b/.github/workflows/scripts/run-consecutive-tests.sh index e57ac0fcc..15ea47b6c 100644 --- a/.github/workflows/scripts/run-consecutive-tests.sh +++ b/.github/workflows/scripts/run-consecutive-tests.sh @@ -1,5 +1,4 @@ #!/bin/bash - # Copyright and related rights waived via CC0 # # You should have received a copy of the CC0 legalcode along with this diff --git a/.github/workflows/test-changed-infrastructure.yml b/.github/workflows/test-changed-infrastructure.yml index c5ddda381..141a9301b 100644 --- a/.github/workflows/test-changed-infrastructure.yml +++ b/.github/workflows/test-changed-infrastructure.yml @@ -82,16 +82,13 @@ jobs: run: | ./gradlew pullAllowedDockerImages -Pcoordinates=${{ matrix.coordinates }} - - name: "Disable docker networking" - run: bash ./.github/workflows/scripts/disable-docker.sh - - name: "๐Ÿ”Ž Check metadata files content" run: | ./gradlew checkMetadataFiles -Pcoordinates=${{ matrix.coordinates }} - name: "๐Ÿงช Run '${{ matrix.coordinates }}' tests" run: | - ./gradlew test -Pcoordinates=${{ matrix.coordinates }} + ./gradlew testInfra -Pcoordinates=${{ matrix.coordinates }} -Pparallelism=1 --stacktrace all-infrastructure-passed: name: "๐Ÿงช All build-logic triggered tests have passed" diff --git a/AGENTS.md b/AGENTS.md index 28c13c71a..333a4e99a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,8 +11,8 @@ - Windows: gradlew.bat [options] - Tip: add --stacktrace for debugging -## One command for complete testing (use at the end of the task to verify) -./gradlew testAllParallel -Pparallelism=4 +## One command for complete infrastructure testing +./gradlew testAllInfra -Pparallelism=4 --stacktrace ## Code Style - Always try to reuse existing code. diff --git a/build.gradle b/build.gradle index 708249dd1..abd10a873 100644 --- a/build.gradle +++ b/build.gradle @@ -6,15 +6,10 @@ */ import groovy.json.JsonSlurper +import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.TimeUnit - -/* - * Copyright and related rights waived via CC0 - * - * You should have received a copy of the CC0 legalcode along with this - * work. If not, see . - */ +import groovy.json.JsonSlurper plugins { id 'base' @@ -37,55 +32,56 @@ spotless { json { target( tck.metadataRoot.map { it.toString() + '/**/*.json' }.get(), - tck.testRoot.map { it.toString() + '/**/*.json' } + tck.testRoot.map { it.toString() + '/**/*.json' }.get() ) targetExclude( - tck.testRoot.map { it.toString() + '/**/build/**/*.json' }, - tck.repoRoot.map { it.toString() + '/.github/**/*.json' } + tck.testRoot.map { it.toString() + '/**/build/**/*.json' }.get(), + tck.repoRoot.map { it.toString() + '/.github/**/*.json' }.get() ) gson() .indentWithSpaces(2) .sortByKeys() .version("2.9.0") } - java { - target('**/*.java') - targetExclude( - 'metadata/**', // only JSON there - '**/build/**', - 'gradle/header.java') - // Place header before package/import/module - licenseHeaderFile("$rootDir/gradle/header.java", "package |import |module ") - } - String[] noHeaderDirs = [ 'docs/**', // only .md files 'metadata/**', // only .json files '**/build/**', // not relevant + '**/.gradle/**', // not relevant 'gradle/wrapper/**', // not relevant + 'tests/src/**/generated/**/*.java', + 'gradle/header.java', + 'gradle/header.sh', ] + + java { + target('**/*.java') + targetExclude(*noHeaderDirs); + // Place header before package/import/module + licenseHeaderFile("$rootDir/gradle/header.java", "package |import |module ") + } + groovy { target('**/*.groovy') - targetExclude(noHeaderDirs) + targetExclude(*noHeaderDirs) // Place header before package/import licenseHeaderFile("$rootDir/gradle/header.java", "package |import ") } groovyGradle { // Build scripts (*.gradle) target('**/*.gradle') - targetExclude(noHeaderDirs) - targetExclude('tests/**') + targetExclude(*noHeaderDirs) // Insert header at file start (works for any Gradle script layout) licenseHeaderFile("$rootDir/gradle/header.java", "(?m)^(import\\b|plugins\\b|buildscript\\b|apply\\b|rootProject\\b|pluginManagement\\b|dependencyResolutionManagement\\b)") } format('shell') { target('**/*.sh') - targetExclude(noHeaderDirs) + targetExclude(*noHeaderDirs) // Ensure header appears after shebang if present, else at the very top. def shHeader = file("$rootDir/gradle/header.sh").text // If file starts with a shebang and is missing the header, insert it after the shebang replaceRegex('insert-license-after-shebang', - '(?s)\\A(#!.*\\n)(?!# Copyright and related rights waived via CC0\\n)', + '(?s)\\A(#![^\\n]*\\n)(?:[ \\t]*\\n)*(?!# Copyright and related rights waived via CC0\\n)', '$1' + shHeader + "\n") // If file has no shebang and is missing the header, insert it at the start replaceRegex('insert-license-at-start', @@ -119,9 +115,196 @@ tasks.register('test') { t -> // and without applying the 'java' plugin to avoid task name conflicts. apply plugin: "org.graalvm.internal.tck-harness" -ext.runLoggedCommand = { List args, String label, File logFile, File workingDir -> - def header = "==== BEGIN ${label}" +static List testInfraCommands(String gradleCmd, String target) { + def taskNames = [ + "checkstyle", + "spotlessCheck", + "checkMetadataFiles", + "fetchExistingLibrariesWithNewerVersions", + "test", + ] + return taskNames.collect { name -> + [name: name, args: [gradleCmd, name, "-Pcoordinates=${target}"]] + } +} + +tasks.register('testInfra') { t -> + t.group = "verification" + t.setDescription("""Runs clean and pullAllowedDockerImages, then runs (${testInfraCommands('', '').collect { it.name }.join(', ')}) concurrently with isolated logs for a single coordinate. + Options: -Pcoordinate or -Pcoordinates to specify the target; -Pparallelism to control the number of concurrent tasks.""") + + doLast { + def coordinateProp = ((project.findProperty('coordinate') ?: project.findProperty('coordinates')) ?: '').toString().trim() + if (coordinateProp.isEmpty()) { + throw new GradleException("Missing -Pcoordinate= (or -Pcoordinates).") + } + + def gradleCmd = System.getProperty("os.name").toLowerCase().contains("windows") ? "gradlew.bat" : "./gradlew" + int parallelism = parsePositiveIntProp('parallelism', 4) + println("Using parallelism=${parallelism} for testInfra.") + + def logsDir = layout.buildDirectory.dir("command-logs").get().asFile + logsDir.mkdirs() + + // Helper to run pull + command set for a given target (artifact or coordinates) + def runPhaseForCoordinate = { String coordinate -> + // Clean project before pulling Docker images for this coordinate + // Note: 'clean' deletes the build/ directory, so log to a temporary location outside build/, + // then move/copy the log into build/ afterwards. + def tmpCleanLogsDir = new File(project.rootDir, ".tmp-command-logs-clean") + tmpCleanLogsDir.mkdirs() + def fileSuffix = sanitizeFileName(coordinate) + def tmpCleanLogFile = new File(tmpCleanLogsDir, "clean-${fileSuffix}.log") + try { + runGradleTaskAndCheck( + [gradleCmd, "clean", "-Pcoordinates=${coordinate}"], + "clean (${coordinate})", + tmpCleanLogFile + ) + } finally { + // After clean, move/copy the log into the build folder (clean wipes build/) + def cleanLogsDir = layout.buildDirectory.dir("command-logs-clean").get().asFile + cleanLogsDir.mkdirs() + def cleanLogFile = new File(cleanLogsDir, "clean-${fileSuffix}.log") + try { + Files.move(tmpCleanLogFile.toPath(), cleanLogFile.toPath(), StandardCopyOption.REPLACE_EXISTING) + } catch (Throwable ignored) { + if (tmpCleanLogFile?.exists()) { + cleanLogFile.text = tmpCleanLogFile.text + } + } + } + // Ensure logsDir exists after clean + logsDir.mkdirs() + + // Pull Docker images sequentially for this coordinate + def pullArgs = [gradleCmd, "pullAllowedDockerImages", "-Pcoordinates=${coordinate}"] + def pullLogFile = new File(logsDir, "pullAllowedDockerImages-${fileSuffix}.log") + runGradleTaskAndCheck(pullArgs, "pullAllowedDockerImages (${coordinate})", pullLogFile) + + def commands = testInfraCommands(gradleCmd, coordinate) + int effectiveParallelism = Math.min(parallelism, commands.size()) + def pool = Executors.newFixedThreadPool(effectiveParallelism) + + runCommandsWithPool(pool, coordinate, commands) + } + + runPhaseForCoordinate(coordinateProp) + } +} + +tasks.register('testAllInfra') { t -> + t.group = "verification" + t.setDescription("Determines one artifact coordinate via generateInfrastructureChangedCoordinatesMatrix (or uses -Pcoordinate/-Pcoordinates if provided), then runs testInfra for that coordinate and also for 1/64. Options: -Pparallelism to control concurrency.") + doLast { + def gradleCmd = System.getProperty("os.name").toLowerCase().contains("windows") ? "gradlew.bat" : "./gradlew" + int parallelism = parsePositiveIntProp('parallelism', 4) + println("Using parallelism=${parallelism} for testAllInfra.") + + String artifactCoordinate = ((project.findProperty('coordinate') ?: project.findProperty('coordinates')) ?: '').toString().trim() + if (artifactCoordinate.isEmpty()) { + def matrixLogsDir = layout.buildDirectory.dir("command-logs").get().asFile + matrixLogsDir.mkdirs() + def matrixLog = runGenerateMatrixAndCheck(gradleCmd, matrixLogsDir) + try { + def line = matrixLog.readLines().find { it.startsWith("matrix=") } + if (line != null) { + def jsonStr = line.substring("matrix=".length()) + def parsed = new JsonSlurper().parseText(jsonStr) + def coords = (parsed["coordinates"] as List) ?: [] + if (!coords.isEmpty()) { + artifactCoordinate = coords[0].toString() + } + } + } catch (Throwable e) { + throw new GradleException("Failed to parse matrix output from ${matrixLog}: ${e.message}") + } + if (!artifactCoordinate) { + throw new GradleException("No coordinates found in generateInfrastructureChangedCoordinatesMatrix output. Provide -Pcoordinate or -Pcoordinates to override.") + } + } + println("Selected artifact coordinate: ${artifactCoordinate}") + + def logsDir = layout.buildDirectory.dir("command-logs").get().asFile + logsDir.mkdirs() + + def runTestInfraAndCheck = { String coordinate, String gradleCmdParam, int parallelismParam, File logsDirParam, def layoutParam -> + def logFile = new File(logsDirParam, "testInfra-${sanitizeFileName(coordinate)}.log") + runGradleTaskAndCheck( + [gradleCmdParam, "testInfra", "-Pcoordinate=${coordinate}", "-Pparallelism=${parallelismParam}"], + "testInfra (${coordinate})", + logFile + ) + } + + runTestInfraAndCheck(artifactCoordinate, gradleCmd, parallelism, logsDir, layout) + def batchCoordinate = "1/64" + runTestInfraAndCheck(batchCoordinate, gradleCmd, parallelism, logsDir, layout) + + println("testAllInfra completed successfully.") + } +} + +/** + * Runs a Gradle task with logging and throws GradleException on non-zero exit. + * + * @param args Full command (gradle executable plus task and its arguments) + * @param label Human-readable label used in logs + * @param logFile Destination log file + * @return The exit code of the process (also throws on non-zero) + */ +def runGradleTaskAndCheck(List args, String label, File logFile) { + int exit = runLoggedCommand(args, label, logFile, project.rootDir) + if (exit != 0) { + throw new GradleException("${label} failed with exit code ${exit} (see ${logFile})") + } + return exit +} + +/** + * Runs generateInfrastructureChangedCoordinatesMatrix and returns the log file. + * + * @param gradleCmd Gradle executable path (e.g., "./gradlew" or "gradlew.bat") + * @param logsDir Directory where the log will be written + * @return The File pointing to the generated matrix log + */ +def runGenerateMatrixAndCheck(String gradleCmd, File logsDir) { + logsDir.mkdirs() + def logFile = new File(logsDir, "generateInfrastructureChangedCoordinatesMatrix.log") + runGradleTaskAndCheck( + [gradleCmd, "generateInfrastructureChangedCoordinatesMatrix"], + "generateInfrastructureChangedCoordinatesMatrix", + logFile + ) + return logFile +} + +/** + * Executes an external command, logs its output to both the console and a dedicated file, + * and returns the exit code. Prepends and appends custom headers and footers to the log + * indicating success or failure. + * + * Behavior: + * - Prints a header to both console and log file before running the command. + * - Ensures parent directories for the log file exist, and overwrites the file with the header. + * - Runs the provided command in a specified working directory, logging both stdout and stderr to the file. + * - Waits for the process to finish and captures its exit code. + * - Appends a footer line to both console and log to indicate SUCCESS or FAILURE, including the exit code if failed. + * - Returns the process exit code to the caller. + * + * @param args The command as a list of strings (executable plus arguments). + * @param label A label for this invocation, included in the header/footer. + * @param logFile File to which all output is logged (will be created/overwritten). + * @param workingDir Directory in which to execute the process. + * @return The exit code of the process. + */ +def runLoggedCommand(List args, String label, File logFile, File workingDir) { + def startTime = new Date().format("yyyy-MM-dd HH:mm:ss") + def header = "==== [${startTime}] BEGIN ${label} logged at ${logFile.absolutePath}" println(header) + if (logFile?.parentFile) { + logFile.parentFile.mkdirs() + } logFile.text = header + System.lineSeparator() def pb = new ProcessBuilder(args as String[]) @@ -131,27 +314,114 @@ ext.runLoggedCommand = { List args, String label, File logFile, File wor def proc = pb.start() int exit = proc.waitFor() + def endTime = new Date().format("yyyy-MM-dd HH:mm:ss") def footer = exit != 0 - ? "==== END ${label} FAILURE (exit ${exit})" - : "==== END ${label} SUCCESS" + ? "==== [${endTime}] END ${label} FAILURE (exit ${exit}) logged at ${logFile.absolutePath}" + : "==== [${endTime}] END ${label} SUCCESS logged at ${logFile.absolutePath}" + + if (exit != 0) { + println("Failure log for ${label}") + try { + if (logFile?.exists()) { + logFile.eachLine { println(it) } + } else { + println("(log file not found)") + } + } catch (Throwable e) { + println("Error reading ${logFile}: ${e.message}") + } + } + logFile.append(footer + System.lineSeparator()) println(footer) return exit } -static List testAllCommands(String gradleCmd, String target) { - def taskNames = [ - "checkstyle", - "spotlessCheck", - "checkMetadataFiles", - "fetchExistingLibrariesWithNewerVersions", - "test" - ] - return taskNames.collect { name -> - [name: name, args: [gradleCmd, name, "-Pcoordinates=${target}"]] +/** + * Runs a set of external commands concurrently using the provided ExecutorService, writing + * per-command logs under build/command-logs and aggregating failures. + * + * Behavior: + * - Submits each command to the pool via submit(Callable) and waits for all futures to complete. + * - Shuts down the pool and awaits termination for up to 1 hour. + * - On any non-zero exit, prints a summary and the contents of the failed logs, then throws a GradleException. + * - On success, prints a summary message indicating the logs directory. + * + * @param coordinate A label used for log file names and messages (e.g., an artifact coordinate or a batch like 1/64). + * @param commands List of maps representing commands to run; each entry must contain: + * - name: String task label (e.g., "checkstyle") + * - args: List full process arguments passed to ProcessBuilder + * @param pool ExecutorService used to run the commands concurrently; this method will call + * shutdown() and awaitTermination(...) on it. + * @throws GradleException if any command exits with a non-zero status. + */ +def runCommandsWithPool(ExecutorService pool, String coordinate, List commands) { + def logsDir = layout.buildDirectory.dir("command-logs").get().asFile + logsDir.mkdirs() + def fileSuffix = sanitizeFileName(coordinate) + def failures = Collections.synchronizedList(new ArrayList()) + def futures = [] + + commands.each { cmd -> + futures << pool.submit { + def label = "${cmd.name} (${coordinate})" + def logFile = new File(logsDir, "${cmd.name}-${fileSuffix}.log") + int exit = runLoggedCommand(cmd.args, label, logFile, project.rootDir) + if (exit != 0) { + failures.add([label: label, logFile: logFile, exit: exit]) + } + } + } + + futures.each { it.get() } + pool.shutdown() + pool.awaitTermination(1, TimeUnit.HOURS) + + if (!failures.isEmpty()) { + println("Some commands failed (coordinates=${coordinate}). Logs in: ${logsDir}") + failures.each { f -> + println(" - ${f.label} failed with exit code ${f.exit} (see ${f.logFile})") + } + println("==== BEGIN FAILED TASK LOGS (${coordinate})") + failures.each { f -> + println("---- LOG: ${f.logFile}") + try { + if (f.logFile?.exists()) { + f.logFile.eachLine { println(it) } + } else { + println("(log file not found)") + } + } catch (Throwable e) { + println("Error reading ${f.logFile}: ${e.message}") + } + } + println("==== END FAILED TASK LOGS (${coordinate})") + throw new GradleException("Failures encountered. See logs above. Logs directory: ${logsDir}") + } else { + println("All commands succeeded for coordinates=${coordinate}. Logs in: ${logsDir}") } } +/** + * Parses a project property as a positive integer, returning defaultValue if empty. + * Throws GradleException for invalid or non-positive values. + */ +def parsePositiveIntProp(String propertyName, int defaultValue) { + def prop = (project.findProperty(propertyName) ?: '').toString().trim() + int value = defaultValue + if (!prop.isEmpty()) { + try { + value = Integer.parseInt(prop) + } catch (NumberFormatException ignored) { + throw new GradleException("Invalid -P${propertyName}='${prop}': must be a positive integer.") + } + if (value <= 0) { + throw new GradleException("Invalid -P${propertyName}='${prop}': must be >= 1.") + } + } + return value +} + /* * Helper utilities for environment setup, and backup/restore * used by testMetadataGeneration. @@ -290,105 +560,6 @@ def testMetadataGeneration(String coordinate, String gradleCmd, File logsDir, St } } -tasks.register('testAllParallel') { t -> - t.group = "verification" - t.setDescription("For each target, run clean and pullAllowedDockerImages first (sequentially), then run style checks and individual testing stages (${testAllCommands('', '').collect { it.name }.join(', ')}) concurrently with isolated logs. Options: -Pcoordinates to specify a single coordinate or 1/64 for the pull step; -Pparallelism to control the number of concurrent tasks.") - - doLast { - def selectedArtifact = "org.postgresql:postgresql:42.7.3" - def selectedBatch = "1/64" - - def gradleCmd = System.getProperty("os.name").toLowerCase().contains("windows") ? "gradlew.bat" : "./gradlew" - def parallelismProp = (project.findProperty('parallelism') ?: '').toString().trim() - int parallelism = 4 - if (!parallelismProp.isEmpty()) { - try { - parallelism = Integer.parseInt(parallelismProp) - } catch (NumberFormatException ignored) { - throw new GradleException("Invalid -Pparallelism='${parallelismProp}': must be a positive integer.") - } - if (parallelism <= 0) { - throw new GradleException("Invalid -Pparallelism='${parallelismProp}': must be >= 1.") - } - } - println("Using parallelism=${parallelism} for testAllParallel.") - - def logsDir = layout.buildDirectory.dir("command-logs").get().asFile - logsDir.mkdirs() - - // Helper to run pull + command set for a given target (artifact or coordinates) - def runPhaseForCoordinate = { String coordinate -> - // Clean project before pulling Docker images for this coordinate - def cleanLogsDir = new File(project.rootDir, "command-logs-clean") - cleanLogsDir.mkdirs() - - def fileSuffix = coordinate.replaceAll(File.separator, "-") - def cleanLogFile = new File(cleanLogsDir, "clean-${fileSuffix}.log") - int cleanExit = project.ext.runLoggedCommand([gradleCmd, "clean", "-Pcoordinates=${coordinate}"], "clean (${coordinate})", cleanLogFile, project.rootDir) - if (cleanExit != 0) { - throw new GradleException("clean failed with exit code ${cleanExit} (see ${cleanLogFile})") - } - // Ensure logsDir exists after clean - logsDir.mkdirs() - - // Pull Docker images sequentially for this coordinate - def pullArgs = [gradleCmd, "pullAllowedDockerImages", "-Pcoordinates=${coordinate}"] - def pullLogFile = new File(logsDir, "pullAllowedDockerImages-${fileSuffix}.log") - int pullExit = project.ext.runLoggedCommand(pullArgs, "pullAllowedDockerImages (${coordinate})", pullLogFile, project.rootDir) - if (pullExit != 0) { - throw new GradleException("pullAllowedDockerImages failed with exit code ${pullExit} (see ${pullLogFile})") - } - - def commands = testAllCommands(gradleCmd, coordinate) - int effectiveParallelism = Math.min(parallelism, commands.size()) - def pool = Executors.newFixedThreadPool(effectiveParallelism) - def failures = Collections.synchronizedList(new ArrayList()) - def futures = [] - - commands.each { cmd -> - futures << pool.submit { - def label = "${cmd.name} (${coordinate})" - def logFile = new File(logsDir, "${cmd.name}-${fileSuffix}.log") - int exit = project.ext.runLoggedCommand(cmd.args, label, logFile, project.rootDir) - if (exit != 0) { - failures.add([label: label, logFile: logFile, exit: exit]) - } - } - } - - futures.each { it.get() } - pool.shutdown() - pool.awaitTermination(1, TimeUnit.HOURS) - - if (!failures.isEmpty()) { - println("Some commands failed (coordinates=${coordinate}). Logs in: ${logsDir}") - failures.each { f -> - println(" - ${f.label} failed with exit code ${f.exit} (see ${f.logFile})") - } - println("==== BEGIN FAILED TASK LOGS (${coordinate})") - failures.each { f -> - println("---- LOG: ${f.logFile}") - try { - if (f.logFile?.exists()) { - f.logFile.eachLine { println(it) } - } else { - println("(log file not found)") - } - } catch (Throwable e) { - println("Error reading ${f.logFile}: ${e.message}") - } - } - println("==== END FAILED TASK LOGS (${coordinate})") - throw new GradleException("Failures encountered. See logs above. Logs directory: ${logsDir}") - } else { - println("All commands succeeded for coordinates=${coordinate}. Logs in: ${logsDir}") - } - } - def newVersionForFix = "42.7.4" - - // Run parallel phases sequentially: first the selectedArtifact, then the selectedBatch - runPhaseForCoordinate(selectedArtifact) - testMetadataGeneration(selectedArtifact, gradleCmd, logsDir, newVersionForFix) - runPhaseForCoordinate(selectedBatch) - } +private static String sanitizeFileName(String coordinate) { + coordinate.replaceAll(File.separator, "-").replaceAll(":", "-") } diff --git a/docs/DEVELOPING.md b/docs/DEVELOPING.md index 848c6b0be..83eeca487 100644 --- a/docs/DEVELOPING.md +++ b/docs/DEVELOPING.md @@ -23,7 +23,7 @@ Tip: When debugging locally, add `--stacktrace` for better error output. ### End-to-end testing before the commit ```console -./gradlew testAllParallel --stacktrace +./gradlew testAllInfra --stacktrace ``` ### Style and formatting diff --git a/metadata/org.jooq/jooq/index.json b/metadata/org.jooq/jooq/index.json index 85640df18..639531374 100644 --- a/metadata/org.jooq/jooq/index.json +++ b/metadata/org.jooq/jooq/index.json @@ -72,17 +72,7 @@ "3.19.25", "3.19.26", "3.19.27", - "3.19.28", - "3.20.1", - "3.20.2", - "3.20.3", - "3.20.4", - "3.20.5", - "3.20.6", - "3.20.7", - "3.20.8", - "3.20.9", - "3.20.10" + "3.19.28" ] }, { diff --git a/tests/src/ch.qos.logback/logback-classic/1.4.1/generate-metadata.sh b/tests/src/ch.qos.logback/logback-classic/1.4.1/generate-metadata.sh index 5f9204fb5..06eb0a437 100644 --- a/tests/src/ch.qos.logback/logback-classic/1.4.1/generate-metadata.sh +++ b/tests/src/ch.qos.logback/logback-classic/1.4.1/generate-metadata.sh @@ -1,4 +1,8 @@ #!/usr/bin/env bash +# Copyright and related rights waived via CC0 +# +# You should have received a copy of the CC0 legalcode along with this +# work. If not, see . ./gradlew clean ./gradlew test --tests "org.graalvm.logback.LogbackTests" -Pagent diff --git a/tests/src/org.flywaydb/flyway-core/10.20.0/generate-metadata.sh b/tests/src/org.flywaydb/flyway-core/10.20.0/generate-metadata.sh index 3be262b11..9b6d80aaf 100755 --- a/tests/src/org.flywaydb/flyway-core/10.20.0/generate-metadata.sh +++ b/tests/src/org.flywaydb/flyway-core/10.20.0/generate-metadata.sh @@ -1,4 +1,8 @@ #!/usr/bin/env bash +# Copyright and related rights waived via CC0 +# +# You should have received a copy of the CC0 legalcode along with this +# work. If not, see . ./gradlew clean ./gradlew test --tests "flyway.FlywayTests" -Pagent diff --git a/tests/src/org.flywaydb/flyway-core/10.20.1/generate-metadata.sh b/tests/src/org.flywaydb/flyway-core/10.20.1/generate-metadata.sh index 3be262b11..9b6d80aaf 100755 --- a/tests/src/org.flywaydb/flyway-core/10.20.1/generate-metadata.sh +++ b/tests/src/org.flywaydb/flyway-core/10.20.1/generate-metadata.sh @@ -1,4 +1,8 @@ #!/usr/bin/env bash +# Copyright and related rights waived via CC0 +# +# You should have received a copy of the CC0 legalcode along with this +# work. If not, see . ./gradlew clean ./gradlew test --tests "flyway.FlywayTests" -Pagent diff --git a/tests/src/org.flywaydb/flyway-core/11.14.1/generate-metadata.sh b/tests/src/org.flywaydb/flyway-core/11.14.1/generate-metadata.sh index 3be262b11..9b6d80aaf 100755 --- a/tests/src/org.flywaydb/flyway-core/11.14.1/generate-metadata.sh +++ b/tests/src/org.flywaydb/flyway-core/11.14.1/generate-metadata.sh @@ -1,4 +1,8 @@ #!/usr/bin/env bash +# Copyright and related rights waived via CC0 +# +# You should have received a copy of the CC0 legalcode along with this +# work. If not, see . ./gradlew clean ./gradlew test --tests "flyway.FlywayTests" -Pagent diff --git a/tests/src/org.hibernate.orm/hibernate-core/6.2.0.Final/generate-metadata.sh b/tests/src/org.hibernate.orm/hibernate-core/6.2.0.Final/generate-metadata.sh index caad58eed..a564104da 100755 --- a/tests/src/org.hibernate.orm/hibernate-core/6.2.0.Final/generate-metadata.sh +++ b/tests/src/org.hibernate.orm/hibernate-core/6.2.0.Final/generate-metadata.sh @@ -1,4 +1,8 @@ #!/usr/bin/env bash +# Copyright and related rights waived via CC0 +# +# You should have received a copy of the CC0 legalcode along with this +# work. If not, see . ../../../../../gradlew deleteGeneratedMetadata ../../../../../gradlew test --tests "org_hibernate_orm.hibernate_core.H2DialectHibernateTest" -Pagent metadataCopy diff --git a/tests/src/org.hibernate.orm/hibernate-core/6.5.0.Final/generate-metadata.sh b/tests/src/org.hibernate.orm/hibernate-core/6.5.0.Final/generate-metadata.sh index c3256f6f1..324b348e9 100755 --- a/tests/src/org.hibernate.orm/hibernate-core/6.5.0.Final/generate-metadata.sh +++ b/tests/src/org.hibernate.orm/hibernate-core/6.5.0.Final/generate-metadata.sh @@ -1,4 +1,8 @@ #!/usr/bin/env bash +# Copyright and related rights waived via CC0 +# +# You should have received a copy of the CC0 legalcode along with this +# work. If not, see . ../../../../../gradlew deleteGeneratedMetadata ../../../../../gradlew test --tests "org_hibernate_orm.hibernate_core.H2DialectHibernateTest" -Pagent metadataCopy diff --git a/tests/src/org.hibernate.orm/hibernate-core/6.6.0.Final/generate-metadata.sh b/tests/src/org.hibernate.orm/hibernate-core/6.6.0.Final/generate-metadata.sh index c3256f6f1..324b348e9 100755 --- a/tests/src/org.hibernate.orm/hibernate-core/6.6.0.Final/generate-metadata.sh +++ b/tests/src/org.hibernate.orm/hibernate-core/6.6.0.Final/generate-metadata.sh @@ -1,4 +1,8 @@ #!/usr/bin/env bash +# Copyright and related rights waived via CC0 +# +# You should have received a copy of the CC0 legalcode along with this +# work. If not, see . ../../../../../gradlew deleteGeneratedMetadata ../../../../../gradlew test --tests "org_hibernate_orm.hibernate_core.H2DialectHibernateTest" -Pagent metadataCopy diff --git a/tests/src/org.hibernate.orm/hibernate-core/7.0.0.Final/generate-metadata.sh b/tests/src/org.hibernate.orm/hibernate-core/7.0.0.Final/generate-metadata.sh index 9398996ae..08bbc0332 100755 --- a/tests/src/org.hibernate.orm/hibernate-core/7.0.0.Final/generate-metadata.sh +++ b/tests/src/org.hibernate.orm/hibernate-core/7.0.0.Final/generate-metadata.sh @@ -1,4 +1,8 @@ #!/usr/bin/env bash +# Copyright and related rights waived via CC0 +# +# You should have received a copy of the CC0 legalcode along with this +# work. If not, see . ../../../../../gradlew deleteGeneratedMetadata ../../../../../gradlew test --tests "org_hibernate_orm.hibernate_core.H2DialectHibernateTest" -Pagent metadataCopy diff --git a/tests/src/org.hibernate.orm/hibernate-core/7.1.0.Final/generate-metadata.sh b/tests/src/org.hibernate.orm/hibernate-core/7.1.0.Final/generate-metadata.sh index 9398996ae..08bbc0332 100755 --- a/tests/src/org.hibernate.orm/hibernate-core/7.1.0.Final/generate-metadata.sh +++ b/tests/src/org.hibernate.orm/hibernate-core/7.1.0.Final/generate-metadata.sh @@ -1,4 +1,8 @@ #!/usr/bin/env bash +# Copyright and related rights waived via CC0 +# +# You should have received a copy of the CC0 legalcode along with this +# work. If not, see . ../../../../../gradlew deleteGeneratedMetadata ../../../../../gradlew test --tests "org_hibernate_orm.hibernate_core.H2DialectHibernateTest" -Pagent metadataCopy diff --git a/tests/src/org.hibernate/hibernate-core/5.6.14.Final/generate-metadata.sh b/tests/src/org.hibernate/hibernate-core/5.6.14.Final/generate-metadata.sh index 35c2a8a4a..fde9cc187 100644 --- a/tests/src/org.hibernate/hibernate-core/5.6.14.Final/generate-metadata.sh +++ b/tests/src/org.hibernate/hibernate-core/5.6.14.Final/generate-metadata.sh @@ -1,4 +1,8 @@ #!/usr/bin/env bash +# Copyright and related rights waived via CC0 +# +# You should have received a copy of the CC0 legalcode along with this +# work. If not, see . ./gradlew deleteGeneratedMetadata ./gradlew test --tests "org_hibernate.hibernate_core.H2DialectHibernateTest" -Pagent metadataCopy diff --git a/tests/src/org.hibernate/hibernate-core/6.0.0.Final/generate-metadata.sh b/tests/src/org.hibernate/hibernate-core/6.0.0.Final/generate-metadata.sh index 35c2a8a4a..fde9cc187 100644 --- a/tests/src/org.hibernate/hibernate-core/6.0.0.Final/generate-metadata.sh +++ b/tests/src/org.hibernate/hibernate-core/6.0.0.Final/generate-metadata.sh @@ -1,4 +1,8 @@ #!/usr/bin/env bash +# Copyright and related rights waived via CC0 +# +# You should have received a copy of the CC0 legalcode along with this +# work. If not, see . ./gradlew deleteGeneratedMetadata ./gradlew test --tests "org_hibernate.hibernate_core.H2DialectHibernateTest" -Pagent metadataCopy diff --git a/tests/src/org.jetbrains.kotlin/kotlin-reflect/1.7.10/settings.gradle b/tests/src/org.jetbrains.kotlin/kotlin-reflect/1.7.10/settings.gradle index 3a5be59a3..fc2131573 100644 --- a/tests/src/org.jetbrains.kotlin/kotlin-reflect/1.7.10/settings.gradle +++ b/tests/src/org.jetbrains.kotlin/kotlin-reflect/1.7.10/settings.gradle @@ -4,7 +4,6 @@ * 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"), diff --git a/tests/src/org.jetbrains.kotlin/kotlin-stdlib/1.7.10/settings.gradle b/tests/src/org.jetbrains.kotlin/kotlin-stdlib/1.7.10/settings.gradle index aaea2d168..d422b2427 100644 --- a/tests/src/org.jetbrains.kotlin/kotlin-stdlib/1.7.10/settings.gradle +++ b/tests/src/org.jetbrains.kotlin/kotlin-stdlib/1.7.10/settings.gradle @@ -4,7 +4,6 @@ * 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"), diff --git a/tests/src/org.jooq/jooq/3.17.7/generate-metadata.sh b/tests/src/org.jooq/jooq/3.17.7/generate-metadata.sh index 8a608f0c5..9b9e4dc22 100644 --- a/tests/src/org.jooq/jooq/3.17.7/generate-metadata.sh +++ b/tests/src/org.jooq/jooq/3.17.7/generate-metadata.sh @@ -1,4 +1,8 @@ #!/usr/bin/env bash +# Copyright and related rights waived via CC0 +# +# You should have received a copy of the CC0 legalcode along with this +# work. If not, see . ./gradlew clean ./gradlew test --tests "org_jooq.jooq.JooqTest" -Pagent diff --git a/tests/src/org.jooq/jooq/3.18.2/generate-metadata.sh b/tests/src/org.jooq/jooq/3.18.2/generate-metadata.sh index 8a608f0c5..9b9e4dc22 100644 --- a/tests/src/org.jooq/jooq/3.18.2/generate-metadata.sh +++ b/tests/src/org.jooq/jooq/3.18.2/generate-metadata.sh @@ -1,4 +1,8 @@ #!/usr/bin/env bash +# Copyright and related rights waived via CC0 +# +# You should have received a copy of the CC0 legalcode along with this +# work. If not, see . ./gradlew clean ./gradlew test --tests "org_jooq.jooq.JooqTest" -Pagent diff --git a/tests/src/org.liquibase/liquibase-core/5.0.1/generate-metadata.sh b/tests/src/org.liquibase/liquibase-core/5.0.1/generate-metadata.sh index 80d37af40..f7c571674 100755 --- a/tests/src/org.liquibase/liquibase-core/5.0.1/generate-metadata.sh +++ b/tests/src/org.liquibase/liquibase-core/5.0.1/generate-metadata.sh @@ -1,4 +1,8 @@ #!/usr/bin/env bash +# Copyright and related rights waived via CC0 +# +# You should have received a copy of the CC0 legalcode along with this +# work. If not, see . ./gradlew clean ./gradlew test --tests "org_liquibase.liquibase_core.LiquibaseCoreTest" -Pagent diff --git a/tests/tck-build-logic/build.gradle b/tests/tck-build-logic/build.gradle index 167fae448..c39b98d6c 100644 --- a/tests/tck-build-logic/build.gradle +++ b/tests/tck-build-logic/build.gradle @@ -4,7 +4,6 @@ * You should have received a copy of the CC0 legalcode along with this * work. If not, see . */ - plugins { id 'groovy-gradle-plugin' id 'maven-publish' diff --git a/tests/tck-build-logic/settings.gradle b/tests/tck-build-logic/settings.gradle index a4fd9bf67..4d93384ed 100644 --- a/tests/tck-build-logic/settings.gradle +++ b/tests/tck-build-logic/settings.gradle @@ -4,7 +4,6 @@ * You should have received a copy of the CC0 legalcode along with this * work. If not, see . */ - pluginManagement { repositories { mavenCentral() diff --git a/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-base.gradle b/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-base.gradle index 036be676e..810ebade5 100644 --- a/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-base.gradle +++ b/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-base.gradle @@ -1,3 +1,9 @@ +/* + * Copyright and related rights waived via CC0 + * + * You should have received a copy of the CC0 legalcode along with this + * work. If not, see . + */ import org.graalvm.internal.tck.harness.TckExtension TckExtension testUtils = project.extensions.create("tck", TckExtension, project) 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 6c62339e5..05a9eaaf9 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 @@ -4,7 +4,6 @@ * You should have received a copy of the CC0 legalcode along with this * work. If not, see . */ - plugins { id 'org.graalvm.internal.tck-base' } @@ -29,6 +28,7 @@ import org.graalvm.internal.tck.harness.tasks.NativeTestCompileInvocationTask import org.graalvm.internal.tck.harness.tasks.FetchExistingLibrariesWithNewerVersionsTask import org.gradle.util.internal.VersionNumber import org.graalvm.internal.tck.harness.tasks.ComputeAndPullAllowedDockerImagesTask +import org.graalvm.internal.tck.harness.tasks.CheckMetadataFilesAllTask import org.graalvm.internal.tck.utils.CoordinateUtils import org.graalvm.internal.tck.utils.MetadataGenerationUtils @@ -63,83 +63,66 @@ if (CoordinateUtils.isFractionalBatch(coordinateFilter)) { matchingCoordinates = matchingCoordinates.findAll { !it.startsWith("samples:") } -// gradle checkstyle -Pcoordinates= -Provider checkstyle = tasks.register("checkstyle") { task -> + // gradle checkstyle -Pcoordinates= +tasks.register("checkstyle", CheckstyleInvocationTask.class) { task -> task.setDescription("Runs checkstyle on all subprojects") task.setGroup(JavaBasePlugin.VERIFICATION_GROUP) -} as Provider +} // gradle compileTestJava -Pcoordinates= -Provider compileTestJava = tasks.register("compileTestJava") { task -> +tasks.register("compileTestJava", CompileTestJavaInvocationTask.class) { task -> task.setDescription("Compiles sources (javac) for all subprojects") task.setGroup(LifecycleBasePlugin.BUILD_GROUP) -} as Provider +} -// gradle javaTest -Pcoordinates= -Provider javaTest = tasks.register("javaTest") { task -> + // gradle javaTest -Pcoordinates= +tasks.register("javaTest", JavaTestInvocationTask.class) { task -> task.setDescription("Runs JVM tests (Gradle 'test') for all subprojects") task.setGroup(JavaBasePlugin.VERIFICATION_GROUP) -} as Provider +} // gradle nativeTestCompile -Pcoordinates= -Provider nativeTestCompile = tasks.register("nativeTestCompile") { task -> +tasks.register("nativeTestCompile", NativeTestCompileInvocationTask.class) { task -> task.setDescription("Compiles native tests (nativeTestCompile) for all subprojects") task.setGroup(LifecycleBasePlugin.BUILD_GROUP) -} as Provider - -// gradle checkMetadataFiles -Pcoordinates= -Provider checkMetadataFiles = tasks.register("checkMetadataFiles") { task -> - task.setDescription("Checks content of metadata files for matching coordinates") - task.setGroup(METADATA_GROUP) -} as Provider - -tasks.named("check").configure { - dependsOn(checkstyle) } - - // Here we want to configure all test and checkstyle tasks for all filtered subprojects -// Extracted helper to register per-coordinate tasks and wire them to aggregate tasks. -def registerPerCoordinateTask = { String coordinates, Object aggregate, String baseName, Class taskClass, boolean passCtorArg = true, Closure customConfigure = null -> - String taskName = generateTaskName(baseName, coordinates) - if ((!tasks.getNames().contains(taskName))) { - if (passCtorArg) { - tasks.register(taskName, taskClass, coordinates) - } else { - tasks.register(taskName, taskClass) { t -> - if (customConfigure != null) { - customConfigure.call(t) - } - } - } + // gradle clean -Pcoordinates= +if (tasks.findByName("clean") == null) { + tasks.register("clean", CleanInvocationTask.class) { t -> + t.setDescription("Cleans all matching subprojects") + t.setGroup(LifecycleBasePlugin.BUILD_GROUP) } - if (aggregate != null) { - if (aggregate instanceof org.gradle.api.provider.Provider) { - aggregate.configure { - dependsOn(taskName) - } - } else { - tasks.named(aggregate.toString()).configure { - dependsOn(taskName) - } - } +} else { + tasks.register("tckClean", CleanInvocationTask.class) { t -> + t.setDescription("Cleans all matching subprojects (TCK)") + t.setGroup(LifecycleBasePlugin.BUILD_GROUP) } + tasks.named("clean").configure { it.dependsOn("tckClean") } } -for (String coordinates in matchingCoordinates) { - registerPerCoordinateTask(coordinates, checkstyle, "checkstyle", CheckstyleInvocationTask, true) - registerPerCoordinateTask(coordinates, "clean", "clean", CleanInvocationTask, true) - registerPerCoordinateTask(coordinates, compileTestJava, "compileTestJava", CompileTestJavaInvocationTask, true) - registerPerCoordinateTask(coordinates, javaTest, "javaTest", JavaTestInvocationTask, true) - registerPerCoordinateTask(coordinates, nativeTestCompile, "nativeTestCompile", NativeTestCompileInvocationTask, true) - // No aggregate wiring for per-coordinate docker pulls; they are invoked by dedicated workflows. - registerPerCoordinateTask(coordinates, null, "pullAllowedDockerImages", DockerTask.class, false) { t -> - t.setCoordinates(coordinates) + // gradle test -Pcoordinates= +if (tasks.findByName("test") == null) { + tasks.register("test", TestInvocationTask.class) { t -> + t.setDescription("Runs Native Image tests for all matching coordinates") + t.setGroup(JavaBasePlugin.VERIFICATION_GROUP) } - registerPerCoordinateTask(coordinates, checkMetadataFiles, "checkMetadataFiles", MetadataFilesCheckerTask.class, false) { t -> - t.setCoordinates(coordinates) +} else { + tasks.register("tckTest", TestInvocationTask.class) { t -> + t.setDescription("Runs Native Image tests for all matching coordinates (TCK)") + t.setGroup(JavaBasePlugin.VERIFICATION_GROUP) } - registerPerCoordinateTask(coordinates, "test", "test", TestInvocationTask, true) + tasks.named("test").configure { it.dependsOn("tckTest") } +} + + // gradle checkMetadataFiles -Pcoordinates= +tasks.register("checkMetadataFiles", CheckMetadataFilesAllTask.class) { task -> + task.setDescription("Checks content of metadata files for matching coordinates") + task.setGroup(METADATA_GROUP) +} + +tasks.named("check").configure { + dependsOn("checkstyle") } // gradle diff -PbaseCommit= -PnewCommit= @@ -158,15 +141,12 @@ List diffCoordinates = new ArrayList<>() if (project.hasProperty("baseCommit")) { String baseCommit = project.findProperty("baseCommit") String newCommit = Objects.requireNonNullElse(project.findProperty("newCommit"), "HEAD") - for (def coordinates in tck.diffCoordinates(baseCommit, newCommit)) { - diffCoordinates.add(coordinates) - String taskTaskName = generateTaskName("test", coordinates) - if ((!tasks.getNames().contains(taskTaskName))) { - tasks.register(taskTaskName, TestInvocationTask, coordinates) - } - diff.configure { - dependsOn(taskTaskName) - } + diffCoordinates.addAll(tck.diffCoordinates(baseCommit, newCommit)) + tasks.register("testDiff", TestInvocationTask.class) { t -> + t.setCoordinatesOverride(diffCoordinates) + } + diff.configure { + dependsOn("testDiff") } } @@ -251,7 +231,7 @@ Provider generateInfrastructureChangedCoordinatesMatrix = tasks.register(" List selected = [] boolean infraChanged = true // Pre-selected modules; keep stable for infra verification - List preselectedModules = [ + List preselectedCoordinates = [ 'ch.qos.logback:logback-classic', 'org.bouncycastle:bcpkix-jdk18on', 'io.netty:netty-common', @@ -263,7 +243,7 @@ Provider generateInfrastructureChangedCoordinatesMatrix = tasks.register(" 'io.undertow:undertow-core', 'com.hazelcast:hazelcast' ] - preselectedModules.each { module -> + preselectedCoordinates.each { module -> List matches = tck.getMatchingCoordinates(module) if (!matches.isEmpty()) { def withVersions = matches.collect { c -> diff --git a/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-settings.settings.gradle b/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-settings.settings.gradle index d1fb19b55..30f0c763d 100644 --- a/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-settings.settings.gradle +++ b/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck-settings.settings.gradle @@ -4,7 +4,6 @@ * You should have received a copy of the CC0 legalcode along with this * work. If not, see . */ - pluginManagement { repositories { mavenCentral() diff --git a/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck.gradle b/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck.gradle index fc7da952b..d1144581c 100644 --- a/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck.gradle +++ b/tests/tck-build-logic/src/main/groovy/org.graalvm.internal.tck.gradle @@ -4,8 +4,6 @@ * You should have received a copy of the CC0 legalcode along with this * work. If not, see . */ - - import groovy.json.JsonSlurper plugins { 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 new file mode 100644 index 000000000..dd5079bd1 --- /dev/null +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/AllCoordinatesExecTask.groovy @@ -0,0 +1,138 @@ +/* + * 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/CheckMetadataFilesAllTask.groovy b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CheckMetadataFilesAllTask.groovy new file mode 100644 index 000000000..3e4d13a67 --- /dev/null +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CheckMetadataFilesAllTask.groovy @@ -0,0 +1,53 @@ +/* + * 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/CheckstyleInvocationTask.groovy b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CheckstyleInvocationTask.groovy index 853d7eddb..729bb8521 100644 --- 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 @@ -13,21 +13,15 @@ import javax.inject.Inject * Task that is used to run checkstyle task on subprojects. */ @SuppressWarnings("unused") -abstract class CheckstyleInvocationTask extends AbstractSubprojectTask { +abstract class CheckstyleInvocationTask extends AllCoordinatesExecTask { - @Inject - CheckstyleInvocationTask(String coordinates) { - super(coordinates) - } - @Override - @Input - List getCommand() { + List commandFor(String coordinates) { return [tckExtension.repoRoot.get().asFile.toPath().resolve("gradlew").toString(), "checkstyle"] } @Override - protected String getErrorMessage(int exitCode) { + 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/CleanInvocationTask.groovy b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CleanInvocationTask.groovy index 6b177b29b..398a107f1 100644 --- 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 @@ -11,21 +11,16 @@ import org.gradle.api.tasks.Input import javax.inject.Inject @SuppressWarnings('unused') -abstract class CleanInvocationTask extends AbstractSubprojectTask { +abstract class CleanInvocationTask extends AllCoordinatesExecTask { - @Inject - CleanInvocationTask(String coordinates) { - super(coordinates) - } @Override - @Input - List getCommand() { + List commandFor(String coordinates) { return [tckExtension.repoRoot.get().asFile.toPath().resolve("gradlew").toString(), "clean"] } @Override - protected String getErrorMessage(int exitCode) { + 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/CompileTestJavaInvocationTask.groovy b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CompileTestJavaInvocationTask.groovy index c72274192..4f6c04a55 100644 --- 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 @@ -6,29 +6,19 @@ */ package org.graalvm.internal.tck.harness.tasks -import org.gradle.api.tasks.Input - -import javax.inject.Inject /** * Task that is used to compile subprojects with javac. */ @SuppressWarnings("unused") -abstract class CompileTestJavaInvocationTask extends AbstractSubprojectTask { - - @Inject - CompileTestJavaInvocationTask(String coordinates) { - super(coordinates) - } +abstract class CompileTestJavaInvocationTask extends AllCoordinatesExecTask { @Override - @Input - List getCommand() { + List commandFor(String coordinates) { return [tckExtension.repoRoot.get().asFile.toPath().resolve("gradlew").toString(), "compileTestJava"] } @Override - protected String getErrorMessage(int exitCode) { + 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/CoordinatesAwareTask.groovy b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CoordinatesAwareTask.groovy new file mode 100644 index 000000000..a4f3a6c4d --- /dev/null +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/CoordinatesAwareTask.groovy @@ -0,0 +1,74 @@ +/* + * 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/JavaTestInvocationTask.groovy b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/JavaTestInvocationTask.groovy index 5c4a754a8..5aff54dda 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.groovy @@ -6,29 +6,19 @@ */ package org.graalvm.internal.tck.harness.tasks -import org.gradle.api.tasks.Input - -import javax.inject.Inject /** * Task that is used to run JVM tests (Gradle 'test') on subprojects. */ @SuppressWarnings("unused") -abstract class JavaTestInvocationTask extends AbstractSubprojectTask { - - @Inject - JavaTestInvocationTask(String coordinates) { - super(coordinates) - } +abstract class JavaTestInvocationTask extends AllCoordinatesExecTask { @Override - @Input - List getCommand() { + List commandFor(String coordinates) { return [tckExtension.repoRoot.get().asFile.toPath().resolve("gradlew").toString(), "test"] } @Override - protected String getErrorMessage(int exitCode) { + protected String errorMessageFor(String coordinates, int exitCode) { "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.groovy index 3b5277bd4..80c3e5f91 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.groovy @@ -6,29 +6,19 @@ */ package org.graalvm.internal.tck.harness.tasks -import org.gradle.api.tasks.Input - -import javax.inject.Inject /** * Task that is used to compile native tests (Gradle 'nativeTestCompile') on subprojects. */ @SuppressWarnings("unused") -abstract class NativeTestCompileInvocationTask extends AbstractSubprojectTask { - - @Inject - NativeTestCompileInvocationTask(String coordinates) { - super(coordinates) - } +abstract class NativeTestCompileInvocationTask extends AllCoordinatesExecTask { @Override - @Input - List getCommand() { + List commandFor(String coordinates) { return [tckExtension.repoRoot.get().asFile.toPath().resolve("gradlew").toString(), "nativeTestCompile"] } @Override - protected String getErrorMessage(int exitCode) { + protected String errorMessageFor(String coordinates, int exitCode) { "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.groovy new file mode 100644 index 000000000..ddf1b9090 --- /dev/null +++ b/tests/tck-build-logic/src/main/groovy/org/graalvm/internal/tck/harness/tasks/SingleCoordinateTask.groovy @@ -0,0 +1,31 @@ +/* + * 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 + +/** + * 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 { + + /** + * Resolves to exactly one coordinate or fails with a helpful error. + */ + protected String resolveSingleCoordinate() { + List coords = resolveCoordinates() + if (coords.isEmpty()) { + 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.") + } + 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 index 2f7c20ef5..8865fce11 100644 --- 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 @@ -7,44 +7,26 @@ package org.graalvm.internal.tck.harness.tasks import org.gradle.api.provider.ProviderFactory -import org.gradle.api.tasks.Input import javax.inject.Inject 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.readIndexFile +import static org.graalvm.internal.tck.Utils.splitCoordinates /** - * Task that is used to start subproject tests. + * 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 AbstractSubprojectTask { - - @Input - String coordinates - - @Inject - TestInvocationTask(String coordinates) { - super(coordinates) - def me = this - project.tasks.named("check") { - dependsOn(me) - } - this.coordinates = coordinates - } +abstract class TestInvocationTask extends AllCoordinatesExecTask { @Inject - abstract ProviderFactory getProviders(); + abstract ProviderFactory getProviders() - /** - * Fetches arguments for test invocation from index.json file (if present). - * @param coordinates - * @return list of processed arguments - */ @Override - @Input - List getCommand() { + 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()) { @@ -88,12 +70,12 @@ abstract class TestInvocationTask extends AbstractSubprojectTask { } @Override - protected String getErrorMessage(int exitCode) { + protected String errorMessageFor(String coordinates, int exitCode) { "Test for ${coordinates} failed with exit code ${exitCode}." } @Override - protected void beforeExecute() { + protected void beforeEach(String coordinates, List command) { getLogger().lifecycle("====================") getLogger().lifecycle("Testing library: {}", coordinates) getLogger().lifecycle("Command: `{}`", String.join(" ", command)) @@ -102,10 +84,9 @@ abstract class TestInvocationTask extends AbstractSubprojectTask { } @Override - protected void afterExecute() { + 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 993f1c007..85c781565 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 @@ -20,7 +20,7 @@ import java.util.stream.Collectors; /** - * Checks content of config files for a new library. + * Checks content of metadata files for a new library. *

* Run with {@code gradle checkMetadataFiles -Pcoordinates com.example:library:1.0.0}. */ @@ -54,10 +54,16 @@ void setCoordinates(String coords) { } { - // Prefer task option, fallback to -Pcoordinates + // Prefer task option, fallback to -Pcoordinates when it looks like a single coordinate (group:artifact:version) String prop = (String) getProject().findProperty("coordinates"); if (prop != null && !getIndexFile().isPresent()) { - extractCoordinates(prop); + // Skip when using fractional batches (k/n) or 'all'. Only parse exact group:artifact:version. + if (!CoordinateUtils.isFractionalBatch(prop)) { + String[] parts = prop.split(":", -1); + if (parts.length == 3) { + extractCoordinates(prop); + } + } } }