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

Side by Side Diff: tools/bisect-builds.py

Issue 463243002: bisect-builds.py: Minor bug fixes for blink bisect and better error handling. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 6 years, 4 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,
(...skipping 298 matching lines...) Expand 10 before | Expand all | Expand 10 after
309 # until an entire directory listing is done. 309 # until an entire directory listing is done.
310 while next_marker: 310 while next_marker:
311 next_url = self.GetListingURL(next_marker) 311 next_url = self.GetListingURL(next_marker)
312 (new_revisions, next_marker, new_dict) = _FetchAndParse(next_url) 312 (new_revisions, next_marker, new_dict) = _FetchAndParse(next_url)
313 revisions.extend(new_revisions) 313 revisions.extend(new_revisions)
314 self.githash_svn_dict.update(new_dict) 314 self.githash_svn_dict.update(new_dict)
315 return revisions 315 return revisions
316 316
317 def _GetSVNRevisionFromGitHashWithoutGitCheckout(self, git_sha1, depot): 317 def _GetSVNRevisionFromGitHashWithoutGitCheckout(self, git_sha1, depot):
318 json_url = GITHASH_TO_SVN_URL[depot] % git_sha1 318 json_url = GITHASH_TO_SVN_URL[depot] % git_sha1
319 try: 319 response = urllib.urlopen(json_url)
320 response = urllib.urlopen(json_url) 320 if response.getcode() == 200:
321 except urllib.HTTPError as error: 321 try:
322 msg = 'HTTP Error %d for %s' % (error.getcode(), git_sha1) 322 data = json.loads(response.read()[4:])
323 return None 323 except ValueError:
324 data = json.loads(response.read()[4:]) 324 print 'ValueError for JSON URL: %s' % json_url
325 raise ValueError
326 else:
327 raise ValueError
325 if 'message' in data: 328 if 'message' in data:
326 message = data['message'].split('\n') 329 message = data['message'].split('\n')
327 message = [line for line in message if line.strip()] 330 message = [line for line in message if line.strip()]
328 search_pattern = re.compile(SEARCH_PATTERN[depot]) 331 search_pattern = re.compile(SEARCH_PATTERN[depot])
329 result = search_pattern.search(message[len(message)-1]) 332 result = search_pattern.search(message[len(message)-1])
330 if result: 333 if result:
331 return result.group(1) 334 return result.group(1)
332 print 'Failed to get svn revision number for %s' % git_sha1 335 print 'Failed to get svn revision number for %s' % git_sha1
333 raise ValueError 336 raise ValueError
334 337
(...skipping 294 matching lines...) Expand 10 before | Expand all | Expand 10 after
629 632
630 def WaitFor(self): 633 def WaitFor(self):
631 """Prints a message and waits for the download to complete. The download 634 """Prints a message and waits for the download to complete. The download
632 must have been started previously.""" 635 must have been started previously."""
633 assert self.thread, 'DownloadJob must be started before WaitFor is called.' 636 assert self.thread, 'DownloadJob must be started before WaitFor is called.'
634 print 'Downloading revision %s...' % str(self.rev) 637 print 'Downloading revision %s...' % str(self.rev)
635 self.progress_event.set() # Display progress of download. 638 self.progress_event.set() # Display progress of download.
636 self.thread.join() 639 self.thread.join()
637 640
638 641
639 def Bisect(base_url, 642 def Bisect(context,
640 platform,
641 official_builds,
642 is_aura,
643 is_asan,
644 use_local_repo,
645 good_rev=0,
646 bad_rev=0,
647 num_runs=1, 643 num_runs=1,
648 command='%p %a', 644 command='%p %a',
649 try_args=(), 645 try_args=(),
650 profile=None, 646 profile=None,
651 flash_path=None,
652 pdf_path=None,
653 interactive=True, 647 interactive=True,
654 evaluate=AskIsGoodBuild): 648 evaluate=AskIsGoodBuild):
655 """Given known good and known bad revisions, run a binary search on all 649 """Given known good and known bad revisions, run a binary search on all
656 archived revisions to determine the last known good revision. 650 archived revisions to determine the last known good revision.
657 651
658 @param platform Which build to download/run ('mac', 'win', 'linux64', etc.). 652 @param context PathContext object initialized with user provided parameters.
659 @param official_builds Specify build type (Chromium or Official build).
660 @param good_rev Number/tag of the known good revision.
661 @param bad_rev Number/tag of the known bad revision.
662 @param num_runs Number of times to run each build for asking good/bad. 653 @param num_runs Number of times to run each build for asking good/bad.
663 @param try_args A tuple of arguments to pass to the test application. 654 @param try_args A tuple of arguments to pass to the test application.
664 @param profile The name of the user profile to run with. 655 @param profile The name of the user profile to run with.
665 @param interactive If it is false, use command exit code for good or bad 656 @param interactive If it is false, use command exit code for good or bad
666 judgment of the argument build. 657 judgment of the argument build.
667 @param evaluate A function which returns 'g' if the argument build is good, 658 @param evaluate A function which returns 'g' if the argument build is good,
668 'b' if it's bad or 'u' if unknown. 659 'b' if it's bad or 'u' if unknown.
669 660
670 Threading is used to fetch Chromium revisions in the background, speeding up 661 Threading is used to fetch Chromium revisions in the background, speeding up
671 the user's experience. For example, suppose the bounds of the search are 662 the user's experience. For example, suppose the bounds of the search are
672 good_rev=0, bad_rev=100. The first revision to be checked is 50. Depending on 663 good_rev=0, bad_rev=100. The first revision to be checked is 50. Depending on
673 whether revision 50 is good or bad, the next revision to check will be either 664 whether revision 50 is good or bad, the next revision to check will be either
674 25 or 75. So, while revision 50 is being checked, the script will download 665 25 or 75. So, while revision 50 is being checked, the script will download
675 revisions 25 and 75 in the background. Once the good/bad verdict on rev 50 is 666 revisions 25 and 75 in the background. Once the good/bad verdict on rev 50 is
676 known: 667 known:
677 668
678 - If rev 50 is good, the download of rev 25 is cancelled, and the next test 669 - If rev 50 is good, the download of rev 25 is cancelled, and the next test
679 is run on rev 75. 670 is run on rev 75.
680 671
681 - If rev 50 is bad, the download of rev 75 is cancelled, and the next test 672 - If rev 50 is bad, the download of rev 75 is cancelled, and the next test
682 is run on rev 25. 673 is run on rev 25.
683 """ 674 """
684 675
685 if not profile: 676 if not profile:
686 profile = 'profile' 677 profile = 'profile'
687 678
688 context = PathContext(base_url, platform, good_rev, bad_rev, 679 good_rev = context.good_revision
689 official_builds, is_aura, is_asan, use_local_repo, 680 bad_rev = context.bad_revision
690 flash_path, pdf_path)
691 cwd = os.getcwd() 681 cwd = os.getcwd()
692 682
693 print 'Downloading list of known revisions...', 683 print 'Downloading list of known revisions...',
694 if not use_local_repo: 684 if not context.use_local_repo:
695 print '(use --use-local-repo for speed if you have a local checkout)' 685 print '(use --use-local-repo for speed if you have a local checkout)'
696 else: 686 else:
697 print 687 print
698 _GetDownloadPath = lambda rev: os.path.join(cwd, 688 _GetDownloadPath = lambda rev: os.path.join(cwd,
699 '%s-%s' % (str(rev), context.archive_name)) 689 '%s-%s' % (str(rev), context.archive_name))
700 if official_builds: 690 if context.is_official:
701 revlist = context.GetOfficialBuildsList() 691 revlist = context.GetOfficialBuildsList()
702 else: 692 else:
703 revlist = context.GetRevList() 693 revlist = context.GetRevList()
704 694
705 # Get a list of revisions to bisect across. 695 # Get a list of revisions to bisect across.
706 if len(revlist) < 2: # Don't have enough builds to bisect. 696 if len(revlist) < 2: # Don't have enough builds to bisect.
707 msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist 697 msg = 'We don\'t have enough builds to bisect. revlist: %s' % revlist
708 raise RuntimeError(msg) 698 raise RuntimeError(msg)
709 699
710 # Figure out our bookends and first pivot point; fetch the pivot revision. 700 # Figure out our bookends and first pivot point; fetch the pivot revision.
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after
767 # other, as described in the comments above. 757 # other, as described in the comments above.
768 try: 758 try:
769 if not interactive: 759 if not interactive:
770 if status: 760 if status:
771 answer = 'b' 761 answer = 'b'
772 print 'Bad revision: %s' % rev 762 print 'Bad revision: %s' % rev
773 else: 763 else:
774 answer = 'g' 764 answer = 'g'
775 print 'Good revision: %s' % rev 765 print 'Good revision: %s' % rev
776 else: 766 else:
777 answer = evaluate(rev, official_builds, status, stdout, stderr) 767 answer = evaluate(rev, context.is_official, status, stdout, stderr)
778 if ((answer == 'g' and good_rev < bad_rev) 768 if ((answer == 'g' and good_rev < bad_rev)
779 or (answer == 'b' and bad_rev < good_rev)): 769 or (answer == 'b' and bad_rev < good_rev)):
780 fetch.Stop() 770 fetch.Stop()
781 minrev = pivot 771 minrev = pivot
782 if down_fetch: 772 if down_fetch:
783 down_fetch.Stop() # Kill the download of the older revision. 773 down_fetch.Stop() # Kill the download of the older revision.
784 fetch = None 774 fetch = None
785 if up_fetch: 775 if up_fetch:
786 up_fetch.WaitFor() 776 up_fetch.WaitFor()
787 pivot = up_pivot 777 pivot = up_pivot
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
835 for f in [_GetDownloadPath(revlist[down_pivot]), 825 for f in [_GetDownloadPath(revlist[down_pivot]),
836 _GetDownloadPath(revlist[up_pivot])]: 826 _GetDownloadPath(revlist[up_pivot])]:
837 try: 827 try:
838 os.unlink(f) 828 os.unlink(f)
839 except OSError: 829 except OSError:
840 pass 830 pass
841 sys.exit(0) 831 sys.exit(0)
842 832
843 rev = revlist[pivot] 833 rev = revlist[pivot]
844 834
845 return (revlist[minrev], revlist[maxrev]) 835 return (revlist[minrev], revlist[maxrev], context)
846 836
847 837
848 def GetBlinkDEPSRevisionForChromiumRevision(rev): 838 def GetBlinkDEPSRevisionForChromiumRevision(rev):
849 """Returns the blink revision that was in REVISIONS file at 839 """Returns the blink revision that was in REVISIONS file at
850 chromium revision |rev|.""" 840 chromium revision |rev|."""
851 # . doesn't match newlines without re.DOTALL, so this is safe. 841 # . doesn't match newlines without re.DOTALL, so this is safe.
852 blink_re = re.compile(r'webkit_revision\D*(\d+)') 842 blink_re = re.compile(r'webkit_revision\D*(\d+)')
853 url = urllib.urlopen(DEPS_FILE % rev) 843 url = urllib.urlopen(DEPS_FILE % rev)
854 m = blink_re.search(url.read()) 844 m = blink_re.search(url.read())
855 url.close() 845 url.close()
856 if m: 846 if m:
857 return int(m.group(1)) 847 return int(m.group(1))
858 else: 848 else:
859 raise Exception('Could not get Blink revision for Chromium rev %d' % rev) 849 raise Exception('Could not get Blink revision for Chromium rev %d' % rev)
860 850
861 851
862 def GetBlinkRevisionForChromiumRevision(self, rev): 852 def GetBlinkRevisionForChromiumRevision(context, rev):
863 """Returns the blink revision that was in REVISIONS file at 853 """Returns the blink revision that was in REVISIONS file at
864 chromium revision |rev|.""" 854 chromium revision |rev|."""
865 def _IsRevisionNumber(revision): 855 def _IsRevisionNumber(revision):
866 if isinstance(revision, int): 856 if isinstance(revision, int):
867 return True 857 return True
868 else: 858 else:
869 return revision.isdigit() 859 return revision.isdigit()
870 if str(rev) in self.githash_svn_dict: 860 if str(rev) in context.githash_svn_dict:
871 rev = self.githash_svn_dict[str(rev)] 861 rev = context.githash_svn_dict[str(rev)]
872 file_url = '%s/%s%s/REVISIONS' % (self.base_url, 862 file_url = '%s/%s%s/REVISIONS' % (context.base_url,
873 self._listing_platform_dir, rev) 863 context._listing_platform_dir, rev)
874 url = urllib.urlopen(file_url) 864 url = urllib.urlopen(file_url)
875 data = json.loads(url.read()) 865 if url.getcode() == 200:
866 try:
867 data = json.loads(url.read())
868 except ValueError:
869 print 'ValueError for JSON URL: %s' % file_url
870 raise ValueError
871 else:
872 raise ValueError
876 url.close() 873 url.close()
877 if 'webkit_revision' in data: 874 if 'webkit_revision' in data:
878 blink_rev = data['webkit_revision'] 875 blink_rev = data['webkit_revision']
879 if not _IsRevisionNumber(blink_rev): 876 if not _IsRevisionNumber(blink_rev):
880 blink_rev = self.GetSVNRevisionFromGitHash(blink_rev, 'blink') 877 blink_rev = int(context.GetSVNRevisionFromGitHash(blink_rev, 'blink'))
881 return blink_rev 878 return blink_rev
882 else: 879 else:
883 raise Exception('Could not get blink revision for cr rev %d' % rev) 880 raise Exception('Could not get blink revision for cr rev %d' % rev)
884 881
885 882
886 def FixChromiumRevForBlink(revisions_final, revisions, self, rev): 883 def FixChromiumRevForBlink(revisions_final, revisions, self, rev):
887 """Returns the chromium revision that has the correct blink revision 884 """Returns the chromium revision that has the correct blink revision
888 for blink bisect, DEPS and REVISIONS file might not match since 885 for blink bisect, DEPS and REVISIONS file might not match since
889 blink snapshots point to tip of tree blink. 886 blink snapshots point to tip of tree blink.
890 Note: The revisions_final variable might get modified to include 887 Note: The revisions_final variable might get modified to include
(...skipping 146 matching lines...) Expand 10 before | Expand all | Expand 10 after
1037 return 1 1034 return 1
1038 1035
1039 if opts.asan: 1036 if opts.asan:
1040 base_url = ASAN_BASE_URL 1037 base_url = ASAN_BASE_URL
1041 elif opts.blink: 1038 elif opts.blink:
1042 base_url = WEBKIT_BASE_URL 1039 base_url = WEBKIT_BASE_URL
1043 else: 1040 else:
1044 base_url = CHROMIUM_BASE_URL 1041 base_url = CHROMIUM_BASE_URL
1045 1042
1046 # Create the context. Initialize 0 for the revisions as they are set below. 1043 # Create the context. Initialize 0 for the revisions as they are set below.
1047 context = PathContext(base_url, opts.archive, 0, 0, 1044 context = PathContext(base_url, opts.archive, opts.good, opts.bad,
1048 opts.official_builds, opts.aura, opts.asan, 1045 opts.official_builds, opts.aura, opts.asan,
1049 opts.use_local_repo, None) 1046 opts.use_local_repo, opts.flash_path, opts.pdf_path)
1050 # Pick a starting point, try to get HEAD for this. 1047 # Pick a starting point, try to get HEAD for this.
1051 if opts.bad: 1048 if not opts.bad:
1052 bad_rev = opts.bad 1049 context.bad_revision = '999.0.0.0'
1053 else: 1050 context.bad_revision = GetChromiumRevision(
1054 bad_rev = '999.0.0.0' 1051 context, context.GetLastChangeURL())
1055 if not opts.official_builds:
1056 bad_rev = GetChromiumRevision(context, context.GetLastChangeURL())
1057 1052
1058 # Find out when we were good. 1053 # Find out when we were good.
1059 if opts.good: 1054 if not opts.good:
1060 good_rev = opts.good 1055 context.good_revision = '0.0.0.0' if opts.official_builds else 0
1061 else:
1062 good_rev = '0.0.0.0' if opts.official_builds else 0
1063 1056
1064 if opts.flash_path: 1057 if opts.flash_path:
1065 flash_path = opts.flash_path 1058 msg = 'Could not find Flash binary at %s' % opts.flash_path
1066 msg = 'Could not find Flash binary at %s' % flash_path 1059 assert os.path.exists(opts.flash_path), msg
1067 assert os.path.exists(flash_path), msg
1068 1060
1069 if opts.pdf_path: 1061 if opts.pdf_path:
1070 pdf_path = opts.pdf_path 1062 msg = 'Could not find PDF binary at %s' % opts.pdf_path
1071 msg = 'Could not find PDF binary at %s' % pdf_path 1063 assert os.path.exists(opts.pdf_path), msg
1072 assert os.path.exists(pdf_path), msg
1073 1064
1074 if opts.official_builds: 1065 if opts.official_builds:
1075 good_rev = LooseVersion(good_rev) 1066 context.good_revision = LooseVersion(context.good_revision)
1076 bad_rev = LooseVersion(bad_rev) 1067 context.bad_revision = LooseVersion(context.bad_revision)
1077 else: 1068 else:
1078 good_rev = int(good_rev) 1069 context.good_revision = int(context.good_revision)
1079 bad_rev = int(bad_rev) 1070 context.bad_revision = int(context.bad_revision)
1080 1071
1081 if opts.times < 1: 1072 if opts.times < 1:
1082 print('Number of times to run (%d) must be greater than or equal to 1.' % 1073 print('Number of times to run (%d) must be greater than or equal to 1.' %
1083 opts.times) 1074 opts.times)
1084 parser.print_help() 1075 parser.print_help()
1085 return 1 1076 return 1
1086 1077
1087 if opts.asan: 1078 if opts.asan:
1088 evaluator = IsGoodASANBuild 1079 evaluator = IsGoodASANBuild
1089 else: 1080 else:
1090 evaluator = AskIsGoodBuild 1081 evaluator = AskIsGoodBuild
1091 1082
1092 (min_chromium_rev, max_chromium_rev) = Bisect( 1083 # Save these revision numbers to compare when showing the changelog URL
1093 base_url, opts.archive, opts.official_builds, opts.aura, opts.asan, 1084 # after the bisect.
1094 opts.use_local_repo, good_rev, bad_rev, opts.times, opts.command, 1085 good_rev = context.good_revision
1095 args, opts.profile, opts.flash_path, opts.pdf_path, 1086 bad_rev = context.bad_revision
1087
1088 (min_chromium_rev, max_chromium_rev, context) = Bisect(
1089 context, opts.times, opts.command, args, opts.profile,
1096 not opts.not_interactive, evaluator) 1090 not opts.not_interactive, evaluator)
1097 1091
1098 # Get corresponding blink revisions. 1092 # Get corresponding blink revisions.
1099 try: 1093 try:
1100 min_blink_rev = GetBlinkRevisionForChromiumRevision(context, 1094 min_blink_rev = GetBlinkRevisionForChromiumRevision(context,
1101 min_chromium_rev) 1095 min_chromium_rev)
1102 max_blink_rev = GetBlinkRevisionForChromiumRevision(context, 1096 max_blink_rev = GetBlinkRevisionForChromiumRevision(context,
1103 max_chromium_rev) 1097 max_chromium_rev)
1104 except Exception: 1098 except Exception:
1105 # Silently ignore the failure. 1099 # Silently ignore the failure.
(...skipping 23 matching lines...) Expand all
1129 1123
1130 print 'CHANGELOG URL:' 1124 print 'CHANGELOG URL:'
1131 if opts.official_builds: 1125 if opts.official_builds:
1132 print OFFICIAL_CHANGELOG_URL % (min_chromium_rev, max_chromium_rev) 1126 print OFFICIAL_CHANGELOG_URL % (min_chromium_rev, max_chromium_rev)
1133 else: 1127 else:
1134 print ' ' + CHANGELOG_URL % (min_chromium_rev, max_chromium_rev) 1128 print ' ' + CHANGELOG_URL % (min_chromium_rev, max_chromium_rev)
1135 1129
1136 1130
1137 if __name__ == '__main__': 1131 if __name__ == '__main__':
1138 sys.exit(main()) 1132 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