Changeset 51012 in webkit


Ignore:
Timestamp:
Nov 16, 2009 12:07:38 AM (14 years ago)
Author:
abarth@webkit.org
Message:

2009-11-15 Adam Barth <abarth@webkit.org>

Reviewed by Eric Seidel.

Refactor bugzilla-tool to allow for multiple queues
https://bugs.webkit.org/show_bug.cgi?id=31513

Divide the commit queue class into three class to make creating
additional queues easier.

  • Scripts/bugzilla-tool:
Location:
trunk/WebKitTools
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/WebKitTools/ChangeLog

    r51010 r51012  
     12009-11-15  Adam Barth  <abarth@webkit.org>
     2
     3        Reviewed by Eric Seidel.
     4
     5        Refactor bugzilla-tool to allow for multiple queues
     6        https://bugs.webkit.org/show_bug.cgi?id=31513
     7
     8        Divide the commit queue class into three class to make creating
     9        additional queues easier.
     10
     11        * Scripts/bugzilla-tool:
     12
    1132009-11-15  Shinichiro Hamaji  <hamaji@chromium.org>
    214
  • trunk/WebKitTools/Scripts/bugzilla-tool

    r51001 r51012  
    675675
    676676
    677 class LandPatchesFromCommitQueue(Command):
    678     def __init__(self):
    679         options = [
    680             make_option("--no-confirm", action="store_false", dest="confirm", default=True, help="Do not ask the user for confirmation before running the queue.  Dangerous!"),
    681             make_option("--status-host", action="store", type="string", dest="status_host", default=StatusBot.default_host, help="Do not ask the user for confirmation before running the queue.  Dangerous!"),
    682         ]
    683         Command.__init__(self, 'Run the commit queue.', options=options)
     677class OutputTee:
     678    def __init__(self):
    684679        self._original_stdout = None
    685680        self._original_stderr = None
    686681        self._files_for_output = []
    687682
    688     queue_log_path = 'commit_queue.log'
    689     bug_logs_directory = 'commit_queue_logs'
    690 
    691     log_date_format = "%Y-%m-%d %H:%M:%S"
    692     sleep_duration_text = "5 mins"
    693     seconds_to_sleep = 300
     683    def add_log(self, path):
     684        log_file = self._open_log_file(path)
     685        self._files_for_output.append(log_file)
     686        self._tee_outputs_to_files(self._files_for_output)
     687        return log_file
     688
     689    def remove_log(self, log_file):
     690        self._files_for_output.remove(log_file)
     691        self._tee_outputs_to_files(self._files_for_output)
     692        log_file.close()
     693
     694    @staticmethod
     695    def _open_log_file(log_path):
     696        (log_directory, log_name) = os.path.split(log_path)
     697        if log_directory and not os.path.exists(log_directory):
     698            os.makedirs(log_directory)
     699        return open(log_path, 'a+')
    694700
    695701    def _tee_outputs_to_files(self, files):
     
    704710            sys.stderr = self._original_stderr
    705711
     712
     713class WorkQueueDelegate:
     714    def queue_log_path(self):
     715        raise NotImplementedError, "subclasses must implement"
     716
     717    def work_logs_directory(self):
     718        raise NotImplementedError, "subclasses must implement"
     719
     720    def status_host(self):
     721        raise NotImplementedError, "subclasses must implement"
     722
     723    def begin_work_queue(self):
     724        raise NotImplementedError, "subclasses must implement"
     725
     726    def next_work_item(self):
     727        raise NotImplementedError, "subclasses must implement"
     728
     729    def should_proceed_with_work_item(self, work_item):
     730        # returns (safe_to_proceed, waiting_message, bug_id)
     731        raise NotImplementedError, "subclasses must implement"
     732
     733    def process_work_item(self, work_item):
     734        raise NotImplementedError, "subclasses must implement"
     735
     736    def handle_unexpected_error(self, work_item, message):
     737        raise NotImplementedError, "subclasses must implement"
     738
     739
     740class WorkQueue:
     741    def __init__(self, delegate):
     742        self._delegate = delegate
     743        self._output_tee = OutputTee()
     744
     745    log_date_format = "%Y-%m-%d %H:%M:%S"
     746    sleep_duration_text = "5 mins"
     747    seconds_to_sleep = 300
     748
     749    def run(self):
     750        self._begin_logging()
     751        self.status_bot = StatusBot(host=self._delegate.status_host())
     752
     753        self._delegate.begin_work_queue()
     754        while (True):
     755            self._ensure_work_log_closed()
     756            try:
     757                work_item = self._delegate.next_work_item()
     758                if not work_item:
     759                    self._update_status_and_sleep("Empty queue.")
     760                    continue
     761                (safe_to_proceed, waiting_message, bug_id) = self._delegate.should_proceed_with_work_item(work_item)
     762                if not safe_to_proceed:
     763                    self._update_status_and_sleep(waiting_message, bug_ig=bug_id)
     764                    continue
     765                self.status_bot.update_status(waiting_message, bug_id=bug_id)
     766            except Exception, e:
     767                # Don't try tell the status bot, in case telling it causes an exception.
     768                self._sleep("Exception while preparing queue: %s." % e)
     769                continue
     770
     771            self._open_work_log(bug_id)
     772            try:
     773                self._delegate.process_work_item(work_item)
     774            except ScriptError, e:
     775                # exit(2) is a special exit code we use to indicate that the error was already
     776                # handled by and we should keep looping anyway.
     777                if e.exit_code == 2:
     778                    continue
     779                message = "Unexpected failure when landing patch!  Please file a bug against bugzilla-tool.\n%s" % e.message_with_output()
     780                self._delegate.handle_unexpected_error(work_item, message)
     781        # Never reached.
     782        self._ensure_work_log_closed()
     783
     784    def _begin_logging(self):
     785        self._queue_log = self._output_tee.add_log(self._delegate.queue_log_path())
     786        self._work_log = None
     787
     788    def _open_work_log(self, bug_id):
     789        work_log_path = os.path.join(self._delegate.work_logs_directory(), "%s.log" % bug_id)
     790        self._work_log = self._output_tee.add_log(work_log_path)
     791
     792    def _ensure_work_log_closed(self):
     793        # If we still have a bug log open, close it.
     794        if self._work_log:
     795            self._output_tee.remove_log(self._work_log)
     796            self._work_log = None
     797
    706798    @classmethod
    707799    def _sleep_message(cls, message):
     
    720812        time.sleep(self.seconds_to_sleep)
    721813
    722     @staticmethod
    723     def _open_log_file(log_path):
    724         (log_directory, log_name) = os.path.split(log_path)
    725         if log_directory and not os.path.exists(log_directory):
    726             os.makedirs(log_directory)
    727         return open(log_path, 'a+')
    728 
    729     def _add_log_to_output_tee(self, path):
    730         log_file = self._open_log_file(path)
    731         self._files_for_output.append(log_file)
    732         self._tee_outputs_to_files(self._files_for_output)
    733         return log_file
    734 
    735     def _remove_log_from_output_tee(self, log_file):
    736         self._files_for_output.remove(log_file)
    737         self._tee_outputs_to_files(self._files_for_output)
    738         log_file.close()
    739 
    740     def execute(self, options, args, tool):
    741         log("CAUTION: commit-queue will discard all local changes in %s" % tool.scm().checkout_root)
    742         if options.confirm:
     814
     815class LandPatchesFromCommitQueue(Command):
     816    def __init__(self):
     817        options = [
     818            make_option("--no-confirm", action="store_false", dest="confirm", default=True, help="Do not ask the user for confirmation before running the queue.  Dangerous!"),
     819            make_option("--status-host", action="store", type="string", dest="status_host", default=StatusBot.default_host, help="Do not ask the user for confirmation before running the queue.  Dangerous!"),
     820        ]
     821        Command.__init__(self, 'Run the commit queue.', options=options)
     822
     823    def queue_log_path(self):
     824        return 'commit_queue.log'
     825
     826    def work_logs_directory(self):
     827        return 'commit_queue_logs'
     828
     829    def status_host(self):
     830        return self.options.status_host
     831
     832    def begin_work_queue(self):
     833        log("CAUTION: commit-queue will discard all local changes in %s" % self.tool.scm().checkout_root)
     834        if self.options.confirm:
    743835            response = raw_input("Are you sure?  Type 'yes' to continue: ")
    744836            if (response != 'yes'):
    745837                error("User declined.")
    746 
    747         queue_log = self._add_log_to_output_tee(self.queue_log_path)
    748         log("Running WebKit Commit Queue. %s" % datetime.now().strftime(self.log_date_format))
    749 
    750         self.status_bot = StatusBot(host=options.status_host)
    751 
    752         bug_log = None
    753         while (True):
    754             # If we still have a bug log open from the last loop, close it.
    755             if bug_log:
    756                 self._remove_log_from_output_tee(bug_log)
    757                 bug_log = None
    758             # Either of these calls could throw URLError which shouldn't stop the queue.
    759             # We catch all exceptions just in case.
    760             try:
    761                 # Fetch patches instead of just bug ids to that we validate reviewer/committer flags on every patch.
    762                 patches = tool.bugs.fetch_patches_from_commit_queue(reject_invalid_patches=True)
    763                 if not len(patches):
    764                     self._update_status_and_sleep("Empty queue.")
    765                     continue
    766                 patch_ids = map(lambda patch: patch['id'], patches)
    767                 first_bug_id = patches[0]['bug_id']
    768                 log("%s in commit queue [%s]" % (pluralize('patch', len(patches)), ", ".join(patch_ids)))
    769 
    770                 red_builders_names = tool.buildbot.red_core_builders_names()
    771                 if red_builders_names:
    772                     red_builders_names = map(lambda name: '"%s"' % name, red_builders_names) # Add quotes around the names.
    773                     self._update_status_and_sleep("Builders [%s] are red. See http://build.webkit.org." % ", ".join(red_builders_names))
    774                     continue
    775 
    776                 self.status_bot.update_status("Landing patches from bug %s." % first_bug_id, bug_id=first_bug_id)
    777             except Exception, e:
    778                 # Don't try tell the status bot, in case telling it causes an exception.
    779                 self._sleep("Exception while checking queue and bots: %s." % e)
    780                 continue
    781 
    782             # Try to land patches on the first bug in the queue before looping
    783             bug_log_path = os.path.join(self.bug_logs_directory, "%s.log" % first_bug_id)
    784             bug_log = self._add_log_to_output_tee(bug_log_path)
    785             bugzilla_tool_path = __file__ # re-execute this script
    786             bugzilla_tool_args = [bugzilla_tool_path, 'land-patches', '--force-clean', '--commit-queue', '--quiet', first_bug_id]
    787             try:
    788                 WebKitLandingScripts.run_and_throw_if_fail(bugzilla_tool_args)
    789             except ScriptError, e:
    790                 # Unexpected failure!  Mark the patch as commit-queue- and comment in the bug.
    791                 # exit(2) is a special exit code we use to indicate that the error was already handled by land-patches and we should keep looping anyway.
    792                 if e.exit_code == 2:
    793                     continue
    794                 message = "Unexpected failure when landing patch!  Please file a bug against bugzilla-tool.\n%s" % e.message_with_output()
    795                 # We don't have a patch id at this point, so try to grab the first patch off of the bug in question.
    796                 patches = tool.bugs.fetch_commit_queue_patches_from_bug(first_bug_id)
    797                 non_obsolete_patches = filter(lambda patch: not patch['is_obsolete'], patches)
    798                 if not len(non_obsolete_patches):
    799                     # If there are no patches left on the bug, assume land-patches already closed it before dying, and just continue.
    800                     log(message)
    801                     continue
    802                 first_patch_id = non_obsolete_patches[0]['id']
    803                 tool.bugs.reject_patch_from_commit_queue(first_patch_id, message)
    804 
    805         # Never reached.
    806         if bug_log:
    807             self._remove_log_from_output_tee(bug_log)
    808         self._remove_log_from_output_tee(queue_log)
    809 
     838        log("Running WebKit Commit Queue. %s" % datetime.now().strftime(WorkQueue.log_date_format))
     839
     840    def next_work_item(self):
     841        # Fetch patches instead of just bug ids to that we validate reviewer/committer flags on every patch.
     842        patches = self.tool.bugs.fetch_patches_from_commit_queue(reject_invalid_patches=True)
     843        if not len(patches):
     844            return None
     845        patch_ids = map(lambda patch: patch['id'], patches)
     846        log("%s in commit queue [%s]" % (pluralize('patch', len(patches)), ", ".join(patch_ids)))
     847        return patches[0]['bug_id']
     848
     849    def should_proceed_with_work_item(self, bug_id):
     850        red_builders_names = self.tool.buildbot.red_core_builders_names()
     851        if red_builders_names:
     852            red_builders_names = map(lambda name: '"%s"' % name, red_builders_names) # Add quotes around the names.
     853            return (False, "Builders [%s] are red. See http://build.webkit.org." % ", ".join(red_builders_names), None)
     854        return (True, "Landing patches from bug %s." % bug_id, bug_id)
     855
     856    def process_work_item(self, bug_id):
     857        bugzilla_tool_path = __file__ # re-execute this script
     858        bugzilla_tool_args = [bugzilla_tool_path, 'land-patches', '--force-clean', '--commit-queue', '--quiet', bug_id]
     859        WebKitLandingScripts.run_and_throw_if_fail(bugzilla_tool_args)
     860
     861    def handle_unexpected_error(self, bug_id, message):
     862        # We don't have a patch id at this point, so try to grab the first patch off
     863        # of the bug in question.  We plan to update the commit-queue to opearate
     864        # off of patch ids in the near future.
     865        patches = self.tool.bugs.fetch_commit_queue_patches_from_bug(bug_id)
     866        non_obsolete_patches = filter(lambda patch: not patch['is_obsolete'], patches)
     867        if not len(non_obsolete_patches):
     868            # If there are no patches left on the bug, assume land-patches already closed it before dying, and just continue.
     869            log(message)
     870            return
     871        bug_id = non_obsolete_patches[0]['id']
     872        self.tool.bugs.reject_patch_from_commit_queue(bug_id, message)
     873
     874    def execute(self, options, args, tool):
     875        self.options = options
     876        self.tool = tool
     877        work_queue = WorkQueue(self)
     878        work_queue.run()
    810879
    811880class NonWrappingEpilogIndentedHelpFormatter(IndentedHelpFormatter):
Note: See TracChangeset for help on using the changeset viewer.