OLD | NEW |
---|---|
(Empty) | |
1 # Copyright (c) 2014 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 json | |
6 import time | |
7 import urllib | |
8 | |
9 | |
10 def NormalizePathLinux(path): | |
11 """Normalizes linux path. | |
12 | |
13 Args: | |
14 path: A string representing a path. | |
15 | |
16 Returns: | |
17 A tuple containing a component this path is in (e.g blink, skia, etc) | |
18 and a path in that component's repository. | |
19 """ | |
20 normalized_path = path | |
21 # TODO(jeun): Integrate with parsing DEPS file. | |
22 if 'WebKit/' in path: | |
23 component = 'blink' | |
24 normalized_path = ''.join(path.split('WebKit/')[1:]) | |
25 else: | |
26 component = 'chromium' | |
27 | |
28 if normalized_path.startswith( | |
29 '/b/build/slave/ASAN_Release__symbolized_/build/'): | |
30 normalized_path = normalized_path.split( | |
31 '/b/build/slave/ASAN_Release__symbolized_/build/')[1] | |
32 | |
33 if '../../' in normalized_path: | |
34 normalized_path = normalized_path.split('../../')[1] | |
35 | |
36 if 'src/v8/' in normalized_path: | |
37 component = 'v8' | |
38 normalized_path = normalized_path.split('src/v8/')[1] | |
39 | |
40 if './' in normalized_path: | |
41 normalized_path = normalized_path.split('./')[1] | |
42 | |
43 if not normalized_path.startswith('src/') and ( | |
44 not normalized_path.startswith('Source/')): | |
45 normalized_path = 'src/' + normalized_path | |
46 | |
47 return (component, normalized_path) | |
48 | |
49 | |
50 def SplitRange(regression): | |
51 """Splits a range as retrieved from clusterfuzz. | |
52 | |
53 Args: | |
54 regression: A string in format 'r1234:r5678'. | |
55 | |
56 Returns: | |
57 A list containing two numbers represented in string, for example | |
58 ['1234','5678']. | |
59 """ | |
60 temp = regression.split(':') | |
Martin Barbella
2014/08/06 21:12:24
s/temp/revisions/
jeun
2014/08/06 23:36:24
Done.
| |
61 | |
62 # If regression information is not available, return none. | |
63 if len(temp) != 2: | |
64 return None | |
65 | |
66 start_range = temp[0] | |
67 end_range = temp[1] | |
68 | |
69 # Check if the range starts with r, such as in 'r10000' format. | |
70 if start_range.startswith('r'): | |
71 start_range = start_range[1:] | |
72 if end_range.startswith('r'): | |
73 end_range = end_range[1:] | |
74 | |
75 return [start_range, end_range] | |
76 | |
77 | |
78 def LoadJSON(json_string): | |
79 """Loads json object from string, or None. | |
80 | |
81 Args: | |
82 json_string: A string to get object from. | |
83 | |
84 Returns: | |
85 JSON object if the string represents a JSON object, None otherwise. | |
86 """ | |
87 try: | |
88 data = json.loads(json_string) | |
89 except ValueError: | |
90 data = None | |
91 return data | |
92 | |
93 | |
94 def GetDataFromURL(url, retry=10, sleep_time=0.1): | |
Martin Barbella
2014/08/06 21:12:24
Using the name retry makes it sound like a boolean
jeun
2014/08/06 23:36:25
Done.
| |
95 """Retrieves raw data from URL, tries 10 times. | |
96 | |
97 Args: | |
98 url: URL to get data from. | |
99 retry: Number of times to retry connection. | |
100 sleep_time: Time in seconds to wait before retrying connection. | |
101 | |
102 Returns: | |
103 None if the data retrieval fails, or the raw data. | |
104 """ | |
105 data = None | |
106 for i in range(retry): | |
107 # Retrieves data from URL. | |
108 try: | |
109 data = urllib.urlopen(url) | |
110 | |
111 # If retrieval is successful, break from the retry look. | |
112 if data: | |
113 break | |
114 | |
115 # If retrieval fails, try after 0.1 second. | |
116 except IOError: | |
117 time.sleep(sleep_time) | |
118 continue | |
119 | |
120 # If returned data has something in it, return the content. | |
121 if data: | |
122 return data.read() | |
123 else: | |
124 return None | |
125 | |
126 | |
127 def FindMinLineDistance(crashed_line_list, changed_line_numbers): | |
128 """Calculates how far the changed line is from one of the crashes. | |
129 | |
130 Finds the minimum distance between the lines that the file crashed on | |
131 and the lines that the file changed. For example, if the file crashed on | |
132 line 200 and the CL changes line 203,204 and 205, the function returns 3. | |
133 | |
134 Args: | |
135 crashed_line_list: A list of lines that the file crashed on. | |
136 changed_line_numbers: A list of lines that the file changed. | |
137 | |
138 Returns: | |
139 The minimum distance. If either of the input lists is empty, | |
140 it returns inf. | |
141 | |
142 """ | |
143 min_distance = float('inf') | |
144 | |
145 for line in crashed_line_list: | |
146 for distance in changed_line_numbers: | |
147 # Find the current distance and update the min if current distance is | |
148 # less than current min. | |
149 current_distance = abs(line - distance) | |
150 if current_distance < min_distance: | |
151 min_distance = current_distance | |
152 | |
153 return min_distance | |
154 | |
155 | |
156 def GuessIfSamePath(path1, path2): | |
157 """Guesses if two paths represent same path. | |
158 | |
159 Compares the name of the folders in the path (by split('/')), and checks | |
160 if they match either more than 3 or min of path lengths. | |
161 | |
162 Args: | |
163 path1: First path. | |
164 path2: Second path to compare. | |
165 | |
166 Returns: | |
167 True if it they are thought to be a same path, False otherwise. | |
168 """ | |
169 path1 = path1.split('/') | |
170 path2 = path2.split('/') | |
171 | |
172 intersection = set(path1).intersection(set(path2)) | |
173 return len(intersection) >= (min(3, min(len(path1), len(path2)))) | |
174 | |
175 | |
176 def FindMinStackFrameNum(stack_frame_index, priorities): | |
177 """Finds the minimum stack number, from the list of stack numbers. | |
178 | |
179 Args: | |
180 stack_frame_index: A list of list containing stack position. | |
181 priorities: A list of of priority for each file. | |
182 | |
183 Returns: | |
184 Inf if stack_frame_index is empty, minimum stack number otherwise. | |
185 """ | |
186 # Get the indexes of the highest priority (or low priority number) | |
187 highest_priority = min(priorities) | |
188 highest_priority_indices = [] | |
189 for i in range(len(priorities)): | |
190 if priorities[i] == highest_priority: | |
191 highest_priority_indices.append(i) | |
192 | |
193 # Gather the list of stack frame numbers for the files that change the | |
194 # crash lines. | |
195 flattened = [] | |
196 for i in highest_priority_indices: | |
197 flattened += stack_frame_index[i] | |
198 | |
199 # If no stack frame information is available, return inf. Else, return min. | |
200 if not flattened: | |
201 return float('inf') | |
202 else: | |
203 return min(flattened) | |
204 | |
205 | |
206 def AddHyperlink(to_add, link): | |
207 """Returns a string with HTML link tag. | |
208 | |
209 Args: | |
210 to_add: A string to add link. | |
211 link: A link to add to the string. | |
212 | |
213 Returns: | |
214 A string with hyperlink added. | |
215 """ | |
216 return '<a href="%s">%s<\\a>' % (link, to_add) | |
Martin Barbella
2014/08/06 21:12:24
Depending on how this is used, this is scary. Use
jeun
2014/08/06 23:36:25
added cgi.escape to link and changed the closing t
| |
217 | |
218 | |
219 def PrettifyList(l): | |
220 """Returns a string representation of a list . | |
Martin Barbella
2014/08/06 21:12:24
Nit: there's an extra space before the period. Cou
jeun
2014/08/06 23:36:25
Added explanation on how the string is prettified.
| |
221 | |
222 Args: | |
223 l: A list to prettify. | |
224 Returns: | |
225 A string representation of the list. | |
226 """ | |
227 return str(l)[1:-1] | |
228 | |
229 | |
230 def PrettifyFiles(file_list): | |
231 """Returns a string representation of a list of file names. | |
232 | |
233 Args: | |
234 file_list: A list of tuple, (file_name, file_url). | |
235 Returns: | |
236 A string representation of file names with their urls. | |
237 """ | |
238 ret = ['\n'] | |
239 for file_name, file_url in file_list: | |
240 ret.append(' %s\n' % AddHyperlink(file_name, file_url)) | |
241 return ''.join(ret) | |
242 | |
243 | |
244 def Intersection(crashed_line_list, stack_frame_index, changed_line_numbers): | |
245 """Finds the overlap betwee changed lines and crashed lines. | |
246 | |
247 Finds the intersection of the lines that caused the crash and | |
248 lines that the file changes. The intersection looks within 3 lines | |
249 of the line that caused the crash. | |
250 | |
251 Args: | |
252 crashed_line_list: A list of lines that the file crashed on. | |
253 stack_frame_index: A list of positions in stack for each of the lines. | |
254 changed_line_numbers: A list of lines that the file changed. | |
255 | |
256 Returns: | |
257 line_intersection: Intersection between crashed_line_list and | |
258 changed_line_numbers. | |
259 stack_frame_index_intersection: Stack number for each of the intersections. | |
260 """ | |
261 line_intersection = [] | |
262 stack_frame_index_intersection = [] | |
263 | |
264 # Iterate through the crashed lines, and its occurence in stack. | |
265 for (line, stack_frame_index) in zip(crashed_line_list, stack_frame_index): | |
266 | |
267 # Also check previous 3 lines. | |
268 line_minus_n = range(line - 3, line + 1) | |
269 | |
270 for changed_line in changed_line_numbers: | |
271 | |
272 # If a CL does not change crahsed line, check next line. | |
273 if changed_line not in line_minus_n: | |
274 continue | |
275 | |
276 # If the changed line is exactly the crashed line, add that line. | |
277 if line in changed_line_numbers: | |
278 to_add = line | |
279 | |
280 # If the changed line is in 3 lines of the crashed line, add the line. | |
281 else: | |
282 to_add = changed_line | |
283 | |
284 # Avoid adding the same line twice. | |
285 if to_add not in line_intersection: | |
286 line_intersection.append(to_add) | |
287 stack_frame_index_intersection.append(stack_frame_index) | |
288 | |
289 break | |
290 | |
291 return (line_intersection, stack_frame_index_intersection) | |
OLD | NEW |