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