# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import collections
import re
import urllib
from common.buildbot import network
StackTraceLine = collections.namedtuple(
'StackTraceLine', ('file', 'function', 'line', 'source'))
class Step(object):
def __init__(self, data, build_url):
self._name = data['name']
self._status = data['results'][0]
self._start_time, self._end_time = data['times']
self._url = '%s/steps/%s' % (build_url, urllib.quote(self._name))
self._log_link = None
self._results_link = None
for link_name, link_url in data['logs']:
if link_name == 'stdio':
self._log_link = link_url + '/text'
elif link_name == 'json.output':
self._results_link = link_url + '/text'
# Property caches.
self._log = None
self._results = None
self._stack_trace = None
def __str__(self):
return self.name
@property
def name(self):
return self._name
@property
def url(self):
return self._url
@property
def status(self):
return self._status
@property
def start_time(self):
return self._start_time
@property
def end_time(self):
return self._end_time
@property
def log_link(self):
return self._log_link
@property
def results_link(self):
return self._results_link
@property
def log(self):
if self._log is None:
if not self.log_link:
return None
self._log = network.FetchText(self.log_link)
return self._log
@property
def results(self):
if self._results is None:
if not self.results_link:
return None
self._results = network.FetchData(self.results_link)
return self._results
@property
def stack_trace(self):
if self._stack_trace is None:
self._stack_trace = _ParseTraceFromLog(self.log)
return self._stack_trace
def _ParseTraceFromLog(log):
"""Searches the log for a stack trace and returns a structured representation.
This function supports both default Python-style stacks and Telemetry-style
stacks. It returns the first stack trace found in the log - sometimes a bug
leads to a cascade of failures, so the first one is usually the root cause.
Args:
log: A string containing Python or Telemetry stack traces.
Returns:
Two values, or (None, None) if no stack trace was found.
The first is a tuple of StackTraceLine objects, most recent call last.
The second is a string with the type and description of the exception.
"""
log_iterator = iter(log.splitlines())
for line in log_iterator:
if line == 'Traceback (most recent call last):':
break
else:
return (None, None)
stack_trace = []
while True:
line = log_iterator.next()
match_python = re.match(r'\s*File "(?P<file>.+)", line (?P<line>[0-9]+), '
r'in (?P<function>.+)', line)
match_telemetry = re.match(r'\s*(?P<function>.+) at '
r'(?P<file>.+):(?P<line>[0-9]+)', line)
match = match_python or match_telemetry
if not match:
exception = line
break
trace_line = match.groupdict()
# Use the base name, because the path will be different
# across platforms and configurations.
file_base_name = trace_line['file'].split('/')[-1].split('\\')[-1]
source = log_iterator.next().strip()
stack_trace.append(StackTraceLine(
file_base_name, trace_line['function'], trace_line['line'], source))
return tuple(stack_trace), exception