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

bodewig pushed a commit to branch tell-errors-and-failures-apart-in-junitlaucher
in repository https://gitbox.apache.org/repos/asf/ant.git

commit 1391074ede617b11314762428f05ce11fb16feac
Author: Stefan Bodewig <bode...@apache.org>
AuthorDate: Sun Jun 8 14:09:13 2025 +0200

    differenciate between errors and failures for junitlauncher
---
 WHATSNEW                                           |  4 ++
 .../AbstractJUnitResultFormatter.java              |  5 ++
 .../junitlauncher/LegacyPlainResultFormatter.java  | 14 +++-
 .../junitlauncher/LegacyXmlResultFormatter.java    | 35 ++++++++--
 .../LegacyXmlResultFormatterTest.java              | 79 ++++++++++++++++++++++
 5 files changed, 130 insertions(+), 7 deletions(-)

diff --git a/WHATSNEW b/WHATSNEW
index 3cba7a7b4..d8279a3d7 100644
--- a/WHATSNEW
+++ b/WHATSNEW
@@ -32,6 +32,10 @@ Fixed bugs:
    path even if it was not a file separator.
    Bugzilla Report 69680
 
+ * <junitlauncher>'s legacy formatters now separate errors from
+   failures like their <junit> counterparts did.
+   Bugzilla Report 69687
+
 Other changes:
 --------------
 
diff --git 
a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/AbstractJUnitResultFormatter.java
 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/AbstractJUnitResultFormatter.java
index be70e94dd..9376882fc 100644
--- 
a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/AbstractJUnitResultFormatter.java
+++ 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/AbstractJUnitResultFormatter.java
@@ -19,6 +19,7 @@ package org.apache.tools.ant.taskdefs.optional.junitlauncher;
 
 import org.apache.tools.ant.Project;
 import org.apache.tools.ant.util.FileUtils;
+import org.junit.platform.engine.TestExecutionResult;
 import org.junit.platform.engine.TestSource;
 import org.junit.platform.engine.support.descriptor.ClassSource;
 import org.junit.platform.launcher.TestIdentifier;
@@ -187,6 +188,10 @@ abstract class AbstractJUnitResultFormatter implements 
TestResultFormatter {
     }
 
 
