This is an automated email from the ASF dual-hosted git repository.
paulk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push:
new f74fd689b6 minor refactor: add aggregate test result summary to build
f74fd689b6 is described below
commit f74fd689b69ec2f3fadea19c095d21399ca9b56a
Author: Paul King <[email protected]>
AuthorDate: Wed Apr 15 17:59:41 2026 +1000
minor refactor: add aggregate test result summary to build
---
.../main/groovy/org.apache.groovy-tested.gradle | 24 ++++--
.../gradle/TestResultAggregatorService.groovy | 89 ++++++++++++++++++++++
2 files changed, 106 insertions(+), 7 deletions(-)
diff --git a/build-logic/src/main/groovy/org.apache.groovy-tested.gradle
b/build-logic/src/main/groovy/org.apache.groovy-tested.gradle
index 99d6d429bf..ef13294b61 100644
--- a/build-logic/src/main/groovy/org.apache.groovy-tested.gradle
+++ b/build-logic/src/main/groovy/org.apache.groovy-tested.gradle
@@ -19,6 +19,7 @@
import org.apache.groovy.gradle.ConcurrentExecutionControlBuildService
import org.apache.groovy.gradle.TargetJavaHomeSupport
+import org.apache.groovy.gradle.TestResultAggregatorService
// TODO: Instead of adding to the test sources, these should be a
// separate source set so that we can run spec tests in isolation
@@ -42,6 +43,9 @@ dependencies {
}
}
+def aggregator = TestResultAggregatorService.register(
+ objects.newInstance(TestServices).buildEventsListenerRegistry, gradle)
+
tasks.withType(Test).configureEach {
def fs = objects.newInstance(TestServices).fileSystemOperations
def grapeDirectory = new File(temporaryDir, '.groovy')
@@ -88,6 +92,7 @@ tasks.withType(Test).configureEach {
}
useJUnitPlatform()
+ usesService(aggregator)
usesService(ConcurrentExecutionControlBuildService.restrict(Test, gradle,
2))
doFirst {
@@ -105,13 +110,16 @@ tasks.withType(Test).configureEach {
}
afterSuite { desc, result ->
- if (!desc.parent && result.resultType ==
TestResult.ResultType.SUCCESS) {
- def green = '\u001B[32m'
- def yellow = '\u001B[33m'
- def reset = '\u001B[0m'
- logger.lifecycle "${desc.name}:
${green}${result.successfulTestCount} passed${reset}, " +
- "${yellow}${result.skippedTestCount} skipped${reset} " +
- "(${result.testCount} tests)"
+ if (!desc.parent) {
+ aggregator.get().recordSuite(result.successfulTestCount,
result.failedTestCount, result.skippedTestCount)
+ if (result.resultType == TestResult.ResultType.SUCCESS) {
+ def green = '\u001B[32m'
+ def yellow = '\u001B[33m'
+ def reset = '\u001B[0m'
+ logger.lifecycle "${desc.name}:
${green}${result.successfulTestCount} passed${reset}, " +
+ "${yellow}${result.skippedTestCount} skipped${reset} " +
+ "(${result.testCount} tests)"
+ }
}
}
}
@@ -170,4 +178,6 @@ Closure buildExcludeFilter(boolean legacyTestSuite) {
interface TestServices {
@javax.inject.Inject
FileSystemOperations getFileSystemOperations()
+ @javax.inject.Inject
+ org.gradle.build.event.BuildEventsListenerRegistry
getBuildEventsListenerRegistry()
}
diff --git
a/build-logic/src/main/groovy/org/apache/groovy/gradle/TestResultAggregatorService.groovy
b/build-logic/src/main/groovy/org/apache/groovy/gradle/TestResultAggregatorService.groovy
new file mode 100644
index 0000000000..e525f231b7
--- /dev/null
+++
b/build-logic/src/main/groovy/org/apache/groovy/gradle/TestResultAggregatorService.groovy
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.gradle
+
+import groovy.transform.CompileStatic
+import org.gradle.api.provider.Provider
+import org.gradle.api.services.BuildService
+import org.gradle.api.services.BuildServiceParameters
+import org.gradle.build.event.BuildEventsListenerRegistry
+import org.gradle.tooling.events.FinishEvent
+import org.gradle.tooling.events.OperationCompletionListener
+
+import java.util.concurrent.atomic.AtomicLong
+
+/**
+ * Aggregates test results across all subprojects and prints
+ * a summary at the end of the build.
+ *
+ * <p>Suite-level results are fed in via {@link #recordSuite} from
+ * {@code afterSuite} callbacks. The service also implements
+ * {@link OperationCompletionListener} so it stays alive until
+ * all tasks complete; {@link #close()} prints the aggregate.</p>
+ */
+@CompileStatic
+abstract class TestResultAggregatorService implements BuildService<Params>,
OperationCompletionListener, AutoCloseable {
+
+ interface Params extends BuildServiceParameters {}
+
+ private final AtomicLong totalPassed = new AtomicLong()
+ private final AtomicLong totalFailed = new AtomicLong()
+ private final AtomicLong totalSkipped = new AtomicLong()
+
+ /** Called from afterSuite when a root suite completes. */
+ void recordSuite(long passed, long failed, long skipped) {
+ totalPassed.addAndGet(passed)
+ totalFailed.addAndGet(failed)
+ totalSkipped.addAndGet(skipped)
+ }
+
+ @Override
+ void onFinish(FinishEvent event) {
+ // Required by OperationCompletionListener; keeps the service
+ // alive until the last task finishes so close() runs at build end.
+ }
+
+ @Override
+ void close() {
+ long passed = totalPassed.get()
+ long failed = totalFailed.get()
+ long skipped = totalSkipped.get()
+ long total = passed + failed + skipped
+ if (total == 0) return
+
+ String green = '\u001B[32m'
+ String red = '\u001B[31m'
+ String yellow = '\u001B[33m'
+ String reset = '\u001B[0m'
+
+ String failText = failed ? "${red}${failed} failed${reset}, " : ''
+ System.out.println("Aggregate test results:
${failText}${green}${passed} passed${reset}, " +
+ "${yellow}${skipped} skipped${reset} " +
+ "(${total} tests)")
+ System.out.flush()
+ }
+
+ static Provider<TestResultAggregatorService>
register(BuildEventsListenerRegistry registry,
+
org.gradle.api.invocation.Gradle gradle) {
+ Provider<TestResultAggregatorService> provider =
gradle.sharedServices.registerIfAbsent(
+ 'testResultAggregator', TestResultAggregatorService) {}
+ registry.onTaskCompletion(provider)
+ return provider
+ }
+}