1 /* 2 * Copyright (C) 2018 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.tradefed.result.suite; 17 18 import com.android.annotations.VisibleForTesting; 19 import com.android.tradefed.build.IBuildInfo; 20 import com.android.tradefed.invoker.IInvocationContext; 21 import com.android.tradefed.invoker.InvocationContext; 22 import com.android.tradefed.log.LogUtil.CLog; 23 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; 24 import com.android.tradefed.result.FailureDescription; 25 import com.android.tradefed.result.LogDataType; 26 import com.android.tradefed.result.LogFile; 27 import com.android.tradefed.result.TestDescription; 28 import com.android.tradefed.result.TestResult; 29 import com.android.tradefed.result.TestRunResult; 30 import com.android.tradefed.result.TestStatus; 31 import com.android.tradefed.result.error.ErrorIdentifier; 32 import com.android.tradefed.testtype.Abi; 33 import com.android.tradefed.testtype.IAbi; 34 import com.android.tradefed.util.AbiUtils; 35 import com.android.tradefed.util.StreamUtil; 36 import com.android.tradefed.util.proto.TfMetricProtoUtil; 37 38 import com.google.common.base.Strings; 39 import com.google.common.xml.XmlEscapers; 40 import com.google.gson.Gson; 41 42 import org.xmlpull.v1.XmlPullParser; 43 import org.xmlpull.v1.XmlPullParserException; 44 import org.xmlpull.v1.XmlPullParserFactory; 45 import org.xmlpull.v1.XmlSerializer; 46 47 import java.io.File; 48 import java.io.FileOutputStream; 49 import java.io.FileReader; 50 import java.io.IOException; 51 import java.io.OutputStream; 52 import java.net.InetAddress; 53 import java.net.UnknownHostException; 54 import java.text.SimpleDateFormat; 55 import java.util.ArrayList; 56 import java.util.Arrays; 57 import java.util.Collection; 58 import java.util.Collections; 59 import java.util.Comparator; 60 import java.util.Date; 61 import java.util.HashMap; 62 import java.util.LinkedHashMap; 63 import java.util.LinkedHashSet; 64 import java.util.List; 65 import java.util.Map; 66 import java.util.Map.Entry; 67 import java.util.Set; 68 69 /** 70 * Utility class to save a suite run as an XML. TODO: Remove all the special Compatibility Test 71 * format work around to get the same format. 72 */ 73 public class XmlSuiteResultFormatter implements IFormatterGenerator { 74 75 // The maximum size of a stack trace saved in the report. 76 private static final int STACK_TRACE_MAX_SIZE = 1024 * 1024; 77 78 private static final String ENCODING = "UTF-8"; 79 private static final String TYPE = "org.kxml2.io.KXmlParser,org.kxml2.io.KXmlSerializer"; 80 public static final String NS = null; 81 82 public static final String TEST_RESULT_FILE_NAME = "test_result.xml"; 83 84 // XML constants 85 private static final String ABI_ATTR = "abi"; 86 private static final String BUGREPORT_TAG = "BugReport"; 87 private static final String BUILD_TAG = "Build"; 88 private static final String CASE_TAG = "TestCase"; 89 private static final String COMMAND_LINE_ARGS = "command_line_args"; 90 private static final String DEVICES_ATTR = "devices"; 91 private static final String DEVICE_KERNEL_INFO_ATTR = "device_kernel_info"; 92 private static final String DONE_ATTR = "done"; 93 private static final String END_DISPLAY_TIME_ATTR = "end_display"; 94 private static final String END_TIME_ATTR = "end"; 95 private static final String FAILED_ATTR = "failed"; 96 private static final String FAILURE_TAG = "Failure"; 97 private static final String HOST_NAME_ATTR = "host_name"; 98 private static final String JAVA_VENDOR_ATTR = "java_vendor"; 99 private static final String JAVA_VERSION_ATTR = "java_version"; 100 private static final String LOGCAT_TAG = "Logcat"; 101 102 private static final String METRIC_TAG = "Metric"; 103 private static final String METRIC_KEY = "key"; 104 105 private static final String MESSAGE_ATTR = "message"; 106 private static final String MODULE_TAG = "Module"; 107 private static final String MODULES_DONE_ATTR = "modules_done"; 108 private static final String MODULES_TOTAL_ATTR = "modules_total"; 109 private static final String MODULES_NOT_DONE_REASON = "Reason"; 110 private static final String NAME_ATTR = "name"; 111 private static final String OS_ARCH_ATTR = "os_arch"; 112 private static final String OS_NAME_ATTR = "os_name"; 113 private static final String OS_VERSION_ATTR = "os_version"; 114 private static final String PASS_ATTR = "pass"; 115 116 private static final String RESULT_ATTR = "result"; 117 private static final String RESULT_TAG = "Result"; 118 private static final String RUN_HISTORY = "run_history"; 119 private static final String RUN_HISTORY_TAG = "RunHistory"; 120 private static final String RUN_TAG = "Run"; 121 private static final String RUNTIME_ATTR = "runtime"; 122 private static final String SCREENSHOT_TAG = "Screenshot"; 123 private static final String SKIPPED_ATTR = "skipped"; 124 private static final String STACK_TAG = "StackTrace"; 125 private static final String ERROR_NAME_ATTR = "error_name"; 126 private static final String ERROR_CODE_ATTR = "error_code"; 127 private static final String START_DISPLAY_TIME_ATTR = "start_display"; 128 private static final String START_TIME_ATTR = "start"; 129 130 private static final String SUMMARY_TAG = "Summary"; 131 private static final String SYSTEM_IMG_INFO_ATTR = "system_img_info"; 132 private static final String TEST_TAG = "Test"; 133 private static final String TOTAL_TESTS_ATTR = "total_tests"; 134 private static final String VENDOR_IMG_INFO_ATTR = "vendor_img_info"; 135 136 private static final String LOG_FILE_NAME_ATTR = "file_name"; 137 138 /** Helper object for JSON conversion. */ 139 public static final class RunHistory { 140 public long startTime; 141 public long endTime; 142 public long passedTests; 143 public long failedTests; 144 public String commandLineArgs; 145 public String hostName; 146 } 147 148 /** Sanitizes a string to escape the special characters. */ sanitizeXmlContent(String s)149 public static String sanitizeXmlContent(String s) { 150 return XmlEscapers.xmlContentEscaper().escape(s); 151 } 152 153 /** Truncates the full stack trace with maximum {@link STACK_TRACE_MAX_SIZE} characters. */ truncateStackTrace(String fullStackTrace, String testCaseName)154 public static String truncateStackTrace(String fullStackTrace, String testCaseName) { 155 if (fullStackTrace == null) { 156 return null; 157 } 158 if (fullStackTrace.length() > STACK_TRACE_MAX_SIZE) { 159 CLog.i( 160 "The stack trace for test case %s contains %d characters, and has been" 161 + " truncated to %d characters in %s.", 162 testCaseName, 163 fullStackTrace.length(), 164 STACK_TRACE_MAX_SIZE, 165 TEST_RESULT_FILE_NAME); 166 return fullStackTrace.substring(0, STACK_TRACE_MAX_SIZE); 167 } 168 return fullStackTrace; 169 } 170 171 /** 172 * Allows to add some attributes to the <Result> tag via {@code serializer.attribute}. 173 * 174 * @param serializer The object that serializes an XML suite result. 175 */ addSuiteAttributes(XmlSerializer serializer)176 public void addSuiteAttributes(XmlSerializer serializer) 177 throws IllegalArgumentException, IllegalStateException, IOException { 178 // Default implementation does nothing 179 } 180 181 /** 182 * Reverse operation from {@link #addSuiteAttributes(XmlSerializer)}. 183 * 184 * @param parser The parser where to read the attributes from. 185 * @param context The {@link IInvocationContext} where to put the attributes. 186 * @throws XmlPullParserException When XmlPullParser fails. 187 */ parseSuiteAttributes(XmlPullParser parser, IInvocationContext context)188 public void parseSuiteAttributes(XmlPullParser parser, IInvocationContext context) 189 throws XmlPullParserException { 190 // Default implementation does nothing 191 } 192 193 /** 194 * Allows to add some attributes to the <Build> tag via {@code serializer.attribute}. 195 * 196 * @param serializer The object that serializes an XML suite result. 197 * @param holder An object that contains information to be written to the suite result. 198 */ addBuildInfoAttributes(XmlSerializer serializer, SuiteResultHolder holder)199 public void addBuildInfoAttributes(XmlSerializer serializer, SuiteResultHolder holder) 200 throws IllegalArgumentException, IllegalStateException, IOException { 201 // Default implementation does nothing 202 } 203 204 /** 205 * Reverse operation from {@link #addBuildInfoAttributes(XmlSerializer, SuiteResultHolder)}. 206 * 207 * @param parser The parser where to read the attributes from. 208 * @param context The {@link IInvocationContext} where to put the attributes. 209 * @throws XmlPullParserException When XmlPullParser fails. 210 */ parseBuildInfoAttributes(XmlPullParser parser, IInvocationContext context)211 public void parseBuildInfoAttributes(XmlPullParser parser, IInvocationContext context) 212 throws XmlPullParserException { 213 // Default implementation does nothing 214 } 215 216 /** 217 * Write the invocation results in an xml format. 218 * 219 * @param holder a {@link SuiteResultHolder} holding all the info required for the xml 220 * @param resultDir the result directory {@link File} where to put the results. 221 * @return a {@link File} pointing to the xml output file. 222 */ 223 @Override writeResults(SuiteResultHolder holder, File resultDir)224 public File writeResults(SuiteResultHolder holder, File resultDir) throws IOException { 225 File resultFile = new File(resultDir, TEST_RESULT_FILE_NAME); 226 OutputStream stream = new FileOutputStream(resultFile); 227 XmlSerializer serializer = null; 228 try { 229 serializer = XmlPullParserFactory.newInstance(TYPE, null).newSerializer(); 230 } catch (XmlPullParserException e) { 231 StreamUtil.close(stream); 232 throw new IOException(e); 233 } 234 serializer.setOutput(stream, ENCODING); 235 serializer.startDocument(ENCODING, false); 236 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 237 serializer.processingInstruction( 238 "xml-stylesheet type=\"text/xsl\" href=\"compatibility_result.xsl\""); 239 serializer.startTag(NS, RESULT_TAG); 240 serializer.attribute(NS, START_TIME_ATTR, String.valueOf(holder.startTime)); 241 serializer.attribute(NS, END_TIME_ATTR, String.valueOf(holder.endTime)); 242 serializer.attribute(NS, START_DISPLAY_TIME_ATTR, toReadableDateString(holder.startTime)); 243 serializer.attribute(NS, END_DISPLAY_TIME_ATTR, toReadableDateString(holder.endTime)); 244 serializer.attribute( 245 NS, 246 COMMAND_LINE_ARGS, 247 Strings.nullToEmpty( 248 holder.context.getAttributes().getUniqueMap().get(COMMAND_LINE_ARGS))); 249 250 addSuiteAttributes(serializer); 251 252 // Device Info 253 Map<Integer, List<String>> serialsShards = holder.context.getShardsSerials(); 254 String deviceList = ""; 255 if (serialsShards.isEmpty()) { 256 deviceList = String.join(",", holder.context.getSerials()); 257 } else { 258 Set<String> subSet = new LinkedHashSet<>(); 259 for (List<String> list : serialsShards.values()) { 260 subSet.addAll(list); 261 } 262 deviceList = String.join(",", subSet); 263 } 264 serializer.attribute(NS, DEVICES_ATTR, deviceList); 265 266 // Host Info 267 String hostName = ""; 268 try { 269 hostName = InetAddress.getLocalHost().getHostName(); 270 } catch (UnknownHostException ignored) { 271 } 272 serializer.attribute(NS, HOST_NAME_ATTR, hostName); 273 serializer.attribute(NS, OS_NAME_ATTR, System.getProperty("os.name")); 274 serializer.attribute(NS, OS_VERSION_ATTR, System.getProperty("os.version")); 275 serializer.attribute(NS, OS_ARCH_ATTR, System.getProperty("os.arch")); 276 serializer.attribute(NS, JAVA_VENDOR_ATTR, System.getProperty("java.vendor")); 277 serializer.attribute(NS, JAVA_VERSION_ATTR, System.getProperty("java.version")); 278 279 // Build Info 280 serializer.startTag(NS, BUILD_TAG); 281 for (String key : holder.context.getAttributes().keySet()) { 282 serializer.attribute( 283 NS, 284 sanitizeAttributesKey(key), 285 String.join(",", holder.context.getAttributes().get(key))); 286 } 287 if (!holder.context.getBuildInfos().isEmpty()) { 288 IBuildInfo buildInfo = holder.context.getBuildInfos().get(0); 289 addBuildInfoAttributesIfNotNull(serializer, buildInfo, DEVICE_KERNEL_INFO_ATTR); 290 addBuildInfoAttributesIfNotNull(serializer, buildInfo, SYSTEM_IMG_INFO_ATTR); 291 addBuildInfoAttributesIfNotNull(serializer, buildInfo, VENDOR_IMG_INFO_ATTR); 292 } 293 addBuildInfoAttributes(serializer, holder); 294 serializer.endTag(NS, BUILD_TAG); 295 296 // Run History 297 String runHistoryJson = holder.context.getAttributes().getUniqueMap().get(RUN_HISTORY); 298 if (runHistoryJson != null) { 299 serializer.startTag(NS, RUN_HISTORY_TAG); 300 Gson gson = new Gson(); 301 RunHistory[] runHistories = gson.fromJson(runHistoryJson, RunHistory[].class); 302 for (RunHistory runHistory : runHistories) { 303 serializer.startTag(NS, RUN_TAG); 304 serializer.attribute(NS, START_TIME_ATTR, String.valueOf(runHistory.startTime)); 305 serializer.attribute(NS, END_TIME_ATTR, String.valueOf(runHistory.endTime)); 306 serializer.attribute(NS, PASS_ATTR, Long.toString(runHistory.passedTests)); 307 serializer.attribute(NS, FAILED_ATTR, Long.toString(runHistory.failedTests)); 308 serializer.attribute(NS, COMMAND_LINE_ARGS, runHistory.commandLineArgs); 309 serializer.attribute(NS, HOST_NAME_ATTR, runHistory.hostName); 310 serializer.endTag(NS, RUN_TAG); 311 } 312 serializer.endTag(NS, RUN_HISTORY_TAG); 313 } 314 315 // Summary 316 serializer.startTag(NS, SUMMARY_TAG); 317 serializer.attribute(NS, PASS_ATTR, Long.toString(holder.passedTests)); 318 serializer.attribute(NS, FAILED_ATTR, Long.toString(holder.failedTests)); 319 serializer.attribute(NS, MODULES_DONE_ATTR, Integer.toString(holder.completeModules)); 320 serializer.attribute(NS, MODULES_TOTAL_ATTR, Integer.toString(holder.totalModules)); 321 serializer.endTag(NS, SUMMARY_TAG); 322 323 List<TestRunResult> sortedModuleList = sortModules(holder.runResults, holder.modulesAbi); 324 // Results 325 for (TestRunResult module : sortedModuleList) { 326 serializer.startTag(NS, MODULE_TAG); 327 // To be compatible of CTS strip the abi from the module name when available. 328 if (holder.modulesAbi.get(module.getName()) != null) { 329 String moduleAbi = holder.modulesAbi.get(module.getName()).getName(); 330 String moduleNameStripped = module.getName().replace(moduleAbi + " ", ""); 331 serializer.attribute(NS, NAME_ATTR, moduleNameStripped); 332 serializer.attribute(NS, ABI_ATTR, moduleAbi); 333 } else { 334 serializer.attribute(NS, NAME_ATTR, module.getName()); 335 } 336 serializer.attribute(NS, RUNTIME_ATTR, String.valueOf(module.getElapsedTime())); 337 boolean isDone = module.isRunComplete() && !module.isRunFailure(); 338 339 serializer.attribute(NS, DONE_ATTR, Boolean.toString(isDone)); 340 serializer.attribute( 341 NS, PASS_ATTR, Integer.toString(module.getNumTestsInState(TestStatus.PASSED))); 342 serializer.attribute(NS, TOTAL_TESTS_ATTR, Integer.toString(module.getNumTests())); 343 344 if (!isDone) { 345 String message = module.getRunFailureMessage(); 346 if (message == null) { 347 message = "Run was incomplete. Some tests might not have finished."; 348 } 349 FailureDescription failureDescription = module.getRunFailureDescription(); 350 serializer.startTag(NS, MODULES_NOT_DONE_REASON); 351 serializer.attribute(NS, MESSAGE_ATTR, sanitizeXmlContent(message)); 352 if (failureDescription != null && failureDescription.getErrorIdentifier() != null) { 353 serializer.attribute( 354 NS, ERROR_NAME_ATTR, failureDescription.getErrorIdentifier().name()); 355 serializer.attribute( 356 NS, 357 ERROR_CODE_ATTR, 358 Long.toString(failureDescription.getErrorIdentifier().code())); 359 } 360 serializer.endTag(NS, MODULES_NOT_DONE_REASON); 361 } 362 serializeTestCases(serializer, module.getTestResults()); 363 serializer.endTag(NS, MODULE_TAG); 364 } 365 serializer.endDocument(); 366 return resultFile; 367 } 368 serializeTestCases( XmlSerializer serializer, Map<TestDescription, TestResult> results)369 private void serializeTestCases( 370 XmlSerializer serializer, Map<TestDescription, TestResult> results) 371 throws IllegalArgumentException, IllegalStateException, IOException { 372 // We reformat into the same format as the ResultHandler from CTS to be compatible for now. 373 Map<String, Map<String, TestResult>> format = new LinkedHashMap<>(); 374 for (Entry<TestDescription, TestResult> cr : results.entrySet()) { 375 if (format.get(cr.getKey().getClassName()) == null) { 376 format.put(cr.getKey().getClassName(), new LinkedHashMap<>()); 377 } 378 Map<String, TestResult> methodResult = format.get(cr.getKey().getClassName()); 379 methodResult.put(cr.getKey().getTestName(), cr.getValue()); 380 } 381 382 for (String className : format.keySet()) { 383 serializer.startTag(NS, CASE_TAG); 384 serializer.attribute(NS, NAME_ATTR, className); 385 for (Entry<String, TestResult> individualResult : format.get(className).entrySet()) { 386 TestStatus status = individualResult.getValue().getResultStatus(); 387 // TODO(b/322204420): Report skipped to XML and support parsing it 388 if (TestStatus.SKIPPED.equals(status)) { 389 continue; 390 } 391 if (status == null) { 392 continue; // test was not executed, don't report 393 } 394 serializer.startTag(NS, TEST_TAG); 395 serializer.attribute( 396 NS, RESULT_ATTR, TestStatus.convertToCompatibilityString(status)); 397 serializer.attribute(NS, NAME_ATTR, individualResult.getKey()); 398 if (TestStatus.IGNORED.equals(status)) { 399 serializer.attribute(NS, SKIPPED_ATTR, Boolean.toString(true)); 400 } 401 402 handleTestFailure(serializer, individualResult); 403 404 HandleLoggedFiles(serializer, individualResult); 405 406 for (Entry<String, String> metric : 407 TfMetricProtoUtil.compatibleConvert( 408 individualResult.getValue().getProtoMetrics()) 409 .entrySet()) { 410 serializer.startTag(NS, METRIC_TAG); 411 serializer.attribute(NS, METRIC_KEY, metric.getKey()); 412 serializer.text(sanitizeXmlContent(metric.getValue())); 413 serializer.endTag(NS, METRIC_TAG); 414 } 415 serializer.endTag(NS, TEST_TAG); 416 } 417 serializer.endTag(NS, CASE_TAG); 418 } 419 } 420 handleTestFailure(XmlSerializer serializer, Entry<String, TestResult> testResult)421 private void handleTestFailure(XmlSerializer serializer, Entry<String, TestResult> testResult) 422 throws IllegalArgumentException, IllegalStateException, IOException { 423 final String fullStack = testResult.getValue().getStackTrace(); 424 if (fullStack != null) { 425 String message; 426 int index = fullStack.indexOf('\n'); 427 if (index < 0) { 428 // Trace is a single line, just set the message to be the same as the stacktrace. 429 message = fullStack; 430 } else { 431 message = fullStack.substring(0, index); 432 } 433 ErrorIdentifier errorIdentifier = 434 testResult.getValue().getFailure().getErrorIdentifier(); 435 String truncatedStackTrace = truncateStackTrace(fullStack, testResult.getKey()); 436 serializer.startTag(NS, FAILURE_TAG); 437 438 serializer.attribute(NS, MESSAGE_ATTR, sanitizeXmlContent(message)); 439 if (errorIdentifier != null) { 440 serializer.attribute(NS, ERROR_NAME_ATTR, errorIdentifier.name()); 441 serializer.attribute(NS, ERROR_CODE_ATTR, Long.toString(errorIdentifier.code())); 442 } 443 serializer.startTag(NS, STACK_TAG); 444 serializer.text(sanitizeXmlContent(truncatedStackTrace)); 445 serializer.endTag(NS, STACK_TAG); 446 447 serializer.endTag(NS, FAILURE_TAG); 448 } 449 } 450 451 /** Add files captured on test failures. */ HandleLoggedFiles( XmlSerializer serializer, Entry<String, TestResult> testResult)452 private static void HandleLoggedFiles( 453 XmlSerializer serializer, Entry<String, TestResult> testResult) 454 throws IllegalArgumentException, IllegalStateException, IOException { 455 Map<String, LogFile> loggedFiles = testResult.getValue().getLoggedFiles(); 456 if (loggedFiles == null || loggedFiles.isEmpty()) { 457 return; 458 } 459 for (String key : loggedFiles.keySet()) { 460 switch (loggedFiles.get(key).getType()) { 461 case BUGREPORT: 462 addLogIfNotNull(serializer, BUGREPORT_TAG, key, loggedFiles.get(key).getUrl()); 463 break; 464 case LOGCAT: 465 addLogIfNotNull(serializer, LOGCAT_TAG, key, loggedFiles.get(key).getUrl()); 466 break; 467 case PNG: 468 case JPEG: 469 addLogIfNotNull(serializer, SCREENSHOT_TAG, key, loggedFiles.get(key).getUrl()); 470 break; 471 default: 472 break; 473 } 474 } 475 } 476 addLogIfNotNull( XmlSerializer serializer, String tag, String key, String text)477 private static void addLogIfNotNull( 478 XmlSerializer serializer, String tag, String key, String text) 479 throws IllegalArgumentException, IllegalStateException, IOException { 480 if (text == null) { 481 CLog.d("Text for tag '%s' and key '%s' is null. skipping it.", tag, key); 482 return; 483 } 484 serializer.startTag(NS, tag); 485 serializer.attribute(NS, LOG_FILE_NAME_ATTR, key); 486 serializer.text(text); 487 serializer.endTag(NS, tag); 488 } 489 490 /** 491 * Return the given time as a {@link String} suitable for displaying. 492 * 493 * <p>Example: Fri Aug 20 15:13:03 PDT 2010 494 * 495 * @param time the epoch time in ms since midnight Jan 1, 1970 496 */ toReadableDateString(long time)497 private static String toReadableDateString(long time) { 498 SimpleDateFormat dateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy"); 499 return dateFormat.format(new Date(time)); 500 } 501 502 /** {@inheritDoc} */ 503 @Override parseResults(File resultDir, boolean shallow)504 public SuiteResultHolder parseResults(File resultDir, boolean shallow) throws IOException { 505 File resultFile = new File(resultDir, TEST_RESULT_FILE_NAME); 506 if (!resultFile.exists()) { 507 CLog.e("Could not find %s for loading the results.", resultFile.getAbsolutePath()); 508 return null; 509 } 510 SuiteResultHolder invocation = new SuiteResultHolder(); 511 IInvocationContext context = new InvocationContext(); 512 try { 513 XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); 514 XmlPullParser parser = factory.newPullParser(); 515 parser.setInput(new FileReader(resultFile)); 516 517 parser.nextTag(); 518 parser.require(XmlPullParser.START_TAG, NS, RESULT_TAG); 519 invocation.startTime = Long.valueOf(parser.getAttributeValue(NS, START_TIME_ATTR)); 520 invocation.endTime = Long.valueOf(parser.getAttributeValue(NS, END_TIME_ATTR)); 521 invocation.hostName = parser.getAttributeValue(NS, HOST_NAME_ATTR); 522 context.addInvocationAttribute( 523 COMMAND_LINE_ARGS, parser.getAttributeValue(NS, COMMAND_LINE_ARGS)); 524 parseSuiteAttributes(parser, context); 525 526 String deviceList = parser.getAttributeValue(NS, DEVICES_ATTR); 527 int i = 0; 528 // TODO: Fix to correctly handle the number of device per shard. 529 for (String device : deviceList.split(",")) { 530 context.addSerialsFromShard(i, Arrays.asList(device)); 531 i++; 532 } 533 534 parser.nextTag(); 535 parser.require(XmlPullParser.START_TAG, NS, BUILD_TAG); 536 537 for (int index = 0; index < parser.getAttributeCount(); index++) { 538 String key = parser.getAttributeName(index); 539 String value = parser.getAttributeValue(NS, key); 540 // TODO: Handle list of values that are comma separated. 541 context.addInvocationAttribute(key, value); 542 } 543 parseBuildInfoAttributes(parser, context); 544 545 parser.nextTag(); 546 parser.require(XmlPullParser.END_TAG, NS, BUILD_TAG); 547 548 parser.nextTag(); 549 boolean hasRunHistoryTag = true; 550 try { 551 parser.require(XmlPullParser.START_TAG, NS, RUN_HISTORY_TAG); 552 } catch (XmlPullParserException e) { 553 hasRunHistoryTag = false; 554 } 555 if (hasRunHistoryTag) { 556 handleRunHistoryLevel(parser); 557 } 558 559 parser.require(XmlPullParser.START_TAG, NS, SUMMARY_TAG); 560 561 invocation.completeModules = 562 Integer.parseInt(parser.getAttributeValue(NS, MODULES_DONE_ATTR)); 563 invocation.totalModules = 564 Integer.parseInt(parser.getAttributeValue(NS, MODULES_TOTAL_ATTR)); 565 invocation.passedTests = Integer.parseInt(parser.getAttributeValue(NS, PASS_ATTR)); 566 invocation.failedTests = Integer.parseInt(parser.getAttributeValue(NS, FAILED_ATTR)); 567 568 parser.nextTag(); 569 parser.require(XmlPullParser.END_TAG, NS, SUMMARY_TAG); 570 571 if (!shallow) { 572 Collection<TestRunResult> results = new ArrayList<>(); 573 Map<String, IAbi> moduleAbis = new HashMap<>(); 574 // Module level information parsing 575 handleModuleLevel(parser, results, moduleAbis); 576 parser.require(XmlPullParser.END_TAG, NS, RESULT_TAG); 577 invocation.runResults = results; 578 invocation.modulesAbi = moduleAbis; 579 } 580 } catch (XmlPullParserException e) { 581 CLog.e(e); 582 return null; 583 } 584 585 invocation.context = context; 586 return invocation; 587 } 588 589 /** Sort the list of results based on their name without abi primarily then secondly on abi. */ 590 @VisibleForTesting sortModules( Collection<TestRunResult> results, Map<String, IAbi> moduleAbis)591 List<TestRunResult> sortModules( 592 Collection<TestRunResult> results, Map<String, IAbi> moduleAbis) { 593 List<TestRunResult> sortedList = new ArrayList<>(results); 594 Collections.sort( 595 sortedList, 596 new Comparator<TestRunResult>() { 597 @Override 598 public int compare(TestRunResult o1, TestRunResult o2) { 599 String module1NameStripped = o1.getName(); 600 String module1Abi = ""; 601 if (moduleAbis.get(module1NameStripped) != null) { 602 module1Abi = moduleAbis.get(module1NameStripped).getName(); 603 module1NameStripped = module1NameStripped.replace(module1Abi + " ", ""); 604 } 605 606 String module2NameStripped = o2.getName(); 607 String module2Abi = ""; 608 if (moduleAbis.get(module2NameStripped) != null) { 609 module2Abi = moduleAbis.get(module2NameStripped).getName(); 610 module2NameStripped = module2NameStripped.replace(module2Abi + " ", ""); 611 } 612 int res = module1NameStripped.compareTo(module2NameStripped); 613 if (res != 0) { 614 return res; 615 } 616 // Use the Abi as discriminant to always sort abi in the same order. 617 return module1Abi.compareTo(module2Abi); 618 } 619 }); 620 return sortedList; 621 } 622 623 /** Handle the parsing and replay of all run history information. */ handleRunHistoryLevel(XmlPullParser parser)624 private void handleRunHistoryLevel(XmlPullParser parser) 625 throws IOException, XmlPullParserException { 626 while (parser.nextTag() == XmlPullParser.START_TAG) { 627 parser.require(XmlPullParser.START_TAG, NS, RUN_TAG); 628 parser.nextTag(); 629 parser.require(XmlPullParser.END_TAG, NS, RUN_TAG); 630 } 631 parser.require(XmlPullParser.END_TAG, NS, RUN_HISTORY_TAG); 632 parser.nextTag(); 633 } 634 635 /** 636 * Handle the parsing and replay of all the information inside a module (class, method, 637 * failures). 638 */ handleModuleLevel( XmlPullParser parser, Collection<TestRunResult> results, Map<String, IAbi> moduleAbis)639 private void handleModuleLevel( 640 XmlPullParser parser, Collection<TestRunResult> results, Map<String, IAbi> moduleAbis) 641 throws IOException, XmlPullParserException { 642 while (parser.nextTag() == XmlPullParser.START_TAG) { 643 parser.require(XmlPullParser.START_TAG, NS, MODULE_TAG); 644 TestRunResult module = new TestRunResult(); 645 results.add(module); 646 String name = parser.getAttributeValue(NS, NAME_ATTR); 647 String abi = parser.getAttributeValue(NS, ABI_ATTR); 648 String moduleId = name; 649 if (abi != null) { 650 moduleId = AbiUtils.createId(abi, name); 651 moduleAbis.put(moduleId, new Abi(abi, AbiUtils.getBitness(abi))); 652 } 653 long moduleElapsedTime = Long.parseLong(parser.getAttributeValue(NS, RUNTIME_ATTR)); 654 boolean moduleDone = Boolean.parseBoolean(parser.getAttributeValue(NS, DONE_ATTR)); 655 int totalTests = Integer.parseInt(parser.getAttributeValue(NS, TOTAL_TESTS_ATTR)); 656 module.testRunStarted(moduleId, totalTests); 657 // TestCase level information parsing 658 while (parser.nextTag() == XmlPullParser.START_TAG) { 659 // If a reason for not done exists, handle it. 660 if (parser.getName().equals(MODULES_NOT_DONE_REASON)) { 661 parser.require(XmlPullParser.START_TAG, NS, MODULES_NOT_DONE_REASON); 662 parser.nextTag(); 663 parser.require(XmlPullParser.END_TAG, NS, MODULES_NOT_DONE_REASON); 664 continue; 665 } 666 parser.require(XmlPullParser.START_TAG, NS, CASE_TAG); 667 String className = parser.getAttributeValue(NS, NAME_ATTR); 668 // Test level information parsing 669 handleTestCaseLevel(parser, module, className); 670 parser.require(XmlPullParser.END_TAG, NS, CASE_TAG); 671 } 672 module.testRunEnded(moduleElapsedTime, new HashMap<String, Metric>()); 673 module.setRunComplete(moduleDone); 674 parser.require(XmlPullParser.END_TAG, NS, MODULE_TAG); 675 } 676 } 677 678 /** Parse and replay all the individual test cases level (method) informations. */ handleTestCaseLevel( XmlPullParser parser, TestRunResult currentModule, String className)679 private void handleTestCaseLevel( 680 XmlPullParser parser, TestRunResult currentModule, String className) 681 throws IOException, XmlPullParserException { 682 while (parser.nextTag() == XmlPullParser.START_TAG) { 683 parser.require(XmlPullParser.START_TAG, NS, TEST_TAG); 684 String methodName = parser.getAttributeValue(NS, NAME_ATTR); 685 TestStatus status = 686 TestStatus.convertFromCompatibilityString( 687 parser.getAttributeValue(NS, RESULT_ATTR)); 688 TestDescription description = new TestDescription(className, methodName); 689 currentModule.testStarted(description); 690 if (TestStatus.IGNORED.equals(status)) { 691 currentModule.testIgnored(description); 692 } 693 HashMap<String, Metric> metrics = new HashMap<String, Metric>(); 694 while (parser.nextTag() == XmlPullParser.START_TAG) { // Failure level 695 if (parser.getName().equals(FAILURE_TAG)) { 696 String failure = parser.getAttributeValue(NS, MESSAGE_ATTR); 697 if (parser.nextTag() == XmlPullParser.START_TAG) { 698 parser.require(XmlPullParser.START_TAG, NS, STACK_TAG); 699 failure = parser.nextText(); 700 parser.require(XmlPullParser.END_TAG, NS, STACK_TAG); 701 } 702 if (TestStatus.FAILURE.equals(status)) { 703 currentModule.testFailed(description, failure); 704 } else if (TestStatus.ASSUMPTION_FAILURE.equals(status)) { 705 currentModule.testAssumptionFailure(description, failure); 706 } 707 parser.nextTag(); 708 parser.require(XmlPullParser.END_TAG, NS, FAILURE_TAG); 709 } 710 parseLoggedFiles(parser, currentModule); 711 metrics.putAll(parseMetrics(parser)); 712 } 713 currentModule.testEnded(description, metrics); 714 parser.require(XmlPullParser.END_TAG, NS, TEST_TAG); 715 } 716 } 717 718 /** Add files captured on test failures. */ parseLoggedFiles(XmlPullParser parser, TestRunResult currentModule)719 private static void parseLoggedFiles(XmlPullParser parser, TestRunResult currentModule) 720 throws XmlPullParserException, IOException { 721 if (parser.getName().equals(BUGREPORT_TAG)) { 722 parseSingleFiles(parser, currentModule, BUGREPORT_TAG, LogDataType.BUGREPORTZ); 723 } else if (parser.getName().equals(LOGCAT_TAG)) { 724 parseSingleFiles(parser, currentModule, LOGCAT_TAG, LogDataType.LOGCAT); 725 } else if (parser.getName().equals(SCREENSHOT_TAG)) { 726 parseSingleFiles(parser, currentModule, SCREENSHOT_TAG, LogDataType.PNG); 727 } 728 } 729 parseSingleFiles( XmlPullParser parser, TestRunResult currentModule, String tagName, LogDataType type)730 private static void parseSingleFiles( 731 XmlPullParser parser, TestRunResult currentModule, String tagName, LogDataType type) 732 throws XmlPullParserException, IOException { 733 String name = parser.getAttributeValue(NS, LOG_FILE_NAME_ATTR); 734 String logFileUrl = parser.nextText(); 735 currentModule.testLogSaved(name, new LogFile(logFileUrl, logFileUrl, type)); 736 parser.require(XmlPullParser.END_TAG, NS, tagName); 737 } 738 parseMetrics(XmlPullParser parser)739 private static HashMap<String, Metric> parseMetrics(XmlPullParser parser) 740 throws XmlPullParserException, IOException { 741 HashMap<String, Metric> metrics = new HashMap<>(); 742 if (parser.getName().equals(METRIC_TAG)) { 743 parser.require(XmlPullParser.START_TAG, NS, METRIC_TAG); 744 for (int index = 0; index < parser.getAttributeCount(); index++) { 745 String key = parser.getAttributeValue(index); 746 String value = parser.nextText(); 747 metrics.put(key, TfMetricProtoUtil.stringToMetric(value)); 748 } 749 parser.require(XmlPullParser.END_TAG, NS, METRIC_TAG); 750 } 751 return metrics; 752 } 753 sanitizeAttributesKey(String attribute)754 private static String sanitizeAttributesKey(String attribute) { 755 return attribute.replace(":", "_"); 756 } 757 addBuildInfoAttributesIfNotNull( XmlSerializer serializer, IBuildInfo buildInfo, String attributeName)758 private static void addBuildInfoAttributesIfNotNull( 759 XmlSerializer serializer, IBuildInfo buildInfo, String attributeName) 760 throws IOException { 761 String attributeValue = buildInfo.getBuildAttributes().get(attributeName); 762 if (attributeValue != null) { 763 serializer.attribute(NS, attributeName, attributeValue); 764 } 765 } 766 } 767