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

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'
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'
24 27
28 # Official Changelogs URL.
29 OFFICIAL_CHANGELOG_URL = 'http://omahaproxy.appspot.com/'\
30 'changelog?old_version=%s&new_version=%s'
31
25 # DEPS file URL. 32 # DEPS file URL.
26 DEPS_FILE= 'http://src.chromium.org/viewvc/chrome/trunk/src/DEPS?revision=%d' 33 DEPS_FILE= 'http://src.chromium.org/viewvc/chrome/trunk/src/DEPS?revision=%d'
27
28 # WebKit Changelogs URL. 34 # WebKit Changelogs URL.
29 WEBKIT_CHANGELOG_URL = 'http://trac.webkit.org/log/' \ 35 WEBKIT_CHANGELOG_URL = 'http://trac.webkit.org/log/' \
30 'trunk/?rev=%d&stop_rev=%d&verbose=on' 36 'trunk/?rev=%d&stop_rev=%d&verbose=on'
31 37
32 ############################################################################### 38 ###############################################################################
33 39
34 import math 40 import math
35 import optparse 41 import optparse
36 import os 42 import os
37 import pipes 43 import pipes
38 import re 44 import re
39 import shutil 45 import shutil
40 import subprocess 46 import subprocess
41 import sys 47 import sys
42 import tempfile 48 import tempfile
43 import threading 49 import threading
44 import urllib 50 import urllib
51 from distutils.version import LooseVersion
45 from xml.etree import ElementTree 52 from xml.etree import ElementTree
46 import zipfile 53 import zipfile
47 54
48 55
49 class PathContext(object): 56 class PathContext(object):
50 """A PathContext is used to carry the information used to construct URLs and 57 """A PathContext is used to carry the information used to construct URLs and
51 paths when dealing with the storage server and archives.""" 58 paths when dealing with the storage server and archives."""
52 def __init__(self, platform, good_revision, bad_revision): 59 def __init__(self, platform, good_revision, bad_revision, is_official):
53 super(PathContext, self).__init__() 60 super(PathContext, self).__init__()
54 # Store off the input parameters. 61 # Store off the input parameters.
55 self.platform = platform # What's passed in to the '-a/--archive' option. 62 self.platform = platform # What's passed in to the '-a/--archive' option.
56 self.good_revision = good_revision 63 self.good_revision = good_revision
57 self.bad_revision = bad_revision 64 self.bad_revision = bad_revision
65 self.is_official = is_official
58 66
59 # The name of the ZIP file in a revision directory on the server. 67 # The name of the ZIP file in a revision directory on the server.
60 self.archive_name = None 68 self.archive_name = None
61 69
62 # Set some internal members: 70 # Set some internal members:
63 # _listing_platform_dir = Directory that holds revisions. Ends with a '/'. 71 # _listing_platform_dir = Directory that holds revisions. Ends with a '/'.
64 # _archive_extract_dir = Uncompressed directory in the archive_name file. 72 # _archive_extract_dir = Uncompressed directory in the archive_name file.
65 # _binary_name = The name of the executable to run. 73 # _binary_name = The name of the executable to run.
66 if self.platform == 'linux' or self.platform == 'linux64': 74 if self.platform == 'linux' or self.platform == 'linux64':
67 self._listing_platform_dir = 'Linux/'
68 self.archive_name = 'chrome-linux.zip'
69 self._archive_extract_dir = 'chrome-linux'
70 self._binary_name = 'chrome' 75 self._binary_name = 'chrome'
71 # Linux and x64 share all the same path data except for the archive dir.
72 if self.platform == 'linux64':
73 self._listing_platform_dir = 'Linux_x64/'
74 elif self.platform == 'mac': 76 elif self.platform == 'mac':
75 self._listing_platform_dir = 'Mac/'
76 self.archive_name = 'chrome-mac.zip' 77 self.archive_name = 'chrome-mac.zip'
77 self._archive_extract_dir = 'chrome-mac' 78 self._archive_extract_dir = 'chrome-mac'
78 self._binary_name = 'Chromium.app/Contents/MacOS/Chromium'
79 elif self.platform == 'win': 79 elif self.platform == 'win':
80 self._listing_platform_dir = 'Win/'
81 self.archive_name = 'chrome-win32.zip' 80 self.archive_name = 'chrome-win32.zip'
82 self._archive_extract_dir = 'chrome-win32' 81 self._archive_extract_dir = 'chrome-win32'
83 self._binary_name = 'chrome.exe' 82 self._binary_name = 'chrome.exe'
84 else: 83 else:
85 raise Exception('Invalid platform: %s' % self.platform) 84 raise Exception('Invalid platform: %s' % self.platform)
86 85
86 if is_official:
87 if self.platform == 'linux':
88 self._listing_platform_dir = 'lucid32bit/'
89 self.archive_name = 'chrome-lucid32bit.zip'
90 self._archive_extract_dir = 'chrome-lucid32bit'
91 elif self.platform == 'linux64':
92 self._listing_platform_dir = 'lucid64bit/'
93 self.archive_name = 'chrome-lucid64bit.zip'
94 self._archive_extract_dir = 'chrome-lucid64bit'
95 elif self.platform == 'mac':
96 self._listing_platform_dir = 'mac/'
97 self._binary_name = 'Google Chrome.app/Contents/MacOS/Google Chrome'
98 elif self.platform == 'win':
99 self._listing_platform_dir = 'win/'
100 else:
101 if self.platform == 'linux' or self.platform == 'linux64':
102 self.archive_name = 'chrome-linux.zip'
103 self._archive_extract_dir = 'chrome-linux'
104 if self.platform == 'linux':
105 self._listing_platform_dir = 'Linux/'
106 elif self.platform == 'linux64':
107 self._listing_platform_dir = 'Linux_x64/'
108 elif self.platform == 'mac':
109 self._listing_platform_dir = 'Mac/'
110 self._binary_name = 'Chromium.app/Contents/MacOS/Chromium'
111 elif self.platform == 'win':
112 self._listing_platform_dir = 'Win/'
113
87 def GetListingURL(self, marker=None): 114 def GetListingURL(self, marker=None):
88 """Returns the URL for a directory listing, with an optional marker.""" 115 """Returns the URL for a directory listing, with an optional marker."""
89 marker_param = '' 116 marker_param = ''
90 if marker: 117 if marker:
91 marker_param = '&marker=' + str(marker) 118 marker_param = '&marker=' + str(marker)
92 return BASE_URL + '/?delimiter=/&prefix=' + self._listing_platform_dir + \ 119 return BASE_URL + '/?delimiter=/&prefix=' + self._listing_platform_dir + \
93 marker_param 120 marker_param
94 121
95 def GetDownloadURL(self, revision): 122 def GetDownloadURL(self, revision):
96 """Gets the download URL for a build archive of a specific revision.""" 123 """Gets the download URL for a build archive of a specific revision."""
97 return "%s/%s%d/%s" % ( 124 if self.is_official:
98 BASE_URL, self._listing_platform_dir, revision, self.archive_name) 125 return "%s/%s/%s%s" % (
126 OFFICIAL_BASE_URL, revision, self._listing_platform_dir,
127 self.archive_name)
128 else:
129 return "%s/%s%s/%s" % (
130 BASE_URL, self._listing_platform_dir, revision, self.archive_name)
99 131
100 def GetLastChangeURL(self): 132 def GetLastChangeURL(self):
101 """Returns a URL to the LAST_CHANGE file.""" 133 """Returns a URL to the LAST_CHANGE file."""
102 return BASE_URL + '/' + self._listing_platform_dir + 'LAST_CHANGE' 134 return BASE_URL + '/' + self._listing_platform_dir + 'LAST_CHANGE'
103 135
104 def GetLaunchPath(self): 136 def GetLaunchPath(self):
105 """Returns a relative path (presumably from the archive extraction location) 137 """Returns a relative path (presumably from the archive extraction location)
106 that is used to run the executable.""" 138 that is used to run the executable."""
107 return os.path.join(self._archive_extract_dir, self._binary_name) 139 return os.path.join(self._archive_extract_dir, self._binary_name)
108 140
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
143 # trailing slash to just have a number. 175 # trailing slash to just have a number.
144 revisions = [] 176 revisions = []
145 for prefix in all_prefixes: 177 for prefix in all_prefixes:
146 revnum = prefix.text[prefix_len:-1] 178 revnum = prefix.text[prefix_len:-1]
147 try: 179 try:
148 revnum = int(revnum) 180 revnum = int(revnum)
149 revisions.append(revnum) 181 revisions.append(revnum)
150 except ValueError: 182 except ValueError:
151 pass 183 pass
152 return (revisions, next_marker) 184 return (revisions, next_marker)
153 185
154 # Fetch the first list of revisions. 186 # Fetch the first list of revisions.
155 (revisions, next_marker) = _FetchAndParse(self.GetListingURL()) 187 (revisions, next_marker) = _FetchAndParse(self.GetListingURL())
156 188
157 # If the result list was truncated, refetch with the next marker. Do this 189 # If the result list was truncated, refetch with the next marker. Do this
158 # until an entire directory listing is done. 190 # until an entire directory listing is done.
159 while next_marker: 191 while next_marker:
160 next_url = self.GetListingURL(next_marker) 192 next_url = self.GetListingURL(next_marker)
161 (new_revisions, next_marker) = _FetchAndParse(next_url) 193 (new_revisions, next_marker) = _FetchAndParse(next_url)
162 revisions.extend(new_revisions) 194 revisions.extend(new_revisions)
163
164 return revisions 195 return revisions
165 196
166 def GetRevList(self): 197 def GetRevList(self):
167 """Gets the list of revision numbers between self.good_revision and 198 """Gets the list of revision numbers between self.good_revision and
168 self.bad_revision.""" 199 self.bad_revision."""
169 # Download the revlist and filter for just the range between good and bad. 200 # Download the revlist and filter for just the range between good and bad.
170 minrev = self.good_revision 201 minrev = self.good_revision
171 maxrev = self.bad_revision 202 maxrev = self.bad_revision
172 revlist = map(int, self.ParseDirectoryIndex()) 203 revlist = map(int, self.ParseDirectoryIndex())
173 revlist = [x for x in revlist if x >= minrev and x <= maxrev] 204 revlist = [x for x in revlist if x >= int(minrev) and x <= int(maxrev)]
174 revlist.sort() 205 revlist.sort()
175 return revlist 206 return revlist
176 207
208 def GetOfficialBuildsList(self):
209 """Gets the list of official build numbers between self.good_revision and
210 self.bad_revision."""
211 # Download the revlist and filter for just the range between good and bad.
212 minrev = self.good_revision
213 maxrev = self.bad_revision
214 handle = urllib.urlopen(OFFICIAL_BASE_URL)
215 dirindex = handle.read()
216 handle.close()
217 build_numbers = re.findall(r'<a href="([0-9][0-9].*)/">', dirindex)
218 final_list = []
219 start_index = '0'
220 end_index = '0'
221 i = 0
222
223 parsed_build_numbers = [LooseVersion(x) for x in build_numbers]
224 for build_number in sorted(parsed_build_numbers):
225 path = OFFICIAL_BASE_URL + '/' + str(build_number) + '/' + \
226 self._listing_platform_dir + self.archive_name
227 i = i + 1
228 try:
229 connection = urllib.urlopen(path)
230 connection.close()
231 final_list.append(str(build_number))
232 if str(build_number) == minrev:
233 start_index = i
234 if str(build_number) == maxrev:
235 end_index = i
236 except urllib.HTTPError, e:
237 pass
238 return final_list[start_index:end_index]
177 239
178 def UnzipFilenameToDir(filename, dir): 240 def UnzipFilenameToDir(filename, dir):
179 """Unzip |filename| to directory |dir|.""" 241 """Unzip |filename| to directory |dir|."""
180 cwd = os.getcwd() 242 cwd = os.getcwd()
181 if not os.path.isabs(filename): 243 if not os.path.isabs(filename):
182 filename = os.path.join(cwd, filename) 244 filename = os.path.join(cwd, filename)
183 zf = zipfile.ZipFile(filename) 245 zf = zipfile.ZipFile(filename)
184 # Make base. 246 # Make base.
185 try: 247 try:
186 if not os.path.isdir(dir): 248 if not os.path.isdir(dir):
(...skipping 26 matching lines...) Expand all
213 @param rev The Chromium revision number/tag to download. 275 @param rev The Chromium revision number/tag to download.
214 @param filename The destination for the downloaded file. 276 @param filename The destination for the downloaded file.
215 @param quit_event A threading.Event which will be set by the master thread to 277 @param quit_event A threading.Event which will be set by the master thread to
216 indicate that the download should be aborted. 278 indicate that the download should be aborted.
217 @param progress_event A threading.Event which will be set by the master thread 279 @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 280 to indicate that the progress of the download should be
219 displayed. 281 displayed.
220 """ 282 """
221 def ReportHook(blocknum, blocksize, totalsize): 283 def ReportHook(blocknum, blocksize, totalsize):
222 if quit_event and quit_event.isSet(): 284 if quit_event and quit_event.isSet():
223 raise RuntimeError("Aborting download of revision %d" % rev) 285 raise RuntimeError("Aborting download of revision %s" % str(rev))
224 if progress_event and progress_event.isSet(): 286 if progress_event and progress_event.isSet():
225 size = blocknum * blocksize 287 size = blocknum * blocksize
226 if totalsize == -1: # Total size not known. 288 if totalsize == -1: # Total size not known.
227 progress = "Received %d bytes" % size 289 progress = "Received %d bytes" % size
228 else: 290 else:
229 size = min(totalsize, size) 291 size = min(totalsize, size)
230 progress = "Received %d of %d bytes, %.2f%%" % ( 292 progress = "Received %d of %d bytes, %.2f%%" % (
231 size, totalsize, 100.0 * size / totalsize) 293 size, totalsize, 100.0 * size / totalsize)
232 # Send a \r to let all progress messages use just one line of output. 294 # Send a \r to let all progress messages use just one line of output.
233 sys.stdout.write("\r" + progress) 295 sys.stdout.write("\r" + progress)
234 sys.stdout.flush() 296 sys.stdout.flush()
235 297
236 download_url = context.GetDownloadURL(rev) 298 download_url = context.GetDownloadURL(rev)
237 try: 299 try:
238 urllib.urlretrieve(download_url, filename, ReportHook) 300 urllib.urlretrieve(download_url, filename, ReportHook)
239 if progress_event and progress_event.isSet(): 301 if progress_event and progress_event.isSet():
240 print 302 print
241 except RuntimeError, e: 303 except RuntimeError, e:
242 pass 304 pass
243 305
244 306
245 def RunRevision(context, revision, zipfile, profile, num_runs, args): 307 def RunRevision(context, revision, zipfile, profile, num_runs, args):
246 """Given a zipped revision, unzip it and run the test.""" 308 """Given a zipped revision, unzip it and run the test."""
247 print "Trying revision %d..." % revision 309 print "Trying revision %s..." % str(revision)
248 310
249 # Create a temp directory and unzip the revision into it. 311 # Create a temp directory and unzip the revision into it.
250 cwd = os.getcwd() 312 cwd = os.getcwd()
251 tempdir = tempfile.mkdtemp(prefix='bisect_tmp') 313 tempdir = tempfile.mkdtemp(prefix='bisect_tmp')
252 UnzipFilenameToDir(zipfile, tempdir) 314 UnzipFilenameToDir(zipfile, tempdir)
253 os.chdir(tempdir) 315 os.chdir(tempdir)
254 316
255 # Run the build as many times as specified. 317 # Run the build as many times as specified.
256 testargs = [context.GetLaunchPath(), '--user-data-dir=%s' % profile] + args 318 testargs = [context.GetLaunchPath(), '--user-data-dir=%s' % profile] + args
319 # The sandbox must be run as root on Official Chrome, so bypass it.
320 if context.is_official and (context.platform == 'linux' or
321 context.platform == 'linux64'):
322 testargs.append('--no-sandbox')
323
257 for i in range(0, num_runs): 324 for i in range(0, num_runs):
258 subproc = subprocess.Popen(testargs, 325 subproc = subprocess.Popen(testargs,
259 bufsize=-1, 326 bufsize=-1,
260 stdout=subprocess.PIPE, 327 stdout=subprocess.PIPE,
261 stderr=subprocess.PIPE) 328 stderr=subprocess.PIPE)
262 (stdout, stderr) = subproc.communicate() 329 (stdout, stderr) = subproc.communicate()
263 330
264 os.chdir(cwd) 331 os.chdir(cwd)
265 try: 332 try:
266 shutil.rmtree(tempdir, True) 333 shutil.rmtree(tempdir, True)
267 except Exception, e: 334 except Exception, e:
268 pass 335 pass
269 336
270 return (subproc.returncode, stdout, stderr) 337 return (subproc.returncode, stdout, stderr)
271 338
272 339
273 def AskIsGoodBuild(rev, status, stdout, stderr): 340 def AskIsGoodBuild(rev, official_builds, status, stdout, stderr):
274 """Ask the user whether build |rev| is good or bad.""" 341 """Ask the user whether build |rev| is good or bad."""
275 # Loop until we get a response that we can parse. 342 # Loop until we get a response that we can parse.
276 while True: 343 while True:
277 response = raw_input('Revision %d is [(g)ood/(b)ad/(q)uit]: ' % int(rev)) 344 response = raw_input('Revision %s is [(g)ood/(b)ad/(q)uit]: ' % str(rev))
278 if response and response in ('g', 'b'): 345 if response and response in ('g', 'b'):
279 return response == 'g' 346 return response == 'g'
280 if response and response == 'q': 347 if response and response == 'q':
281 raise SystemExit() 348 raise SystemExit()
282 349
283 350
284 def Bisect(platform, 351 def Bisect(platform,
352 official_builds,
285 good_rev=0, 353 good_rev=0,
286 bad_rev=0, 354 bad_rev=0,
287 num_runs=1, 355 num_runs=1,
288 try_args=(), 356 try_args=(),
289 profile=None, 357 profile=None,
290 predicate=AskIsGoodBuild): 358 predicate=AskIsGoodBuild):
291 """Given known good and known bad revisions, run a binary search on all 359 """Given known good and known bad revisions, run a binary search on all
292 archived revisions to determine the last known good revision. 360 archived revisions to determine the last known good revision.
293 361
294 @param platform Which build to download/run ('mac', 'win', 'linux64', etc.). 362 @param platform Which build to download/run ('mac', 'win', 'linux64', etc.).
363 @param official_builds Specify build type (Chromium or Official build).
295 @param good_rev Number/tag of the last known good revision. 364 @param good_rev Number/tag of the last known good revision.
296 @param bad_rev Number/tag of the first known bad revision. 365 @param bad_rev Number/tag of the first known bad revision.
297 @param num_runs Number of times to run each build for asking good/bad. 366 @param num_runs Number of times to run each build for asking good/bad.
298 @param try_args A tuple of arguments to pass to the test application. 367 @param try_args A tuple of arguments to pass to the test application.
299 @param profile The name of the user profile to run with. 368 @param profile The name of the user profile to run with.
300 @param predicate A predicate function which returns True iff the argument 369 @param predicate A predicate function which returns True iff the argument
301 chromium revision is good. 370 chromium revision is good.
302 371
303 Threading is used to fetch Chromium revisions in the background, speeding up 372 Threading is used to fetch Chromium revisions in the background, speeding up
304 the user's experience. For example, suppose the bounds of the search are 373 the user's experience. For example, suppose the bounds of the search are
305 good_rev=0, bad_rev=100. The first revision to be checked is 50. Depending on 374 good_rev=0, bad_rev=100. The first revision to be checked is 50. Depending on
306 whether revision 50 is good or bad, the next revision to check will be either 375 whether revision 50 is good or bad, the next revision to check will be either
307 25 or 75. So, while revision 50 is being checked, the script will download 376 25 or 75. So, while revision 50 is being checked, the script will download
308 revisions 25 and 75 in the background. Once the good/bad verdict on rev 50 is 377 revisions 25 and 75 in the background. Once the good/bad verdict on rev 50 is
309 known: 378 known:
310 379
311 - If rev 50 is good, the download of rev 25 is cancelled, and the next test 380 - If rev 50 is good, the download of rev 25 is cancelled, and the next test
312 is run on rev 75. 381 is run on rev 75.
313 382
314 - If rev 50 is bad, the download of rev 75 is cancelled, and the next test 383 - If rev 50 is bad, the download of rev 75 is cancelled, and the next test
315 is run on rev 25. 384 is run on rev 25.
316 """ 385 """
317 386
318 if not profile: 387 if not profile:
319 profile = 'profile' 388 profile = 'profile'
320 389
321 context = PathContext(platform, good_rev, bad_rev) 390 context = PathContext(platform, good_rev, bad_rev, official_builds)
322 cwd = os.getcwd() 391 cwd = os.getcwd()
323 392
324 _GetDownloadPath = lambda rev: os.path.join(cwd, 393
325 '%d-%s' % (rev, context.archive_name))
326 394
327 print "Downloading list of known revisions..." 395 print "Downloading list of known revisions..."
328 396 _GetDownloadPath = lambda rev: os.path.join(cwd,
329 revlist = context.GetRevList() 397 '%s-%s' % (str(rev), context.archive_name))
398 if official_builds:
399 revlist = context.GetOfficialBuildsList()
400 else:
401 revlist = context.GetRevList()
330 402
331 # Get a list of revisions to bisect across. 403 # Get a list of revisions to bisect across.
332 if len(revlist) < 2: # Don't have enough builds to bisect. 404 if len(revlist) < 2: # Don't have enough builds to bisect.
333 msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist 405 msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist
334 raise RuntimeError(msg) 406 raise RuntimeError(msg)
335 407
336 # Figure out our bookends and first pivot point; fetch the pivot revision. 408 # Figure out our bookends and first pivot point; fetch the pivot revision.
337 good = 0 409 good = 0
338 bad = len(revlist) - 1 410 bad = len(revlist) - 1
339 pivot = bad / 2 411 pivot = bad / 2
340 rev = revlist[pivot] 412 rev = revlist[pivot]
341 zipfile = _GetDownloadPath(rev) 413 zipfile = _GetDownloadPath(rev)
342 progress_event = threading.Event() 414 progress_event = threading.Event()
343 progress_event.set() 415 progress_event.set()
344 print "Downloading revision %d..." % rev 416 print "Downloading revision %s..." % str(rev)
345 FetchRevision(context, rev, zipfile, 417 FetchRevision(context, rev, zipfile,
346 quit_event=None, progress_event=progress_event) 418 quit_event=None, progress_event=progress_event)
347 419
348 # Binary search time! 420 # Binary search time!
349 while zipfile and bad - good > 1: 421 while zipfile and bad - good > 1:
350 # Pre-fetch next two possible pivots 422 # Pre-fetch next two possible pivots
351 # - down_pivot is the next revision to check if the current revision turns 423 # - down_pivot is the next revision to check if the current revision turns
352 # out to be bad. 424 # out to be bad.
353 # - up_pivot is the next revision to check if the current revision turns 425 # - up_pivot is the next revision to check if the current revision turns
354 # out to be good. 426 # out to be good.
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
393 profile, 465 profile,
394 num_runs, 466 num_runs,
395 try_args) 467 try_args)
396 os.unlink(zipfile) 468 os.unlink(zipfile)
397 zipfile = None 469 zipfile = None
398 470
399 # Call the predicate function to see if the current revision is good or bad. 471 # 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 472 # On that basis, kill one of the background downloads and complete the
401 # other, as described in the comments above. 473 # other, as described in the comments above.
402 try: 474 try:
403 if predicate(rev, status, stdout, stderr): 475 if predicate(rev, official_builds, status, stdout, stderr):
404 good = pivot 476 good = pivot
405 if down_thread: 477 if down_thread:
406 down_quit_event.set() # Kill the download of older revision. 478 down_quit_event.set() # Kill the download of older revision.
407 down_thread.join() 479 down_thread.join()
408 os.unlink(down_zipfile) 480 os.unlink(down_zipfile)
409 if up_thread: 481 if up_thread:
410 print "Downloading revision %d..." % up_rev 482 print "Downloading revision %s..." % str(up_rev)
411 up_progress_event.set() # Display progress of download. 483 up_progress_event.set() # Display progress of download.
412 up_thread.join() # Wait for newer revision to finish downloading. 484 up_thread.join() # Wait for newer revision to finish downloading.
413 pivot = up_pivot 485 pivot = up_pivot
414 zipfile = up_zipfile 486 zipfile = up_zipfile
415 else: 487 else:
416 bad = pivot 488 bad = pivot
417 if up_thread: 489 if up_thread:
418 up_quit_event.set() # Kill download of newer revision. 490 up_quit_event.set() # Kill download of newer revision.
419 up_thread.join() 491 up_thread.join()
420 os.unlink(up_zipfile) 492 os.unlink(up_zipfile)
421 if down_thread: 493 if down_thread:
422 print "Downloading revision %d..." % down_rev 494 print "Downloading revision %s..." % str(down_rev)
423 down_progress_event.set() # Display progress of download. 495 down_progress_event.set() # Display progress of download.
424 down_thread.join() # Wait for older revision to finish downloading. 496 down_thread.join() # Wait for older revision to finish downloading.
425 pivot = down_pivot 497 pivot = down_pivot
426 zipfile = down_zipfile 498 zipfile = down_zipfile
427 except SystemExit: 499 except SystemExit:
428 print "Cleaning up..." 500 print "Cleaning up..."
429 for f in [_GetDownloadPath(revlist[down_pivot]), 501 for f in [_GetDownloadPath(revlist[down_pivot]),
430 _GetDownloadPath(revlist[up_pivot])]: 502 _GetDownloadPath(revlist[up_pivot])]:
431 try: 503 try:
432 os.unlink(f) 504 os.unlink(f)
(...skipping 26 matching lines...) Expand all
459 '\n' 531 '\n'
460 'Tip: add "-- --no-first-run" to bypass the first run prompts.') 532 'Tip: add "-- --no-first-run" to bypass the first run prompts.')
461 parser = optparse.OptionParser(usage=usage) 533 parser = optparse.OptionParser(usage=usage)
462 # Strangely, the default help output doesn't include the choice list. 534 # Strangely, the default help output doesn't include the choice list.
463 choices = ['mac', 'win', 'linux', 'linux64'] 535 choices = ['mac', 'win', 'linux', 'linux64']
464 # linux-chromiumos lacks a continuous archive http://crbug.com/78158 536 # linux-chromiumos lacks a continuous archive http://crbug.com/78158
465 parser.add_option('-a', '--archive', 537 parser.add_option('-a', '--archive',
466 choices = choices, 538 choices = choices,
467 help = 'The buildbot archive to bisect [%s].' % 539 help = 'The buildbot archive to bisect [%s].' %
468 '|'.join(choices)) 540 '|'.join(choices))
469 parser.add_option('-b', '--bad', type = 'int', 541 parser.add_option('-o', action="store_true", dest='official_builds',
542 help = 'Bisect across official ' +
543 'Chrome builds (internal only) instead of ' +
544 'Chromium archives.')
545 parser.add_option('-b', '--bad', type = 'str',
470 help = 'The bad revision to bisect to.') 546 help = 'The bad revision to bisect to.')
471 parser.add_option('-g', '--good', type = 'int', 547 parser.add_option('-g', '--good', type = 'str',
472 help = 'The last known good revision to bisect from.') 548 help = 'The last known good revision to bisect from.')
473 parser.add_option('-p', '--profile', '--user-data-dir', type = 'str', 549 parser.add_option('-p', '--profile', '--user-data-dir', type = 'str',
474 help = 'Profile to use; this will not reset every run. ' + 550 help = 'Profile to use; this will not reset every run. ' +
475 'Defaults to a clean profile.', default = 'profile') 551 'Defaults to a clean profile.', default = 'profile')
476 parser.add_option('-t', '--times', type = 'int', 552 parser.add_option('-t', '--times', type = 'int',
477 help = 'Number of times to run each build before asking ' + 553 help = 'Number of times to run each build before asking ' +
478 'if it\'s good or bad. Temporary profiles are reused.', 554 'if it\'s good or bad. Temporary profiles are reused.',
479 default = 1) 555 default = 1)
480 (opts, args) = parser.parse_args() 556 (opts, args) = parser.parse_args()
481 557
482 if opts.archive is None: 558 if opts.archive is None:
483 print 'Error: missing required parameter: --archive' 559 print 'Error: missing required parameter: --archive'
484 print 560 print
485 parser.print_help() 561 parser.print_help()
486 return 1 562 return 1
487 563
488 if opts.bad and opts.good and (opts.good > opts.bad): 564 if opts.bad and opts.good and (opts.good > opts.bad):
489 print ('The good revision (%d) must precede the bad revision (%d).\n' % 565 print ('The good revision (%d) must precede the bad revision (%d).\n' %
490 (opts.good, opts.bad)) 566 (opts.good, opts.bad))
491 parser.print_help() 567 parser.print_help()
492 return 1 568 return 1
493 569
494 # Create the context. Initialize 0 for the revisions as they are set below. 570 # Create the context. Initialize 0 for the revisions as they are set below.
495 context = PathContext(opts.archive, 0, 0) 571 context = PathContext(opts.archive, 0, 0, opts.official_builds)
572
573 if opts.official_builds and opts.bad is None:
574 print >>sys.stderr, 'Bisecting official builds requires a bad build number.'
575 parser.print_help()
576 return 1
496 577
497 # Pick a starting point, try to get HEAD for this. 578 # Pick a starting point, try to get HEAD for this.
498 if opts.bad: 579 if opts.bad:
499 bad_rev = opts.bad 580 bad_rev = opts.bad
500 else: 581 else:
501 bad_rev = 0 582 bad_rev = 0
502 try: 583 try:
503 # Location of the latest build revision number 584 # Location of the latest build revision number
504 nh = urllib.urlopen(context.GetLastChangeURL()) 585 nh = urllib.urlopen(context.GetLastChangeURL())
505 latest = int(nh.read()) 586 latest = int(nh.read())
(...skipping 16 matching lines...) Expand all
522 except Exception, e: 603 except Exception, e:
523 pass 604 pass
524 605
525 if opts.times < 1: 606 if opts.times < 1:
526 print('Number of times to run (%d) must be greater than or equal to 1.' % 607 print('Number of times to run (%d) must be greater than or equal to 1.' %
527 opts.times) 608 opts.times)
528 parser.print_help() 609 parser.print_help()
529 return 1 610 return 1
530 611
531 (last_known_good_rev, first_known_bad_rev) = Bisect( 612 (last_known_good_rev, first_known_bad_rev) = Bisect(
532 opts.archive, good_rev, bad_rev, opts.times, args, opts.profile) 613 opts.archive, opts.official_builds, good_rev, bad_rev, opts.times, args,
614 opts.profile)
533 615
534 # Get corresponding webkit revisions. 616 # Get corresponding webkit revisions.
535 try: 617 try:
536 last_known_good_webkit_rev = GetWebKitRevisionForChromiumRevision( 618 last_known_good_webkit_rev = GetWebKitRevisionForChromiumRevision(
537 last_known_good_rev) 619 last_known_good_rev)
538 first_known_bad_webkit_rev = GetWebKitRevisionForChromiumRevision( 620 first_known_bad_webkit_rev = GetWebKitRevisionForChromiumRevision(
539 first_known_bad_rev) 621 first_known_bad_rev)
540 except Exception, e: 622 except Exception, e:
541 # Silently ignore the failure. 623 # Silently ignore the failure.
542 last_known_good_webkit_rev, first_known_bad_webkit_rev = 0, 0 624 last_known_good_webkit_rev, first_known_bad_webkit_rev = 0, 0
543 625
544 # We're done. Let the user know the results in an official manner. 626 # 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) 627 print('You are probably looking for build %s.' % str(first_known_bad_rev))
628
546 if last_known_good_webkit_rev != first_known_bad_webkit_rev: 629 if last_known_good_webkit_rev != first_known_bad_webkit_rev:
547 print 'WEBKIT CHANGELOG URL:' 630 print 'WEBKIT CHANGELOG URL:'
548 print WEBKIT_CHANGELOG_URL % (first_known_bad_webkit_rev, 631 print WEBKIT_CHANGELOG_URL % (first_known_bad_webkit_rev,
549 last_known_good_webkit_rev) 632 last_known_good_webkit_rev)
550 print 'CHANGELOG URL:' 633 print 'CHANGELOG URL:'
551 print CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev) 634 if opts.official_builds:
552 print 'Built at revision:' 635 print OFFICIAL_CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev)
553 print BUILD_VIEWVC_URL % first_known_bad_rev 636 else:
637 print CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev)
638 print 'Built at revision:'
639 print BUILD_VIEWVC_URL % first_known_bad_rev
554 640
555 641
556 if __name__ == '__main__': 642 if __name__ == '__main__':
557 sys.exit(main()) 643 sys.exit(main())
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