Merge "Allow legacy subprocess reporter to use TF/CTS 9+ methods if possible"
diff --git a/invocation_interfaces/com/android/tradefed/result/ITestInvocationListener.java b/invocation_interfaces/com/android/tradefed/result/ITestInvocationListener.java
index e6f5357..ed8e1b7 100644
--- a/invocation_interfaces/com/android/tradefed/result/ITestInvocationListener.java
+++ b/invocation_interfaces/com/android/tradefed/result/ITestInvocationListener.java
@@ -73,6 +73,23 @@
     default public void invocationFailed(Throwable cause) { }
 
     /**
+     * Reports an incomplete invocation due to some error condition.
+     *
+     * <p>Will be automatically called by the TradeFederation framework.
+     *
+     * @param failure the {@link FailureDescription} describing the cause of the failure
+     */
+    public default void invocationFailed(FailureDescription failure) {
+        if (failure.getCause() != null) {
+            invocationFailed(failure.getCause());
+        } else {
+            invocationFailed(
+                    new RuntimeException(
+                            String.format("ConvertedFailure: %s", failure.getErrorMessage())));
+        }
+    }
+
+    /**
      * Allows the InvocationListener to return a summary.
      *
      * @return A {@link TestSummary} summarizing the run, or null
diff --git a/src/com/android/tradefed/device/metric/BaseDeviceMetricCollector.java b/src/com/android/tradefed/device/metric/BaseDeviceMetricCollector.java
index 4cea7b1..1f5be9d 100644
--- a/src/com/android/tradefed/device/metric/BaseDeviceMetricCollector.java
+++ b/src/com/android/tradefed/device/metric/BaseDeviceMetricCollector.java
@@ -204,6 +204,11 @@
     }
 
     @Override
+    public final void invocationFailed(FailureDescription failure) {
+        mForwarder.invocationFailed(failure);
+    }
+
+    @Override
     public final void invocationEnded(long elapsedTime) {
         mForwarder.invocationEnded(elapsedTime);
     }
diff --git a/src/com/android/tradefed/postprocessor/BasePostProcessor.java b/src/com/android/tradefed/postprocessor/BasePostProcessor.java
index afa7b8a..7b2c464 100644
--- a/src/com/android/tradefed/postprocessor/BasePostProcessor.java
+++ b/src/com/android/tradefed/postprocessor/BasePostProcessor.java
@@ -107,6 +107,11 @@
     }
 
     @Override
+    public final void invocationFailed(FailureDescription failure) {
+        mForwarder.invocationFailed(failure);
+    }
+
+    @Override
     public final void invocationEnded(long elapsedTime) {
         mForwarder.invocationEnded(elapsedTime);
     }
diff --git a/src/com/android/tradefed/result/ResultForwarder.java b/src/com/android/tradefed/result/ResultForwarder.java
index 750b125..ba38e54 100644
--- a/src/com/android/tradefed/result/ResultForwarder.java
+++ b/src/com/android/tradefed/result/ResultForwarder.java
@@ -118,6 +118,21 @@
         }
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public void invocationFailed(FailureDescription failure) {
+        for (ITestInvocationListener listener : mListeners) {
+            try {
+                listener.invocationFailed(failure);
+            } catch (RuntimeException e) {
+                CLog.e(
+                        "Exception while invoking %s#invocationFailed",
+                        listener.getClass().getName());
+                CLog.e(e);
+            }
+        }
+    }
+
     /**
      * {@inheritDoc}
      */
diff --git a/src/com/android/tradefed/retry/ResultAggregator.java b/src/com/android/tradefed/retry/ResultAggregator.java
index 306604c..42af517 100644
--- a/src/com/android/tradefed/retry/ResultAggregator.java
+++ b/src/com/android/tradefed/retry/ResultAggregator.java
@@ -119,6 +119,13 @@
 
     /** {@inheritDoc} */
     @Override
