You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by gn...@apache.org on 2026/08/24 11:36:03 UTC

(maven) branch master updated: Fix duplicate model problem reporting, fixes #12691 (#12707)

This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git


The following commit(s) were added to refs/heads/master by this push:
     new f5649cad75 Fix duplicate model problem reporting, fixes #12691 (#12707)
f5649cad75 is described below

commit f5649cad75eba544a170342038a981372f140a85
Author: Mazen Kamal <71...@users.noreply.github.com>
AuthorDate: Mon Aug 24 14:35:57 2026 +0300

    Fix duplicate model problem reporting, fixes #12691 (#12707)
    
    Remove the detailed per-project diagnostics from
    ProjectBuildingException#createMessage(List<ProjectBuildingResult>) which
    duplicated the rendering already performed by DefaultExceptionHandler.
    The exception message now returns a short summary with the problem count,
    leaving DefaultExceptionHandler as the single source of detailed output.
    
    Adds a regression test in DefaultExceptionHandlerTest to verify each
    model problem is rendered exactly once.
    
    Closes #12691
---
 .../maven/project/ProjectBuildingException.java    |  87 +----------
 .../exception/DefaultExceptionHandlerTest.java     |  86 +++++++++++
 .../project/ProjectBuildingExceptionTest.java      | 163 ---------------------
 3 files changed, 90 insertions(+), 246 deletions(-)

diff --git a/impl/maven-core/src/main/java/org/apache/maven/project/ProjectBuildingException.java b/impl/maven-core/src/main/java/org/apache/maven/project/ProjectBuildingException.java
index 89ca05d43a..88c76b7aea 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/project/ProjectBuildingException.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/project/ProjectBuildingException.java
@@ -21,8 +21,6 @@
 import java.io.File;
 import java.util.List;
 
-import org.apache.maven.model.building.ModelProblem;
-
 /**
  * @deprecated use {@code org.apache.maven.api.services.ProjectBuilder} instead
  */
@@ -63,7 +61,10 @@ protected ProjectBuildingException(String projectId, String message, File pomFil
     }
 
     public ProjectBuildingException(List<ProjectBuildingResult> results) {
-        super(createMessage(results));
+        super("Encountered %d problem(s) while processing the POMs"
+                .formatted(results.stream()
+                        .mapToLong(result -> result.getProblems().size())
+                        .sum()));
         this.projectId = "";
         this.results = results;
     }
@@ -101,84 +102,4 @@ private static String createMessage(String message, String projectId, File pomFi
         }
         return buffer.toString();
     }
-
-    private static String createMessage(List<ProjectBuildingResult> results) {
-        if (results == null || results.isEmpty()) {
-            return "Some problems were encountered while processing the POMs";
-        }
-
-        long totalProblems = 0;
-        long errorProblems = 0;
-
-        for (ProjectBuildingResult result : results) {
-            List<ModelProblem> problems = result.getProblems();
-            totalProblems += problems.size();
-
-            for (ModelProblem problem : problems) {
-                if (problem.getSeverity() != ModelProblem.Severity.WARNING) {
-                    errorProblems++;
-                }
-            }
-        }
-
-        StringBuilder buffer = new StringBuilder(1024);
-        buffer.append(totalProblems);
-        buffer.append(totalProblems == 1 ? " problem was " : " problems were ");
-        buffer.append("encountered while processing the POMs");
-
-        if (errorProblems > 0) {
-            buffer.append(" (")
-                    .append(errorProblems)
-                    .append(" ")
-                    .append(errorProblems > 1 ? "errors" : "error")
-                    .append(")");
-        }
-
-        buffer.append(":\n");
-
-        for (ProjectBuildingResult result : results) {
-            if (!result.getProblems().isEmpty()) {
-                String projectInfo = result.getProjectId();
-                if (projectInfo.trim().isEmpty()) {
-                    projectInfo =
-                            result.getPomFile() != null ? result.getPomFile().getName() : "unknown project";
-                }
-
-                buffer.append("\n[").append(projectInfo).append("]\n");
-
-                for (ModelProblem problem : result.getProblems()) {
-                    if (errorProblems > 0 && problem.getSeverity() == ModelProblem.Severity.WARNING) {
-                        continue;
-                    }
-
-                    buffer.append("  [").append(problem.getSeverity()).append("] ");
-                    buffer.append(problem.getMessage());
-
-                    String location = "";
-                    if (!problem.getSource().trim().isEmpty()) {
-                        location = problem.getSource();
-                    }
-                    if (problem.getLineNumber() > 0) {
-                        if (!location.isEmpty()) {
-                            location += ", ";
-                        }
-                        location += "line " + problem.getLineNumber();
-                    }
-                    if (problem.getColumnNumber() > 0) {
-                        if (!location.isEmpty()) {
-                            location += ", ";
-                        }
-                        location += "column " + problem.getColumnNumber();
-                    }
-
-                    if (!location.isEmpty()) {
-                        buffer.append(" @ ").append(location);
-                    }
-                    buffer.append("\n");
-                }
-            }
-        }
-
-        return buffer.toString();
-    }
 }
