• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2015 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
5import collections
6import re
7import urllib
8
9from common.buildbot import network
10
11
12StackTraceLine = collections.namedtuple(
13    'StackTraceLine', ('file', 'function', 'line', 'source'))
14
15
16class Step(object):
17
18  def __init__(self, data, build_url):
19    self._name = data['name']
20    self._status = data['results'][0]
21    self._start_time, self._end_time = data['times']
22    self._url = '%s/steps/%s' % (build_url, urllib.quote(self._name))
23
24    self._log_link = None
25    self._results_link = None
26    for link_name, link_url in data['logs']:
27      if link_name == 'stdio':
28        self._log_link = link_url + '/text'
29      elif link_name == 'json.output':
30        self._results_link = link_url + '/text'
31
32    # Property caches.
33    self._log = None
34    self._results = None
35    self._stack_trace = None
36
37  def __str__(self):
38    return self.name
39
40  @property
41  def name(self):
42    return self._name
43
44  @property
45  def url(self):
46    return self._url
47
48  @property
49  def status(self):
50    return self._status
51
52  @property
53  def start_time(self):
54    return self._start_time
55
56  @property
57  def end_time(self):
58    return self._end_time
59
60  @property
61  def log_link(self):
62    return self._log_link
63
64  @property
65  def results_link(self):
66    return self._results_link
67
68  @property
69  def log(self):
70    if self._log is None:
71      if not self.log_link:
72        return None
73
74      self._log = network.FetchText(self.log_link)
75    return self._log
76
77  @property
78  def results(self):
79    if self._results is None:
80      if not self.results_link:
81        return None
82
83      self._results = network.FetchData(self.results_link)
84    return self._results
85
86  @property
87  def stack_trace(self):
88    if self._stack_trace is None:
89      self._stack_trace = _ParseTraceFromLog(self.log)
90    return self._stack_trace
91
92
93def _ParseTraceFromLog(log):
94  """Searches the log for a stack trace and returns a structured representation.
95
96  This function supports both default Python-style stacks and Telemetry-style
97  stacks. It returns the first stack trace found in the log - sometimes a bug
98  leads to a cascade of failures, so the first one is usually the root cause.
99
100  Args:
101    log: A string containing Python or Telemetry stack traces.
102
103  Returns:
104    Two values, or (None, None) if no stack trace was found.
105    The first is a tuple of StackTraceLine objects, most recent call last.
106    The second is a string with the type and description of the exception.
107  """
108  log_iterator = iter(log.splitlines())
109  for line in log_iterator:
110    if line == 'Traceback (most recent call last):':
111      break
112  else:
113    return (None, None)
114
115  stack_trace = []
116  while True:
117    line = log_iterator.next()
118    match_python = re.match(r'\s*File "(?P<file>.+)", line (?P<line>[0-9]+), '
119                            r'in (?P<function>.+)', line)
120    match_telemetry = re.match(r'\s*(?P<function>.+) at '
121                               r'(?P<file>.+):(?P<line>[0-9]+)', line)
122    match = match_python or match_telemetry
123    if not match:
124      exception = line
125      break
126    trace_line = match.groupdict()
127    # Use the base name, because the path will be different
128    # across platforms and configurations.
129    file_base_name = trace_line['file'].split('/')[-1].split('\\')[-1]
130    source = log_iterator.next().strip()
131    stack_trace.append(StackTraceLine(
132        file_base_name, trace_line['function'], trace_line['line'], source))
133
134  return tuple(stack_trace), exception
135