Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(233)

Side by Side Diff: tools/compare_codereview.py

Issue 143503003: Chromium Codereview Comparison Script. (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: Created 6 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/python2
2
3 # Copyright 2014 Google Inc.
4 #
5 # Use of this source code is governed by a BSD-style license that can be
6 # found in the LICENSE file.
7
8 """Skia's Chromium Codereview Comparison Script.
9
10 This script takes two Codereview URLs, looks at the trybot results for
11 the two codereviews and compares the results.
12
13 Usage:
14 compare_codereview.py CONTROL_URL ROLL_URL
15 """
16
17 import collections
18 import os
19 import re
20 import sys
21 import urllib2
22 import HTMLParser
23
24
25 class CodeReviewHTMLParser(HTMLParser.HTMLParser):
26 """parses CodeReview web pages.
27 """
28 # pylint: disable=I0011,R0904
29 @staticmethod
30 def parse(url):
31 """Returns a dictionary of {bot_name:CodeReviewHTMLParser.Status}
32 """
borenet 2014/01/21 21:55:43 I'd prefer that you use the following docstring fo
hal.canary 2014/01/22 15:25:32 Done.
33 parser = CodeReviewHTMLParser()
34 try:
35 parser.feed(urllib2.urlopen(url).read())
36 except (urllib2.URLError,):
37 print >> sys.stderr, 'Error getting', url
38 return None
39 parser.close()
40 return parser.statuses
41
42 Status = collections.namedtuple('Status', ['status', 'url'])
43
44 def __init__(self):
45 HTMLParser.HTMLParser.__init__(self)
46 self._id = None
47 self._status = None
48 self._href = None
49 self._anchor_data = None
50 # statuses is a dictionary of CodeReviewHTMLParser.Status
51 self.statuses = {}
52
53 def handle_starttag(self, tag, attrs):
borenet 2014/01/21 21:55:43 I'd appreciate a docstring here and elsewhere, eve
hal.canary 2014/01/22 15:25:32 I'll just note that I'm overriding a method and co
54 attrs = dict(attrs)
55 if tag == 'div':
56 id_attr = attrs.get('id','')
57 if id_attr.startswith('tryjobdiv'):
58 self._id = id_attr
59 if (self._id and tag == 'a'
60 and 'build-result' in attrs.get('class', '').split()):
61 self._status = attrs.get('status')
62 self._href = attrs.get('href')
63 self._anchor_data = ''
64
65 def handle_endtag(self, tag):
66 if tag == 'a' and self._status:
67 bot = self._anchor_data.strip()
68 stat = CodeReviewHTMLParser.Status(status=self._status,
69 url=self._href)
70 if bot:
71 self.statuses[bot] = stat
72 self._anchor_data = None
73 self._status = None
74 self._href = None
75
76 def handle_data(self, data):
77 if self._anchor_data is not None:
78 self._anchor_data += data
borenet 2014/01/21 21:55:43 I find this flow hard to follow; I think I'd rathe
hal.canary 2014/01/22 15:25:32 Done.
79
80
81 class BuilderHTMLParser(HTMLParser.HTMLParser):
82 """parses Trybot web pages.
83 """
84 # pylint: disable=I0011,R0904
85 @staticmethod
86 def parse(url):
87 """Returns an array of BuilderHTMLParser.Results, each a
88 description of failure results, along with an optional url.
89 """
90 parser = BuilderHTMLParser()
91 try:
92 parser.feed(urllib2.urlopen(url).read())
93 except (urllib2.URLError,):
94 print >> sys.stderr, 'Error getting', url
95 return []
96 parser.close()
97 return parser.failure_results
98
99 Result = collections.namedtuple('Result', ['text', 'url'])
100
101 def __init__(self):
102 HTMLParser.HTMLParser.__init__(self)
103 self.failure_results = []
104 self._current_failure_result = None
105 self._divlevel = None
106 self._li_level = 0
107 self._li_data = ''
108 self._current_failure = False
109 self._failure_results_url = ''
110
111 def handle_starttag(self, tag, attrs):
112 attrs = dict(attrs)
113 if tag == 'li':
114 self._li_level += 1
115 return
116 if tag == 'div' and attrs.get('class') == 'failure result':
117 if self._li_level > 0:
118 self._current_failure = True
119 return
120
121 if tag == 'a' and self._current_failure:
122 href = attrs.get('href')
123 if href.endswith('/logs/stdio'):
124 self._failure_results_url = href
125
126 def handle_endtag(self, tag):
127 if tag == 'li':
128 self._li_level -= 1
129 if 0 == self._li_level:
130 if self._current_failure:
131 result = self._li_data.strip()
132 first = result.split()[0]
133 if first:
134 result = re.sub(r'^%s(\s+%s)+' % (first, first),
135 first, result)
136 result = re.sub(r'unexpected flaky.*', '', result)
137 result = re.sub(r'\bpreamble\b', '', result)
138 result = re.sub(r'\bstdio\b', '', result)
139 url = self._failure_results_url
140 self.failure_results.append(
141 BuilderHTMLParser.Result(result, url))
142 self._current_failure_result = None
143 self._current_failure = False
144 self._li_data = ''
145 self._failure_results_url = ''
146
147 def handle_data(self, data):
148 if self._current_failure:
149 self._li_data += data
150
151
152 def printer(indent, string):
153 """Print indented, wrapped text.
154 """
155 def wrap_to(line, columns):
156 """Wrap a line to the given number of columns, return a list
157 of strings.
158 """
159 ret = []
160 nextline = ''
161 for word in line.split():
162 if nextline:
163 if len(nextline) + 1 + len(word) > columns:
164 ret.append(nextline)
165 nextline = word
166 else:
167 nextline += (' ' + word)
168 else:
169 nextline = word
170 if nextline:
171 ret.append(nextline)
172 return ret
173 out = sys.stdout
174 spacer = ' '
175 for line in string.split('\n'):
176 for i, wrapped_line in enumerate(wrap_to(line, 68 - (2 * indent))):
177 out.write(spacer * indent)
178 if i > 0:
179 out.write(spacer)
180 out.write(wrapped_line)
181 out.write('\n')
182 out.flush()
183
184
185 def main(control_url, roll_url, verbosity):
borenet 2014/01/21 21:55:43 Optional: you could add a default verbosity level
hal.canary 2014/01/22 15:25:32 Done.
186 """Compare two Codereview URLs
187
188 Args:
189 control_url, roll_url: (strings) URL of the format
190 https://codereview.chromium.org/?????????
191
192 verbosity: (int) verbose level. 0, 1, or 2.
193 """
194 # pylint: disable=I0011,R0914,R0912
195 control = CodeReviewHTMLParser.parse(control_url)
196 roll = CodeReviewHTMLParser.parse(roll_url)
197 if not (control and roll):
198 return
199
200 control_name = '[control %s]' % control_url.split('/')[-1]
201 roll_name = '[roll %s]' % roll_url.split('/')[-1]
202 all_bots = set(control) & set(roll)
203
204 if verbosity > 0:
205 print '%11s %11s %4s %s' % ('CONTROL', 'ROLL', 'DIFF', 'BOT')
206 print
207 for bot in sorted(all_bots):
208 if control[bot].status != roll[bot].status:
209 diff = '****'
210 elif (control[bot].status != 'success' or
211 roll[bot].status != 'success'):
212 diff = '....'
213 else:
214 diff = ''
215 print '%11s %11s %4s %s' % (
216 control[bot].status, roll[bot].status, diff, bot)
217 print
218 sys.stdout.flush()
219
220 for bot in sorted(all_bots):
221 if (roll[bot].status == 'success'):
222 if verbosity > 1:
223 print '\n==%s==' % bot
224 printer(1, 'OK')
225 continue
226 print '\n==%s==' % bot
227
228 for (status, name, url) in (
229 (control[bot].status, control_name, control[bot].url),
230 (roll[bot].status, roll_name, roll[bot].url)):
231
232 if status == 'failure':
233 printer(1, name)
234 results = BuilderHTMLParser.parse(url)
235 for result in results:
236 formatted_result = re.sub(r'(\S*\.html) ', '\n__\g<1>\n',
237 result.text)
238 printer(2, formatted_result)
239 if ('compile' in result.text
240 or '...and more' in result.text):
241 printer(3, re.sub('/[^/]*$', '/', url) + result.url )
242 else:
243 printer(1, name)
244 printer(2, status)
245
246
247 if __name__ == '__main__':
248 if len(sys.argv) < 3:
249 print >> sys.stderr, __doc__
250 exit(1)
251 main(sys.argv[1], sys.argv[2],
252 int(os.environ.get('COMPARE_CODEREVIEW_VERBOSITY', 1)))
253
254
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698