1dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# Copyright (c) 2009 Google Inc. All rights reserved.
2dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# Copyright (c) 2009 Apple Inc. All rights reserved.
3dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block#
4dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# Redistribution and use in source and binary forms, with or without
5dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# modification, are permitted provided that the following conditions are
6dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# met:
7dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block#
8dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block#     * Redistributions of source code must retain the above copyright
9dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# notice, this list of conditions and the following disclaimer.
10dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block#     * Redistributions in binary form must reproduce the above
11dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# copyright notice, this list of conditions and the following disclaimer
12dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# in the documentation and/or other materials provided with the
13dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# distribution.
14dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block#     * Neither the name of Google Inc. nor the names of its
15dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# contributors may be used to endorse or promote products derived from
16dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# this software without specific prior written permission.
17dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block#
18dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
30dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
31dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockfrom optparse import make_option
32dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
332fc2651226baac27029e38c9d6ef883fa32084dbSteve Blockfrom webkitpy.tool import steps
34a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch
35dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockfrom webkitpy.common.checkout.commitinfo import CommitInfo
36dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockfrom webkitpy.common.config.committers import CommitterList
37dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockfrom webkitpy.common.net.buildbot import BuildBot
38bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsenfrom webkitpy.common.net.regressionwindow import RegressionWindow
39dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockfrom webkitpy.common.system.user import User
40dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockfrom webkitpy.tool.grammar import pluralize
41dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockfrom webkitpy.tool.multicommandtool import AbstractDeclarativeCommand
42dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockfrom webkitpy.common.system.deprecated_logging import log
435abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrickfrom webkitpy.layout_tests import port
44dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
45dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
46a94275402997c11dd2e778633dacf4b7e630a35dBen Murdochclass SuggestReviewers(AbstractDeclarativeCommand):
47a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch    name = "suggest-reviewers"
48a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch    help_text = "Suggest reviewers for a patch based on recent changes to the modified files."
49a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch
50a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch    def __init__(self):
51a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        options = [
52a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            steps.Options.git_commit,
53a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        ]
54a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        AbstractDeclarativeCommand.__init__(self, options=options)
55a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch
56a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch    def execute(self, options, args, tool):
57a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        reviewers = tool.checkout().suggested_reviewers(options.git_commit)
58a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        print "\n".join([reviewer.full_name for reviewer in reviewers])
59a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch
60a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch
61dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockclass BugsToCommit(AbstractDeclarativeCommand):
62dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    name = "bugs-to-commit"
63dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    help_text = "List bugs in the commit-queue"
64dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
65dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    def execute(self, options, args, tool):
66dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        # FIXME: This command is poorly named.  It's fetching the commit-queue list here.  The name implies it's fetching pending-commit (all r+'d patches).
67dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        bug_ids = tool.bugs.queries.fetch_bug_ids_from_commit_queue()
68dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        for bug_id in bug_ids:
69dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            print "%s" % bug_id
70dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
71dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
72dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockclass PatchesInCommitQueue(AbstractDeclarativeCommand):
73dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    name = "patches-in-commit-queue"
74dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    help_text = "List patches in the commit-queue"
75dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
76dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    def execute(self, options, args, tool):
77dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        patches = tool.bugs.queries.fetch_patches_from_commit_queue()
78dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        log("Patches in commit queue:")
79dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        for patch in patches:
80dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            print patch.url()
81dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
82dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
83dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockclass PatchesToCommitQueue(AbstractDeclarativeCommand):
84dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    name = "patches-to-commit-queue"
85dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    help_text = "Patches which should be added to the commit queue"
86dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    def __init__(self):
87dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        options = [
88dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            make_option("--bugs", action="store_true", dest="bugs", help="Output bug links instead of patch links"),
89dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        ]
90dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        AbstractDeclarativeCommand.__init__(self, options=options)
91dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
92dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    @staticmethod
93dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    def _needs_commit_queue(patch):
94dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        if patch.commit_queue() == "+": # If it's already cq+, ignore the patch.
95dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            log("%s already has cq=%s" % (patch.id(), patch.commit_queue()))
96dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            return False
97dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
98dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        # We only need to worry about patches from contributers who are not yet committers.
99dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        committer_record = CommitterList().committer_by_email(patch.attacher_email())
100dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        if committer_record:
101dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            log("%s committer = %s" % (patch.id(), committer_record))
102dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        return not committer_record
103dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
104dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    def execute(self, options, args, tool):
105dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        patches = tool.bugs.queries.fetch_patches_from_pending_commit_list()
106dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        patches_needing_cq = filter(self._needs_commit_queue, patches)
107dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        if options.bugs:
108dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            bugs_needing_cq = map(lambda patch: patch.bug_id(), patches_needing_cq)
109dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            bugs_needing_cq = sorted(set(bugs_needing_cq))
110dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            for bug_id in bugs_needing_cq:
111dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block                print "%s" % tool.bugs.bug_url_for_bug_id(bug_id)
112dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        else:
113dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            for patch in patches_needing_cq:
114dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block                print "%s" % tool.bugs.attachment_url_for_id(patch.id(), action="edit")
115dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
116dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
117dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockclass PatchesToReview(AbstractDeclarativeCommand):
118dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    name = "patches-to-review"
119dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    help_text = "List patches that are pending review"
120dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
121dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    def execute(self, options, args, tool):
122dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        patch_ids = tool.bugs.queries.fetch_attachment_ids_from_review_queue()
123dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        log("Patches pending review:")
124dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        for patch_id in patch_ids:
125dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            print patch_id
126dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
127dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
128dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockclass LastGreenRevision(AbstractDeclarativeCommand):
129dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    name = "last-green-revision"
130dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    help_text = "Prints the last known good revision"
131dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
132dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    def execute(self, options, args, tool):
133bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen        print self._tool.buildbot.last_green_revision()
134dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
135dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
136dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockclass WhatBroke(AbstractDeclarativeCommand):
137dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    name = "what-broke"
138dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    help_text = "Print failing buildbots (%s) and what revisions broke them" % BuildBot.default_host
139dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
140dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    def _print_builder_line(self, builder_name, max_name_width, status_message):
141dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        print "%s : %s" % (builder_name.ljust(max_name_width), status_message)
142dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
143dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    def _print_blame_information_for_builder(self, builder_status, name_width, avoid_flakey_tests=True):
144bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen        builder = self._tool.buildbot.builder_with_name(builder_status["name"])
145dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        red_build = builder.build(builder_status["build_number"])
146bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen        regression_window = builder.find_regression_window(red_build)
147bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen        if not regression_window.failing_build():
148dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            self._print_builder_line(builder.name(), name_width, "FAIL (error loading build information)")
149dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            return
150bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen        if not regression_window.build_before_failure():
151bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen            self._print_builder_line(builder.name(), name_width, "FAIL (blame-list: sometime before %s?)" % regression_window.failing_build().revision())
152dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            return
153dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
154bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen        revisions = regression_window.revisions()
155dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        first_failure_message = ""
156bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen        if (regression_window.failing_build() == builder.build(builder_status["build_number"])):
157dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            first_failure_message = " FIRST FAILURE, possibly a flaky test"
158bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen        self._print_builder_line(builder.name(), name_width, "FAIL (blame-list: %s%s)" % (revisions, first_failure_message))
159bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen        for revision in revisions:
160bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen            commit_info = self._tool.checkout().commit_info_for_revision(revision)
161dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            if commit_info:
162bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen                print commit_info.blame_string(self._tool.bugs)
163dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            else:
164dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block                print "FAILED to fetch CommitInfo for r%s, likely missing ChangeLog" % revision
165dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
166dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    def execute(self, options, args, tool):
167dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        builder_statuses = tool.buildbot.builder_statuses()
168dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        longest_builder_name = max(map(len, map(lambda builder: builder["name"], builder_statuses)))
169dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        failing_builders = 0
170dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        for builder_status in builder_statuses:
171dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            # If the builder is green, print OK, exit.
172dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            if builder_status["is_green"]:
173dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block                continue
174dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            self._print_blame_information_for_builder(builder_status, name_width=longest_builder_name)
175dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            failing_builders += 1
176dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        if failing_builders:
177dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            print "%s of %s are failing" % (failing_builders, pluralize("builder", len(builder_statuses)))
178dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        else:
179dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            print "All builders are passing!"
180dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
181dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
182dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockclass ResultsFor(AbstractDeclarativeCommand):
183dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    name = "results-for"
184dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    help_text = "Print a list of failures for the passed revision from bots on %s" % BuildBot.default_host
185dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    argument_names = "REVISION"
186dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
187dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    def _print_layout_test_results(self, results):
188dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        if not results:
189dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            print " No results."
190dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            return
191dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        for title, files in results.parsed_results().items():
192dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            print " %s" % title
193dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            for filename in files:
194dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block                print "  %s" % filename
195dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
196dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    def execute(self, options, args, tool):
197bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen        builders = self._tool.buildbot.builders()
198dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        for builder in builders:
199dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            print "%s:" % builder.name()
200dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            build = builder.build_for_revision(args[0], allow_failed_lookups=True)
201dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            self._print_layout_test_results(build.layout_test_results())
202dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
203dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
204dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockclass FailureReason(AbstractDeclarativeCommand):
205dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    name = "failure-reason"
206dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    help_text = "Lists revisions where individual test failures started at %s" % BuildBot.default_host
207dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
208a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch    def _blame_line_for_revision(self, revision):
209a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        try:
210a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            commit_info = self._tool.checkout().commit_info_for_revision(revision)
211a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        except Exception, e:
212a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            return "FAILED to fetch CommitInfo for r%s, exception: %s" % (revision, e)
213a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        if not commit_info:
214a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            return "FAILED to fetch CommitInfo for r%s, likely missing ChangeLog" % revision
215a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        return commit_info.blame_string(self._tool.bugs)
216a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch
217a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch    def _print_blame_information_for_transition(self, regression_window, failing_tests):
218a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        red_build = regression_window.failing_build()
219dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        print "SUCCESS: Build %s (r%s) was the first to show failures: %s" % (red_build._number, red_build.revision(), failing_tests)
220dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        print "Suspect revisions:"
221a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        for revision in regression_window.revisions():
222a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            print self._blame_line_for_revision(revision)
223dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
224dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    def _explain_failures_for_builder(self, builder, start_revision):
225dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        print "Examining failures for \"%s\", starting at r%s" % (builder.name(), start_revision)
226dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        revision_to_test = start_revision
227dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        build = builder.build_for_revision(revision_to_test, allow_failed_lookups=True)
228dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        layout_test_results = build.layout_test_results()
229dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        if not layout_test_results:
230dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            # FIXME: This could be made more user friendly.
231dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            print "Failed to load layout test results; can't continue. (start revision = r%s)" % start_revision
232dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            return 1
233dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
234dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        results_to_explain = set(layout_test_results.failing_tests())
235dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        last_build_with_results = build
236dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        print "Starting at %s" % revision_to_test
237dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        while results_to_explain:
238dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            revision_to_test -= 1
239dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            new_build = builder.build_for_revision(revision_to_test, allow_failed_lookups=True)
240dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            if not new_build:
241dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block                print "No build for %s" % revision_to_test
242dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block                continue
243dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            build = new_build
244dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            latest_results = build.layout_test_results()
245dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            if not latest_results:
246dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block                print "No results build %s (r%s)" % (build._number, build.revision())
247dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block                continue
248dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            failures = set(latest_results.failing_tests())
249dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            if len(failures) >= 20:
250dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block                # FIXME: We may need to move this logic into the LayoutTestResults class.
251dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block                # The buildbot stops runs after 20 failures so we don't have full results to work with here.
252dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block                print "Too many failures in build %s (r%s), ignoring." % (build._number, build.revision())
253dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block                continue
254dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            fixed_results = results_to_explain - failures
255dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            if not fixed_results:
256dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block                print "No change in build %s (r%s), %s unexplained failures (%s in this build)" % (build._number, build.revision(), len(results_to_explain), len(failures))
257dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block                last_build_with_results = build
258dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block                continue
259a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            regression_window = RegressionWindow(build, last_build_with_results)
260a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            self._print_blame_information_for_transition(regression_window, fixed_results)
261dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            last_build_with_results = build
262dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            results_to_explain -= fixed_results
263dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        if results_to_explain:
264dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            print "Failed to explain failures: %s" % results_to_explain
265dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            return 1
266dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        print "Explained all results for %s" % builder.name()
267dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        return 0
268dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
269dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    def _builder_to_explain(self):
270bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen        builder_statuses = self._tool.buildbot.builder_statuses()
271dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        red_statuses = [status for status in builder_statuses if not status["is_green"]]
272dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        print "%s failing" % (pluralize("builder", len(red_statuses)))
273dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        builder_choices = [status["name"] for status in red_statuses]
274dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        # We could offer an "All" choice here.
275cad810f21b803229eb11403f9209855525a25d57Steve Block        chosen_name = self._tool.user.prompt_with_list("Which builder to diagnose:", builder_choices)
276dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        # FIXME: prompt_with_list should really take a set of objects and a set of names and then return the object.
277dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        for status in red_statuses:
278dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            if status["name"] == chosen_name:
279bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen                return (self._tool.buildbot.builder_with_name(chosen_name), status["built_revision"])
280dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
281dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    def execute(self, options, args, tool):
282dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        (builder, latest_revision) = self._builder_to_explain()
283bec39347bb3bb5bf1187ccaf471d26247f28b585Kristian Monsen        start_revision = self._tool.user.prompt("Revision to walk backwards from? [%s] " % latest_revision) or latest_revision
284dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        if not start_revision:
285dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            print "Revision required."
286dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            return 1
287dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        return self._explain_failures_for_builder(builder, start_revision=int(start_revision))
288dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
289967717af5423377c967781471ee106e2bb4e11c8Ben Murdoch
290a94275402997c11dd2e778633dacf4b7e630a35dBen Murdochclass FindFlakyTests(AbstractDeclarativeCommand):
291a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch    name = "find-flaky-tests"
292a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch    help_text = "Lists tests that often fail for a single build at %s" % BuildBot.default_host
293a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch
294a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch    def _find_failures(self, builder, revision):
295a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        build = builder.build_for_revision(revision, allow_failed_lookups=True)
296a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        if not build:
297a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            print "No build for %s" % revision
298a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            return (None, None)
299a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        results = build.layout_test_results()
300a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        if not results:
301a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            print "No results build %s (r%s)" % (build._number, build.revision())
302a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            return (None, None)
303a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        failures = set(results.failing_tests())
304a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        if len(failures) >= 20:
305a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            # FIXME: We may need to move this logic into the LayoutTestResults class.
306a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            # The buildbot stops runs after 20 failures so we don't have full results to work with here.
307a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            print "Too many failures in build %s (r%s), ignoring." % (build._number, build.revision())
308a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            return (None, None)
309a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        return (build, failures)
310a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch
311a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch    def _increment_statistics(self, flaky_tests, flaky_test_statistics):
312a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        for test in flaky_tests:
313a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            count = flaky_test_statistics.get(test, 0)
314a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            flaky_test_statistics[test] = count + 1
315a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch
316a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch    def _print_statistics(self, statistics):
317a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        print "=== Results ==="
318a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        print "Occurances Test name"
319a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        for value, key in sorted([(value, key) for key, value in statistics.items()]):
320a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            print "%10d %s" % (value, key)
321a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch
322a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch    def _walk_backwards_from(self, builder, start_revision, limit):
323a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        flaky_test_statistics = {}
324a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        all_previous_failures = set([])
325a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        one_time_previous_failures = set([])
326a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        previous_build = None
327a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        for i in range(limit):
328a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            revision = start_revision - i
329a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            print "Analyzing %s ... " % revision,
330a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            (build, failures) = self._find_failures(builder, revision)
331a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            if failures == None:
332a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch                # Notice that we don't loop on the empty set!
333a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch                continue
334a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            print "has %s failures" % len(failures)
335a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            flaky_tests = one_time_previous_failures - failures
336a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            if flaky_tests:
337a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch                print "Flaky tests: %s %s" % (sorted(flaky_tests),
338a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch                                              previous_build.results_url())
339a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            self._increment_statistics(flaky_tests, flaky_test_statistics)
340a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            one_time_previous_failures = failures - all_previous_failures
341a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            all_previous_failures = failures
342a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            previous_build = build
343a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        self._print_statistics(flaky_test_statistics)
344a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch
345a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch    def _builder_to_analyze(self):
346a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        statuses = self._tool.buildbot.builder_statuses()
347a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        choices = [status["name"] for status in statuses]
348cad810f21b803229eb11403f9209855525a25d57Steve Block        chosen_name = self._tool.user.prompt_with_list("Which builder to analyze:", choices)
349a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        for status in statuses:
350a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch            if status["name"] == chosen_name:
351a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch                return (self._tool.buildbot.builder_with_name(chosen_name), status["built_revision"])
352a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch
353a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch    def execute(self, options, args, tool):
354a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        (builder, latest_revision) = self._builder_to_analyze()
355a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        limit = self._tool.user.prompt("How many revisions to look through? [10000] ") or 10000
356a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch        return self._walk_backwards_from(builder, latest_revision, limit=int(limit))
357a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch
358a94275402997c11dd2e778633dacf4b7e630a35dBen Murdoch
359dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockclass TreeStatus(AbstractDeclarativeCommand):
360dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    name = "tree-status"
361dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    help_text = "Print the status of the %s buildbots" % BuildBot.default_host
362dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    long_help = """Fetches build status from http://build.webkit.org/one_box_per_builder
363dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Blockand displayes the status of each builder."""
364dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block
365dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block    def execute(self, options, args, tool):
366dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block        for builder in tool.buildbot.builder_statuses():
367dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            status_string = "ok" if builder["is_green"] else "FAIL"
368dcc8cf2e65d1aa555cce12431a16547e66b469eeSteve Block            print "%s : %s" % (status_string.ljust(4), builder["name"])
3695abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrick
3705abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrick
3715abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrickclass SkippedPorts(AbstractDeclarativeCommand):
3725abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrick    name = "skipped-ports"
3735abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrick    help_text = "Print the list of ports skipping the given layout test(s)"
3745abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrick    long_help = """Scans the the Skipped file of each port and figure
3755abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrickout what ports are skipping the test(s). Categories are taken in account too."""
3765abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrick    argument_names = "TEST_NAME"
3775abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrick
3785abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrick    def execute(self, options, args, tool):
3795abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrick        results = dict([(test_name, []) for test_name in args])
380f05b935882198ccf7d81675736e3aeb089c5113aBen Murdoch        for port_name, port_object in tool.port_factory.get_all().iteritems():
3815abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrick            for test_name in args:
3825abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrick                if port_object.skips_layout_test(test_name):
3835abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrick                    results[test_name].append(port_name)
3845abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrick
3855abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrick        for test_name, ports in results.iteritems():
3865abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrick            if ports:
3875abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrick                print "Ports skipping test %r: %s" % (test_name, ', '.join(ports))
3885abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrick            else:
3895abb8606fa57c3ebfc8b3c3dbc3fa4a25d2ae306Iain Merrick                print "Test %r is not skipped by any port." % test_name
390