Continue updating internal structures to FailureDescription

Also start forwarding testAssumption with
FailureDescription.

Test: unit tests
Bug: 147887662
Change-Id: I0f049aff2e3bac6738eaefa7d33cb3f56987b9f3
diff --git a/invocation_interfaces/com/android/tradefed/result/TestResult.java b/invocation_interfaces/com/android/tradefed/result/TestResult.java
index 078dab2..94298be 100644
--- a/invocation_interfaces/com/android/tradefed/result/TestResult.java
+++ b/invocation_interfaces/com/android/tradefed/result/TestResult.java
@@ -33,7 +33,7 @@
     public static final String IS_FLAKY = "is_flaky";
 
     private TestStatus mStatus;
-    private String mStackTrace;
+    private FailureDescription mFailureDescription;
     private Map<String, String> mMetrics;
     private HashMap<String, Metric> mProtoMetrics;
     private Map<String, LogFile> mLoggedFiles;
@@ -59,7 +59,18 @@
      * #getStatus()} is {@link TestStatus#PASSED}.
      */
     public String getStackTrace() {
-        return mStackTrace;
+        if (mFailureDescription == null) {
+            return null;
+        }
+        return mFailureDescription.toString();
+    }
+
+    /**
+     * Get the associated {@link FailureDescription}. Should be <code>null</code> if {@link
+     * #getStatus()} is {@link TestStatus#PASSED}.
+     */
+    public FailureDescription getFailure() {
+        return mFailureDescription;
     }
 
     /** Get the associated test metrics. */
@@ -123,8 +134,13 @@
     }
 
     /** Set the stack trace. */
-    public void setStackTrace(String trace) {
-        mStackTrace = trace;
+    public void setStackTrace(String stackTrace) {
+        mFailureDescription = FailureDescription.create(stackTrace);
+    }
+
+    /** Set the stack trace. */
+    public void setFailure(FailureDescription failureDescription) {
+        mFailureDescription = failureDescription;
     }
 
     /** Sets the end time */
@@ -134,7 +150,7 @@
 
     @Override
     public int hashCode() {
-        return Arrays.hashCode(new Object[] {mMetrics, mStackTrace, mStatus});
+        return Arrays.hashCode(new Object[] {mMetrics, mFailureDescription, mStatus});
     }
 
     @Override
@@ -150,7 +166,7 @@
         }
         TestResult other = (TestResult) obj;
         return equal(mMetrics, other.mMetrics)
-                && equal(mStackTrace, other.mStackTrace)
+                && equal(mFailureDescription.toString(), other.mFailureDescription.toString())
                 && equal(mStatus, other.mStatus);
     }
 
@@ -186,7 +202,7 @@
         long earliestStartTime = Long.MAX_VALUE;
         long latestEndTime = Long.MIN_VALUE;
 
-        List<String> errorMsg = new ArrayList<>();
+        List<FailureDescription> errors = new ArrayList<>();
         int pass = 0;
         int fail = 0;
         int assumption_failure = 0;
@@ -205,18 +221,18 @@
                     break;
                 case FAILURE:
                     fail++;
-                    if (attempt.getStackTrace() != null) {
-                        errorMsg.add(attempt.getStackTrace());
+                    if (attempt.getFailure() != null) {
+                        errors.add(attempt.getFailure());
                     }
                     break;
                 case INCOMPLETE:
                     incomplete++;
-                    errorMsg.add("incomplete test case result.");
+                    errors.add(FailureDescription.create("incomplete test case result."));
                     break;
                 case ASSUMPTION_FAILURE:
                     assumption_failure++;
-                    if (attempt.getStackTrace() != null) {
-                        errorMsg.add(attempt.getStackTrace());
+                    if (attempt.getFailure() != null) {
+                        errors.add(attempt.getFailure());
                     }
                     break;
                 case IGNORED:
@@ -263,10 +279,12 @@
                 }
                 break;
         }
-        if (errorMsg.isEmpty()) {
-            mergedResult.mStackTrace = null;
+        if (errors.isEmpty()) {
+            mergedResult.mFailureDescription = null;
+        } else if (errors.size() == 1) {
+            mergedResult.mFailureDescription = errors.get(0);
         } else {
-            mergedResult.mStackTrace = String.join("\n\n", errorMsg);
+            mergedResult.mFailureDescription = new MultiFailureDescription(errors);
         }
         mergedResult.setStartTime(earliestStartTime);
         mergedResult.setEndTime(latestEndTime);
diff --git a/invocation_interfaces/com/android/tradefed/result/TestRunResult.java b/invocation_interfaces/com/android/tradefed/result/TestRunResult.java
index dcc10cb..0d9af0e 100644
--- a/invocation_interfaces/com/android/tradefed/result/TestRunResult.java
+++ b/invocation_interfaces/com/android/tradefed/result/TestRunResult.java
@@ -277,23 +277,26 @@
         mTestResults.put(test, testResult);
     }
 
