فهرست منبع

ctest: support <CTestMeasurement> for runtime measurements

Teach CTest to parse output for <CTestMeasurement> in addition to
<DartMeasurement> for measurements defined at runtime.

Use a new class (cmCTestTestMeasurementXMLParser) derived from cmXMLParser
to parse the data and attributes these XML elements. This is an improvement
over our previous approach of using a series of regular expressions.

As part of this commit we also rename some member variables and methods
to make their purpose more clear.

DartStuff                        -> AllTestMeasurementsRegex
DartStuff1                       -> SingleTestMeasurementRegex
DartString                       -> TestMeasurementsOutput
GenerateDartOutput()             -> GenerateCTestXML()
GenerateRegressionImages()       -> RecordCustomTestMeasurements()
cmCTestRunTest::DartProcessing() -> ParseOutputForMeasurements()
Zack Galbreath 4 سال پیش
والد
کامیت
5489ce74b3

+ 17 - 13
Help/command/ctest_test.rst

@@ -186,29 +186,33 @@ Check the `CDash test measurement documentation
 <https://github.com/Kitware/CDash/blob/master/docs/test_measurements.md>`_
 <https://github.com/Kitware/CDash/blob/master/docs/test_measurements.md>`_
 for more information on the types of test measurements that CDash recognizes.
 for more information on the types of test measurements that CDash recognizes.
 
 
+Starting in version 3.22, CTest can parse custom measurements from tags named
+``<CTestMeasurement>`` or ``<CTestMeasurementFile>``. The older names
+``<DartMeasurement>`` and ``<DartMeasurementFile>`` are still supported.
+
 The following example demonstrates how to output a variety of custom test
 The following example demonstrates how to output a variety of custom test
 measurements.
 measurements.
 
 
 .. code-block:: c++
 .. code-block:: c++
 
 
    std::cout <<
    std::cout <<
-     "<DartMeasurement type=\"numeric/double\" name=\"score\">28.3</DartMeasurement>"
+     "<CTestMeasurement type=\"numeric/double\" name=\"score\">28.3</CTestMeasurement>"
      << std::endl;
      << std::endl;
 
 
    std::cout <<
    std::cout <<
-     "<DartMeasurement type=\"text/string\" name=\"color\">red</DartMeasurement>"
+     "<CTestMeasurement type=\"text/string\" name=\"color\">red</CTestMeasurement>"
      << std::endl;
      << std::endl;
 
 
    std::cout <<
    std::cout <<
-     "<DartMeasurement type=\"text/link\" name=\"CMake URL\">https://cmake.org</DartMeasurement>"
+     "<CTestMeasurement type=\"text/link\" name=\"CMake URL\">https://cmake.org</CTestMeasurement>"
      << std::endl;
      << std::endl;
 
 
    std::cout <<
    std::cout <<
-     "<DartMeasurement type=\"text/preformatted\" name=\"Console Output\">" <<
+     "<CTestMeasurement type=\"text/preformatted\" name=\"Console Output\">" <<
      "line 1.\n" <<
      "line 1.\n" <<
      "  \033[31;1m line 2. Bold red, and indented!\033[0;0ml\n" <<
      "  \033[31;1m line 2. Bold red, and indented!\033[0;0ml\n" <<
      "line 3. Not bold or indented...\n" <<
      "line 3. Not bold or indented...\n" <<
-     "</DartMeasurement>" << std::endl;
+     "</CTestMeasurement>" << std::endl;
 
 
 Image Measurements
 Image Measurements
 """"""""""""""""""
 """"""""""""""""""
@@ -218,16 +222,16 @@ The following example demonstrates how to upload test images to CDash.
 .. code-block:: c++
 .. code-block:: c++
 
 
    std::cout <<
    std::cout <<
-     "<DartMeasurementFile type=\"image/jpg\" name=\"TestImage\">" <<
-     "/dir/to/test_img.jpg</DartMeasurementFile>" << std::endl;
+     "<CTestMeasurementFile type=\"image/jpg\" name=\"TestImage\">" <<
+     "/dir/to/test_img.jpg</CTestMeasurementFile>" << std::endl;
 
 
    std::cout <<
    std::cout <<
-     "<DartMeasurementFile type=\"image/gif\" name=\"ValidImage\">" <<
-     "/dir/to/valid_img.gif</DartMeasurementFile>" << std::endl;
+     "<CTestMeasurementFile type=\"image/gif\" name=\"ValidImage\">" <<
+     "/dir/to/valid_img.gif</CTestMeasurementFile>" << std::endl;
 
 
    std::cout <<
    std::cout <<
