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

stevedlawrence pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/daffodil.git


The following commit(s) were added to refs/heads/main by this push:
     new b9fb1e53b Fix duplicated TDML exception output and simplify exception 
handling
b9fb1e53b is described below

commit b9fb1e53ba95cdf5727495cf80a8f768660cb71c
Author: Guichard Desrosiers <[email protected]>
AuthorDate: Tue May 5 21:05:06 2026 -0400

    Fix duplicated TDML exception output and simplify exception handling
    
    TDMLException previously embedded cause message text in the outer
    exception message while also passing the same cause into the Java
    Exception cause chain. This resulted in duplicate output, where the
    same error appeared both in the exception message and again in the
    "Caused by:" section.
    
    -Simplified TDMLException to support two construction paths only:
     one for explicit message strings, and one for wrapping a Throwable.
     Removed the causes field from the TDMLException trait and use cause
     from TDMLExceptionImpl constructor parameter.
    -Also removed the constructor that accepted multiple causes. Callers
     that previously passed multiple exceptions now aggregate their
     messages into a single newline-delimited message before constructing
     the string-based exception.
    -Additionally updated TDMLRunner to catch XMLDifferenceException
     directly when calling XMLUtils.compareAndReport, since that method
     specifically throws XMLDifferenceException.
    
    DAFFODIL-3078
---
 .../org/apache/daffodil/tdml/TDMLException.scala   | 26 +++++-----------
 .../org/apache/daffodil/tdml/TDMLRunner.scala      | 35 ++++++++++++----------
 2 files changed, 28 insertions(+), 33 deletions(-)

