OSDN Git Service

Add support for multiple instrumentation test result listeners.
[android-x86/sdk.git] / ddms / libs / ddmlib / src / com / android / ddmlib / testrunner / InstrumentationResultParser.java
index 1a0b21f..a3f89cc 100755 (executable)
@@ -20,26 +20,29 @@ import com.android.ddmlib.IShellOutputReceiver;
 import com.android.ddmlib.Log;
 import com.android.ddmlib.MultiLineReceiver;
 
+import java.util.ArrayList;
+import java.util.Collection;
+
 /**
- * Parses the 'raw output mode' results of an instrumentation test run from shell and informs a 
+ * Parses the 'raw output mode' results of an instrumentation test run from shell and informs a
  * ITestRunListener of the results.
- * 
+ *
  * <p>Expects the following output:
- * 
+ *
  * <p>If fatal error occurred when attempted to run the tests:
  * <pre>
  * INSTRUMENTATION_STATUS: Error=error Message
- * INSTRUMENTATION_FAILED: 
+ * INSTRUMENTATION_FAILED:
  * </pre>
  * <p>or
  * <pre>
  * INSTRUMENTATION_RESULT: shortMsg=error Message
  * </pre>
- * 
+ *
  * <p>Otherwise, expect a series of test results, each one containing a set of status key/value
- * pairs, delimited by a start(1)/pass(0)/fail(-2)/error(-1) status code result. At end of test 
- * run, expects that the elapsed test time in seconds will be displayed  
- * 
+ * pairs, delimited by a start(1)/pass(0)/fail(-2)/error(-1) status code result. At end of test
+ * run, expects that the elapsed test time in seconds will be displayed
+ *
  * <p>For example:
  * <pre>
  * INSTRUMENTATION_STATUS_CODE: 1
@@ -48,15 +51,15 @@ import com.android.ddmlib.MultiLineReceiver;
  * INSTRUMENTATION_STATUS: numtests=2
  * INSTRUMENTATION_STATUS: stack=com.foo.FooTest#testFoo:312
  *    com.foo.X
- * INSTRUMENTATION_STATUS_CODE: -2   
- * ... 
- * 
+ * INSTRUMENTATION_STATUS_CODE: -2
+ * ...
+ *
  * Time: X
  * </pre>
  * <p>Note that the "value" portion of the key-value pair may wrap over several text lines
  */
 public class InstrumentationResultParser extends MultiLineReceiver {
-    
+
     /** Relevant test status keys. */
     private static class StatusKeys {
         private static final String TEST = "test";
@@ -66,7 +69,7 @@ public class InstrumentationResultParser extends MultiLineReceiver {
         private static final String ERROR = "Error";
         private static final String SHORTMSG = "shortMsg";
     }
-    
+
     /** Test result status codes. */
     private static class StatusCodes {
         private static final int FAILURE = -2;
@@ -84,10 +87,10 @@ public class InstrumentationResultParser extends MultiLineReceiver {
         private static final String RESULT = "INSTRUMENTATION_RESULT: ";
         private static final String TIME_REPORT = "Time: ";
     }
-    
-    private final ITestRunListener mTestListener;
 
-    /** 
+    private final Collection<ITestRunListener> mTestListeners;
+
+    /**
      * Test result data
      */
     private static class TestResult {
@@ -96,12 +99,12 @@ public class InstrumentationResultParser extends MultiLineReceiver {
         private String mTestClass = null;
         private String mStackTrace = null;
         private Integer mNumTests = null;
-        
+
         /** Returns true if all expected values have been parsed */
         boolean isComplete() {
             return mCode != null && mTestName != null && mTestClass != null;
         }
-        
+
         /** Provides a more user readable string for TestResult, if possible */
         @Override
         public String toString() {
@@ -109,49 +112,68 @@ public class InstrumentationResultParser extends MultiLineReceiver {
             if (mTestClass != null ) {
                 output.append(mTestClass);
                 output.append('#');
-            }    
+            }
             if (mTestName != null) {
                 output.append(mTestName);
             }
             if (output.length() > 0) {
                 return output.toString();
-            }    
+            }
             return "unknown result";
         }
     }
-    
+
     /** Stores the status values for the test result currently being parsed */
     private TestResult mCurrentTestResult = null;
-    
+
     /** Stores the current "key" portion of the status key-value being parsed. */
     private String mCurrentKey = null;
-    
+
     /** Stores the current "value" portion of the status key-value being parsed. */
     private StringBuilder mCurrentValue = null;
-    
+
     /** True if start of test has already been reported to listener. */
     private boolean mTestStartReported = false;
-    
+
+    /** True if test run failure has already been reported to listener. */
+    private boolean mTestRunFailReported = false;
+
     /** The elapsed time of the test run, in milliseconds. */
     private long mTestTime = 0;
-    
+
     /** True if current test run has been canceled by user. */
     private boolean mIsCancelled = false;
-    
+
+    /** The number of tests currently run  */
+    private int mNumTestsRun = 0;
+
+    /** The number of tests expected to run  */
+    private int mNumTestsExpected = 0;
+
     private static final String LOG_TAG = "InstrumentationResultParser";
-    
+
     /**
      * Creates the InstrumentationResultParser.
-     * 
+     *
+     * @param listeners informed of test results as the tests are executing
+     */
+    public InstrumentationResultParser(Collection<ITestRunListener> listeners) {
+        mTestListeners = new ArrayList<ITestRunListener>(listeners);
+    }
+
+    /**
+     * Creates the InstrumentationResultParser for a single listener.
+     *
      * @param listener informed of test results as the tests are executing
      */
     public InstrumentationResultParser(ITestRunListener listener) {
-        mTestListener = listener;
+        mTestListeners = new ArrayList<ITestRunListener>(1);
+        mTestListeners.add(listener);
     }
-    
+
     /**
      * Processes the instrumentation test output from shell.
-     * 
+     *
      * @see MultiLineReceiver#processNewLines
      */
     @Override
@@ -162,23 +184,23 @@ public class InstrumentationResultParser extends MultiLineReceiver {
             Log.v(LOG_TAG, line);
         }
     }
-    
+
     /**
      * Parse an individual output line. Expects a line that is one of:
      * <ul>
-     * <li> 
-     * The start of a new status line (starts with Prefixes.STATUS or Prefixes.STATUS_CODE), 
-     * and thus there is a new key=value pair to parse, and the previous key-value pair is 
-     * finished. 
+     * <li>
+     * The start of a new status line (starts with Prefixes.STATUS or Prefixes.STATUS_CODE),
+     * and thus there is a new key=value pair to parse, and the previous key-value pair is
+     * finished.
      * </li>
      * <li>
      * A continuation of the previous status (the "value" portion of the key has wrapped
      * to the next line).
-     * </li>  
+     * </li>
      * <li> A line reporting a fatal error in the test run (Prefixes.STATUS_FAILED) </li>
-     * <li> A line reporting the total elapsed time of the test run. (Prefixes.TIME_REPORT) </li>  
+     * <li> A line reporting the total elapsed time of the test run. (Prefixes.TIME_REPORT) </li>
      * </ul>
-     *    
+     *
      * @param line  Text output line
      */
     private void parse(String line) {
@@ -193,25 +215,25 @@ public class InstrumentationResultParser extends MultiLineReceiver {
         } else if (line.startsWith(Prefixes.RESULT)) {
             // Previous status key-value has been collected. Store it.
             submitCurrentKeyValue();
-            parseKey(line, Prefixes.RESULT.length());  
-        } else if (line.startsWith(Prefixes.STATUS_FAILED) || 
+            parseKey(line, Prefixes.RESULT.length());
+        } else if (line.startsWith(Prefixes.STATUS_FAILED) ||
                    line.startsWith(Prefixes.CODE)) {
             // Previous status key-value has been collected. Store it.
             submitCurrentKeyValue();
-            // just ignore the remaining data on this line            
+            // just ignore the remaining data on this line
         } else if (line.startsWith(Prefixes.TIME_REPORT)) {
             parseTime(line, Prefixes.TIME_REPORT.length());
         } else {
             if (mCurrentValue != null) {
-                // this is a value that has wrapped to next line. 
+                // this is a value that has wrapped to next line.
                 mCurrentValue.append("\r\n");
                 mCurrentValue.append(line);
             } else {
-                Log.w(LOG_TAG, "unrecognized line " + line);
+                Log.i(LOG_TAG, "unrecognized line " + line);
             }
         }
     }
-    
+
     /**
      * Stores the currently parsed key-value pair into mCurrentTestInfo.
      */
@@ -230,10 +252,10 @@ public class InstrumentationResultParser extends MultiLineReceiver {
                 } catch (NumberFormatException e) {
                     Log.e(LOG_TAG, "Unexpected integer number of tests, received " + statusValue);
                 }
-            } else if (mCurrentKey.equals(StatusKeys.ERROR) || 
+            } else if (mCurrentKey.equals(StatusKeys.ERROR) ||
                     mCurrentKey.equals(StatusKeys.SHORTMSG)) {
                 // test run must have failed
-                handleTestRunFailed(statusValue); 
+                handleTestRunFailed(statusValue);
             } else if (mCurrentKey.equals(StatusKeys.STACK)) {
                 testInfo.mStackTrace = statusValue;
             }
@@ -242,23 +264,23 @@ public class InstrumentationResultParser extends MultiLineReceiver {
             mCurrentValue = null;
         }
     }