-     "<DartMeasurementFile type=\"image/png\" name=\"AlgoResult\"> <<
-     "/dir/to/img.png</DartMeasurementFile>"
+     "<CTestMeasurementFile type=\"image/png\" name=\"AlgoResult\"> <<
+     "/dir/to/img.png</CTestMeasurementFile>"
      << std::endl;
      << std::endl;
 
 
 Images will be displayed together in an interactive comparison mode on CDash
 Images will be displayed together in an interactive comparison mode on CDash
@@ -253,8 +257,8 @@ The following example demonstrates how to upload non-image files to CDash.
 .. code-block:: c++
 .. code-block:: c++
 
 
    std::cout <<
    std::cout <<
-     "<DartMeasurementFile type=\"file\" name=\"MyTestInputData\">" <<
-     "/dir/to/data.csv</DartMeasurementFile>" << std::endl;
+     "<CTestMeasurementFile type=\"file\" name=\"MyTestInputData\">" <<
+     "/dir/to/data.csv</CTestMeasurementFile>" << std::endl;
 
 
 If the name of the file to upload is known at configure time, you can use the
 If the name of the file to upload is known at configure time, you can use the
 :prop_test:`ATTACHED_FILES` or :prop_test:`ATTACHED_FILES_ON_FAIL` test
 :prop_test:`ATTACHED_FILES` or :prop_test:`ATTACHED_FILES_ON_FAIL` test

+ 1 - 0
Source/CMakeLists.txt

@@ -960,6 +960,7 @@ set(CTEST_SRCS cmCTest.cxx
   CTest/cmCTestSubmitHandler.cxx
   CTest/cmCTestSubmitHandler.cxx
   CTest/cmCTestTestCommand.cxx
   CTest/cmCTestTestCommand.cxx
   CTest/cmCTestTestHandler.cxx
   CTest/cmCTestTestHandler.cxx
+  CTest/cmCTestTestMeasurementXMLParser.cxx
   CTest/cmCTestUpdateCommand.cxx
   CTest/cmCTestUpdateCommand.cxx
   CTest/cmCTestUpdateHandler.cxx
   CTest/cmCTestUpdateHandler.cxx
   CTest/cmCTestUploadCommand.cxx
   CTest/cmCTestUploadCommand.cxx

+ 1 - 1
Source/CTest/cmCTestMemCheckHandler.cxx

@@ -305,7 +305,7 @@ int cmCTestMemCheckHandler::GetDefectCount() const
   return this->DefectCount;
   return this->DefectCount;
 }
 }
 
 
-void cmCTestMemCheckHandler::GenerateDartOutput(cmXMLWriter& xml)
+void cmCTestMemCheckHandler::GenerateCTestXML(cmXMLWriter& xml)
 {
 {
   if (!this->CTest->GetProduceXML()) {
   if (!this->CTest->GetProduceXML()) {
     return;
     return;

+ 2 - 2
Source/CTest/cmCTestMemCheckHandler.h

@@ -119,9 +119,9 @@ private:
   bool InitializeMemoryChecking();
   bool InitializeMemoryChecking();
 
 
   /**
   /**
-   * Generate the Dart compatible output
+   * Generate CTest DynamicAnalysis.xml files
    */
    */
-  void GenerateDartOutput(cmXMLWriter& xml) override;
+  void GenerateCTestXML(cmXMLWriter& xml) override;
 
 
   std::vector<std::string> CustomPreMemCheck;
   std::vector<std::string> CustomPreMemCheck;
   std::vector<std::string> CustomPostMemCheck;
   std::vector<std::string> CustomPostMemCheck;

+ 12 - 8
Source/CTest/cmCTestRunTest.cxx

@@ -262,7 +262,7 @@ bool cmCTestRunTest::EndTest(size_t completed, size_t total, bool started)
     *this->TestHandler->LogFile << "Test time = " << buf << std::endl;
     *this->TestHandler->LogFile << "Test time = " << buf << std::endl;
   }
   }
 
 
-  this->DartProcessing();
+  this->ParseOutputForMeasurements();
 
 
   // if this is doing MemCheck then all the output needs to be put into
   // if this is doing MemCheck then all the output needs to be put into
   // Output since that is what is parsed by cmCTestMemCheckHandler
   // Output since that is what is parsed by cmCTestMemCheckHandler
@@ -698,18 +698,22 @@ void cmCTestRunTest::ComputeArguments()
   }
   }
 }
 }
 
 
