1 // Copyright 2013 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_util.h"
6
7 #include <stdint.h>
8
9 #include "base/base64.h"
10 #include "base/check.h"
11 #include "base/files/file_util.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/test/gtest_util.h"
14 #include "base/test/launcher/test_launcher.h"
15 #include "third_party/libxml/chromium/libxml_utils.h"
16 #include "third_party/libxml/chromium/xml_reader.h"
17
18 namespace base {
19
20 namespace {
21
22 // No-op error handler that replaces libxml's default, which writes to stderr.
23 // The test launcher's worker threads speculatively parse results XML to detect
24 // timeouts in the processes they manage, so logging parsing errors could be
25 // noisy (e.g., crbug.com/1466897).
NullXmlErrorFunc(void * context,const char * message,...)26 void NullXmlErrorFunc(void* context, const char* message, ...) {}
27
28 } // namespace
29
30 struct Link {
31 // The name of the test case.
32 std::string name;
33 // The name of the classname of the test.
34 std::string classname;
35 // The name of the link.
36 std::string link_name;
37 // The actual link.
38 std::string link;
39 };
40
41 struct Property {
42 // The name of the property.
43 std::string name;
44 // The value of the property.
45 std::string value;
46 };
47
48 struct Tag {
49 // The name of the test case.
50 std::string name;
51 // The name of the classname of the test.
52 std::string classname;
53 // The name of the tag.
54 std::string tag_name;
55 // The value of the tag.
56 std::string tag_value;
57 };
58
ProcessGTestOutput(const base::FilePath & output_file,std::vector<TestResult> * results,bool * crashed)59 bool ProcessGTestOutput(const base::FilePath& output_file,
60 std::vector<TestResult>* results,
61 bool* crashed) {
62 DCHECK(results);
63
64 std::string xml_contents;
65 if (!ReadFileToString(output_file, &xml_contents))
66 return false;
67
68 // Silence XML errors - otherwise they go to stderr.
69 ScopedXmlErrorFunc error_func(nullptr, &NullXmlErrorFunc);
70
71 XmlReader xml_reader;
72 if (!xml_reader.Load(xml_contents))
73 return false;
74
75 enum {
76 STATE_INIT,
77 STATE_TESTSUITE,
78 STATE_TESTCASE,
79 STATE_TEST_RESULT,
80 STATE_FAILURE,
81 STATE_END,
82 } state = STATE_INIT;
83
84 std::vector<Link> links;
85
86 std::vector<Property> properties;
87
88 std::vector<Tag> tags;
89
90 while (xml_reader.Read()) {
91 xml_reader.SkipToElement();
92 std::string node_name(xml_reader.NodeName());
93
94 switch (state) {
95 case STATE_INIT:
96 if (node_name == "testsuites" && !xml_reader.IsClosingElement())
97 state = STATE_TESTSUITE;
98 else
99 return false;
100 break;
101 case STATE_TESTSUITE:
102 if (node_name == "testsuites" && xml_reader.IsClosingElement())
103 state = STATE_END;
104 else if (node_name == "testsuite" && !xml_reader.IsClosingElement())
105 state = STATE_TESTCASE;
106 else
107 return false;
108 break;
109 case STATE_TESTCASE:
110 if (node_name == "testsuite" && xml_reader.IsClosingElement()) {
111 state = STATE_TESTSUITE;
112 } else if (node_name == "x-teststart" &&
113 !xml_reader.IsClosingElement()) {
114 // This is our custom extension that helps recognize which test was
115 // running when the test binary crashed.
116 TestResult result;
117
118 std::string test_case_name;
119 if (!xml_reader.NodeAttribute("classname", &test_case_name))
120 return false;
121 std::string test_name;
122 if (!xml_reader.NodeAttribute("name", &test_name))
123 return false;
124 result.full_name = FormatFullTestName(test_case_name, test_name);
125
126 result.elapsed_time = TimeDelta();
127
128 std::string test_timestamp_str;
129 Time test_timestamp;
130 if (xml_reader.NodeAttribute("timestamp", &test_timestamp_str) &&
131 Time::FromString(test_timestamp_str.c_str(), &test_timestamp)) {
132 result.timestamp = test_timestamp;
133 }
134
135 // Assume the test crashed - we can correct that later.
136 result.status = TestResult::TEST_CRASH;
137
138 results->push_back(result);
139 } else if (node_name == "testcase" && !xml_reader.IsClosingElement()) {
140 std::string test_status;
141 if (!xml_reader.NodeAttribute("status", &test_status))
142 return false;
143
144 if (test_status != "run" && test_status != "notrun")
145 return false;
146 if (test_status != "run")
147 break;
148
149 TestResult result;
150
151 std::string test_case_name;
152 if (!xml_reader.NodeAttribute("classname", &test_case_name))
153 return false;
154 std::string test_name;
155 if (!xml_reader.NodeAttribute("name", &test_name))
156 return false;
157 result.full_name = test_case_name + "." + test_name;
158
159 std::string test_time_str;
160 if (!xml_reader.NodeAttribute("time", &test_time_str))
161 return false;
162 result.elapsed_time = Microseconds(
163 static_cast<int64_t>(strtod(test_time_str.c_str(), nullptr) *
164 Time::kMicrosecondsPerSecond));
165
166 // The timestamp attribute records the local date and time of the test
167 // execution. It might be missing in the xml generated by older
168 // version of test launcher or gtest.
169 // https://github.com/google/googletest/blob/main/docs/advanced.md#generating-an-xml-report
170 std::string test_timestamp_str;
171 Time test_timestamp;
172 if (xml_reader.NodeAttribute("timestamp", &test_timestamp_str) &&
173 Time::FromString(test_timestamp_str.c_str(), &test_timestamp)) {
174 result.timestamp = test_timestamp;
175 }
176
177 result.status = TestResult::TEST_SUCCESS;
178
179 if (!results->empty() &&
180 results->back().full_name == result.full_name &&
181 results->back().status == TestResult::TEST_CRASH) {
182 // Erase the fail-safe "crashed" result - now we know the test did
183 // not crash.
184 results->pop_back();
185 }
186
187 for (const Link& link : links) {
188 if (link.name == test_name && link.classname == test_case_name) {
189 result.AddLink(link.link_name, link.link);
190 }
191 }
192 links.clear();
193 for (const Property& property : properties) {
194 result.AddProperty(property.name, property.value);
195 }
196 properties.clear();
197 for (const Tag& tag : tags) {
198 if (tag.name == test_name && tag.classname == test_case_name) {
199 result.AddTag(tag.tag_name, tag.tag_value);
200 }
201 }
202 tags.clear();
203 results->push_back(result);
204 } else if (node_name == "link" && !xml_reader.IsClosingElement()) {
205 Link link;
206 if (!xml_reader.NodeAttribute("name", &link.name))
207 return false;
208 if (!xml_reader.NodeAttribute("classname", &link.classname))
209 return false;
210 if (!xml_reader.NodeAttribute("link_name", &link.link_name))
211 return false;
212 if (!xml_reader.ReadElementContent(&link.link))
213 return false;
214 links.push_back(link);
215 } else if (node_name == "link" && xml_reader.IsClosingElement()) {
216 // Deliberately empty.
217 } else if (node_name == "tag" && !xml_reader.IsClosingElement()) {
218 Tag tag;
219 if (!xml_reader.NodeAttribute("name", &tag.name))
220 return false;
221 if (!xml_reader.NodeAttribute("classname", &tag.classname))
222 return false;
223 if (!xml_reader.NodeAttribute("tag_name", &tag.tag_name))
224 return false;
225 if (!xml_reader.ReadElementContent(&tag.tag_value))
226 return false;
227 tags.push_back(tag);
228 } else if (node_name == "tag" && xml_reader.IsClosingElement()) {
229 // Deliberately empty.
230 } else if (node_name == "properties" &&
231 !xml_reader.IsClosingElement()) {
232 // Deliberately empty, begin of the test properties.
233 } else if (node_name == "property" && !xml_reader.IsClosingElement()) {
234 Property property;
235 if (!xml_reader.NodeAttribute("name", &property.name))
236 return false;
237 if (!xml_reader.NodeAttribute("value", &property.value))
238 return false;
239 properties.push_back(property);
240 } else if (node_name == "properties" && xml_reader.IsClosingElement()) {
241 // Deliberately empty, end of the test properties.
242 } else if (node_name == "failure" && !xml_reader.IsClosingElement()) {
243 std::string failure_message;
244 if (!xml_reader.NodeAttribute("message", &failure_message))
245 return false;
246
247 DCHECK(!results->empty());
248 results->back().status = TestResult::TEST_FAILURE;
249
250 state = STATE_FAILURE;
251 } else if (node_name == "testcase" && xml_reader.IsClosingElement()) {
252 // Deliberately empty.
253 } else if (node_name == "x-test-result-part" &&
254 !xml_reader.IsClosingElement()) {
255 std::string result_type;
256 if (!xml_reader.NodeAttribute("type", &result_type))
257 return false;
258
259 std::string file_name;
260 if (!xml_reader.NodeAttribute("file", &file_name))
261 return false;
262
263 std::string line_number_str;
264 if (!xml_reader.NodeAttribute("line", &line_number_str))
265 return false;
266
267 int line_number;
268 if (!StringToInt(line_number_str, &line_number))
269 return false;
270
271 TestResultPart::Type type;
272 if (!TestResultPart::TypeFromString(result_type, &type))
273 return false;
274
275 TestResultPart test_result_part;
276 test_result_part.type = type;
277 test_result_part.file_name = file_name,
278 test_result_part.line_number = line_number;
279 DCHECK(!results->empty());
280 results->back().test_result_parts.push_back(test_result_part);
281
282 state = STATE_TEST_RESULT;
283 } else {
284 return false;
285 }
286 break;
287 case STATE_TEST_RESULT:
288 if (node_name == "summary" && !xml_reader.IsClosingElement()) {
289 std::string summary;
290 if (!xml_reader.ReadElementContent(&summary))
291 return false;
292
293 if (!Base64Decode(summary, &summary))
294 return false;
295
296 DCHECK(!results->empty());
297 DCHECK(!results->back().test_result_parts.empty());
298 results->back().test_result_parts.back().summary = summary;
299 } else if (node_name == "summary" && xml_reader.IsClosingElement()) {
300 } else if (node_name == "message" && !xml_reader.IsClosingElement()) {
301 std::string message;
302 if (!xml_reader.ReadElementContent(&message))
303 return false;
304
305 if (!Base64Decode(message, &message))
306 return false;
307
308 DCHECK(!results->empty());
309 DCHECK(!results->back().test_result_parts.empty());
310 results->back().test_result_parts.back().message = message;
311 } else if (node_name == "message" && xml_reader.IsClosingElement()) {
312 } else if (node_name == "x-test-result-part" &&
313 xml_reader.IsClosingElement()) {
314 state = STATE_TESTCASE;
315 } else {
316 return false;
317 }
318 break;
319 case STATE_FAILURE:
320 if (node_name == "failure" && xml_reader.IsClosingElement())
321 state = STATE_TESTCASE;
322 else
323 return false;
324 break;
325 case STATE_END:
326 // If we are here and there are still XML elements, the file has wrong
327 // format.
328 return false;
329 }
330 }
331
332 if (crashed) {
333 *crashed = (state != STATE_END);
334 }
335 return true;
336 }
337
338 } // namespace base
339