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