From 895a543e045c8098dfb1f93d26cf9ad749ee812f Mon Sep 17 00:00:00 2001 From: Manfred Hanke Date: Sun, 13 Sep 2026 13:35:21 +0200 Subject: [PATCH 1/5] refactor archunit-maven-test to lazy task registration Replace the deprecated `task` DSL, eager `tasks.create` calls and `tasks.findAll` wiring with lazy `tasks.register`, keeping the returned `TaskProvider`s in variables and inlining the task names that were previously built as separate string locals. `dependsOn`, `onlyIf`, `finalizedBy` and `mustRunAfter` are now set in `configure` blocks, the aggregate `runMavenTest` depends on the per-config variants via `tasks.named`, and the JDK log line is attached through `tasks.configureEach` instead of eagerly realizing every task during configuration. Signed-off-by: Manfred Hanke --- archunit-maven-test/build.gradle | 67 +++++++++++++++++--------------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/archunit-maven-test/build.gradle b/archunit-maven-test/build.gradle index d16710170..b36372c0f 100644 --- a/archunit-maven-test/build.gradle +++ b/archunit-maven-test/build.gradle @@ -85,7 +85,7 @@ clean { } } -task initializeMavenTest { +def initializeMavenTest = tasks.register('initializeMavenTest') { doLast { verificationResultArchivePath.with { deleteDir() @@ -102,16 +102,12 @@ def mavenCommand = { String... params -> } def addMavenTest = { IntegrationTestConfig config -> - String prepareMavenTest = "prepareMavenTest${config.suffix}" - String executeRules = "executeRules${config.suffix}" - String verifyRules = "verifyRules${config.suffix}" - String cleanUpMavenTest = "cleanUpMavenTest${config.suffix}" - String runMavenTest = "runMavenTest${config.suffix}" def mavenSurefirePlugin = libs.mavenSurefirePlugin.get() def mavenCompilerPlugin = libs.mavenCompilerPlugin.get() return { - tasks.create(name: prepareMavenTest, dependsOn: initializeMavenTest) { + def prepareMavenTest = tasks.register("prepareMavenTest${config.suffix}") { + dependsOn initializeMavenTest doLast { mavenPom.delete() Files.copy(mavenPomTemplate.toPath(), mavenPom.toPath()) @@ -156,23 +152,21 @@ def addMavenTest = { IntegrationTestConfig config -> } } - def mavenExec = { closure -> - return { - workingDir projectDir - environment JAVA_HOME: jdkHome - delegate.with(closure) - } - } - - tasks.create(name: executeRules, type: Exec, dependsOn: prepareMavenTest).configure mavenExec { + def executeRules = tasks.register("executeRules${config.suffix}", Exec) { + dependsOn prepareMavenTest + workingDir projectDir + environment JAVA_HOME: jdkHome commandLine mavenCommand('clean', 'test', '-P', 'execute-rules') } - tasks.create(name: verifyRules, type: Exec, dependsOn: executeRules).configure mavenExec { + def verifyRules = tasks.register("verifyRules${config.suffix}", Exec) { + dependsOn executeRules + workingDir projectDir + environment JAVA_HOME: jdkHome commandLine mavenCommand('surefire:test', '-P', 'verify-rules') } - tasks.create(name: cleanUpMavenTest) { + def cleanUpMavenTest = tasks.register("cleanUpMavenTest${config.suffix}") { doFirst { copy { from verificationResultOriginPath @@ -188,22 +182,27 @@ def addMavenTest = { IntegrationTestConfig config -> } // Run cleanUp even if task fails - [tasks[prepareMavenTest], tasks[verifyRules], tasks[executeRules]]*.finalizedBy tasks[cleanUpMavenTest] + [prepareMavenTest, verifyRules, executeRules].each { taskProvider -> + taskProvider.configure { finalizedBy cleanUpMavenTest } + } - tasks.create(name: runMavenTest, dependsOn: [prepareMavenTest, executeRules, verifyRules, cleanUpMavenTest]) + def runMavenTest = tasks.register("runMavenTest${config.suffix}") { + dependsOn prepareMavenTest, executeRules, verifyRules, cleanUpMavenTest + } - [prepareMavenTest, executeRules, verifyRules, cleanUpMavenTest, runMavenTest].each { taskName -> - tasks[taskName].onlyIf("requires Java ${config.minimumJavaVersion} or higher") { - config.supports(integrationTestJavaVersion) + [prepareMavenTest, executeRules, verifyRules, cleanUpMavenTest, runMavenTest].each { taskProvider -> + taskProvider.configure { + onlyIf("requires Java ${config.minimumJavaVersion} or higher") { + config.supports(integrationTestJavaVersion) + } } } - def executeRulesTask = tasks[executeRules] gradle.projectsEvaluated { def publishTasks = allprojects .findAll { it.findProperty('isReleaseProject') } .collectMany { [it.publish, it.publishToMavenLocal] } - executeRulesTask.mustRunAfter(*publishTasks) + executeRules.configure { mustRunAfter(*publishTasks) } } } } @@ -265,14 +264,20 @@ integrationTestConfigs.each { config -> project.with(addMavenTest(config)) } def suffixes = integrationTestConfigs*.suffix.sort() [suffixes, suffixes.tail()].transpose().each { twoConsecutiveSuffixes -> - tasks["prepareMavenTest${twoConsecutiveSuffixes[1]}"].mustRunAfter(tasks["cleanUpMavenTest${twoConsecutiveSuffixes[0]}"]) + tasks.named("prepareMavenTest${twoConsecutiveSuffixes[1]}").configure { + mustRunAfter tasks.named("cleanUpMavenTest${twoConsecutiveSuffixes[0]}") + } } -task runMavenTest -tasks.findAll { it.name ==~ /^runMavenTest.+/ }.each { runMavenTest.dependsOn it } -tasks.findAll { it.name ==~ /.*MavenTest.+/ }.each { - it.doFirst { - println "Running Maven Integration Test with JDK ${integrationTestJavaVersion} located at ${jdkHome}" +tasks.register('runMavenTest') { + dependsOn integrationTestConfigs.collect { tasks.named("runMavenTest${it.suffix}") } +} + +tasks.configureEach { + if (name ==~ /.*MavenTest.+/) { + doFirst { + println "Running Maven Integration Test with JDK ${integrationTestJavaVersion} located at ${jdkHome}" + } } } From 83353032793d21a848db56c92b5f5417fb29948e Mon Sep 17 00:00:00 2001 From: Manfred Hanke Date: Sun, 13 Sep 2026 16:50:38 +0200 Subject: [PATCH 2/5] fix archunit-maven-test publish task ordering `allprojects` within `archunit-maven-test` only contains the project itself, so the lookup of release projects for `mustRunAfter` wiring always returned an empty list. The lookup was correct when it was introduced in 7a1cc9639042cb60f7c500ac0911b77e97582fcc, as the code then lived in `build-steps/maven-integration-test/` and was applied from the root project, where `allprojects` spans the whole build. It silently broke when 4779b3abfc775da708da948722457522e0bb4b07 moved the build config verbatim into the `archunit-maven-test` subproject, changing the project context of the script. Use `rootProject.allprojects` to consider the whole build again. Signed-off-by: Manfred Hanke --- archunit-maven-test/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/archunit-maven-test/build.gradle b/archunit-maven-test/build.gradle index b36372c0f..60c414067 100644 --- a/archunit-maven-test/build.gradle +++ b/archunit-maven-test/build.gradle @@ -199,7 +199,7 @@ def addMavenTest = { IntegrationTestConfig config -> } gradle.projectsEvaluated { - def publishTasks = allprojects + def publishTasks = rootProject.allprojects .findAll { it.findProperty('isReleaseProject') } .collectMany { [it.publish, it.publishToMavenLocal] } executeRules.configure { mustRunAfter(*publishTasks) } From 5925feeb11f3b98d8de205965c0b87a49884b6ff Mon Sep 17 00:00:00 2001 From: Manfred Hanke Date: Wed, 16 Sep 2026 08:34:08 +0200 Subject: [PATCH 3/5] simplify assertMatchWith, especially show context of failure Signed-off-by: Manfred Hanke --- .../verification/TestResultTest.java | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/archunit-maven-test/verification/TestResultTest.java b/archunit-maven-test/verification/TestResultTest.java index 5654fced7..1f89fabab 100644 --- a/archunit-maven-test/verification/TestResultTest.java +++ b/archunit-maven-test/verification/TestResultTest.java @@ -149,20 +149,7 @@ private void validateAllArchitectureFailures(Match testCaseTags) { } void assertMatchWith(GivenTestClasses givenTestClasses) { - Set onlyGiven = difference(givenTestClasses.tests, failedArchitectureTests); - assertThat(onlyGiven).as("Tests that were expected to fail, but didn't").isEmpty(); - Set onlyFailed = difference(failedArchitectureTests, givenTestClasses.tests); - assertThat(onlyFailed).as("Tests that unexpectedly failed").isEmpty(); - } - - private Set difference(Set set, Set toSubtract) { - Set result = new HashSet<>(); - for (T elem : set) { - if (!toSubtract.contains(elem)) { - result.add(elem); - } - } - return result; + assertThat(failedArchitectureTests).as("actual failures").hasSameElementsAs(givenTestClasses.tests); } void markProcessed(List testReports) { From dc82ee8a5b528b46a454dfc2d9b5127f7bd23b03 Mon Sep 17 00:00:00 2001 From: Manfred Hanke Date: Wed, 16 Sep 2026 09:16:44 +0200 Subject: [PATCH 4/5] redirect Maven test execution into log file Redirect `executeRules` stdout and stderr into a log file under `verificationResultArchivePath//`, keeping the console output limited to the echoed command. Also print the `verifyRules` command and the `cleanUpMavenTest` copy destination for better traceability of the Maven integration test run. Upload the archived `verification-results/` directory as a GitHub Actions artifact on the `integration-test` job, so the logs and surefire XML reports remain available when a Maven integration test fails in CI. Signed-off-by: Manfred Hanke --- .github/workflows/build.yml | 12 ++++++++++++ archunit-maven-test/build.gradle | 27 ++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a9e900773..cfd012067 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -138,3 +138,15 @@ jobs: run: ./gradlew runMavenTest -PtestJavaVersion=${{ matrix.test_java_version }} "-Porg.gradle.java.installations.paths=${{ env.test_jdk_path }}" env: JAVA_HOME: ${{ env.build_jdk_path }} + - name: Upload Maven integration test verification results + id: upload-results + uses: actions/upload-artifact@v7 + if: always() + with: + name: maven-test-results-${{ matrix.os }}-java${{ matrix.test_java_version }} + path: archunit-maven-test/verification-results/ + if-no-files-found: ignore + - name: Report artifact location + if: always() && steps.upload-results.outputs.artifact-url + run: | + echo "::notice title=Maven test results (logs & surefire XML)::${{ steps.upload-results.outputs.artifact-url }}" diff --git a/archunit-maven-test/build.gradle b/archunit-maven-test/build.gradle index 60c414067..7cdee135d 100644 --- a/archunit-maven-test/build.gradle +++ b/archunit-maven-test/build.gradle @@ -154,13 +154,36 @@ def addMavenTest = { IntegrationTestConfig config -> def executeRules = tasks.register("executeRules${config.suffix}", Exec) { dependsOn prepareMavenTest + + def logStream + doFirst { + def logPath = new File(verificationResultArchivePath, config.identifier) + assert logPath.mkdir() || logPath.directory : "Couldn't create directory $logPath" + def logFile = new File(logPath, "${name}.log") + logStream = new FileOutputStream(logFile) + standardOutput = logStream + errorOutput = logStream + println "\$ ${commandLine.join(" ")} > ${project.relativePath(logFile)}" + } + workingDir projectDir environment JAVA_HOME: jdkHome commandLine mavenCommand('clean', 'test', '-P', 'execute-rules') + + doLast { + if (logStream) { + logStream.close() + } + } } def verifyRules = tasks.register("verifyRules${config.suffix}", Exec) { dependsOn executeRules + + doFirst { + println "\$ ${commandLine.join(" ")}" + } + workingDir projectDir environment JAVA_HOME: jdkHome commandLine mavenCommand('surefire:test', '-P', 'verify-rules') @@ -168,10 +191,12 @@ def addMavenTest = { IntegrationTestConfig config -> def cleanUpMavenTest = tasks.register("cleanUpMavenTest${config.suffix}") { doFirst { + def archivePath = new File(verificationResultArchivePath, config.identifier) + println "Copying test results to ${project.relativePath(archivePath)}" copy { from verificationResultOriginPath include '**/*.xml' - into new File(verificationResultArchivePath, "${config.identifier}") + into archivePath } } doLast { From cbb6b51a512f252e6142f09a493becfd32809690 Mon Sep 17 00:00:00 2001 From: Manfred Hanke Date: Mon, 19 Oct 2026 22:33:50 +0200 Subject: [PATCH 5/5] gitignore intermediary files generated by archunit-maven-test's prepareMavenTest* tasks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the comment asserting that `cleanUpMavenTest*` runs even when a task fails, as `finalizedBy` only runs cleanup after a task that actually executes – it is not a build-level `finally` block, and may not be reached if tests fail and the build is interrupted. Signed-off-by: Manfred Hanke --- archunit-maven-test/.gitignore | 2 ++ archunit-maven-test/build.gradle | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 archunit-maven-test/.gitignore diff --git a/archunit-maven-test/.gitignore b/archunit-maven-test/.gitignore new file mode 100644 index 000000000..1d43c8300 --- /dev/null +++ b/archunit-maven-test/.gitignore @@ -0,0 +1,2 @@ +/src +/pom.xml diff --git a/archunit-maven-test/build.gradle b/archunit-maven-test/build.gradle index 7cdee135d..e5ae89a1d 100644 --- a/archunit-maven-test/build.gradle +++ b/archunit-maven-test/build.gradle @@ -206,7 +206,6 @@ def addMavenTest = { IntegrationTestConfig config -> } } - // Run cleanUp even if task fails [prepareMavenTest, verifyRules, executeRules].each { taskProvider -> taskProvider.configure { finalizedBy cleanUpMavenTest } }