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