-void cmCTestRunTest::DartProcessing()
+void cmCTestRunTest::ParseOutputForMeasurements()
 {
 {
   if (!this->ProcessOutput.empty() &&
   if (!this->ProcessOutput.empty() &&
-      this->ProcessOutput.find("<DartMeasurement") != std::string::npos) {
-    if (this->TestHandler->DartStuff.find(this->ProcessOutput)) {
-      this->TestResult.DartString = this->TestHandler->DartStuff.match(1);
+      (this->ProcessOutput.find("<DartMeasurement") != std::string::npos ||
+       this->ProcessOutput.find("<CTestMeasurement") != std::string::npos)) {
+    if (this->TestHandler->AllTestMeasurementsRegex.find(
+          this->ProcessOutput)) {
+      this->TestResult.TestMeasurementsOutput =
+        this->TestHandler->AllTestMeasurementsRegex.match(1);
       // keep searching and replacing until none are left
       // keep searching and replacing until none are left
-      while (this->TestHandler->DartStuff1.find(this->ProcessOutput)) {
+      while (this->TestHandler->SingleTestMeasurementRegex.find(
+        this->ProcessOutput)) {
         // replace the exact match for the string
         // replace the exact match for the string
         cmSystemTools::ReplaceString(
         cmSystemTools::ReplaceString(
-          this->ProcessOutput, this->TestHandler->DartStuff1.match(1).c_str(),
-          "");
+          this->ProcessOutput,
+          this->TestHandler->SingleTestMeasurementRegex.match(1).c_str(), "");
       }
       }
     }
     }
   }
   }

+ 1 - 1
Source/CTest/cmCTestRunTest.h

@@ -109,7 +109,7 @@ public:
 
 
 private:
 private:
   bool NeedsToRepeat();
   bool NeedsToRepeat();
-  void DartProcessing();
+  void ParseOutputForMeasurements();
   void ExeNotFound(std::string exe);
   void ExeNotFound(std::string exe);
   bool ForkProcess(cmDuration testTimeOut, bool explicitTimeout,
   bool ForkProcess(cmDuration testTimeOut, bool explicitTimeout,
                    std::vector<std::string>* environment,
                    std::vector<std::string>* environment,

+ 54 - 138
Source/CTest/cmCTestTestHandler.cxx

@@ -32,6 +32,7 @@
 #include "cmCTest.h"
 #include "cmCTest.h"
 #include "cmCTestMultiProcessHandler.h"
 #include "cmCTestMultiProcessHandler.h"
 #include "cmCTestResourceGroupsLexerHelper.h"
 #include "cmCTestResourceGroupsLexerHelper.h"
+#include "cmCTestTestMeasurementXMLParser.h"
 #include "cmDuration.h"
 #include "cmDuration.h"
 #include "cmExecutionStatus.h"
 #include "cmExecutionStatus.h"
 #include "cmGeneratedFileStream.h"
 #include "cmGeneratedFileStream.h"
@@ -303,16 +304,23 @@ cmCTestTestHandler::cmCTestTestHandler()
   // Support for JUnit XML output.
   // Support for JUnit XML output.
   this->JUnitXMLFileName = "";
   this->JUnitXMLFileName = "";
 
 
-  // regex to detect <DartMeasurement>...</DartMeasurement>
-  this->DartStuff.compile("(<DartMeasurement.*/DartMeasurement[a-zA-Z]*>)");
-  // regex to detect each individual <DartMeasurement>...</DartMeasurement>
-  this->DartStuff1.compile(
-    "(<DartMeasurement[^<]*</DartMeasurement[a-zA-Z]*>)");
+  // Regular expressions to scan test output for custom measurements.
 
 
-  // regex to detect <CTestDetails>...</CTestDetails>
+  // Capture the whole section of test output from the first opening
+  // <(CTest|Dart)Measurement*> tag to the last </(CTest|Dart)Measurement*>
+  // closing tag.
+  this->AllTestMeasurementsRegex.compile(
+    "(<(CTest|Dart)Measurement.*/(CTest|Dart)Measurement[a-zA-Z]*>)");
+
+  // Capture a single <(CTest|Dart)Measurement*> XML element.
+  this->SingleTestMeasurementRegex.compile(
+    "(<(CTest|Dart)Measurement[^<]*</(CTest|Dart)Measurement[a-zA-Z]*>)");
+
+  // Capture content from <CTestDetails>...</CTestDetails>
   this->CustomCompletionStatusRegex.compile(
   this->CustomCompletionStatusRegex.compile(
     "<CTestDetails>(.*)</CTestDetails>");
     "<CTestDetails>(.*)</CTestDetails>");
-  // regex to detect <CTestLabel>...</CTestLabel>
+
+  // Capture content from <CTestLabel>...</CTestLabel>
   this->CustomLabelRegex.compile("<CTestLabel>(.*)</CTestLabel>");
   this->CustomLabelRegex.compile("<CTestLabel>(.*)</CTestLabel>");
 }
 }
 
 
@@ -694,7 +702,7 @@ bool cmCTestTestHandler::GenerateXML()
       return false;
       return false;
     }
     }
     cmXMLWriter xml(xmlfile);
     cmXMLWriter xml(xmlfile);
