Home | History | Annotate | Download | only in util
      1 # Copyright 2014 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 """Print prettier and more detailed exceptions."""
      6 
      7 import math
      8 import os
      9 import sys
     10 import traceback
     11 
     12 from telemetry.core import exceptions
     13 from telemetry.core import util
     14 
     15 
     16 def PrintFormattedException(exception_class=None, exception=None, tb=None,
     17                             msg=None):
     18   if not (bool(exception_class) == bool(exception) == bool(tb)):
     19     raise ValueError('Must specify all or none of '
     20                      'exception_class, exception, and tb')
     21 
     22   if not exception_class:
     23     exception_class, exception, tb = sys.exc_info()
     24 
     25   if exception_class == exceptions.IntentionalException:
     26     return
     27 
     28   def _GetFinalFrame(tb_level):
     29     while tb_level.tb_next:
     30       tb_level = tb_level.tb_next
     31     return tb_level.tb_frame
     32 
     33   processed_tb = traceback.extract_tb(tb)
     34   frame = _GetFinalFrame(tb)
     35   exception_list = traceback.format_exception_only(exception_class, exception)
     36   exception_string = '\n'.join(l.strip() for l in exception_list)
     37 
     38   if msg:
     39     print >> sys.stderr
     40     print >> sys.stderr, msg
     41 
     42   _PrintFormattedTrace(processed_tb, frame, exception_string)
     43 
     44 
     45 def PrintFormattedFrame(frame, exception_string=None):
     46   _PrintFormattedTrace(traceback.extract_stack(frame), frame, exception_string)
     47 
     48 
     49 def _PrintFormattedTrace(processed_tb, frame, exception_string=None):
     50   """Prints an Exception in a more useful format than the default.
     51 
     52   TODO(tonyg): Consider further enhancements. For instance:
     53     - Report stacks to maintainers like depot_tools does.
     54     - Add a debug flag to automatically start pdb upon exception.
     55   """
     56   print >> sys.stderr
     57 
     58   # Format the traceback.
     59   base_dir = os.path.abspath(util.GetChromiumSrcDir())
     60   print >> sys.stderr, 'Traceback (most recent call last):'
     61   for filename, line, function, text in processed_tb:
     62     filename = os.path.abspath(filename)
     63     if filename.startswith(base_dir):
     64       filename = filename[len(base_dir)+1:]
     65     print >> sys.stderr, '  %s at %s:%d' % (function, filename, line)
     66     print >> sys.stderr, '    %s' % text
     67 
     68   # Format the exception.
     69   if exception_string:
     70     print >> sys.stderr, exception_string
     71 
     72   # Format the locals.
     73   local_variables = [(variable, value) for variable, value in
     74                      frame.f_locals.iteritems() if variable != 'self']
     75   print >> sys.stderr
     76   print >> sys.stderr, 'Locals:'
     77   if local_variables:
     78     longest_variable = max(len(v) for v, _ in local_variables)
     79     for variable, value in sorted(local_variables):
     80       value = repr(value)
     81       possibly_truncated_value = _AbbreviateMiddleOfString(value, ' ... ', 1024)
     82       truncation_indication = ''
     83       if len(possibly_truncated_value) != len(value):
     84         truncation_indication = ' (truncated)'
     85       print >> sys.stderr, '  %s: %s%s' % (variable.ljust(longest_variable + 1),
     86                                            possibly_truncated_value,
     87                                            truncation_indication)
     88   else:
     89     print >> sys.stderr, '  No locals!'
     90 
     91   print >> sys.stderr
     92   sys.stderr.flush()
     93 
     94 
     95 def _AbbreviateMiddleOfString(target, middle, max_length):
     96   if max_length < 0:
     97     raise ValueError('Must provide positive max_length')
     98   if len(middle) > max_length:
     99     raise ValueError('middle must not be greater than max_length')
    100 
    101   if len(target) <= max_length:
    102     return target
    103   half_length = (max_length - len(middle)) / 2.
    104   return (target[:int(math.floor(half_length))] + middle +
    105           target[-int(math.ceil(half_length)):])
    106