xref: /aosp_15_r20/external/cronet/base/test/gtest_xml_util.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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