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

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
Robert Sesek 2012/05/24 21:16:51 nit: reinstate this blank line
154 # Fetch the first list of revisions. 185 # Fetch the first list of revisions.
155 (revisions, next_marker) = _FetchAndParse(self.GetListingURL()) 186 (revisions, next_marker) = _FetchAndParse(self.GetListingURL())
156 187
157 # If the result list was truncated, refetch with the next marker. Do this 188 # If the result list was truncated, refetch with the next marker. Do this
158 # until an entire directory listing is done. 189 # until an entire directory listing is done.
159 while next_marker: 190 while next_marker:
160 next_url = self.GetListingURL(next_marker) 191 next_url = self.GetListingURL(next_marker)
161 (new_revisions, next_marker) = _FetchAndParse(next_url) 192 (new_revisions, next_marker) = _FetchAndParse(next_url)
162 revisions.extend(new_revisions) 193 revisions.extend(new_revisions)
163
164 return revisions 194 return revisions
165 195
166 def GetRevList(self): 196 def GetRevList(self):
167 """Gets the list of revision numbers between self.good_revision and 197 """Gets the list of revision numbers between self.good_revision and
168 self.bad_revision.""" 198 self.bad_revision."""
169 # Download the revlist and filter for just the range between good and bad. 199 # Download the revlist and filter for just the range between good and bad.
170 minrev = self.good_revision 200 minrev = self.good_revision
171 maxrev = self.bad_revision 201 maxrev = self.bad_revision
172 revlist = map(int, self.ParseDirectoryIndex()) 202 revlist = map(int, self.ParseDirectoryIndex())
173 revlist = [x for x in revlist if x >= minrev and x <= maxrev] 203 revlist = [x for x in revlist if x >= int(minrev) and x <= int(maxrev)]
174 revlist.sort() 204 revlist.sort()
175 return revlist 205 return revlist
176 206
207 def GetOfficialBuildsList(self):
208 """Gets the list of official build numbers between self.good_revision and
209 self.bad_revision."""
210 # Download the revlist and filter for just the range between good and bad.
211 minrev = self.good_revision
212 maxrev = self.bad_revision
213 handle = urllib.urlopen(OFFICIAL_BASE_URL)
214 dirindex = handle.read()
215 handle.close()
216 build_numbers = re.findall(r'<a href="([0-9][0-9].*)/">', dirindex)
217 final_list = []
218 start_index = '0'
219 end_index = '0'
220 i = 0
221
222 parsed_build_numbers = [LooseVersion(x) for x in build_numbers]
223 for build_number in sorted(parsed_build_numbers):
224 path = OFFICIAL_BASE_URL + '/' + str(build_number) + '/' + \
225 self._listing_platform_dir + self.archive_name
226 i = i + 1
227 try:
228 connection = urllib.urlopen(path)
229 connection.close()
230 final_list.append(str(build_number))
231 if str(build_number) == minrev:
232 start_index = i
233 if str(build_number) == maxrev:
234 end_index = i
235 except urllib.HTTPError, e:
236 pass
237 return final_list[start_index:end_index]
177 238
178 def UnzipFilenameToDir(filename, dir): 239 def UnzipFilenameToDir(filename, dir):
179 """Unzip |filename| to directory |dir|.""" 240 """Unzip |filename| to directory |dir|."""
180 cwd = os.getcwd() 241 cwd = os.getcwd()
181 if not os.path.isabs(filename): 242 if not os.path.isabs(filename):
182 filename = os.path.join(cwd, filename) 243 filename = os.path.join(cwd, filename)
183 zf = zipfile.ZipFile(filename) 244 zf = zipfile.ZipFile(filename)
184 # Make base. 245 # Make base.
185 try: 246 try:
186 if not os.path.isdir(dir): 247 if not os.path.isdir(dir):
(...skipping 26 matching lines...) Expand all
213 @param rev The Chromium revision number/tag to download. 274 @param rev The Chromium revision number/tag to download.
214 @param filename The destination for the downloaded file. 275 @param filename The destination for the downloaded file.
215 @param quit_event A threading.Event which will be set by the master thread to 276 @param quit_event A threading.Event which will be set by the master thread to
216 indicate that the download should be aborted. 277 indicate that the download should be aborted.
217 @param progress_event A threading.Event which will be set by the master thread 278 @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 279 to indicate that the progress of the download should be
219 displayed. 280 displayed.
220 """ 281 """
221 def ReportHook(blocknum, blocksize, totalsize): 282 def ReportHook(blocknum, blocksize, totalsize):
222 if quit_event and quit_event.isSet(): 283 if quit_event and quit_event.isSet():
223 raise RuntimeError("Aborting download of revision %d" % rev) 284 raise RuntimeError("Aborting download of revision %s" % str(rev))
224 if progress_event and progress_event.isSet(): 285 if progress_event and progress_event.isSet():
225 size = blocknum * blocksize 286 size = blocknum * blocksize
226 if totalsize == -1: # Total size not known. 287 if totalsize == -1: # Total size not known.
227 progress = "Received %d bytes" % size 288 progress = "Received %d bytes" % size
228 else: 289 else:
229 size = min(totalsize, size) 290 size = min(totalsize, size)
230 progress = "Received %d of %d bytes, %.2f%%" % ( 291 progress = "Received %d of %d bytes, %.2f%%" % (
231 size, totalsize, 100.0 * size / totalsize) 292 size, totalsize, 100.0 * size / totalsize)
232 # Send a \r to let all progress messages use just one line of output. 293 # Send a \r to let all progress messages use just one line of output.
233 sys.stdout.write("\r" + progress) 294 sys.stdout.write("\r" + progress)
234 sys.stdout.flush() 295 sys.stdout.flush()
235 296
236 download_url = context.GetDownloadURL(rev) 297 download_url = context.GetDownloadURL(rev)
237 try: 298 try:
238 urllib.urlretrieve(download_url, filename, ReportHook) 299 urllib.urlretrieve(download_url, filename, ReportHook)
239 if progress_event and progress_event.isSet(): 300 if progress_event and progress_event.isSet():
240 print 301 print
241 except RuntimeError, e: 302 except RuntimeError, e:
242 pass 303 pass
243 304
244 305
245 def RunRevision(context, revision, zipfile, profile, num_runs, args): 306 def RunRevision(context, revision, zipfile, profile, num_runs, args):
246 """Given a zipped revision, unzip it and run the test.""" 307 """Given a zipped revision, unzip it and run the test."""
247 print "Trying revision %d..." % revision 308 print "Trying revision %s..." % str(revision)
248 309
249 # Create a temp directory and unzip the revision into it. 310 # Create a temp directory and unzip the revision into it.
250 cwd = os.getcwd() 311 cwd = os.getcwd()
251 tempdir = tempfile.mkdtemp(prefix='bisect_tmp') 312 tempdir = tempfile.mkdtemp(prefix='bisect_tmp')
252 UnzipFilenameToDir(zipfile, tempdir) 313 UnzipFilenameToDir(zipfile, tempdir)
253 os.chdir(tempdir) 314 os.chdir(tempdir)
254 315
255 # Run the build as many times as specified. 316 # Run the build as many times as specified.
256 testargs = [context.GetLaunchPath(), '--user-data-dir=%s' % profile] + args 317 testargs = [context.GetLaunchPath(), '--user-data-dir=%s' % profile] + args
318 # The sandbox must be run as root on Official Chrome, so bypass it.
319 if context.is_official and (context.platform == 'linux' or
320 context.platform == 'linux64'):
321 testargs.append('--no-sandbox')
322
257 for i in range(0, num_runs): 323 for i in range(0, num_runs):
258 subproc = subprocess.Popen(testargs, 324 subproc = subprocess.Popen(testargs,
259 bufsize=-1, 325 bufsize=-1,
260 stdout=subprocess.PIPE, 326 stdout=subprocess.PIPE,
261 stderr=subprocess.PIPE) 327 stderr=subprocess.PIPE)
262 (stdout, stderr) = subproc.communicate() 328 (stdout, stderr) = subproc.communicate()
263 329
264 os.chdir(cwd) 330 os.chdir(cwd)
265 try: 331 try:
266 shutil.rmtree(tempdir, True) 332 shutil.rmtree(tempdir, True)
267 except Exception, e: 333 except Exception, e:
268 pass 334 pass
269 335
270 return (subproc.returncode, stdout, stderr) 336 return (subproc.returncode, stdout, stderr)
271 337
272 338
273 def AskIsGoodBuild(rev, status, stdout, stderr): 339 def AskIsGoodBuild(rev, official_builds, status, stdout, stderr):
274 """Ask the user whether build |rev| is good or bad.""" 340 """Ask the user whether build |rev| is good or bad."""
275 # Loop until we get a response that we can parse. 341 # Loop until we get a response that we can parse.
276 while True: 342 while True:
277 response = raw_input('Revision %d is [(g)ood/(b)ad/(q)uit]: ' % int(rev)) 343 response = raw_input('Revision %s is [(g)ood/(b)ad/(q)uit]: ' % str(rev))
278 if response and response in ('g', 'b'): 344 if response and response in ('g', 'b'):
279 return response == 'g' 345 return response == 'g'
280 if response and response == 'q': 346 if response and response == 'q':
281 raise SystemExit() 347 raise SystemExit()
282 348
283 349
284 def Bisect(platform, 350 def Bisect(platform,
351 official_builds,
285 good_rev=0, 352 good_rev=0,
286 bad_rev=0, 353 bad_rev=0,
287 num_runs=1, 354 num_runs=1,
288 try_args=(), 355 try_args=(),
289 profile=None, 356 profile=None,
290 predicate=AskIsGoodBuild): 357 predicate=AskIsGoodBuild):
291 """Given known good and known bad revisions, run a binary search on all 358 """Given known good and known bad revisions, run a binary search on all
292 archived revisions to determine the last known good revision. 359 archived revisions to determine the last known good revision.
293 360
294 @param platform Which build to download/run ('mac', 'win', 'linux64', etc.). 361 @param platform Which build to download/run ('mac', 'win', 'linux64', etc.).
362 @param official_builds Specify build type(Chromium or Official build).
Robert Sesek 2012/05/24 21:16:51 nit: space before (
295 @param good_rev Number/tag of the last known good revision. 363 @param good_rev Number/tag of the last known good revision.
296 @param bad_rev Number/tag of the first known bad revision. 364 @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. 365 @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. 366 @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. 367 @param profile The name of the user profile to run with.
300 @param predicate A predicate function which returns True iff the argument 368 @param predicate A predicate function which returns True iff the argument
301 chromium revision is good. 369 chromium revision is good.
302 370
303 Threading is used to fetch Chromium revisions in the background, speeding up 371 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 372 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 373 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 374 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 375 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 376 revisions 25 and 75 in the background. Once the good/bad verdict on rev 50 is
309 known: 377 known:
310 378
311 - If rev 50 is good, the download of rev 25 is cancelled, and the next test 379 - If rev 50 is good, the download of rev 25 is cancelled, and the next test
312 is run on rev 75. 380 is run on rev 75.
313 381
314 - If rev 50 is bad, the download of rev 75 is cancelled, and the next test 382 - If rev 50 is bad, the download of rev 75 is cancelled, and the next test
315 is run on rev 25. 383 is run on rev 25.
316 """ 384 """
317 385
318 if not profile: 386 if not profile:
319 profile = 'profile' 387 profile = 'profile'
320 388
321 context = PathContext(platform, good_rev, bad_rev) 389 context = PathContext(platform, good_rev, bad_rev, official_builds)
322 cwd = os.getcwd() 390 cwd = os.getcwd()
323 391
324 _GetDownloadPath = lambda rev: os.path.join(cwd, 392
325 '%d-%s' % (rev, context.archive_name))
326 393
327 print "Downloading list of known revisions..." 394 print "Downloading list of known revisions..."
328 395 _GetDownloadPath = lambda rev: os.path.join(cwd,
329 revlist = context.GetRevList() 396 '%s-%s' % (str(rev), context.archive_name))
397 if official_builds:
398 revlist = context.GetOfficialBuildsList()
399 else:
400 revlist = context.GetRevList()
330 401
331 # Get a list of revisions to bisect across. 402 # Get a list of revisions to bisect across.
332 if len(revlist) < 2: # Don't have enough builds to bisect. 403 if len(revlist) < 2: # Don't have enough builds to bisect.
333 msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist 404 msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist
334 raise RuntimeError(msg) 405 raise RuntimeError(msg)
335 406
336 # Figure out our bookends and first pivot point; fetch the pivot revision. 407 # Figure out our bookends and first pivot point; fetch the pivot revision.
337 good = 0 408 good = 0
338 bad = len(revlist) - 1 409 bad = len(revlist) - 1
339 pivot = bad / 2 410 pivot = bad / 2
340 rev = revlist[pivot] 411 rev = revlist[pivot]
341 zipfile = _GetDownloadPath(rev) 412 zipfile = _GetDownloadPath(rev)
342 progress_event = threading.Event() 413 progress_event = threading.Event()
343 progress_event.set() 414 progress_event.set()
344 print "Downloading revision %d..." % rev 415 print "Downloading revision %s..." % str(rev)
345 FetchRevision(context, rev, zipfile, 416 FetchRevision(context, rev, zipfile,
346 quit_event=None, progress_event=progress_event) 417 quit_event=None, progress_event=progress_event)
347 418
348 # Binary search time! 419 # Binary search time!
349 while zipfile and bad - good > 1: 420 while zipfile and bad - good > 1:
350 # Pre-fetch next two possible pivots 421 # Pre-fetch next two possible pivots
351 # - down_pivot is the next revision to check if the current revision turns 422 # - down_pivot is the next revision to check if the current revision turns
352 # out to be bad. 423 # out to be bad.
353 # - up_pivot is the next revision to check if the current revision turns 424 # - up_pivot is the next revision to check if the current revision turns
354 # out to be good. 425 # out to be good.
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
393 profile, 464 profile,
394 num_runs, 465 num_runs,
395 try_args) 466 try_args)
396 os.unlink(zipfile) 467 os.unlink(zipfile)
397 zipfile = None 468 zipfile = None
398 469
399 # Call the predicate function to see if the current revision is good or bad. 470 # 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 471 # On that basis, kill one of the background downloads and complete the
401 # other, as described in the comments above. 472 # other, as described in the comments above.
402 try: 473 try:
403 if predicate(rev, status, stdout, stderr): 474 if predicate(rev, official_builds, status, stdout, stderr):
404 good = pivot 475 good = pivot
405 if down_thread: 476 if down_thread:
406 down_quit_event.set() # Kill the download of older revision. 477 down_quit_event.set() # Kill the download of older revision.
407 down_thread.join() 478 down_thread.join()
408 os.unlink(down_zipfile) 479 os.unlink(down_zipfile)
409 if up_thread: 480 if up_thread:
410 print "Downloading revision %d..." % up_rev 481 print "Downloading revision %s..." % str(up_rev)
411 up_progress_event.set() # Display progress of download. 482 up_progress_event.set() # Display progress of download.
412 up_thread.join() # Wait for newer revision to finish downloading. 483 up_thread.join() # Wait for newer revision to finish downloading.
413 pivot = up_pivot 484 pivot = up_pivot
414 zipfile = up_zipfile 485 zipfile = up_zipfile
415 else: 486 else:
416 bad = pivot 487 bad = pivot
417 if up_thread: 488 if up_thread:
418 up_quit_event.set() # Kill download of newer revision. 489 up_quit_event.set() # Kill download of newer revision.
419 up_thread.join() 490 up_thread.join()
420 os.unlink(up_zipfile) 491 os.unlink(up_zipfile)
421 if down_thread: 492 if down_thread:
422 print "Downloading revision %d..." % down_rev 493 print "Downloading revision %s..." % str(down_rev)
423 down_progress_event.set() # Display progress of download. 494 down_progress_event.set() # Display progress of download.
424 down_thread.join() # Wait for older revision to finish downloading. 495 down_thread.join() # Wait for older revision to finish downloading.
425 pivot = down_pivot 496 pivot = down_pivot
426 zipfile = down_zipfile 497 zipfile = down_zipfile
427 except SystemExit: 498 except SystemExit:
428 print "Cleaning up..." 499 print "Cleaning up..."
429 for f in [_GetDownloadPath(revlist[down_pivot]), 500 for f in [_GetDownloadPath(revlist[down_pivot]),
430 _GetDownloadPath(revlist[up_pivot])]: 501 _GetDownloadPath(revlist[up_pivot])]:
431 try: 502 try:
432 os.unlink(f) 503 os.unlink(f)
(...skipping 26 matching lines...) Expand all
459 '\n' 530 '\n'
460 'Tip: add "-- --no-first-run" to bypass the first run prompts.') 531 'Tip: add "-- --no-first-run" to bypass the first run prompts.')
461 parser = optparse.OptionParser(usage=usage) 532 parser = optparse.OptionParser(usage=usage)
462 # Strangely, the default help output doesn't include the choice list. 533 # Strangely, the default help output doesn't include the choice list.
463 choices = ['mac', 'win', 'linux', 'linux64'] 534 choices = ['mac', 'win', 'linux', 'linux64']
464 # linux-chromiumos lacks a continuous archive http://crbug.com/78158 535 # linux-chromiumos lacks a continuous archive http://crbug.com/78158
465 parser.add_option('-a', '--archive', 536 parser.add_option('-a', '--archive',
466 choices = choices, 537 choices = choices,
467 help = 'The buildbot archive to bisect [%s].' % 538 help = 'The buildbot archive to bisect [%s].' %
468 '|'.join(choices)) 539 '|'.join(choices))
469 parser.add_option('-b', '--bad', type = 'int', 540 parser.add_option('-o', action="store_true", dest='official_builds',
541 help = 'Bisect across official ' +
542 'Chrome builds (internal only) instead of ' +
543 'Chromium archives.')
544 parser.add_option('-b', '--bad', type = 'str',
470 help = 'The bad revision to bisect to.') 545 help = 'The bad revision to bisect to.')
471 parser.add_option('-g', '--good', type = 'int', 546 parser.add_option('-g', '--good', type = 'str',
472 help = 'The last known good revision to bisect from.') 547 help = 'The last known good revision to bisect from.')
473 parser.add_option('-p', '--profile', '--user-data-dir', type = 'str', 548 parser.add_option('-p', '--profile', '--user-data-dir', type = 'str',
474 help = 'Profile to use; this will not reset every run. ' + 549 help = 'Profile to use; this will not reset every run. ' +
475 'Defaults to a clean profile.', default = 'profile') 550 'Defaults to a clean profile.', default = 'profile')
476 parser.add_option('-t', '--times', type = 'int', 551 parser.add_option('-t', '--times', type = 'int',
477 help = 'Number of times to run each build before asking ' + 552 help = 'Number of times to run each build before asking ' +
478 'if it\'s good or bad. Temporary profiles are reused.', 553 'if it\'s good or bad. Temporary profiles are reused.',
479 default = 1) 554 default = 1)
480 (opts, args) = parser.parse_args() 555 (opts, args) = parser.parse_args()
481 556
482 if opts.archive is None: 557 if opts.archive is None:
483 print 'Error: missing required parameter: --archive' 558 print 'Error: missing required parameter: --archive'
484 print 559 print
485 parser.print_help() 560 parser.print_help()
486 return 1 561 return 1
487 562
488 if opts.bad and opts.good and (opts.good > opts.bad): 563 if opts.bad and opts.good and (opts.good > opts.bad):
489 print ('The good revision (%d) must precede the bad revision (%d).\n' % 564 print ('The good revision (%d) must precede the bad revision (%d).\n' %
490 (opts.good, opts.bad)) 565 (opts.good, opts.bad))
491 parser.print_help() 566 parser.print_help()
492 return 1 567 return 1
493 568
494 # Create the context. Initialize 0 for the revisions as they are set below. 569 # Create the context. Initialize 0 for the revisions as they are set below.
495 context = PathContext(opts.archive, 0, 0) 570 context = PathContext(opts.archive, 0, 0, opts.official_builds)
571
572 if opts.official_builds and opts.bad is None:
573 print >>sys.stderr, 'Bisecting official builds requires a bad build number.'
574 parser.print_help()
575 return 1
496 576
497 # Pick a starting point, try to get HEAD for this. 577 # Pick a starting point, try to get HEAD for this.
498 if opts.bad: 578 if opts.bad:
499 bad_rev = opts.bad 579 bad_rev = opts.bad
500 else: 580 else:
501 bad_rev = 0 581 bad_rev = 0
502 try: 582 try:
503 # Location of the latest build revision number 583 # Location of the latest build revision number
504 nh = urllib.urlopen(context.GetLastChangeURL()) 584 nh = urllib.urlopen(context.GetLastChangeURL())
505 latest = int(nh.read()) 585 latest = int(nh.read())
(...skipping 16 matching lines...) Expand all
522 except Exception, e: 602 except Exception, e:
523 pass 603 pass
524 604
525 if opts.times < 1: 605 if opts.times < 1:
526 print('Number of times to run (%d) must be greater than or equal to 1.' % 606 print('Number of times to run (%d) must be greater than or equal to 1.' %
527 opts.times) 607 opts.times)
528 parser.print_help() 608 parser.print_help()
529 return 1 609 return 1
530 610
531 (last_known_good_rev, first_known_bad_rev) = Bisect( 611 (last_known_good_rev, first_known_bad_rev) = Bisect(
532 opts.archive, good_rev, bad_rev, opts.times, args, opts.profile) 612 opts.archive, opts.official_builds, good_rev, bad_rev, opts.times, args,
613 opts.profile)
533 614
534 # Get corresponding webkit revisions. 615 # Get corresponding webkit revisions.
535 try: 616 try:
536 last_known_good_webkit_rev = GetWebKitRevisionForChromiumRevision( 617 last_known_good_webkit_rev = GetWebKitRevisionForChromiumRevision(
537 last_known_good_rev) 618 last_known_good_rev)
538 first_known_bad_webkit_rev = GetWebKitRevisionForChromiumRevision( 619 first_known_bad_webkit_rev = GetWebKitRevisionForChromiumRevision(
539 first_known_bad_rev) 620 first_known_bad_rev)
540 except Exception, e: 621 except Exception, e:
541 # Silently ignore the failure. 622 # Silently ignore the failure.
542 last_known_good_webkit_rev, first_known_bad_webkit_rev = 0, 0 623 last_known_good_webkit_rev, first_known_bad_webkit_rev = 0, 0
543 624
544 # We're done. Let the user know the results in an official manner. 625 # 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) 626 print('You are probably looking for build %s.' % str(first_known_bad_rev))
627
546 if last_known_good_webkit_rev != first_known_bad_webkit_rev: 628 if last_known_good_webkit_rev != first_known_bad_webkit_rev:
547 print 'WEBKIT CHANGELOG URL:' 629 print 'WEBKIT CHANGELOG URL:'
548 print WEBKIT_CHANGELOG_URL % (first_known_bad_webkit_rev, 630 print WEBKIT_CHANGELOG_URL % (first_known_bad_webkit_rev,
549 last_known_good_webkit_rev) 631 last_known_good_webkit_rev)
550 print 'CHANGELOG URL:' 632 print 'CHANGELOG URL:'
551 print CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev) 633 if opts.official_builds:
552 print 'Built at revision:' 634 print OFFICIAL_CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev)
553 print BUILD_VIEWVC_URL % first_known_bad_rev 635 else:
636 print CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev)
637 print 'Built at revision:'
638 print BUILD_VIEWVC_URL % first_known_bad_rev
554 639
555 640
556 if __name__ == '__main__': 641 if __name__ == '__main__':
557 sys.exit(main()) 642 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