From 6985d8a616ccd6ff4fa80bec2dae67cf3c70d7b2 Mon Sep 17 00:00:00 2001 From: Romain Manni-Bucau Date: Thu, 17 Sep 2026 23:16:24 +0200 Subject: [PATCH 1/2] Fixes #13135: ensure completeness of the reactor summary, privilege failures last Backport of the reactor summary improvements to the 3.10.x branch: every module is now always listed (including skipped ones) and failures are displayed last, logged at error level, so they are easier to spot. --- .../maven/cli/event/ExecutionEventLogger.java | 120 ++++++++++++++---- .../cli/event/ExecutionEventLoggerTest.java | 114 +++++++++++++++-- 2 files changed, 199 insertions(+), 35 deletions(-) diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/event/ExecutionEventLogger.java b/maven-embedder/src/main/java/org/apache/maven/cli/event/ExecutionEventLogger.java index d081cc4b711e..d6098013d511 100644 --- a/maven-embedder/src/main/java/org/apache/maven/cli/event/ExecutionEventLogger.java +++ b/maven-embedder/src/main/java/org/apache/maven/cli/event/ExecutionEventLogger.java @@ -20,6 +20,7 @@ import java.io.File; import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -170,39 +171,48 @@ private void logReactorSummary(MavenSession session) { String failureMessage = builder().failure("FAILURE").build(); String unknownMessage = builder().warning("UNKNOWN").build(); - boolean lastWasSkipped = false; + List entries = new ArrayList<>(projects.size()); for (MavenProject project : projects) { BuildSummary buildSummary = result.getBuildSummary(project); String statusMessage; - boolean shouldSkip = result.hasExceptions(); - if (buildSummary == null) { - statusMessage = skippedMessage; - } else if (buildSummary instanceof BuildSuccess) { + int group; + if (buildSummary instanceof BuildSuccess) { statusMessage = successMessage; + group = 1; } else if (buildSummary instanceof BuildFailure) { statusMessage = failureMessage; - shouldSkip = false; + group = 2; + } else if (buildSummary == null) { + statusMessage = skippedMessage; + group = 0; } else { statusMessage = unknownMessage; + group = 0; } + entries.add(new ReactorSummaryEntry(project, buildSummary, group, statusMessage)); + } - if (shouldSkip) { - lastWasSkipped = true; + ReactorSummaryRequest request = new ReactorSummaryRequest(entries, new StringBuilder(128), isSingleVersion); + + logReactorSummaryGroup(request, 0); + logReactorSummaryGroup(request, 1); + logReactorSummaryGroup(request, 2); + } + + private void logReactorSummaryGroup(ReactorSummaryRequest request, int group) { + StringBuilder buffer = request.getBuffer(); + + for (ReactorSummaryEntry entry : request.getEntries()) { + if (entry.getGroup() != group) { continue; } - if (lastWasSkipped) { - logger.info("..."); - lastWasSkipped = false; - } - - StringBuilder buffer = new StringBuilder(128); - buffer.append(project.getName()); + buffer.append(entry.getProject().getName()); buffer.append(' '); - if (!isSingleVersion) { - buffer.append(project.getVersion()); + if (!request.isSingleVersion()) { + buffer.append(entry.getProject().getVersion()); buffer.append(' '); } @@ -213,16 +223,71 @@ private void logReactorSummary(MavenSession session) { buffer.append(' '); } - buffer.append(statusMessage); - if (buildSummary != null) { - formatBuildTime(buffer, buildSummary); + buffer.append(entry.getStatusMessage()); + if (entry.getBuildSummary() != null) { + formatBuildTime(buffer, entry.getBuildSummary()); } - logger.info(buffer.toString()); + if (entry.getBuildSummary() instanceof BuildFailure) { + logger.error(buffer.toString()); + } else { + logger.info(buffer.toString()); + } + buffer.setLength(0); + } + } + + private static final class ReactorSummaryRequest { + private final List entries; + private final StringBuilder buffer; + private final boolean singleVersion; + + private ReactorSummaryRequest(List entries, StringBuilder buffer, boolean singleVersion) { + this.entries = entries; + this.buffer = buffer; + this.singleVersion = singleVersion; + } + + private List getEntries() { + return entries; + } + + private StringBuilder getBuffer() { + return buffer; } - if (lastWasSkipped) { - logger.info("..."); + private boolean isSingleVersion() { + return singleVersion; + } + } + + private static final class ReactorSummaryEntry { + private final MavenProject project; + private final BuildSummary buildSummary; + private final int group; + private final String statusMessage; + + private ReactorSummaryEntry(MavenProject project, BuildSummary buildSummary, int group, String statusMessage) { + this.project = project; + this.buildSummary = buildSummary; + this.group = group; + this.statusMessage = statusMessage; + } + + private MavenProject getProject() { + return project; + } + + private BuildSummary getBuildSummary() { + return buildSummary; + } + + private int getGroup() { + return group; + } + + private String getStatusMessage() { + return statusMessage; } } @@ -241,12 +306,17 @@ private void logResult(MavenSession session) { infoLine('-'); MessageBuilder buffer = MessageUtils.builder(); - if (session.getResult().hasExceptions()) { + boolean failure = session.getResult().hasExceptions(); + if (failure) { buffer.failure("BUILD FAILURE"); } else { buffer.success("BUILD SUCCESS"); } - logger.info(buffer.toString()); + if (failure) { + logger.error(buffer.toString()); + } else { + logger.info(buffer.toString()); + } } private void logStats(MavenSession session) { diff --git a/maven-embedder/src/test/java/org/apache/maven/cli/event/ExecutionEventLoggerTest.java b/maven-embedder/src/test/java/org/apache/maven/cli/event/ExecutionEventLoggerTest.java index 00ce1f79c30e..0d4d0730bce0 100644 --- a/maven-embedder/src/test/java/org/apache/maven/cli/event/ExecutionEventLoggerTest.java +++ b/maven-embedder/src/test/java/org/apache/maven/cli/event/ExecutionEventLoggerTest.java @@ -279,11 +279,11 @@ public void testSessionEndedFailureMultimodule() { inOrder.verify(logger).info("------------------------------------------------------------------------"); inOrder.verify(logger).info("Reactor Summary for Maven Project artifact1 1.0.0-SNAPSHOT:"); inOrder.verify(logger).info(""); - inOrder.verify(logger).info("..."); - inOrder.verify(logger).info("Maven Project artifact2 ............................ FAILURE [ 2.000 s]"); - inOrder.verify(logger).info("..."); + inOrder.verify(logger).info("Maven Project artifact3 ............................ SKIPPED"); + inOrder.verify(logger).info("Maven Project artifact1 ............................ SUCCESS [ 1.000 s]"); + inOrder.verify(logger).error("Maven Project artifact2 ............................ FAILURE [ 2.000 s]"); inOrder.verify(logger).info("------------------------------------------------------------------------"); - inOrder.verify(logger).info("BUILD FAILURE"); + inOrder.verify(logger).error("BUILD FAILURE"); inOrder.verify(logger).info("------------------------------------------------------------------------"); inOrder.verify(logger).info(eq("Total time: {}{}"), anyString(), anyString()); inOrder.verify(logger).info(eq("Finished at: {}"), anyString()); @@ -335,13 +335,107 @@ public void testSessionEndedFailureMultimoduleWithSeparatedFailures() { inOrder.verify(logger).info("------------------------------------------------------------------------"); inOrder.verify(logger).info("Reactor Summary for Maven Project artifact1 1.0.0-SNAPSHOT:"); inOrder.verify(logger).info(""); - inOrder.verify(logger).info("..."); - inOrder.verify(logger).info("Maven Project artifact2 ............................ FAILURE [ 2.000 s]"); - inOrder.verify(logger).info("..."); - inOrder.verify(logger).info("Maven Project artifact5 ............................ FAILURE [ 5.000 s]"); - inOrder.verify(logger).info("..."); + inOrder.verify(logger).info("Maven Project artifact6 ............................ SKIPPED"); + inOrder.verify(logger).info("Maven Project artifact1 ............................ SUCCESS [ 1.000 s]"); + inOrder.verify(logger).info("Maven Project artifact3 ............................ SUCCESS [ 3.000 s]"); + inOrder.verify(logger).info("Maven Project artifact4 ............................ SUCCESS [ 4.000 s]"); + inOrder.verify(logger).error("Maven Project artifact2 ............................ FAILURE [ 2.000 s]"); + inOrder.verify(logger).error("Maven Project artifact5 ............................ FAILURE [ 5.000 s]"); + inOrder.verify(logger).info("------------------------------------------------------------------------"); + inOrder.verify(logger).error("BUILD FAILURE"); + inOrder.verify(logger).info("------------------------------------------------------------------------"); + inOrder.verify(logger).info(eq("Total time: {}{}"), anyString(), anyString()); + inOrder.verify(logger).info(eq("Finished at: {}"), anyString()); + inOrder.verify(logger).info("------------------------------------------------------------------------"); + } + + @Test + public void testSessionEndedSuccessWithSkippedModules() { + // prepare + MavenProject project1 = aProject("artifact1"); + MavenProject project2 = aProject("artifact2"); + MavenProject project3 = aProject("artifact3"); + + MavenExecutionResult executionResult = new DefaultMavenExecutionResult(); + executionResult.addBuildSummary(new BuildSuccess(project1, 1000)); + executionResult.addBuildSummary(new BuildSuccess(project3, 3000)); + + MavenExecutionRequest executionRequest = new DefaultMavenExecutionRequest(); + executionRequest.setStartTime(new Date()); + + ProjectDependencyGraph projectDependencyGraph = mock(ProjectDependencyGraph.class); + when(projectDependencyGraph.getSortedProjects()).thenReturn(Arrays.asList(project1, project2, project3)); + + MavenSession mavenSession = mock(MavenSession.class); + when(mavenSession.getResult()).thenReturn(executionResult); + when(mavenSession.getRequest()).thenReturn(executionRequest); + when(mavenSession.getProjects()).thenReturn(Arrays.asList(project1, project2, project3)); + when(mavenSession.getTopLevelProject()).thenReturn(project1); + when(mavenSession.getProjectDependencyGraph()).thenReturn(projectDependencyGraph); + + ExecutionEvent event = mock(ExecutionEvent.class); + when(event.getSession()).thenReturn(mavenSession); + + // execute + executionEventLogger.sessionEnded(event); + + // verify + InOrder inOrder = inOrder(logger); + inOrder.verify(logger).info("------------------------------------------------------------------------"); + inOrder.verify(logger).info("Reactor Summary for Maven Project artifact1 1.0.0-SNAPSHOT:"); + inOrder.verify(logger).info(""); + inOrder.verify(logger).info("Maven Project artifact2 ............................ SKIPPED"); + inOrder.verify(logger).info("Maven Project artifact1 ............................ SUCCESS [ 1.000 s]"); + inOrder.verify(logger).info("Maven Project artifact3 ............................ SUCCESS [ 3.000 s]"); + inOrder.verify(logger).info("------------------------------------------------------------------------"); + inOrder.verify(logger).info("BUILD SUCCESS"); + inOrder.verify(logger).info("------------------------------------------------------------------------"); + inOrder.verify(logger).info(eq("Total time: {}{}"), anyString(), anyString()); + inOrder.verify(logger).info(eq("Finished at: {}"), anyString()); + inOrder.verify(logger).info("------------------------------------------------------------------------"); + } + + @Test + public void testSessionEndedFailureMixedWithSkippedModules() { + // prepare + MavenProject project1 = aProject("artifact1"); + MavenProject project2 = aProject("artifact2"); + MavenProject project3 = aProject("artifact3"); + + MavenExecutionResult executionResult = new DefaultMavenExecutionResult(); + executionResult.addBuildSummary(new BuildSuccess(project1, 1000)); + executionResult.addBuildSummary(new BuildSuccess(project3, 3000)); + executionResult.addException(new Exception("Failure")); + + MavenExecutionRequest executionRequest = new DefaultMavenExecutionRequest(); + executionRequest.setStartTime(new Date()); + + ProjectDependencyGraph projectDependencyGraph = mock(ProjectDependencyGraph.class); + when(projectDependencyGraph.getSortedProjects()).thenReturn(Arrays.asList(project1, project2, project3)); + + MavenSession mavenSession = mock(MavenSession.class); + when(mavenSession.getResult()).thenReturn(executionResult); + when(mavenSession.getRequest()).thenReturn(executionRequest); + when(mavenSession.getProjects()).thenReturn(Arrays.asList(project1, project2, project3)); + when(mavenSession.getTopLevelProject()).thenReturn(project1); + when(mavenSession.getProjectDependencyGraph()).thenReturn(projectDependencyGraph); + + ExecutionEvent event = mock(ExecutionEvent.class); + when(event.getSession()).thenReturn(mavenSession); + + // execute + executionEventLogger.sessionEnded(event); + + // verify + InOrder inOrder = inOrder(logger); + inOrder.verify(logger).info("------------------------------------------------------------------------"); + inOrder.verify(logger).info("Reactor Summary for Maven Project artifact1 1.0.0-SNAPSHOT:"); + inOrder.verify(logger).info(""); + inOrder.verify(logger).info("Maven Project artifact2 ............................ SKIPPED"); + inOrder.verify(logger).info("Maven Project artifact1 ............................ SUCCESS [ 1.000 s]"); + inOrder.verify(logger).info("Maven Project artifact3 ............................ SUCCESS [ 3.000 s]"); inOrder.verify(logger).info("------------------------------------------------------------------------"); - inOrder.verify(logger).info("BUILD FAILURE"); + inOrder.verify(logger).error("BUILD FAILURE"); inOrder.verify(logger).info("------------------------------------------------------------------------"); inOrder.verify(logger).info(eq("Total time: {}{}"), anyString(), anyString()); inOrder.verify(logger).info(eq("Finished at: {}"), anyString()); From 1794551405c6b842c62ac70bf894e666c1854ac1 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Fri, 18 Sep 2026 11:25:11 +0000 Subject: [PATCH 2/2] Address review: local StringBuilder, UNKNOWN group=2 - Move StringBuilder from ReactorSummaryRequest into logReactorSummaryGroup as a local variable, eliminating the mutable state from the data-holder. - Assign group=2 to the UNKNOWN BuildSummary branch so any future BuildSummary subclass is treated like a failure rather than silently grouped with SKIPPED modules. --- .../maven/cli/event/ExecutionEventLogger.java | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/event/ExecutionEventLogger.java b/maven-embedder/src/main/java/org/apache/maven/cli/event/ExecutionEventLogger.java index d6098013d511..5ed5400e078f 100644 --- a/maven-embedder/src/main/java/org/apache/maven/cli/event/ExecutionEventLogger.java +++ b/maven-embedder/src/main/java/org/apache/maven/cli/event/ExecutionEventLogger.java @@ -188,12 +188,12 @@ private void logReactorSummary(MavenSession session) { group = 0; } else { statusMessage = unknownMessage; - group = 0; + group = 2; } entries.add(new ReactorSummaryEntry(project, buildSummary, group, statusMessage)); } - ReactorSummaryRequest request = new ReactorSummaryRequest(entries, new StringBuilder(128), isSingleVersion); + ReactorSummaryRequest request = new ReactorSummaryRequest(entries, isSingleVersion); logReactorSummaryGroup(request, 0); logReactorSummaryGroup(request, 1); @@ -201,7 +201,7 @@ private void logReactorSummary(MavenSession session) { } private void logReactorSummaryGroup(ReactorSummaryRequest request, int group) { - StringBuilder buffer = request.getBuffer(); + StringBuilder buffer = new StringBuilder(128); for (ReactorSummaryEntry entry : request.getEntries()) { if (entry.getGroup() != group) { @@ -239,12 +239,10 @@ private void logReactorSummaryGroup(ReactorSummaryRequest request, int group) { private static final class ReactorSummaryRequest { private final List entries; - private final StringBuilder buffer; private final boolean singleVersion; - private ReactorSummaryRequest(List entries, StringBuilder buffer, boolean singleVersion) { + private ReactorSummaryRequest(List entries, boolean singleVersion) { this.entries = entries; - this.buffer = buffer; this.singleVersion = singleVersion; } @@ -252,10 +250,6 @@ private List getEntries() { return entries; } - private StringBuilder getBuffer() { - return buffer; - } - private boolean isSingleVersion() { return singleVersion; }