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