diff --git a/impl/maven-core/src/test/java/org/apache/maven/exception/DefaultExceptionHandlerTest.java b/impl/maven-core/src/test/java/org/apache/maven/exception/DefaultExceptionHandlerTest.java
index 88359e2e8f..e4cc6ab06a 100644
--- a/impl/maven-core/src/test/java/org/apache/maven/exception/DefaultExceptionHandlerTest.java
+++ b/impl/maven-core/src/test/java/org/apache/maven/exception/DefaultExceptionHandlerTest.java
@@ -18,17 +18,23 @@
  */
 package org.apache.maven.exception;
 
+import java.io.File;
 import java.io.IOException;
 import java.net.ConnectException;
+import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.maven.model.Plugin;
+import org.apache.maven.model.building.DefaultModelProblem;
+import org.apache.maven.model.building.ModelProblem;
 import org.apache.maven.plugin.MojoExecution;
 import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugin.PluginContainerException;
 import org.apache.maven.plugin.PluginExecutionException;
 import org.apache.maven.plugin.descriptor.MojoDescriptor;
 import org.apache.maven.plugin.descriptor.PluginDescriptor;
+import org.apache.maven.project.ProjectBuildingException;
+import org.apache.maven.project.ProjectBuildingResult;
 import org.eclipse.aether.artifact.DefaultArtifact;
 import org.eclipse.aether.repository.RemoteRepository;
 import org.eclipse.aether.resolution.ArtifactResolutionException;
@@ -38,6 +44,8 @@
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 /**
  */
@@ -179,4 +187,82 @@ void testHandleExceptionSelfReferencing() {
         assertEquals(0, summary.getChildren().size());
         assertEquals(boom1, summary.getException());
     }
+
+    @Test
+    void testProjectBuildingExceptionNotRenderedTwice() {
+        ModelProblem problem1 = new DefaultModelProblem(
+                "Malformed POM test.xml: unexpected element",
+                ModelProblem.Severity.FATAL,
+                null,
+                "test.xml",
+                8,
+                3,
+                null,
+                null);
+
+        ModelProblem problem2 = new DefaultModelProblem(
+                "Malformed POM test.xml: missing artifactId",
+                ModelProblem.Severity.ERROR,
+                null,
+                "test.xml",
+                12,
+                5,
+                null,
+                null);
+
+        ProjectBuildingResult result = mock(ProjectBuildingResult.class);
+        when(result.getProjectId()).thenReturn("test:fail-build:0.1-SNAPSHOT");
+        when(result.getPomFile()).thenReturn(new File("test.xml"));
+        when(result.getProblems()).thenReturn(List.of(problem1, problem2));
+
+        ProjectBuildingException exception = new ProjectBuildingException(List.of(result));
+
+        assertEquals(
+                "Encountered 2 problem(s) while processing the POMs",
+                exception.getMessage(),
+                "exception message must report the total number of problems");
+
+        ExceptionSummary summary = new DefaultExceptionHandler().handleException(exception);
+
+        ExceptionSummary projectSummary = summary.getChildren().get(0);
+        ExceptionSummary problemSummary = projectSummary.getChildren().get(0);
+
+        assertTrue(
+                problemSummary.getMessage().contains("Malformed POM test.xml"),
+                "the handler's own tree should still render the problem detail exactly once");
+
+        assertEquals(1, countOccurrences(flatten(summary), "Malformed POM test.xml: unexpected element"));
+    }
+
+    private static String flatten(ExceptionSummary summary) {
+        if (summary == null) {
+            return "";
+        }
+
+        StringBuilder sb = new StringBuilder();
+        if (summary.getMessage() != null) {
+            sb.append(summary.getMessage());
+        }
+
+        for (ExceptionSummary child : summary.getChildren()) {
+            if (!sb.isEmpty()) {
+                sb.append('\n');
+            }
+            sb.append(flatten(child));
+        }
+
+        return sb.toString();
+    }
+
+    private static int countOccurrences(String text, String substring) {
+        int count = 0;
+        int index = 0;
+
+        while ((index = text.indexOf(substring, index)) != -1) {
+            count++;
+            index += substring.length();
+        }
+
+        return count;
+    }
 }
