| 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 |