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