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