+    public void invocationFailed(FailureDescription failure) {
+        super.invocationFailed(failure);
+        mAllForwarder.invocationFailed(failure);
+    }
+
+    /** {@inheritDoc} */
+    @Override
     public void invocationEnded(long elapsedTime) {
         if (!mPureRunResultForAgg.isEmpty()) {
             for (String name : mPureRunResultForAgg.keySet()) {
diff --git a/test_result_interfaces/com/android/tradefed/result/FailureDescription.java b/test_result_interfaces/com/android/tradefed/result/FailureDescription.java
index 23d4994..1acb558 100644
--- a/test_result_interfaces/com/android/tradefed/result/FailureDescription.java
+++ b/test_result_interfaces/com/android/tradefed/result/FailureDescription.java
@@ -33,6 +33,8 @@
     private @Nullable ActionInProgress mActionInProgress = null;
     // Optional: A free-formed text that help debugging the failure
     private @Nullable String mDebugHelpMessage = null;
+    // Optional: The exception that triggered the failure
+    private @Nullable Throwable mCause = null;
 
     FailureDescription() {}
 
@@ -75,6 +77,17 @@
         return mDebugHelpMessage;
     }
 
+    /** Sets the exception that caused the failure if any. */
+    public FailureDescription setCause(Throwable cause) {
+        mCause = cause;
+        return this;
+    }
+
+    /** Returns the exception that caused the failure. Can be null. */
+    public @Nullable Throwable getCause() {
+        return mCause;
+    }
+
     /** Sets the error message. */
     public void setErrorMessage(String errorMessage) {
         mErrorMessage = errorMessage;
diff --git a/tests/src/com/android/tradefed/device/metric/BaseDeviceMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/BaseDeviceMetricCollectorTest.java
index ea2a0dc..fa4144a 100644
--- a/tests/src/com/android/tradefed/device/metric/BaseDeviceMetricCollectorTest.java
+++ b/tests/src/com/android/tradefed/device/metric/BaseDeviceMetricCollectorTest.java
@@ -112,7 +112,7 @@
         Mockito.verify(mMockListener, times(1)).testRunStopped(0L);
         Mockito.verify(mMockListener, times(1)).testRunEnded(0L, new HashMap<String, Metric>());
         Mockito.verify(mMockListener, times(1)).testModuleEnded();
-        Mockito.verify(mMockListener, times(1)).invocationFailed(Mockito.any());
+        Mockito.verify(mMockListener, times(1)).invocationFailed(Mockito.<Throwable>any());
         Mockito.verify(mMockListener, times(1)).invocationEnded(0L);
 
         Assert.assertSame(mMockListener, mBase.getInvocationListener());
@@ -198,7 +198,7 @@
         Mockito.verify(mMockListener, times(1)).testRunFailed("test run failed");
         Mockito.verify(mMockListener, times(1)).testRunStopped(0L);
         Mockito.verify(mMockListener, times(1)).testRunEnded(0L, new HashMap<String, Metric>());
-        Mockito.verify(mMockListener, times(1)).invocationFailed(Mockito.any());
+        Mockito.verify(mMockListener, times(1)).invocationFailed(Mockito.<Throwable>any());
         Mockito.verify(mMockListener, times(1)).invocationEnded(0L);
 
         Assert.assertSame(mMockListener, mBase.getInvocationListener());
diff --git a/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java b/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java
index 98ed019..9257c88 100644
--- a/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java
+++ b/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java
@@ -193,7 +193,7 @@
         mMockTestListener.invocationStarted(mContext);
         EasyMock.expect(mMockTestListener.getSummary()).andReturn(null);
         mMockLogSaver.invocationStarted(mContext);
-        mMockTestListener.invocationFailed(EasyMock.anyObject());
+        mMockTestListener.invocationFailed(EasyMock.<Throwable>anyObject());
         mMockTestListener.testLog(EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject());
         EasyMock.expect(
                         mMockLogSaver.saveLogData(
@@ -359,7 +359,7 @@
         mMockTestListener.invocationStarted(mContext);
         EasyMock.expect(mMockTestListener.getSummary()).andReturn(null);
         mMockLogSaver.invocationStarted(mContext);
-        mMockTestListener.invocationFailed(EasyMock.anyObject());
+        mMockTestListener.invocationFailed(EasyMock.<Throwable>anyObject());
         mMockTestListener.testLog(EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject());
         EasyMock.expect(
                         mMockLogSaver.saveLogData(
@@ -446,7 +446,7 @@
         mMockTestListener.invocationStarted(mContext);
         EasyMock.expect(mMockTestListener.getSummary()).andReturn(null);
         mMockLogSaver.invocationStarted(mContext);
-        mMockTestListener.invocationFailed(EasyMock.anyObject());
+        mMockTestListener.invocationFailed(EasyMock.<Throwable>anyObject());
         mMockTestListener.testLog(EasyMock.anyObject(), EasyMock.anyObject(), EasyMock.anyObject());
         EasyMock.expect(
                         mMockLogSaver.saveLogData(
diff --git a/tests/src/com/android/tradefed/invoker/TestInvocationTest.java b/tests/src/com/android/tradefed/invoker/TestInvocationTest.java
index 48a2590..8f4044c 100644
--- a/tests/src/com/android/tradefed/invoker/TestInvocationTest.java
+++ b/tests/src/com/android/tradefed/invoker/TestInvocationTest.java
@@ -1073,8 +1073,8 @@
         // invocationFailed
         if (!status.equals(InvocationStatus.SUCCESS)) {
             if (stubFailures) {
-                mMockTestListener.invocationFailed(EasyMock.anyObject());
-                mMockSummaryListener.invocationFailed(EasyMock.anyObject());
+                mMockTestListener.invocationFailed(EasyMock.<Throwable>anyObject());
+                mMockSummaryListener.invocationFailed(EasyMock.<Throwable>anyObject());
             } else {
                 mMockTestListener.invocationFailed(EasyMock.eq(throwable));
                 mMockSummaryListener.invocationFailed(EasyMock.eq(throwable));