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, "<", "<", &escaped_string);
35 ReplaceChars(escaped_string, ">", ">", &escaped_string);
36 ReplaceChars(escaped_string, "'", "'", &escaped_string);
37 ReplaceChars(escaped_string, "\"", """, &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