/*
* Copyright 2012-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.facebook.buck.test;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.facebook.buck.test.result.type.ResultType;
import com.facebook.buck.util.XmlDomParser;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import java.io.IOException;
import java.io.StringReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
public class XmlTestResultParser {
/** Utility Class: Do not instantiate. */
private XmlTestResultParser() {}
public static List<TestCaseSummary> parseAndroid(Path xmlFile, String serialNumber)
throws IOException, SAXException {
String fileContents = new String(Files.readAllBytes(xmlFile), UTF_8);
Document doc =
XmlDomParser.parse(
new InputSource(new StringReader(fileContents)), /* namespaceAware */ true);
Element root = doc.getDocumentElement();
Preconditions.checkState("testsuite".equals(root.getTagName()));
NodeList testElements = doc.getElementsByTagName("testcase");
throwIfProcessFailed(root);
List<TestCaseSummary> results = new ArrayList<>();
Map<String, List<TestResultSummary>> testResultsMap = new LinkedHashMap<>();
for (int i = 0; i < testElements.getLength(); i++) {
Element node = (Element) testElements.item(i);
String className = node.getAttribute("classname");
List<TestResultSummary> testResults = testResultsMap.get(className);
if (testResults == null) {
testResults = new ArrayList<>();
testResultsMap.put(className, testResults);
}
testResults.add(getTestResultFromElement(node, getTestCaseName(className, serialNumber)));
}
for (Map.Entry<String, List<TestResultSummary>> entry : testResultsMap.entrySet()) {
TestCaseSummary testCaseSummary =
new TestCaseSummary(getTestCaseName(entry.getKey(), serialNumber), entry.getValue());
results.add(testCaseSummary);
}
return results;
}
private static void throwIfProcessFailed(Element root) {
NodeList children = root.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
if (!(children.item(i) instanceof Element)) {
continue;
}
Element child = (Element) children.item(i);
if (child.getTagName().equals("failure")) {
throw new TestProcessCrashed(child.getTextContent() + "\nSee logcat for more details.");
}
}
}
private static String getTestCaseName(String className, String serialNumber) {
return className + " (" + serialNumber + ")";
}
private static TestResultSummary getTestResultFromElement(Element node, String testCaseName) {
double time = Float.parseFloat(node.getAttribute("time"));
String testName = node.getAttribute("name");
String message = null;
String stacktrace = null;
String stdOut = null;
String stdErr = null;
ResultType type = ResultType.SUCCESS;
NodeList failure = node.getElementsByTagName("failure");
if (failure.getLength() == 1) {
stacktrace = failure.item(0).getTextContent();
type = ResultType.FAILURE;
String[] firstLineParts = stacktrace.split("\n")[0].split(":", 2);
message = firstLineParts.length > 1 ? firstLineParts[1].trim() : "";
}
return new TestResultSummary(
testCaseName, testName, type, Math.round(time * 1000), message, stacktrace, stdOut, stdErr);
}
public static TestCaseSummary parse(Path xmlFile) throws IOException {
String xmlFileContents = new String(Files.readAllBytes(xmlFile), UTF_8);
try {
return doParse(xmlFileContents);
} catch (NumberFormatException | SAXException e) {
// This is an attempt to track down an inexplicable error that we have observed in the wild.
String message = createDetailedExceptionMessage(xmlFile, xmlFileContents);
throw new RuntimeException(message, e);
}
}
private static TestCaseSummary doParse(String xml) throws IOException, SAXException {
Document doc =
XmlDomParser.parse(new InputSource(new StringReader(xml)), /* namespaceAware */ true);
Element root = doc.getDocumentElement();
Preconditions.checkState("testcase".equals(root.getTagName()));
String testCaseName = root.getAttribute("name");
NodeList testElements = doc.getElementsByTagName("test");
List<TestResultSummary> testResults = Lists.newArrayListWithCapacity(testElements.getLength());
for (int i = 0; i < testElements.getLength(); i++) {
Element node = (Element) testElements.item(i);
String testName = node.getAttribute("name");
long time = Long.parseLong(node.getAttribute("time"));
String typeString = node.getAttribute("type");
ResultType type = ResultType.valueOf(typeString);
String message;
String stacktrace;
if (type == ResultType.SUCCESS) {
message = null;
stacktrace = null;
} else {
message = node.getAttribute("message");
stacktrace = node.getAttribute("stacktrace");
}
NodeList stdoutElements = node.getElementsByTagName("stdout");
String stdOut;
if (stdoutElements.getLength() == 1) {
stdOut = stdoutElements.item(0).getTextContent();
} else {
stdOut = null;
}
NodeList stderrElements = node.getElementsByTagName("stderr");
String stdErr;
if (stderrElements.getLength() == 1) {
stdErr = stderrElements.item(0).getTextContent();
} else {
stdErr = null;
}
TestResultSummary testResult =
new TestResultSummary(
testCaseName, testName, type, time, message, stacktrace, stdOut, stdErr);
testResults.add(testResult);
}
return new TestCaseSummary(testCaseName, testResults);
}
private static String createDetailedExceptionMessage(Path xmlFile, String xmlFileContents) {
return "Error parsing test result data in "
+ xmlFile.toAbsolutePath()
+ ".\n"
+ "File contents:\n"
+ xmlFileContents;
}
}