• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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