-    this->GenerateDartOutput(xml);
+    this->GenerateCTestXML(xml);
   }
   }
 
 
   return true;
   return true;
@@ -1402,7 +1410,7 @@ void cmCTestTestHandler::GenerateTestCommand(
 {
 {
 }
 }
 
 
-void cmCTestTestHandler::GenerateDartOutput(cmXMLWriter& xml)
+void cmCTestTestHandler::GenerateCTestXML(cmXMLWriter& xml)
 {
 {
   if (!this->CTest->GetProduceXML()) {
   if (!this->CTest->GetProduceXML()) {
     return;
     return;
@@ -1438,7 +1446,7 @@ void cmCTestTestHandler::GenerateDartOutput(cmXMLWriter& xml)
         xml.Element("Value", result.ReturnValue);
         xml.Element("Value", result.ReturnValue);
         xml.EndElement(); // NamedMeasurement
         xml.EndElement(); // NamedMeasurement
       }
       }
-      this->GenerateRegressionImages(xml, result.DartString);
+      this->RecordCustomTestMeasurements(xml, result.TestMeasurementsOutput);
       xml.StartElement("NamedMeasurement");
       xml.StartElement("NamedMeasurement");
       xml.Attribute("type", "numeric/double");
       xml.Attribute("type", "numeric/double");
       xml.Attribute("name", "Execution Time");
       xml.Attribute("name", "Execution Time");
@@ -1978,124 +1986,48 @@ void cmCTestTestHandler::ExpandTestsToRunInformationForRerunFailed()
   }
   }
 }
 }
 
 
