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