1// Copyright 2013 The Chromium Authors. All rights reserved.
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 "base/files/file_util.h"
8#include "base/logging.h"
9#include "base/strings/stringprintf.h"
10#include "base/test/launcher/test_launcher.h"
11#include "third_party/libxml/chromium/libxml_utils.h"
12
13namespace base {
14
15namespace {
16
17// This is used for the xml parser to report errors. This assumes the context
18// is a pointer to a std::string where the error message should be appended.
19static void XmlErrorFunc(void *context, const char *message, ...) {
20  va_list args;
21  va_start(args, message);
22  std::string* error = static_cast<std::string*>(context);
23  base::StringAppendV(error, message, args);
24  va_end(args);
25}
26
27}  // namespace
28
29XmlUnitTestResultPrinter::XmlUnitTestResultPrinter() : output_file_(NULL) {
30}
31
32XmlUnitTestResultPrinter::~XmlUnitTestResultPrinter() {
33  if (output_file_) {
34    fprintf(output_file_, "</testsuites>\n");
35    fflush(output_file_);
36    base::CloseFile(output_file_);
37  }
38}
39
40bool XmlUnitTestResultPrinter::Initialize(const FilePath& output_file_path) {
41  DCHECK(!output_file_);
42  output_file_ = OpenFile(output_file_path, "w");
43  if (!output_file_)
44    return false;
45
46  fprintf(output_file_,
47          "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<testsuites>\n");
48  fflush(output_file_);
49
50  return true;
51}
52
53void XmlUnitTestResultPrinter::OnTestCaseStart(
54    const testing::TestCase& test_case) {
55  fprintf(output_file_, "  <testsuite>\n");
56  fflush(output_file_);
57}
58
59void XmlUnitTestResultPrinter::OnTestStart(const testing::TestInfo& test_info) {
60  // This is our custom extension - it helps to recognize which test was running
61  // when the test binary crashed. Note that we cannot even open the <testcase>
62  // tag here - it requires e.g. run time of the test to be known.
63  fprintf(output_file_,
64          "    <x-teststart name=\"%s\" classname=\"%s\" />\n",
65          test_info.name(),
66          test_info.test_case_name());
67  fflush(output_file_);
68}
69
70void XmlUnitTestResultPrinter::OnTestEnd(const testing::TestInfo& test_info) {
71  fprintf(output_file_,
72          "    <testcase name=\"%s\" status=\"run\" time=\"%.3f\""
73          " classname=\"%s\">\n",
74          test_info.name(),
75          static_cast<double>(test_info.result()->elapsed_time()) /
76              Time::kMillisecondsPerSecond,
77          test_info.test_case_name());
78  if (test_info.result()->Failed())
79    fprintf(output_file_, "      <failure message=\"\" type=\"\"></failure>\n");
80  fprintf(output_file_, "    </testcase>\n");
81  fflush(output_file_);
82}
83
84void XmlUnitTestResultPrinter::OnTestCaseEnd(
85    const testing::TestCase& test_case) {
86  fprintf(output_file_, "  </testsuite>\n");
87  fflush(output_file_);
88}
89
90bool ProcessGTestOutput(const base::FilePath& output_file,
91                        std::vector<TestResult>* results,
92                        bool* crashed) {
93  DCHECK(results);
94
95  std::string xml_contents;
96  if (!ReadFileToString(output_file, &xml_contents))
97    return false;
98
99  // Silence XML errors - otherwise they go to stderr.
100  std::string xml_errors;
101  ScopedXmlErrorFunc error_func(&xml_errors, &XmlErrorFunc);
102
103  XmlReader xml_reader;
104  if (!xml_reader.Load(xml_contents))
105    return false;
106
107  enum {
108    STATE_INIT,
109    STATE_TESTSUITE,
110    STATE_TESTCASE,
111    STATE_FAILURE,
112    STATE_END,
113  } state = STATE_INIT;
114
115  while (xml_reader.Read()) {
116    xml_reader.SkipToElement();
117    std::string node_name(xml_reader.NodeName());
118
119    switch (state) {
120      case STATE_INIT:
121        if (node_name == "testsuites" && !xml_reader.IsClosingElement())
122          state = STATE_TESTSUITE;
123        else
124          return false;
125        break;
126      case STATE_TESTSUITE:
127        if (node_name == "testsuites" && xml_reader.IsClosingElement())
128          state = STATE_END;
129        else if (node_name == "testsuite" && !xml_reader.IsClosingElement())
130          state = STATE_TESTCASE;
131        else
132          return false;
133        break;
134      case STATE_TESTCASE:
135        if (node_name == "testsuite" && xml_reader.IsClosingElement()) {
136          state = STATE_TESTSUITE;
137        } else if (node_name == "x-teststart" &&
138                   !xml_reader.IsClosingElement()) {
139          // This is our custom extension that helps recognize which test was
140          // running when the test binary crashed.
141          TestResult result;
142
143          std::string test_case_name;
144          if (!xml_reader.NodeAttribute("classname", &test_case_name))
145            return false;
146          std::string test_name;
147          if (!xml_reader.NodeAttribute("name", &test_name))
148            return false;
149          result.full_name = TestLauncher::FormatFullTestName(test_case_name,
150                                                              test_name);
151
152          result.elapsed_time = TimeDelta();
153
154          // Assume the test crashed - we can correct that later.
155          result.status = TestResult::TEST_CRASH;
156
157          results->push_back(result);
158        } else if (node_name == "testcase" && !xml_reader.IsClosingElement()) {
159          std::string test_status;
160          if (!xml_reader.NodeAttribute("status", &test_status))
161            return false;
162
163          if (test_status != "run" && test_status != "notrun")
164            return false;
165          if (test_status != "run")
166            break;
167
168          TestResult result;
169
170          std::string test_case_name;
171          if (!xml_reader.NodeAttribute("classname", &test_case_name))
172            return false;
173          std::string test_name;
174          if (!xml_reader.NodeAttribute("name", &test_name))
175            return false;
176          result.full_name = test_case_name + "." + test_name;
177
178          std::string test_time_str;
179          if (!xml_reader.NodeAttribute("time", &test_time_str))
180            return false;
181          result.elapsed_time =
182              TimeDelta::FromMicroseconds(strtod(test_time_str.c_str(), NULL) *
183                                          Time::kMicrosecondsPerSecond);
184
185          result.status = TestResult::TEST_SUCCESS;
186
187          if (!results->empty() &&
188              results->at(results->size() - 1).full_name == result.full_name &&
189              results->at(results->size() - 1).status ==
190                  TestResult::TEST_CRASH) {
191            // Erase the fail-safe "crashed" result - now we know the test did
192            // not crash.
193            results->pop_back();
194          }
195
196          results->push_back(result);
197        } else if (node_name == "failure" && !xml_reader.IsClosingElement()) {
198          std::string failure_message;
199          if (!xml_reader.NodeAttribute("message", &failure_message))
200            return false;
201
202          DCHECK(!results->empty());
203          results->at(results->size() - 1).status = TestResult::TEST_FAILURE;
204
205          state = STATE_FAILURE;
206        } else if (node_name == "testcase" && xml_reader.IsClosingElement()) {
207          // Deliberately empty.
208        } else {
209          return false;
210        }
211        break;
212      case STATE_FAILURE:
213        if (node_name == "failure" && xml_reader.IsClosingElement())
214          state = STATE_TESTCASE;
215        else
216          return false;
217        break;
218      case STATE_END:
219        // If we are here and there are still XML elements, the file has wrong
220        // format.
221        return false;
222    }
223  }
224
225  *crashed = (state != STATE_END);
226  return true;
227}
228
229}  // namespace base
230