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

Side by Side Diff: bisect-builds.py

Issue 10408045: Modified the bisect script to work for official Chrome builds. (Closed) Base URL: http://src.chromium.org/svn/trunk/src/tools/
Patch Set: Created 8 years, 7 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 | Annotate | Revision Log
« 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
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Snapshot Build Bisect Tool 6 """Snapshot Build Bisect Tool
7 7
8 This script bisects a snapshot archive using binary search. It starts at 8 This script bisects a snapshot archive using binary search. It starts at
9 a bad revision (it will try to guess HEAD) and asks for a last known-good 9 a bad revision (it will try to guess HEAD) and asks for a last known-good
10 revision. It will then binary search across this revision range by downloading, 10 revision. It will then binary search across this revision range by downloading,
11 unzipping, and opening Chromium for you. After testing the specific revision, 11 unzipping, and opening Chromium for you. After testing the specific revision,
12 it will ask you whether it is good or bad before continuing the search. 12 it will ask you whether it is good or bad before continuing the search.
13 """ 13 """
14 14
15 # The root URL for storage. 15 # The root URL for storage.
16 BASE_URL = 'http://commondatastorage.googleapis.com/chromium-browser-snapshots' 16 BASE_URL = 'http://commondatastorage.googleapis.com/chromium-browser-snapshots'
17 17
18 # The root URL for official builds.
19 OFFICIAL_BASE_URL = 'http://chrome4linux.mtv.corp.google.com/archives/'
Robert Sesek 2012/05/21 21:39:47 Do not end this with a / for consistency with the
Robert Sesek 2012/05/21 21:39:47 This instead of chrome-master2.mtv/official_builds
anantha 2012/05/22 22:55:39 Done.
anantha 2012/05/22 22:55:39 This location has files from chrome-master2.mtv an
20
18 # URL to the ViewVC commit page. 21 # URL to the ViewVC commit page.
19 BUILD_VIEWVC_URL = 'http://src.chromium.org/viewvc/chrome?view=rev&revision=%d' 22 BUILD_VIEWVC_URL = 'http://src.chromium.org/viewvc/chrome?view=rev&revision=%d'
20 23
21 # Changelogs URL. 24 # Changelogs URL.
22 CHANGELOG_URL = 'http://build.chromium.org/f/chromium/' \ 25 CHANGELOG_URL = 'http://build.chromium.org/f/chromium/' \
23 'perf/dashboard/ui/changelog.html?url=/trunk/src&range=%d:%d' 26 'perf/dashboard/ui/changelog.html?url=/trunk/src&range=%d:%d'
27 # Official Changelogs URL.
28 OFFICIAL_CHANGELOG_URL = 'http://omahaproxy.appspot.com/'\
29 'changelog?old_version=%s&new_version=%s'
24 30
25 # DEPS file URL. 31 # DEPS file URL.
26 DEPS_FILE= 'http://src.chromium.org/viewvc/chrome/trunk/src/DEPS?revision=%d' 32 DEPS_FILE= 'http://src.chromium.org/viewvc/chrome/trunk/src/DEPS?revision=%d'
27
28 # WebKit Changelogs URL. 33 # WebKit Changelogs URL.
29 WEBKIT_CHANGELOG_URL = 'http://trac.webkit.org/log/' \ 34 WEBKIT_CHANGELOG_URL = 'http://trac.webkit.org/log/' \
30 'trunk/?rev=%d&stop_rev=%d&verbose=on' 35 'trunk/?rev=%d&stop_rev=%d&verbose=on'
31 36
32 ############################################################################### 37 ###############################################################################
33 38
34 import math 39 import math
35 import optparse 40 import optparse
36 import os 41 import os
37 import pipes 42 import pipes
38 import re 43 import re
39 import shutil 44 import shutil
40 import subprocess 45 import subprocess
41 import sys 46 import sys
42 import tempfile 47 import tempfile
43 import threading 48 import threading
44 import urllib 49 import urllib
50 from distutils.version import LooseVersion
45 from xml.etree import ElementTree 51 from xml.etree import ElementTree
46 import zipfile 52 import zipfile
47 53
48 54
49 class PathContext(object): 55 class PathContext(object):
50 """A PathContext is used to carry the information used to construct URLs and 56 """A PathContext is used to carry the information used to construct URLs and
51 paths when dealing with the storage server and archives.""" 57 paths when dealing with the storage server and archives."""
52 def __init__(self, platform, good_revision, bad_revision): 58 def __init__(self, platform, good_revision, bad_revision, build_type):
53 super(PathContext, self).__init__() 59 super(PathContext, self).__init__()
54 # Store off the input parameters. 60 # Store off the input parameters.
55 self.platform = platform # What's passed in to the '-a/--archive' option. 61 self.platform = platform # What's passed in to the '-a/--archive' option.
56 self.good_revision = good_revision 62 self.good_revision = good_revision
57 self.bad_revision = bad_revision 63 self.bad_revision = bad_revision
64 self.build_type = build_type
Robert Sesek 2012/05/21 21:39:47 self.is_official per comment in Main()
anantha 2012/05/22 22:55:39 Done.
58 65
59 # The name of the ZIP file in a revision directory on the server. 66 # The name of the ZIP file in a revision directory on the server.
60 self.archive_name = None 67 self.archive_name = None
61 68
62 # Set some internal members: 69 # Set some internal members:
63 # _listing_platform_dir = Directory that holds revisions. Ends with a '/'. 70 # _listing_platform_dir = Directory that holds revisions. Ends with a '/'.
64 # _archive_extract_dir = Uncompressed directory in the archive_name file. 71 # _archive_extract_dir = Uncompressed directory in the archive_name file.
65 # _binary_name = The name of the executable to run. 72 # _binary_name = The name of the executable to run.
66 if self.platform == 'linux' or self.platform == 'linux64': 73 if self.platform == 'linux' or self.platform == 'linux64':
67 self._listing_platform_dir = 'Linux/' 74 self._listing_platform_dir = 'Linux/'
68 self.archive_name = 'chrome-linux.zip' 75 if build_type != 'Official':
Robert Sesek 2012/05/21 21:39:47 This is getting messy. Beak this down into two top
anantha 2012/05/22 22:55:39 Done.
69 self._archive_extract_dir = 'chrome-linux' 76 self._archive_extract_dir = 'chrome-linux'
77 else:
78 self._archive_extract_dir = 'chrome-lucid64bit'
70 self._binary_name = 'chrome' 79 self._binary_name = 'chrome'
71 # Linux and x64 share all the same path data except for the archive dir. 80 # Linux and x64 share all the same path data except for the archive dir.
72 if self.platform == 'linux64': 81 if self.platform == 'linux64':
73 self._listing_platform_dir = 'Linux_x64/' 82 if build_type != 'Official':
83 self.archive_name = 'chrome-linux.zip'
84 self._listing_platform_dir = 'Linux_x64/'
85 else:
86 self.archive_name = 'chrome-lucid64bit.zip'
87 self._listing_platform_dir = 'lucid64bit/'
88 else:
89 if build_type != 'Official':
90 self.archive_name = 'chrome-linux.zip'
91 self._listing_platform_dir = 'Linux_x32/'
92 else:
93 self.archive_name = 'chrome-lucid32bit.zip'
94 self._listing_platform_dir = 'lucid32bit/'
74 elif self.platform == 'mac': 95 elif self.platform == 'mac':
75 self._listing_platform_dir = 'Mac/' 96 if build_type != 'Official':
97 self._listing_platform_dir = 'Mac/'
98 self._binary_name = 'Chromium.app/Contents/MacOS/Chromium'
99 else:
100 self._listing_platform_dir = 'mac/'
101 self._binary_name = 'Google Chrome.app/Contents/MacOS/Google Chrome'
76 self.archive_name = 'chrome-mac.zip' 102 self.archive_name = 'chrome-mac.zip'
77 self._archive_extract_dir = 'chrome-mac' 103 self._archive_extract_dir = 'chrome-mac'
78 self._binary_name = 'Chromium.app/Contents/MacOS/Chromium' 104
79 elif self.platform == 'win': 105 elif self.platform == 'win':
80 self._listing_platform_dir = 'Win/' 106 if build_type != 'Official':
107 self._listing_platform_dir = 'Win/'
108 else:
109 self._listing_platform_dir = 'win/'
81 self.archive_name = 'chrome-win32.zip' 110 self.archive_name = 'chrome-win32.zip'
82 self._archive_extract_dir = 'chrome-win32' 111 self._archive_extract_dir = 'chrome-win32'
83 self._binary_name = 'chrome.exe' 112 self._binary_name = 'chrome.exe'
84 else: 113 else:
85 raise Exception('Invalid platform: %s' % self.platform) 114 raise Exception('Invalid platform: %s' % self.platform)
86 115
87 def GetListingURL(self, marker=None): 116 def GetListingURL(self, marker=None):
88 """Returns the URL for a directory listing, with an optional marker.""" 117 """Returns the URL for a directory listing, with an optional marker."""
89 marker_param = '' 118 marker_param = ''
90 if marker: 119 if marker:
91 marker_param = '&marker=' + str(marker) 120 marker_param = '&marker=' + str(marker)
92 return BASE_URL + '/?delimiter=/&prefix=' + self._listing_platform_dir + \ 121 return BASE_URL + '/?delimiter=/&prefix=' + self._listing_platform_dir + \
93 marker_param 122 marker_param
94 123
95 def GetDownloadURL(self, revision): 124 def GetDownloadURL(self, revision):
96 """Gets the download URL for a build archive of a specific revision.""" 125 """Gets the download URL for a build archive of a specific revision."""
97 return "%s/%s%d/%s" % ( 126 if self.build_type != 'Official':
98 BASE_URL, self._listing_platform_dir, revision, self.archive_name) 127 return "%s/%s%d/%s" % (
128 BASE_URL, self._listing_platform_dir, revision, self.archive_name)
129 else:
130 return "%s%s/%s%s" % (
131 OFFICIAL_BASE_URL, revision, self._listing_platform_dir,
132 self.archive_name)
99 133
100 def GetLastChangeURL(self): 134 def GetLastChangeURL(self):
101 """Returns a URL to the LAST_CHANGE file.""" 135 """Returns a URL to the LAST_CHANGE file."""
102 return BASE_URL + '/' + self._listing_platform_dir + 'LAST_CHANGE' 136 return BASE_URL + '/' + self._listing_platform_dir + 'LAST_CHANGE'
103 137
104 def GetLaunchPath(self): 138 def GetLaunchPath(self):
105 """Returns a relative path (presumably from the archive extraction location) 139 """Returns a relative path (presumably from the archive extraction location)
106 that is used to run the executable.""" 140 that is used to run the executable."""
107 return os.path.join(self._archive_extract_dir, self._binary_name) 141 return os.path.join(self._archive_extract_dir, self._binary_name)
108 142
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
143 # trailing slash to just have a number. 177 # trailing slash to just have a number.
144 revisions = [] 178 revisions = []
145 for prefix in all_prefixes: 179 for prefix in all_prefixes:
146 revnum = prefix.text[prefix_len:-1] 180 revnum = prefix.text[prefix_len:-1]
147 try: 181 try:
148 revnum = int(revnum) 182 revnum = int(revnum)
149 revisions.append(revnum) 183 revisions.append(revnum)
150 except ValueError: 184 except ValueError:
151 pass 185 pass
152 return (revisions, next_marker) 186 return (revisions, next_marker)
187 if self.build_type != 'Official':
Robert Sesek 2012/05/21 21:39:47 This logic doesn't belong in this function, as it'
anantha 2012/05/22 22:55:39 Correct. I forgot to clean this up.
188 # Fetch the first list of revisions.
189 (revisions, next_marker) = _FetchAndParse(self.GetListingURL())
153 190
154 # Fetch the first list of revisions. 191 # If the result list was truncated, refetch with the next marker. Do this
155 (revisions, next_marker) = _FetchAndParse(self.GetListingURL()) 192 # until an entire directory listing is done.
156 193 while next_marker:
157 # If the result list was truncated, refetch with the next marker. Do this 194 next_url = self.GetListingURL(next_marker)
158 # until an entire directory listing is done. 195 (new_revisions, next_marker) = _FetchAndParse(next_url)
159 while next_marker: 196 revisions.extend(new_revisions)
160 next_url = self.GetListingURL(next_marker) 197 return revisions
161 (new_revisions, next_marker) = _FetchAndParse(next_url) 198 else:
162 revisions.extend(new_revisions) 199 handle = urllib.urlopen(OFFICIAL_BASE_URL)
163 200 dirindex = handle.read()
164 return revisions 201 handle.close()
202 return re.findall(r'<a href="([0-9][0-9].*)/">', dirindex)
165 203
166 def GetRevList(self): 204 def GetRevList(self):
167 """Gets the list of revision numbers between self.good_revision and 205 """Gets the list of revision numbers between self.good_revision and
168 self.bad_revision.""" 206 self.bad_revision."""
169 # Download the revlist and filter for just the range between good and bad. 207 # Download the revlist and filter for just the range between good and bad.
170 minrev = self.good_revision 208 minrev = self.good_revision
171 maxrev = self.bad_revision 209 maxrev = self.bad_revision
172 revlist = map(int, self.ParseDirectoryIndex()) 210 revlist = map(int, self.ParseDirectoryIndex())
173 revlist = [x for x in revlist if x >= minrev and x <= maxrev] 211 revlist = [x for x in revlist if x >= int(minrev) and x <= int(maxrev)]
174 revlist.sort() 212 revlist.sort()
175 return revlist 213 return revlist
176 214
215 def GetBuildsList(self):
Robert Sesek 2012/05/21 21:39:47 naming: GetOfficialBuildsList
anantha 2012/05/22 22:55:39 Done.
216 """Gets the list of revision numbers between self.good_revision and
Robert Sesek 2012/05/21 21:39:47 nit: this isn't returning revision numbers, this i
anantha 2012/05/22 22:55:39 Done.
anantha 2012/05/22 22:55:39 Done.
217 self.bad_revision."""
218 # Download the revlist and filter for just the range between good and bad.
219 minrev = self.good_revision
220 maxrev = self.bad_revision
221 handle = urllib.urlopen(OFFICIAL_BASE_URL)
222 dirindex = handle.read()
223 handle.close()
224 build_numbers = re.findall(r'<a href="([0-9][0-9].*)/">', dirindex)
Robert Sesek 2012/05/21 21:39:47 This logic is duplicated above.
anantha 2012/05/22 22:55:39 Removed from above.
225 final_list = []
226 start_index = '0'
227 end_index = '0'
228 i = 0
229
230 parsed_build_numbers = [LooseVersion(x) for x in build_numbers]
231 for build_number in sorted(parsed_build_numbers):
232 path = OFFICIAL_BASE_URL + str(build_number) + '/' + \
Robert Sesek 2012/05/21 21:39:47 nit: should be indented 2
anantha 2012/05/22 22:55:39 Done.
233 self._listing_platform_dir + self.archive_name
234 i = i+1
235 try:
236 connection = urllib.urlopen(path)
Robert Sesek 2012/05/21 21:39:47 nit: over-indented
anantha 2012/05/22 22:55:39 Done.
anantha 2012/05/22 22:55:39 Done.
237 connection.close()
238 final_list.append(str(build_number))
239 if str(build_number) == minrev:
240 start_index = i
241 if str(build_number) == maxrev:
242 end_index = i
243 except urllib.HTTPError, e:
244 print e.getcode()
245 return final_list[start_index:end_index]
177 246
178 def UnzipFilenameToDir(filename, dir): 247 def UnzipFilenameToDir(filename, dir):
179 """Unzip |filename| to directory |dir|.""" 248 """Unzip |filename| to directory |dir|."""
180 cwd = os.getcwd() 249 cwd = os.getcwd()
181 if not os.path.isabs(filename): 250 if not os.path.isabs(filename):
182 filename = os.path.join(cwd, filename) 251 filename = os.path.join(cwd, filename)
183 zf = zipfile.ZipFile(filename) 252 zf = zipfile.ZipFile(filename)
184 # Make base. 253 # Make base.
185 try: 254 try:
186 if not os.path.isdir(dir): 255 if not os.path.isdir(dir):
(...skipping 26 matching lines...) Expand all
213 @param rev The Chromium revision number/tag to download. 282 @param rev The Chromium revision number/tag to download.
214 @param filename The destination for the downloaded file. 283 @param filename The destination for the downloaded file.
215 @param quit_event A threading.Event which will be set by the master thread to 284 @param quit_event A threading.Event which will be set by the master thread to
216 indicate that the download should be aborted. 285 indicate that the download should be aborted.
217 @param progress_event A threading.Event which will be set by the master thread 286 @param progress_event A threading.Event which will be set by the master thread
218 to indicate that the progress of the download should be 287 to indicate that the progress of the download should be
219 displayed. 288 displayed.
220 """ 289 """
221 def ReportHook(blocknum, blocksize, totalsize): 290 def ReportHook(blocknum, blocksize, totalsize):
222 if quit_event and quit_event.isSet(): 291 if quit_event and quit_event.isSet():
223 raise RuntimeError("Aborting download of revision %d" % rev) 292 if context.build_type != 'Official':
Robert Sesek 2012/05/21 21:39:47 Don't do this. Just cast rev to a string and forma
anantha 2012/05/22 22:55:39 Done.
293 raise RuntimeError("Aborting download of revision %d" % rev)
294 else:
295 raise RuntimeError("Aborting download of revision %s" % rev)
224 if progress_event and progress_event.isSet(): 296 if progress_event and progress_event.isSet():
225 size = blocknum * blocksize 297 size = blocknum * blocksize
226 if totalsize == -1: # Total size not known. 298 if totalsize == -1: # Total size not known.
227 progress = "Received %d bytes" % size 299 progress = "Received %d bytes" % size
228 else: 300 else:
229 size = min(totalsize, size) 301 size = min(totalsize, size)
230 progress = "Received %d of %d bytes, %.2f%%" % ( 302 progress = "Received %d of %d bytes, %.2f%%" % (
231 size, totalsize, 100.0 * size / totalsize) 303 size, totalsize, 100.0 * size / totalsize)
232 # Send a \r to let all progress messages use just one line of output. 304 # Send a \r to let all progress messages use just one line of output.
233 sys.stdout.write("\r" + progress) 305 sys.stdout.write("\r" + progress)
234 sys.stdout.flush() 306 sys.stdout.flush()
235 307
236 download_url = context.GetDownloadURL(rev) 308 download_url = context.GetDownloadURL(rev)
237 try: 309 try:
238 urllib.urlretrieve(download_url, filename, ReportHook) 310 urllib.urlretrieve(download_url, filename, ReportHook)
239 if progress_event and progress_event.isSet():
Robert Sesek 2012/05/21 21:39:47 Why'd you remove this?
240 print
241 except RuntimeError, e: 311 except RuntimeError, e:
242 pass 312 pass
Robert Sesek 2012/05/21 21:39:47 nit: over-indented
anantha 2012/05/22 22:55:39 Done.
243 313
244 314
245 def RunRevision(context, revision, zipfile, profile, num_runs, args): 315 def RunRevision(context, revision, zipfile, profile, num_runs, args):
246 """Given a zipped revision, unzip it and run the test.""" 316 """Given a zipped revision, unzip it and run the test."""
247 print "Trying revision %d..." % revision 317 if context.build_type != 'Official':
318 print "Trying revision %d..." % revision
319 else:
320 print "Trying revision %s..." % revision
248 321
249 # Create a temp directory and unzip the revision into it. 322 # Create a temp directory and unzip the revision into it.
250 cwd = os.getcwd() 323 cwd = os.getcwd()
251 tempdir = tempfile.mkdtemp(prefix='bisect_tmp') 324 tempdir = tempfile.mkdtemp(prefix='bisect_tmp')
252 UnzipFilenameToDir(zipfile, tempdir) 325 UnzipFilenameToDir(zipfile, tempdir)
253 os.chdir(tempdir) 326 os.chdir(tempdir)
254 327
255 # Run the build as many times as specified. 328 if context.platform == 'linux' or context.platform == 'linux64':
256 testargs = [context.GetLaunchPath(), '--user-data-dir=%s' % profile] + args 329 if context.build_type == 'Official':
330 # Run the build as many times as specified.
331 testargs = [context.GetLaunchPath(), '--no-sandbox',
Robert Sesek 2012/05/21 21:39:47 Why this? I think this should just be done manuall
anantha 2012/05/22 22:55:39 Looks like we need root permission to setup the sa
332 '--user-data-dir=%s' % profile] + args
333 else:
334 # Run the build as many times as specified.
335 testargs = [context.GetLaunchPath(),
336 '--user-data-dir=%s' % profile] + args
337 else:
338 # Run the build as many times as specified.
339 testargs = [context.GetLaunchPath(), '--user-data-dir=%s' % profile] + args
340
257 for i in range(0, num_runs): 341 for i in range(0, num_runs):
258 subproc = subprocess.Popen(testargs, 342 subproc = subprocess.Popen(testargs,
259 bufsize=-1, 343 bufsize=-1,
260 stdout=subprocess.PIPE, 344 stdout=subprocess.PIPE,
261 stderr=subprocess.PIPE) 345 stderr=subprocess.PIPE)
262 (stdout, stderr) = subproc.communicate() 346 (stdout, stderr) = subproc.communicate()
263 347
264 os.chdir(cwd) 348 os.chdir(cwd)
265 try: 349 try:
266 shutil.rmtree(tempdir, True) 350 shutil.rmtree(tempdir, True)
267 except Exception, e: 351 except Exception, e:
268 pass 352 pass
269 353
270 return (subproc.returncode, stdout, stderr) 354 return (subproc.returncode, stdout, stderr)
271 355
272 356
273 def AskIsGoodBuild(rev, status, stdout, stderr): 357 def AskIsGoodBuild(rev, build_type, status, stdout, stderr):
274 """Ask the user whether build |rev| is good or bad.""" 358 """Ask the user whether build |rev| is good or bad."""
275 # Loop until we get a response that we can parse. 359 # Loop until we get a response that we can parse.
276 while True: 360 while True:
277 response = raw_input('Revision %d is [(g)ood/(b)ad/(q)uit]: ' % int(rev)) 361 if build_type != 'Official':
362 response = raw_input('Revision %d is [(g)ood/(b)ad/(q)uit]: ' % int(rev))
363 else:
364 response = raw_input('Revision %s is [(g)ood/(b)ad/(q)uit]: ' % str(rev))
278 if response and response in ('g', 'b'): 365 if response and response in ('g', 'b'):
279 return response == 'g' 366 return response == 'g'
280 if response and response == 'q': 367 if response and response == 'q':
281 raise SystemExit() 368 raise SystemExit()
282 369
283 370
284 def Bisect(platform, 371 def Bisect(platform, build_type,
Robert Sesek 2012/05/21 21:39:47 nit: goes on its own line, and the parameter also
anantha 2012/05/22 22:55:39 Done.
285 good_rev=0, 372 good_rev=0,
286 bad_rev=0, 373 bad_rev=0,
287 num_runs=1, 374 num_runs=1,
288 try_args=(), 375 try_args=(),
289 profile=None, 376 profile=None,
290 predicate=AskIsGoodBuild): 377 predicate=AskIsGoodBuild):
291 """Given known good and known bad revisions, run a binary search on all 378 """Given known good and known bad revisions, run a binary search on all
292 archived revisions to determine the last known good revision. 379 archived revisions to determine the last known good revision.
293 380
294 @param platform Which build to download/run ('mac', 'win', 'linux64', etc.). 381 @param platform Which build to download/run ('mac', 'win', 'linux64', etc.).
(...skipping 16 matching lines...) Expand all
311 - If rev 50 is good, the download of rev 25 is cancelled, and the next test 398 - If rev 50 is good, the download of rev 25 is cancelled, and the next test
312 is run on rev 75. 399 is run on rev 75.
313 400
314 - If rev 50 is bad, the download of rev 75 is cancelled, and the next test 401 - If rev 50 is bad, the download of rev 75 is cancelled, and the next test
315 is run on rev 25. 402 is run on rev 25.
316 """ 403 """
317 404
318 if not profile: 405 if not profile:
319 profile = 'profile' 406 profile = 'profile'
320 407
321 context = PathContext(platform, good_rev, bad_rev) 408 context = PathContext(platform, good_rev, bad_rev, build_type)
322 cwd = os.getcwd() 409 cwd = os.getcwd()
323 410
324 _GetDownloadPath = lambda rev: os.path.join(cwd, 411
325 '%d-%s' % (rev, context.archive_name))
326 412
327 print "Downloading list of known revisions..." 413 print "Downloading list of known revisions..."
328 414 if build_type != 'Official':
329 revlist = context.GetRevList() 415 _GetDownloadPath = lambda rev: os.path.join(cwd,
416 '%d-%s' % (rev, context.archive_name))
417 revlist = context.GetRevList()
418 else:
419 _GetDownloadPath = lambda rev: os.path.join(cwd,
420 '%s-%s' % (rev, context.archive_name))
421 revlist = context.GetBuildsList()
330 422
331 # Get a list of revisions to bisect across. 423 # Get a list of revisions to bisect across.
332 if len(revlist) < 2: # Don't have enough builds to bisect. 424 if len(revlist) < 2: # Don't have enough builds to bisect.
333 msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist 425 msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist
334 raise RuntimeError(msg) 426 raise RuntimeError(msg)
335 427
336 # Figure out our bookends and first pivot point; fetch the pivot revision. 428 # Figure out our bookends and first pivot point; fetch the pivot revision.
337 good = 0 429 good = 0
338 bad = len(revlist) - 1 430 bad = len(revlist) - 1
339 pivot = bad / 2 431 pivot = bad / 2
340 rev = revlist[pivot] 432 rev = revlist[pivot]
341 zipfile = _GetDownloadPath(rev) 433 zipfile = _GetDownloadPath(rev)
342 progress_event = threading.Event() 434 progress_event = threading.Event()
343 progress_event.set() 435 progress_event.set()
344 print "Downloading revision %d..." % rev 436 if build_type != 'Official':
437 print "Downloading revision %d..." % rev
438 else:
439 print "Downloading revision %s..." % rev
345 FetchRevision(context, rev, zipfile, 440 FetchRevision(context, rev, zipfile,
346 quit_event=None, progress_event=progress_event) 441 quit_event=None, progress_event=progress_event)
347 442
348 # Binary search time! 443 # Binary search time!
349 while zipfile and bad - good > 1: 444 while zipfile and bad - good > 1:
350 # Pre-fetch next two possible pivots 445 # Pre-fetch next two possible pivots
351 # - down_pivot is the next revision to check if the current revision turns 446 # - down_pivot is the next revision to check if the current revision turns
352 # out to be bad. 447 # out to be bad.
353 # - up_pivot is the next revision to check if the current revision turns 448 # - up_pivot is the next revision to check if the current revision turns
354 # out to be good. 449 # out to be good.
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
393 profile, 488 profile,
394 num_runs, 489 num_runs,
395 try_args) 490 try_args)
396 os.unlink(zipfile) 491 os.unlink(zipfile)
397 zipfile = None 492 zipfile = None
398 493
399 # Call the predicate function to see if the current revision is good or bad. 494 # Call the predicate function to see if the current revision is good or bad.
400 # On that basis, kill one of the background downloads and complete the 495 # On that basis, kill one of the background downloads and complete the
401 # other, as described in the comments above. 496 # other, as described in the comments above.
402 try: 497 try:
403 if predicate(rev, status, stdout, stderr): 498 if predicate(rev, build_type, status, stdout, stderr):
404 good = pivot 499 good = pivot
405 if down_thread: 500 if down_thread:
406 down_quit_event.set() # Kill the download of older revision. 501 down_quit_event.set() # Kill the download of older revision.
407 down_thread.join() 502 down_thread.join()
408 os.unlink(down_zipfile) 503 os.unlink(down_zipfile)
409 if up_thread: 504 if up_thread:
410 print "Downloading revision %d..." % up_rev 505 if build_type == 'Official':
506 print "Downloading revision %s..." % up_rev
507 else:
508 print "Downloading revision %d..." % up_rev
411 up_progress_event.set() # Display progress of download. 509 up_progress_event.set() # Display progress of download.
412 up_thread.join() # Wait for newer revision to finish downloading. 510 up_thread.join() # Wait for newer revision to finish downloading.
413 pivot = up_pivot 511 pivot = up_pivot
414 zipfile = up_zipfile 512 zipfile = up_zipfile
415 else: 513 else:
416 bad = pivot 514 bad = pivot
417 if up_thread: 515 if up_thread:
418 up_quit_event.set() # Kill download of newer revision. 516 up_quit_event.set() # Kill download of newer revision.
419 up_thread.join() 517 up_thread.join()
420 os.unlink(up_zipfile) 518 os.unlink(up_zipfile)
421 if down_thread: 519 if down_thread:
422 print "Downloading revision %d..." % down_rev 520 if build_type == 'Official':
521 print "Downloading revision %s..." % down_rev
522 else:
523 print "Downloading revision %d..." % down_rev
423 down_progress_event.set() # Display progress of download. 524 down_progress_event.set() # Display progress of download.
424 down_thread.join() # Wait for older revision to finish downloading. 525 down_thread.join() # Wait for older revision to finish downloading.
425 pivot = down_pivot 526 pivot = down_pivot
426 zipfile = down_zipfile 527 zipfile = down_zipfile
427 except SystemExit: 528 except SystemExit:
428 print "Cleaning up..." 529 print "Cleaning up..."
429 for f in [_GetDownloadPath(revlist[down_pivot]), 530 for f in [_GetDownloadPath(revlist[down_pivot]),
430 _GetDownloadPath(revlist[up_pivot])]: 531 _GetDownloadPath(revlist[up_pivot])]:
431 try: 532 try:
432 os.unlink(f) 533 os.unlink(f)
(...skipping 26 matching lines...) Expand all
459 '\n' 560 '\n'
460 'Tip: add "-- --no-first-run" to bypass the first run prompts.') 561 'Tip: add "-- --no-first-run" to bypass the first run prompts.')
461 parser = optparse.OptionParser(usage=usage) 562 parser = optparse.OptionParser(usage=usage)
462 # Strangely, the default help output doesn't include the choice list. 563 # Strangely, the default help output doesn't include the choice list.
463 choices = ['mac', 'win', 'linux', 'linux64'] 564 choices = ['mac', 'win', 'linux', 'linux64']
464 # linux-chromiumos lacks a continuous archive http://crbug.com/78158 565 # linux-chromiumos lacks a continuous archive http://crbug.com/78158
465 parser.add_option('-a', '--archive', 566 parser.add_option('-a', '--archive',
466 choices = choices, 567 choices = choices,
467 help = 'The buildbot archive to bisect [%s].' % 568 help = 'The buildbot archive to bisect [%s].' %
468 '|'.join(choices)) 569 '|'.join(choices))
469 parser.add_option('-b', '--bad', type = 'int', 570 parser.add_option('-o', '--build_type', type = 'str',
Robert Sesek 2012/05/21 21:39:47 This should just be type bool and make the flag --
anantha 2012/05/22 22:55:39 Done.
571 help = 'Specify if it is a chromium or official build.')
572 parser.add_option('-b', '--bad', type = 'str',
470 help = 'The bad revision to bisect to.') 573 help = 'The bad revision to bisect to.')
471 parser.add_option('-g', '--good', type = 'int', 574 parser.add_option('-g', '--good', type = 'str',
472 help = 'The last known good revision to bisect from.') 575 help = 'The last known good revision to bisect from.')
473 parser.add_option('-p', '--profile', '--user-data-dir', type = 'str', 576 parser.add_option('-p', '--profile', '--user-data-dir', type = 'str',
474 help = 'Profile to use; this will not reset every run. ' + 577 help = 'Profile to use; this will not reset every run. ' +
475 'Defaults to a clean profile.', default = 'profile') 578 'Defaults to a clean profile.', default = 'profile')
476 parser.add_option('-t', '--times', type = 'int', 579 parser.add_option('-t', '--times', type = 'int',
477 help = 'Number of times to run each build before asking ' + 580 help = 'Number of times to run each build before asking ' +
478 'if it\'s good or bad. Temporary profiles are reused.', 581 'if it\'s good or bad. Temporary profiles are reused.',
479 default = 1) 582 default = 1)
480 (opts, args) = parser.parse_args() 583 (opts, args) = parser.parse_args()
481 584
482 if opts.archive is None: 585 if opts.archive is None:
483 print 'Error: missing required parameter: --archive' 586 print 'Error: missing required parameter: --archive'
484 print 587 print
485 parser.print_help() 588 parser.print_help()
486 return 1 589 return 1
487 590
488 if opts.bad and opts.good and (opts.good > opts.bad): 591 if opts.bad and opts.good and (opts.good > opts.bad):
489 print ('The good revision (%d) must precede the bad revision (%d).\n' % 592 print ('The good revision (%d) must precede the bad revision (%d).\n' %
490 (opts.good, opts.bad)) 593 (opts.good, opts.bad))
491 parser.print_help() 594 parser.print_help()
492 return 1 595 return 1
493 596
494 # Create the context. Initialize 0 for the revisions as they are set below. 597 # Create the context. Initialize 0 for the revisions as they are set below.
495 context = PathContext(opts.archive, 0, 0) 598 context = PathContext(opts.archive, 0, 0, opts.build_type)
599
600 if opts.build_type == 'Official':
601 if opts.bad is None:
Robert Sesek 2012/05/21 21:39:47 Join this and the previous line with an |and|
anantha 2012/05/22 22:55:39 Done.
602 print ' Need a bad build range'
Robert Sesek 2012/05/21 21:39:47 print >>sys.stderr, 'Needs...'
anantha 2012/05/22 22:55:39 Done.
Robert Sesek 2012/05/23 21:24:00 Not done. Also, maybe change the error to "Bisecti
603 print
604 parser.print_help()
605 return 1
496 606
497 # Pick a starting point, try to get HEAD for this. 607 # Pick a starting point, try to get HEAD for this.
498 if opts.bad: 608 if opts.bad:
499 bad_rev = opts.bad 609 bad_rev = opts.bad
500 else: 610 else:
501 bad_rev = 0 611 bad_rev = 0
502 try: 612 try:
503 # Location of the latest build revision number 613 # Location of the latest build revision number
504 nh = urllib.urlopen(context.GetLastChangeURL()) 614 nh = urllib.urlopen(context.GetLastChangeURL())
505 latest = int(nh.read()) 615 latest = int(nh.read())
(...skipping 16 matching lines...) Expand all
522 except Exception, e: 632 except Exception, e:
523 pass 633 pass
524 634
525 if opts.times < 1: 635 if opts.times < 1:
526 print('Number of times to run (%d) must be greater than or equal to 1.' % 636 print('Number of times to run (%d) must be greater than or equal to 1.' %
527 opts.times) 637 opts.times)
528 parser.print_help() 638 parser.print_help()
529 return 1 639 return 1
530 640
531 (last_known_good_rev, first_known_bad_rev) = Bisect( 641 (last_known_good_rev, first_known_bad_rev) = Bisect(
532 opts.archive, good_rev, bad_rev, opts.times, args, opts.profile) 642 opts.archive, opts.build_type, good_rev, bad_rev, opts.times, args,
643 opts.profile)
533 644
534 # Get corresponding webkit revisions. 645 # Get corresponding webkit revisions.
535 try: 646 try:
536 last_known_good_webkit_rev = GetWebKitRevisionForChromiumRevision( 647 last_known_good_webkit_rev = GetWebKitRevisionForChromiumRevision(
537 last_known_good_rev) 648 last_known_good_rev)
538 first_known_bad_webkit_rev = GetWebKitRevisionForChromiumRevision( 649 first_known_bad_webkit_rev = GetWebKitRevisionForChromiumRevision(
539 first_known_bad_rev) 650 first_known_bad_rev)
540 except Exception, e: 651 except Exception, e:
541 # Silently ignore the failure. 652 # Silently ignore the failure.
542 last_known_good_webkit_rev, first_known_bad_webkit_rev = 0, 0 653 last_known_good_webkit_rev, first_known_bad_webkit_rev = 0, 0
543 654
544 # We're done. Let the user know the results in an official manner. 655 # We're done. Let the user know the results in an official manner.
545 print('You are probably looking for build %d.' % first_known_bad_rev) 656 if opts.build_type != 'Official':
657 print('You are probably looking for build %d.' % first_known_bad_rev)
658 else:
659 print('You are probably looking for build %s.' % first_known_bad_rev)
660
546 if last_known_good_webkit_rev != first_known_bad_webkit_rev: 661 if last_known_good_webkit_rev != first_known_bad_webkit_rev:
547 print 'WEBKIT CHANGELOG URL:' 662 print 'WEBKIT CHANGELOG URL:'
548 print WEBKIT_CHANGELOG_URL % (first_known_bad_webkit_rev, 663 print WEBKIT_CHANGELOG_URL % (first_known_bad_webkit_rev,
549 last_known_good_webkit_rev) 664 last_known_good_webkit_rev)
550 print 'CHANGELOG URL:' 665 print 'CHANGELOG URL:'
551 print CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev) 666 if opts.build_type == 'Official':
552 print 'Built at revision:' 667 print OFFICIAL_CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev)
553 print BUILD_VIEWVC_URL % first_known_bad_rev 668 else:
669 print CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev)
670 print 'Built at revision:'
671 print BUILD_VIEWVC_URL % first_known_bad_rev
554 672
555 673
556 if __name__ == '__main__': 674 if __name__ == '__main__':
557 sys.exit(main()) 675 sys.exit(main())
Robert Sesek 2012/05/21 21:39:47 What's this change?
anantha 2012/05/22 22:55:39 I somehow ended up adding two spaces. So deleted t
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