-// Just for convenience
-#define SPACE_REGEX "[ \t\r\n]"
-void cmCTestTestHandler::GenerateRegressionImages(cmXMLWriter& xml,
-                                                  const std::string& dart)
-{
-  cmsys::RegularExpression twoattributes(
-    "<DartMeasurement" SPACE_REGEX
-    "*(name|type|encoding|compression)=\"([^\"]*)\"" SPACE_REGEX
-    "*(name|type|encoding|compression)=\"([^\"]*)\"" SPACE_REGEX
-    "*>([^<]*)</DartMeasurement>");
-  cmsys::RegularExpression threeattributes(
-    "<DartMeasurement" SPACE_REGEX
-    "*(name|type|encoding|compression)=\"([^\"]*)\"" SPACE_REGEX
-    "*(name|type|encoding|compression)=\"([^\"]*)\"" SPACE_REGEX
-    "*(name|type|encoding|compression)=\"([^\"]*)\"" SPACE_REGEX
-    "*>([^<]*)</DartMeasurement>");
-  cmsys::RegularExpression fourattributes(
-    "<DartMeasurement" SPACE_REGEX
-    "*(name|type|encoding|compression)=\"([^\"]*)\"" SPACE_REGEX
-    "*(name|type|encoding|compression)=\"([^\"]*)\"" SPACE_REGEX
-    "*(name|type|encoding|compression)=\"([^\"]*)\"" SPACE_REGEX
-    "*(name|type|encoding|compression)=\"([^\"]*)\"" SPACE_REGEX
-    "*>([^<]*)</DartMeasurement>");
-  cmsys::RegularExpression cdatastart(
-    "<DartMeasurement" SPACE_REGEX
-    "*(name|type|encoding|compression)=\"([^\"]*)\"" SPACE_REGEX
-    "*(name|type|encoding|compression)=\"([^\"]*)\"" SPACE_REGEX
-    "*>" SPACE_REGEX "*<!\\[CDATA\\[");
-  cmsys::RegularExpression cdataend("]]>" SPACE_REGEX "*</DartMeasurement>");
-  cmsys::RegularExpression measurementfile(
-    "<DartMeasurementFile" SPACE_REGEX
-    "*(name|type|encoding|compression)=\"([^\"]*)\"" SPACE_REGEX
-    "*(name|type|encoding|compression)=\"([^\"]*)\"" SPACE_REGEX
-    "*>([^<]*)</DartMeasurementFile>");
-
-  bool done = false;
-  std::string cxml = dart;
-  while (!done) {
-    if (twoattributes.find(cxml)) {
-      xml.StartElement("NamedMeasurement");
-      xml.Attribute(twoattributes.match(1).c_str(), twoattributes.match(2));
-      xml.Attribute(twoattributes.match(3).c_str(), twoattributes.match(4));
-      xml.Element("Value", twoattributes.match(5));
-      xml.EndElement();
-      cxml.erase(twoattributes.start(),
-                 twoattributes.end() - twoattributes.start());
-    } else if (threeattributes.find(cxml)) {
-      xml.StartElement("NamedMeasurement");
-      xml.Attribute(threeattributes.match(1).c_str(),
-                    threeattributes.match(2));
-      xml.Attribute(threeattributes.match(3).c_str(),
-                    threeattributes.match(4));
-      xml.Attribute(threeattributes.match(5).c_str(),
-                    threeattributes.match(6));
-      xml.Element("Value", twoattributes.match(7));
-      xml.EndElement();
-      cxml.erase(threeattributes.start(),
-                 threeattributes.end() - threeattributes.start());
-    } else if (fourattributes.find(cxml)) {
+void cmCTestTestHandler::RecordCustomTestMeasurements(cmXMLWriter& xml,
+                                                      std::string content)
+{
+  while (this->SingleTestMeasurementRegex.find(content)) {
+    // Extract regex match from content and parse it as an XML element.
+    auto measurement_str = this->SingleTestMeasurementRegex.match(1);
+    auto parser = cmCTestTestMeasurementXMLParser();
+    parser.Parse(measurement_str.c_str());
+
+    if (parser.ElementName == "CTestMeasurement" ||
+        parser.ElementName == "DartMeasurement") {
       xml.StartElement("NamedMeasurement");
       xml.StartElement("NamedMeasurement");
-      xml.Attribute(fourattributes.match(1).c_str(), fourattributes.match(2));
-      xml.Attribute(fourattributes.match(3).c_str(), fourattributes.match(4));
-      xml.Attribute(fourattributes.match(5).c_str(), fourattributes.match(6));
-      xml.Attribute(fourattributes.match(7).c_str(), fourattributes.match(8));
-      xml.Element("Value", twoattributes.match(9));
+      xml.Attribute("type", parser.MeasurementType);
+      xml.Attribute("name", parser.MeasurementName);
+      xml.Element("Value", parser.CharacterData);
       xml.EndElement();
       xml.EndElement();
-      cxml.erase(fourattributes.start(),
-                 fourattributes.end() - fourattributes.start());
-    } else if (cdatastart.find(cxml) && cdataend.find(cxml)) {
-      xml.StartElement("NamedMeasurement");
-      xml.Attribute(cdatastart.match(1).c_str(), cdatastart.match(2));
-      xml.Attribute(cdatastart.match(3).c_str(), cdatastart.match(4));
-      xml.StartElement("Value");
-      xml.CData(
-        cxml.substr(cdatastart.end(), cdataend.start() - cdatastart.end()));
-      xml.EndElement(); // Value
-      xml.EndElement(); // NamedMeasurement
-      cxml.erase(cdatastart.start(), cdataend.end() - cdatastart.start());
-    } else if (measurementfile.find(cxml)) {
-      const std::string& filename =
-        cmCTest::CleanString(measurementfile.match(5));
-      if (cmSystemTools::FileExists(filename)) {
+    } else if (parser.ElementName == "CTestMeasurementFile" ||
+               parser.ElementName == "DartMeasurementFile") {
+      const std::string& filename = cmCTest::CleanString(parser.CharacterData);
+      if (!cmSystemTools::FileExists(filename)) {
+        xml.StartElement("NamedMeasurement");
+        xml.Attribute("name", parser.MeasurementName);
+        xml.Attribute("text", "text/string");
+        xml.Element("Value", "File " + filename + " not found");
+        xml.EndElement();
+        cmCTestOptionalLog(
+          this->CTest, HANDLER_OUTPUT,
+          "File \"" << filename << "\" not found." << std::endl, this->Quiet);
+      } else {
         long len = cmSystemTools::FileLength(filename);
         long len = cmSystemTools::FileLength(filename);
-        std::string k1 = measurementfile.match(1);
-        std::string v1 = measurementfile.match(2);
-        std::string k2 = measurementfile.match(3);
-        std::string v2 = measurementfile.match(4);
         if (len == 0) {
         if (len == 0) {
-          if (cmSystemTools::LowerCase(k1) == "type") {
-            v1 = "text/string";
-          }
-          if (cmSystemTools::LowerCase(k2) == "type") {
-            v2 = "text/string";
-          }
-
           xml.StartElement("NamedMeasurement");
           xml.StartElement("NamedMeasurement");
-          xml.Attribute(k1.c_str(), v1);
-          xml.Attribute(k2.c_str(), v2);
+          xml.Attribute("name", parser.MeasurementName);
+          xml.Attribute("type", "text/string");
           xml.Attribute("encoding", "none");
           xml.Attribute("encoding", "none");
           xml.Element("Value", "Image " + filename + " is empty");
           xml.Element("Value", "Image " + filename + " is empty");
           xml.EndElement();
           xml.EndElement();
         } else {
         } else {
-          std::string type;
-          std::string name;
-          if (cmSystemTools::LowerCase(k1) == "type") {
-            type = v1;
-          } else if (cmSystemTools::LowerCase(k2) == "type") {
-            type = v2;
-          }
-          if (cmSystemTools::LowerCase(k1) == "name") {
-            name = v1;
-          } else if (cmSystemTools::LowerCase(k2) == "name") {
-            name = v2;
-          }
-          if (type == "file") {
+          if (parser.MeasurementType == "file") {
             // Treat this measurement like an "ATTACHED_FILE" when the type
             // Treat this measurement like an "ATTACHED_FILE" when the type
             // is explicitly "file" (not an image).
             // is explicitly "file" (not an image).
-            this->AttachFile(xml, filename, name);
+            this->AttachFile(xml, filename, parser.MeasurementName);
           } else {
           } else {
             cmsys::ifstream ifs(filename.c_str(),
             cmsys::ifstream ifs(filename.c_str(),
                                 std::ios::in
                                 std::ios::in
@@ -2112,10 +2044,8 @@ void cmCTestTestHandler::GenerateRegressionImages(cmXMLWriter& xml,
                                              encoded_buffer.get(), 1);
                                              encoded_buffer.get(), 1);
 
 
             xml.StartElement("NamedMeasurement");
             xml.StartElement("NamedMeasurement");
-            xml.Attribute(measurementfile.match(1).c_str(),
-                          measurementfile.match(2));
-            xml.Attribute(measurementfile.match(3).c_str(),
-                          measurementfile.match(4));
+            xml.Attribute("name", parser.MeasurementName);
+            xml.Attribute("type", parser.MeasurementType);
             xml.Attribute("encoding", "base64");
             xml.Attribute("encoding", "base64");
             std::ostringstream ostr;
             std::ostringstream ostr;
             for (size_t cc = 0; cc < rlen; cc++) {
             for (size_t cc = 0; cc < rlen; cc++) {
@@ -2128,25 +2058,11 @@ void cmCTestTestHandler::GenerateRegressionImages(cmXMLWriter& xml,
             xml.EndElement(); // NamedMeasurement
             xml.EndElement(); // NamedMeasurement
           }
           }
         }
         }
-      } else {
-        int idx = 4;
-        if (measurementfile.match(1) == "name") {
-          idx = 2;
-        }
-        xml.StartElement("NamedMeasurement");
-        xml.Attribute("name", measurementfile.match(idx));
-        xml.Attribute("text", "text/string");
-        xml.Element("Value", "File " + filename + " not found");
-        xml.EndElement();
-        cmCTestOptionalLog(
-          this->CTest, HANDLER_OUTPUT,
-          "File \"" << filename << "\" not found." << std::endl, this->Quiet);
       }
       }
-      cxml.erase(measurementfile.start(),
-                 measurementfile.end() - measurementfile.start());
-    } else {
-      done = true;
     }
     }
+
+    // Remove this element from content.
+    cmSystemTools::ReplaceString(content, measurement_str.c_str(), "");
   }
   }
 }
 }
 
 

+ 6 - 6
Source/CTest/cmCTestTestHandler.h

@@ -177,7 +177,7 @@ public:
     std::string CompletionStatus;
     std::string CompletionStatus;
     std::string CustomCompletionStatus;
     std::string CustomCompletionStatus;
     std::string Output;
     std::string Output;
-    std::string DartString;
+    std::string TestMeasurementsOutput;
     int TestCount;
     int TestCount;
     cmCTestTestProperties* Properties;
     cmCTestTestProperties* Properties;
   };
   };
@@ -276,9 +276,9 @@ public:
 
 
 private:
 private:
   /**
   /**
-   * Generate the Dart compatible output
+   * Write test results in CTest's Test.xml format
    */
    */
-  virtual void GenerateDartOutput(cmXMLWriter& xml);
+  virtual void GenerateCTestXML(cmXMLWriter& xml);
 
 
   /**
   /**
    * Write test results in JUnit XML format
    * Write test results in JUnit XML format
@@ -348,8 +348,7 @@ private:
   cmCTestResourceSpec ResourceSpec;
   cmCTestResourceSpec ResourceSpec;
   std::string ResourceSpecFile;
   std::string ResourceSpecFile;
 
 
-  void GenerateRegressionImages(cmXMLWriter& xml, const std::string& dart);
-  cmsys::RegularExpression DartStuff1;
+  void RecordCustomTestMeasurements(cmXMLWriter& xml, std::string content);
   void CheckLabelFilter(cmCTestTestProperties& it);
   void CheckLabelFilter(cmCTestTestProperties& it);
   void CheckLabelFilterExclude(cmCTestTestProperties& it);
   void CheckLabelFilterExclude(cmCTestTestProperties& it);
   void CheckLabelFilterInclude(cmCTestTestProperties& it);
   void CheckLabelFilterInclude(cmCTestTestProperties& it);
@@ -358,7 +357,8 @@ private:
   bool UseUnion;
   bool UseUnion;
   ListOfTests TestList;
   ListOfTests TestList;
   size_t TotalNumberOfTests;
   size_t TotalNumberOfTests;
-  cmsys::RegularExpression DartStuff;
+  cmsys::RegularExpression AllTestMeasurementsRegex;
+  cmsys::RegularExpression SingleTestMeasurementRegex;
   cmsys::RegularExpression CustomCompletionStatusRegex;
   cmsys::RegularExpression CustomCompletionStatusRegex;
   cmsys::RegularExpression CustomLabelRegex;
   cmsys::RegularExpression CustomLabelRegex;
 
 

+ 26 - 0
Source/CTest/cmCTestTestMeasurementXMLParser.cxx

@@ -0,0 +1,26 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include "cmCTestTestMeasurementXMLParser.h"
+
+#include <cstring>
+
+void cmCTestTestMeasurementXMLParser::StartElement(const std::string& name,
+                                                   const char** attributes)
+{
+  this->CharacterData.clear();
+  this->ElementName = name;
+  for (const char** attr = attributes; *attr; attr += 2) {
+    if (strcmp(attr[0], "name") == 0) {
+      this->MeasurementName = attr[1];
+    } else if (strcmp(attr[0], "type") == 0) {
+      this->MeasurementType = attr[1];
+    }
+  }
+}
+
+void cmCTestTestMeasurementXMLParser::CharacterDataHandler(const char* data,
+                                                           int length)
+{
+  this->CharacterData.append(data, length);
+}

+ 21 - 0
Source/CTest/cmCTestTestMeasurementXMLParser.h

@@ -0,0 +1,21 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+
+#include <string>
+
+#include "cmXMLParser.h"
+
+class cmCTestTestMeasurementXMLParser : public cmXMLParser
+{
+public:
+  cmCTestTestMeasurementXMLParser() {}
+  std::string CharacterData;
+  std::string ElementName;
+  std::string MeasurementName;
+  std::string MeasurementType;
+
+protected:
+  void StartElement(const std::string& name, const char** atts) override;
+  void EndElement(const std::string& /*name*/) override {}
+  void CharacterDataHandler(const char* data, int length) override;
+};