diff --git a/impl/maven-core/src/test/java/org/apache/maven/project/ProjectBuildingExceptionTest.java b/impl/maven-core/src/test/java/org/apache/maven/project/ProjectBuildingExceptionTest.java
deleted file mode 100644
index fae4355e0e..0000000000
--- a/impl/maven-core/src/test/java/org/apache/maven/project/ProjectBuildingExceptionTest.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * 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.maven.project;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.maven.model.building.DefaultModelProblem;
-import org.apache.maven.model.building.ModelProblem;
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-/**
- * Test for {@link ProjectBuildingException} message generation.
- */
-@SuppressWarnings("deprecation")
-class ProjectBuildingExceptionTest {
-
-    @Test
-    void testDetailedExceptionMessageWithMultipleProblems() {
-        List<ProjectBuildingResult> results = new ArrayList<>();
-
-        List<ModelProblem> problems1 = new ArrayList<>();
-        Collections.addAll(
-                problems1,
-                new DefaultModelProblem(
-                        "Missing required dependency",
-                        ModelProblem.Severity.ERROR,
-                        null,
-                        "pom.xml",
-                        25,
-                        10,
-                        null,
-                        null),
-                new DefaultModelProblem(
-                        "Invalid version format", ModelProblem.Severity.ERROR, null, "pom.xml", 30, 5, null, null));
-        DefaultProjectBuildingResult result1 =
-                new DefaultProjectBuildingResult("com.example:project1:1.0", new File("project1/pom.xml"), problems1);
-        results.add(result1);
-
-        List<ModelProblem> problems2 = new ArrayList<>();
-        Collections.addAll(
-                problems2,
-                new DefaultModelProblem(
-                        "Deprecated plugin usage", ModelProblem.Severity.WARNING, null, "pom.xml", 15, 3, null, null));
-        DefaultProjectBuildingResult result2 =
-                new DefaultProjectBuildingResult("com.example:project2:1.0", new File("project2/pom.xml"), problems2);
-        results.add(result2);
-
-        ProjectBuildingException exception = new ProjectBuildingException(results);
-        String message = exception.getMessage();
-
-        assertTrue(
-                message.contains("3 problems were encountered while processing the POMs (2 errors)"),
-                "Message should contain problem count and error count");
-
-        assertTrue(message.contains("[com.example:project1:1.0]"), "Message should contain project1 identifier");
-
-        assertTrue(message.contains("[com.example:project2:1.0]"), "Message should contain project2 identifier");
-
-        assertTrue(
-                message.contains("[ERROR] Missing required dependency @ pom.xml, line 25, column 10"),
-                "Message should contain error details with location");
-
-        assertTrue(
-                message.contains("[ERROR] Invalid version format @ pom.xml, line 30, column 5"),
-                "Message should contain second error details");
-
-        assertTrue(
-                !message.contains("[WARNING]") || message.contains("[WARNING] Deprecated plugin usage"),
-                "Warnings should be filtered when errors are present or shown if explicitly included");
-    }
-
-    @Test
-    void testExceptionMessageWithOnlyWarnings() {
-        List<ProjectBuildingResult> results = new ArrayList<>();
-
-        List<ModelProblem> problems = new ArrayList<>();
-        Collections.addAll(
-                problems,
-                new DefaultModelProblem(
-                        "Deprecated feature used", ModelProblem.Severity.WARNING, null, "pom.xml", 10, 1, null, null));
-        DefaultProjectBuildingResult result =
-                new DefaultProjectBuildingResult("com.example:project:1.0", new File("project/pom.xml"), problems);
-        results.add(result);
-
-        ProjectBuildingException exception = new ProjectBuildingException(results);
-        String message = exception.getMessage();
-
-        assertTrue(
-                message.contains("1 problem was encountered while processing the POMs"),
-                "Message should use singular form for single problem");
-
-        assertTrue(
-                message.contains("[WARNING] Deprecated feature used"),
-                "Message should contain warning when no errors are present");
-
-        assertTrue(
-                !message.contains("(") || !message.contains("error"),
-                "Message should not contain error count when there are no errors");
-    }
-
-    @Test
-    void testExceptionMessageWithEmptyResults() {
-        List<ProjectBuildingResult> results = Collections.emptyList();
-
-        ProjectBuildingException exception = new ProjectBuildingException(results);
-        String message = exception.getMessage();
-
-        assertEquals(
-                "Some problems were encountered while processing the POMs",
-                message,
-                "Empty results should fall back to generic message");
-    }
-
-    @Test
-    void testExceptionMessageWithNullResults() {
-        ProjectBuildingException exception = new ProjectBuildingException((List<ProjectBuildingResult>) null);
-        String message = exception.getMessage();
-
-        assertEquals(
-                "Some problems were encountered while processing the POMs",
-                message,
-                "Null results should fall back to generic message");
-    }
-
-    @Test
-    void testExceptionMessageWithUnknownProject() {
-        List<ProjectBuildingResult> results = new ArrayList<>();
-
-        List<ModelProblem> problems = new ArrayList<>();
-        Collections.addAll(
-                problems,
-                new DefaultModelProblem("Some error", ModelProblem.Severity.ERROR, null, "unknown", 1, 1, null, null));
-        DefaultProjectBuildingResult result = new DefaultProjectBuildingResult(null, null, problems);
-        results.add(result);
-
-        ProjectBuildingException exception = new ProjectBuildingException(results);
-        String message = exception.getMessage();
-
-        assertTrue(message.contains("[unknown project]"), "Message should handle unknown project gracefully");
-    }
-}