|  | // Copyright 2015 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_unittest_result_printer.h" | 
|  |  | 
|  | #include "base/base64.h" | 
|  | #include "base/command_line.h" | 
|  | #include "base/files/file_util.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/test/test_switches.h" | 
|  | #include "base/time/time.h" | 
|  |  | 
|  | namespace base { | 
|  |  | 
|  | namespace { | 
|  | const int kDefaultTestPartResultsLimit = 10; | 
|  |  | 
|  | const char kTestPartLesultsLimitExceeded[] = | 
|  | "Test part results limit exceeded. Use --test-launcher-test-part-limit to " | 
|  | "increase or disable limit."; | 
|  | }  // namespace | 
|  |  | 
|  | XmlUnitTestResultPrinter::XmlUnitTestResultPrinter() | 
|  | : output_file_(nullptr), open_failed_(false) {} | 
|  |  | 
|  | XmlUnitTestResultPrinter::~XmlUnitTestResultPrinter() { | 
|  | if (output_file_ && !open_failed_) { | 
|  | fprintf(output_file_, "</testsuites>\n"); | 
|  | fflush(output_file_); | 
|  | CloseFile(output_file_); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool XmlUnitTestResultPrinter::Initialize(const FilePath& output_file_path) { | 
|  | DCHECK(!output_file_); | 
|  | output_file_ = OpenFile(output_file_path, "w"); | 
|  | if (!output_file_) { | 
|  | // If the file open fails, we set the output location to stderr. This is | 
|  | // because in current usage our caller CHECKs the result of this function. | 
|  | // But that in turn causes a LogMessage that comes back to this object, | 
|  | // which in turn causes a (double) crash. By pointing at stderr, there might | 
|  | // be some indication what's going wrong. See https://crbug.com/736783. | 
|  | output_file_ = stderr; | 
|  | open_failed_ = true; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | fprintf(output_file_, | 
|  | "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<testsuites>\n"); | 
|  | fflush(output_file_); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void XmlUnitTestResultPrinter::OnAssert(const char* file, | 
|  | int line, | 
|  | const std::string& summary, | 
|  | const std::string& message) { | 
|  | WriteTestPartResult(file, line, testing::TestPartResult::kFatalFailure, | 
|  | summary, message); | 
|  | } | 
|  |  | 
|  | 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"); | 
|  | } | 
|  |  | 
|  | int limit = test_info.result()->total_part_count(); | 
|  | if (CommandLine::ForCurrentProcess()->HasSwitch( | 
|  | switches::kTestLauncherTestPartResultsLimit)) { | 
|  | std::string limit_str = | 
|  | CommandLine::ForCurrentProcess()->GetSwitchValueASCII( | 
|  | switches::kTestLauncherTestPartResultsLimit); | 
|  | int test_part_results_limit = std::strtol(limit_str.c_str(), nullptr, 10); | 
|  | if (test_part_results_limit >= 0) | 
|  | limit = std::min(limit, test_part_results_limit); | 
|  | } else { | 
|  | limit = std::min(limit, kDefaultTestPartResultsLimit); | 
|  | } | 
|  |  | 
|  | for (int i = 0; i < limit; ++i) { | 
|  | const auto& test_part_result = test_info.result()->GetTestPartResult(i); | 
|  | WriteTestPartResult(test_part_result.file_name(), | 
|  | test_part_result.line_number(), test_part_result.type(), | 
|  | test_part_result.summary(), test_part_result.message()); | 
|  | } | 
|  |  | 
|  | if (test_info.result()->total_part_count() > limit) { | 
|  | WriteTestPartResult( | 
|  | "<unknown>", 0, testing::TestPartResult::kNonFatalFailure, | 
|  | kTestPartLesultsLimitExceeded, kTestPartLesultsLimitExceeded); | 
|  | } | 
|  |  | 
|  | fprintf(output_file_, "    </testcase>\n"); | 
|  | fflush(output_file_); | 
|  | } | 
|  |  | 
|  | void XmlUnitTestResultPrinter::OnTestCaseEnd( | 
|  | const testing::TestCase& test_case) { | 
|  | fprintf(output_file_, "  </testsuite>\n"); | 
|  | fflush(output_file_); | 
|  | } | 
|  |  | 
|  | void XmlUnitTestResultPrinter::WriteTestPartResult( | 
|  | const char* file, | 
|  | int line, | 
|  | testing::TestPartResult::Type result_type, | 
|  | const std::string& summary, | 
|  | const std::string& message) { | 
|  | const char* type = "unknown"; | 
|  | switch (result_type) { | 
|  | case testing::TestPartResult::kSuccess: | 
|  | type = "success"; | 
|  | break; | 
|  | case testing::TestPartResult::kNonFatalFailure: | 
|  | type = "failure"; | 
|  | break; | 
|  | case testing::TestPartResult::kFatalFailure: | 
|  | type = "fatal_failure"; | 
|  | break; | 
|  | } | 
|  | std::string summary_encoded; | 
|  | Base64Encode(summary, &summary_encoded); | 
|  | std::string message_encoded; | 
|  | Base64Encode(message, &message_encoded); | 
|  | fprintf(output_file_, | 
|  | "      <x-test-result-part type=\"%s\" file=\"%s\" line=\"%d\">\n" | 
|  | "        <summary>%s</summary>\n" | 
|  | "        <message>%s</message>\n" | 
|  | "      </x-test-result-part>\n", | 
|  | type, file, line, summary_encoded.c_str(), message_encoded.c_str()); | 
|  | fflush(output_file_); | 
|  | } | 
|  |  | 
|  | }  // namespace base |