failures_lib: do not print the textual tracebacks for CompoundFailure

Including the textual tracebacks in __str__ makes the failure reason
hard to read on the waterfall. Remove the tracebacks from __str__, but
still print them in HandleExceptionAsError/Warning for debugging
purpose.

BUG=chromium:382731
TEST=`cbuildbot/run_tests`

Change-Id: Iea992dbda67d734cdeaa3c2c4e33e1375354680b
Reviewed-on: https://chromium-review.googlesource.com/203177
Tested-by: Yu-Ju Hong <yjhong@chromium.org>
Reviewed-by: Yu-Ju Hong <yjhong@chromium.org>
Commit-Queue: Yu-Ju Hong <yjhong@chromium.org>
diff --git a/cbuildbot/failures_lib.py b/cbuildbot/failures_lib.py
index d3d5e14..e7d2b79 100644
--- a/cbuildbot/failures_lib.py
+++ b/cbuildbot/failures_lib.py
@@ -79,12 +79,22 @@
     """
     self.exc_infos = exc_infos if exc_infos else []
     if not message:
-      # By default, print all stored ExceptInfo objects.
-      message = '\n'.join(['%s: %s\n%s' % e for e in self.exc_infos])
+      # By default, print the type and string of each ExceptInfo object.
+      message = '\n'.join(['%s: %s' % (e.type, e.str) for e in self.exc_infos])
 
     super(CompoundFailure, self).__init__(message=message,
                                           possibly_flaky=possibly_flaky)
 
+  def ToFullMessage(self):
+    """Returns a string with all information in self.exc_infos."""
+    if self.HasEmptyList():
+      # Fall back to return self.message if list is empty.
+      return self.message
+    else:
+      # This includes the textual traceback(s).
+      return '\n'.join(['{e.type}: {e.str} {e.traceback}'.format(e=ex) for
+                        ex in self.exc_infos])
+
   def HasEmptyList(self):
     """Returns True if self.exc_infos is empty."""
     return not bool(self.exc_infos)
diff --git a/cbuildbot/failures_lib_unittest.py b/cbuildbot/failures_lib_unittest.py
index f3fce4d..417d3f3 100755
--- a/cbuildbot/failures_lib_unittest.py
+++ b/cbuildbot/failures_lib_unittest.py
@@ -72,10 +72,10 @@
     exc = failures_lib.CompoundFailure(exc_infos=exc_infos)
     self.assertTrue('bar1' in str(exc))
     self.assertTrue('bar2' in str(exc))
-    self.assertTrue('foo1' in str(exc))
-    self.assertTrue('foo2' in str(exc))
     self.assertTrue('KeyError' in str(exc))
     self.assertTrue('ValueError' in str(exc))
+    self.assertTrue('foo1' in exc.ToFullMessage())
+    self.assertTrue('foo2' in exc.ToFullMessage())
 
 
 class SetFailureTypeTest(cros_test_lib.TestCase):
@@ -144,8 +144,8 @@
       self.assertEqual(e.exc_infos, org_infos)
       # All essential inforamtion should be included in the message of
       # the new excpetion.
-      self.assertTrue(tb1 in str(e))
-      self.assertTrue(tb2 in str(e))
+      self.assertTrue(tb1 in e.ToFullMessage())
+      self.assertTrue(tb2 in e.ToFullMessage())
       self.assertTrue(str(ValueError) in str(e))
       self.assertTrue(str(OSError) in str(e))
       self.assertTrue(str('No taco') in str(e))
diff --git a/cbuildbot/stages/generic_stages.py b/cbuildbot/stages/generic_stages.py
index 7539d56..3d70508 100644
--- a/cbuildbot/stages/generic_stages.py
+++ b/cbuildbot/stages/generic_stages.py
@@ -265,7 +265,9 @@
       A string description of the exception.
     """
     exc_type, exc_value = exc_info[:2]
-    if issubclass(exc_type, failures_lib.StepFailure):
+    if issubclass(exc_type, failures_lib.CompoundFailure):
+      return exc_value.ToFullMessage()
+    elif issubclass(exc_type, failures_lib.StepFailure):
       return str(exc_value)
     else:
       return ''.join(traceback.format_exception(*exc_info))
diff --git a/lib/parallel_unittest.py b/lib/parallel_unittest.py
index f07f359..ef1cb21 100755
--- a/lib/parallel_unittest.py
+++ b/lib/parallel_unittest.py
@@ -351,8 +351,8 @@
           ex_str = str(ex)
 
       self.assertTrue(exc_type in [x.type for x in ex.exc_infos])
-      self.assertTrue('Traceback' in ex_str)
       self.assertEqual(output_str, _GREETING)
+      self.assertTrue(str(exc_type) in ex_str)
 
   def testExceptionRaising(self):
     """Tests the exceptions are raised correctly."""