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