blob: 772075b6b3e570e83dd820f3a42fe8963630361e [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* 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.android.tradefed.result.suite;
import com.android.annotations.VisibleForTesting;
import com.android.ddmlib.testrunner.TestResult.TestStatus;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.InvocationContext;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.result.LogFile;
import com.android.tradefed.result.TestDescription;
import com.android.tradefed.result.TestResult;
import com.android.tradefed.result.TestRunResult;
import com.android.tradefed.testtype.Abi;
import com.android.tradefed.testtype.IAbi;
import com.android.tradefed.testtype.suite.TestFailureListener;
import com.android.tradefed.util.AbiUtils;
import com.android.tradefed.util.StreamUtil;
import com.android.tradefed.util.proto.TfMetricProtoUtil;
import com.google.common.base.Strings;
import com.google.common.xml.XmlEscapers;
import com.google.gson.Gson;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* Utility class to save a suite run as an XML. TODO: Remove all the special Compatibility Test
* format work around to get the same format.
*/
public class XmlSuiteResultFormatter implements IFormatterGenerator {
private static final String ENCODING = "UTF-8";
private static final String TYPE = "org.kxml2.io.KXmlParser,org.kxml2.io.KXmlSerializer";
public static final String NS = null;
public static final String TEST_RESULT_FILE_NAME = "test_result.xml";
// XML constants
private static final String ABI_ATTR = "abi";
private static final String BUGREPORT_TAG = "BugReport";
private static final String BUILD_TAG = "Build";
private static final String CASE_TAG = "TestCase";
private static final String COMMAND_LINE_ARGS = "command_line_args";
private static final String DEVICES_ATTR = "devices";
private static final String DONE_ATTR = "done";
private static final String END_DISPLAY_TIME_ATTR = "end_display";
private static final String END_TIME_ATTR = "end";
private static final String FAILED_ATTR = "failed";
private static final String FAILURE_TAG = "Failure";
private static final String HOST_NAME_ATTR = "host_name";
private static final String JAVA_VENDOR_ATTR = "java_vendor";
private static final String JAVA_VERSION_ATTR = "java_version";
private static final String LOGCAT_TAG = "Logcat";
private static final String METRIC_TAG = "Metric";
private static final String METRIC_KEY = "key";
private static final String MESSAGE_ATTR = "message";
private static final String MODULE_TAG = "Module";
private static final String MODULES_DONE_ATTR = "modules_done";
private static final String MODULES_TOTAL_ATTR = "modules_total";
private static final String MODULES_NOT_DONE_REASON = "Reason";
private static final String NAME_ATTR = "name";
private static final String OS_ARCH_ATTR = "os_arch";
private static final String OS_NAME_ATTR = "os_name";
private static final String OS_VERSION_ATTR = "os_version";
private static final String PASS_ATTR = "pass";
private static final String RESULT_ATTR = "result";
private static final String RESULT_TAG = "Result";
private static final String RUN_HISTORY = "run_history";
private static final String RUN_HISTORY_TAG = "RunHistory";
private static final String RUN_TAG = "Run";
private static final String RUNTIME_ATTR = "runtime";
private static final String SCREENSHOT_TAG = "Screenshot";
private static final String SKIPPED_ATTR = "skipped";
private static final String STACK_TAG = "StackTrace";
private static final String START_DISPLAY_TIME_ATTR = "start_display";
private static final String START_TIME_ATTR = "start";
private static final String SUMMARY_TAG = "Summary";
private static final String TEST_TAG = "Test";
private static final String TOTAL_TESTS_ATTR = "total_tests";
private static final String LOG_FILE_NAME_ATTR = "file_name";
/** Helper object for JSON conversion. */
public static final class RunHistory {
public long startTime;
public long endTime;
public long passedTests;
public long failedTests;
public String commandLineArgs;
public String hostName;
}
/**
* Allows to add some attributes to the <Result> tag via {@code serializer.attribute}.
*
* @param serializer The object that serializes an XML suite result.
*/
public void addSuiteAttributes(XmlSerializer serializer)
throws IllegalArgumentException, IllegalStateException, IOException {
// Default implementation does nothing
}
/**
* Reverse operation from {@link #addSuiteAttributes(XmlSerializer)}.
*
* @param parser The parser where to read the attributes from.
* @param context The {@link IInvocationContext} where to put the attributes.
* @throws XmlPullParserException When XmlPullParser fails.
*/
public void parseSuiteAttributes(XmlPullParser parser, IInvocationContext context)
throws XmlPullParserException {
// Default implementation does nothing
}
/**
* Allows to add some attributes to the <Build> tag via {@code serializer.attribute}.
*
* @param serializer The object that serializes an XML suite result.
* @param holder An object that contains information to be written to the suite result.
*/
public void addBuildInfoAttributes(XmlSerializer serializer, SuiteResultHolder holder)
throws IllegalArgumentException, IllegalStateException, IOException {
// Default implementation does nothing
}
/**
* Reverse operation from {@link #addBuildInfoAttributes(XmlSerializer, SuiteResultHolder)}.
*
* @param parser The parser where to read the attributes from.
* @param context The {@link IInvocationContext} where to put the attributes.
* @throws XmlPullParserException When XmlPullParser fails.
*/
public void parseBuildInfoAttributes(XmlPullParser parser, IInvocationContext context)
throws XmlPullParserException {
// Default implementation does nothing
}
/**
* Write the invocation results in an xml format.
*
* @param holder a {@link SuiteResultHolder} holding all the info required for the xml
* @param resultDir the result directory {@link File} where to put the results.
* @return a {@link File} pointing to the xml output file.
*/
@Override
public File writeResults(SuiteResultHolder holder, File resultDir) throws IOException {
File resultFile = new File(resultDir, TEST_RESULT_FILE_NAME);
OutputStream stream = new FileOutputStream(resultFile);
XmlSerializer serializer = null;
try {
serializer = XmlPullParserFactory.newInstance(TYPE, null).newSerializer();
} catch (XmlPullParserException e) {
StreamUtil.close(stream);
throw new IOException(e);
}
serializer.setOutput(stream, ENCODING);
serializer.startDocument(ENCODING, false);
serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
serializer.processingInstruction(
"xml-stylesheet type=\"text/xsl\" href=\"compatibility_result.xsl\"");
serializer.startTag(NS, RESULT_TAG);
serializer.attribute(NS, START_TIME_ATTR, String.valueOf(holder.startTime));
serializer.attribute(NS, END_TIME_ATTR, String.valueOf(holder.endTime));
serializer.attribute(NS, START_DISPLAY_TIME_ATTR, toReadableDateString(holder.startTime));
serializer.attribute(NS, END_DISPLAY_TIME_ATTR, toReadableDateString(holder.endTime));
serializer.attribute(
NS,
COMMAND_LINE_ARGS,
Strings.nullToEmpty(
holder.context.getAttributes().getUniqueMap().get(COMMAND_LINE_ARGS)));
addSuiteAttributes(serializer);
// Device Info
Map<Integer, List<String>> serialsShards = holder.context.getShardsSerials();
String deviceList = "";
if (serialsShards.isEmpty()) {
deviceList = String.join(",", holder.context.getSerials());
} else {
List<String> subList = new ArrayList<>();
for (List<String> list : serialsShards.values()) {
subList.add(String.join(",", list));
}
deviceList = String.join(",", subList);
}
serializer.attribute(NS, DEVICES_ATTR, deviceList);
// Host Info
String hostName = "";
try {
hostName = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException ignored) {
}
serializer.attribute(NS, HOST_NAME_ATTR, hostName);
serializer.attribute(NS, OS_NAME_ATTR, System.getProperty("os.name"));
serializer.attribute(NS, OS_VERSION_ATTR, System.getProperty("os.version"));
serializer.attribute(NS, OS_ARCH_ATTR, System.getProperty("os.arch"));
serializer.attribute(NS, JAVA_VENDOR_ATTR, System.getProperty("java.vendor"));
serializer.attribute(NS, JAVA_VERSION_ATTR, System.getProperty("java.version"));
// Build Info
serializer.startTag(NS, BUILD_TAG);
for (String key : holder.context.getAttributes().keySet()) {
serializer.attribute(
NS, key, String.join(",", holder.context.getAttributes().get(key)));
}
addBuildInfoAttributes(serializer, holder);
serializer.endTag(NS, BUILD_TAG);
// Run History
String runHistoryJson = holder.context.getAttributes().getUniqueMap().get(RUN_HISTORY);
if (runHistoryJson != null) {
serializer.startTag(NS, RUN_HISTORY_TAG);
Gson gson = new Gson();
RunHistory[] runHistories = gson.fromJson(runHistoryJson, RunHistory[].class);
for (RunHistory runHistory : runHistories) {
serializer.startTag(NS, RUN_TAG);
serializer.attribute(NS, START_TIME_ATTR, String.valueOf(runHistory.startTime));
serializer.attribute(NS, END_TIME_ATTR, String.valueOf(runHistory.endTime));
serializer.attribute(NS, PASS_ATTR, Long.toString(runHistory.passedTests));
serializer.attribute(NS, FAILED_ATTR, Long.toString(runHistory.failedTests));
serializer.attribute(NS, COMMAND_LINE_ARGS, runHistory.commandLineArgs);
serializer.attribute(NS, HOST_NAME_ATTR, runHistory.hostName);
serializer.endTag(NS, RUN_TAG);
}
serializer.endTag(NS, RUN_HISTORY_TAG);
}
// Summary
serializer.startTag(NS, SUMMARY_TAG);
serializer.attribute(NS, PASS_ATTR, Long.toString(holder.passedTests));
serializer.attribute(NS, FAILED_ATTR, Long.toString(holder.failedTests));
serializer.attribute(NS, MODULES_DONE_ATTR, Integer.toString(holder.completeModules));
serializer.attribute(NS, MODULES_TOTAL_ATTR, Integer.toString(holder.totalModules));
serializer.endTag(NS, SUMMARY_TAG);
List<TestRunResult> sortedModuleList = sortModules(holder.runResults, holder.modulesAbi);
// Results
for (TestRunResult module : sortedModuleList) {
serializer.startTag(NS, MODULE_TAG);
// To be compatible of CTS strip the abi from the module name when available.
if (holder.modulesAbi.get(module.getName()) != null) {
String moduleAbi = holder.modulesAbi.get(module.getName()).getName();
String moduleNameStripped = module.getName().replace(moduleAbi + " ", "");
serializer.attribute(NS, NAME_ATTR, moduleNameStripped);
serializer.attribute(NS, ABI_ATTR, moduleAbi);
} else {
serializer.attribute(NS, NAME_ATTR, module.getName());
}
serializer.attribute(NS, RUNTIME_ATTR, String.valueOf(module.getElapsedTime()));
boolean isDone = module.isRunComplete() && !module.isRunFailure();
serializer.attribute(NS, DONE_ATTR, Boolean.toString(isDone));
serializer.attribute(
NS, PASS_ATTR, Integer.toString(module.getNumTestsInState(TestStatus.PASSED)));
serializer.attribute(NS, TOTAL_TESTS_ATTR, Integer.toString(module.getNumTests()));
if (!isDone) {
String message = module.getRunFailureMessage();
if (message == null) {
message = "Run was incomplete. Some tests might not have finished.";
}
serializer.startTag(NS, MODULES_NOT_DONE_REASON);
serializer.attribute(NS, MESSAGE_ATTR, message);
serializer.endTag(NS, MODULES_NOT_DONE_REASON);
}
serializeTestCases(serializer, module.getTestResults());
serializer.endTag(NS, MODULE_TAG);
}
serializer.endDocument();
return resultFile;
}
private static void serializeTestCases(
XmlSerializer serializer, Map<TestDescription, TestResult> results)
throws IllegalArgumentException, IllegalStateException, IOException {
// We reformat into the same format as the ResultHandler from CTS to be compatible for now.
Map<String, Map<String, TestResult>> format = new LinkedHashMap<>();
for (Entry<TestDescription, TestResult> cr : results.entrySet()) {
if (format.get(cr.getKey().getClassName()) == null) {
format.put(cr.getKey().getClassName(), new LinkedHashMap<>());
}
Map<String, TestResult> methodResult = format.get(cr.getKey().getClassName());
methodResult.put(cr.getKey().getTestName(), cr.getValue());
}
for (String className : format.keySet()) {
serializer.startTag(NS, CASE_TAG);
serializer.attribute(NS, NAME_ATTR, className);
for (Entry<String, TestResult> individualResult : format.get(className).entrySet()) {
TestStatus status = individualResult.getValue().getStatus();
if (status == null) {
continue; // test was not executed, don't report
}
serializer.startTag(NS, TEST_TAG);
serializer.attribute(NS, RESULT_ATTR, getTestStatusCompatibilityString(status));
serializer.attribute(NS, NAME_ATTR, individualResult.getKey());
if (TestStatus.IGNORED.equals(status)) {
serializer.attribute(NS, SKIPPED_ATTR, Boolean.toString(true));
}
handleTestFailure(serializer, individualResult.getValue().getStackTrace());
HandleLoggedFiles(serializer, individualResult);
for (Entry<String, String> metric :
individualResult.getValue().getMetrics().entrySet()) {
serializer.startTag(NS, METRIC_TAG);
serializer.attribute(NS, METRIC_KEY, metric.getKey());
serializer.text(sanitizeXmlContent(metric.getValue()));
serializer.endTag(NS, METRIC_TAG);
}
serializer.endTag(NS, TEST_TAG);
}
serializer.endTag(NS, CASE_TAG);
}
}
private static void handleTestFailure(XmlSerializer serializer, String fullStack)
throws IllegalArgumentException, IllegalStateException, IOException {
if (fullStack != null) {
String message;
int index = fullStack.indexOf('\n');
if (index < 0) {
// Trace is a single line, just set the message to be the same as the stacktrace.
message = fullStack;
} else {
message = fullStack.substring(0, index);
}
serializer.startTag(NS, FAILURE_TAG);
serializer.attribute(NS, MESSAGE_ATTR, sanitizeXmlContent(message));
serializer.startTag(NS, STACK_TAG);
serializer.text(sanitizeXmlContent(fullStack));
serializer.endTag(NS, STACK_TAG);
serializer.endTag(NS, FAILURE_TAG);
}
}
/** Add files captured by {@link TestFailureListener} on test failures. */
private static void HandleLoggedFiles(
XmlSerializer serializer, Entry<String, TestResult> testResult)
throws IllegalArgumentException, IllegalStateException, IOException {
Map<String, LogFile> loggedFiles = testResult.getValue().getLoggedFiles();
if (loggedFiles == null || loggedFiles.isEmpty()) {
return;
}
for (String key : loggedFiles.keySet()) {
switch (loggedFiles.get(key).getType()) {
case BUGREPORT:
addLogIfNotNull(serializer, BUGREPORT_TAG, key, loggedFiles.get(key).getUrl());
break;
case LOGCAT:
addLogIfNotNull(serializer, LOGCAT_TAG, key, loggedFiles.get(key).getUrl());
break;
case PNG:
case JPEG:
addLogIfNotNull(serializer, SCREENSHOT_TAG, key, loggedFiles.get(key).getUrl());
break;
default:
break;
}
}
}
private static void addLogIfNotNull(
XmlSerializer serializer, String tag, String key, String text)
throws IllegalArgumentException, IllegalStateException, IOException {
if (text == null) {
CLog.d("Text for tag '%s' and key '%s' is null. skipping it.", tag, key);
return;
}
serializer.startTag(NS, tag);
serializer.attribute(NS, LOG_FILE_NAME_ATTR, key);
serializer.text(text);
serializer.endTag(NS, tag);
}
/**
* Return the given time as a {@link String} suitable for displaying.
*
* <p>Example: Fri Aug 20 15:13:03 PDT 2010
*
* @param time the epoch time in ms since midnight Jan 1, 1970
*/
private static String toReadableDateString(long time) {
SimpleDateFormat dateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy");
return dateFormat.format(new Date(time));
}
/** Convert our test status to a format compatible with CTS backend. */
private static String getTestStatusCompatibilityString(TestStatus status) {
switch (status) {
case PASSED:
return "pass";
case FAILURE:
return "fail";
default:
return status.toString();
}
}
private static TestStatus getStatusFromString(String status) {
switch (status) {
case "pass":
return TestStatus.PASSED;
case "fail":
return TestStatus.FAILURE;
default:
return TestStatus.valueOf(status);
}
}
/** {@inheritDoc} */
@Override
public SuiteResultHolder parseResults(File resultDir, boolean shallow) throws IOException {
File resultFile = new File(resultDir, TEST_RESULT_FILE_NAME);
if (!resultFile.exists()) {
CLog.e("Could not find %s for loading the results.", resultFile.getAbsolutePath());
return null;
}
SuiteResultHolder invocation = new SuiteResultHolder();
IInvocationContext context = new InvocationContext();
try {
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser parser = factory.newPullParser();
parser.setInput(new FileReader(resultFile));
parser.nextTag();
parser.require(XmlPullParser.START_TAG, NS, RESULT_TAG);
invocation.startTime = Long.valueOf(parser.getAttributeValue(NS, START_TIME_ATTR));
invocation.endTime = Long.valueOf(parser.getAttributeValue(NS, END_TIME_ATTR));
invocation.hostName = parser.getAttributeValue(NS, HOST_NAME_ATTR);
context.addInvocationAttribute(
COMMAND_LINE_ARGS, parser.getAttributeValue(NS, COMMAND_LINE_ARGS));
parseSuiteAttributes(parser, context);
String deviceList = parser.getAttributeValue(NS, DEVICES_ATTR);
int i = 0;
// TODO: Fix to correctly handle the number of device per shard.
for (String device : deviceList.split(",")) {
context.addSerialsFromShard(i, Arrays.asList(device));
i++;
}
parser.nextTag();
parser.require(XmlPullParser.START_TAG, NS, BUILD_TAG);
for (int index = 0; index < parser.getAttributeCount(); index++) {
String key = parser.getAttributeName(index);
String value = parser.getAttributeValue(NS, key);
// TODO: Handle list of values that are comma separated.
context.addInvocationAttribute(key, value);
}
parseBuildInfoAttributes(parser, context);
parser.nextTag();
parser.require(XmlPullParser.END_TAG, NS, BUILD_TAG);
parser.nextTag();
boolean hasRunHistoryTag = true;
try {
parser.require(XmlPullParser.START_TAG, NS, RUN_HISTORY_TAG);
} catch (XmlPullParserException e) {
hasRunHistoryTag = false;
}
if (hasRunHistoryTag) {
handleRunHistoryLevel(parser);
}
parser.require(XmlPullParser.START_TAG, NS, SUMMARY_TAG);
invocation.completeModules =
Integer.parseInt(parser.getAttributeValue(NS, MODULES_DONE_ATTR));
invocation.totalModules =
Integer.parseInt(parser.getAttributeValue(NS, MODULES_TOTAL_ATTR));
invocation.passedTests = Integer.parseInt(parser.getAttributeValue(NS, PASS_ATTR));
invocation.failedTests = Integer.parseInt(parser.getAttributeValue(NS, FAILED_ATTR));
parser.nextTag();
parser.require(XmlPullParser.END_TAG, NS, SUMMARY_TAG);
if (!shallow) {
Collection<TestRunResult> results = new ArrayList<>();
Map<String, IAbi> moduleAbis = new HashMap<>();
// Module level information parsing
handleModuleLevel(parser, results, moduleAbis);
parser.require(XmlPullParser.END_TAG, NS, RESULT_TAG);
invocation.runResults = results;
invocation.modulesAbi = moduleAbis;
}
} catch (XmlPullParserException e) {
CLog.e(e);
return null;
}
invocation.context = context;
return invocation;
}
/** Sort the list of results based on their name without abi primarily then secondly on abi. */
@VisibleForTesting
List<TestRunResult> sortModules(
Collection<TestRunResult> results, Map<String, IAbi> moduleAbis) {
List<TestRunResult> sortedList = new ArrayList<>(results);
Collections.sort(
sortedList,
new Comparator<TestRunResult>() {
@Override
public int compare(TestRunResult o1, TestRunResult o2) {
String module1NameStripped = o1.getName();
String module1Abi = "";
if (moduleAbis.get(module1NameStripped) != null) {
module1Abi = moduleAbis.get(module1NameStripped).getName();
module1NameStripped = module1NameStripped.replace(module1Abi + " ", "");
}
String module2NameStripped = o2.getName();
String module2Abi = "";
if (moduleAbis.get(module2NameStripped) != null) {
module2Abi = moduleAbis.get(module2NameStripped).getName();
module2NameStripped = module2NameStripped.replace(module2Abi + " ", "");
}
int res = module1NameStripped.compareTo(module2NameStripped);
if (res != 0) {
return res;
}
// Use the Abi as discriminant to always sort abi in the same order.
return module1Abi.compareTo(module2Abi);
}
});
return sortedList;
}
/** Handle the parsing and replay of all run history information. */
private void handleRunHistoryLevel(XmlPullParser parser)
throws IOException, XmlPullParserException {
while (parser.nextTag() == XmlPullParser.START_TAG) {
parser.require(XmlPullParser.START_TAG, NS, RUN_TAG);
parser.nextTag();
parser.require(XmlPullParser.END_TAG, NS, RUN_TAG);
}
parser.require(XmlPullParser.END_TAG, NS, RUN_HISTORY_TAG);
parser.nextTag();
}
/**
* Handle the parsing and replay of all the information inside a module (class, method,
* failures).
*/
private void handleModuleLevel(
XmlPullParser parser, Collection<TestRunResult> results, Map<String, IAbi> moduleAbis)
throws IOException, XmlPullParserException {
while (parser.nextTag() == XmlPullParser.START_TAG) {
parser.require(XmlPullParser.START_TAG, NS, MODULE_TAG);
TestRunResult module = new TestRunResult();
results.add(module);
String name = parser.getAttributeValue(NS, NAME_ATTR);
String abi = parser.getAttributeValue(NS, ABI_ATTR);
String moduleId = name;
if (abi != null) {
moduleId = AbiUtils.createId(abi, name);
moduleAbis.put(moduleId, new Abi(abi, AbiUtils.getBitness(abi)));
}
long moduleElapsedTime = Long.parseLong(parser.getAttributeValue(NS, RUNTIME_ATTR));
boolean moduleDone = Boolean.parseBoolean(parser.getAttributeValue(NS, DONE_ATTR));
int totalTests = Integer.parseInt(parser.getAttributeValue(NS, TOTAL_TESTS_ATTR));
module.testRunStarted(moduleId, totalTests);
// TestCase level information parsing
while (parser.nextTag() == XmlPullParser.START_TAG) {
// If a reason for not done exists, handle it.
if (parser.getName().equals(MODULES_NOT_DONE_REASON)) {
parser.require(XmlPullParser.START_TAG, NS, MODULES_NOT_DONE_REASON);
parser.nextTag();
parser.require(XmlPullParser.END_TAG, NS, MODULES_NOT_DONE_REASON);
continue;
}
parser.require(XmlPullParser.START_TAG, NS, CASE_TAG);
String className = parser.getAttributeValue(NS, NAME_ATTR);
// Test level information parsing
handleTestCaseLevel(parser, module, className);
parser.require(XmlPullParser.END_TAG, NS, CASE_TAG);
}
module.testRunEnded(moduleElapsedTime, new HashMap<String, Metric>());
module.setRunComplete(moduleDone);
parser.require(XmlPullParser.END_TAG, NS, MODULE_TAG);
}
}
/** Parse and replay all the individual test cases level (method) informations. */
private void handleTestCaseLevel(
XmlPullParser parser, TestRunResult currentModule, String className)
throws IOException, XmlPullParserException {
while (parser.nextTag() == XmlPullParser.START_TAG) {
parser.require(XmlPullParser.START_TAG, NS, TEST_TAG);
String methodName = parser.getAttributeValue(NS, NAME_ATTR);
TestStatus status = getStatusFromString(parser.getAttributeValue(NS, RESULT_ATTR));
TestDescription description = new TestDescription(className, methodName);
currentModule.testStarted(description);
if (TestStatus.IGNORED.equals(status)) {
currentModule.testIgnored(description);
}
HashMap<String, Metric> metrics = new HashMap<String, Metric>();
while (parser.nextTag() == XmlPullParser.START_TAG) { // Failure level
if (parser.getName().equals(FAILURE_TAG)) {
String failure = parser.getAttributeValue(NS, MESSAGE_ATTR);
if (parser.nextTag() == XmlPullParser.START_TAG) {
parser.require(XmlPullParser.START_TAG, NS, STACK_TAG);
failure = parser.nextText();
parser.require(XmlPullParser.END_TAG, NS, STACK_TAG);
}
if (TestStatus.FAILURE.equals(status)) {
currentModule.testFailed(description, failure);
} else if (TestStatus.ASSUMPTION_FAILURE.equals(status)) {
currentModule.testAssumptionFailure(description, failure);
}
parser.nextTag();
parser.require(XmlPullParser.END_TAG, NS, FAILURE_TAG);
}
parseLoggedFiles(parser, currentModule);
metrics.putAll(parseMetrics(parser));
}
currentModule.testEnded(description, metrics);
parser.require(XmlPullParser.END_TAG, NS, TEST_TAG);
}
}
/** Add files captured by {@link TestFailureListener} on test failures. */
private static void parseLoggedFiles(XmlPullParser parser, TestRunResult currentModule)
throws XmlPullParserException, IOException {
if (parser.getName().equals(BUGREPORT_TAG)) {
parseSingleFiles(parser, currentModule, BUGREPORT_TAG, LogDataType.BUGREPORTZ);
} else if (parser.getName().equals(LOGCAT_TAG)) {
parseSingleFiles(parser, currentModule, LOGCAT_TAG, LogDataType.LOGCAT);
} else if (parser.getName().equals(SCREENSHOT_TAG)) {
parseSingleFiles(parser, currentModule, SCREENSHOT_TAG, LogDataType.PNG);
}
}
private static void parseSingleFiles(
XmlPullParser parser, TestRunResult currentModule, String tagName, LogDataType type)
throws XmlPullParserException, IOException {
String name = parser.getAttributeValue(NS, LOG_FILE_NAME_ATTR);
String logFileUrl = parser.nextText();
currentModule.testLogSaved(name, new LogFile(logFileUrl, logFileUrl, type));
parser.require(XmlPullParser.END_TAG, NS, tagName);
}
private static HashMap<String, Metric> parseMetrics(XmlPullParser parser)
throws XmlPullParserException, IOException {
HashMap<String, Metric> metrics = new HashMap<>();
if (parser.getName().equals(METRIC_TAG)) {
parser.require(XmlPullParser.START_TAG, NS, METRIC_TAG);
for (int index = 0; index < parser.getAttributeCount(); index++) {
String key = parser.getAttributeValue(index);
String value = parser.nextText();
metrics.put(key, TfMetricProtoUtil.stringToMetric(value));
}
parser.require(XmlPullParser.END_TAG, NS, METRIC_TAG);
}
return metrics;
}
@VisibleForTesting
static String sanitizeXmlContent(String s) {
return XmlEscapers.xmlContentEscaper().escape(s);
}
}