Index: base/test/gtest_xml_util.cc |
diff --git a/base/test/gtest_xml_util.cc b/base/test/gtest_xml_util.cc |
index 6187d9f3e5abb7d4eb11dce7ed88c653dadbba57..524369a6bd8144a6b727d2f0ff6227ee2d29a126 100644 |
--- a/base/test/gtest_xml_util.cc |
+++ b/base/test/gtest_xml_util.cc |
@@ -6,19 +6,100 @@ |
#include "base/file_util.h" |
#include "base/logging.h" |
+#include "base/strings/stringprintf.h" |
#include "base/test/test_launcher.h" |
#include "third_party/libxml/chromium/libxml_utils.h" |
namespace base { |
+namespace { |
+ |
+// This is used for the xml parser to report errors. This assumes the context |
+// is a pointer to a std::string where the error message should be appended. |
+static void XmlErrorFunc(void *context, const char *message, ...) { |
+ va_list args; |
+ va_start(args, message); |
+ std::string* error = static_cast<std::string*>(context); |
+ base::StringAppendV(error, message, args); |
+ va_end(args); |
+} |
+ |
+} // namespace |
+ |
+XmlUnitTestResultPrinter::XmlUnitTestResultPrinter() : output_file_(NULL) { |
+} |
+ |
+XmlUnitTestResultPrinter::~XmlUnitTestResultPrinter() { |
+ if (output_file_) { |
+ fprintf(output_file_, "</testsuites>\n"); |
+ fflush(output_file_); |
+ file_util::CloseFile(output_file_); |
+ } |
+} |
+ |
+bool XmlUnitTestResultPrinter::Initialize(const FilePath& output_file_path) { |
+ DCHECK(!output_file_); |
+ output_file_ = file_util::OpenFile(output_file_path, "w"); |
+ if (!output_file_) |
+ return false; |
+ |
+ fprintf(output_file_, |
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<testsuites>\n"); |
+ fflush(output_file_); |
+ |
+ return true; |
+} |
+ |
+void XmlUnitTestResultPrinter::OnTestCaseStart( |
+ const testing::TestCase& test_case) { |
+ fprintf(output_file_, " <testsuite>\n"); |
+ fflush(output_file_); |
+} |
+ |
+void XmlUnitTestResultPrinter::OnTestStart(const testing::TestInfo& test_info) { |
+ // This is our custom extension - it helps to recognize which test was running |
+ // when the test binary crashed. Note that we cannot even open the <testcase> |
+ // tag here - it requires e.g. run time of the test to be known. |
+ fprintf(output_file_, |
+ " <x-teststart name=\"%s\" classname=\"%s\" />\n", |
+ test_info.name(), |
+ test_info.test_case_name()); |
+ fflush(output_file_); |
+} |
+ |
+void XmlUnitTestResultPrinter::OnTestEnd(const testing::TestInfo& test_info) { |
+ fprintf(output_file_, |
+ " <testcase name=\"%s\" status=\"run\" time=\"%.3f\"" |
+ " classname=\"%s\">\n", |
+ test_info.name(), |
+ static_cast<double>(test_info.result()->elapsed_time()) / |
+ Time::kMillisecondsPerSecond, |
+ test_info.test_case_name()); |
+ if (test_info.result()->Failed()) |
+ fprintf(output_file_, " <failure message=\"\" type=\"\"></failure>\n"); |
+ fprintf(output_file_, " </testcase>\n"); |
+ fflush(output_file_); |
+} |
+ |
+void XmlUnitTestResultPrinter::OnTestCaseEnd( |
+ const testing::TestCase& test_case) { |
+ fprintf(output_file_, " </testsuite>\n"); |
+ fflush(output_file_); |
+} |
+ |
bool ProcessGTestOutput(const base::FilePath& output_file, |
- std::vector<TestResult>* results) { |
+ std::vector<TestResult>* results, |
+ bool* crashed) { |
DCHECK(results); |
std::string xml_contents; |
if (!file_util::ReadFileToString(output_file, &xml_contents)) |
return false; |
+ // Silence XML errors - otherwise they go to stderr. |
+ std::string xml_errors; |
+ ScopedXmlErrorFunc error_func(&xml_errors, &XmlErrorFunc); |
+ |
XmlReader xml_reader; |
if (!xml_reader.Load(xml_contents)) |
return false; |
@@ -53,6 +134,23 @@ bool ProcessGTestOutput(const base::FilePath& output_file, |
case STATE_TESTCASE: |
if (node_name == "testsuite" && xml_reader.IsClosingElement()) { |
state = STATE_TESTSUITE; |
+ } else if (node_name == "x-teststart" && |
+ !xml_reader.IsClosingElement()) { |
+ // This is our custom extension that helps recognize which test was |
+ // running when the test binary crashed. |
+ TestResult result; |
+ if (!xml_reader.NodeAttribute("classname", &result.test_case_name)) |
+ return false; |
+ if (!xml_reader.NodeAttribute("name", &result.test_name)) |
+ return false; |
+ |
+ result.elapsed_time = TimeDelta(); |
+ |
+ // Assume the test crashed - we can correct that later. |
+ result.success = false; |
+ result.crashed = true; |
+ |
+ results->push_back(result); |
} else if (node_name == "testcase" && !xml_reader.IsClosingElement()) { |
std::string test_status; |
if (!xml_reader.NodeAttribute("status", &test_status)) |
@@ -77,6 +175,16 @@ bool ProcessGTestOutput(const base::FilePath& output_file, |
Time::kMicrosecondsPerSecond); |
result.success = true; |
+ result.crashed = false; |
+ |
+ if (!results->empty() && |
+ results->at(results->size() - 1).GetFullName() == |
+ result.GetFullName() && |
+ results->at(results->size() - 1).crashed) { |
+ // Erase the fail-safe "crashed" result - now we know the test did |
+ // not crash. |
+ results->pop_back(); |
+ } |
results->push_back(result); |
} else if (node_name == "failure" && !xml_reader.IsClosingElement()) { |
@@ -107,7 +215,8 @@ bool ProcessGTestOutput(const base::FilePath& output_file, |
} |
} |
+ *crashed = (state != STATE_END); |
return true; |
} |
-} // namespace base |
+} // namespace base |