1 /* 2 * Copyright (C) 2010 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.testtype; 17 18 import com.android.ddmlib.IShellOutputReceiver; 19 import com.android.ddmlib.MultiLineReceiver; 20 import com.android.tradefed.log.LogUtil.CLog; 21 import com.android.tradefed.metrics.proto.MetricMeasurement.Measurements; 22 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; 23 import com.android.tradefed.result.ITestInvocationListener; 24 import com.android.tradefed.result.TestDescription; 25 import com.android.tradefed.testtype.testdefs.XmlDefsTest; 26 27 import java.util.ArrayList; 28 import java.util.Collection; 29 import java.util.HashMap; 30 import java.util.Map; 31 import java.util.regex.Matcher; 32 import java.util.regex.Pattern; 33 34 /** 35 * Parses the 'raw output mode' results of native tests using GTest that run from shell, and informs 36 * a ITestInvocationListener of the results. 37 * 38 * <p>Sample format of output expected: 39 * 40 * <pre> 41 * [==========] Running 15 tests from 1 test case. 42 * [----------] Global test environment set-up. 43 * [----------] 15 tests from MessageTest 44 * [ RUN ] MessageTest.DefaultConstructor 45 * [ OK ] MessageTest.DefaultConstructor (1 ms) 46 * [ RUN ] MessageTest.CopyConstructor 47 * external/gtest/test/gtest-message_test.cc:67: Failure 48 * Value of: 5 49 * Expected: 2 50 * external/gtest/test/gtest-message_test.cc:68: Failure 51 * Value of: 1 == 1 52 * Actual: true 53 * Expected: false 54 * [ FAILED ] MessageTest.CopyConstructor (2 ms) 55 * ... 56 * [ RUN ] MessageTest.DoesNotTakeUpMuchStackSpace 57 * [ OK ] MessageTest.DoesNotTakeUpMuchStackSpace (0 ms) 58 * [----------] 15 tests from MessageTest (26 ms total) 59 * 60 * [----------] Global test environment tear-down 61 * [==========] 15 tests from 1 test case ran. (26 ms total) 62 * [ PASSED ] 6 tests. 63 * [ FAILED ] 9 tests, listed below: 64 * [ FAILED ] MessageTest.CopyConstructor 65 * [ FAILED ] MessageTest.ConstructsFromCString 66 * [ FAILED ] MessageTest.StreamsCString 67 * [ FAILED ] MessageTest.StreamsNullCString 68 * [ FAILED ] MessageTest.StreamsString 69 * [ FAILED ] MessageTest.StreamsStringWithEmbeddedNUL 70 * [ FAILED ] MessageTest.StreamsNULChar 71 * [ FAILED ] MessageTest.StreamsInt 72 * [ FAILED ] MessageTest.StreamsBasicIoManip 73 * 9 FAILED TESTS 74 * </pre> 75 * 76 * <p>where the following tags are used to signal certain events: 77 * 78 * <pre> 79 * [==========]: the first occurrence indicates a new run started, including the number of tests 80 * to be expected in this run 81 * [ RUN ]: indicates a new test has started to run; a series of zero or more lines may 82 * follow a test start, and will be captured in case of a test failure or error 83 * [ OK ]: the preceding test has completed successfully, optionally including the time it 84 * took to run (in ms) 85 * [ FAILED ]: the preceding test has failed, optionally including the time it took to run (in ms) 86 * [==========]: the preceding test run has completed, optionally including the time it took to run 87 * (in ms) 88 * </pre> 89 * 90 * All other lines are ignored. 91 */ 92 public class GTestResultParser extends MultiLineReceiver { 93 // Variables to keep track of state 94 private TestResult mCurrentTestResult = null; 95 private int mNumTestsRun = 0; 96 private int mNumTestsExpected = 0; 97 private long mTotalRunTime = 0; 98 private boolean mTestInProgress = false; 99 private boolean mTestRunInProgress = false; 100 private final String mTestRunName; 101 private final Collection<ITestInvocationListener> mTestListeners; 102 103 /** True if start of test has already been reported to listener. */ 104 private boolean mTestRunStartReported = false; 105 106 /** True if current test run has been canceled by user. */ 107 private boolean mIsCancelled = false; 108 109 private String mCoverageTarget = null; 110 111 /** Whether or not to prepend filename to classname. */ 112 private boolean mPrependFileName = false; 113 114 setPrependFileName(boolean prepend)115 public void setPrependFileName(boolean prepend) { 116 mPrependFileName = prepend; 117 } 118 getPrependFileName()119 public boolean getPrependFileName() { 120 return mPrependFileName; 121 } 122 123 /** 124 * Test result data 125 */ 126 private static class TestResult { 127 private String mTestName = null; 128 private String mTestClass = null; 129 private StringBuilder mStackTrace = null; 130 @SuppressWarnings("unused") 131 private Long mRunTime = null; 132 133 /** Returns whether expected values have been parsed 134 * 135 * @return true if all expected values have been parsed 136 */ isComplete()137 boolean isComplete() { 138 return mTestName != null && mTestClass != null; 139 } 140 141 /** Returns whether there is currently a stack trace 142 * 143 * @return true if there is currently a stack trace, false otherwise 144 */ hasStackTrace()145 boolean hasStackTrace() { 146 return mStackTrace != null; 147 } 148 149 /** 150 * Returns the stack trace of the current test. 151 * 152 * @return a String representation of the current test's stack trace; if there is not 153 * a current stack trace, it returns an error string. Use {@link TestResult#hasStackTrace} 154 * if you need to know whether there is a stack trace. 155 */ getTrace()156 String getTrace() { 157 if (hasStackTrace()) { 158 return mStackTrace.toString(); 159 } else { 160 CLog.e("Could not find stack trace for failed test"); 161 return new Throwable("Unknown failure").toString(); 162 } 163 } 164 165 /** Provides a more user readable string for TestResult, if possible */ 166 @Override toString()167 public String toString() { 168 StringBuilder output = new StringBuilder(); 169 if (mTestClass != null ) { 170 output.append(mTestClass); 171 output.append('#'); 172 } 173 if (mTestName != null) { 174 output.append(mTestName); 175 } 176 if (output.length() > 0) { 177 return output.toString(); 178 } 179 return "unknown result"; 180 } 181 } 182 183 /** Internal helper struct to store parsed test info. */ 184 private static class ParsedTestInfo { 185 String mTestName = null; 186 String mTestClassName = null; 187 String mTestRunTime = null; 188 ParsedTestInfo(String testName, String testClassName, String testRunTime)189 public ParsedTestInfo(String testName, String testClassName, String testRunTime) { 190 mTestName = testName; 191 mTestClassName = testClassName; 192 mTestRunTime = testRunTime; 193 } 194 } 195 196 /** Prefixes used to demarcate and identify output. */ 197 private static class Prefixes { 198 @SuppressWarnings("unused") 199 private static final String INFORMATIONAL_MARKER = "[----------]"; 200 private static final String START_TEST_RUN_MARKER = "[==========] Running"; 201 private static final String TEST_RUN_MARKER = "[==========]"; 202 private static final String START_TEST_MARKER = "[ RUN ]"; // GTest format 203 private static final String OK_TEST_MARKER = "[ OK ]"; // GTest format 204 private static final String FAILED_TEST_MARKER = "[ FAILED ]"; 205 // Alternative non GTest format can be generated from Google Test AOSP and respond to 206 // different needs (parallelism of tests) that the GTest format can't describe well. 207 private static final String ALT_OK_MARKER = "[ OK ]"; // Non GTest format 208 private static final String TIMEOUT_MARKER = "[ TIMEOUT ]"; // Non GTest format 209 // Native test failures: shared library link failure. 210 private static final String LINK_FAILURE_MARKER = "CANNOT LINK EXECUTABLE "; 211 } 212 213 /** 214 * Creates the GTestResultParser. 215 * 216 * @param testRunName the test run name to provide to {@link 217 * ITestInvocationListener#testRunStarted(String, int)} 218 * @param listeners informed of test results as the tests are executing 219 */ GTestResultParser(String testRunName, Collection<ITestInvocationListener> listeners)220 public GTestResultParser(String testRunName, Collection<ITestInvocationListener> listeners) { 221 mTestRunName = testRunName; 222 mTestListeners = new ArrayList<>(listeners); 223 } 224 225 /** 226 * Creates the GTestResultParser for a single listener. 227 * 228 * @param testRunName the test run name to provide to {@link 229 * ITestInvocationListener#testRunStarted(String, int)} 230 * @param listener informed of test results as the tests are executing 231 */ GTestResultParser(String testRunName, ITestInvocationListener listener)232 public GTestResultParser(String testRunName, ITestInvocationListener listener) { 233 mTestRunName = testRunName; 234 mTestListeners = new ArrayList<>(1); 235 mTestListeners.add(listener); 236 } 237 238 /** 239 * Returns the current TestResult for test in progress, or a new default one. 240 * 241 * @return The TestResult for the current test run 242 */ getCurrentTestResult()243 private TestResult getCurrentTestResult() { 244 if (mCurrentTestResult == null) { 245 mCurrentTestResult = new TestResult(); 246 } 247 return mCurrentTestResult; 248 } 249 250 251 /** 252 * Clears out the current TestResult. 253 */ clearCurrentTestResult()254 private void clearCurrentTestResult() { 255 mCurrentTestResult = null; 256 } 257 258 /** 259 * {@inheritDoc} 260 */ 261 @Override processNewLines(String[] lines)262 public void processNewLines(String[] lines) { 263 if (lines.length != 0 && lines[0].startsWith(Prefixes.LINK_FAILURE_MARKER)) { 264 for (String line : lines) { 265 // in verbose mode, dump all adb output to log 266 CLog.v(line); 267 } 268 for (ITestInvocationListener listener : mTestListeners) { 269 listener.testRunStarted(mTestRunName, 0); 270 listener.testRunFailed(lines[0]); 271 listener.testRunEnded(0, new HashMap<String, Metric>()); 272 } 273 } else { 274 for (String line : lines) { 275 parse(line); 276 // in verbose mode, dump all adb output to log 277 CLog.v(line); 278 } 279 } 280 } 281 282 /** 283 * Parse an individual output line. 284 * 285 * @param line Text output line 286 */ parse(String line)287 private void parse(String line) { 288 String message = null; 289 290 if (mTestRunInProgress || line.startsWith(Prefixes.TEST_RUN_MARKER)) { 291 if (line.startsWith(Prefixes.START_TEST_MARKER)) { 292 // Individual test started 293 message = line.substring(Prefixes.START_TEST_MARKER.length()).trim(); 294 processTestStartedTag(message); 295 } 296 else if (line.contains(Prefixes.OK_TEST_MARKER)) { 297 // Individual test completed successfully 298 // Logs from test could offset the OK marker 299 message = line.substring(line.indexOf(Prefixes.OK_TEST_MARKER) + 300 Prefixes.OK_TEST_MARKER.length()).trim(); 301 if (!testInProgress()) { 302 // If we are missing the RUN tag, skip it wrong format 303 CLog.e("Found %s without %s before, Ensure you are using GTest format", 304 line, Prefixes.START_TEST_MARKER); 305 return; 306 } 307 processOKTag(message); 308 clearCurrentTestResult(); 309 } 310 else if (line.contains(Prefixes.ALT_OK_MARKER)) { 311 message = line.substring(line.indexOf(Prefixes.ALT_OK_MARKER) + 312 Prefixes.ALT_OK_MARKER.length()).trim(); 313 // This alternative format does not have a RUN tag, so we fake it. 314 fakeRunMarker(message); 315 processOKTag(message); 316 clearCurrentTestResult(); 317 } 318 else if (line.contains(Prefixes.FAILED_TEST_MARKER)) { 319 // Individual test completed with failure 320 message = line.substring(line.indexOf(Prefixes.FAILED_TEST_MARKER) + 321 Prefixes.FAILED_TEST_MARKER.length()).trim(); 322 if (!testInProgress()) { 323 // If we are missing the RUN tag (ALT format) 324 fakeRunMarker(message); 325 } 326 processFailedTag(message); 327 clearCurrentTestResult(); 328 } 329 else if (line.contains(Prefixes.TIMEOUT_MARKER)) { 330 // Individual test timeout is considered a failure 331 message = line.substring(line.indexOf(Prefixes.TIMEOUT_MARKER) + 332 Prefixes.TIMEOUT_MARKER.length()).trim(); 333 fakeRunMarker(message); 334 processFailedTag(message); 335 clearCurrentTestResult(); 336 } 337 else if (line.startsWith(Prefixes.START_TEST_RUN_MARKER)) { 338 // Test run started 339 // Make sure to leave the "Running" in the string 340 message = line.substring(Prefixes.TEST_RUN_MARKER.length()).trim(); 341 processRunStartedTag(message); 342 } 343 else if (line.startsWith(Prefixes.TEST_RUN_MARKER)) { 344 // Test run ended 345 // This is for the end of the test suite run, so make sure this else-if is after the 346 // check for START_TEST_SUITE_MARKER 347 message = line.substring(Prefixes.TEST_RUN_MARKER.length()).trim(); 348 processRunCompletedTag(message); 349 } 350 else if (testInProgress()) { 351 // Note this does not handle the case of an error outside an actual test run 352 appendTestOutputLine(line); 353 } 354 } 355 } 356 357 /** 358 * Returns true if test run canceled. 359 * 360 * @see IShellOutputReceiver#isCancelled() 361 */ 362 @Override isCancelled()363 public boolean isCancelled() { 364 return mIsCancelled; 365 } 366 367 /** 368 * Create a fake run marker for alternative format that doesn't have it. 369 * @param message 370 */ fakeRunMarker(String message)371 private void fakeRunMarker(String message) { 372 // Remove everything after the test name. 373 String fakeRunMaker = message.split(" +")[0]; 374 // Do as if we had found a [RUN] tag. 375 processTestStartedTag(fakeRunMaker); 376 } 377 378 /** 379 * Requests cancellation of test run. 380 */ cancel()381 public void cancel() { 382 mIsCancelled = true; 383 } 384 385 /** 386 * Returns whether we're in the middle of running a test. 387 * 388 * @return True if a test was started, false otherwise 389 */ testInProgress()390 private boolean testInProgress() { 391 return mTestInProgress; 392 } 393 394 /** 395 * Set state to indicate we've started running a test. 396 * 397 */ setTestStarted()398 private void setTestStarted() { 399 mTestInProgress = true; 400 } 401 402 /** 403 * Set state to indicate we've started running a test. 404 * 405 */ setTestEnded()406 private void setTestEnded() { 407 mTestInProgress = false; 408 } 409 410 /** 411 * Reports the start of a test run, and the total test count, if it has not been previously 412 * reported. 413 */ reportTestRunStarted()414 private void reportTestRunStarted() { 415 // if start test run not reported yet 416 if (!mTestRunStartReported) { 417 for (ITestInvocationListener listener : mTestListeners) { 418 listener.testRunStarted(mTestRunName, mNumTestsExpected); 419 } 420 mTestRunStartReported = true; 421 } 422 } 423 424 /** 425 * Reports the end of a test run, and resets that test 426 */ reportTestRunEnded()427 private void reportTestRunEnded() { 428 for (ITestInvocationListener listener : mTestListeners) { 429 listener.testRunEnded(mTotalRunTime, getRunMetrics()); 430 } 431 mTestRunStartReported = false; 432 } 433 434 /** 435 * Create the run metrics {@link Map} to report. 436 * 437 * @return a {@link Map} of run metrics data 438 */ getRunMetrics()439 private HashMap<String, Metric> getRunMetrics() { 440 HashMap<String, Metric> metricsMap = new HashMap<>(); 441 if (mCoverageTarget != null) { 442 Measurements measure = 443 Measurements.newBuilder().setSingleString(mCoverageTarget).build(); 444 Metric m = Metric.newBuilder().setMeasurements(measure).build(); 445 metricsMap.put(XmlDefsTest.COVERAGE_TARGET_KEY, m); 446 } 447 return metricsMap; 448 } 449 450 /** 451 * Parse the test identifier (class and test name), and optional time info. 452 * 453 * @param identifier Raw identifier of the form classname.testname, with an optional time 454 * element in the format of (XX ms) at the end 455 * @return A ParsedTestInfo representing the parsed info from the identifier string. 456 * 457 * If no time tag was detected, then the third element in the array (time_in_ms) will 458 * be null. If the line failed to parse properly (eg: could not determine name of 459 * test/class) then an "UNKNOWN" string value will be returned for the classname and 460 * testname. This method guarantees a string will always be returned for the class and 461 * test names (but not for the time value). 462 */ parseTestDescription(String identifier)463 private ParsedTestInfo parseTestDescription(String identifier) { 464 ParsedTestInfo returnInfo = new ParsedTestInfo("UNKNOWN_CLASS", "UNKNOWN_TEST", null); 465 466 Pattern timePattern = Pattern.compile(".*(\\((\\d+) ms\\))"); // eg: (XX ms) 467 Matcher time = timePattern.matcher(identifier); 468 469 // Try to find a time 470 if (time.find()) { 471 String timeString = time.group(2); // the "XX" in "(XX ms)" 472 String discardPortion = time.group(1); // everything after the test class/name 473 identifier = identifier.substring(0, identifier.lastIndexOf(discardPortion)).trim(); 474 returnInfo.mTestRunTime = timeString; 475 } 476 477 String[] testId = identifier.split("\\."); 478 if (testId.length < 2) { 479 CLog.e("Could not detect the test class and test name, received: %s", identifier); 480 } 481 else { 482 returnInfo.mTestClassName = testId[0]; 483 returnInfo.mTestName = testId[1]; 484 } 485 return returnInfo; 486 } 487 488 /** 489 * Parses and stores the test identifier (class and test name). 490 * 491 * @param identifier Raw identifier 492 */ processRunStartedTag(String identifier)493 private void processRunStartedTag(String identifier) { 494 // eg: (Running XX tests from 1 test case.) 495 Pattern numTestsPattern = Pattern.compile("Running (\\d+) test[s]? from .*"); 496 Matcher numTests = numTestsPattern.matcher(identifier); 497 498 // Try to find number of tests 499 if (numTests.find()) { 500 try { 501 mNumTestsExpected = Integer.parseInt(numTests.group(1)); 502 } 503 catch (NumberFormatException e) { 504 CLog.e( 505 "Unable to determine number of tests expected, received: %s", 506 numTests.group(1)); 507 } 508 } 509 if (mNumTestsExpected > 0) { 510 reportTestRunStarted(); 511 mNumTestsRun = 0; 512 mTestRunInProgress = true; 513 } else if (mNumTestsExpected == 0) { 514 reportTestRunStarted(); 515 } 516 } 517 518 /** 519 * Processes and informs listener when we encounter a tag indicating that a test suite is done. 520 * 521 * @param identifier Raw log output from the suite ended tag 522 */ processRunCompletedTag(String identifier)523 private void processRunCompletedTag(String identifier) { 524 Pattern timePattern = Pattern.compile(".*\\((\\d+) ms total\\)"); // eg: (XX ms total) 525 Matcher time = timePattern.matcher(identifier); 526 527 // Try to find the total run time 528 if (time.find()) { 529 try { 530 mTotalRunTime = Long.parseLong(time.group(1)); 531 } 532 catch (NumberFormatException e) { 533 CLog.e("Unable to determine the total running time, received: %s", time.group(1)); 534 } 535 } 536 reportTestRunEnded(); 537 mTestRunInProgress = false; 538 } 539 getTestClass(TestResult testResult)540 private String getTestClass(TestResult testResult) { 541 542 if (mPrependFileName) { 543 StringBuilder sb = new StringBuilder(); 544 sb.append(mTestRunName); 545 sb.append("."); 546 sb.append(testResult.mTestClass); 547 return sb.toString(); 548 } 549 return testResult.mTestClass; 550 } 551 552 /** 553 * Processes and informs listener when we encounter a tag indicating that a test has started. 554 * 555 * @param identifier Raw log output of the form classname.testname, with an optional time (x ms) 556 */ processTestStartedTag(String identifier)557 private void processTestStartedTag(String identifier) { 558 ParsedTestInfo parsedResults = parseTestDescription(identifier); 559 TestResult testResult = getCurrentTestResult(); 560 testResult.mTestClass = parsedResults.mTestClassName; 561 testResult.mTestName = parsedResults.mTestName; 562 TestDescription testId = null; 563 if (getTestClass(testResult) !=null && testResult.mTestName !=null) { 564 testId = new TestDescription(getTestClass(testResult), testResult.mTestName); 565 } else { 566 CLog.e("Error during parsing, className: %s and testName: %s, should both be not null", 567 getTestClass(testResult), testResult.mTestName); 568 return; 569 } 570 571 for (ITestInvocationListener listener : mTestListeners) { 572 listener.testStarted(testId); 573 } 574 setTestStarted(); 575 } 576 577 /** 578 * Helper method to do the work necessary when a test has ended. 579 * 580 * @param identifier Raw log output of the form "classname.testname" with an optional (XX ms) 581 * at the end indicating the running time. 582 * @param testPassed Indicates whether the test passed or failed (set to true if passed, false 583 * if failed) 584 */ doTestEnded(String identifier, boolean testPassed)585 private void doTestEnded(String identifier, boolean testPassed) { 586 ParsedTestInfo parsedResults = parseTestDescription(identifier); 587 TestResult testResult = getCurrentTestResult(); 588 TestDescription testId = null; 589 if (getTestClass(testResult) !=null && testResult.mTestName !=null) { 590 testId = new TestDescription(getTestClass(testResult), testResult.mTestName); 591 } else { 592 CLog.e("Error during parsing, className: %s and testName: %s, should both be not null", 593 getTestClass(testResult), testResult.mTestName); 594 return; 595 } 596 597 // Error - trying to end a test when one isn't in progress 598 if (!testInProgress()) { 599 CLog.e("Test currently not in progress when trying to end test: %s", identifier); 600 return; 601 } 602 603 // Save the run time for this test if one exists 604 if (parsedResults.mTestRunTime != null) { 605 try { 606 testResult.mRunTime = Long.valueOf(parsedResults.mTestRunTime); 607 } catch (NumberFormatException e) { 608 CLog.e("Test run time value is invalid, received: %s", parsedResults.mTestRunTime); 609 } 610 } 611 612 // Check that the test result is for the same test/class we're expecting it to be for 613 boolean encounteredUnexpectedTest = false; 614 if (!testResult.isComplete()) { 615 CLog.e("No test/class name is currently recorded as running!"); 616 } 617 else { 618 if (testResult.mTestClass.compareTo(parsedResults.mTestClassName) != 0) { 619 CLog.e( 620 "Name for current test class does not match class we started with, " 621 + "expected: %s but got:%s ", 622 testResult.mTestClass, parsedResults.mTestClassName); 623 encounteredUnexpectedTest = true; 624 } 625 if (testResult.mTestName.compareTo(parsedResults.mTestName) != 0) { 626 CLog.e( 627 "Name for current test does not match test we started with, expected: %s " 628 + " but got: %s", 629 testResult.mTestName, parsedResults.mTestName); 630 encounteredUnexpectedTest = true; 631 } 632 } 633 634 if (encounteredUnexpectedTest) { 635 // If the test name of the result changed from what we started with, report that 636 // the last known test failed, regardless of whether we received a pass or fail tag. 637 for (ITestInvocationListener listener : mTestListeners) { 638 listener.testFailed(testId, mCurrentTestResult.getTrace()); 639 } 640 } 641 else if (!testPassed) { // test failed 642 for (ITestInvocationListener listener : mTestListeners) { 643 listener.testFailed(testId, mCurrentTestResult.getTrace()); 644 } 645 } 646 // For all cases (pass or fail), we ultimately need to report test has ended 647 HashMap<String, Metric> emptyMap = new HashMap<>(); 648 for (ITestInvocationListener listener : mTestListeners) { 649 // @TODO: Add reporting of test run time to ITestInvocationListener 650 listener.testEnded(testId, emptyMap); 651 } 652 653 setTestEnded(); 654 ++mNumTestsRun; 655 } 656 657 /** 658 * Processes and informs listener when we encounter the OK tag. 659 * 660 * @param identifier Raw log output of the form "classname.testname" with an optional (XX ms) 661 * at the end indicating the running time. 662 */ processOKTag(String identifier)663 private void processOKTag(String identifier) { 664 doTestEnded(identifier, true); 665 } 666 667 /** 668 * Processes and informs listener when we encounter the FAILED tag. 669 * 670 * @param identifier Raw log output of the form "classname.testname" with an optional (XX ms) 671 * at the end indicating the running time. 672 */ processFailedTag(String identifier)673 private void processFailedTag(String identifier) { 674 doTestEnded(identifier, false); 675 } 676 677 /** 678 * Appends the test output to the current TestResult. 679 * 680 * @param line Raw test result line of output. 681 */ appendTestOutputLine(String line)682 private void appendTestOutputLine(String line) { 683 TestResult testResult = getCurrentTestResult(); 684 if (testResult.mStackTrace == null) { 685 testResult.mStackTrace = new StringBuilder(); 686 } 687 else { 688 testResult.mStackTrace.append("\r\n"); 689 } 690 testResult.mStackTrace.append(line); 691 } 692 693 /** 694 * Process an instrumentation run failure 695 * 696 * @param errorMsg The message to output about the nature of the error 697 */ handleTestRunFailed(String errorMsg)698 private void handleTestRunFailed(String errorMsg) { 699 errorMsg = (errorMsg == null ? "Unknown error" : errorMsg); 700 CLog.i("Test run failed: %s", errorMsg); 701 String testRunStackTrace = ""; 702 703 // Report that the last known test failed 704 if ((mCurrentTestResult != null) && (mCurrentTestResult.isComplete())) { 705 // current test results are cleared out after every complete test run, 706 // if it's not null, assume the last test caused this and report as a test failure 707 TestDescription testId = 708 new TestDescription( 709 getTestClass(mCurrentTestResult), mCurrentTestResult.mTestName); 710 711 // If there was any stack trace during the test run, append it to the "test failed" 712 // error message so we have an idea of what caused the crash/failure. 713 HashMap<String, Metric> emptyMap = new HashMap<>(); 714 if (mCurrentTestResult.hasStackTrace()) { 715 testRunStackTrace = mCurrentTestResult.getTrace(); 716 } 717 for (ITestInvocationListener listener : mTestListeners) { 718 listener.testFailed(testId, 719 "No test results.\r\n" + testRunStackTrace); 720 listener.testEnded(testId, emptyMap); 721 } 722 clearCurrentTestResult(); 723 } 724 // Report the test run failed 725 for (ITestInvocationListener listener : mTestListeners) { 726 listener.testRunFailed(errorMsg); 727 listener.testRunEnded(mTotalRunTime, getRunMetrics()); 728 } 729 } 730 731 /** 732 * Called by parent when adb session is complete. 733 */ 734 @Override done()735 public void done() { 736 super.done(); 737 if (mNumTestsExpected > mNumTestsRun) { 738 handleTestRunFailed(String.format("Test run incomplete. Expected %d tests, received %d", 739 mNumTestsExpected, mNumTestsRun)); 740 } 741 else if (mTestRunInProgress) { 742 handleTestRunFailed("No test results"); 743 } 744 } 745 746 /** 747 * Sets the coverage target for this test. 748 * <p/> 749 * Will be sent as a metric to test listeners. 750 * 751 * @param coverageTarget the coverage target 752 */ setCoverageTarget(String coverageTarget)753 public void setCoverageTarget(String coverageTarget) { 754 mCoverageTarget = coverageTarget; 755 } 756 } 757