| 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 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 55 import urllib | 55 import urllib |
| 56 from distutils.version import LooseVersion | 56 from distutils.version import LooseVersion |
| 57 from xml.etree import ElementTree | 57 from xml.etree import ElementTree |
| 58 import zipfile | 58 import zipfile |
| 59 | 59 |
| 60 | 60 |
| 61 class PathContext(object): | 61 class PathContext(object): |
| 62 """A PathContext is used to carry the information used to construct URLs and | 62 """A PathContext is used to carry the information used to construct URLs and |
| 63 paths when dealing with the storage server and archives.""" | 63 paths when dealing with the storage server and archives.""" |
| 64 def __init__(self, base_url, platform, good_revision, bad_revision, | 64 def __init__(self, base_url, platform, good_revision, bad_revision, |
| 65 is_official, is_aura, flash_path = None, pdf_path = None): | 65 is_official, is_aura, flash_path = None): |
| 66 super(PathContext, self).__init__() | 66 super(PathContext, self).__init__() |
| 67 # Store off the input parameters. | 67 # Store off the input parameters. |
| 68 self.base_url = base_url | 68 self.base_url = base_url |
| 69 self.platform = platform # What's passed in to the '-a/--archive' option. | 69 self.platform = platform # What's passed in to the '-a/--archive' option. |
| 70 self.good_revision = good_revision | 70 self.good_revision = good_revision |
| 71 self.bad_revision = bad_revision | 71 self.bad_revision = bad_revision |
| 72 self.is_official = is_official | 72 self.is_official = is_official |
| 73 self.is_aura = is_aura | 73 self.is_aura = is_aura |
| 74 self.flash_path = flash_path | 74 self.flash_path = flash_path |
| 75 self.pdf_path = pdf_path | |
| 76 | 75 |
| 77 # The name of the ZIP file in a revision directory on the server. | 76 # The name of the ZIP file in a revision directory on the server. |
| 78 self.archive_name = None | 77 self.archive_name = None |
| 79 | 78 |
| 80 # Set some internal members: | 79 # Set some internal members: |
| 81 # _listing_platform_dir = Directory that holds revisions. Ends with a '/'. | 80 # _listing_platform_dir = Directory that holds revisions. Ends with a '/'. |
| 82 # _archive_extract_dir = Uncompressed directory in the archive_name file. | 81 # _archive_extract_dir = Uncompressed directory in the archive_name file. |
| 83 # _binary_name = The name of the executable to run. | 82 # _binary_name = The name of the executable to run. |
| 84 if self.platform in ('linux', 'linux64', 'linux-arm'): | 83 if self.platform in ('linux', 'linux64', 'linux-arm'): |
| 85 self._binary_name = 'chrome' | 84 self._binary_name = 'chrome' |
| (...skipping 270 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 356 | 355 |
| 357 # Create a temp directory and unzip the revision into it. | 356 # Create a temp directory and unzip the revision into it. |
| 358 cwd = os.getcwd() | 357 cwd = os.getcwd() |
| 359 tempdir = tempfile.mkdtemp(prefix='bisect_tmp') | 358 tempdir = tempfile.mkdtemp(prefix='bisect_tmp') |
| 360 UnzipFilenameToDir(zipfile, tempdir) | 359 UnzipFilenameToDir(zipfile, tempdir) |
| 361 os.chdir(tempdir) | 360 os.chdir(tempdir) |
| 362 | 361 |
| 363 # Run the build as many times as specified. | 362 # Run the build as many times as specified. |
| 364 testargs = ['--user-data-dir=%s' % profile] + args | 363 testargs = ['--user-data-dir=%s' % profile] + args |
| 365 # The sandbox must be run as root on Official Chrome, so bypass it. | 364 # The sandbox must be run as root on Official Chrome, so bypass it. |
| 366 if ((context.is_official or context.flash_path or context.pdf_path) and | 365 if ((context.is_official or context.flash_path) and |
| 367 context.platform.startswith('linux')): | 366 context.platform.startswith('linux')): |
| 368 testargs.append('--no-sandbox') | 367 testargs.append('--no-sandbox') |
| 369 if context.flash_path: | 368 if context.flash_path: |
| 370 testargs.append('--ppapi-flash-path=%s' % context.flash_path) | 369 testargs.append('--ppapi-flash-path=%s' % context.flash_path) |
| 371 # We have to pass a large enough Flash version, which currently needs not | 370 # We have to pass a large enough Flash version, which currently needs not |
| 372 # be correct. Instead of requiring the user of the script to figure out and | 371 # be correct. Instead of requiring the user of the script to figure out and |
| 373 # pass the correct version we just spoof it. | 372 # pass the correct version we just spoof it. |
| 374 testargs.append('--ppapi-flash-version=99.9.999.999') | 373 testargs.append('--ppapi-flash-version=99.9.999.999') |
| 375 | 374 |
| 376 if context.pdf_path: | |
| 377 shutil.copy(context.pdf_path, os.path.dirname(context.GetLaunchPath())) | |
| 378 testargs.append('--enable-print-preview') | |
| 379 | |
| 380 runcommand = [] | 375 runcommand = [] |
| 381 for token in shlex.split(command): | 376 for token in shlex.split(command): |
| 382 if token == "%a": | 377 if token == "%a": |
| 383 runcommand.extend(testargs) | 378 runcommand.extend(testargs) |
| 384 else: | 379 else: |
| 385 runcommand.append( \ | 380 runcommand.append( \ |
| 386 token.replace('%p', os.path.abspath(context.GetLaunchPath())) \ | 381 token.replace('%p', os.path.abspath(context.GetLaunchPath())) \ |
| 387 .replace('%s', ' '.join(testargs))) | 382 .replace('%s', ' '.join(testargs))) |
| 388 | 383 |
| 389 results = [] | 384 results = [] |
| (...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 462 platform, | 457 platform, |
| 463 official_builds, | 458 official_builds, |
| 464 is_aura, | 459 is_aura, |
| 465 good_rev=0, | 460 good_rev=0, |
| 466 bad_rev=0, | 461 bad_rev=0, |
| 467 num_runs=1, | 462 num_runs=1, |
| 468 command="%p %a", | 463 command="%p %a", |
| 469 try_args=(), | 464 try_args=(), |
| 470 profile=None, | 465 profile=None, |
| 471 flash_path=None, | 466 flash_path=None, |
| 472 pdf_path=None, | |
| 473 interactive=True, | 467 interactive=True, |
| 474 evaluate=AskIsGoodBuild): | 468 evaluate=AskIsGoodBuild): |
| 475 """Given known good and known bad revisions, run a binary search on all | 469 """Given known good and known bad revisions, run a binary search on all |
| 476 archived revisions to determine the last known good revision. | 470 archived revisions to determine the last known good revision. |
| 477 | 471 |
| 478 @param platform Which build to download/run ('mac', 'win', 'linux64', etc.). | 472 @param platform Which build to download/run ('mac', 'win', 'linux64', etc.). |
| 479 @param official_builds Specify build type (Chromium or Official build). | 473 @param official_builds Specify build type (Chromium or Official build). |
| 480 @param good_rev Number/tag of the known good revision. | 474 @param good_rev Number/tag of the known good revision. |
| 481 @param bad_rev Number/tag of the known bad revision. | 475 @param bad_rev Number/tag of the known bad revision. |
| 482 @param num_runs Number of times to run each build for asking good/bad. | 476 @param num_runs Number of times to run each build for asking good/bad. |
| (...skipping 16 matching lines...) Expand all Loading... |
| 499 is run on rev 75. | 493 is run on rev 75. |
| 500 | 494 |
| 501 - If rev 50 is bad, the download of rev 75 is cancelled, and the next test | 495 - If rev 50 is bad, the download of rev 75 is cancelled, and the next test |
| 502 is run on rev 25. | 496 is run on rev 25. |
| 503 """ | 497 """ |
| 504 | 498 |
| 505 if not profile: | 499 if not profile: |
| 506 profile = 'profile' | 500 profile = 'profile' |
| 507 | 501 |
| 508 context = PathContext(base_url, platform, good_rev, bad_rev, | 502 context = PathContext(base_url, platform, good_rev, bad_rev, |
| 509 official_builds, is_aura, flash_path, pdf_path) | 503 official_builds, is_aura, flash_path) |
| 510 cwd = os.getcwd() | 504 cwd = os.getcwd() |
| 511 | 505 |
| 512 print "Downloading list of known revisions..." | 506 print "Downloading list of known revisions..." |
| 513 _GetDownloadPath = lambda rev: os.path.join(cwd, | 507 _GetDownloadPath = lambda rev: os.path.join(cwd, |
| 514 '%s-%s' % (str(rev), context.archive_name)) | 508 '%s-%s' % (str(rev), context.archive_name)) |
| 515 if official_builds: | 509 if official_builds: |
| 516 revlist = context.GetOfficialBuildsList() | 510 revlist = context.GetOfficialBuildsList() |
| 517 else: | 511 else: |
| 518 revlist = context.GetRevList() | 512 revlist = context.GetRevList() |
| 519 | 513 |
| (...skipping 228 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 748 'Chromium archives.') | 742 'Chromium archives.') |
| 749 parser.add_option('-b', '--bad', type = 'str', | 743 parser.add_option('-b', '--bad', type = 'str', |
| 750 help = 'A bad revision to start bisection. ' + | 744 help = 'A bad revision to start bisection. ' + |
| 751 'May be earlier or later than the good revision. ' + | 745 'May be earlier or later than the good revision. ' + |
| 752 'Default is HEAD.') | 746 'Default is HEAD.') |
| 753 parser.add_option('-f', '--flash_path', type = 'str', | 747 parser.add_option('-f', '--flash_path', type = 'str', |
| 754 help = 'Absolute path to a recent Adobe Pepper Flash ' + | 748 help = 'Absolute path to a recent Adobe Pepper Flash ' + |
| 755 'binary to be used in this bisection (e.g. ' + | 749 'binary to be used in this bisection (e.g. ' + |
| 756 'on Windows C:\...\pepflashplayer.dll and on Linux ' + | 750 'on Windows C:\...\pepflashplayer.dll and on Linux ' + |
| 757 '/opt/google/chrome/PepperFlash/libpepflashplayer.so).') | 751 '/opt/google/chrome/PepperFlash/libpepflashplayer.so).') |
| 758 parser.add_option('-d', '--pdf_path', type = 'str', | |
| 759 help = 'Absolute path to a recent PDF pluggin ' + | |
| 760 'binary to be used in this bisection (e.g. ' + | |
| 761 'on Windows C:\...\pdf.dll and on Linux ' + | |
| 762 '/opt/google/chrome/libpdf.so). Option also enables ' + | |
| 763 'print preview.') | |
| 764 parser.add_option('-g', '--good', type = 'str', | 752 parser.add_option('-g', '--good', type = 'str', |
| 765 help = 'A good revision to start bisection. ' + | 753 help = 'A good revision to start bisection. ' + |
| 766 'May be earlier or later than the bad revision. ' + | 754 'May be earlier or later than the bad revision. ' + |
| 767 'Default is 0.') | 755 'Default is 0.') |
| 768 parser.add_option('-p', '--profile', '--user-data-dir', type = 'str', | 756 parser.add_option('-p', '--profile', '--user-data-dir', type = 'str', |
| 769 help = 'Profile to use; this will not reset every run. ' + | 757 help = 'Profile to use; this will not reset every run. ' + |
| 770 'Defaults to a clean profile.', default = 'profile') | 758 'Defaults to a clean profile.', default = 'profile') |
| 771 parser.add_option('-t', '--times', type = 'int', | 759 parser.add_option('-t', '--times', type = 'int', |
| 772 help = 'Number of times to run each build before asking ' + | 760 help = 'Number of times to run each build before asking ' + |
| 773 'if it\'s good or bad. Temporary profiles are reused.', | 761 'if it\'s good or bad. Temporary profiles are reused.', |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 824 if opts.good: | 812 if opts.good: |
| 825 good_rev = opts.good | 813 good_rev = opts.good |
| 826 else: | 814 else: |
| 827 good_rev = '0.0.0.0' if opts.official_builds else 0 | 815 good_rev = '0.0.0.0' if opts.official_builds else 0 |
| 828 | 816 |
| 829 if opts.flash_path: | 817 if opts.flash_path: |
| 830 flash_path = opts.flash_path | 818 flash_path = opts.flash_path |
| 831 msg = 'Could not find Flash binary at %s' % flash_path | 819 msg = 'Could not find Flash binary at %s' % flash_path |
| 832 assert os.path.exists(flash_path), msg | 820 assert os.path.exists(flash_path), msg |
| 833 | 821 |
| 834 if opts.pdf_path: | |
| 835 pdf_path = opts.pdf_path | |
| 836 msg = 'Could not find PDF binary at %s' % pdf_path | |
| 837 assert os.path.exists(pdf_path), msg | |
| 838 | |
| 839 if opts.official_builds: | 822 if opts.official_builds: |
| 840 good_rev = LooseVersion(good_rev) | 823 good_rev = LooseVersion(good_rev) |
| 841 bad_rev = LooseVersion(bad_rev) | 824 bad_rev = LooseVersion(bad_rev) |
| 842 else: | 825 else: |
| 843 good_rev = int(good_rev) | 826 good_rev = int(good_rev) |
| 844 bad_rev = int(bad_rev) | 827 bad_rev = int(bad_rev) |
| 845 | 828 |
| 846 if opts.times < 1: | 829 if opts.times < 1: |
| 847 print('Number of times to run (%d) must be greater than or equal to 1.' % | 830 print('Number of times to run (%d) must be greater than or equal to 1.' % |
| 848 opts.times) | 831 opts.times) |
| 849 parser.print_help() | 832 parser.print_help() |
| 850 return 1 | 833 return 1 |
| 851 | 834 |
| 852 (min_chromium_rev, max_chromium_rev) = Bisect( | 835 (min_chromium_rev, max_chromium_rev) = Bisect( |
| 853 base_url, opts.archive, opts.official_builds, opts.aura, good_rev, | 836 base_url, opts.archive, opts.official_builds, opts.aura, good_rev, |
| 854 bad_rev, opts.times, opts.command, args, opts.profile, opts.flash_path, | 837 bad_rev, opts.times, opts.command, args, opts.profile, opts.flash_path, |
| 855 opts.pdf_path, not opts.not_interactive) | 838 not opts.not_interactive) |
| 856 | 839 |
| 857 # Get corresponding blink revisions. | 840 # Get corresponding blink revisions. |
| 858 try: | 841 try: |
| 859 min_blink_rev = GetBlinkRevisionForChromiumRevision(context, | 842 min_blink_rev = GetBlinkRevisionForChromiumRevision(context, |
| 860 min_chromium_rev) | 843 min_chromium_rev) |
| 861 max_blink_rev = GetBlinkRevisionForChromiumRevision(context, | 844 max_blink_rev = GetBlinkRevisionForChromiumRevision(context, |
| 862 max_chromium_rev) | 845 max_chromium_rev) |
| 863 except Exception, e: | 846 except Exception, e: |
| 864 # Silently ignore the failure. | 847 # Silently ignore the failure. |
| 865 min_blink_rev, max_blink_rev = 0, 0 | 848 min_blink_rev, max_blink_rev = 0, 0 |
| (...skipping 21 matching lines...) Expand all Loading... |
| 887 "you might also want to do a Blink bisect.") | 870 "you might also want to do a Blink bisect.") |
| 888 | 871 |
| 889 print 'CHANGELOG URL:' | 872 print 'CHANGELOG URL:' |
| 890 if opts.official_builds: | 873 if opts.official_builds: |
| 891 print OFFICIAL_CHANGELOG_URL % (min_chromium_rev, max_chromium_rev) | 874 print OFFICIAL_CHANGELOG_URL % (min_chromium_rev, max_chromium_rev) |
| 892 else: | 875 else: |
| 893 print ' ' + CHANGELOG_URL % (min_chromium_rev, max_chromium_rev) | 876 print ' ' + CHANGELOG_URL % (min_chromium_rev, max_chromium_rev) |
| 894 | 877 |
| 895 if __name__ == '__main__': | 878 if __name__ == '__main__': |
| 896 sys.exit(main()) | 879 sys.exit(main()) |
| OLD | NEW |