differenciate between errors and failures for junitlauncher
diff --git a/WHATSNEW b/WHATSNEW
index 3cba7a7..d8279a3 100644
--- a/WHATSNEW
+++ b/WHATSNEW
@@ -32,6 +32,10 @@
    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 be70e94..9376882 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 @@
 
 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 @@
     }
 
 
+    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 7583d78..5f1f591 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 @@
             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 @@
                     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 @@
                 break;
             }
             case FAILED: {
-                parentClassStats.numTestsFailed.incrementAndGet();
+                if (isFailure(testExecutionResult)) {
+                    parentClassStats.numTestsFailed.incrementAndGet();
+                } else {
+                    parentClassStats.numTestsWithError.incrementAndGet();
+                }
                 break;
             }
         }
@@ -264,6 +273,7 @@
         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 f72822d..ca3f7f4 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 @@
     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 @@
     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 @@
                 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 @@
         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 @@
         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 @@
             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 @@
         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 @@
                 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 @@
             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 3735de8..cd42209 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 @@
 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 @@
         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 @@
             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;
+        }
+    }
 }