• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.tradefed.result.suite;
17 
18 import com.android.annotations.VisibleForTesting;
19 import com.android.tradefed.build.IBuildInfo;
20 import com.android.tradefed.invoker.IInvocationContext;
21 import com.android.tradefed.invoker.InvocationContext;
22 import com.android.tradefed.log.LogUtil.CLog;
23 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
24 import com.android.tradefed.result.FailureDescription;
25 import com.android.tradefed.result.LogDataType;
26 import com.android.tradefed.result.LogFile;
27 import com.android.tradefed.result.TestDescription;
28 import com.android.tradefed.result.TestResult;
29 import com.android.tradefed.result.TestRunResult;
30 import com.android.tradefed.result.TestStatus;
31 import com.android.tradefed.result.error.ErrorIdentifier;
32 import com.android.tradefed.testtype.Abi;
33 import com.android.tradefed.testtype.IAbi;
34 import com.android.tradefed.util.AbiUtils;
35 import com.android.tradefed.util.StreamUtil;
36 import com.android.tradefed.util.proto.TfMetricProtoUtil;
37 
38 import com.google.common.base.Strings;
39 import com.google.common.xml.XmlEscapers;
40 import com.google.gson.Gson;
41 
42 import org.xmlpull.v1.XmlPullParser;
43 import org.xmlpull.v1.XmlPullParserException;
44 import org.xmlpull.v1.XmlPullParserFactory;
45 import org.xmlpull.v1.XmlSerializer;
46 
47 import java.io.File;
48 import java.io.FileOutputStream;
49 import java.io.FileReader;
50 import java.io.IOException;
51 import java.io.OutputStream;
52 import java.net.InetAddress;
53 import java.net.UnknownHostException;
54 import java.text.SimpleDateFormat;
55 import java.util.ArrayList;
56 import java.util.Arrays;
57 import java.util.Collection;
58 import java.util.Collections;
59 import java.util.Comparator;
60 import java.util.Date;
61 import java.util.HashMap;
62 import java.util.LinkedHashMap;
63 import java.util.LinkedHashSet;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.Map.Entry;
67 import java.util.Set;
68 
69 /**
70  * Utility class to save a suite run as an XML. TODO: Remove all the special Compatibility Test
71  * format work around to get the same format.
72  */
73 public class XmlSuiteResultFormatter implements IFormatterGenerator {
74 
75     // The maximum size of a stack trace saved in the report.
76     private static final int STACK_TRACE_MAX_SIZE = 1024 * 1024;
77 
78     private static final String ENCODING = "UTF-8";
79     private static final String TYPE = "org.kxml2.io.KXmlParser,org.kxml2.io.KXmlSerializer";
80     public static final String NS = null;
81 
82     public static final String TEST_RESULT_FILE_NAME = "test_result.xml";
83 
84     // XML constants
85     private static final String ABI_ATTR = "abi";
86     private static final String BUGREPORT_TAG = "BugReport";
87     private static final String BUILD_TAG = "Build";
88     private static final String CASE_TAG = "TestCase";
89     private static final String COMMAND_LINE_ARGS = "command_line_args";
90     private static final String DEVICES_ATTR = "devices";
91     private static final String DEVICE_KERNEL_INFO_ATTR = "device_kernel_info";
92     private static final String DONE_ATTR = "done";
93     private static final String END_DISPLAY_TIME_ATTR = "end_display";
94     private static final String END_TIME_ATTR = "end";
95     private static final String FAILED_ATTR = "failed";
96     private static final String FAILURE_TAG = "Failure";
97     private static final String HOST_NAME_ATTR = "host_name";
98     private static final String JAVA_VENDOR_ATTR = "java_vendor";
99     private static final String JAVA_VERSION_ATTR = "java_version";
100     private static final String LOGCAT_TAG = "Logcat";
101 
102     private static final String METRIC_TAG = "Metric";
103     private static final String METRIC_KEY = "key";
104 
105     private static final String MESSAGE_ATTR = "message";
106     private static final String MODULE_TAG = "Module";
107     private static final String MODULES_DONE_ATTR = "modules_done";
108     private static final String MODULES_TOTAL_ATTR = "modules_total";
109     private static final String MODULES_NOT_DONE_REASON = "Reason";
110     private static final String NAME_ATTR = "name";
111     private static final String OS_ARCH_ATTR = "os_arch";
112     private static final String OS_NAME_ATTR = "os_name";
113     private static final String OS_VERSION_ATTR = "os_version";
114     private static final String PASS_ATTR = "pass";
115 
116     private static final String RESULT_ATTR = "result";
117     private static final String RESULT_TAG = "Result";
118     private static final String RUN_HISTORY = "run_history";
119     private static final String RUN_HISTORY_TAG = "RunHistory";
120     private static final String RUN_TAG = "Run";
121     private static final String RUNTIME_ATTR = "runtime";
122     private static final String SCREENSHOT_TAG = "Screenshot";
123     private static final String SKIPPED_ATTR = "skipped";
124     private static final String STACK_TAG = "StackTrace";
125     private static final String ERROR_NAME_ATTR = "error_name";
126     private static final String ERROR_CODE_ATTR = "error_code";
127     private static final String START_DISPLAY_TIME_ATTR = "start_display";
128     private static final String START_TIME_ATTR = "start";
129 
130     private static final String SUMMARY_TAG = "Summary";
131     private static final String SYSTEM_IMG_INFO_ATTR = "system_img_info";
132     private static final String TEST_TAG = "Test";
133     private static final String TOTAL_TESTS_ATTR = "total_tests";
134     private static final String VENDOR_IMG_INFO_ATTR = "vendor_img_info";
135 
136     private static final String LOG_FILE_NAME_ATTR = "file_name";
137 
138     /** Helper object for JSON conversion. */
139     public static final class RunHistory {
140         public long startTime;
141         public long endTime;
142         public long passedTests;
143         public long failedTests;
144         public String commandLineArgs;
145         public String hostName;
146     }
147 
148     /** Sanitizes a string to escape the special characters. */
sanitizeXmlContent(String s)149     public static String sanitizeXmlContent(String s) {
150         return XmlEscapers.xmlContentEscaper().escape(s);
151     }
152 
153     /** Truncates the full stack trace with maximum {@link STACK_TRACE_MAX_SIZE} characters. */
truncateStackTrace(String fullStackTrace, String testCaseName)154     public static String truncateStackTrace(String fullStackTrace, String testCaseName) {
155         if (fullStackTrace == null) {
156             return null;
157         }
158         if (fullStackTrace.length() > STACK_TRACE_MAX_SIZE) {
159             CLog.i(
160                     "The stack trace for test case %s contains %d characters, and has been"
161                             + " truncated to %d characters in %s.",
162                     testCaseName,
163                     fullStackTrace.length(),
164                     STACK_TRACE_MAX_SIZE,
165                     TEST_RESULT_FILE_NAME);
166             return fullStackTrace.substring(0, STACK_TRACE_MAX_SIZE);
167         }
168         return fullStackTrace;
169     }
170 
171     /**
172      * Allows to add some attributes to the <Result> tag via {@code serializer.attribute}.
173      *
174      * @param serializer The object that serializes an XML suite result.
175      */
addSuiteAttributes(XmlSerializer serializer)176     public void addSuiteAttributes(XmlSerializer serializer)
177             throws IllegalArgumentException, IllegalStateException, IOException {
178         // Default implementation does nothing
179     }
180 
181     /**
182      * Reverse operation from {@link #addSuiteAttributes(XmlSerializer)}.
183      *
184      * @param parser The parser where to read the attributes from.
185      * @param context The {@link IInvocationContext} where to put the attributes.
186      * @throws XmlPullParserException When XmlPullParser fails.
187      */
parseSuiteAttributes(XmlPullParser parser, IInvocationContext context)188     public void parseSuiteAttributes(XmlPullParser parser, IInvocationContext context)
189             throws XmlPullParserException {
190         // Default implementation does nothing
191     }
192 
193     /**
194      * Allows to add some attributes to the <Build> tag via {@code serializer.attribute}.
195      *
196      * @param serializer The object that serializes an XML suite result.
197      * @param holder An object that contains information to be written to the suite result.
198      */
addBuildInfoAttributes(XmlSerializer serializer, SuiteResultHolder holder)199     public void addBuildInfoAttributes(XmlSerializer serializer, SuiteResultHolder holder)
200             throws IllegalArgumentException, IllegalStateException, IOException {
201         // Default implementation does nothing
202     }
203 
204     /**
205      * Reverse operation from {@link #addBuildInfoAttributes(XmlSerializer, SuiteResultHolder)}.
206      *
207      * @param parser The parser where to read the attributes from.
208      * @param context The {@link IInvocationContext} where to put the attributes.
209      * @throws XmlPullParserException When XmlPullParser fails.
210      */
parseBuildInfoAttributes(XmlPullParser parser, IInvocationContext context)211     public void parseBuildInfoAttributes(XmlPullParser parser, IInvocationContext context)
212             throws XmlPullParserException {
213         // Default implementation does nothing
214     }
215 
216     /**
217      * Write the invocation results in an xml format.
218      *
219      * @param holder a {@link SuiteResultHolder} holding all the info required for the xml
220      * @param resultDir the result directory {@link File} where to put the results.
221      * @return a {@link File} pointing to the xml output file.
222      */
223     @Override
writeResults(SuiteResultHolder holder, File resultDir)224     public File writeResults(SuiteResultHolder holder, File resultDir) throws IOException {
225         File resultFile = new File(resultDir, TEST_RESULT_FILE_NAME);
226         OutputStream stream = new FileOutputStream(resultFile);
227         XmlSerializer serializer = null;
228         try {
229             serializer = XmlPullParserFactory.newInstance(TYPE, null).newSerializer();
230         } catch (XmlPullParserException e) {
231             StreamUtil.close(stream);
232             throw new IOException(e);
233         }
234         serializer.setOutput(stream, ENCODING);
235         serializer.startDocument(ENCODING, false);
236         serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
237         serializer.processingInstruction(
238                 "xml-stylesheet type=\"text/xsl\" href=\"compatibility_result.xsl\"");
239         serializer.startTag(NS, RESULT_TAG);
240         serializer.attribute(NS, START_TIME_ATTR, String.valueOf(holder.startTime));
241         serializer.attribute(NS, END_TIME_ATTR, String.valueOf(holder.endTime));
242         serializer.attribute(NS, START_DISPLAY_TIME_ATTR, toReadableDateString(holder.startTime));
243         serializer.attribute(NS, END_DISPLAY_TIME_ATTR, toReadableDateString(holder.endTime));
244         serializer.attribute(
245                 NS,
246                 COMMAND_LINE_ARGS,
247                 Strings.nullToEmpty(
248                         holder.context.getAttributes().getUniqueMap().get(COMMAND_LINE_ARGS)));
249 
250         addSuiteAttributes(serializer);
251 
252         // Device Info
253         Map<Integer, List<String>> serialsShards = holder.context.getShardsSerials();
254         String deviceList = "";
255         if (serialsShards.isEmpty()) {
256             deviceList = String.join(",", holder.context.getSerials());
257         } else {
258             Set<String> subSet = new LinkedHashSet<>();
259             for (List<String> list : serialsShards.values()) {
260                 subSet.addAll(list);
261             }
262             deviceList = String.join(",", subSet);
263         }
264         serializer.attribute(NS, DEVICES_ATTR, deviceList);
265 
266         // Host Info
267         String hostName = "";
268         try {
269             hostName = InetAddress.getLocalHost().getHostName();
270         } catch (UnknownHostException ignored) {
271         }
272         serializer.attribute(NS, HOST_NAME_ATTR, hostName);
273         serializer.attribute(NS, OS_NAME_ATTR, System.getProperty("os.name"));
274         serializer.attribute(NS, OS_VERSION_ATTR, System.getProperty("os.version"));
275         serializer.attribute(NS, OS_ARCH_ATTR, System.getProperty("os.arch"));
276         serializer.attribute(NS, JAVA_VENDOR_ATTR, System.getProperty("java.vendor"));
277         serializer.attribute(NS, JAVA_VERSION_ATTR, System.getProperty("java.version"));
278 
279         // Build Info
280         serializer.startTag(NS, BUILD_TAG);
281         for (String key : holder.context.getAttributes().keySet()) {
282             serializer.attribute(
283                     NS,
284                     sanitizeAttributesKey(key),
285                     String.join(",", holder.context.getAttributes().get(key)));
286         }
287         if (!holder.context.getBuildInfos().isEmpty()) {
288             IBuildInfo buildInfo = holder.context.getBuildInfos().get(0);
289             addBuildInfoAttributesIfNotNull(serializer, buildInfo, DEVICE_KERNEL_INFO_ATTR);
290             addBuildInfoAttributesIfNotNull(serializer, buildInfo, SYSTEM_IMG_INFO_ATTR);
291             addBuildInfoAttributesIfNotNull(serializer, buildInfo, VENDOR_IMG_INFO_ATTR);
292         }
293         addBuildInfoAttributes(serializer, holder);
294         serializer.endTag(NS, BUILD_TAG);
295 
296         // Run History
297         String runHistoryJson = holder.context.getAttributes().getUniqueMap().get(RUN_HISTORY);
298         if (runHistoryJson != null) {
299             serializer.startTag(NS, RUN_HISTORY_TAG);
300             Gson gson = new Gson();
301             RunHistory[] runHistories = gson.fromJson(runHistoryJson, RunHistory[].class);
302             for (RunHistory runHistory : runHistories) {
303                 serializer.startTag(NS, RUN_TAG);
304                 serializer.attribute(NS, START_TIME_ATTR, String.valueOf(runHistory.startTime));
305                 serializer.attribute(NS, END_TIME_ATTR, String.valueOf(runHistory.endTime));
306                 serializer.attribute(NS, PASS_ATTR, Long.toString(runHistory.passedTests));
307                 serializer.attribute(NS, FAILED_ATTR, Long.toString(runHistory.failedTests));
308                 serializer.attribute(NS, COMMAND_LINE_ARGS, runHistory.commandLineArgs);
309                 serializer.attribute(NS, HOST_NAME_ATTR, runHistory.hostName);
310                 serializer.endTag(NS, RUN_TAG);
311             }
312             serializer.endTag(NS, RUN_HISTORY_TAG);
313         }
314 
315         // Summary
316         serializer.startTag(NS, SUMMARY_TAG);
317         serializer.attribute(NS, PASS_ATTR, Long.toString(holder.passedTests));
318         serializer.attribute(NS, FAILED_ATTR, Long.toString(holder.failedTests));
319         serializer.attribute(NS, MODULES_DONE_ATTR, Integer.toString(holder.completeModules));
320         serializer.attribute(NS, MODULES_TOTAL_ATTR, Integer.toString(holder.totalModules));
321         serializer.endTag(NS, SUMMARY_TAG);
322 
323         List<TestRunResult> sortedModuleList = sortModules(holder.runResults, holder.modulesAbi);
324         // Results
325         for (TestRunResult module : sortedModuleList) {
326             serializer.startTag(NS, MODULE_TAG);
327             // To be compatible of CTS strip the abi from the module name when available.
328             if (holder.modulesAbi.get(module.getName()) != null) {
329                 String moduleAbi = holder.modulesAbi.get(module.getName()).getName();
330                 String moduleNameStripped = module.getName().replace(moduleAbi + " ", "");
331                 serializer.attribute(NS, NAME_ATTR, moduleNameStripped);
332                 serializer.attribute(NS, ABI_ATTR, moduleAbi);
333             } else {
334                 serializer.attribute(NS, NAME_ATTR, module.getName());
335             }
336             serializer.attribute(NS, RUNTIME_ATTR, String.valueOf(module.getElapsedTime()));
337             boolean isDone = module.isRunComplete() && !module.isRunFailure();
338 
339             serializer.attribute(NS, DONE_ATTR, Boolean.toString(isDone));
340             serializer.attribute(
341                     NS, PASS_ATTR, Integer.toString(module.getNumTestsInState(TestStatus.PASSED)));
342             serializer.attribute(NS, TOTAL_TESTS_ATTR, Integer.toString(module.getNumTests()));
343 
344             if (!isDone) {
345                 String message = module.getRunFailureMessage();
346                 if (message == null) {
347                     message = "Run was incomplete. Some tests might not have finished.";
348                 }
349                 FailureDescription failureDescription = module.getRunFailureDescription();
350                 serializer.startTag(NS, MODULES_NOT_DONE_REASON);
351                 serializer.attribute(NS, MESSAGE_ATTR, sanitizeXmlContent(message));
352                 if (failureDescription != null && failureDescription.getErrorIdentifier() != null) {
353                     serializer.attribute(
354                             NS, ERROR_NAME_ATTR, failureDescription.getErrorIdentifier().name());
355                     serializer.attribute(
356                             NS,
357                             ERROR_CODE_ATTR,
358                             Long.toString(failureDescription.getErrorIdentifier().code()));
359                 }
360                 serializer.endTag(NS, MODULES_NOT_DONE_REASON);
361             }
362             serializeTestCases(serializer, module.getTestResults());
363             serializer.endTag(NS, MODULE_TAG);
364         }
365         serializer.endDocument();
366         return resultFile;
367     }
368 
serializeTestCases( XmlSerializer serializer, Map<TestDescription, TestResult> results)369     private void serializeTestCases(
370             XmlSerializer serializer, Map<TestDescription, TestResult> results)
371             throws IllegalArgumentException, IllegalStateException, IOException {
372         // We reformat into the same format as the ResultHandler from CTS to be compatible for now.
373         Map<String, Map<String, TestResult>> format = new LinkedHashMap<>();
374         for (Entry<TestDescription, TestResult> cr : results.entrySet()) {
375             if (format.get(cr.getKey().getClassName()) == null) {
376                 format.put(cr.getKey().getClassName(), new LinkedHashMap<>());
377             }
378             Map<String, TestResult> methodResult = format.get(cr.getKey().getClassName());
379             methodResult.put(cr.getKey().getTestName(), cr.getValue());
380         }
381 
382         for (String className : format.keySet()) {
383             serializer.startTag(NS, CASE_TAG);
384             serializer.attribute(NS, NAME_ATTR, className);
385             for (Entry<String, TestResult> individualResult : format.get(className).entrySet()) {
386                 TestStatus status = individualResult.getValue().getResultStatus();
387                 // TODO(b/322204420): Report skipped to XML and support parsing it
388                 if (TestStatus.SKIPPED.equals(status)) {
389                     continue;
390                 }
391                 if (status == null) {
392                     continue; // test was not executed, don't report
393                 }
394                 serializer.startTag(NS, TEST_TAG);
395                 serializer.attribute(
396                         NS, RESULT_ATTR, TestStatus.convertToCompatibilityString(status));
397                 serializer.attribute(NS, NAME_ATTR, individualResult.getKey());
398                 if (TestStatus.IGNORED.equals(status)) {
399                     serializer.attribute(NS, SKIPPED_ATTR, Boolean.toString(true));
400                 }
401 
402                 handleTestFailure(serializer, individualResult);
403 
404                 HandleLoggedFiles(serializer, individualResult);
405 
406                 for (Entry<String, String> metric :
407                         TfMetricProtoUtil.compatibleConvert(
408                                         individualResult.getValue().getProtoMetrics())
409                                 .entrySet()) {
410                     serializer.startTag(NS, METRIC_TAG);
411                     serializer.attribute(NS, METRIC_KEY, metric.getKey());
412                     serializer.text(sanitizeXmlContent(metric.getValue()));
413                     serializer.endTag(NS, METRIC_TAG);
414                 }
415                 serializer.endTag(NS, TEST_TAG);
416             }
417             serializer.endTag(NS, CASE_TAG);
418         }
419     }
420 
handleTestFailure(XmlSerializer serializer, Entry<String, TestResult> testResult)421     private void handleTestFailure(XmlSerializer serializer, Entry<String, TestResult> testResult)
422             throws IllegalArgumentException, IllegalStateException, IOException {
423         final String fullStack = testResult.getValue().getStackTrace();
424         if (fullStack != null) {
425             String message;
426             int index = fullStack.indexOf('\n');
427             if (index < 0) {
428                 // Trace is a single line, just set the message to be the same as the stacktrace.
429                 message = fullStack;
430             } else {
431                 message = fullStack.substring(0, index);
432             }
433             ErrorIdentifier errorIdentifier =
434                     testResult.getValue().getFailure().getErrorIdentifier();
435             String truncatedStackTrace = truncateStackTrace(fullStack, testResult.getKey());
436             serializer.startTag(NS, FAILURE_TAG);
437 
438             serializer.attribute(NS, MESSAGE_ATTR, sanitizeXmlContent(message));
439             if (errorIdentifier != null) {
440                 serializer.attribute(NS, ERROR_NAME_ATTR, errorIdentifier.name());
441                 serializer.attribute(NS, ERROR_CODE_ATTR, Long.toString(errorIdentifier.code()));
442             }
443             serializer.startTag(NS, STACK_TAG);
444             serializer.text(sanitizeXmlContent(truncatedStackTrace));
445             serializer.endTag(NS, STACK_TAG);
446 
447             serializer.endTag(NS, FAILURE_TAG);
448         }
449     }
450 
451     /** Add files captured on test failures. */
HandleLoggedFiles( XmlSerializer serializer, Entry<String, TestResult> testResult)452     private static void HandleLoggedFiles(
453             XmlSerializer serializer, Entry<String, TestResult> testResult)
454             throws IllegalArgumentException, IllegalStateException, IOException {
455         Map<String, LogFile> loggedFiles = testResult.getValue().getLoggedFiles();
456         if (loggedFiles == null || loggedFiles.isEmpty()) {
457             return;
458         }
459         for (String key : loggedFiles.keySet()) {
460             switch (loggedFiles.get(key).getType()) {
461                 case BUGREPORT:
462                     addLogIfNotNull(serializer, BUGREPORT_TAG, key, loggedFiles.get(key).getUrl());
463                     break;
464                 case LOGCAT:
465                     addLogIfNotNull(serializer, LOGCAT_TAG, key, loggedFiles.get(key).getUrl());
466                     break;
467                 case PNG:
468                 case JPEG:
469                     addLogIfNotNull(serializer, SCREENSHOT_TAG, key, loggedFiles.get(key).getUrl());
470                     break;
471                 default:
472                     break;
473             }
474         }
475     }
476 
addLogIfNotNull( XmlSerializer serializer, String tag, String key, String text)477     private static void addLogIfNotNull(
478             XmlSerializer serializer, String tag, String key, String text)
479             throws IllegalArgumentException, IllegalStateException, IOException {
480         if (text == null) {
481             CLog.d("Text for tag '%s' and key '%s' is null. skipping it.", tag, key);
482             return;
483         }
484         serializer.startTag(NS, tag);
485         serializer.attribute(NS, LOG_FILE_NAME_ATTR, key);
486         serializer.text(text);
487         serializer.endTag(NS, tag);
488     }
489 
490     /**
491      * Return the given time as a {@link String} suitable for displaying.
492      *
493      * <p>Example: Fri Aug 20 15:13:03 PDT 2010
494      *
495      * @param time the epoch time in ms since midnight Jan 1, 1970
496      */
toReadableDateString(long time)497     private static String toReadableDateString(long time) {
498         SimpleDateFormat dateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy");
499         return dateFormat.format(new Date(time));
500     }
501 
502     /** {@inheritDoc} */
503     @Override
parseResults(File resultDir, boolean shallow)504     public SuiteResultHolder parseResults(File resultDir, boolean shallow) throws IOException {
505         File resultFile = new File(resultDir, TEST_RESULT_FILE_NAME);
506         if (!resultFile.exists()) {
507             CLog.e("Could not find %s for loading the results.", resultFile.getAbsolutePath());
508             return null;
509         }
510         SuiteResultHolder invocation = new SuiteResultHolder();
511         IInvocationContext context = new InvocationContext();
512         try {
513             XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
514             XmlPullParser parser = factory.newPullParser();
515             parser.setInput(new FileReader(resultFile));
516 
517             parser.nextTag();
518             parser.require(XmlPullParser.START_TAG, NS, RESULT_TAG);
519             invocation.startTime = Long.valueOf(parser.getAttributeValue(NS, START_TIME_ATTR));
520             invocation.endTime = Long.valueOf(parser.getAttributeValue(NS, END_TIME_ATTR));
521             invocation.hostName = parser.getAttributeValue(NS, HOST_NAME_ATTR);
522             context.addInvocationAttribute(
523                     COMMAND_LINE_ARGS, parser.getAttributeValue(NS, COMMAND_LINE_ARGS));
524             parseSuiteAttributes(parser, context);
525 
526             String deviceList = parser.getAttributeValue(NS, DEVICES_ATTR);
527             int i = 0;
528             // TODO: Fix to correctly handle the number of device per shard.
529             for (String device : deviceList.split(",")) {
530                 context.addSerialsFromShard(i, Arrays.asList(device));
531                 i++;
532             }
533 
534             parser.nextTag();
535             parser.require(XmlPullParser.START_TAG, NS, BUILD_TAG);
536 
537             for (int index = 0; index < parser.getAttributeCount(); index++) {
538                 String key = parser.getAttributeName(index);
539                 String value = parser.getAttributeValue(NS, key);
540                 // TODO: Handle list of values that are comma separated.
541                 context.addInvocationAttribute(key, value);
542             }
543             parseBuildInfoAttributes(parser, context);
544 
545             parser.nextTag();
546             parser.require(XmlPullParser.END_TAG, NS, BUILD_TAG);
547 
548             parser.nextTag();
549             boolean hasRunHistoryTag = true;
550             try {
551                 parser.require(XmlPullParser.START_TAG, NS, RUN_HISTORY_TAG);
552             } catch (XmlPullParserException e) {
553                 hasRunHistoryTag = false;
554             }
555             if (hasRunHistoryTag) {
556                 handleRunHistoryLevel(parser);
557             }
558 
559             parser.require(XmlPullParser.START_TAG, NS, SUMMARY_TAG);
560 
561             invocation.completeModules =
562                     Integer.parseInt(parser.getAttributeValue(NS, MODULES_DONE_ATTR));
563             invocation.totalModules =
564                     Integer.parseInt(parser.getAttributeValue(NS, MODULES_TOTAL_ATTR));
565             invocation.passedTests = Integer.parseInt(parser.getAttributeValue(NS, PASS_ATTR));
566             invocation.failedTests = Integer.parseInt(parser.getAttributeValue(NS, FAILED_ATTR));
567 
568             parser.nextTag();
569             parser.require(XmlPullParser.END_TAG, NS, SUMMARY_TAG);
570 
571             if (!shallow) {
572                 Collection<TestRunResult> results = new ArrayList<>();
573                 Map<String, IAbi> moduleAbis = new HashMap<>();
574                 // Module level information parsing
575                 handleModuleLevel(parser, results, moduleAbis);
576                 parser.require(XmlPullParser.END_TAG, NS, RESULT_TAG);
577                 invocation.runResults = results;
578                 invocation.modulesAbi = moduleAbis;
579             }
580         } catch (XmlPullParserException e) {
581             CLog.e(e);
582             return null;
583         }
584 
585         invocation.context = context;
586         return invocation;
587     }
588 
589     /** Sort the list of results based on their name without abi primarily then secondly on abi. */
590     @VisibleForTesting
sortModules( Collection<TestRunResult> results, Map<String, IAbi> moduleAbis)591     List<TestRunResult> sortModules(
592             Collection<TestRunResult> results, Map<String, IAbi> moduleAbis) {
593         List<TestRunResult> sortedList = new ArrayList<>(results);
594         Collections.sort(
595                 sortedList,
596                 new Comparator<TestRunResult>() {
597                     @Override
598                     public int compare(TestRunResult o1, TestRunResult o2) {
599                         String module1NameStripped = o1.getName();
600                         String module1Abi = "";
601                         if (moduleAbis.get(module1NameStripped) != null) {
602                             module1Abi = moduleAbis.get(module1NameStripped).getName();
603                             module1NameStripped = module1NameStripped.replace(module1Abi + " ", "");
604                         }
605 
606                         String module2NameStripped = o2.getName();
607                         String module2Abi = "";
608                         if (moduleAbis.get(module2NameStripped) != null) {
609                             module2Abi = moduleAbis.get(module2NameStripped).getName();
610                             module2NameStripped = module2NameStripped.replace(module2Abi + " ", "");
611                         }
612                         int res = module1NameStripped.compareTo(module2NameStripped);
613                         if (res != 0) {
614                             return res;
615                         }
616                         // Use the Abi as discriminant to always sort abi in the same order.
617                         return module1Abi.compareTo(module2Abi);
618                     }
619                 });
620         return sortedList;
621     }
622 
623     /** Handle the parsing and replay of all run history information. */
handleRunHistoryLevel(XmlPullParser parser)624     private void handleRunHistoryLevel(XmlPullParser parser)
625             throws IOException, XmlPullParserException {
626         while (parser.nextTag() == XmlPullParser.START_TAG) {
627             parser.require(XmlPullParser.START_TAG, NS, RUN_TAG);
628             parser.nextTag();
629             parser.require(XmlPullParser.END_TAG, NS, RUN_TAG);
630         }
631         parser.require(XmlPullParser.END_TAG, NS, RUN_HISTORY_TAG);
632         parser.nextTag();
633     }
634 
635     /**
636      * Handle the parsing and replay of all the information inside a module (class, method,
637      * failures).
638      */
handleModuleLevel( XmlPullParser parser, Collection<TestRunResult> results, Map<String, IAbi> moduleAbis)639     private void handleModuleLevel(
640             XmlPullParser parser, Collection<TestRunResult> results, Map<String, IAbi> moduleAbis)
641             throws IOException, XmlPullParserException {
642         while (parser.nextTag() == XmlPullParser.START_TAG) {
643             parser.require(XmlPullParser.START_TAG, NS, MODULE_TAG);
644             TestRunResult module = new TestRunResult();
645             results.add(module);
646             String name = parser.getAttributeValue(NS, NAME_ATTR);
647             String abi = parser.getAttributeValue(NS, ABI_ATTR);
648             String moduleId = name;
649             if (abi != null) {
650                 moduleId = AbiUtils.createId(abi, name);
651                 moduleAbis.put(moduleId, new Abi(abi, AbiUtils.getBitness(abi)));
652             }
653             long moduleElapsedTime = Long.parseLong(parser.getAttributeValue(NS, RUNTIME_ATTR));
654             boolean moduleDone = Boolean.parseBoolean(parser.getAttributeValue(NS, DONE_ATTR));
655             int totalTests = Integer.parseInt(parser.getAttributeValue(NS, TOTAL_TESTS_ATTR));
656             module.testRunStarted(moduleId, totalTests);
657             // TestCase level information parsing
658             while (parser.nextTag() == XmlPullParser.START_TAG) {
659                 // If a reason for not done exists, handle it.
660                 if (parser.getName().equals(MODULES_NOT_DONE_REASON)) {
661                     parser.require(XmlPullParser.START_TAG, NS, MODULES_NOT_DONE_REASON);
662                     parser.nextTag();
663                     parser.require(XmlPullParser.END_TAG, NS, MODULES_NOT_DONE_REASON);
664                     continue;
665                 }
666                 parser.require(XmlPullParser.START_TAG, NS, CASE_TAG);
667                 String className = parser.getAttributeValue(NS, NAME_ATTR);
668                 // Test level information parsing
669                 handleTestCaseLevel(parser, module, className);
670                 parser.require(XmlPullParser.END_TAG, NS, CASE_TAG);
671             }
672             module.testRunEnded(moduleElapsedTime, new HashMap<String, Metric>());
673             module.setRunComplete(moduleDone);
674             parser.require(XmlPullParser.END_TAG, NS, MODULE_TAG);
675         }
676     }
677 
678     /** Parse and replay all the individual test cases level (method) informations. */
handleTestCaseLevel( XmlPullParser parser, TestRunResult currentModule, String className)679     private void handleTestCaseLevel(
680             XmlPullParser parser, TestRunResult currentModule, String className)
681             throws IOException, XmlPullParserException {
682         while (parser.nextTag() == XmlPullParser.START_TAG) {
683             parser.require(XmlPullParser.START_TAG, NS, TEST_TAG);
684             String methodName = parser.getAttributeValue(NS, NAME_ATTR);
685             TestStatus status =
686                     TestStatus.convertFromCompatibilityString(
687                             parser.getAttributeValue(NS, RESULT_ATTR));
688             TestDescription description = new TestDescription(className, methodName);
689             currentModule.testStarted(description);
690             if (TestStatus.IGNORED.equals(status)) {
691                 currentModule.testIgnored(description);
692             }
693             HashMap<String, Metric> metrics = new HashMap<String, Metric>();
694             while (parser.nextTag() == XmlPullParser.START_TAG) { // Failure level
695                 if (parser.getName().equals(FAILURE_TAG)) {
696                     String failure = parser.getAttributeValue(NS, MESSAGE_ATTR);
697                     if (parser.nextTag() == XmlPullParser.START_TAG) {
698                         parser.require(XmlPullParser.START_TAG, NS, STACK_TAG);
699                         failure = parser.nextText();
700                         parser.require(XmlPullParser.END_TAG, NS, STACK_TAG);
701                     }
702                     if (TestStatus.FAILURE.equals(status)) {
703                         currentModule.testFailed(description, failure);
704                     } else if (TestStatus.ASSUMPTION_FAILURE.equals(status)) {
705                         currentModule.testAssumptionFailure(description, failure);
706                     }
707                     parser.nextTag();
708                     parser.require(XmlPullParser.END_TAG, NS, FAILURE_TAG);
709                 }
710                 parseLoggedFiles(parser, currentModule);
711                 metrics.putAll(parseMetrics(parser));
712             }
713             currentModule.testEnded(description, metrics);
714             parser.require(XmlPullParser.END_TAG, NS, TEST_TAG);
715         }
716     }
717 
718     /** Add files captured on test failures. */
parseLoggedFiles(XmlPullParser parser, TestRunResult currentModule)719     private static void parseLoggedFiles(XmlPullParser parser, TestRunResult currentModule)
720             throws XmlPullParserException, IOException {
721         if (parser.getName().equals(BUGREPORT_TAG)) {
722             parseSingleFiles(parser, currentModule, BUGREPORT_TAG, LogDataType.BUGREPORTZ);
723         } else if (parser.getName().equals(LOGCAT_TAG)) {
724             parseSingleFiles(parser, currentModule, LOGCAT_TAG, LogDataType.LOGCAT);
725         } else if (parser.getName().equals(SCREENSHOT_TAG)) {
726             parseSingleFiles(parser, currentModule, SCREENSHOT_TAG, LogDataType.PNG);
727         }
728     }
729 
parseSingleFiles( XmlPullParser parser, TestRunResult currentModule, String tagName, LogDataType type)730     private static void parseSingleFiles(
731             XmlPullParser parser, TestRunResult currentModule, String tagName, LogDataType type)
732             throws XmlPullParserException, IOException {
733         String name = parser.getAttributeValue(NS, LOG_FILE_NAME_ATTR);
734         String logFileUrl = parser.nextText();
735         currentModule.testLogSaved(name, new LogFile(logFileUrl, logFileUrl, type));
736         parser.require(XmlPullParser.END_TAG, NS, tagName);
737     }
738 
parseMetrics(XmlPullParser parser)739     private static HashMap<String, Metric> parseMetrics(XmlPullParser parser)
740             throws XmlPullParserException, IOException {
741         HashMap<String, Metric> metrics = new HashMap<>();
742         if (parser.getName().equals(METRIC_TAG)) {
743             parser.require(XmlPullParser.START_TAG, NS, METRIC_TAG);
744             for (int index = 0; index < parser.getAttributeCount(); index++) {
745                 String key = parser.getAttributeValue(index);
746                 String value = parser.nextText();
747                 metrics.put(key, TfMetricProtoUtil.stringToMetric(value));
748             }
749             parser.require(XmlPullParser.END_TAG, NS, METRIC_TAG);
750         }
751         return metrics;
752     }
753 
sanitizeAttributesKey(String attribute)754     private static String sanitizeAttributesKey(String attribute) {
755         return attribute.replace(":", "_");
756     }
757 
addBuildInfoAttributesIfNotNull( XmlSerializer serializer, IBuildInfo buildInfo, String attributeName)758     private static void addBuildInfoAttributesIfNotNull(
759             XmlSerializer serializer, IBuildInfo buildInfo, String attributeName)
760             throws IOException {
761         String attributeValue = buildInfo.getBuildAttributes().get(attributeName);
762         if (attributeValue != null) {
763             serializer.attribute(NS, attributeName, attributeValue);
764         }
765     }
766 }
767