stevedlawrence commented on code in PR #1204:
URL: https://github.com/apache/daffodil/pull/1204#discussion_r1756898964


##########
daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLRunner.scala:
##########
@@ -634,15 +642,32 @@ abstract class TestCase(testCaseXML: NodeSeq, val parent: 
DFDLTestSuite) {
   lazy val optExpectedOrInputInfoset = (testCaseXML \ 
"infoset").headOption.map { node =>
     new Infoset(node, this)
   }
-  lazy val optExpectedErrors: Option[ExpectedErrors] = (testCaseXML \ 
"errors").headOption.map {
-    node => ExpectedErrors(node, this)
+  lazy val optExpectedErrors: Option[Seq[ExpectedErrors]] = {
+    val errors = testCaseXML \ "errors"
+    val error = (errors \\ "error")

Review Comment:
   Thoughts on moving the `<tdml:errors />` check to the `ExpectedErrors class? 
It simplifies this a bit and means the ExpectedErrors class contains the logic 
about what makes an ExpectedError valid or not, which is probably where that 
logic belongs, especially since it alreadys is looking a the `<error>` 
elements. Something like
   
   ```scala
   val diagnoticNodes = {
     val nodes = node \\ "error"
     if (nodes.isEmpty) throw TDMLExceptino(...)
     nodes
   }
   ```
   
   And then this function just becomes
   ```scala
   val errors = testCaseXML \ "errors"
   val s = errors.map { node => ExpectedErrors(node, this) }
   if (s.nonEmpty) Some(s) else None
   ```
   
   Same for expected warnings.
   
   This also makes it consitent with the ExpectedValidationErrors change. So 
the only differences are withing the ExpectedErrors/Warning classes.



##########
daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLRunner.scala:
##########
@@ -1806,68 +1881,101 @@ object VerifyTestCase {
    * @param expectedDiags                             Expected diagnostics 
from test cases
    * @param implString                                Implementation string
    */
-  def verifyAllDiagnosticsFound(
+  def verifyDiagnosticsFound(
     actualDiags: Seq[Diagnostic],
-    expectedDiags: Option[ErrorWarningBase],
+    expectedDiags: Option[Seq[ErrorWarningBase]],
+    ignoreUnexpectedWarnings: Boolean,

Review Comment:
   Can this parameter be changed to a more generic `ignoreUnexpectedDiags: 
Boolean`? For example, when the `expectedDiags` are `ExpectedErrors` we would 
always pass in false (we never ignore unexpected errors). When `expectedDiags` 
are `ExpectedWarnings` we would pass in the value of 
`ignoreUnexpectedWarnings`. That way this function doesn't need to have have 
specific logic for warnings (or validationErrors when we add 
ignoreUnexpectedValidationErrors). It just has something like:
   
   ```scala
   val hasUnexpectedDiags = actualDiagsFiltered.isNonEmpty && 
expectedDiags.isEmpty
   if (hasUnexpectedDiags && !ignoreUnexpectedDiags)
     throw new TDMLExecption(found unexpected diags)
   ```



##########
daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLRunner.scala:
##########
@@ -1806,68 +1881,101 @@ object VerifyTestCase {
    * @param expectedDiags                             Expected diagnostics 
from test cases
    * @param implString                                Implementation string
    */
-  def verifyAllDiagnosticsFound(
+  def verifyDiagnosticsFound(
     actualDiags: Seq[Diagnostic],
-    expectedDiags: Option[ErrorWarningBase],
+    expectedDiags: Option[Seq[ErrorWarningBase]],
+    ignoreUnexpectedWarnings: Boolean,
     implString: Option[String]
-  ) = {
+  ): Option[Seq[Unit]] = {
 
-    val actualDiagMsgs = actualDiags.map {
-      _.toString()
-    }
     val expectedDiagMsgs = expectedDiags
-      .map {
-        _.messages
+      .map { d =>
+        d.flatMap(_.messages)
       }
       .getOrElse(Nil)
 
-    if (expectedDiags.isDefined && actualDiags.isEmpty) {
+    val expectedDiagnosticType = if (expectedDiags.isDefined) {
+      expectedDiags.get.head.diagnosticType
+    } else {
+      ""
+    }
+    val actualDiagsFiltered = if (expectedDiagnosticType == "validation 
error") {
+      actualDiags.filter(diag => diag.isValidation)
+    } else if (expectedDiagnosticType == "warning") {
+      actualDiags.filterNot(d => d.isValidation || d.isError)
+    } else if (expectedDiagnosticType == "error") {
+      actualDiags.filter(_.isError)
+    } else {
+      Seq.empty
+    }
+
+    val actualDiagsFilteredMessages = actualDiagsFiltered.map(_.toString())
+
+    // throw exception if we have no actualDiags, but have expected diagnostics
+    if (
+      expectedDiags.isDefined
+      && actualDiagsFiltered.isEmpty
+    ) {
       throw TDMLException(
         """"Diagnostic message(s) were expected but not found."""" +
           "\n" +
           """Expected: """ + expectedDiagMsgs.mkString("\n") +
-          (if (actualDiagMsgs.isEmpty)
-             "\n No diagnostic messages were issued."
+          (if (actualDiagsFiltered.isEmpty)
+             s"\n No ${expectedDiagnosticType} diagnostic messages were 
issued."
            else
-             "\n The actual diagnostics messages were: " + 
actualDiagMsgs.mkString("\n")),
+             "\n The actual diagnostics messages were: " + 
actualDiagsFilteredMessages.mkString(
+               "\n"
+             )),
         implString
       )
     }
 
-    // must find each expected warning message within some actual warning 
message.
-    expectedDiags.foreach { expectedDiag =>
-      {
-        expectedDiag.messages.foreach { expectedMsg =>
-          {
-            val wasFound = actualDiags.exists { actualDiag =>
-              val actualDiagMsg = actualDiag.getMessage()
-              actualDiag.isError == expectedDiag.isError &&
-              actualDiagMsg.toLowerCase.contains(expectedMsg.toLowerCase)
-            }
-            if (!wasFound) {
-              throw TDMLException(
-                s"""Did not find diagnostic ${expectedDiag.diagnosticType} 
message """" +
-                  expectedMsg +
-                  """" in any of the actual diagnostic messages: """ + "\n" +
-                  actualDiagMsgs.mkString("\n"),
-                implString
-              )
-            }
+    if (
+      !actualDiags.forall(d => d.isValidation || d.isError)
+      && expectedDiags.isEmpty
+      && !ignoreUnexpectedWarnings
+    ) {
+      throw TDMLException(
+        s"ignoreUnexpectedWarnings = false and test does not expect warnings, 
but created the following: " +
+          s"${actualDiags.filterNot(d => d.isValidation || 
d.isError).mkString("\n")}",
+        implString
+      )
+    }

Review Comment:
   Yeah, here it might be nice to make this a more generic check.



##########
daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLRunner.scala:
##########
@@ -1806,68 +1881,101 @@ object VerifyTestCase {
    * @param expectedDiags                             Expected diagnostics 
from test cases
    * @param implString                                Implementation string
    */
-  def verifyAllDiagnosticsFound(
+  def verifyDiagnosticsFound(
     actualDiags: Seq[Diagnostic],
-    expectedDiags: Option[ErrorWarningBase],
+    expectedDiags: Option[Seq[ErrorWarningBase]],
+    ignoreUnexpectedWarnings: Boolean,
     implString: Option[String]
-  ) = {
+  ): Option[Seq[Unit]] = {
 
-    val actualDiagMsgs = actualDiags.map {
-      _.toString()
-    }
     val expectedDiagMsgs = expectedDiags
-      .map {
-        _.messages
+      .map { d =>
+        d.flatMap(_.messages)
       }
       .getOrElse(Nil)
 
-    if (expectedDiags.isDefined && actualDiags.isEmpty) {
+    val expectedDiagnosticType = if (expectedDiags.isDefined) {
+      expectedDiags.get.head.diagnosticType
+    } else {
+      ""
+    }
+    val actualDiagsFiltered = if (expectedDiagnosticType == "validation 
error") {
+      actualDiags.filter(diag => diag.isValidation)
+    } else if (expectedDiagnosticType == "warning") {
+      actualDiags.filterNot(d => d.isValidation || d.isError)
+    } else if (expectedDiagnosticType == "error") {
+      actualDiags.filter(_.isError)
+    } else {
+      Seq.empty
+    }
+
+    val actualDiagsFilteredMessages = actualDiagsFiltered.map(_.toString())
+
+    // throw exception if we have no actualDiags, but have expected diagnostics
+    if (
+      expectedDiags.isDefined
+      && actualDiagsFiltered.isEmpty
+    ) {
       throw TDMLException(
         """"Diagnostic message(s) were expected but not found."""" +
           "\n" +
           """Expected: """ + expectedDiagMsgs.mkString("\n") +
-          (if (actualDiagMsgs.isEmpty)
-             "\n No diagnostic messages were issued."
+          (if (actualDiagsFiltered.isEmpty)
+             s"\n No ${expectedDiagnosticType} diagnostic messages were 
issued."
            else
-             "\n The actual diagnostics messages were: " + 
actualDiagMsgs.mkString("\n")),
+             "\n The actual diagnostics messages were: " + 
actualDiagsFilteredMessages.mkString(
+               "\n"
+             )),
         implString
       )
     }
 
-    // must find each expected warning message within some actual warning 
message.
-    expectedDiags.foreach { expectedDiag =>
-      {
-        expectedDiag.messages.foreach { expectedMsg =>
-          {
-            val wasFound = actualDiags.exists { actualDiag =>
-              val actualDiagMsg = actualDiag.getMessage()
-              actualDiag.isError == expectedDiag.isError &&
-              actualDiagMsg.toLowerCase.contains(expectedMsg.toLowerCase)
-            }
-            if (!wasFound) {
-              throw TDMLException(
-                s"""Did not find diagnostic ${expectedDiag.diagnosticType} 
message """" +
-                  expectedMsg +
-                  """" in any of the actual diagnostic messages: """ + "\n" +
-                  actualDiagMsgs.mkString("\n"),
-                implString
-              )
-            }
+    if (
+      !actualDiags.forall(d => d.isValidation || d.isError)
+      && expectedDiags.isEmpty
+      && !ignoreUnexpectedWarnings
+    ) {
+      throw TDMLException(
+        s"ignoreUnexpectedWarnings = false and test does not expect warnings, 
but created the following: " +
+          s"${actualDiags.filterNot(d => d.isValidation || 
d.isError).mkString("\n")}",
+        implString
+      )
+    }
+
+    expectedDiags.map { diags =>
+      diags.map { diag =>
+        val matchAttr = diag.matchAttrib
+        val diagsFound = diag.messages.map { d =>
+          // make case insensitive
+          val wasFound =
+            actualDiagsFilteredMessages.exists(ad => 
ad.toLowerCase.contains(d.toLowerCase))
+          (d, wasFound)
+        }
+        matchAttr match {
+          case "all" if (!diagsFound.forall(_._2)) => {

Review Comment:
   Technically, you probably want the condition inside the case block. For 
example, currently if matchAttr is "all" but the condition fails then it will 
do the "none" and efault cases, which shouldn't happen. And that way, the 
default case implies the user had something like `match="foo"` and it should be 
an error.



##########
daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLRunner.scala:
##########
@@ -169,6 +169,8 @@ private[tdml] object DFDLTestSuite {
  *
  * shouldDoWarningComparisonOnCrossTests controls whether test warning 
messages  are compared
  * during cross testing.
+ *
+ * defaultNoWarningsDefault specifies that a test should not create warnings 
at all

Review Comment:
   Update comment to reference new name and meaning.



##########
daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLRunner.scala:
##########
@@ -959,32 +990,45 @@ abstract class TestCase(testCaseXML: NodeSeq, val parent: 
DFDLTestSuite) {
 
   protected def checkDiagnosticMessages(
     diagnostics: Seq[Diagnostic],
-    errors: ExpectedErrors,
-    optWarnings: Option[ExpectedWarnings],
+    errors: Seq[ExpectedErrors],

Review Comment:
   Is there value in making `errors` an `Option[Seq[ExpectedErrors]]` and then 
checkDiagnosticMessages can be use anywhere diagnostics need to be checked, not 
just for the case where errors are expected? The TDML Runner has really gotten 
complex over time, so having one way to check diagnostics might help to 
simplify things. You went down the path of making checkDiagnostics also do 
validation checks so it could be used in more places. Would making errors 
optional make it allowed to be used everywhere?



##########
daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLRunner.scala:
##########
@@ -1806,68 +1881,101 @@ object VerifyTestCase {
    * @param expectedDiags                             Expected diagnostics 
from test cases
    * @param implString                                Implementation string
    */
-  def verifyAllDiagnosticsFound(
+  def verifyDiagnosticsFound(
     actualDiags: Seq[Diagnostic],
-    expectedDiags: Option[ErrorWarningBase],
+    expectedDiags: Option[Seq[ErrorWarningBase]],
+    ignoreUnexpectedWarnings: Boolean,
     implString: Option[String]
-  ) = {
+  ): Option[Seq[Unit]] = {

Review Comment:
   Can this just return a `Unit`? I think this function either throws an 
exception if there are problems or returns nothing?



##########
daffodil-tdml-lib/src/main/scala/org/apache/daffodil/tdml/TDMLRunner.scala:
##########
@@ -1806,68 +1881,101 @@ object VerifyTestCase {
    * @param expectedDiags                             Expected diagnostics 
from test cases
    * @param implString                                Implementation string
    */
-  def verifyAllDiagnosticsFound(
+  def verifyDiagnosticsFound(
     actualDiags: Seq[Diagnostic],
-    expectedDiags: Option[ErrorWarningBase],
+    expectedDiags: Option[Seq[ErrorWarningBase]],
+    ignoreUnexpectedWarnings: Boolean,
     implString: Option[String]
-  ) = {
+  ): Option[Seq[Unit]] = {
 
-    val actualDiagMsgs = actualDiags.map {
-      _.toString()
-    }
     val expectedDiagMsgs = expectedDiags
-      .map {
-        _.messages
+      .map { d =>
+        d.flatMap(_.messages)
       }
       .getOrElse(Nil)
 
-    if (expectedDiags.isDefined && actualDiags.isEmpty) {
+    val expectedDiagnosticType = if (expectedDiags.isDefined) {
+      expectedDiags.get.head.diagnosticType
+    } else {
+      ""
+    }
+    val actualDiagsFiltered = if (expectedDiagnosticType == "validation 
error") {
+      actualDiags.filter(diag => diag.isValidation)
+    } else if (expectedDiagnosticType == "warning") {
+      actualDiags.filterNot(d => d.isValidation || d.isError)
+    } else if (expectedDiagnosticType == "error") {
+      actualDiags.filter(_.isError)
+    } else {
+      Seq.empty
+    }

Review Comment:
   I general prefer to avoid comparing strings, they tend to be fragile. For 
example, if we one day decide to capitalize these this stuff will break. 
Instead, we can use Scala type system, e.g.
   
   ```
   val expectedDiagType = expectedDiags.headOption
   val actualDiagsFiltered = expectedDiagType match {
     case Some(_: ExpectedValidationError) => actualDiags.filter(diag => 
diag.isValidation)
     case Some(_: ExpectedWarning) => actualDiags.filterNot(d => d.isValidation 
|| d.isError)
     case Some(_: ExpectedError) => actualDiags.filter(_.isError)
     case None => Seq.empty
   }
   ```
   That said, it might be useful to pass in an enum or a filter function or 
something as a paramter, or do the filtering in checkDiagnosticMessages 
(assuming that can be updated so everyone calls it). Something like that might 
be preferred, since this ends up with `Seq.empty` if we don't know the type, 
which makes it look like there are no actual diagnostics to check.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to