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