| // 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 |