+ 12 - 0
Tests/RunCMake/ctest_test/RunCMakeTest.cmake

@@ -157,14 +157,26 @@ add_test(
   NAME double_measurement
   NAME double_measurement
   COMMAND ${CMAKE_COMMAND} -E
   COMMAND ${CMAKE_COMMAND} -E
   echo <DartMeasurement type="numeric/double" name="my_custom_value">1.4847</DartMeasurement>)
   echo <DartMeasurement type="numeric/double" name="my_custom_value">1.4847</DartMeasurement>)
+add_test(
+  NAME double_measurement2
+  COMMAND ${CMAKE_COMMAND} -E
+  echo <CTestMeasurement type="numeric/double" name="another_custom_value">1.8474</CTestMeasurement>)
 add_test(
 add_test(
   NAME img_measurement
   NAME img_measurement
   COMMAND ${CMAKE_COMMAND} -E
   COMMAND ${CMAKE_COMMAND} -E
   echo <DartMeasurementFile name="TestImage" type="image/png">]] ${IMAGE_DIR}/cmake-logo-16.png [[</DartMeasurementFile>)
   echo <DartMeasurementFile name="TestImage" type="image/png">]] ${IMAGE_DIR}/cmake-logo-16.png [[</DartMeasurementFile>)
+add_test(
+  NAME img_measurement2
+  COMMAND ${CMAKE_COMMAND} -E
+  echo <CTestMeasurementFile name="TestImage2" type="image/png">]] ${IMAGE_DIR}/cmake-logo-16.png [[</CTestMeasurementFile>)
 add_test(
 add_test(
   NAME file_measurement
   NAME file_measurement
   COMMAND ${CMAKE_COMMAND} -E
   COMMAND ${CMAKE_COMMAND} -E
   echo <DartMeasurementFile name="my_test_input_data" type="file">]] ${IMAGE_DIR}/cmake-logo-16.png [[</DartMeasurementFile>)
   echo <DartMeasurementFile name="my_test_input_data" type="file">]] ${IMAGE_DIR}/cmake-logo-16.png [[</DartMeasurementFile>)
+add_test(
+  NAME file_measurement2
+  COMMAND ${CMAKE_COMMAND} -E
+  echo <CTestMeasurementFile name="another_test_input_data" type="file">]] ${IMAGE_DIR}/cmake-logo-16.png [[</CTestMeasurementFile>)
   ]])
   ]])
   run_ctest(TestMeasurements)
   run_ctest(TestMeasurements)
 endfunction()
 endfunction()