-    private void updateTestResult(TestDescription test, TestStatus status, String trace) {
+    private void updateTestResult(
+            TestDescription test, TestStatus status, FailureDescription failure) {
         TestResult r = mTestResults.get(test);
         if (r == null) {
             CLog.d("received test event without test start for %s", test);
             r = new TestResult();
         }
         r.setStatus(status);
-        r.setStackTrace(trace);
+        if (failure != null) {
+            r.setFailure(failure);
+        }
         addTestResult(test, r);
     }
 
     public void testFailed(TestDescription test, String trace) {
-        updateTestResult(test, TestStatus.FAILURE, trace);
+        updateTestResult(test, TestStatus.FAILURE, FailureDescription.create(trace));
     }
 
     public void testAssumptionFailure(TestDescription test, String trace) {
-        updateTestResult(test, TestStatus.ASSUMPTION_FAILURE, trace);
+        updateTestResult(test, TestStatus.ASSUMPTION_FAILURE, FailureDescription.create(trace));
     }
 
     public void testIgnored(TestDescription test) {
diff --git a/src/com/android/tradefed/device/metric/BaseDeviceMetricCollector.java b/src/com/android/tradefed/device/metric/BaseDeviceMetricCollector.java
index 05c68c7..4cea7b1 100644
--- a/src/com/android/tradefed/device/metric/BaseDeviceMetricCollector.java
+++ b/src/com/android/tradefed/device/metric/BaseDeviceMetricCollector.java
@@ -358,6 +358,19 @@
     }
 
     @Override
+    public final void testAssumptionFailure(TestDescription test, FailureDescription failure) {
+        if (!mSkipTestCase) {
+            try {
+                onTestAssumptionFailure(mTestData, test);
+            } catch (Throwable t) {
+                // Prevent exception from messing up the status reporting.
+                CLog.e(t);
+            }
+        }
+        mForwarder.testAssumptionFailure(test, failure);
+    }
+
+    @Override
     public final void testIgnored(TestDescription test) {
         mForwarder.testIgnored(test);
     }
diff --git a/src/com/android/tradefed/postprocessor/BasePostProcessor.java b/src/com/android/tradefed/postprocessor/BasePostProcessor.java
index 727cafc..afa7b8a 100644
--- a/src/com/android/tradefed/postprocessor/BasePostProcessor.java
+++ b/src/com/android/tradefed/postprocessor/BasePostProcessor.java
@@ -272,6 +272,11 @@
     }
 
     @Override
+    public final void testAssumptionFailure(TestDescription test, FailureDescription failure) {
+        mForwarder.testAssumptionFailure(test, failure);
+    }
+
+    @Override
     public final void testIgnored(TestDescription test) {
         mForwarder.testIgnored(test);
     }
