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