Home | History | Annotate | Download | only in buildbot
      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 
      5 import collections
      6 import re
      7 import urllib
      8 
      9 from common.buildbot import network
     10 
     11 
     12 StackTraceLine = collections.namedtuple(
     13     'StackTraceLine', ('file', 'function', 'line', 'source'))
     14 
     15 
     16 class 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 
     93 def _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