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