• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2015 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "base/test/gtest_xml_unittest_result_printer.h"
6 
7 #include "base/base64.h"
8 #include "base/check.h"
9 #include "base/command_line.h"
10 #include "base/files/file_util.h"
11 #include "base/strings/string_util.h"
12 #include "base/test/test_switches.h"
13 #include "base/threading/thread_checker.h"
14 #include "base/time/time.h"
15 #include "base/time/time_to_iso8601.h"
16 
17 namespace base {
18 
19 namespace {
20 const int kDefaultTestPartResultsLimit = 10;
21 
22 const char kTestPartLesultsLimitExceeded[] =
23     "Test part results limit exceeded. Use --test-launcher-test-part-limit to "
24     "increase or disable limit.";
25 
EscapeString(const std::string & input_string)26 std::string EscapeString(const std::string& input_string) {
27   std::string escaped_string;
28   ReplaceChars(input_string, "&", "&", &escaped_string);
29   ReplaceChars(escaped_string, "<", "&lt;", &escaped_string);
30   ReplaceChars(escaped_string, ">", "&gt;", &escaped_string);
31   ReplaceChars(escaped_string, "'", "&apos;", &escaped_string);
32   ReplaceChars(escaped_string, "\"", "&quot;", &escaped_string);
33   return escaped_string;
34 }
35 
36 }  // namespace
37 
38 XmlUnitTestResultPrinter* XmlUnitTestResultPrinter::instance_ = nullptr;
39 
XmlUnitTestResultPrinter()40 XmlUnitTestResultPrinter::XmlUnitTestResultPrinter() {
41   DCHECK_EQ(instance_, nullptr);
42   instance_ = this;
43 }
44 
~XmlUnitTestResultPrinter()45 XmlUnitTestResultPrinter::~XmlUnitTestResultPrinter() {
46   DCHECK_EQ(instance_, this);
47   instance_ = nullptr;
48   if (output_file_ && !open_failed_) {
49     fprintf(output_file_.get(), "</testsuites>\n");
50     fflush(output_file_);
51     CloseFile(output_file_.ExtractAsDangling());
52   }
53 }
54 
Get()55 XmlUnitTestResultPrinter* XmlUnitTestResultPrinter::Get() {
56   DCHECK(instance_);
57   DCHECK(instance_->thread_checker_.CalledOnValidThread());
58   return instance_;
59 }
60 
AddLink(const std::string & name,const std::string & url)61 void XmlUnitTestResultPrinter::AddLink(const std::string& name,
62                                        const std::string& url) {
63   DCHECK(output_file_);
64   DCHECK(!open_failed_);
65   // Escape the url so it's safe to save in xml file.
66   const std::string escaped_url = EscapeString(url);
67   const testing::TestInfo* info =
68       testing::UnitTest::GetInstance()->current_test_info();
69   // When this function is not called from a gtest test body, it will
70   // return null. E.g. call from Chromium itself or from test launcher.
71   // But when that happens, the previous two DCHECK won't pass. So in
72   // theory it should not be possible to reach here and the info is null.
73   DCHECK(info);
74 
75   fprintf(output_file_.get(),
76           "    <link name=\"%s\" classname=\"%s\" "
77           "link_name=\"%s\">%s</link>\n",
78           info->name(), info->test_case_name(), name.c_str(),
79           escaped_url.c_str());
80   fflush(output_file_);
81 }
82 
AddTag(const std::string & name,const std::string & value)83 void XmlUnitTestResultPrinter::AddTag(const std::string& name,
84                                       const std::string& value) {
85   DCHECK(output_file_);
86   DCHECK(!open_failed_);
87   // Escape the value so it's safe to save in xml file.
88   const std::string escaped_value = EscapeString(value);
89   const testing::TestInfo* info =
90       testing::UnitTest::GetInstance()->current_test_info();
91   // When this function is not called from a gtest test body, it will
92   // return null. E.g. call from Chromium itself or from test launcher.
93   // But when that happens, the previous two DCHECK won't pass. So in
94   // theory it should not be possible to reach here and the info is null.
95   DCHECK(info);
96 
97   fprintf(output_file_.get(),
98           "    <tag name=\"%s\" classname=\"%s\" "
99           "tag_name=\"%s\">%s</tag>\n",
100           info->name(), info->test_case_name(), name.c_str(),
101           escaped_value.c_str());
102   fflush(output_file_);
103 }
104 
Initialize(const FilePath & output_file_path)105 bool XmlUnitTestResultPrinter::Initialize(const FilePath& output_file_path) {
106   DCHECK(!output_file_);
107   output_file_ = OpenFile(output_file_path, "w");
108   if (!output_file_) {
109     // If the file open fails, we set the output location to stderr. This is
110     // because in current usage our caller CHECKs the result of this function.
111     // But that in turn causes a LogMessage that comes back to this object,
112     // which in turn causes a (double) crash. By pointing at stderr, there might
113     // be some indication what's going wrong. See https://crbug.com/736783.
114     output_file_ = stderr;
115     open_failed_ = true;
116     return false;
117   }
118 
119   fprintf(output_file_.get(),
120           "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<testsuites>\n");
121   fflush(output_file_);
122 
123   return true;
124 }
125 
OnAssert(const char * file,int line,const std::string & summary,const std::string & message)126 void XmlUnitTestResultPrinter::OnAssert(const char* file,
127                                         int line,
128                                         const std::string& summary,
129                                         const std::string& message) {
130   WriteTestPartResult(file, line, testing::TestPartResult::kFatalFailure,
131                       summary, message);
132 }
133 
OnTestCaseStart(const testing::TestCase & test_case)134 void XmlUnitTestResultPrinter::OnTestCaseStart(
135     const testing::TestCase& test_case) {
136   fprintf(output_file_.get(), "  <testsuite>\n");
137   fflush(output_file_);
138 }
139 
OnTestStart(const testing::TestInfo & test_info)140 void XmlUnitTestResultPrinter::OnTestStart(
141     const testing::TestInfo& test_info) {
142   // This is our custom extension - it helps to recognize which test was
143   // running when the test binary crashed. Note that we cannot even open the
144   // <testcase> tag here - it requires e.g. run time of the test to be known.
145   fprintf(output_file_.get(),
146           "    <x-teststart name=\"%s\" classname=\"%s\" timestamp=\"%s\" />\n",
147           test_info.name(), test_info.test_case_name(),
148           TimeToISO8601(Time::Now()).c_str());
149   fflush(output_file_);
150 }
151 
OnTestEnd(const testing::TestInfo & test_info)152 void XmlUnitTestResultPrinter::OnTestEnd(const testing::TestInfo& test_info) {
153   fprintf(
154       output_file_.get(),
155       "    <testcase name=\"%s\" status=\"run\" time=\"%.3f\""
156       " classname=\"%s\" timestamp=\"%s\">\n",
157       test_info.name(),
158       static_cast<double>(test_info.result()->elapsed_time()) /
159           Time::kMillisecondsPerSecond,
160       test_info.test_case_name(),
161       TimeToISO8601(Time::FromJavaTime(test_info.result()->start_timestamp()))
162           .c_str());
163   if (test_info.result()->Failed()) {
164     fprintf(output_file_.get(),
165             "      <failure message=\"\" type=\"\"></failure>\n");
166   }
167 
168   int limit = test_info.result()->total_part_count();
169   if (CommandLine::ForCurrentProcess()->HasSwitch(
170           switches::kTestLauncherTestPartResultsLimit)) {
171     std::string limit_str =
172         CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
173             switches::kTestLauncherTestPartResultsLimit);
174     int test_part_results_limit = std::strtol(limit_str.c_str(), nullptr, 10);
175     if (test_part_results_limit >= 0)
176       limit = std::min(limit, test_part_results_limit);
177   } else {
178     limit = std::min(limit, kDefaultTestPartResultsLimit);
179   }
180 
181   for (int i = 0; i < limit; ++i) {
182     const auto& test_part_result = test_info.result()->GetTestPartResult(i);
183     WriteTestPartResult(test_part_result.file_name(),
184                         test_part_result.line_number(), test_part_result.type(),
185                         test_part_result.summary(), test_part_result.message());
186   }
187 
188   if (test_info.result()->total_part_count() > limit) {
189     WriteTestPartResult(
190         "unknown", 0, testing::TestPartResult::kNonFatalFailure,
191         kTestPartLesultsLimitExceeded, kTestPartLesultsLimitExceeded);
192   }
193 
194   fprintf(output_file_.get(), "    </testcase>\n");
195   fflush(output_file_);
196 }
197 
OnTestCaseEnd(const testing::TestCase & test_case)198 void XmlUnitTestResultPrinter::OnTestCaseEnd(
199     const testing::TestCase& test_case) {
200   fprintf(output_file_.get(), "  </testsuite>\n");
201   fflush(output_file_);
202 }
203 
WriteTestPartResult(const char * file,int line,testing::TestPartResult::Type result_type,const std::string & summary,const std::string & message)204 void XmlUnitTestResultPrinter::WriteTestPartResult(
205     const char* file,
206     int line,
207     testing::TestPartResult::Type result_type,
208     const std::string& summary,
209     const std::string& message) {
210   const char* type = "unknown";
211   switch (result_type) {
212     case testing::TestPartResult::kSuccess:
213       type = "success";
214       break;
215     case testing::TestPartResult::kNonFatalFailure:
216       type = "failure";
217       break;
218     case testing::TestPartResult::kFatalFailure:
219       type = "fatal_failure";
220       break;
221     case testing::TestPartResult::kSkip:
222       type = "skip";
223       break;
224   }
225   std::string summary_encoded;
226   Base64Encode(summary, &summary_encoded);
227   std::string message_encoded;
228   Base64Encode(message, &message_encoded);
229   fprintf(output_file_.get(),
230           "      <x-test-result-part type=\"%s\" file=\"%s\" line=\"%d\">\n"
231           "        <summary>%s</summary>\n"
232           "        <message>%s</message>\n"
233           "      </x-test-result-part>\n",
234           type, file, line, summary_encoded.c_str(), message_encoded.c_str());
235   fflush(output_file_);
236 }
237 
238 }  // namespace base
239