+ 18 - 0
Tests/RunCMake/ctest_test/TestMeasurements-check.cmake

@@ -10,13 +10,31 @@ endif()
 if(NOT _test_contents MATCHES "<Value>1.4847</Value>")
 if(NOT _test_contents MATCHES "<Value>1.4847</Value>")
   string(APPEND RunCMake_TEST_FAILED "Could not find expected measurement value in Test.xml")
   string(APPEND RunCMake_TEST_FAILED "Could not find expected measurement value in Test.xml")
 endif()
 endif()
+# Check the other double measurement.
+if(NOT _test_contents MATCHES [[NamedMeasurement type="numeric/double" name="another_custom_value"]])
+  string(APPEND RunCMake_TEST_FAILED
+    "Could not find expected <NamedMeasurement> tag(2) for type='numeric/double' in Test.xml")
+endif()
+if(NOT _test_contents MATCHES "<Value>1.8474</Value>")
+  string(APPEND RunCMake_TEST_FAILED "Could not find expected measurement value(2) in Test.xml")
+endif()
 # Check img measurement.
 # Check img measurement.
 if(NOT _test_contents MATCHES [[NamedMeasurement name="TestImage" type="image/png" encoding="base64"]])
 if(NOT _test_contents MATCHES [[NamedMeasurement name="TestImage" type="image/png" encoding="base64"]])
   string(APPEND RunCMake_TEST_FAILED
   string(APPEND RunCMake_TEST_FAILED
     "Could not find expected <NamedMeasurement> tag for type='image/png' in Test.xml")
     "Could not find expected <NamedMeasurement> tag for type='image/png' in Test.xml")
 endif()
 endif()
