325 lines
11 KiB
C++
325 lines
11 KiB
C++
// Copyright 2013 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "base/test/gtest_xml_util.h"
|
|
|
|
#include <stdint.h>
|
|
|
|
#include "base/base64.h"
|
|
#include "base/check.h"
|
|
#include "base/files/file_util.h"
|
|
#include "base/strings/string_number_conversions.h"
|
|
#include "base/test/gtest_util.h"
|
|
#include "base/test/launcher/test_launcher.h"
|
|
#include "third_party/libxml/chromium/xml_reader.h"
|
|
|
|
namespace base {
|
|
|
|
struct Link {
|
|
// The name of the test case.
|
|
std::string name;
|
|
// The name of the classname of the test.
|
|
std::string classname;
|
|
// The name of the link.
|
|
std::string link_name;
|
|
// The actual link.
|
|
std::string link;
|
|
};
|
|
|
|
struct Property {
|
|
// The name of the property.
|
|
std::string name;
|
|
// The value of the property.
|
|
std::string value;
|
|
};
|
|
|
|
struct Tag {
|
|
// The name of the test case.
|
|
std::string name;
|
|
// The name of the classname of the test.
|
|
std::string classname;
|
|
// The name of the tag.
|
|
std::string tag_name;
|
|
// The value of the tag.
|
|
std::string tag_value;
|
|
};
|
|
|
|
bool ProcessGTestOutput(const base::FilePath& output_file,
|
|
std::vector<TestResult>* results,
|
|
bool* crashed) {
|
|
DCHECK(results);
|
|
|
|
std::string xml_contents;
|
|
if (!ReadFileToString(output_file, &xml_contents))
|
|
return false;
|
|
|
|
XmlReader xml_reader;
|
|
if (!xml_reader.Load(xml_contents))
|
|
return false;
|
|
|
|
enum {
|
|
STATE_INIT,
|
|
STATE_TESTSUITE,
|
|
STATE_TESTCASE,
|
|
STATE_TEST_RESULT,
|
|
STATE_FAILURE,
|
|
STATE_END,
|
|
} state = STATE_INIT;
|
|
|
|
std::vector<Link> links;
|
|
|
|
std::vector<Property> properties;
|
|
|
|
std::vector<Tag> tags;
|
|
|
|
while (xml_reader.Read()) {
|
|
xml_reader.SkipToElement();
|
|
std::string node_name(xml_reader.NodeName());
|
|
|
|
switch (state) {
|
|
case STATE_INIT:
|
|
if (node_name == "testsuites" && !xml_reader.IsClosingElement())
|
|
state = STATE_TESTSUITE;
|
|
else
|
|
return false;
|
|
break;
|
|
case STATE_TESTSUITE:
|
|
if (node_name == "testsuites" && xml_reader.IsClosingElement())
|
|
state = STATE_END;
|
|
else if (node_name == "testsuite" && !xml_reader.IsClosingElement())
|
|
state = STATE_TESTCASE;
|
|
else
|
|
return false;
|
|
break;
|
|
case STATE_TESTCASE:
|
|
if (node_name == "testsuite" && xml_reader.IsClosingElement()) {
|
|
state = STATE_TESTSUITE;
|
|
} else if (node_name == "x-teststart" &&
|
|
!xml_reader.IsClosingElement()) {
|
|
// This is our custom extension that helps recognize which test was
|
|
// running when the test binary crashed.
|
|
TestResult result;
|
|
|
|
std::string test_case_name;
|
|
if (!xml_reader.NodeAttribute("classname", &test_case_name))
|
|
return false;
|
|
std::string test_name;
|
|
if (!xml_reader.NodeAttribute("name", &test_name))
|
|
return false;
|
|
result.full_name = FormatFullTestName(test_case_name, test_name);
|
|
|
|
result.elapsed_time = TimeDelta();
|
|
|
|
std::string test_timestamp_str;
|
|
Time test_timestamp;
|
|
if (xml_reader.NodeAttribute("timestamp", &test_timestamp_str) &&
|
|
Time::FromString(test_timestamp_str.c_str(), &test_timestamp)) {
|
|
result.timestamp = test_timestamp;
|
|
}
|
|
|
|
// Assume the test crashed - we can correct that later.
|
|
result.status = TestResult::TEST_CRASH;
|
|
|
|
results->push_back(result);
|
|
} else if (node_name == "testcase" && !xml_reader.IsClosingElement()) {
|
|
std::string test_status;
|
|
if (!xml_reader.NodeAttribute("status", &test_status))
|
|
return false;
|
|
|
|
if (test_status != "run" && test_status != "notrun")
|
|
return false;
|
|
if (test_status != "run")
|
|
break;
|
|
|
|
TestResult result;
|
|
|
|
std::string test_case_name;
|
|
if (!xml_reader.NodeAttribute("classname", &test_case_name))
|
|
return false;
|
|
std::string test_name;
|
|
if (!xml_reader.NodeAttribute("name", &test_name))
|
|
return false;
|
|
result.full_name = test_case_name + "." + test_name;
|
|
|
|
std::string test_time_str;
|
|
if (!xml_reader.NodeAttribute("time", &test_time_str))
|
|
return false;
|
|
result.elapsed_time = Microseconds(
|
|
static_cast<int64_t>(strtod(test_time_str.c_str(), nullptr) *
|
|
Time::kMicrosecondsPerSecond));
|
|
|
|
// The timestamp attribute records the local date and time of the test
|
|
// execution. It might be missing in the xml generated by older
|
|
// version of test launcher or gtest.
|
|
// https://github.com/google/googletest/blob/main/docs/advanced.md#generating-an-xml-report
|
|
std::string test_timestamp_str;
|
|
Time test_timestamp;
|
|
if (xml_reader.NodeAttribute("timestamp", &test_timestamp_str) &&
|
|
Time::FromString(test_timestamp_str.c_str(), &test_timestamp)) {
|
|
result.timestamp = test_timestamp;
|
|
}
|
|
|
|
result.status = TestResult::TEST_SUCCESS;
|
|
|
|
if (!results->empty() &&
|
|
results->back().full_name == result.full_name &&
|
|
results->back().status == TestResult::TEST_CRASH) {
|
|
// Erase the fail-safe "crashed" result - now we know the test did
|
|
// not crash.
|
|
results->pop_back();
|
|
}
|
|
|
|
for (const Link& link : links) {
|
|
if (link.name == test_name && link.classname == test_case_name) {
|
|
result.AddLink(link.link_name, link.link);
|
|
}
|
|
}
|
|
links.clear();
|
|
for (const Property& property : properties) {
|
|
result.AddProperty(property.name, property.value);
|
|
}
|
|
properties.clear();
|
|
for (const Tag& tag : tags) {
|
|
if (tag.name == test_name && tag.classname == test_case_name) {
|
|
result.AddTag(tag.tag_name, tag.tag_value);
|
|
}
|
|
}
|
|
tags.clear();
|
|
results->push_back(result);
|
|
} else if (node_name == "link" && !xml_reader.IsClosingElement()) {
|
|
Link link;
|
|
if (!xml_reader.NodeAttribute("name", &link.name))
|
|
return false;
|
|
if (!xml_reader.NodeAttribute("classname", &link.classname))
|
|
return false;
|
|
if (!xml_reader.NodeAttribute("link_name", &link.link_name))
|
|
return false;
|
|
if (!xml_reader.ReadElementContent(&link.link))
|
|
return false;
|
|
links.push_back(link);
|
|
} else if (node_name == "link" && xml_reader.IsClosingElement()) {
|
|
// Deliberately empty.
|
|
} else if (node_name == "tag" && !xml_reader.IsClosingElement()) {
|
|
Tag tag;
|
|
if (!xml_reader.NodeAttribute("name", &tag.name))
|
|
return false;
|
|
if (!xml_reader.NodeAttribute("classname", &tag.classname))
|
|
return false;
|
|
if (!xml_reader.NodeAttribute("tag_name", &tag.tag_name))
|
|
return false;
|
|
if (!xml_reader.ReadElementContent(&tag.tag_value))
|
|
return false;
|
|
tags.push_back(tag);
|
|
} else if (node_name == "tag" && xml_reader.IsClosingElement()) {
|
|
// Deliberately empty.
|
|
} else if (node_name == "properties" &&
|
|
!xml_reader.IsClosingElement()) {
|
|
// Deliberately empty, begin of the test properties.
|
|
} else if (node_name == "property" && !xml_reader.IsClosingElement()) {
|
|
Property property;
|
|
if (!xml_reader.NodeAttribute("name", &property.name))
|
|
return false;
|
|
if (!xml_reader.NodeAttribute("value", &property.value))
|
|
return false;
|
|
properties.push_back(property);
|
|
} else if (node_name == "properties" && xml_reader.IsClosingElement()) {
|
|
// Deliberately empty, end of the test properties.
|
|
} else if (node_name == "failure" && !xml_reader.IsClosingElement()) {
|
|
std::string failure_message;
|
|
if (!xml_reader.NodeAttribute("message", &failure_message))
|
|
return false;
|
|
|
|
DCHECK(!results->empty());
|
|
results->back().status = TestResult::TEST_FAILURE;
|
|
|
|
state = STATE_FAILURE;
|
|
} else if (node_name == "testcase" && xml_reader.IsClosingElement()) {
|
|
// Deliberately empty.
|
|
} else if (node_name == "x-test-result-part" &&
|
|
!xml_reader.IsClosingElement()) {
|
|
std::string result_type;
|
|
if (!xml_reader.NodeAttribute("type", &result_type))
|
|
return false;
|
|
|
|
std::string file_name;
|
|
if (!xml_reader.NodeAttribute("file", &file_name))
|
|
return false;
|
|
|
|
std::string line_number_str;
|
|
if (!xml_reader.NodeAttribute("line", &line_number_str))
|
|
return false;
|
|
|
|
int line_number;
|
|
if (!StringToInt(line_number_str, &line_number))
|
|
return false;
|
|
|
|
TestResultPart::Type type;
|
|
if (!TestResultPart::TypeFromString(result_type, &type))
|
|
return false;
|
|
|
|
TestResultPart test_result_part;
|
|
test_result_part.type = type;
|
|
test_result_part.file_name = file_name,
|
|
test_result_part.line_number = line_number;
|
|
DCHECK(!results->empty());
|
|
results->back().test_result_parts.push_back(test_result_part);
|
|
|
|
state = STATE_TEST_RESULT;
|
|
} else {
|
|
return false;
|
|
}
|
|
break;
|
|
case STATE_TEST_RESULT:
|
|
if (node_name == "summary" && !xml_reader.IsClosingElement()) {
|
|
std::string summary;
|
|
if (!xml_reader.ReadElementContent(&summary))
|
|
return false;
|
|
|
|
if (!Base64Decode(summary, &summary))
|
|
return false;
|
|
|
|
DCHECK(!results->empty());
|
|
DCHECK(!results->back().test_result_parts.empty());
|
|
results->back().test_result_parts.back().summary = summary;
|
|
} else if (node_name == "summary" && xml_reader.IsClosingElement()) {
|
|
} else if (node_name == "message" && !xml_reader.IsClosingElement()) {
|
|
std::string message;
|
|
if (!xml_reader.ReadElementContent(&message))
|
|
return false;
|
|
|
|
if (!Base64Decode(message, &message))
|
|
return false;
|
|
|
|
DCHECK(!results->empty());
|
|
DCHECK(!results->back().test_result_parts.empty());
|
|
results->back().test_result_parts.back().message = message;
|
|
} else if (node_name == "message" && xml_reader.IsClosingElement()) {
|
|
} else if (node_name == "x-test-result-part" &&
|
|
xml_reader.IsClosingElement()) {
|
|
state = STATE_TESTCASE;
|
|
} else {
|
|
return false;
|
|
}
|
|
break;
|
|
case STATE_FAILURE:
|
|
if (node_name == "failure" && xml_reader.IsClosingElement())
|
|
state = STATE_TESTCASE;
|
|
else
|
|
return false;
|
|
break;
|
|
case STATE_END:
|
|
// If we are here and there are still XML elements, the file has wrong
|
|
// format.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (crashed) {
|
|
*crashed = (state != STATE_END);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace base
|