1 /* 2 * Copyright (C) 2008 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.ddmlib; 17 18 import static com.android.ddmlib.testrunner.IInstrumentationResultParser.StatusKeys.KNOWN_KEYS; 19 20 import com.android.annotations.NonNull; 21 import com.android.ddmlib.IShellOutputReceiver; 22 import com.android.ddmlib.MultiLineReceiver; 23 import com.android.ddmlib.testrunner.IInstrumentationResultParser; 24 import com.android.ddmlib.testrunner.ITestRunListener; 25 import com.android.ddmlib.testrunner.TestIdentifier; 26 import com.android.tradefed.log.Log; 27 28 import java.text.NumberFormat; 29 import java.text.ParseException; 30 import java.util.ArrayList; 31 import java.util.Collection; 32 import java.util.Collections; 33 import java.util.HashMap; 34 import java.util.LinkedHashMap; 35 import java.util.Map; 36 import java.util.regex.Matcher; 37 import java.util.regex.Pattern; 38 39 /** 40 * Parses the 'raw output mode' results of an instrumentation test run from shell and informs a 41 * ITestRunListener of the results. 42 * 43 * <p>Expects the following output: 44 * 45 * <p>If fatal error occurred when attempted to run the tests: 46 * 47 * <pre> 48 * INSTRUMENTATION_STATUS: Error=error Message 49 * INSTRUMENTATION_FAILED: 50 * </pre> 51 * 52 * <p>or 53 * 54 * <pre> 55 * INSTRUMENTATION_RESULT: shortMsg=error Message 56 * </pre> 57 * 58 * <p>Otherwise, expect a series of test results, each one containing a set of status key/value 59 * pairs, delimited by a start(1)/pass(0)/fail(-2)/error(-1) status code result. At end of test run, 60 * expects that the elapsed test time in seconds will be displayed 61 * 62 * <p>For example: 63 * 64 * <pre> 65 * INSTRUMENTATION_STATUS_CODE: 1 66 * INSTRUMENTATION_STATUS: class=com.foo.FooTest 67 * INSTRUMENTATION_STATUS: test=testFoo 68 * INSTRUMENTATION_STATUS: numtests=2 69 * INSTRUMENTATION_STATUS: stack=com.foo.FooTest#testFoo:312 70 * com.foo.X 71 * INSTRUMENTATION_STATUS_CODE: -2 72 * ... 73 * 74 * Time: X 75 * </pre> 76 * 77 * <p>Note that the "value" portion of the key-value pair may wrap over several text lines 78 * 79 * <p>Use {@link InstrumentationProtoResultParser} instead. The proto based parser has additional 80 * information such as logcat message. 81 */ 82 public class InstrumentationResultParser extends MultiLineReceiver 83 implements IInstrumentationResultParser { 84 85 /** Prefixes used to identify output. */ 86 private static class Prefixes { 87 private static final String STATUS = "INSTRUMENTATION_STATUS: "; 88 private static final String STATUS_CODE = "INSTRUMENTATION_STATUS_CODE: "; 89 private static final String STATUS_FAILED = "INSTRUMENTATION_FAILED: "; 90 private static final String STATUS_ABORTED = "INSTRUMENTATION_ABORTED: "; 91 private static final String ON_ERROR = "onError:"; 92 private static final String CODE = "INSTRUMENTATION_CODE: "; 93 private static final String RESULT = "INSTRUMENTATION_RESULT: "; 94 private static final String TIME_REPORT = "Time: "; 95 } 96 97 private final Collection<ITestRunListener> mTestListeners; 98 99 /** Test result data */ 100 private static class TestResult { 101 private Integer mCode = null; 102 private String mTestName = null; 103 private String mTestClass = null; 104 private String mStackTrace = null; 105 private Integer mNumTests = null; 106 private String mCurrentTestNumber = null; 107 108 /** Returns true if all expected values have been parsed */ isComplete()109 boolean isComplete() { 110 return mCode != null && mTestName != null && mTestClass != null; 111 } 112 113 /** Provides a more user readable string for TestResult, if possible */ 114 @Override toString()115 public String toString() { 116 StringBuilder output = new StringBuilder(); 117 if (mTestClass != null) { 118 output.append(mTestClass); 119 output.append('#'); 120 } 121 if (mTestName != null) { 122 output.append(mTestName); 123 } 124 if (output.length() > 0) { 125 return output.toString(); 126 } 127 return "unknown result"; 128 } 129 } 130 131 /** the name to provide to {@link ITestRunListener#testRunStarted(String, int)} */ 132 private final String mTestRunName; 133 134 /** Stores the status values for the test result currently being parsed */ 135 private TestResult mCurrentTestResult = null; 136 137 /** Stores the status values for the test result last parsed */ 138 private TestResult mLastTestResult = null; 139 140 /** Stores the current "key" portion of the status key-value being parsed. */ 141 private String mCurrentKey = null; 142 143 /** Stores the current "value" portion of the status key-value being parsed. */ 144 private StringBuilder mCurrentValue = null; 145 146 /** True if start of test has already been reported to listener. */ 147 private boolean mTestStartReported = false; 148 149 /** True if the completion of the test run has been detected. */ 150 private boolean mTestRunFinished = false; 151 152 /** True if test run failure has already been reported to listener. */ 153 private boolean mTestRunFailReported = false; 154 155 /** The elapsed time of the test run, in milliseconds. */ 156 private Long mTestTime = null; 157 158 /** True if current test run has been canceled by user. */ 159 private boolean mIsCancelled = false; 160 161 /** The number of tests currently run */ 162 private int mNumTestsRun = 0; 163 164 /** The number of tests expected to run */ 165 private int mNumTestsExpected = 0; 166 167 /** True if the parser is parsing a line beginning with "INSTRUMENTATION_RESULT" */ 168 private boolean mInInstrumentationResultKey = false; 169 170 /** Contains the full error available in 'stream=' in case of test runner fatal exception. */ 171 private String mStreamError = null; 172 173 /** Contains the error message associated with the onError callback output. */ 174 private String mOnError = null; 175 176 /** 177 * Stores key-value pairs under INSTRUMENTATION_RESULT header, keeping the order in which they 178 * were reported. The {@link ITestRunListener}s may choose to display some or all of them when 179 * the test run ends. 180 */ 181 private Map<String, String> mInstrumentationResultBundle = new LinkedHashMap<>(); 182 183 /** 184 * Stores key-value pairs of metrics emitted during the execution of each test case, keeping the 185 * order in which they were reported. Note that standard keys that are stored in the TestResults 186 * class are filtered out of this Map. The {@link ITestRunListener}s may choose to display some 187 * or all of them when the test case ends. 188 */ 189 private Map<String, String> mTestMetrics = new LinkedHashMap<>(); 190 191 private static final String LOG_TAG = "InstrumentationResultParser"; 192 193 /** Error message supplied when no parseable test results are received from test run. */ 194 static final String NO_TEST_RESULTS_MSG = "No test results"; 195 196 /** Error message supplied when a test start bundle is parsed, but not the test end bundle. */ 197 static final String INCOMPLETE_TEST_ERR_MSG_PREFIX = "Test failed to run to completion"; 198 199 static final String INCOMPLETE_TEST_ERR_MSG_POSTFIX = "Check device logcat for details"; 200 201 /** Error message supplied when the test run is incomplete. */ 202 static final String INCOMPLETE_RUN_ERR_MSG_PREFIX = "Test run failed to complete"; 203 204 /** Error message supplied from the test runner when some critical failure occurred */ 205 static final String FATAL_EXCEPTION_MSG = "Fatal exception when running tests"; 206 207 /** 208 * Pattern for the instrumentation reported errors (fatal & non-fatal) printed at the end of the 209 * instrumentation. 210 */ 211 static final Pattern INSTRUMENTATION_FAILURES_PATTERN = 212 Pattern.compile("There (was|were) (\\d+) failure(.*)", Pattern.DOTALL); 213 214 /** Error message supplied when the test run output doesn't contain a valid time stamp. */ 215 static final String INVALID_OUTPUT_ERR_MSG = 216 "Output from instrumentation is missing its time stamp"; 217 218 /** 219 * Creates the InstrumentationResultParser. 220 * 221 * @param runName the test run name to provide to {@link ITestRunListener#testRunStarted(String, 222 * int)} 223 * @param listeners informed of test results as the tests are executing 224 */ InstrumentationResultParser(String runName, Collection<ITestRunListener> listeners)225 public InstrumentationResultParser(String runName, Collection<ITestRunListener> listeners) { 226 mTestRunName = runName; 227 mTestListeners = new ArrayList<ITestRunListener>(listeners); 228 } 229 230 /** 231 * Creates the InstrumentationResultParser for a single listener. 232 * 233 * @param runName the test run name to provide to {@link ITestRunListener#testRunStarted(String, 234 * int)} 235 * @param listener informed of test results as the tests are executing 236 */ InstrumentationResultParser(String runName, ITestRunListener listener)237 public InstrumentationResultParser(String runName, ITestRunListener listener) { 238 this(runName, Collections.singletonList(listener)); 239 } 240 241 /** 242 * Processes the instrumentation test output from shell. 243 * 244 * @see MultiLineReceiver#processNewLines 245 */ 246 @Override processNewLines(@onNull String[] lines)247 public void processNewLines(@NonNull String[] lines) { 248 for (String line : lines) { 249 parse(line); 250 // in verbose mode, dump all adb output to log 251 Log.v(LOG_TAG, line); 252 } 253 } 254 255 /** 256 * Parse an individual output line. Expects a line that is one of: 257 * 258 * <ul> 259 * <li>The start of a new status line (starts with Prefixes.STATUS or Prefixes.STATUS_CODE), 260 * and thus there is a new key=value pair to parse, and the previous key-value pair is 261 * finished. 262 * <li>A continuation of the previous status (the "value" portion of the key has wrapped to 263 * the next line). 264 * <li>A line reporting a fatal error in the test run (Prefixes.STATUS_FAILED) 265 * <li>A line reporting the total elapsed time of the test run. (Prefixes.TIME_REPORT) 266 * </ul> 267 * 268 * @param line Text output line 269 */ parse(String line)270 private void parse(String line) { 271 if (line.startsWith(Prefixes.STATUS_CODE)) { 272 // Previous status key-value has been collected. Store it. 273 submitCurrentKeyValue(); 274 mInInstrumentationResultKey = false; 275 parseStatusCode(line); 276 } else if (line.startsWith(Prefixes.STATUS)) { 277 // Previous status key-value has been collected. Store it. 278 submitCurrentKeyValue(); 279 mInInstrumentationResultKey = false; 280 parseKey(line, Prefixes.STATUS.length()); 281 } else if (line.startsWith(Prefixes.RESULT)) { 282 // Previous status key-value has been collected. Store it. 283 submitCurrentKeyValue(); 284 mInInstrumentationResultKey = true; 285 parseKey(line, Prefixes.RESULT.length()); 286 } else if (line.startsWith(Prefixes.STATUS_FAILED) || line.startsWith(Prefixes.CODE)) { 287 // Previous status key-value has been collected. Store it. 288 submitCurrentKeyValue(); 289 mInInstrumentationResultKey = false; 290 // these codes signal the end of the instrumentation run 291 mTestRunFinished = true; 292 // just ignore the remaining data on this line 293 } else if (line.startsWith(Prefixes.TIME_REPORT)) { 294 parseTime(line); 295 } else if (line.startsWith(Prefixes.ON_ERROR)) { 296 mOnError = line; 297 } else if (line.startsWith(Prefixes.STATUS_ABORTED)) { 298 if (mOnError == null) { 299 mOnError = line; 300 } 301 } else { 302 if (mCurrentValue != null) { 303 // this is a value that has wrapped to next line. 304 mCurrentValue.append("\r\n"); 305 mCurrentValue.append(line); 306 } else if (!line.trim().isEmpty()) { 307 Log.d(LOG_TAG, "unrecognized line " + line); 308 } 309 } 310 } 311 312 /** Stores the currently parsed key-value pair in the appropriate place. */ submitCurrentKeyValue()313 private void submitCurrentKeyValue() { 314 if (mCurrentKey != null && mCurrentValue != null) { 315 String statusValue = mCurrentValue.toString(); 316 if (mInInstrumentationResultKey) { 317 if (!KNOWN_KEYS.contains(mCurrentKey)) { 318 mInstrumentationResultBundle.put(mCurrentKey, statusValue); 319 } else if (mCurrentKey.equals(StatusKeys.SHORTMSG)) { 320 // test run must have failed 321 handleTestRunFailed( 322 String.format("Instrumentation run failed due to '%1$s'", statusValue)); 323 } else if (StatusKeys.STREAM.equals(mCurrentKey)) { 324 if (statusValue != null) { 325 if (statusValue.contains(FATAL_EXCEPTION_MSG)) { 326 mStreamError = statusValue.trim(); 327 } else if (INSTRUMENTATION_FAILURES_PATTERN 328 .matcher(statusValue.trim()) 329 .matches()) { 330 mStreamError = statusValue.trim(); 331 } 332 } 333 } 334 } else { 335 TestResult testInfo = getCurrentTestInfo(); 336 337 if (mCurrentKey.equals(StatusKeys.CLASS)) { 338 testInfo.mTestClass = statusValue.trim(); 339 } else if (mCurrentKey.equals(StatusKeys.TEST)) { 340 testInfo.mTestName = statusValue.trim(); 341 } else if (mCurrentKey.equals(StatusKeys.NUMTESTS)) { 342 try { 343 testInfo.mNumTests = Integer.parseInt(statusValue); 344 } catch (NumberFormatException e) { 345 Log.w( 346 LOG_TAG, 347 "Unexpected integer number of tests, received " + statusValue); 348 } 349 } else if (mCurrentKey.equals(StatusKeys.ERROR)) { 350 // test run must have failed 351 handleTestRunFailed(statusValue); 352 } else if (mCurrentKey.equals(StatusKeys.STACK)) { 353 testInfo.mStackTrace = statusValue; 354 } else if (StatusKeys.CURRENT.equals(mCurrentKey)) { 355 testInfo.mCurrentTestNumber = statusValue; 356 } else if (!KNOWN_KEYS.contains(mCurrentKey)) { 357 // Not one of the recognized key/value pairs, so dump it in mTestMetrics 358 mTestMetrics.put(mCurrentKey, statusValue); 359 } 360 } 361 362 mCurrentKey = null; 363 mCurrentValue = null; 364 } 365 } 366 367 /** 368 * A utility method to return the test metrics from the current test case execution and get 369 * ready for the next one. 370 */ getAndResetTestMetrics()371 private Map<String, String> getAndResetTestMetrics() { 372 Map<String, String> retVal = mTestMetrics; 373 mTestMetrics = new HashMap<String, String>(); 374 return retVal; 375 } 376 getCurrentTestInfo()377 private TestResult getCurrentTestInfo() { 378 if (mCurrentTestResult == null) { 379 mCurrentTestResult = new TestResult(); 380 } 381 return mCurrentTestResult; 382 } 383 clearCurrentTestInfo()384 private void clearCurrentTestInfo() { 385 mLastTestResult = mCurrentTestResult; 386 mCurrentTestResult = null; 387 } 388 389 /** 390 * Parses the key from the current line. Expects format of "key=value". 391 * 392 * @param line full line of text to parse 393 * @param keyStartPos the starting position of the key in the given line 394 */ parseKey(String line, int keyStartPos)395 private void parseKey(String line, int keyStartPos) { 396 int endKeyPos = line.indexOf('=', keyStartPos); 397 if (endKeyPos != -1) { 398 mCurrentKey = line.substring(keyStartPos, endKeyPos).trim(); 399 parseValue(line, endKeyPos + 1); 400 } 401 } 402 403 /** 404 * Parses the start of a key=value pair. 405 * 406 * @param line - full line of text to parse 407 * @param valueStartPos - the starting position of the value in the given line 408 */ parseValue(String line, int valueStartPos)409 private void parseValue(String line, int valueStartPos) { 410 mCurrentValue = new StringBuilder(); 411 mCurrentValue.append(line.substring(valueStartPos)); 412 } 413 414 /** Parses out a status code result. */ parseStatusCode(String line)415 private void parseStatusCode(String line) { 416 String value = line.substring(Prefixes.STATUS_CODE.length()).trim(); 417 TestResult testInfo = getCurrentTestInfo(); 418 testInfo.mCode = StatusCodes.ERROR; 419 try { 420 testInfo.mCode = Integer.parseInt(value); 421 } catch (NumberFormatException e) { 422 Log.w(LOG_TAG, "Expected integer status code, received: " + value); 423 testInfo.mCode = StatusCodes.ERROR; 424 } 425 if (testInfo.mCode != StatusCodes.IN_PROGRESS) { 426 // this means we're done with current test result bundle 427 reportResult(testInfo); 428 clearCurrentTestInfo(); 429 } 430 } 431 432 /** 433 * Returns true if test run canceled. 434 * 435 * @see IShellOutputReceiver#isCancelled() 436 */ 437 @Override isCancelled()438 public boolean isCancelled() { 439 return mIsCancelled; 440 } 441 442 /** Requests cancellation of test run. */ 443 @Override cancel()444 public void cancel() { 445 mIsCancelled = true; 446 } 447 448 /** 449 * Reports a test result to the test run listener. Must be called when a individual test result 450 * has been fully parsed. 451 * 452 * @param testInfo The {@link TestResult} holding the current test infos. 453 */ reportResult(TestResult testInfo)454 private void reportResult(TestResult testInfo) { 455 if (!testInfo.isComplete()) { 456 Log.w(LOG_TAG, "invalid instrumentation status bundle " + testInfo.toString()); 457 return; 458 } 459 reportTestRunStarted(testInfo); 460 TestIdentifier testId = new TestIdentifier(testInfo.mTestClass, testInfo.mTestName); 461 Map<String, String> metrics; 462 463 switch (testInfo.mCode) { 464 case StatusCodes.START: 465 for (ITestRunListener listener : mTestListeners) { 466 listener.testStarted(testId); 467 } 468 break; 469 case StatusCodes.FAILURE: 470 // If a test failure was already reported for the same test number 471 // ('current' number), we avoid reporting a second repeated failure since it would 472 // cause inconsistent events. 473 if (mLastTestResult.mCurrentTestNumber != null 474 && mLastTestResult.mCurrentTestNumber.equals( 475 mCurrentTestResult.mCurrentTestNumber) 476 && mLastTestResult.mStackTrace != null) { 477 Log.e( 478 LOG_TAG, 479 String.format( 480 "Ignoring repeated failed event for %s. Stack: %s", 481 mCurrentTestResult.toString(), mCurrentTestResult.mStackTrace)); 482 break; 483 } 484 metrics = getAndResetTestMetrics(); 485 for (ITestRunListener listener : mTestListeners) { 486 listener.testFailed(testId, getTrace(testInfo)); 487 listener.testEnded(testId, metrics); 488 } 489 mNumTestsRun++; 490 break; 491 case StatusCodes.ERROR: 492 // we're dealing with a legacy JUnit3 runner that still reports errors. 493 // just report this as a failure, since thats what upstream JUnit4 does 494 metrics = getAndResetTestMetrics(); 495 for (ITestRunListener listener : mTestListeners) { 496 listener.testFailed(testId, getTrace(testInfo)); 497 listener.testEnded(testId, metrics); 498 } 499 mNumTestsRun++; 500 break; 501 case StatusCodes.IGNORED: 502 metrics = getAndResetTestMetrics(); 503 for (ITestRunListener listener : mTestListeners) { 504 listener.testIgnored(testId); 505 listener.testEnded(testId, metrics); 506 } 507 mNumTestsRun++; 508 break; 509 case StatusCodes.ASSUMPTION_FAILURE: 510 metrics = getAndResetTestMetrics(); 511 for (ITestRunListener listener : mTestListeners) { 512 listener.testAssumptionFailure(testId, getTrace(testInfo)); 513 listener.testEnded(testId, metrics); 514 } 515 mNumTestsRun++; 516 break; 517 case StatusCodes.OK: 518 metrics = getAndResetTestMetrics(); 519 for (ITestRunListener listener : mTestListeners) { 520 listener.testEnded(testId, metrics); 521 } 522 mNumTestsRun++; 523 break; 524 default: 525 metrics = getAndResetTestMetrics(); 526 Log.e(LOG_TAG, "Unknown status code received: " + testInfo.mCode); 527 for (ITestRunListener listener : mTestListeners) { 528 listener.testEnded(testId, metrics); 529 } 530 mNumTestsRun++; 531 break; 532 } 533 } 534 535 /** 536 * Reports the start of a test run, and the total test count, if it has not been previously 537 * reported. 538 * 539 * @param testInfo current test status values 540 */ reportTestRunStarted(TestResult testInfo)541 private void reportTestRunStarted(TestResult testInfo) { 542 // if start test run not reported yet 543 if (!mTestStartReported && testInfo.mNumTests != null) { 544 for (ITestRunListener listener : mTestListeners) { 545 listener.testRunStarted(mTestRunName, testInfo.mNumTests); 546 } 547 mNumTestsExpected = testInfo.mNumTests; 548 mTestStartReported = true; 549 } 550 } 551 552 /** Returns the stack trace of the current failed test, from the provided testInfo. */ getTrace(TestResult testInfo)553 private String getTrace(TestResult testInfo) { 554 if (testInfo.mStackTrace != null) { 555 return testInfo.mStackTrace; 556 } else { 557 Log.e(LOG_TAG, "Could not find stack trace for failed test "); 558 return new Throwable("Unknown failure").toString(); 559 } 560 } 561 562 /** 563 * Parses out and store the elapsed time. Elapsed time format use comma separation above 1000. 564 * For example: "Time: 1,745.755" which should be handled. 565 */ parseTime(String line)566 private void parseTime(String line) { 567 final Pattern timePattern = 568 Pattern.compile(String.format("%s\\s*([\\d\\,]*[\\d\\.]+)", Prefixes.TIME_REPORT)); 569 Matcher timeMatcher = timePattern.matcher(line); 570 if (timeMatcher.find()) { 571 String timeString = timeMatcher.group(1); 572 try { 573 Number n = NumberFormat.getInstance().parse(timeString); 574 float timeSeconds = n.floatValue(); 575 mTestTime = (long) (timeSeconds * 1000); 576 } catch (ParseException e) { 577 Log.w(LOG_TAG, String.format("Unexpected time format %1$s", line)); 578 } 579 } else { 580 Log.w(LOG_TAG, String.format("Unexpected time format %1$s", line)); 581 } 582 } 583 584 @Override handleTestRunFailed(@onNull String errorMsg)585 public void handleTestRunFailed(@NonNull String errorMsg) { 586 Log.i(LOG_TAG, String.format("test run failed: '%1$s'", errorMsg)); 587 if (mLastTestResult != null 588 && mLastTestResult.isComplete() 589 && StatusCodes.START == mLastTestResult.mCode) { 590 591 // received test start msg, but not test complete 592 // assume test caused this, report as test failure 593 TestIdentifier testId = 594 new TestIdentifier(mLastTestResult.mTestClass, mLastTestResult.mTestName); 595 for (ITestRunListener listener : mTestListeners) { 596 listener.testFailed( 597 testId, 598 String.format( 599 "%1$s. Reason: '%2$s'. %3$s", 600 INCOMPLETE_TEST_ERR_MSG_PREFIX, 601 errorMsg, 602 INCOMPLETE_TEST_ERR_MSG_POSTFIX)); 603 listener.testEnded(testId, getAndResetTestMetrics()); 604 } 605 } 606 for (ITestRunListener listener : mTestListeners) { 607 if (!mTestStartReported) { 608 // test run wasn't started - must have crashed before it started 609 listener.testRunStarted(mTestRunName, 0); 610 } 611 String runErrorMsg = errorMsg; 612 if (mOnError != null) { 613 runErrorMsg = String.format("%s. %s", errorMsg, mOnError); 614 } else if (mStreamError != null) { 615 runErrorMsg = String.format("%s. %s", errorMsg, mStreamError); 616 } 617 listener.testRunFailed(runErrorMsg); 618 619 if (mTestTime == null) { 620 // We don't report an extra failure due to missing time stamp. 621 mTestTime = 0L; 622 } 623 listener.testRunEnded(mTestTime, mInstrumentationResultBundle); 624 } 625 mOnError = null; 626 mTestStartReported = true; 627 mTestRunFailReported = true; 628 } 629 630 /** Called by parent when adb session is complete. */ 631 @Override done()632 public void done() { 633 super.done(); 634 if (!mTestRunFailReported) { 635 handleOutputDone(); 636 } 637 } 638 639 /** Handles the end of the adb session when a test run failure has not been reported yet */ handleOutputDone()640 private void handleOutputDone() { 641 if (!mTestStartReported && !mTestRunFinished) { 642 // no results 643 handleTestRunFailed(NO_TEST_RESULTS_MSG); 644 } else if (mNumTestsExpected > mNumTestsRun) { 645 final String message = 646 String.format( 647 "%1$s. Expected %2$d tests, received %3$d", 648 INCOMPLETE_RUN_ERR_MSG_PREFIX, mNumTestsExpected, mNumTestsRun); 649 handleTestRunFailed(message); 650 } else { 651 if (!mTestStartReported) { 652 // test run wasn't started, but it finished successfully. Must be a run with 653 // no tests 654 for (ITestRunListener listener : mTestListeners) { 655 listener.testRunStarted(mTestRunName, 0); 656 } 657 } 658 if (mTestTime == null) { 659 mTestTime = 0L; 660 } 661 for (ITestRunListener listener : mTestListeners) { 662 // If we haven't reported a failure yet 663 if (!mTestRunFailReported 664 && mStreamError != null 665 && mStreamError.contains(FATAL_EXCEPTION_MSG)) { 666 // If we reach here, this means the instrumentation fatally failed while being 667 // in -e log true mode. Resulting in only the stream containing the exception. 668 listener.testRunFailed(mStreamError.trim()); 669 } 670 listener.testRunEnded(mTestTime, mInstrumentationResultBundle); 671 } 672 } 673 } 674 } 675