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