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