OLD | NEW |
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, |
(...skipping 164 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
175 # trailing slash to just have a number. | 175 # trailing slash to just have a number. |
176 revisions = [] | 176 revisions = [] |
177 for prefix in all_prefixes: | 177 for prefix in all_prefixes: |
178 revnum = prefix.text[prefix_len:-1] | 178 revnum = prefix.text[prefix_len:-1] |
179 try: | 179 try: |
180 revnum = int(revnum) | 180 revnum = int(revnum) |
181 revisions.append(revnum) | 181 revisions.append(revnum) |
182 except ValueError: | 182 except ValueError: |
183 pass | 183 pass |
184 return (revisions, next_marker) | 184 return (revisions, next_marker) |
185 | 185 |
186 # Fetch the first list of revisions. | 186 # Fetch the first list of revisions. |
187 (revisions, next_marker) = _FetchAndParse(self.GetListingURL()) | 187 (revisions, next_marker) = _FetchAndParse(self.GetListingURL()) |
188 | 188 |
189 # 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 |
190 # until an entire directory listing is done. | 190 # until an entire directory listing is done. |
191 while next_marker: | 191 while next_marker: |
192 next_url = self.GetListingURL(next_marker) | 192 next_url = self.GetListingURL(next_marker) |
193 (new_revisions, next_marker) = _FetchAndParse(next_url) | 193 (new_revisions, next_marker) = _FetchAndParse(next_url) |
194 revisions.extend(new_revisions) | 194 revisions.extend(new_revisions) |
195 return revisions | 195 return revisions |
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
289 | 289 |
290 download_url = context.GetDownloadURL(rev) | 290 download_url = context.GetDownloadURL(rev) |
291 try: | 291 try: |
292 urllib.urlretrieve(download_url, filename, ReportHook) | 292 urllib.urlretrieve(download_url, filename, ReportHook) |
293 if progress_event and progress_event.isSet(): | 293 if progress_event and progress_event.isSet(): |
294 print | 294 print |
295 except RuntimeError, e: | 295 except RuntimeError, e: |
296 pass | 296 pass |
297 | 297 |
298 | 298 |
299 def RunRevision(context, revision, zipfile, profile, num_runs, args): | 299 def RunRevision(context, revision, zipfile, num_runs, exec_args, outdir=None): |
300 """Given a zipped revision, unzip it and run the test.""" | 300 """Given a zipped revision, unzip it and run the test. |
| 301 @param context A PathContext instance. |
| 302 @param revision The Chromium revision number/tag to run. |
| 303 @param zipfile Path to a zip file containing the Chromium revision. |
| 304 @param num_runs Number of times to run the given Chromium revision. |
| 305 @param exec_args Function which takes in the path of the unzipped Chromium |
| 306 revision and returns the full command to execute Chromium. |
| 307 @param outdir Path to save output to. If specified, stdout will be saved at |
| 308 outdir/<revision>_<iteration>.out and stderr will be saved |
| 309 at outdir/<revision>_<iteration>.err. If left as None, the |
| 310 output is not saved. |
| 311 """ |
301 print "Trying revision %s..." % str(revision) | 312 print "Trying revision %s..." % str(revision) |
302 | 313 |
303 # Create a temp directory and unzip the revision into it. | |
304 cwd = os.getcwd() | |
305 tempdir = tempfile.mkdtemp(prefix='bisect_tmp') | 314 tempdir = tempfile.mkdtemp(prefix='bisect_tmp') |
306 UnzipFilenameToDir(zipfile, tempdir) | 315 UnzipFilenameToDir(zipfile, tempdir) |
307 os.chdir(tempdir) | 316 testargs = exec_args(os.path.join(tempdir, context.GetLaunchPath())) |
308 | 317 |
309 # Run the build as many times as specified. | |
310 testargs = [context.GetLaunchPath(), '--user-data-dir=%s' % profile] + args | |
311 # The sandbox must be run as root on Official Chrome, so bypass it. | 318 # The sandbox must be run as root on Official Chrome, so bypass it. |
312 if context.is_official and (context.platform == 'linux' or | 319 if context.is_official and (context.platform == 'linux' or |
313 context.platform == 'linux64'): | 320 context.platform == 'linux64'): |
314 testargs.append('--no-sandbox') | 321 testargs.append('--no-sandbox') |
315 | 322 |
| 323 # Run the build as many times as specified. |
316 for i in range(0, num_runs): | 324 for i in range(0, num_runs): |
317 subproc = subprocess.Popen(testargs, | 325 subproc = subprocess.Popen(testargs, |
318 bufsize=-1, | 326 bufsize=-1, |
319 stdout=subprocess.PIPE, | 327 stdout=subprocess.PIPE, |
320 stderr=subprocess.PIPE) | 328 stderr=subprocess.PIPE) |
321 (stdout, stderr) = subproc.communicate() | 329 (stdout, stderr) = subproc.communicate() |
322 | 330 |
323 os.chdir(cwd) | 331 if outdir is not None: |
| 332 if not os.path.isdir(outdir): |
| 333 os.mkdir(outdir) |
| 334 |
| 335 fout = open(os.path.join(outdir, '%s_%s.out' % (str(revision), str(i))), |
| 336 'w') |
| 337 fout.write(stdout) |
| 338 fout.close() |
| 339 ferr = open(os.path.join(outdir, '%s_%s.err' % (str(revision), str(i))), |
| 340 'w') |
| 341 ferr.write(stderr) |
| 342 ferr.close() |
| 343 |
324 try: | 344 try: |
325 shutil.rmtree(tempdir, True) | 345 shutil.rmtree(tempdir, True) |
326 except Exception, e: | 346 except Exception, e: |
327 pass | 347 pass |
328 | 348 |
329 return (subproc.returncode, stdout, stderr) | 349 return (subproc.returncode, stdout, stderr) |
330 | 350 |
331 | 351 |
332 def AskIsGoodBuild(rev, official_builds, status, stdout, stderr): | 352 def AskIsGoodBuild(rev, official_builds, status, stdout, stderr): |
333 """Ask the user whether build |rev| is good or bad.""" | 353 """Ask the user whether build |rev| is good or bad.""" |
(...skipping 130 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
464 down_fetch.Start() | 484 down_fetch.Start() |
465 | 485 |
466 up_pivot = int((bad - pivot) / 2) + pivot | 486 up_pivot = int((bad - pivot) / 2) + pivot |
467 up_fetch = None | 487 up_fetch = None |
468 if up_pivot != pivot and up_pivot != bad: | 488 if up_pivot != pivot and up_pivot != bad: |
469 up_rev = revlist[up_pivot] | 489 up_rev = revlist[up_pivot] |
470 up_fetch = DownloadJob(context, 'up_fetch', up_rev, | 490 up_fetch = DownloadJob(context, 'up_fetch', up_rev, |
471 _GetDownloadPath(up_rev)) | 491 _GetDownloadPath(up_rev)) |
472 up_fetch.Start() | 492 up_fetch.Start() |
473 | 493 |
| 494 def execute_args(chrome_path): |
| 495 return [chrome_path, '--user-data-dir=%s' % profile] + try_args |
| 496 |
474 # Run test on the pivot revision. | 497 # Run test on the pivot revision. |
475 status = None | 498 status = None |
476 stdout = None | 499 stdout = None |
477 stderr = None | 500 stderr = None |
478 try: | 501 try: |
479 (status, stdout, stderr) = RunRevision(context, | 502 (status, stdout, stderr) = RunRevision(context, |
480 rev, | 503 rev, |
481 zipfile, | 504 zipfile, |
482 profile, | |
483 num_runs, | 505 num_runs, |
484 try_args) | 506 execute_args) |
485 except Exception, e: | 507 except Exception, e: |
486 print >>sys.stderr, e | 508 print >>sys.stderr, e |
| 509 |
487 os.unlink(zipfile) | 510 os.unlink(zipfile) |
488 zipfile = None | 511 zipfile = None |
489 | 512 |
490 # Call the evaluate function to see if the current revision is good or bad. | 513 # Call the evaluate function to see if the current revision is good or bad. |
491 # On that basis, kill one of the background downloads and complete the | 514 # On that basis, kill one of the background downloads and complete the |
492 # other, as described in the comments above. | 515 # other, as described in the comments above. |
493 try: | 516 try: |
494 answer = evaluate(rev, official_builds, status, stdout, stderr) | 517 answer = evaluate(rev, official_builds, status, stdout, stderr) |
495 if answer == 'g': | 518 if answer == 'g': |
496 good = pivot | 519 good = pivot |
(...skipping 174 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
671 print ' ' + WEBKIT_CHANGELOG_URL % (first_known_bad_webkit_rev, | 694 print ' ' + WEBKIT_CHANGELOG_URL % (first_known_bad_webkit_rev, |
672 last_known_good_webkit_rev) | 695 last_known_good_webkit_rev) |
673 print 'CHANGELOG URL:' | 696 print 'CHANGELOG URL:' |
674 if opts.official_builds: | 697 if opts.official_builds: |
675 print OFFICIAL_CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev) | 698 print OFFICIAL_CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev) |
676 else: | 699 else: |
677 print ' ' + CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev) | 700 print ' ' + CHANGELOG_URL % (last_known_good_rev, first_known_bad_rev) |
678 | 701 |
679 if __name__ == '__main__': | 702 if __name__ == '__main__': |
680 sys.exit(main()) | 703 sys.exit(main()) |
OLD | NEW |