-    
+
     private TestResult getCurrentTestInfo() {
         if (mCurrentTestResult == null) {
             mCurrentTestResult = new TestResult();
         }
         return mCurrentTestResult;
     }
-    
+
     private void clearCurrentTestInfo() {
         mCurrentTestResult = null;
     }
-    
+
     /**
      * Parses the key from the current line.
      * Expects format of "key=value".
-     *  
-     * @param line full line of text to parse 
+     *
+     * @param line full line of text to parse
      * @param keyStartPos the starting position of the key in the given line
      */
     private void parseKey(String line, int keyStartPos) {
@@ -268,55 +290,55 @@ public class InstrumentationResultParser extends MultiLineReceiver {
             parseValue(line, endKeyPos + 1);
         }
     }
-    
+
     /**
      * Parses the start of a key=value pair.
-     *  
-     * @param line - full line of text to parse 
+     *
+     * @param line - full line of text to parse
      * @param valueStartPos - the starting position of the value in the given line
      */
     private void parseValue(String line, int valueStartPos) {
         mCurrentValue = new StringBuilder();
         mCurrentValue.append(line.substring(valueStartPos));
     }
-    
+
     /**
-     * Parses out a status code result. 
+     * Parses out a status code result.
      */
     private void parseStatusCode(String line) {
         String value = line.substring(Prefixes.STATUS_CODE.length()).trim();
         TestResult testInfo = getCurrentTestInfo();
         try {
-            testInfo.mCode = Integer.parseInt(value);    
+            testInfo.mCode = Integer.parseInt(value);
         } catch (NumberFormatException e) {
             Log.e(LOG_TAG, "Expected integer status code, received: " + value);
         }
-        
+
         // this means we're done with current test result bundle
         reportResult(testInfo);
         clearCurrentTestInfo();
     }
