• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.compatibility.common.util;
17 
18 import com.android.compatibility.common.util.ChecksumReporter.ChecksumValidationException;
19 
20 import com.google.common.base.Strings;
21 
22 import org.xmlpull.v1.XmlPullParser;
23 import org.xmlpull.v1.XmlPullParserException;
24 import org.xmlpull.v1.XmlPullParserFactory;
25 import org.xmlpull.v1.XmlSerializer;
26 
27 import java.io.File;
28 import java.io.FileOutputStream;
29 import java.io.FileReader;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.io.OutputStream;
33 import java.net.InetAddress;
34 import java.net.UnknownHostException;
35 import java.nio.file.FileSystems;
36 import java.nio.file.Files;
37 import java.nio.file.Path;
38 import java.text.SimpleDateFormat;
39 import java.util.ArrayList;
40 import java.util.Collection;
41 import java.util.Collections;
42 import java.util.Date;
43 import java.util.List;
44 import java.util.Locale;
45 import java.util.Map;
46 import java.util.Map.Entry;
47 import java.util.Set;
48 
49 import javax.xml.transform.Transformer;
50 import javax.xml.transform.TransformerException;
51 import javax.xml.transform.TransformerFactory;
52 import javax.xml.transform.stream.StreamResult;
53 import javax.xml.transform.stream.StreamSource;
54 /**
55  * Handles conversion of results to/from files.
56  */
57 public class ResultHandler {
58 
59     private static final String ENCODING = "UTF-8";
60     private static final String TYPE = "org.kxml2.io.KXmlParser,org.kxml2.io.KXmlSerializer";
61     private static final String NS = null;
62     private static final String RESULT_FILE_VERSION = "5.0";
63     public static final String TEST_RESULT_FILE_NAME = "test_result.xml";
64     public static final String FAILURE_REPORT_NAME = "test_result_failures.html";
65     private static final String FAILURE_XSL_FILE_NAME = "compatibility_failures.xsl";
66 
67     public static final String[] RESULT_RESOURCES = {
68         "compatibility_result.css",
69         "compatibility_result.xsd",
70         "compatibility_result.xsl",
71         "logo.png"
72     };
73 
74     // XML constants
75     private static final String ABI_ATTR = "abi";
76     private static final String BUGREPORT_TAG = "BugReport";
77     private static final String BUILD_FINGERPRINT = "build_fingerprint";
78     private static final String BUILD_FINGERPRINT_UNALTERED = "build_fingerprint_unaltered";
79     private static final String BUILD_ID = "build_id";
80     private static final String BUILD_PRODUCT = "build_product";
81     private static final String BUILD_TAG = "Build";
82     private static final String CASE_TAG = "TestCase";
83     private static final String COMMAND_LINE_ARGS = "command_line_args";
84     private static final String DEVICES_ATTR = "devices";
85     private static final String DONE_ATTR = "done";
86     private static final String END_DISPLAY_TIME_ATTR = "end_display";
87     private static final String END_TIME_ATTR = "end";
88     private static final String FAILED_ATTR = "failed";
89     private static final String FAILURE_TAG = "Failure";
90     private static final String HOST_NAME_ATTR = "host_name";
91     private static final String JAVA_VENDOR_ATTR = "java_vendor";
92     private static final String JAVA_VERSION_ATTR = "java_version";
93     private static final String LOGCAT_TAG = "Logcat";
94     private static final String LOG_URL_ATTR = "log_url";
95     private static final String MESSAGE_ATTR = "message";
96     private static final String MODULE_TAG = "Module";
97     private static final String MODULES_DONE_ATTR = "modules_done";
98     private static final String MODULES_TOTAL_ATTR = "modules_total";
99     private static final String MODULES_NOT_DONE_REASON = "Reason";
100     private static final String NAME_ATTR = "name";
101     private static final String OS_ARCH_ATTR = "os_arch";
102     private static final String OS_NAME_ATTR = "os_name";
103     private static final String OS_VERSION_ATTR = "os_version";
104     private static final String PASS_ATTR = "pass";
105     private static final String REPORT_VERSION_ATTR = "report_version";
106     private static final String REFERENCE_URL_ATTR = "reference_url";
107     private static final String RESULT_ATTR = "result";
108     private static final String RESULT_TAG = "Result";
109     private static final String RUNTIME_ATTR = "runtime";
110     private static final String RUN_HISTORY_ATTR = "run_history";
111     private static final String RUN_HISTORY_TAG = "RunHistory";
112     private static final String RUN_TAG = "Run";
113     private static final String SCREENSHOT_TAG = "Screenshot";
114     private static final String SKIPPED_ATTR = "skipped";
115     private static final String STACK_TAG = "StackTrace";
116     private static final String START_DISPLAY_TIME_ATTR = "start_display";
117     private static final String START_TIME_ATTR = "start";
118     private static final String SUITE_NAME_ATTR = "suite_name";
119     private static final String SUITE_PLAN_ATTR = "suite_plan";
120     private static final String SUITE_VERSION_ATTR = "suite_version";
121     private static final String SUITE_BUILD_ATTR = "suite_build_number";
122     private static final String SUMMARY_TAG = "Summary";
123     private static final String METRIC_TAG = "Metric";
124     private static final String TEST_TAG = "Test";
125 
126     private static final String LATEST_RESULT_DIR = "latest";
127 
128     /**
129      * Returns IInvocationResults that can be queried for general reporting information, but that
130      * do not store underlying module data. Useful for summarizing invocation history.
131      * @param resultsDir
132      */
getLightResults(File resultsDir)133     public static List<IInvocationResult> getLightResults(File resultsDir) {
134         List<IInvocationResult> results = new ArrayList<>();
135         List<File> files = getResultDirectories(resultsDir);
136         for (File resultDir : files) {
137             if (LATEST_RESULT_DIR.equals(resultDir.getName())) {
138                 continue;
139             }
140             IInvocationResult result = getResultFromDir(resultDir, false);
141             if (result != null) {
142                 results.add(new LightInvocationResult(result));
143                 result = null; // ensure all references are removed to free memory
144             }
145         }
146         // Sort the table entries on each entry's timestamp.
147         Collections.sort(results,  (result1, result2) -> Long.compare(
148                 result1.getStartTime(),
149                 result2.getStartTime()));
150         return results;
151     }
152 
153     /**
154      * @param resultDir
155      * @return an IInvocationResult for this result, or null upon error
156      */
getResultFromDir(File resultDir)157     public static IInvocationResult getResultFromDir(File resultDir) {
158         return getResultFromDir(resultDir, false);
159     }
160 
161     /**
162      * @param resultDir
163      * @param useChecksum
164      * @return an IInvocationResult for this result, or null upon error
165      */
getResultFromDir(File resultDir, Boolean useChecksum)166     public static IInvocationResult getResultFromDir(File resultDir, Boolean useChecksum) {
167         File resultFile = null;
168         try {
169             resultFile = new File(resultDir, TEST_RESULT_FILE_NAME);
170             if (!resultFile.exists()) {
171                 return null;
172             }
173             Boolean invocationUseChecksum = useChecksum;
174             IInvocationResult invocation = new InvocationResult();
175             invocation.setRetryDirectory(resultDir);
176             ChecksumReporter checksumReporter = null;
177             if (invocationUseChecksum) {
178                 try {
179                     checksumReporter = ChecksumReporter.load(resultDir);
180                     invocation.setRetryChecksumStatus(RetryChecksumStatus.RetryWithChecksum);
181                 } catch (ChecksumValidationException e) {
182                     // Unable to read checksum form previous execution
183                     invocation.setRetryChecksumStatus(RetryChecksumStatus.RetryWithoutChecksum);
184                     invocationUseChecksum = false;
185                 }
186             }
187             XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
188             XmlPullParser parser = factory.newPullParser();
189             parser.setInput(new FileReader(resultFile));
190 
191             parser.nextTag();
192             parser.require(XmlPullParser.START_TAG, NS, RESULT_TAG);
193             invocation.setStartTime(Long.valueOf(
194                     parser.getAttributeValue(NS, START_TIME_ATTR)));
195             invocation.setTestPlan(parser.getAttributeValue(NS, SUITE_PLAN_ATTR));
196             invocation.setCommandLineArgs(parser.getAttributeValue(NS, COMMAND_LINE_ARGS));
197             String deviceList = parser.getAttributeValue(NS, DEVICES_ATTR);
198             for (String device : deviceList.split(",")) {
199                 invocation.addDeviceSerial(device);
200             }
201 
202             parser.nextTag();
203             parser.require(XmlPullParser.START_TAG, NS, BUILD_TAG);
204             invocation.addInvocationInfo(BUILD_ID, parser.getAttributeValue(NS, BUILD_ID));
205             invocation.addInvocationInfo(BUILD_PRODUCT, parser.getAttributeValue(NS,
206                     BUILD_PRODUCT));
207             String runHistoryValue = parser.getAttributeValue(NS, RUN_HISTORY_ATTR);
208             if (runHistoryValue != null) {
209                 invocation.addInvocationInfo(RUN_HISTORY_ATTR, runHistoryValue);
210             }
211 
212             // The build fingerprint needs to reflect the true fingerprint of the device under test,
213             // ignoring potential overrides made by test suites (namely STS) for APFE build
214             // association.
215             String reportFingerprint = parser.getAttributeValue(NS, BUILD_FINGERPRINT);
216             String unalteredFingerprint = parser.getAttributeValue(NS, BUILD_FINGERPRINT_UNALTERED);
217             Boolean fingerprintWasAltered = !Strings.isNullOrEmpty(unalteredFingerprint);
218             invocation.setBuildFingerprint(fingerprintWasAltered ? unalteredFingerprint :
219                 reportFingerprint );
220 
221             // TODO(stuartscott): may want to reload these incase the retry was done with
222             // --skip-device-info flag
223             parser.nextTag();
224             parser.require(XmlPullParser.END_TAG, NS, BUILD_TAG);
225 
226             // Parse RunHistory tag.
227             parser.nextTag();
228             boolean hasRunHistoryTag = true;
229             try {
230                 parser.require(XmlPullParser.START_TAG, NS, RUN_HISTORY_TAG);
231             } catch (XmlPullParserException e) {
232                 hasRunHistoryTag = false;
233             }
234             if (hasRunHistoryTag) {
235                 parseRunHistory(parser);
236             }
237 
238             parser.require(XmlPullParser.START_TAG, NS, SUMMARY_TAG);
239             parser.nextTag();
240             parser.require(XmlPullParser.END_TAG, NS, SUMMARY_TAG);
241             while (parser.nextTag() == XmlPullParser.START_TAG) {
242                 parser.require(XmlPullParser.START_TAG, NS, MODULE_TAG);
243                 String name = parser.getAttributeValue(NS, NAME_ATTR);
244                 String abi = parser.getAttributeValue(NS, ABI_ATTR);
245                 String moduleId = AbiUtils.createId(abi, name);
246                 boolean done = Boolean.parseBoolean(parser.getAttributeValue(NS, DONE_ATTR));
247                 IModuleResult module = invocation.getOrCreateModule(moduleId);
248                 module.initializeDone(done);
249                 long runtime = Long.parseLong(parser.getAttributeValue(NS, RUNTIME_ATTR));
250                 module.addRuntime(runtime);
251                 while (parser.nextTag() == XmlPullParser.START_TAG) {
252                     // If a reason for not done exists, handle it.
253                     if (parser.getName().equals(MODULES_NOT_DONE_REASON)) {
254                         parser.require(XmlPullParser.START_TAG, NS, MODULES_NOT_DONE_REASON);
255                         parser.nextTag();
256                         parser.require(XmlPullParser.END_TAG, NS, MODULES_NOT_DONE_REASON);
257                         continue;
258                     }
259                     parser.require(XmlPullParser.START_TAG, NS, CASE_TAG);
260                     String caseName = parser.getAttributeValue(NS, NAME_ATTR);
261                     ICaseResult testCase = module.getOrCreateResult(caseName);
262                     while (parser.nextTag() == XmlPullParser.START_TAG) {
263                         parser.require(XmlPullParser.START_TAG, NS, TEST_TAG);
264                         String testName = parser.getAttributeValue(NS, NAME_ATTR);
265                         ITestResult test = testCase.getOrCreateResult(testName);
266                         String result = parser.getAttributeValue(NS, RESULT_ATTR);
267                         String skipped = parser.getAttributeValue(NS, SKIPPED_ATTR);
268                         if (skipped != null && Boolean.parseBoolean(skipped)) {
269                             // mark test passed and skipped
270                             test.skipped();
271                         } else {
272                             // only apply result status directly if test was not skipped
273                             test.setResultStatus(TestStatus.getStatus(result));
274                         }
275                         test.setRetry(true);
276                         while (parser.nextTag() == XmlPullParser.START_TAG) {
277                             if (parser.getName().equals(FAILURE_TAG)) {
278                                 test.setMessage(parser.getAttributeValue(NS, MESSAGE_ATTR));
279                                 if (parser.nextTag() == XmlPullParser.START_TAG) {
280                                     parser.require(XmlPullParser.START_TAG, NS, STACK_TAG);
281                                     test.setStackTrace(parser.nextText());
282                                     parser.require(XmlPullParser.END_TAG, NS, STACK_TAG);
283                                     parser.nextTag();
284                                 }
285                                 parser.require(XmlPullParser.END_TAG, NS, FAILURE_TAG);
286                             } else if (parser.getName().equals(BUGREPORT_TAG)) {
287                                 test.setBugReport(parser.nextText());
288                                 parser.require(XmlPullParser.END_TAG, NS, BUGREPORT_TAG);
289                             } else if (parser.getName().equals(LOGCAT_TAG)) {
290                                 test.setLog(parser.nextText());
291                                 parser.require(XmlPullParser.END_TAG, NS, LOGCAT_TAG);
292                             } else if (parser.getName().equals(SCREENSHOT_TAG)) {
293                                 test.setScreenshot(parser.nextText());
294                                 parser.require(XmlPullParser.END_TAG, NS, SCREENSHOT_TAG);
295                             } else if (SUMMARY_TAG.equals(parser.getName())) {
296                                 test.setReportLog(ReportLog.parse(parser));
297                             } else if (METRIC_TAG.equals(parser.getName())) {
298                                 // Ignore the new format in the old parser.
299                                 parser.nextText();
300                                 parser.require(XmlPullParser.END_TAG, NS, METRIC_TAG);
301                             } else if (RUN_HISTORY_TAG.equals(parser.getName())) {
302                                 // Ignore the test result history since it only exists in
303                                 // CTS Verifier, which will not use parsing feature.
304                                 skipCurrentTag(parser);
305                             } else {
306                                 parser.nextTag();
307                             }
308                         }
309                         parser.require(XmlPullParser.END_TAG, NS, TEST_TAG);
310                         // If the fingerprint was altered, then checksum against the fingerprint
311                         // originally reported
312                         Boolean checksumMismatch = invocationUseChecksum &&
313                              !checksumReporter.containsTestResult(test, module, reportFingerprint)
314                              && (fingerprintWasAltered ? !checksumReporter.containsTestResult(
315                                  test, module, unalteredFingerprint) : true);
316                         if (checksumMismatch) {
317                             test.removeResult();
318                         }
319                     }
320                     parser.require(XmlPullParser.END_TAG, NS, CASE_TAG);
321                 }
322                 parser.require(XmlPullParser.END_TAG, NS, MODULE_TAG);
323                 // If the fingerprint was altered, then checksum against the fingerprint
324                 // originally reported
325                 Boolean checksumMismatch = invocationUseChecksum &&
326                      !checksumReporter.containsModuleResult(module, reportFingerprint) &&
327                      (fingerprintWasAltered ? !checksumReporter.containsModuleResult(
328                          module, unalteredFingerprint) : true);
329                 if (checksumMismatch) {
330                     module.initializeDone(false);
331                 }
332             }
333             parser.require(XmlPullParser.END_TAG, NS, RESULT_TAG);
334             return invocation;
335         } catch (XmlPullParserException | IOException e) {
336             System.out.println(
337                     String.format("Exception when trying to load %s",
338                             resultFile.getAbsolutePath()));
339             e.printStackTrace();
340             return null;
341         }
342     }
343 
344     /** Parse and replay all run history information. */
parseRunHistory(XmlPullParser parser)345     private static void parseRunHistory(XmlPullParser parser)
346             throws IOException, XmlPullParserException {
347         while (parser.nextTag() == XmlPullParser.START_TAG) {
348             parser.require(XmlPullParser.START_TAG, NS, RUN_TAG);
349             parser.nextTag();
350             parser.require(XmlPullParser.END_TAG, NS, RUN_TAG);
351         }
352         parser.require(XmlPullParser.END_TAG, NS, RUN_HISTORY_TAG);
353         parser.nextTag();
354     }
355 
356     /** Skip the current XML tags. */
skipCurrentTag(XmlPullParser parser)357     private static void skipCurrentTag(XmlPullParser parser)
358             throws XmlPullParserException, IOException {
359         int depth = 1;
360         while (depth != 0) {
361             switch (parser.next()) {
362                 case XmlPullParser.END_TAG:
363                     depth--;
364                     break;
365                 case XmlPullParser.START_TAG:
366                     depth++;
367                     break;
368             }
369         }
370     }
371 
372     /**
373      * @param result
374      * @param resultDir
375      * @param startTime
376      * @param referenceUrl A nullable string that can contain a URL to a related data
377      * @param logUrl A nullable string that can contain a URL to related log files
378      * @param commandLineArgs A string containing the arguments to the run command
379      * @param resultAttributes Extra key-value pairs to be added as attributes and corresponding
380      *     values into the result XML file
381      * @return The result file created.
382      * @throws IOException
383      * @throws XmlPullParserException
384      */
writeResults( String suiteName, String suiteVersion, String suitePlan, String suiteBuild, IInvocationResult result, File resultDir, long startTime, long endTime, String referenceUrl, String logUrl, String commandLineArgs, Map<String, String> resultAttributes)385     public static File writeResults(
386             String suiteName,
387             String suiteVersion,
388             String suitePlan,
389             String suiteBuild,
390             IInvocationResult result,
391             File resultDir,
392             long startTime,
393             long endTime,
394             String referenceUrl,
395             String logUrl,
396             String commandLineArgs,
397             Map<String, String> resultAttributes)
398             throws IOException, XmlPullParserException {
399         int passed = result.countResults(TestStatus.PASS);
400         int failed = result.countResults(TestStatus.FAIL);
401         File resultFile = new File(resultDir, TEST_RESULT_FILE_NAME);
402         OutputStream stream = new FileOutputStream(resultFile);
403         XmlSerializer serializer = XmlPullParserFactory.newInstance(TYPE, null).newSerializer();
404         serializer.setOutput(stream, ENCODING);
405         serializer.startDocument(ENCODING, false);
406         serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
407         serializer.processingInstruction(
408                 "xml-stylesheet type=\"text/xsl\" href=\"compatibility_result.xsl\"");
409         serializer.startTag(NS, RESULT_TAG);
410         serializer.attribute(NS, START_TIME_ATTR, String.valueOf(startTime));
411         serializer.attribute(NS, END_TIME_ATTR, String.valueOf(endTime));
412         serializer.attribute(NS, START_DISPLAY_TIME_ATTR, toReadableDateString(startTime));
413         serializer.attribute(NS, END_DISPLAY_TIME_ATTR, toReadableDateString(endTime));
414 
415         serializer.attribute(NS, SUITE_NAME_ATTR, suiteName);
416         serializer.attribute(NS, SUITE_VERSION_ATTR, suiteVersion);
417         serializer.attribute(NS, SUITE_PLAN_ATTR, suitePlan);
418         serializer.attribute(NS, SUITE_BUILD_ATTR, suiteBuild);
419         serializer.attribute(NS, REPORT_VERSION_ATTR, RESULT_FILE_VERSION);
420         serializer.attribute(NS, COMMAND_LINE_ARGS, nullToEmpty(commandLineArgs));
421 
422         if (resultAttributes != null) {
423             for (Entry<String, String> entry : resultAttributes.entrySet()) {
424                 serializer.attribute(NS, entry.getKey(), entry.getValue());
425             }
426         }
427 
428         if (referenceUrl != null) {
429             serializer.attribute(NS, REFERENCE_URL_ATTR, referenceUrl);
430         }
431 
432         if (logUrl != null) {
433             serializer.attribute(NS, LOG_URL_ATTR, logUrl);
434         }
435 
436         // Device Info
437         Set<String> devices = result.getDeviceSerials();
438         StringBuilder deviceList = new StringBuilder();
439         boolean first = true;
440         for (String device : devices) {
441             if (first) {
442                 first = false;
443             } else {
444                 deviceList.append(",");
445             }
446             deviceList.append(device);
447         }
448         serializer.attribute(NS, DEVICES_ATTR, deviceList.toString());
449 
450         // Host Info
451         String hostName = "";
452         try {
453             hostName = InetAddress.getLocalHost().getHostName();
454         } catch (UnknownHostException ignored) {}
455         serializer.attribute(NS, HOST_NAME_ATTR, hostName);
456         serializer.attribute(NS, OS_NAME_ATTR, System.getProperty("os.name"));
457         serializer.attribute(NS, OS_VERSION_ATTR, System.getProperty("os.version"));
458         serializer.attribute(NS, OS_ARCH_ATTR, System.getProperty("os.arch"));
459         serializer.attribute(NS, JAVA_VENDOR_ATTR, System.getProperty("java.vendor"));
460         serializer.attribute(NS, JAVA_VERSION_ATTR, System.getProperty("java.version"));
461 
462         // Build Info
463         serializer.startTag(NS, BUILD_TAG);
464         for (Entry<String, String> entry : result.getInvocationInfo().entrySet()) {
465             serializer.attribute(NS, entry.getKey(), entry.getValue());
466             if (Strings.isNullOrEmpty(result.getBuildFingerprint()) &&
467                 entry.getKey().equals(BUILD_FINGERPRINT)) {
468                 result.setBuildFingerprint(entry.getValue());
469             }
470         }
471         serializer.endTag(NS, BUILD_TAG);
472 
473         // Run history - this contains a list of start and end times of previous runs. More
474         // information may be added in the future.
475         Collection<InvocationResult.RunHistory> runHistories =
476                 ((InvocationResult) result).getRunHistories();
477         if (!runHistories.isEmpty()) {
478             serializer.startTag(NS, RUN_HISTORY_TAG);
479             for (InvocationResult.RunHistory runHistory : runHistories) {
480                 serializer.startTag(NS, RUN_TAG);
481                 serializer.attribute(NS, START_TIME_ATTR, String.valueOf(runHistory.startTime));
482                 serializer.attribute(NS, END_TIME_ATTR, String.valueOf(runHistory.endTime));
483                 serializer.endTag(NS, RUN_TAG);
484             }
485             serializer.endTag(NS, RUN_HISTORY_TAG);
486         }
487 
488         // Summary
489         serializer.startTag(NS, SUMMARY_TAG);
490         serializer.attribute(NS, PASS_ATTR, Integer.toString(passed));
491         serializer.attribute(NS, FAILED_ATTR, Integer.toString(failed));
492         serializer.attribute(NS, MODULES_DONE_ATTR,
493                 Integer.toString(result.getModuleCompleteCount()));
494         serializer.attribute(NS, MODULES_TOTAL_ATTR,
495                 Integer.toString(result.getModules().size()));
496         serializer.endTag(NS, SUMMARY_TAG);
497 
498         // Results
499         for (IModuleResult module : result.getModules()) {
500             serializer.startTag(NS, MODULE_TAG);
501             serializer.attribute(NS, NAME_ATTR, module.getName());
502             serializer.attribute(NS, ABI_ATTR, module.getAbi());
503             serializer.attribute(NS, RUNTIME_ATTR, String.valueOf(module.getRuntime()));
504             serializer.attribute(NS, DONE_ATTR, Boolean.toString(module.isDone()));
505             serializer.attribute(NS, PASS_ATTR,
506                     Integer.toString(module.countResults(TestStatus.PASS)));
507             for (ICaseResult cr : module.getResults()) {
508                 serializer.startTag(NS, CASE_TAG);
509                 serializer.attribute(NS, NAME_ATTR, cr.getName());
510                 for (ITestResult r : cr.getResults()) {
511                     TestStatus status = r.getResultStatus();
512                     if (status == null) {
513                         continue; // test was not executed, don't report
514                     }
515                     serializer.startTag(NS, TEST_TAG);
516                     serializer.attribute(NS, RESULT_ATTR, status.getValue());
517                     serializer.attribute(NS, NAME_ATTR, r.getName());
518                     if (r.isSkipped()) {
519                         serializer.attribute(NS, SKIPPED_ATTR, Boolean.toString(true));
520                     }
521                     String message = r.getMessage();
522                     if (message != null) {
523                         serializer.startTag(NS, FAILURE_TAG);
524                         serializer.attribute(NS, MESSAGE_ATTR, message);
525                         String stackTrace = r.getStackTrace();
526                         if (stackTrace != null) {
527                             serializer.startTag(NS, STACK_TAG);
528                             serializer.text(stackTrace);
529                             serializer.endTag(NS, STACK_TAG);
530                         }
531                         serializer.endTag(NS, FAILURE_TAG);
532                     }
533                     String bugreport = r.getBugReport();
534                     if (bugreport != null) {
535                         serializer.startTag(NS, BUGREPORT_TAG);
536                         serializer.text(bugreport);
537                         serializer.endTag(NS, BUGREPORT_TAG);
538                     }
539                     String logcat = r.getLog();
540                     if (logcat != null) {
541                         serializer.startTag(NS, LOGCAT_TAG);
542                         serializer.text(logcat);
543                         serializer.endTag(NS, LOGCAT_TAG);
544                     }
545                     String screenshot = r.getScreenshot();
546                     if (screenshot != null) {
547                         serializer.startTag(NS, SCREENSHOT_TAG);
548                         serializer.text(screenshot);
549                         serializer.endTag(NS, SCREENSHOT_TAG);
550                     }
551                     ReportLog report = r.getReportLog();
552                     if (report != null) {
553                         ReportLog.serialize(serializer, report);
554                     }
555 
556                     // Test result history contains a list of execution time for each test item.
557                     List<TestResultHistory> testResultHistories = r.getTestResultHistories();
558                     if (testResultHistories != null) {
559                         for (TestResultHistory resultHistory : testResultHistories) {
560                             TestResultHistory.serialize(serializer, resultHistory, r.getName());
561                         }
562                     }
563 
564                     serializer.endTag(NS, TEST_TAG);
565                 }
566                 serializer.endTag(NS, CASE_TAG);
567             }
568             serializer.endTag(NS, MODULE_TAG);
569         }
570         serializer.endDocument();
571         createChecksum(resultDir, result);
572         return resultFile;
573     }
574 
575     /**
576      * Generate html report listing an failed tests
577      */
createFailureReport(File inputXml)578     public static File createFailureReport(File inputXml) {
579         File failureReport = new File(inputXml.getParentFile(), FAILURE_REPORT_NAME);
580         try (InputStream xslStream = ResultHandler.class.getResourceAsStream(
581                 String.format("/report/%s", FAILURE_XSL_FILE_NAME));
582              OutputStream outputStream = new FileOutputStream(failureReport)) {
583 
584             Transformer transformer = TransformerFactory.newInstance().newTransformer(
585                     new StreamSource(xslStream));
586             transformer.transform(new StreamSource(inputXml), new StreamResult(outputStream));
587         } catch (IOException | TransformerException ignored) { }
588         return failureReport;
589     }
590 
createChecksum(File resultDir, IInvocationResult invocationResult)591     private static void createChecksum(File resultDir, IInvocationResult invocationResult) {
592         RetryChecksumStatus retryStatus = invocationResult.getRetryChecksumStatus();
593         switch (retryStatus) {
594             case NotRetry: case RetryWithChecksum:
595                 // Do not disrupt the process if there is a problem generating checksum.
596                 ChecksumReporter.tryCreateChecksum(resultDir, invocationResult);
597                 break;
598             case RetryWithoutChecksum:
599                 // If the previous run has an invalid checksum file,
600                 // copy it into current results folder for future troubleshooting
601                 File retryDirectory = invocationResult.getRetryDirectory();
602                 Path retryChecksum = FileSystems.getDefault().getPath(
603                         retryDirectory.getAbsolutePath(), ChecksumReporter.NAME);
604                 if (!retryChecksum.toFile().exists()) {
605                     // if no checksum file, check for a copy from a previous retry
606                     retryChecksum = FileSystems.getDefault().getPath(
607                             retryDirectory.getAbsolutePath(), ChecksumReporter.PREV_NAME);
608                 }
609 
610                 if (retryChecksum.toFile().exists()) {
611                     File checksumCopy = new File(resultDir, ChecksumReporter.PREV_NAME);
612                     try (FileOutputStream stream = new FileOutputStream(checksumCopy)) {
613                         Files.copy(retryChecksum, stream);
614                     } catch (IOException e) {
615                         // Do not disrupt the process if there is a problem copying checksum
616                     }
617                 }
618         }
619     }
620 
621 
622     /**
623      * Find the IInvocationResult for the given sessionId.
624      */
findResult(File resultsDir, Integer sessionId)625     public static IInvocationResult findResult(File resultsDir, Integer sessionId) {
626         return findResult(resultsDir, sessionId, true);
627     }
628 
629     /**
630      * Find the IInvocationResult for the given sessionId.
631      */
findResult( File resultsDir, Integer sessionId, Boolean useChecksum)632     private static IInvocationResult findResult(
633             File resultsDir, Integer sessionId, Boolean useChecksum) {
634         if (sessionId < 0) {
635             throw new IllegalArgumentException(
636                 String.format("Invalid session id [%d] ", sessionId));
637         }
638         File resultDir = getResultDirectory(resultsDir, sessionId);
639         IInvocationResult result = getResultFromDir(resultDir, useChecksum);
640         if (result == null) {
641             throw new RuntimeException(String.format("Could not find session [%d]", sessionId));
642         }
643         return result;
644     }
645 
646     /**
647      * Get the result directory for the given sessionId.
648      */
getResultDirectory(File resultsDir, Integer sessionId)649     public static File getResultDirectory(File resultsDir, Integer sessionId) {
650         if (sessionId < 0) {
651             throw new IllegalArgumentException(
652                 String.format("Invalid session id [%d] ", sessionId));
653         }
654         List<File> allResultDirs = getResultDirectories(resultsDir);
655         if (sessionId >= allResultDirs.size()) {
656             throw new IllegalArgumentException(String.format("Invalid session id [%d], results " +
657                     "directory (%s) contains only %d results",
658                     sessionId, resultsDir.getAbsolutePath(), allResultDirs.size()));
659         }
660         return allResultDirs.get(sessionId);
661     }
662 
663     /**
664      * Get a list of child directories that contain test invocation results
665      * @param resultsDir the root test result directory
666      * @return the list of {@link File} results directory.
667      */
getResultDirectories(File resultsDir)668     public static List<File> getResultDirectories(File resultsDir) {
669         List<File> directoryList = new ArrayList<>();
670         File[] files = resultsDir.listFiles();
671         if (files == null || files.length == 0) {
672             // No results, just return the empty list
673             return directoryList;
674         }
675         for (File resultDir : files) {
676             if (!resultDir.isDirectory()) {
677                 continue;
678             }
679             // Only include if it contain results file
680             File resultFile = new File(resultDir, TEST_RESULT_FILE_NAME);
681             if (!resultFile.exists()) {
682                 continue;
683             }
684             directoryList.add(resultDir);
685         }
686         Collections.sort(directoryList, (d1, d2) -> d1.getName().compareTo(d2.getName()));
687         return directoryList;
688     }
689 
690     /**
691      * Return the given time as a {@link String} suitable for displaying.
692      * <p/>
693      * Example: Fri Aug 20 15:13:03 PDT 2010
694      *
695      * @param time the epoch time in ms since midnight Jan 1, 1970
696      */
toReadableDateString(long time)697     static String toReadableDateString(long time) {
698     SimpleDateFormat dateFormat =
699         new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", Locale.ENGLISH);
700         return dateFormat.format(new Date(time));
701     }
702 
703     /**
704      * When nullable is null, return an empty string. Otherwise, return the value in nullable.
705      */
nullToEmpty(String nullable)706     private static String nullToEmpty(String nullable) {
707         return nullable == null ? "" : nullable;
708     }
709 }
710