// 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 #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 "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(context); StringAppendV(error, message, args); va_end(args); } } // namespace bool ProcessGTestOutput(const base::FilePath& output_file, std::vector* 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(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