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
7import math
8import os
9import sys
10import traceback
11
12from telemetry.core import exceptions
13from telemetry.core import util
14
15
16def 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
45def PrintFormattedFrame(frame, exception_string=None):
46  _PrintFormattedTrace(traceback.extract_stack(frame), frame, exception_string)
47
48
49def _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
95def _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