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

Side by Side Diff: third_party/WebKit/Tools/Scripts/webkitpy/tool/commands/rebaseline.py

Issue 2191423003: Extract AutoRebaseline out of rebaseline.py. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebased Created 4 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
OLDNEW
1 # Copyright (c) 2010 Google Inc. All rights reserved. 1 # Copyright (c) 2010 Google Inc. All rights reserved.
2 # 2 #
3 # Redistribution and use in source and binary forms, with or without 3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions are 4 # modification, are permitted provided that the following conditions are
5 # met: 5 # met:
6 # 6 #
7 # * Redistributions of source code must retain the above copyright 7 # * Redistributions of source code must retain the above copyright
8 # notice, this list of conditions and the following disclaimer. 8 # notice, this list of conditions and the following disclaimer.
9 # * Redistributions in binary form must reproduce the above 9 # * Redistributions in binary form must reproduce the above
10 # copyright notice, this list of conditions and the following disclaimer 10 # copyright notice, this list of conditions and the following disclaimer
(...skipping 12 matching lines...) Expand all
23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 # (INCLUDING NEGLIGENCE OR/ OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 # (INCLUDING NEGLIGENCE OR/ OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 28
29 from __future__ import print_function 29 from __future__ import print_function
30 import json 30 import json
31 import logging 31 import logging
32 import optparse 32 import optparse
33 import re
34 import sys 33 import sys
35 import time
36 import traceback 34 import traceback
37 import urllib2
38 35
39 from webkitpy.common.memoized import memoized 36 from webkitpy.common.memoized import memoized
40 from webkitpy.common.net.buildbot import Build 37 from webkitpy.common.net.buildbot import Build
41 from webkitpy.common.system.executive import ScriptError 38 from webkitpy.common.system.executive import ScriptError
42 from webkitpy.layout_tests.models.test_expectations import TestExpectations, BAS ELINE_SUFFIX_LIST, SKIP 39 from webkitpy.layout_tests.models.test_expectations import TestExpectations, BAS ELINE_SUFFIX_LIST, SKIP
43 from webkitpy.layout_tests.port import factory 40 from webkitpy.layout_tests.port import factory
44 from webkitpy.tool.commands.command import Command 41 from webkitpy.tool.commands.command import Command
45 42
46 43
47 _log = logging.getLogger(__name__) 44 _log = logging.getLogger(__name__)
(...skipping 587 matching lines...) Expand 10 before | Expand all | Expand 10 after
635 for test in args: 632 for test in args:
636 if test not in test_prefix_list: 633 if test not in test_prefix_list:
637 test_prefix_list[test] = {} 634 test_prefix_list[test] = {}
638 build = Build(builder) 635 build = Build(builder)
639 test_prefix_list[test][build] = suffixes_to_update 636 test_prefix_list[test][build] = suffixes_to_update
640 637
641 if options.verbose: 638 if options.verbose:
642 _log.debug("rebaseline-json: " + str(test_prefix_list)) 639 _log.debug("rebaseline-json: " + str(test_prefix_list))
643 640
644 self._rebaseline(options, test_prefix_list) 641 self._rebaseline(options, test_prefix_list)
645
646
647 class AutoRebaseline(AbstractParallelRebaselineCommand):
648 name = "auto-rebaseline"
649 help_text = "Rebaselines any NeedsRebaseline lines in TestExpectations that have cycled through all the bots."
650 AUTO_REBASELINE_BRANCH_NAME = "auto-rebaseline-temporary-branch"
651 AUTO_REBASELINE_ALT_BRANCH_NAME = "auto-rebaseline-alt-temporary-branch"
652
653 # Rietveld uploader stinks. Limit the number of rebaselines in a given patch to keep upload from failing.
654 # FIXME: http://crbug.com/263676 Obviously we should fix the uploader here.
655 MAX_LINES_TO_REBASELINE = 200
656
657 SECONDS_BEFORE_GIVING_UP = 300
658
659 def __init__(self):
660 super(AutoRebaseline, self).__init__(options=[
661 # FIXME: Remove this option.
662 self.no_optimize_option,
663 # FIXME: Remove this option.
664 self.results_directory_option,
665 optparse.make_option("--auth-refresh-token-json", help="Rietveld aut h refresh JSON token."),
666 optparse.make_option("--dry-run", action='store_true', default=False ,
667 help='Run without creating a temporary branch, committing locally, or uploading/landing '
668 'changes to the remote repository.')
669 ])
670 self._blame_regex = re.compile(r"""
671 ^(\S*) # Commit hash
672 [^(]* \( # Whitespace and open parenthesis
673 < # Email address is surrounded by <>
674 (
675 [^@]+ # Username preceding @
676 @
677 [^@>]+ # Domain terminated by @ or >, some lines have an ad ditional @ fragment after the email.
678 )
679 .*?([^ ]*) # Test file name
680 \ \[ # Single space followed by opening [ for expectation specifier
681 [^[]*$ # Prevents matching previous [ for version specifier s instead of expectation specifiers
682 """, re.VERBOSE)
683
684 def bot_revision_data(self, scm):
685 revisions = []
686 for result in self.build_data().values():
687 if result.run_was_interrupted():
688 _log.error("Can't rebaseline because the latest run on %s exited early.", result.builder_name())
689 return []
690 revisions.append({
691 "builder": result.builder_name(),
692 "revision": result.chromium_revision(scm),
693 })
694 return revisions
695
696 def _strip_comments(self, line):
697 comment_index = line.find("#")
698 if comment_index == -1:
699 comment_index = len(line)
700 return re.sub(r"\s+", " ", line[:comment_index].strip())
701
702 def tests_to_rebaseline(self, tool, min_revision, print_revisions):
703 port = tool.port_factory.get()
704 expectations_file_path = port.path_to_generic_test_expectations_file()
705
706 tests = set()
707 revision = None
708 commit = None
709 author = None
710 bugs = set()
711 has_any_needs_rebaseline_lines = False
712
713 for line in tool.scm().blame(expectations_file_path).split("\n"):
714 line = self._strip_comments(line)
715 if "NeedsRebaseline" not in line:
716 continue
717
718 has_any_needs_rebaseline_lines = True
719
720 parsed_line = self._blame_regex.match(line)
721 if not parsed_line:
722 # Deal gracefully with inability to parse blame info for a line in TestExpectations.
723 # Parsing could fail if for example during local debugging the d eveloper modifies
724 # TestExpectations and does not commit.
725 _log.info("Couldn't find blame info for expectations line, skipp ing [line=%s].", line)
726 continue
727
728 commit_hash = parsed_line.group(1)
729 commit_position = tool.scm().commit_position_from_git_commit(commit_ hash)
730
731 test = parsed_line.group(3)
732 if print_revisions:
733 _log.info("%s is waiting for r%s", test, commit_position)
734
735 if not commit_position or commit_position > min_revision:
736 continue
737
738 if revision and commit_position != revision:
739 continue
740
741 if not revision:
742 revision = commit_position
743 commit = commit_hash
744 author = parsed_line.group(2)
745
746 bugs.update(re.findall(r"crbug\.com\/(\d+)", line))
747 tests.add(test)
748
749 if len(tests) >= self.MAX_LINES_TO_REBASELINE:
750 _log.info("Too many tests to rebaseline in one patch. Doing the first %d.", self.MAX_LINES_TO_REBASELINE)
751 break
752
753 return tests, revision, commit, author, bugs, has_any_needs_rebaseline_l ines
754
755 def link_to_patch(self, commit):
756 return "https://chromium.googlesource.com/chromium/src/+/" + commit
757
758 def commit_message(self, author, revision, commit, bugs):
759 bug_string = ""
760 if bugs:
761 bug_string = "BUG=%s\n" % ",".join(bugs)
762
763 return """Auto-rebaseline for r%s
764
765 %s
766
767 %sTBR=%s
768 """ % (revision, self.link_to_patch(commit), bug_string, author)
769
770 def get_test_prefix_list(self, tests):
771 test_prefix_list = {}
772 lines_to_remove = {}
773
774 for builder_name in self._release_builders():
775 port_name = self._tool.builders.port_name_for_builder_name(builder_n ame)
776 port = self._tool.port_factory.get(port_name)
777 expectations = TestExpectations(port, include_overrides=True)
778 for test in expectations.get_needs_rebaseline_failures():
779 if test not in tests:
780 continue
781
782 if test not in test_prefix_list:
783 lines_to_remove[test] = []
784 test_prefix_list[test] = {}
785 lines_to_remove[test].append(builder_name)
786 test_prefix_list[test][Build(builder_name)] = BASELINE_SUFFIX_LI ST
787
788 return test_prefix_list, lines_to_remove
789
790 def _run_git_cl_command(self, options, command):
791 subprocess_command = ['git', 'cl'] + command
792 if options.verbose:
793 subprocess_command.append('--verbose')
794 if options.auth_refresh_token_json:
795 subprocess_command.append('--auth-refresh-token-json')
796 subprocess_command.append(options.auth_refresh_token_json)
797
798 process = self._tool.executive.popen(subprocess_command, stdout=self._to ol.executive.PIPE,
799 stderr=self._tool.executive.STDOUT)
800 last_output_time = time.time()
801
802 # git cl sometimes completely hangs. Bail if we haven't gotten any outpu t to stdout/stderr in a while.
803 while process.poll() is None and time.time() < last_output_time + self.S ECONDS_BEFORE_GIVING_UP:
804 # FIXME: This doesn't make any sense. readline blocks, so all this c ode to
805 # try and bail is useless. Instead, we should do the readline calls on a
806 # subthread. Then the rest of this code would make sense.
807 out = process.stdout.readline().rstrip('\n')
808 if out:
809 last_output_time = time.time()
810 _log.info(out)
811
812 if process.poll() is None:
813 _log.error('Command hung: %s', subprocess_command)
814 return False
815 return True
816
817 # FIXME: Move this somewhere more general.
818 def tree_status(self):
819 blink_tree_status_url = "http://chromium-status.appspot.com/status"
820 status = urllib2.urlopen(blink_tree_status_url).read().lower()
821 if 'closed' in status or status == "0":
822 return 'closed'
823 elif 'open' in status or status == "1":
824 return 'open'
825 return 'unknown'
826
827 def execute(self, options, args, tool):
828 if tool.scm().executable_name == "svn":
829 _log.error("Auto rebaseline only works with a git checkout.")
830 return
831
832 if not options.dry_run and tool.scm().has_working_directory_changes():
833 _log.error("Cannot proceed with working directory changes. Clean wor king directory first.")
834 return
835
836 revision_data = self.bot_revision_data(tool.scm())
837 if not revision_data:
838 return
839
840 min_revision = int(min([item["revision"] for item in revision_data]))
841 tests, revision, commit, author, bugs, _ = self.tests_to_rebaseline(
842 tool, min_revision, print_revisions=options.verbose)
843
844 if options.verbose:
845 _log.info("Min revision across all bots is %s.", min_revision)
846 for item in revision_data:
847 _log.info("%s: r%s", item["builder"], item["revision"])
848
849 if not tests:
850 _log.debug('No tests to rebaseline.')
851 return
852
853 if self.tree_status() == 'closed':
854 _log.info('Cannot proceed. Tree is closed.')
855 return
856
857 _log.info('Rebaselining %s for r%s by %s.', list(tests), revision, autho r)
858
859 test_prefix_list, _ = self.get_test_prefix_list(tests)
860
861 did_switch_branches = False
862 did_finish = False
863 old_branch_name_or_ref = ''
864 rebaseline_branch_name = self.AUTO_REBASELINE_BRANCH_NAME
865 try:
866 # Save the current branch name and check out a clean branch for the patch.
867 old_branch_name_or_ref = tool.scm().current_branch_or_ref()
868 if old_branch_name_or_ref == self.AUTO_REBASELINE_BRANCH_NAME:
869 rebaseline_branch_name = self.AUTO_REBASELINE_ALT_BRANCH_NAME
870 if not options.dry_run:
871 tool.scm().delete_branch(rebaseline_branch_name)
872 tool.scm().create_clean_branch(rebaseline_branch_name)
873 did_switch_branches = True
874
875 if test_prefix_list:
876 self._rebaseline(options, test_prefix_list)
877
878 if options.dry_run:
879 return
880
881 tool.scm().commit_locally_with_message(
882 self.commit_message(author, revision, commit, bugs))
883
884 # FIXME: It would be nice if we could dcommit the patch without uplo ading, but still
885 # go through all the precommit hooks. For rebaselines with lots of f iles, uploading
886 # takes a long time and sometimes fails, but we don't want to commit if, e.g. the
887 # tree is closed.
888 did_finish = self._run_git_cl_command(options, ['upload', '-f'])
889
890 if did_finish:
891 # Uploading can take a very long time. Do another pull to make s ure TestExpectations is up to date,
892 # so the dcommit can go through.
893 # FIXME: Log the pull and dcommit stdout/stderr to the log-serve r.
894 tool.executive.run_command(['git', 'pull'])
895
896 self._run_git_cl_command(options, ['land', '-f', '-v'])
897 except Exception:
898 traceback.print_exc(file=sys.stderr)
899 finally:
900 if did_switch_branches:
901 if did_finish:
902 # Close the issue if dcommit failed.
903 issue_already_closed = tool.executive.run_command(
904 ['git', 'config', 'branch.%s.rietveldissue' % rebaseline _branch_name],
905 return_exit_code=True)
906 if not issue_already_closed:
907 self._run_git_cl_command(options, ['set_close'])
908
909 tool.scm().ensure_cleanly_tracking_remote_master()
910 if old_branch_name_or_ref:
911 tool.scm().checkout_branch(old_branch_name_or_ref)
912 tool.scm().delete_branch(rebaseline_branch_name)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698