-    
+
     /**
      * Returns true if test run canceled.
-     * 
+     *
      * @see IShellOutputReceiver#isCancelled()
      */
     public boolean isCancelled() {
         return mIsCancelled;
     }
-    
+
     /**
      * Requests cancellation of test run.
      */
     public void cancel() {
         mIsCancelled = true;
     }
-    
+
     /**
      * Reports a test result to the test run listener. Must be called when a individual test
-     * result has been fully parsed. 
-     * 
+     * result has been fully parsed.
+     *
      * @param statusMap key-value status pairs of test result
      */
     private void reportResult(TestResult testInfo) {
@@ -329,55 +351,73 @@ public class InstrumentationResultParser extends MultiLineReceiver {
 
         switch (testInfo.mCode) {
             case StatusCodes.START:
-                mTestListener.testStarted(testId);
+                for (ITestRunListener listener : mTestListeners) {
+                    listener.testStarted(testId);
+                }
                 break;
             case StatusCodes.FAILURE:
-                mTestListener.testFailed(ITestRunListener.TestFailure.FAILURE, testId, 
+                for (ITestRunListener listener : mTestListeners) {
+                    listener.testFailed(ITestRunListener.TestFailure.FAILURE, testId,
                         getTrace(testInfo));
-                mTestListener.testEnded(testId);
+
+                    listener.testEnded(testId);
+                }
+                mNumTestsRun++;
                 break;
             case StatusCodes.ERROR:
-                mTestListener.testFailed(ITestRunListener.TestFailure.ERROR, testId, 
+                for (ITestRunListener listener : mTestListeners) {
+                    listener.testFailed(ITestRunListener.TestFailure.ERROR, testId,
                         getTrace(testInfo));
-                mTestListener.testEnded(testId);
+                    listener.testEnded(testId);
+                }
+                mNumTestsRun++;
                 break;
             case StatusCodes.OK:
-                mTestListener.testEnded(testId);
+                for (ITestRunListener listener : mTestListeners) {
+                    listener.testEnded(testId);
+                }
+                mNumTestsRun++;
                 break;
             default:
                 Log.e(LOG_TAG, "Unknown status code received: " + testInfo.mCode);
-                mTestListener.testEnded(testId);
+                for (ITestRunListener listener : mTestListeners) {
+                    listener.testEnded(testId);
+                }
+                mNumTestsRun++;
             break;
         }
 
     }
-    
+
     /**
-     * Reports the start of a test run, and the total test count, if it has not been previously 
+     * Reports the start of a test run, and the total test count, if it has not been previously
      * reported.
-     * 
+     *
      * @param testInfo current test status values
      */
     private void reportTestRunStarted(TestResult testInfo) {
         // if start test run not reported yet
         if (!mTestStartReported && testInfo.mNumTests != null) {
-            mTestListener.testRunStarted(testInfo.mNumTests);
+            for (ITestRunListener listener : mTestListeners) {
+                listener.testRunStarted(testInfo.mNumTests);
+            }
+            mNumTestsExpected = testInfo.mNumTests;
             mTestStartReported = true;
         }
     }
-    
+
     /**
      * Returns the stack trace of the current failed test, from the provided testInfo.
      */
     private String getTrace(TestResult testInfo) {
         if (testInfo.mStackTrace != null) {
-            return testInfo.mStackTrace;    
+            return testInfo.mStackTrace;
         } else {
             Log.e(LOG_TAG, "Could not find stack trace for failed test ");
             return new Throwable("Unknown failure").toString();
         }
     }
-    
+
     /**
      * Parses out and store the elapsed time.
      */
@@ -385,25 +425,41 @@ public class InstrumentationResultParser extends MultiLineReceiver {
         String timeString = line.substring(startPos);
         try {
             float timeSeconds = Float.parseFloat(timeString);
-            mTestTime = (long) (timeSeconds * 1000); 
+            mTestTime = (long) (timeSeconds * 1000);
         } catch (NumberFormatException e) {
             Log.e(LOG_TAG, "Unexpected time format " + timeString);
         }
     }
-    
+
     /**
      * Process a instrumentation run failure
      */
     private void handleTestRunFailed(String errorMsg) {
-        mTestListener.testRunFailed(errorMsg == null ? "Unknown error" : errorMsg);
+        Log.i(LOG_TAG, String.format("test run failed %s", errorMsg));
+        for (ITestRunListener listener : mTestListeners) {
+            listener.testRunFailed(errorMsg == null ? "Unknown error" : errorMsg);
+        }
+        mTestRunFailReported = true;
     }
-    
+
     /**
-     * Called by parent when adb session is complete. 
+     * Called by parent when adb session is complete.
      */
     @Override
     public void done() {
         super.done();
-        mTestListener.testRunEnded(mTestTime);
+        if (!mTestRunFailReported && mNumTestsExpected > mNumTestsRun) {
+            final String message =
+                String.format("Test run incomplete. Expected %d tests, received %d",
+                        mNumTestsExpected, mNumTestsRun);
+            Log.w(LOG_TAG, message);
+            for (ITestRunListener listener : mTestListeners) {
+                listener.testRunFailed(message);
+            }
+        } else {
+            for (ITestRunListener listener : mTestListeners) {
+                listener.testRunEnded(mTestTime);
+            }
+        }
     }
 }