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