|  | // Copyright 2013 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "base/test/gtest_xml_util.h" | 
|  |  | 
|  | #include "base/base64.h" | 
|  | #include "base/files/file_util.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/strings/string_number_conversions.h" | 
|  | #include "base/strings/stringprintf.h" | 
|  | #include "base/test/gtest_util.h" | 
|  | #include "base/test/launcher/test_launcher.h" | 
|  | #include "starboard/types.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); | 
|  | StringAppendV(error, message, args); | 
|  | va_end(args); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | bool ProcessGTestOutput(const base::FilePath& output_file, | 
|  | std::vector<TestResult>* results, | 
|  | bool* crashed) { | 
|  | DCHECK(results); | 
|  |  | 
|  | std::string xml_contents; | 
|  | if (!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; | 
|  |  | 
|  | enum { | 
|  | STATE_INIT, | 
|  | STATE_TESTSUITE, | 
|  | STATE_TESTCASE, | 
|  | STATE_TEST_RESULT, | 
|  | STATE_FAILURE, | 
|  | STATE_END, | 
|  | } state = STATE_INIT; | 
|  |  | 
|  | while (xml_reader.Read()) { | 
|  | xml_reader.SkipToElement(); | 
|  | std::string node_name(xml_reader.NodeName()); | 
|  |  | 
|  | switch (state) { | 
|  | case STATE_INIT: | 
|  | if (node_name == "testsuites" && !xml_reader.IsClosingElement()) | 
|  | state = STATE_TESTSUITE; | 
|  | else | 
|  | return false; | 
|  | break; | 
|  | case STATE_TESTSUITE: | 
|  | if (node_name == "testsuites" && xml_reader.IsClosingElement()) | 
|  | state = STATE_END; | 
|  | else if (node_name == "testsuite" && !xml_reader.IsClosingElement()) | 
|  | state = STATE_TESTCASE; | 
|  | else | 
|  | return false; | 
|  | break; | 
|  | 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; | 
|  |  | 
|  | std::string test_case_name; | 
|  | if (!xml_reader.NodeAttribute("classname", &test_case_name)) | 
|  | return false; | 
|  | std::string test_name; | 
|  | if (!xml_reader.NodeAttribute("name", &test_name)) | 
|  | return false; | 
|  | result.full_name = FormatFullTestName(test_case_name, test_name); | 
|  |  | 
|  | result.elapsed_time = TimeDelta(); | 
|  |  | 
|  | // Assume the test crashed - we can correct that later. | 
|  | result.status = TestResult::TEST_CRASH; | 
|  |  | 
|  | results->push_back(result); | 
|  | } else if (node_name == "testcase" && !xml_reader.IsClosingElement()) { | 
|  | std::string test_status; | 
|  | if (!xml_reader.NodeAttribute("status", &test_status)) | 
|  | return false; | 
|  |  | 
|  | if (test_status != "run" && test_status != "notrun") | 
|  | return false; | 
|  | if (test_status != "run") | 
|  | break; | 
|  |  | 
|  | TestResult result; | 
|  |  | 
|  | std::string test_case_name; | 
|  | if (!xml_reader.NodeAttribute("classname", &test_case_name)) | 
|  | return false; | 
|  | std::string test_name; | 
|  | if (!xml_reader.NodeAttribute("name", &test_name)) | 
|  | return false; | 
|  | result.full_name = test_case_name + "." + test_name; | 
|  |  | 
|  | std::string test_time_str; | 
|  | if (!xml_reader.NodeAttribute("time", &test_time_str)) | 
|  | return false; | 
|  | result.elapsed_time = TimeDelta::FromMicroseconds( | 
|  | static_cast<int64_t>(strtod(test_time_str.c_str(), nullptr) * | 
|  | Time::kMicrosecondsPerSecond)); | 
|  |  | 
|  | result.status = TestResult::TEST_SUCCESS; | 
|  |  | 
|  | if (!results->empty() && | 
|  | results->back().full_name == result.full_name && | 
|  | results->back().status == TestResult::TEST_CRASH) { | 
|  | // 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()) { | 
|  | std::string failure_message; | 
|  | if (!xml_reader.NodeAttribute("message", &failure_message)) | 
|  | return false; | 
|  |  | 
|  | DCHECK(!results->empty()); | 
|  | results->back().status = TestResult::TEST_FAILURE; | 
|  |  | 
|  | state = STATE_FAILURE; | 
|  | } else if (node_name == "testcase" && xml_reader.IsClosingElement()) { | 
|  | // Deliberately empty. | 
|  | } else if (node_name == "x-test-result-part" && | 
|  | !xml_reader.IsClosingElement()) { | 
|  | std::string result_type; | 
|  | if (!xml_reader.NodeAttribute("type", &result_type)) | 
|  | return false; | 
|  |  | 
|  | std::string file_name; | 
|  | if (!xml_reader.NodeAttribute("file", &file_name)) | 
|  | return false; | 
|  |  | 
|  | std::string line_number_str; | 
|  | if (!xml_reader.NodeAttribute("line", &line_number_str)) | 
|  | return false; | 
|  |  | 
|  | int line_number; | 
|  | if (!StringToInt(line_number_str, &line_number)) | 
|  | return false; | 
|  |  | 
|  | TestResultPart::Type type; | 
|  | if (!TestResultPart::TypeFromString(result_type, &type)) | 
|  | return false; | 
|  |  | 
|  | TestResultPart test_result_part; | 
|  | test_result_part.type = type; | 
|  | test_result_part.file_name = file_name, | 
|  | test_result_part.line_number = line_number; | 
|  | DCHECK(!results->empty()); | 
|  | results->back().test_result_parts.push_back(test_result_part); | 
|  |  | 
|  | state = STATE_TEST_RESULT; | 
|  | } else { | 
|  | return false; | 
|  | } | 
|  | break; | 
|  | case STATE_TEST_RESULT: | 
|  | if (node_name == "summary" && !xml_reader.IsClosingElement()) { | 
|  | std::string summary; | 
|  | if (!xml_reader.ReadElementContent(&summary)) | 
|  | return false; | 
|  |  | 
|  | if (!Base64Decode(summary, &summary)) | 
|  | return false; | 
|  |  | 
|  | DCHECK(!results->empty()); | 
|  | DCHECK(!results->back().test_result_parts.empty()); | 
|  | results->back().test_result_parts.back().summary = summary; | 
|  | } else if (node_name == "summary" && xml_reader.IsClosingElement()) { | 
|  | } else if (node_name == "message" && !xml_reader.IsClosingElement()) { | 
|  | std::string message; | 
|  | if (!xml_reader.ReadElementContent(&message)) | 
|  | return false; | 
|  |  | 
|  | if (!Base64Decode(message, &message)) | 
|  | return false; | 
|  |  | 
|  | DCHECK(!results->empty()); | 
|  | DCHECK(!results->back().test_result_parts.empty()); | 
|  | results->back().test_result_parts.back().message = message; | 
|  | } else if (node_name == "message" && xml_reader.IsClosingElement()) { | 
|  | } else if (node_name == "x-test-result-part" && | 
|  | xml_reader.IsClosingElement()) { | 
|  | state = STATE_TESTCASE; | 
|  | } else { | 
|  | return false; | 
|  | } | 
|  | break; | 
|  | case STATE_FAILURE: | 
|  | if (node_name == "failure" && xml_reader.IsClosingElement()) | 
|  | state = STATE_TESTCASE; | 
|  | else | 
|  | return false; | 
|  | break; | 
|  | case STATE_END: | 
|  | // If we are here and there are still XML elements, the file has wrong | 
|  | // format. | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | *crashed = (state != STATE_END); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | }  // namespace base |