// 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/files/file_util.h" #include "base/logging.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); base::StringAppendV(error, message, args); va_end(args); } } // namespace XmlUnitTestResultPrinter::XmlUnitTestResultPrinter() : output_file_(NULL) { } XmlUnitTestResultPrinter::~XmlUnitTestResultPrinter() { if (output_file_) { fprintf(output_file_, "\n"); fflush(output_file_); base::CloseFile(output_file_); } } bool XmlUnitTestResultPrinter::Initialize(const FilePath& output_file_path) { DCHECK(!output_file_); output_file_ = OpenFile(output_file_path, "w"); if (!output_file_) return false; fprintf(output_file_, "\n\n"); fflush(output_file_); return true; } void XmlUnitTestResultPrinter::OnTestCaseStart( const testing::TestCase& test_case) { fprintf(output_file_, " \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 // tag here - it requires e.g. run time of the test to be known. fprintf(output_file_, " \n", test_info.name(), test_info.test_case_name()); fflush(output_file_); } void XmlUnitTestResultPrinter::OnTestEnd(const testing::TestInfo& test_info) { fprintf(output_file_, " \n", test_info.name(), static_cast(test_info.result()->elapsed_time()) / Time::kMillisecondsPerSecond, test_info.test_case_name()); if (test_info.result()->Failed()) fprintf(output_file_, " \n"); fprintf(output_file_, " \n"); fflush(output_file_); } void XmlUnitTestResultPrinter::OnTestCaseEnd( const testing::TestCase& test_case) { fprintf(output_file_, " \n"); fflush(output_file_); } 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_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(), NULL) * Time::kMicrosecondsPerSecond)); result.status = TestResult::TEST_SUCCESS; if (!results->empty() && results->at(results->size() - 1).full_name == result.full_name && results->at(results->size() - 1).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->at(results->size() - 1).status = TestResult::TEST_FAILURE; state = STATE_FAILURE; } else if (node_name == "testcase" && xml_reader.IsClosingElement()) { // Deliberately empty. } 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