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