+# Check img measurement 2.
+if(NOT _test_contents MATCHES [[NamedMeasurement name="TestImage2" type="image/png" encoding="base64"]])
+  string(APPEND RunCMake_TEST_FAILED
+    "Could not find expected <NamedMeasurement> tag(2) for type='image/png' in Test.xml")
+endif()
 # Check file measurement.
 # Check file measurement.
 if(NOT _test_contents MATCHES [[NamedMeasurement name="my_test_input_data" encoding="base64" compression="tar/gzip" filename="cmake-logo-16.png" type="file"]])
 if(NOT _test_contents MATCHES [[NamedMeasurement name="my_test_input_data" encoding="base64" compression="tar/gzip" filename="cmake-logo-16.png" type="file"]])
   string(APPEND RunCMake_TEST_FAILED
   string(APPEND RunCMake_TEST_FAILED
     "Could not find expected <NamedMeasurement> tag for type='file' in Test.xml")
     "Could not find expected <NamedMeasurement> tag for type='file' in Test.xml")
 endif()
 endif()
+# Check file measurement 2.
+if(NOT _test_contents MATCHES [[NamedMeasurement name="another_test_input_data" encoding="base64" compression="tar/gzip" filename="cmake-logo-16.png" type="file"]])
+  string(APPEND RunCMake_TEST_FAILED
+    "Could not find expected <NamedMeasurement> tag(2) for type='file' in Test.xml")
+endif()