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, "<", "<", &escaped_string);
30 ReplaceChars(escaped_string, ">", ">", &escaped_string);
31 ReplaceChars(escaped_string, "'", "'", &escaped_string);
32 ReplaceChars(escaped_string, "\"", """, &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