diff --git 
a/daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLException.scala 
b/daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLException.scala
index f17289448..1d46f1f51 100644
--- 
a/daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLException.scala
+++ 
b/daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLException.scala
@@ -34,8 +34,6 @@ object TDMLException {
     new TDMLExceptionImpl(msg, implementation)
   def apply(cause: Throwable, implementation: Option[String]) =
     new TDMLExceptionImpl(cause, implementation)
-  def apply(causes: Iterable[Throwable], implementation: Option[String]) =
-    new TDMLExceptionImpl(causes, implementation)
 }
 
 /**
@@ -46,7 +44,6 @@ object TDMLException {
  */
 trait TDMLException { self: Exception =>
   def msg: String
-  def causes: Iterable[Throwable]
   def implementation: Option[String]
 
   /**
@@ -63,27 +60,20 @@ trait TDMLException { self: Exception =>
 
 class TDMLExceptionImpl(
   override val msg: String,
-  override val causes: Iterable[Throwable],
+  cause: Throwable,
   override val implementation: Option[String]
 ) extends Exception(
     TDMLException.msgWithImpl(msg, implementation),
-    if (causes.nonEmpty) causes.head else null
+    cause
   )
   with TDMLException {
-
+  // message-only constructor
   def this(msg: String, implementation: Option[String]) =
-    this(msg, Nil, implementation)
+    this(msg, null, implementation)
 
+  // cause-based constructor
   def this(cause: Throwable, implementation: Option[String]) =
-    this(Misc.getNameFromClass(cause) + ": " + cause.getMessage(), 
List(cause), implementation)
-
-  def this(causes: Iterable[Throwable], implementation: Option[String]) = this(
-    causes
-      .map { cause => Misc.getNameFromClass(cause) + ": " + cause.getMessage() 
}
-      .mkString("\n"),
-    causes,
-    implementation
-  )
+    this(Misc.getNameFromClass(cause), cause, implementation)
 }
 
 /**
@@ -105,7 +95,7 @@ class TDMLDiagnostic(diag: String, implementation: 
Option[String])
  * with the implementation. Useful since this isn't necessarily a failure and
  * may want to be treated differently in some cases.
  *
- * Carries causes because a failure to detect compatibility can be due to
+ * Carries a cause because a failure to detect compatibility can be due to
  * failures to reflectively create a junit 
org.junit.AssumptionViolatedException, and
  * if that is the case, we may need the exception to figure out the reason why
  * the reflective access failed.
@@ -116,6 +106,6 @@ class TDMLTestNotCompatibleException(
   cause: Option[Throwable] = None
 ) extends TDMLExceptionImpl(
     "Test '%s' not compatible with implementation.".format(testName),
-    cause.toSeq,
+    cause.orNull,
     implementation
   )
diff --git 
a/daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLRunner.scala 
b/daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLRunner.scala
index 0549c61ac..24ea5857b 100644
--- a/daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLRunner.scala
+++ b/daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLRunner.scala
@@ -75,6 +75,7 @@ import org.apache.daffodil.lib.util.SchemaUtils
 import org.apache.daffodil.lib.util.ThreadSafePool
 import org.apache.daffodil.lib.xml.DaffodilXMLLoader
 import org.apache.daffodil.lib.xml.XMLUtils
+import org.apache.daffodil.lib.xml.XMLUtils.XMLDifferenceException
 import org.apache.daffodil.tdml.DiagnosticType.DiagnosticType
 import org.apache.daffodil.tdml.processor.AbstractTDMLDFDLProcessorFactory
 import org.apache.daffodil.tdml.processor.TDML
@@ -140,6 +141,13 @@ private[tdml] object DFDLTestSuite {
 
 }
 
+private[tdml] def aggregateExceptionMessages(
+  exceptions: Iterable[Throwable]
+): String =
+  exceptions
+    .map(_.getMessage)
+    .mkString("\n")
+
 /**
  * TDML test suite runner
  *
@@ -375,7 +383,7 @@ class DFDLTestSuite private[tdml] (
   }
 
   def reportLoadingErrors(): Nothing = {
-    throw TDMLException(loadingExceptions, None)
+    throw TDMLException(aggregateExceptionMessages(loadingExceptions), None)
   }
 
   var checkAllTopLevel: Boolean = compileAllTopLevel
@@ -819,7 +827,8 @@ abstract class TestCase(testCaseXML: NodeSeq, val parent: 
DFDLTestSuite) {
           Some(XMLUtils.dafextURI)
         )
         if (node eq null)
-          throw TDMLException(parent.loadingExceptions, None)
+          throw 
TDMLException(aggregateExceptionMessages(parent.loadingExceptions), None)
+
         val definedConfig = DefinedConfig(node, parent)
         Some(definedConfig)
       }
@@ -1212,9 +1221,7 @@ case class ParserTestCase(ptc: NodeSeq, parentArg: 
DFDLTestSuite)
 
     if (actual.isProcessingError) {
       // Means there was an error, not just warnings.
-      if (diagObjs.length == 1) throw TDMLException(diagObjs.head, implString)
-      val diags = 
actual.getDiagnostics.asScala.map(_.toString()).mkString("\n")
-      throw TDMLException(diags, implString)
+      throw TDMLException(aggregateExceptionMessages(diagObjs), implString)
     } else {
       // If we think we've succeeded, verify there are no errors
       // captured in the diagnostics. Otherwise there's probably
@@ -1283,8 +1290,7 @@ case class ParserTestCase(ptc: NodeSeq, parentArg: 
DFDLTestSuite)
     val unparseResult = processor.unparse(parseResult, outStream)
     if (unparseResult.isProcessingError) {
       val diagObjs = unparseResult.getDiagnostics.asScala
-      if (diagObjs.length == 1) throw TDMLException(diagObjs.head, implString)
-      throw TDMLException(diagObjs, implString)
+      throw TDMLException(aggregateExceptionMessages(diagObjs), implString)
     }
     unparseResult
   }
@@ -1545,7 +1551,8 @@ case class UnparserTestCase(ptc: NodeSeq, parentArg: 
DFDLTestSuite)
     (optExpectedData, optErrors) match {
       case (Some(expectedData), None) => {
         compileResult match {
-          case Left(diags) => throw TDMLException(diags.asScala, implString)
+          case Left(diags) =>
+            throw TDMLException(aggregateExceptionMessages(diags.asScala), 
implString)
           case Right((diags, proc)) => {
             processor = proc
             runUnparserExpectSuccess(
@@ -1614,7 +1621,7 @@ case class UnparserTestCase(ptc: NodeSeq, parentArg: 
DFDLTestSuite)
         case t: Throwable => toss(t, implString)
       }
     if (actual.isProcessingError)
-      throw TDMLException(actual.getDiagnostics.asScala, implString)
+      throw 
TDMLException(aggregateExceptionMessages(actual.getDiagnostics.asScala), 
implString)
 
     //
     // Test that we are getting the number of full bytes needed.
@@ -1662,9 +1669,7 @@ case class UnparserTestCase(ptc: NodeSeq, parentArg: 
DFDLTestSuite)
 
       if (parseActual.isProcessingError) {
         // Means there was an error, not just warnings.
-        val diagObjs = parseActual.getDiagnostics
-        if (diagObjs.size == 1) throw diagObjs.get(0)
-        val diags = 
parseActual.getDiagnostics.asScala.map(_.toString()).mkString("\n")
+        val diags = 
aggregateExceptionMessages(parseActual.getDiagnostics.asScala)
         throw TDMLException(diags, implString)
       }
       val loc: api.DataLocation = parseActual.currentLocation
@@ -1784,8 +1789,8 @@ object VerifyTestCase {
     try {
       XMLUtils.compareAndReport(expected, actual)
     } catch {
-      case e: Exception =>
-        throw TDMLException(e, implString)
+      case e: XMLDifferenceException =>
+        throw TDMLException(e.getMessage, implString)
     }
   }
 
@@ -2819,7 +2824,7 @@ case class DFDLInfoset(di: Node, parent: Infoset) {
     val hasMoreExceptions = before.size < nAfter
     if (hasMoreExceptions) {
       val newExceptions = (testSuite.loadingExceptions.diff(before))
-      testCase.toss(TDMLException(newExceptions, None), None)
+      testCase.toss(TDMLException(aggregateExceptionMessages(newExceptions), 
None), None)
     }
     elem.asInstanceOf[Elem]
   }

Reply via email to