# 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