diff --git a/test_result_interfaces/com/android/tradefed/result/ITestLifeCycleReceiver.java b/test_result_interfaces/com/android/tradefed/result/ITestLifeCycleReceiver.java
index 99f563f..74203cf 100644
--- a/test_result_interfaces/com/android/tradefed/result/ITestLifeCycleReceiver.java
+++ b/test_result_interfaces/com/android/tradefed/result/ITestLifeCycleReceiver.java
@@ -174,6 +174,16 @@
     public default void testAssumptionFailure(TestDescription test, String trace) {}
 
     /**
+     * Called when an atomic test flags that it assumes a condition that is false
+     *
+     * @param test identifies the test
+     * @param failure {@link FailureDescription} describing the failure and its context.
+     */
+    public default void testAssumptionFailure(TestDescription test, FailureDescription failure) {
+        testAssumptionFailure(test, failure.toString());
+    }
+
+    /**
      * Called when a test will not be run, generally because a test method is annotated with
      * org.junit.Ignore.
      *
diff --git a/test_result_interfaces/com/android/tradefed/result/MultiFailureDescription.java b/test_result_interfaces/com/android/tradefed/result/MultiFailureDescription.java
index e6d11f6..00795f4 100644
--- a/test_result_interfaces/com/android/tradefed/result/MultiFailureDescription.java
+++ b/test_result_interfaces/com/android/tradefed/result/MultiFailureDescription.java
@@ -34,12 +34,17 @@
 
     public MultiFailureDescription(List<FailureDescription> failures) {
         super();
-        mFailures.addAll(failures);
+        for (FailureDescription failure : failures) {
+            if (failure instanceof MultiFailureDescription) {
+                mFailures.addAll(((MultiFailureDescription) failure).getFailures());
+            } else {
+                mFailures.add(failure);
+            }
+        }
     }
 
     public MultiFailureDescription(FailureDescription... failures) {
-        super();
-        mFailures.addAll(Arrays.asList(failures));
+        this(Arrays.asList(failures));
     }
 
     /**
diff --git a/tests/src/com/android/tradefed/result/TestResultTest.java b/tests/src/com/android/tradefed/result/TestResultTest.java
index 92b46ef..23a5317 100644
--- a/tests/src/com/android/tradefed/result/TestResultTest.java
+++ b/tests/src/com/android/tradefed/result/TestResultTest.java
@@ -69,7 +69,7 @@
         assertEquals(TestStatus.FAILURE, finalRes.getStatus());
         assertEquals(2, finalRes.getStartTime());
         assertEquals(7, finalRes.getEndTime());
-        assertEquals("failed\n\nfailed", finalRes.getStackTrace());
+        assertEquals("There were 2 failures:\n  failed\n  failed", finalRes.getStackTrace());
     }
 
     @Test
diff --git a/tests/src/com/android/tradefed/result/TestRunResultTest.java b/tests/src/com/android/tradefed/result/TestRunResultTest.java
index 76de395..2f14eb0 100644
--- a/tests/src/com/android/tradefed/result/TestRunResultTest.java
+++ b/tests/src/com/android/tradefed/result/TestRunResultTest.java
@@ -291,8 +291,10 @@
         assertEquals(TestStatus.FAILURE, test3Result.getStatus());
 
         assertEquals(null, test1Result.getStackTrace());
-        assertEquals("flaky 1\n\nflaky 2", test2Result.getStackTrace());
-        assertEquals("bad_code1\n\nbad_code2\n\nbad_code3", test3Result.getStackTrace());
+        assertEquals("There were 2 failures:\n  flaky 1\n  flaky 2", test2Result.getStackTrace());
+        assertEquals(
+                "There were 3 failures:\n  bad_code1\n  bad_code2\n  bad_code3",
+                test3Result.getStackTrace());
     }
 
     /** Ensure that the merging logic among multiple testRunResults for the same test is correct. */
@@ -342,7 +344,9 @@
         TestResult testResult = testRunResult.get(testcase);
         assertEquals(TestStatus.PASSED, testResult.getStatus());
 
-        assertEquals("Second run failed.\n\nFourth run failed.", testResult.getStackTrace());
+        assertEquals(
+                "There were 2 failures:\n  Second run failed.\n  Fourth run failed.",
+                testResult.getStackTrace());
     }
 
     /** Ensure that merge will raise exceptions if merge TestRunResult for different test. */
@@ -602,6 +606,8 @@
         TestResult testResult = testRunResult.get(testcase);
         assertEquals(TestStatus.PASSED, testResult.getStatus());
 
-        assertEquals("Second run failed.\n\nthird run failed.", testResult.getStackTrace());
+        assertEquals(
+                "There were 2 failures:\n  Second run failed.\n  third run failed.",
+                testResult.getStackTrace());
     }
 }
diff --git a/tests/src/com/android/tradefed/testtype/HostTestTest.java b/tests/src/com/android/tradefed/testtype/HostTestTest.java
index c119c26..5ac0f23 100644
--- a/tests/src/com/android/tradefed/testtype/HostTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/HostTestTest.java
@@ -1282,7 +1282,7 @@
                 new TestDescription(JUnit4TestClassAssume.class.getName(), "testPass5");
         mListener.testRunStarted((String) EasyMock.anyObject(), EasyMock.eq(1));
         mListener.testStarted(EasyMock.eq(test1));
-        mListener.testAssumptionFailure(EasyMock.eq(test1), EasyMock.anyObject());
+        mListener.testAssumptionFailure(EasyMock.eq(test1), (String) EasyMock.anyObject());
         mListener.testEnded(EasyMock.eq(test1), (HashMap<String, Metric>) EasyMock.anyObject());
         mListener.testRunEnded(EasyMock.anyLong(), (HashMap<String, Metric>) EasyMock.anyObject());
         EasyMock.replay(mListener);