Fix elapsed time of tests by using new ddmlib callback

Carry the execution time from subprocess and shard to
master reporters.

Test: local presubmit tests
Bug: 36119387
Change-Id: Ic6d45c54a1f952b8a4113e966989bb4d0161f6d8
diff --git a/src/com/android/tradefed/invoker/ShardListener.java b/src/com/android/tradefed/invoker/ShardListener.java
index f429fcc..2cd2c69 100644
--- a/src/com/android/tradefed/invoker/ShardListener.java
+++ b/src/com/android/tradefed/invoker/ShardListener.java
@@ -126,7 +126,7 @@
 
     private void forwardTestResults(Map<TestIdentifier, TestResult> testResults) {
         for (Map.Entry<TestIdentifier, TestResult> testEntry : testResults.entrySet()) {
-            mMasterListener.testStarted(testEntry.getKey());
+            mMasterListener.testStarted(testEntry.getKey(), testEntry.getValue().getStartTime());
             switch (testEntry.getValue().getStatus()) {
                 case FAILURE:
                     mMasterListener.testFailed(testEntry.getKey(),
@@ -143,7 +143,10 @@
                     break;
             }
             if (!testEntry.getValue().getStatus().equals(TestStatus.INCOMPLETE)) {
-                mMasterListener.testEnded(testEntry.getKey(), testEntry.getValue().getMetrics());
+                mMasterListener.testEnded(
+                        testEntry.getKey(),
+                        testEntry.getValue().getEndTime(),
+                        testEntry.getValue().getMetrics());
             }
         }
     }
diff --git a/src/com/android/tradefed/result/CollectingTestListener.java b/src/com/android/tradefed/result/CollectingTestListener.java
index 1ca1cc1..1363a7d 100644
--- a/src/com/android/tradefed/result/CollectingTestListener.java
+++ b/src/com/android/tradefed/result/CollectingTestListener.java
@@ -144,8 +144,14 @@
      */
     @Override
     public void testStarted(TestIdentifier test) {
+        testStarted(test, System.currentTimeMillis());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void testStarted(TestIdentifier test, long startTime) {
         mIsCountDirty = true;
-        mCurrentResults.testStarted(test);
+        mCurrentResults.testStarted(test, startTime);
     }
 
     /**
@@ -153,13 +159,17 @@
      */
     @Override
     public void testEnded(TestIdentifier test, Map<String, String> testMetrics) {
-        mIsCountDirty = true;
-        mCurrentResults.testEnded(test, testMetrics);
+        testEnded(test, System.currentTimeMillis(), testMetrics);
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
+    @Override
+    public void testEnded(TestIdentifier test, long endTime, Map<String, String> testMetrics) {
+        mIsCountDirty = true;
+        mCurrentResults.testEnded(test, endTime, testMetrics);
+    }
+
+    /** {@inheritDoc} */
     @Override
     public void testFailed(TestIdentifier test, String trace) {
         mIsCountDirty = true;
diff --git a/src/com/android/tradefed/result/ITestInvocationListener.java b/src/com/android/tradefed/result/ITestInvocationListener.java
index c84d3d0..c8fbd7c 100644
--- a/src/com/android/tradefed/result/ITestInvocationListener.java
+++ b/src/com/android/tradefed/result/ITestInvocationListener.java
@@ -100,11 +100,16 @@
     @Override
     default public void testEnded(TestIdentifier test, Map<String, String> testMetrics) { }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     @Override
-    default public void testFailed(TestIdentifier test, String trace) { }
+    public default void testEnded(
+            TestIdentifier test, long endTime, Map<String, String> testMetrics) {
+        testEnded(test, testMetrics);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public default void testFailed(TestIdentifier test, String trace) {}
 
     /**
      * {@inheritDoc}
@@ -147,4 +152,10 @@
      */
     @Override
     default public void testStarted(TestIdentifier test) { }
+
+    /** {@inheritDoc} */
+    @Override
+    default void testStarted(TestIdentifier test, long startTime) {
+        testStarted(test);
+    }
 }
diff --git a/src/com/android/tradefed/result/ResultForwarder.java b/src/com/android/tradefed/result/ResultForwarder.java
index 55345d3..494fc7a 100644
--- a/src/com/android/tradefed/result/ResultForwarder.java
+++ b/src/com/android/tradefed/result/ResultForwarder.java
@@ -217,20 +217,25 @@
      */
     @Override
     public void testStarted(TestIdentifier test) {
+        testStarted(test, System.currentTimeMillis());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void testStarted(TestIdentifier test, long startTime) {
         for (ITestInvocationListener listener : mListeners) {
             try {
-                listener.testStarted(test);
+                listener.testStarted(test, startTime);
             } catch (RuntimeException e) {
-                CLog.e("RuntimeException while invoking %s#testStarted",
+                CLog.e(
+                        "RuntimeException while invoking %s#testStarted",
                         listener.getClass().getName());
                 CLog.e(e);
             }
         }
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     @Override
     public void testFailed(TestIdentifier test, String trace) {
         for (ITestInvocationListener listener : mListeners) {
@@ -249,11 +254,18 @@
      */
     @Override
     public void testEnded(TestIdentifier test, Map<String, String> testMetrics) {
+        testEnded(test, System.currentTimeMillis(), testMetrics);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void testEnded(TestIdentifier test, long endTime, Map<String, String> testMetrics) {
         for (ITestInvocationListener listener : mListeners) {
             try {
-                listener.testEnded(test, testMetrics);
+                listener.testEnded(test, endTime, testMetrics);
             } catch (RuntimeException e) {
-                CLog.e("RuntimeException while invoking %s#testEnded",
+                CLog.e(
+                        "RuntimeException while invoking %s#testEnded",
                         listener.getClass().getName());
                 CLog.e(e);
             }
diff --git a/src/com/android/tradefed/result/SubprocessResultsReporter.java b/src/com/android/tradefed/result/SubprocessResultsReporter.java
index 71f0022..141adbc 100644
--- a/src/com/android/tradefed/result/SubprocessResultsReporter.java
+++ b/src/com/android/tradefed/result/SubprocessResultsReporter.java
@@ -29,6 +29,7 @@
 import com.android.tradefed.util.SubprocessEventHelper.TestRunEndedEventInfo;
 import com.android.tradefed.util.SubprocessEventHelper.TestRunFailedEventInfo;
 import com.android.tradefed.util.SubprocessEventHelper.TestRunStartedEventInfo;
+import com.android.tradefed.util.SubprocessEventHelper.TestStartedEventInfo;
 import com.android.tradefed.util.SubprocessTestResultsParser;
 
 import java.io.File;
@@ -70,14 +71,19 @@
      */
     @Override
     public void testEnded(TestIdentifier testId, Map<String, String> metrics) {
+        testEnded(testId, System.currentTimeMillis(), metrics);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void testEnded(TestIdentifier testId, long endTime, Map<String, String> metrics) {
         TestEndedEventInfo info =
-                new TestEndedEventInfo(testId.getClassName(), testId.getTestName(), metrics);
+                new TestEndedEventInfo(
+                        testId.getClassName(), testId.getTestName(), endTime, metrics);
         printEvent(SubprocessTestResultsParser.StatusKeys.TEST_ENDED, info);
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     @Override
     public void testFailed(TestIdentifier testId, String reason) {
         FailedTestEventInfo info =
@@ -131,7 +137,14 @@
      */
     @Override
     public void testStarted(TestIdentifier testId) {
-        BaseTestEventInfo info = new BaseTestEventInfo(testId.getClassName(), testId.getTestName());
+        testStarted(testId, System.currentTimeMillis());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void testStarted(TestIdentifier testId, long startTime) {
+        TestStartedEventInfo info =
+                new TestStartedEventInfo(testId.getClassName(), testId.getTestName(), startTime);
         printEvent(SubprocessTestResultsParser.StatusKeys.TEST_STARTED, info);
     }
 
diff --git a/src/com/android/tradefed/testtype/suite/ModuleDefinition.java b/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
index c9e99d3..69ccec1 100644
--- a/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
+++ b/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
@@ -247,7 +247,7 @@
     private void forwardTestResults(
             Map<TestIdentifier, TestResult> testResults, ITestInvocationListener listener) {
         for (Map.Entry<TestIdentifier, TestResult> testEntry : testResults.entrySet()) {
-            listener.testStarted(testEntry.getKey());
+            listener.testStarted(testEntry.getKey(), testEntry.getValue().getStartTime());
             switch (testEntry.getValue().getStatus()) {
                 case FAILURE:
                     listener.testFailed(testEntry.getKey(), testEntry.getValue().getStackTrace());
@@ -263,7 +263,10 @@
                     break;
             }
             if (!testEntry.getValue().getStatus().equals(TestStatus.INCOMPLETE)) {
-                listener.testEnded(testEntry.getKey(), testEntry.getValue().getMetrics());
+                listener.testEnded(
+                        testEntry.getKey(),
+                        testEntry.getValue().getEndTime(),
+                        testEntry.getValue().getMetrics());
             }
         }
     }
diff --git a/src/com/android/tradefed/util/SubprocessEventHelper.java b/src/com/android/tradefed/util/SubprocessEventHelper.java
index 50c37f2..ef38d05 100644
--- a/src/com/android/tradefed/util/SubprocessEventHelper.java
+++ b/src/com/android/tradefed/util/SubprocessEventHelper.java
@@ -40,6 +40,8 @@
     private static final String TESTCOUNT_KEY = "testCount";
     private static final String TIME_KEY = "time";
     private static final String REASON_KEY = "reason";
+    private static final String START_TIME = "start_time";
+    private static final String END_TIME = "end_time";
 
     private static final String DATA_NAME_KEY = "dataName";
     private static final String DATA_TYPE_KEY = "dataType";
@@ -181,9 +183,7 @@
         }
     }
 
-    /**
-     * Base Helper for testStarted and TestIgnored information.
-     */
+    /** Base Helper for TestIgnored information. */
     public static class BaseTestEventInfo {
         public String mClassName = null;
         public String mTestName = null;
@@ -222,9 +222,36 @@
         }
     }
 
-    /**
-     * Helper for testFailed information.
-     */
+    /** Helper for testStarted information */
+    public static class TestStartedEventInfo extends BaseTestEventInfo {
+        public Long mStartTime = null;
+
+        public TestStartedEventInfo(String className, String testName, Long startTime) {
+            super(className, testName);
+            mStartTime = startTime;
+        }
+
+        public TestStartedEventInfo(JSONObject jsonObject) throws JSONException {
+            super(jsonObject);
+            if (jsonObject.has(START_TIME)) {
+                mStartTime = jsonObject.getLong(START_TIME);
+            }
+            jsonObject.remove(START_TIME);
+        }
+
+        @Override
+        protected JSONObject getNewJson() {
+            JSONObject json = new JSONObject();
+            try {
+                json.put(START_TIME, mStartTime);
+            } catch (JSONException e) {
+                CLog.e(e);
+            }
+            return json;
+        }
+    }
+
+    /** Helper for testFailed information. */
     public static class FailedTestEventInfo extends BaseTestEventInfo {
         public String mTrace = null;
 
@@ -258,15 +285,38 @@
      */
     public static class TestEndedEventInfo extends BaseTestEventInfo {
         public Map<String, String> mRunMetrics = null;
+        public Long mEndTime = null;
 
         public TestEndedEventInfo(String className, String testName,
                 Map<String, String> runMetrics) {
             super(className, testName);
             mRunMetrics = runMetrics;
+            mEndTime = System.currentTimeMillis();
         }
 
+        /**
+         * Create an event object to represent the testEnded callback.
+         *
+         * @param className the classname of the tests
+         * @param testName the name of the tests
+         * @param endTime the timestamp at which the test ended (from {@link
+         *     System#currentTimeMillis()})
+         * @param runMetrics the metrics reported by the test.
+         */
+        public TestEndedEventInfo(
+                String className, String testName, Long endTime, Map<String, String> runMetrics) {
+            super(className, testName);
+            mEndTime = endTime;
+            mRunMetrics = runMetrics;
+        }
+
+        /** Create and populate and event object for testEnded from a JSON. */
         public TestEndedEventInfo(JSONObject jsonObject) throws JSONException {
             super(jsonObject);
+            if (jsonObject.has(END_TIME)) {
+                mEndTime = jsonObject.getLong(END_TIME);
+            }
+            jsonObject.remove(END_TIME);
             Iterator<?> i = jsonObject.keys();
             mRunMetrics = new HashMap<String, String>();
             while(i.hasNext()) {
@@ -277,11 +327,18 @@
 
         @Override
         protected JSONObject getNewJson() {
+            JSONObject json;
             if (mRunMetrics != null) {
-                return new JSONObject(mRunMetrics);
+                json = new JSONObject(mRunMetrics);
             } else {
-                return new JSONObject();
+                json = new JSONObject();
             }
+            try {
+                json.put(END_TIME, mEndTime);
+            } catch (JSONException e) {
+                CLog.e(e);
+            }
+            return json;
         }
     }
 
diff --git a/src/com/android/tradefed/util/SubprocessTestResultsParser.java b/src/com/android/tradefed/util/SubprocessTestResultsParser.java
index ae4a408..3e085a5 100644
--- a/src/com/android/tradefed/util/SubprocessTestResultsParser.java
+++ b/src/com/android/tradefed/util/SubprocessTestResultsParser.java
@@ -29,6 +29,7 @@
 import com.android.tradefed.util.SubprocessEventHelper.TestRunEndedEventInfo;
 import com.android.tradefed.util.SubprocessEventHelper.TestRunFailedEventInfo;
 import com.android.tradefed.util.SubprocessEventHelper.TestRunStartedEventInfo;
+import com.android.tradefed.util.SubprocessEventHelper.TestStartedEventInfo;
 
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -334,9 +335,13 @@
     private class TestStartedEventHandler implements EventHandler {
         @Override
         public void handleEvent(String eventJson) throws JSONException {
-            BaseTestEventInfo bti = new BaseTestEventInfo(new JSONObject(eventJson));
+            TestStartedEventInfo bti = new TestStartedEventInfo(new JSONObject(eventJson));
             currentTest = new TestIdentifier(bti.mClassName, bti.mTestName);
-            mListener.testStarted(currentTest);
+            if (bti.mStartTime != null) {
+                mListener.testStarted(currentTest, bti.mStartTime);
+            } else {
+                mListener.testStarted(currentTest);
+            }
         }
     }
 
@@ -355,7 +360,11 @@
             try {
                 TestEndedEventInfo tei = new TestEndedEventInfo(new JSONObject(eventJson));
                 checkCurrentTestId(tei.mClassName, tei.mTestName);
-                mListener.testEnded(currentTest, tei.mRunMetrics);
+                if (tei.mEndTime != null) {
+                    mListener.testEnded(currentTest, tei.mEndTime, tei.mRunMetrics);
+                } else {
+                    mListener.testEnded(currentTest, tei.mRunMetrics);
+                }
             } finally {
                 currentTest = null;
             }
diff --git a/tests/res/testdata/subprocess1.txt b/tests/res/testdata/subprocess1.txt
index a3c24fe..4a9a272 100644
--- a/tests/res/testdata/subprocess1.txt
+++ b/tests/res/testdata/subprocess1.txt
@@ -1,13 +1,13 @@
 03-22 14:04:02 E/SubprocessResultsReporter: TEST_RUN_STARTED {"testCount":4,"runName":"arm64-v8a CtsGestureTestCases"}
-03-22 14:04:02 E/SubprocessResultsReporter: TEST_STARTED {"className":"android.gesture.cts.GestureLibraryTest","testName":"testIsReadOnly"}
-03-22 14:04:02 E/SubprocessResultsReporter: TEST_ENDED {"className":"android.gesture.cts.GestureLibraryTest","testName":"testIsReadOnly"}
-03-22 14:04:02 E/SubprocessResultsReporter: TEST_STARTED {"className":"android.gesture.cts.GestureLibraryTest","testName":"testGetGestureEntries"}
+03-22 14:04:02 E/SubprocessResultsReporter: TEST_STARTED {"start_time":1489160957284,"className":"android.gesture.cts.GestureLibraryTest","testName":"testIsReadOnly"}
+03-22 14:04:02 E/SubprocessResultsReporter: TEST_ENDED {"end_time":1489160958359,"className":"android.gesture.cts.GestureLibraryTest","testName":"testIsReadOnly"}
+03-22 14:04:02 E/SubprocessResultsReporter: TEST_STARTED {"start_time":1489160957284,"className":"android.gesture.cts.GestureLibraryTest","testName":"testGetGestureEntries"}
 03-22 14:04:02 E/SubprocessResultsReporter: TEST_FAILED {"className":"android.gesture.cts.GestureLibraryTest","testName":"testGetGestureEntries", "trace":"Failed deal with it"}
-03-22 14:04:02 E/SubprocessResultsReporter: TEST_ENDED {"className":"android.gesture.cts.GestureLibraryTest","testName":"testGetGestureEntries"}
-03-22 14:04:02 E/SubprocessResultsReporter: TEST_STARTED {"className":"android.gesture.cts.GestureLibraryTest","testName":"testGetGestures"}
+03-22 14:04:02 E/SubprocessResultsReporter: TEST_ENDED {"end_time":1489160958359,"className":"android.gesture.cts.GestureLibraryTest","testName":"testGetGestureEntries"}
+03-22 14:04:02 E/SubprocessResultsReporter: TEST_STARTED {"start_time":1489160957284,"className":"android.gesture.cts.GestureLibraryTest","testName":"testGetGestures"}
 03-22 14:04:02 E/SubprocessResultsReporter: TEST_IGNORED {"className":"android.gesture.cts.GestureLibraryTest","testName":"testGetGestures"}
-03-22 14:04:02 E/SubprocessResultsReporter: TEST_ENDED {"className":"android.gesture.cts.GestureLibraryTest","testName":"testGetGestures"}
-03-22 14:04:02 E/SubprocessResultsReporter: TEST_STARTED {"className":"android.gesture.cts.GestureLibraryTest","testName":"testRecognize"}
+03-22 14:04:02 E/SubprocessResultsReporter: TEST_ENDED {"end_time":1489160958359,"className":"android.gesture.cts.GestureLibraryTest","testName":"testGetGestures"}
+03-22 14:04:02 E/SubprocessResultsReporter: TEST_STARTED {"start_time":1489160957285,"className":"android.gesture.cts.GestureLibraryTest","testName":"testRecognize"}
 03-22 14:04:02 E/SubprocessResultsReporter: TEST_ASSUMPTION_FAILURE {"className":"android.gesture.cts.GestureLibraryTest","testName":"testRecognize", "trace":"assume I fail..."}
-03-22 14:04:02 E/SubprocessResultsReporter: TEST_ENDED {"className":"android.gesture.cts.GestureLibraryTest","testName":"testRecognize"}
+03-22 14:04:02 E/SubprocessResultsReporter: TEST_ENDED {"end_time":1489160958359,"className":"android.gesture.cts.GestureLibraryTest","testName":"testRecognize"}
 03-22 14:04:02 E/SubprocessResultsReporter: TEST_RUN_ENDED {"time":259}
diff --git a/tests/res/testdata/subprocess2.txt b/tests/res/testdata/subprocess2.txt
index 128ab83..b20d83c 100644
--- a/tests/res/testdata/subprocess2.txt
+++ b/tests/res/testdata/subprocess2.txt
@@ -1,12 +1,12 @@
 03-22 14:04:02 E/SubprocessResultsReporter: TEST_RUN_STARTED {"testCount":4,"runName":"arm64-v8a CtsGestureTestCases"}
-03-22 14:04:02 E/SubprocessResultsReporter: TEST_STARTED {"className":"android.gesture.cts.GestureLibraryTest","testName":"testIsReadOnly"}
-03-22 14:04:02 E/SubprocessResultsReporter: TEST_ENDED {"className":"android.gesture.cts.GestureLibraryTest","testName":"testIsReadOnly"}
-03-22 14:04:02 E/SubprocessResultsReporter: TEST_STARTED {"className":"android.gesture.cts.GestureLibraryTest","testName":"testGetGestureEntries"}
-03-22 14:04:02 E/SubprocessResultsReporter: TEST_STARTED {"className":"android.gesture.cts.GestureLibraryTest","testName":"testGetGestures"}
+03-22 14:04:02 E/SubprocessResultsReporter: TEST_STARTED {"start_time":1489160957285,"className":"android.gesture.cts.GestureLibraryTest","testName":"testIsReadOnly"}
+03-22 14:04:02 E/SubprocessResultsReporter: TEST_ENDED {"end_time":1489160958359,"className":"android.gesture.cts.GestureLibraryTest","testName":"testIsReadOnly"}
+03-22 14:04:02 E/SubprocessResultsReporter: TEST_STARTED {"start_time":1489160957285,"className":"android.gesture.cts.GestureLibraryTest","testName":"testGetGestureEntries"}
+03-22 14:04:02 E/SubprocessResultsReporter: TEST_STARTED {"start_time":1489160957285,"className":"android.gesture.cts.GestureLibraryTest","testName":"testGetGestures"}
 03-22 14:04:02 E/SubprocessResultsReporter: TEST_IGNORED {"className":"android.gesture.cts.GestureLibraryTest","testName":"testGetGestures"}
-03-22 14:04:02 E/SubprocessResultsReporter: TEST_ENDED {"className":"android.gesture.cts.GestureLibraryTest","testName":"testGetGestures"}
-03-22 14:04:02 E/SubprocessResultsReporter: TEST_STARTED {"className":"android.gesture.cts.GestureLibraryTest","testName":"testRecognize"}
+03-22 14:04:02 E/SubprocessResultsReporter: TEST_ENDED {"end_time":1489160958359,"className":"android.gesture.cts.GestureLibraryTest","testName":"testGetGestures"}
+03-22 14:04:02 E/SubprocessResultsReporter: TEST_STARTED {"start_time":1489160957285,"className":"android.gesture.cts.GestureLibraryTest","testName":"testRecognize"}
 03-22 14:04:02 E/SubprocessResultsReporter: TEST_ASSUMPTION_FAILURE {"className":"android.gesture.cts.GestureLibraryTest","testName":"testRecognize", "trace":"assume I fail..."}
-03-22 14:04:02 E/SubprocessResultsReporter: TEST_ENDED {"className":"android.gesture.cts.GestureLibraryTest","testName":"testRecognize"}
+03-22 14:04:02 E/SubprocessResultsReporter: TEST_ENDED {"end_time":1489160958359,"className":"android.gesture.cts.GestureLibraryTest","testName":"testRecognize"}
 03-22 14:04:02 E/SubprocessResultsReporter: TEST_RUN_FAILED {"reason":"damn I failed"}
 03-22 14:04:02 E/SubprocessResultsReporter: TEST_RUN_ENDED {"time":259, "stats":"additional stuff"}
diff --git a/tests/src/com/android/tradefed/testtype/InstrumentationFileTestTest.java b/tests/src/com/android/tradefed/testtype/InstrumentationFileTestTest.java
index e2dec55..4c55eba 100644
--- a/tests/src/com/android/tradefed/testtype/InstrumentationFileTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/InstrumentationFileTestTest.java
@@ -123,8 +123,9 @@
 
         // mock successful test run lifecycle
         mMockListener.testRunStarted(TEST_PACKAGE_VALUE, 1);
-        mMockListener.testStarted(test);
-        mMockListener.testEnded(test, Collections.EMPTY_MAP);
+        mMockListener.testStarted(EasyMock.eq(test), EasyMock.anyLong());
+        mMockListener.testEnded(
+                EasyMock.eq(test), EasyMock.anyLong(), EasyMock.eq(Collections.EMPTY_MAP));
         mMockListener.testRunEnded(0, Collections.EMPTY_MAP);
 
         EasyMock.replay(mMockListener, mMockTestDevice);
@@ -202,20 +203,23 @@
         // First run:
         mMockListener.testRunStarted(TEST_PACKAGE_VALUE, 2);
         // expect test1 to start and finish successfully
-        mMockListener.testStarted(test1);
-        mMockListener.testEnded(test1, Collections.EMPTY_MAP);
+        mMockListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+        mMockListener.testEnded(
+                EasyMock.eq(test1), EasyMock.anyLong(), EasyMock.eq(Collections.EMPTY_MAP));
         mMockListener.testRunEnded(1, Collections.EMPTY_MAP);
         // expect test2 to start but never finish
-        mMockListener.testStarted(test2);
+        mMockListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
         // Second run:
         mMockListener.testRunStarted(TEST_PACKAGE_VALUE, 2);
         // expect test3 to start and finish successfully
-        mMockListener.testStarted(test3);
-        mMockListener.testEnded(test3, Collections.EMPTY_MAP);
+        mMockListener.testStarted(EasyMock.eq(test3), EasyMock.anyLong());
+        mMockListener.testEnded(
+                EasyMock.eq(test3), EasyMock.anyLong(), EasyMock.eq(Collections.EMPTY_MAP));
         mMockListener.testRunEnded(1, Collections.EMPTY_MAP);
         // expect to rerun test2 successfully
-        mMockListener.testStarted(test2);
-        mMockListener.testEnded(test2, Collections.EMPTY_MAP);
+        mMockListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+        mMockListener.testEnded(
+                EasyMock.eq(test2), EasyMock.anyLong(), EasyMock.eq(Collections.EMPTY_MAP));
         mMockListener.testRunEnded(1, Collections.EMPTY_MAP);
 
         EasyMock.replay(mMockListener, mMockTestDevice);
@@ -299,21 +303,23 @@
         // First run:
         mMockListener.testRunStarted(TEST_PACKAGE_VALUE, 2);
         // expect test1 and test 2 to start but never finish
-        mMockListener.testStarted(test1);
-        mMockListener.testStarted(test2);
+        mMockListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+        mMockListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
 
         // re-run test1 and test2 serially
         // first serial re-run:
         mMockListener.testRunStarted(TEST_PACKAGE_VALUE, 1);
         // expect test1 to start and finish successfully
-        mMockListener.testStarted(test1);
-        mMockListener.testEnded(test1, Collections.EMPTY_MAP);
+        mMockListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+        mMockListener.testEnded(
+                EasyMock.eq(test1), EasyMock.anyLong(), EasyMock.eq(Collections.EMPTY_MAP));
         mMockListener.testRunEnded(1, Collections.EMPTY_MAP);
         // first serial re-run:
         mMockListener.testRunStarted(TEST_PACKAGE_VALUE, 1);
         // expect test2 to start and finish successfully
-        mMockListener.testStarted(test2);
-        mMockListener.testEnded(test2, Collections.EMPTY_MAP);
+        mMockListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+        mMockListener.testEnded(
+                EasyMock.eq(test2), EasyMock.anyLong(), EasyMock.eq(Collections.EMPTY_MAP));
         mMockListener.testRunEnded(1, Collections.EMPTY_MAP);
 
         EasyMock.replay(mMockListener, mMockTestDevice);
@@ -371,8 +377,8 @@
         // First run:
         mMockListener.testRunStarted(TEST_PACKAGE_VALUE, 2);
         // expect test1 and test 2 to start but never finish
-        mMockListener.testStarted(test1);
-        mMockListener.testStarted(test2);
+        mMockListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+        mMockListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
 
         EasyMock.replay(mMockListener, mMockTestDevice);
         mInstrumentationFileTest.run(mMockListener);
@@ -476,24 +482,27 @@
 
         // First run:
         mMockListener.testRunStarted(TEST_PACKAGE_VALUE, 6);
-        mMockListener.testStarted(test1);
-        mMockListener.testEnded(test1, Collections.EMPTY_MAP);
+        mMockListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+        mMockListener.testEnded(
+                EasyMock.eq(test1), EasyMock.anyLong(), EasyMock.eq(Collections.EMPTY_MAP));
         mMockListener.testRunEnded(1, Collections.EMPTY_MAP);
-        mMockListener.testStarted(test2);
+        mMockListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
 
         // Second run:
         mMockListener.testRunStarted(TEST_PACKAGE_VALUE, 5);
-        mMockListener.testStarted(test2);
-        mMockListener.testEnded(test2, Collections.EMPTY_MAP);
+        mMockListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+        mMockListener.testEnded(
+                EasyMock.eq(test2), EasyMock.anyLong(), EasyMock.eq(Collections.EMPTY_MAP));
         mMockListener.testRunEnded(1, Collections.EMPTY_MAP);
-        mMockListener.testStarted(test3);
+        mMockListener.testStarted(EasyMock.eq(test3), EasyMock.anyLong());
 
         // Third run:
         mMockListener.testRunStarted(TEST_PACKAGE_VALUE, 4);
-        mMockListener.testStarted(test3);
-        mMockListener.testEnded(test3, Collections.EMPTY_MAP);
+        mMockListener.testStarted(EasyMock.eq(test3), EasyMock.anyLong());
+        mMockListener.testEnded(
+                EasyMock.eq(test3), EasyMock.anyLong(), EasyMock.eq(Collections.EMPTY_MAP));
         mMockListener.testRunEnded(1, Collections.EMPTY_MAP);
-        mMockListener.testStarted(test4);
+        mMockListener.testStarted(EasyMock.eq(test4), EasyMock.anyLong());
 
         // MAX_ATTEMPTS is 3, so there will be no forth run.
 
diff --git a/tests/src/com/android/tradefed/testtype/InstrumentationSerialTestTest.java b/tests/src/com/android/tradefed/testtype/InstrumentationSerialTestTest.java
index e546056..8c088d0 100644
--- a/tests/src/com/android/tradefed/testtype/InstrumentationSerialTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/InstrumentationSerialTestTest.java
@@ -82,8 +82,9 @@
             }
         };
         mMockListener.testRunStarted(packageName, 1);
-        mMockListener.testStarted(test);
-        mMockListener.testEnded(test, Collections.EMPTY_MAP);
+        mMockListener.testStarted(EasyMock.eq(test), EasyMock.anyLong());
+        mMockListener.testEnded(
+                EasyMock.eq(test), EasyMock.anyLong(), EasyMock.eq(Collections.EMPTY_MAP));
         mMockListener.testRunEnded(0, Collections.EMPTY_MAP);
 
         EasyMock.replay(mMockListener, mMockTestDevice);
@@ -133,10 +134,11 @@
         EasyMock.expectLastCall().times(expectedAttempts);
 
         // now expect test to be marked as failed
-        mMockListener.testStarted(test);
+        mMockListener.testStarted(EasyMock.eq(test), EasyMock.anyLong());
         mMockListener.testFailed(EasyMock.eq(test),
                 EasyMock.contains(runFailureMsg));
-        mMockListener.testEnded(test, Collections.EMPTY_MAP);
+        mMockListener.testEnded(
+                EasyMock.eq(test), EasyMock.anyLong(), EasyMock.eq(Collections.EMPTY_MAP));
 
         EasyMock.replay(mMockListener, mMockTestDevice);
         mInstrumentationSerialTest.run(mMockListener);
diff --git a/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java b/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java
index 0b72e37..19d10fe 100644
--- a/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java
@@ -448,13 +448,15 @@
 
         // expect both TEST1 and TEST2 to be executed
         mMockListener.testRunStarted(TEST_PACKAGE_VALUE, 2);
-        mMockListener.testStarted(TEST1);
-        mMockListener.testEnded(TEST1, EMPTY_STRING_MAP);
+        mMockListener.testStarted(EasyMock.eq(TEST1), EasyMock.anyLong());
+        mMockListener.testEnded(
+                EasyMock.eq(TEST1), EasyMock.anyLong(), EasyMock.eq(EMPTY_STRING_MAP));
         mMockListener.testRunFailed(RUN_ERROR_MSG);
         mMockListener.testRunEnded(1, EMPTY_STRING_MAP);
         mMockListener.testRunStarted(TEST_PACKAGE_VALUE, 1);
-        mMockListener.testStarted(TEST2);
-        mMockListener.testEnded(TEST2, EMPTY_STRING_MAP);
+        mMockListener.testStarted(EasyMock.eq(TEST2), EasyMock.anyLong());
+        mMockListener.testEnded(
+                EasyMock.eq(TEST2), EasyMock.anyLong(), EasyMock.eq(EMPTY_STRING_MAP));
         mMockListener.testRunEnded(1, EMPTY_STRING_MAP);
     }
 
diff --git a/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java b/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
index f2317c9..bb07650 100644
--- a/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
@@ -107,8 +107,8 @@
                     throw mRunException;
                 }
                 TestIdentifier test = new TestIdentifier(EMPTY_CONFIG, EMPTY_CONFIG);
-                listener.testStarted(test);
-                listener.testEnded(test, Collections.emptyMap());
+                listener.testStarted(test, 0);
+                listener.testEnded(test, 5, Collections.emptyMap());
             } finally {
                 listener.testRunEnded(0, Collections.emptyMap());
             }
@@ -145,8 +145,8 @@
     private void expectTestRun(ITestInvocationListener listener) {
         listener.testRunStarted(TEST_CONFIG_NAME, 1);
         TestIdentifier test = new TestIdentifier(EMPTY_CONFIG, EMPTY_CONFIG);
-        listener.testStarted(test);
-        listener.testEnded(test, Collections.emptyMap());
+        listener.testStarted(test, 0);
+        listener.testEnded(test, 5, Collections.emptyMap());
         listener.testRunEnded(0, Collections.emptyMap());
     }
 
diff --git a/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java b/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java
index 60e4d9b..4d76928 100644
--- a/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java
@@ -214,8 +214,11 @@
                 EasyMock.eq(mMockDevice), EasyMock.eq(mMockBuildInfo), EasyMock.isNull());
         mMockListener.testRunStarted(MODULE_NAME, testCount);
         for (int i = 0; i < testCount; i++) {
-            mMockListener.testStarted(EasyMock.anyObject());
-            mMockListener.testEnded(EasyMock.anyObject(), EasyMock.anyObject());
+            mMockListener.testStarted((TestIdentifier) EasyMock.anyObject(), EasyMock.anyLong());
+            mMockListener.testEnded(
+                    (TestIdentifier) EasyMock.anyObject(),
+                    EasyMock.anyLong(),
+                    EasyMock.anyObject());
         }
         mMockListener.testRunEnded(0, Collections.emptyMap());
         replayMocks();
diff --git a/tests/src/com/android/tradefed/testtype/suite/TestFailureListenerTest.java b/tests/src/com/android/tradefed/testtype/suite/TestFailureListenerTest.java
index 98e65fe..be0c82e 100644
--- a/tests/src/com/android/tradefed/testtype/suite/TestFailureListenerTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/TestFailureListenerTest.java
@@ -22,7 +22,6 @@
 import com.android.tradefed.result.ITestInvocationListener;
 import com.android.tradefed.result.InputStreamSource;
 import com.android.tradefed.result.LogDataType;
-import com.android.tradefed.testtype.suite.TestFailureListener;
 import com.android.tradefed.util.IRunUtil;
 
 import org.easymock.EasyMock;
@@ -33,9 +32,7 @@
 
 import java.util.Collections;
 
-/**
- * Unit tests for {@link TestFailureListener}
- */
+/** Unit tests for {@link com.android.tradefed.testtype.suite.TestFailureListener} */
 @RunWith(JUnit4.class)
 public class TestFailureListenerTest {
 
@@ -46,7 +43,7 @@
     @Before
     public void setUp() {
         mMockListener = EasyMock.createMock(ITestInvocationListener.class);
-        mMockDevice = EasyMock.createMock(ITestDevice.class);
+        mMockDevice = EasyMock.createStrictMock(ITestDevice.class);
         EasyMock.expect(mMockDevice.getSerialNumber()).andStubReturn("SERIAL");
         // Create base failure listener with all option ON and default logcat size.
         mFailureListener = new TestFailureListener(mMockListener, mMockDevice,
diff --git a/tests/src/com/android/tradefed/util/SubprocessTestResultsParserTest.java b/tests/src/com/android/tradefed/util/SubprocessTestResultsParserTest.java
index 898728e..c4c8d0f 100644
--- a/tests/src/com/android/tradefed/util/SubprocessTestResultsParserTest.java
+++ b/tests/src/com/android/tradefed/util/SubprocessTestResultsParserTest.java
@@ -79,10 +79,12 @@
         ITestInvocationListener mockRunListener =
                 EasyMock.createMock(ITestInvocationListener.class);
         mockRunListener.testRunStarted("arm64-v8a CtsGestureTestCases", 4);
-        mockRunListener.testStarted((TestIdentifier)EasyMock.anyObject());
+        mockRunListener.testStarted((TestIdentifier) EasyMock.anyObject(), EasyMock.anyLong());
         EasyMock.expectLastCall().times(4);
-        mockRunListener.testEnded((TestIdentifier)EasyMock.anyObject(),
-                (Map<String, String>)EasyMock.anyObject());
+        mockRunListener.testEnded(
+                (TestIdentifier) EasyMock.anyObject(),
+                EasyMock.anyLong(),
+                (Map<String, String>) EasyMock.anyObject());
         EasyMock.expectLastCall().times(4);
         mockRunListener.testRunEnded(EasyMock.anyLong(),
                 (Map<String, String>) EasyMock.anyObject());
@@ -118,10 +120,12 @@
         ITestInvocationListener mockRunListener =
                 EasyMock.createMock(ITestInvocationListener.class);
         mockRunListener.testRunStarted("arm64-v8a CtsGestureTestCases", 4);
-        mockRunListener.testStarted((TestIdentifier)EasyMock.anyObject());
+        mockRunListener.testStarted((TestIdentifier) EasyMock.anyObject(), EasyMock.anyLong());
         EasyMock.expectLastCall().times(4);
-        mockRunListener.testEnded((TestIdentifier)EasyMock.anyObject(),
-                (Map<String, String>)EasyMock.anyObject());
+        mockRunListener.testEnded(
+                (TestIdentifier) EasyMock.anyObject(),
+                EasyMock.anyLong(),
+                (Map<String, String>) EasyMock.anyObject());
         EasyMock.expectLastCall().times(3);
         mockRunListener.testRunFailed((String)EasyMock.anyObject());
         EasyMock.expectLastCall().times(1);
@@ -155,8 +159,42 @@
         ITestInvocationListener mockRunListener =
                 EasyMock.createMock(ITestInvocationListener.class);
         mockRunListener.testRunStarted("arm64-v8a CtsGestureTestCases", 4);
-        mockRunListener.testEnded((TestIdentifier)EasyMock.anyObject(),
-                (Map<String, String>)EasyMock.anyObject());
+        mockRunListener.testEnded(
+                (TestIdentifier) EasyMock.anyObject(),
+                EasyMock.anyLong(),
+                (Map<String, String>) EasyMock.anyObject());
+        EasyMock.expectLastCall().times(1);
+        EasyMock.replay(mockRunListener);
+        File tmp = FileUtil.createTempFile("sub", "unit");
+        SubprocessTestResultsParser resultParser = null;
+        try {
+            resultParser =
+                    new SubprocessTestResultsParser(mockRunListener, new InvocationContext());
+            String startRun =
+                    "TEST_RUN_STARTED {\"testCount\":4,\"runName\":\"arm64-v8a "
+                            + "CtsGestureTestCases\"}\n";
+            FileUtil.writeToFile(startRun, tmp, true);
+            String testEnded =
+                    "03-22 14:04:02 E/SubprocessResultsReporter: TEST_ENDED "
+                            + "{\"end_time\":1489160958359,\"className\":\"android.gesture.cts."
+                            + "GestureLibraryTest\",\"testName\":\"testGetGestures\",\"extra\":\""
+                            + "data\"}\n";
+            FileUtil.writeToFile(testEnded, tmp, true);
+            resultParser.parseFile(tmp);
+            EasyMock.verify(mockRunListener);
+        } finally {
+            StreamUtil.close(resultParser);
+            FileUtil.deleteFile(tmp);
+        }
+    }
+
+    /** Tests the parser for a cases when there is no start/end time stamp. */
+    public void testParse_noTimeStamp() throws Exception {
+        ITestInvocationListener mockRunListener =
+                EasyMock.createMock(ITestInvocationListener.class);
+        mockRunListener.testRunStarted("arm64-v8a CtsGestureTestCases", 4);
+        mockRunListener.testStarted(EasyMock.anyObject());
+        mockRunListener.testEnded((TestIdentifier) EasyMock.anyObject(), EasyMock.anyObject());
         EasyMock.expectLastCall().times(1);
         EasyMock.replay(mockRunListener);
         File tmp = FileUtil.createTempFile("sub", "unit");
@@ -167,9 +205,16 @@
             String startRun = "TEST_RUN_STARTED {\"testCount\":4,\"runName\":\"arm64-v8a "
                     + "CtsGestureTestCases\"}\n";
             FileUtil.writeToFile(startRun, tmp, true);
-            String testEnded = "03-22 14:04:02 E/SubprocessResultsReporter: TEST_ENDED "
-                    + "{\"className\":\"android.gesture.cts.GestureLibraryTest\",\"testName\":"
-                    + "\"testGetGestures\",\"extra\":\"data\"}\n";
+            String testStarted =
+                    "03-22 14:04:02 E/SubprocessResultsReporter: TEST_STARTED "
+                            + "{\"className\":\"android.gesture.cts."
+                            + "GestureLibraryTest\",\"testName\":\"testGetGestures\"}\n";
+            FileUtil.writeToFile(testStarted, tmp, true);
+            String testEnded =
+                    "03-22 14:04:02 E/SubprocessResultsReporter: TEST_ENDED "
+                            + "{\"className\":\"android.gesture.cts."
+                            + "GestureLibraryTest\",\"testName\":\"testGetGestures\",\"extra\":\""
+                            + "data\"}\n";
             FileUtil.writeToFile(testEnded, tmp, true);
             resultParser.parseFile(tmp);
             EasyMock.verify(mockRunListener);
@@ -222,8 +267,10 @@
         ITestInvocationListener mockRunListener =
                 EasyMock.createMock(ITestInvocationListener.class);
         mockRunListener.testRunStarted("arm64-v8a CtsGestureTestCases", 4);
-        mockRunListener.testEnded((TestIdentifier)EasyMock.anyObject(),
-                (Map<String, String>)EasyMock.anyObject());
+        mockRunListener.testEnded(
+                (TestIdentifier) EasyMock.anyObject(),
+                EasyMock.anyLong(),
+                (Map<String, String>) EasyMock.anyObject());
         EasyMock.expectLastCall().times(1);
         EasyMock.replay(mockRunListener);
         SubprocessTestResultsParser resultParser = null;
@@ -240,9 +287,11 @@
                     + "CtsGestureTestCases\"}\n";
             out.print(startRun);
             out.flush();
-            String testEnded = "03-22 14:04:02 E/SubprocessResultsReporter: TEST_ENDED "
-                    + "{\"className\":\"android.gesture.cts.GestureLibraryTest\",\"testName\":"
-                    + "\"testGetGestures\",\"extra\":\"data\"}\n";
+            String testEnded =
+                    "03-22 14:04:02 E/SubprocessResultsReporter: TEST_ENDED "
+                            + "{\"end_time\":1489160958359,\"className\":\"android.gesture.cts."
+                            + "GestureLibraryTest\",\"testName\":\"testGetGestures\",\"extra\":\""
+                            + "data\"}\n";
             out.print(testEnded);
             out.flush();
             StreamUtil.close(socket);