+    protected static boolean isFailure(final TestExecutionResult 
executionResult) {
+        return executionResult.getThrowable().orElse(null) instanceof 
AssertionError;
+    }
+
     /*
     A "store" for sysout/syserr content that gets sent to the 
AbstractJUnitResultFormatter.
     This store first uses a relatively decent sized in-memory buffer for 
storing the sysout/syserr
diff --git 
a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/LegacyPlainResultFormatter.java
 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/LegacyPlainResultFormatter.java
index 7583d780c..5f1f591d8 100644
--- 
a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/LegacyPlainResultFormatter.java
+++ 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/LegacyPlainResultFormatter.java
@@ -65,6 +65,7 @@ class LegacyPlainResultFormatter extends 
AbstractJUnitResultFormatter implements
             final Stats stats = entry.getValue();
             final StringBuilder sb = new StringBuilder("Tests run: 
").append(stats.numTestsRun.get());
             sb.append(", Failures: ").append(stats.numTestsFailed.get());
+            sb.append(", Errors: ").append(stats.numTestsWithError.get());
             sb.append(", Skipped: ").append(stats.numTestsSkipped.get());
             sb.append(", Aborted: ").append(stats.numTestsAborted.get());
             sb.append(", Time elapsed: ");
@@ -188,7 +189,11 @@ class LegacyPlainResultFormatter extends 
AbstractJUnitResultFormatter implements
                     break;
                 }
                 case FAILED: {
-                    sb.append(" FAILED");
+                    if (isFailure(testExecutionResult)) {
+                        sb.append(" FAILED");
+                    } else {
+                        sb.append(" Caused an ERROR");
+                    }
                     appendThrowable(sb, testExecutionResult);
                     break;
                 }
@@ -214,7 +219,11 @@ class LegacyPlainResultFormatter extends 
AbstractJUnitResultFormatter implements
                 break;
             }
             case FAILED: {
-                parentClassStats.numTestsFailed.incrementAndGet();
+                if (isFailure(testExecutionResult)) {
+                    parentClassStats.numTestsFailed.incrementAndGet();
+                } else {
+                    parentClassStats.numTestsWithError.incrementAndGet();
+                }
                 break;
             }
         }
@@ -264,6 +273,7 @@ class LegacyPlainResultFormatter extends 
AbstractJUnitResultFormatter implements
         private final TestIdentifier testIdentifier;
         private final AtomicLong numTestsRun = new AtomicLong(0);
         private final AtomicLong numTestsFailed = new AtomicLong(0);
+        private final AtomicLong numTestsWithError = new AtomicLong(0);
         private final AtomicLong numTestsSkipped = new AtomicLong(0);
         private final AtomicLong numTestsAborted = new AtomicLong(0);
         private final long startedAt;
diff --git 
a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/LegacyXmlResultFormatter.java
 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/LegacyXmlResultFormatter.java
index f72822d38..ca3f7f41d 100644
--- 
a/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/LegacyXmlResultFormatter.java
+++ 
b/src/main/org/apache/tools/ant/taskdefs/optional/junitlauncher/LegacyXmlResultFormatter.java
@@ -55,6 +55,7 @@ class LegacyXmlResultFormatter extends 
AbstractJUnitResultFormatter implements T
     private final Map<TestIdentifier, Stats> testIds = new 
ConcurrentHashMap<>();
     private final Map<TestIdentifier, Optional<String>> skipped = new 
ConcurrentHashMap<>();
     private final Map<TestIdentifier, Optional<Throwable>> failed = new 
ConcurrentHashMap<>();
+    private final Map<TestIdentifier, Optional<Throwable>> errored = new 
ConcurrentHashMap<>();
     private final Map<TestIdentifier, Optional<Throwable>> aborted = new 
ConcurrentHashMap<>();
 
     private TestPlan testPlan;
@@ -62,6 +63,7 @@ class LegacyXmlResultFormatter extends 
AbstractJUnitResultFormatter implements T
     private long testPlanEndedAt = -1;
     private final AtomicLong numTestsRun = new AtomicLong(0);
     private final AtomicLong numTestsFailed = new AtomicLong(0);
+    private final AtomicLong numTestsErrored = new AtomicLong(0);
     private final AtomicLong numTestsSkipped = new AtomicLong(0);
     private final AtomicLong numTestsAborted = new AtomicLong(0);
     private boolean useLegacyReportingName = true;
@@ -126,8 +128,13 @@ class LegacyXmlResultFormatter extends 
AbstractJUnitResultFormatter implements T
                 break;
             }
             case FAILED: {
-                this.numTestsFailed.incrementAndGet();
-                this.failed.put(testIdentifier, 
testExecutionResult.getThrowable());
+                if (isFailure(testExecutionResult)) {
+                    this.numTestsFailed.incrementAndGet();
+                    this.failed.put(testIdentifier, 
testExecutionResult.getThrowable());
+                } else {
+                    this.numTestsErrored.incrementAndGet();
+                    this.errored.put(testIdentifier, 
testExecutionResult.getThrowable());
+                }
                 break;
             }
         }
@@ -168,6 +175,7 @@ class LegacyXmlResultFormatter extends 
AbstractJUnitResultFormatter implements T
         private static final String ELEM_TESTCASE = "testcase";
         private static final String ELEM_SKIPPED = "skipped";
         private static final String ELEM_FAILURE = "failure";
+        private static final String ELEM_ERROR = "error";
         private static final String ELEM_ABORTED = "aborted";
         private static final String ELEM_SYSTEM_OUT = "system-out";
         private static final String ELEM_SYSTEM_ERR = "system-err";
@@ -180,6 +188,7 @@ class LegacyXmlResultFormatter extends 
AbstractJUnitResultFormatter implements T
         private static final String ATTR_TIMESTAMP = "timestamp";
         private static final String ATTR_NUM_ABORTED = "aborted";
         private static final String ATTR_NUM_FAILURES = "failures";
+        private static final String ATTR_NUM_ERRORS = "errors";
         private static final String ATTR_NUM_TESTS = "tests";
         private static final String ATTR_NUM_SKIPPED = "skipped";
         private static final String ATTR_MESSAGE = "message";
@@ -208,6 +217,7 @@ class LegacyXmlResultFormatter extends 
AbstractJUnitResultFormatter implements T
             writeAttribute(writer, ATTR_TIMESTAMP, timestamp);
             writeAttribute(writer, ATTR_NUM_TESTS, 
String.valueOf(numTestsRun.longValue()));
             writeAttribute(writer, ATTR_NUM_FAILURES, 
String.valueOf(numTestsFailed.longValue()));
+            writeAttribute(writer, ATTR_NUM_ERRORS, 
String.valueOf(numTestsErrored.longValue()));
             writeAttribute(writer, ATTR_NUM_SKIPPED, 
String.valueOf(numTestsSkipped.longValue()));
             writeAttribute(writer, ATTR_NUM_ABORTED, 
String.valueOf(numTestsAborted.longValue()));
 
@@ -239,7 +249,7 @@ class LegacyXmlResultFormatter extends 
AbstractJUnitResultFormatter implements T
         void writeTestCase(final XMLStreamWriter writer) throws 
XMLStreamException {
             for (final Map.Entry<TestIdentifier, Stats> entry : 
testIds.entrySet()) {
                 final TestIdentifier testId = entry.getKey();
-                if (!testId.isTest() && !failed.containsKey(testId)) {
+                if (!testId.isTest() && !failed.containsKey(testId) && 
!errored.containsKey(testId)) {
                     // only interested in test methods unless there was a 
failure,
                     // in which case we want the exception reported
                     // (https://bz.apache.org/bugzilla/show_bug.cgi?id=63850)
@@ -283,6 +293,8 @@ class LegacyXmlResultFormatter extends 
AbstractJUnitResultFormatter implements T
                 writeSkipped(writer, testId);
                 // failed element if the test failed
                 writeFailed(writer, testId);
+                // error element if the test caused an error
+                writeErrored(writer, testId);
                 // aborted element if the test was aborted
                 writeAborted(writer, testId);
 
@@ -306,8 +318,21 @@ class LegacyXmlResultFormatter extends 
AbstractJUnitResultFormatter implements T
             if (!failed.containsKey(testIdentifier)) {
                 return;
             }
-            writer.writeStartElement(ELEM_FAILURE);
-            final Optional<Throwable> cause = failed.get(testIdentifier);
+            writeFailedOrErrored(writer, ELEM_FAILURE, 
failed.get(testIdentifier));
+        }
+
+        private void writeErrored(final XMLStreamWriter writer, final 
TestIdentifier testIdentifier) throws XMLStreamException {
+            if (!errored.containsKey(testIdentifier)) {
+                return;
+            }
+            writeFailedOrErrored(writer, ELEM_ERROR, 
errored.get(testIdentifier));
+        }
+
+        private void writeFailedOrErrored(final XMLStreamWriter writer,
+                                          final String elementName,
+                                          final Optional<Throwable> cause)
+            throws XMLStreamException {
+            writer.writeStartElement(elementName);
             if (cause.isPresent()) {
                 final Throwable t = cause.get();
                 final String message = t.getMessage();
diff --git 
a/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junitlauncher/LegacyXmlResultFormatterTest.java
 
b/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junitlauncher/LegacyXmlResultFormatterTest.java
index 3735de838..cd422096c 100644
--- 
a/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junitlauncher/LegacyXmlResultFormatterTest.java
+++ 
b/src/tests/junit/org/apache/tools/ant/taskdefs/optional/junitlauncher/LegacyXmlResultFormatterTest.java
@@ -20,18 +20,36 @@ package 
org.apache.tools.ant.taskdefs.optional.junitlauncher;
 import org.apache.tools.ant.Project;
 import org.junit.Test;
 import org.junit.platform.engine.ConfigurationParameters;
+import org.junit.platform.engine.TestDescriptor;
+import org.junit.platform.engine.TestExecutionResult;
+import org.junit.platform.engine.UniqueId;
+import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor;
+import org.junit.platform.launcher.TestIdentifier;
 import org.junit.platform.launcher.TestPlan;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
 import java.nio.charset.StandardCharsets;
 import java.util.Collections;
 import java.util.Optional;
 import java.util.Properties;
 import java.util.Set;
 import java.util.function.Function;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.sax.SAXSource;
+import javax.xml.transform.stream.StreamSource;
 
 import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.instanceOf;
 import static org.junit.Assert.assertThat;
 
 public class LegacyXmlResultFormatterTest {
@@ -57,6 +75,56 @@ public class LegacyXmlResultFormatterTest {
         assertThat(result, containsString(ENCODED));
     }
 
+    @Test
+    public void properlyReportsFailures() throws Exception {
+        properlyReportsFailuresAndErrors(new AssertionError("failed", null), 
true);
+    }
+
+    @Test
+    public void properlyReportsErrors() throws Exception {
+        properlyReportsFailuresAndErrors(new NullPointerException("failed"), 
false);
+    }
+
+    private void properlyReportsFailuresAndErrors(Throwable errorOrFailure,
+                                                  boolean shouldBeFailure)
+        throws Exception {
+
+        final TestPlan plan = startTest(false);
+        final TestDescriptor testDescriptor = new 
DummyTestDescriptor("failure");
+        final TestIdentifier test = TestIdentifier.from(testDescriptor);
+        f.executionStarted(test);
+        final TestExecutionResult testResult = 
TestExecutionResult.failed(errorOrFailure);
+        f.executionFinished(test, testResult);
+        final String result = finishTest(plan);
+
+        final int expectedFailureCount = shouldBeFailure ? 1 : 0;
+        final int expectedErrorCount = shouldBeFailure ? 0 : 1;
+
+        final StreamSource source = new StreamSource() {
+                @Override
+                public Reader getReader() {
+                    return new StringReader(result);
+                }
+            };
+        final DocumentBuilderFactory dbf = 
DocumentBuilderFactory.newInstance();
+        final InputSource is = SAXSource.sourceToInputSource(source);
+        final DocumentBuilder b = dbf.newDocumentBuilder();
+        final Document doc = b.parse(is);
+        final Element suite = doc.getDocumentElement();
+        assertThat(suite.getTagName(), equalTo("testsuite"));
+        assertThat(suite.getAttribute("failures"), 
equalTo(expectedFailureCount + ""));
+        assertThat(suite.getAttribute("errors"), equalTo(expectedErrorCount + 
""));
+        final NodeList testCases = suite.getElementsByTagName("testcase");
+        assertThat(testCases.getLength(), equalTo(1));
+        final Node testCase = testCases.item(0);
+        assertThat(testCase, instanceOf(Element.class));
+        final Element testCaseElement = (Element) testCase;
+        NodeList failureElements = 
testCaseElement.getElementsByTagName("failure");
+        assertThat(failureElements.getLength(), equalTo(expectedFailureCount));
+        NodeList errorElements = testCaseElement.getElementsByTagName("error");
+        assertThat(errorElements.getLength(), equalTo(expectedErrorCount));
+    }
+
     private TestPlan startTest(final boolean withProperties) {
         f.setContext(new TestExecutionContext() {
             @Override
@@ -111,4 +179,15 @@ public class LegacyXmlResultFormatterTest {
             return new String(bos.toByteArray(), StandardCharsets.UTF_8);
         }
     }
+
+    private static class DummyTestDescriptor extends AbstractTestDescriptor {
+        private DummyTestDescriptor(String displayName) {
+            super(UniqueId.forEngine("testengine"), displayName);
+        }
+
+        @Override
+        public TestDescriptor.Type getType() {
+            return TestDescriptor.Type.TEST;
+        }
+    }
 }

Reply via email to