summaryrefslogtreecommitdiffstats
path: root/Tools/Scripts/webkitpy/tool/commands/queries.py
diff options
context:
space:
mode:
Diffstat (limited to 'Tools/Scripts/webkitpy/tool/commands/queries.py')
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/queries.py611
1 files changed, 0 insertions, 611 deletions
diff --git a/Tools/Scripts/webkitpy/tool/commands/queries.py b/Tools/Scripts/webkitpy/tool/commands/queries.py
deleted file mode 100644
index ff1b46ef2..000000000
--- a/Tools/Scripts/webkitpy/tool/commands/queries.py
+++ /dev/null
@@ -1,611 +0,0 @@
-# Copyright (c) 2009 Google Inc. All rights reserved.
-# Copyright (c) 2009 Apple Inc. All rights reserved.
-# Copyright (c) 2012 Intel Corporation. All rights reserved.
-# Copyright (c) 2013 University of Szeged. All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-#
-# * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above
-# copyright notice, this list of conditions and the following disclaimer
-# in the documentation and/or other materials provided with the
-# distribution.
-# * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-import fnmatch
-import logging
-import re
-
-from datetime import datetime
-from optparse import make_option
-
-from webkitpy.tool import steps
-
-from webkitpy.common.checkout.commitinfo import CommitInfo
-from webkitpy.common.config.committers import CommitterList
-import webkitpy.common.config.urls as config_urls
-from webkitpy.common.net.buildbot import BuildBot
-from webkitpy.common.net.bugzilla import Bugzilla
-from webkitpy.common.net.regressionwindow import RegressionWindow
-from webkitpy.common.system.crashlogs import CrashLogs
-from webkitpy.common.system.user import User
-from webkitpy.tool.commands.abstractsequencedcommand import AbstractSequencedCommand
-from webkitpy.tool.grammar import pluralize
-from webkitpy.tool.multicommandtool import Command
-from webkitpy.layout_tests.models.test_expectations import TestExpectations
-from webkitpy.port import platform_options, configuration_options
-
-_log = logging.getLogger(__name__)
-
-
-class SuggestReviewers(AbstractSequencedCommand):
- name = "suggest-reviewers"
- help_text = "Suggest reviewers for a patch based on recent changes to the modified files."
- steps = [
- steps.SuggestReviewers,
- ]
-
- def _prepare_state(self, options, args, tool):
- options.suggest_reviewers = True
-
-
-class BugsToCommit(Command):
- name = "bugs-to-commit"
- help_text = "List bugs in the commit-queue"
-
- def execute(self, options, args, tool):
- # 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).
- bug_ids = tool.bugs.queries.fetch_bug_ids_from_commit_queue()
- for bug_id in bug_ids:
- print "%s" % bug_id
-
-
-class PatchesInCommitQueue(Command):
- name = "patches-in-commit-queue"
- help_text = "List patches in the commit-queue"
-
- def execute(self, options, args, tool):
- patches = tool.bugs.queries.fetch_patches_from_commit_queue()
- _log.info("Patches in commit queue:")
- for patch in patches:
- print patch.url()
-
-
-class PatchesToCommitQueue(Command):
- name = "patches-to-commit-queue"
- help_text = "Patches which should be added to the commit queue"
- def __init__(self):
- options = [
- make_option("--bugs", action="store_true", dest="bugs", help="Output bug links instead of patch links"),
- ]
- Command.__init__(self, options=options)
-
- @staticmethod
- def _needs_commit_queue(patch):
- if patch.commit_queue() == "+": # If it's already cq+, ignore the patch.
- _log.info("%s already has cq=%s" % (patch.id(), patch.commit_queue()))
- return False
-
- # We only need to worry about patches from contributers who are not yet committers.
- committer_record = CommitterList().committer_by_email(patch.attacher_email())
- if committer_record:
- _log.info("%s committer = %s" % (patch.id(), committer_record))
- return not committer_record
-
- def execute(self, options, args, tool):
- patches = tool.bugs.queries.fetch_patches_from_pending_commit_list()
- patches_needing_cq = filter(self._needs_commit_queue, patches)
- if options.bugs:
- bugs_needing_cq = map(lambda patch: patch.bug_id(), patches_needing_cq)
- bugs_needing_cq = sorted(set(bugs_needing_cq))
- for bug_id in bugs_needing_cq:
- print "%s" % tool.bugs.bug_url_for_bug_id(bug_id)
- else:
- for patch in patches_needing_cq:
- print "%s" % tool.bugs.attachment_url_for_id(patch.id(), action="edit")
-
-
-class PatchesToReview(Command):
- name = "patches-to-review"
- help_text = "List bugs which have attachments pending review"
-
- def __init__(self):
- options = [
- make_option("--all", action="store_true",
- help="Show all bugs regardless of who is on CC (it might take a while)"),
- make_option("--include-cq-denied", action="store_true",
- help="By default, r? patches with cq- are omitted unless this option is set"),
- make_option("--cc-email",
- help="Specifies the email on the CC field (defaults to your bugzilla login email)"),
- ]
- Command.__init__(self, options=options)
-
- def _print_report(self, report, cc_email, print_all):
- if print_all:
- print "Bugs with attachments pending review:"
- else:
- print "Bugs with attachments pending review that has %s in the CC list:" % cc_email
-
- print "http://webkit.org/b/bugid Description (age in days)"
- for row in report:
- print "%s (%d)" % (row[1], row[0])
-
- print "Total: %d" % len(report)
-
- def _generate_report(self, bugs, include_cq_denied):
- report = []
-
- for bug in bugs:
- patch = bug.unreviewed_patches()[-1]
-
- if not include_cq_denied and patch.commit_queue() == "-":
- continue
-
- age_in_days = (datetime.today() - patch.attach_date()).days
- report.append((age_in_days, "http://webkit.org/b/%-7s %s" % (bug.id(), bug.title())))
-
- report.sort()
- return report
-
- def execute(self, options, args, tool):
- tool.bugs.authenticate()
-
- cc_email = options.cc_email
- if not cc_email and not options.all:
- cc_email = tool.bugs.username
-
- bugs = tool.bugs.queries.fetch_bugs_from_review_queue(cc_email=cc_email)
- report = self._generate_report(bugs, options.include_cq_denied)
- self._print_report(report, cc_email, options.all)
-
-
-class WhatBroke(Command):
- name = "what-broke"
- help_text = "Print failing buildbots (%s) and what revisions broke them" % config_urls.buildbot_url
-
- def _print_builder_line(self, builder_name, max_name_width, status_message):
- print "%s : %s" % (builder_name.ljust(max_name_width), status_message)
-
- def _print_blame_information_for_builder(self, builder_status, name_width, avoid_flakey_tests=True):
- builder = self._tool.buildbot.builder_with_name(builder_status["name"])
- red_build = builder.build(builder_status["build_number"])
- regression_window = builder.find_regression_window(red_build)
- if not regression_window.failing_build():
- self._print_builder_line(builder.name(), name_width, "FAIL (error loading build information)")
- return
- if not regression_window.build_before_failure():
- self._print_builder_line(builder.name(), name_width, "FAIL (blame-list: sometime before %s?)" % regression_window.failing_build().revision())
- return
-
- revisions = regression_window.revisions()
- first_failure_message = ""
- if (regression_window.failing_build() == builder.build(builder_status["build_number"])):
- first_failure_message = " FIRST FAILURE, possibly a flaky test"
- self._print_builder_line(builder.name(), name_width, "FAIL (blame-list: %s%s)" % (revisions, first_failure_message))
- for revision in revisions:
- commit_info = self._tool.checkout().commit_info_for_revision(revision)
- if commit_info:
- print commit_info.blame_string(self._tool.bugs)
- else:
- print "FAILED to fetch CommitInfo for r%s, likely missing ChangeLog" % revision
-
- def execute(self, options, args, tool):
- builder_statuses = tool.buildbot.builder_statuses()
- longest_builder_name = max(map(len, map(lambda builder: builder["name"], builder_statuses)))
- failing_builders = 0
- for builder_status in builder_statuses:
- # If the builder is green, print OK, exit.
- if builder_status["is_green"]:
- continue
- self._print_blame_information_for_builder(builder_status, name_width=longest_builder_name)
- failing_builders += 1
- if failing_builders:
- print "%s of %s are failing" % (failing_builders, pluralize("builder", len(builder_statuses)))
- else:
- print "All builders are passing!"
-
-
-class ResultsFor(Command):
- name = "results-for"
- help_text = "Print a list of failures for the passed revision from bots on %s" % config_urls.buildbot_url
- argument_names = "REVISION"
-
- def _print_layout_test_results(self, results):
- if not results:
- print " No results."
- return
- for title, files in results.parsed_results().items():
- print " %s" % title
- for filename in files:
- print " %s" % filename
-
- def execute(self, options, args, tool):
- builders = self._tool.buildbot.builders()
- for builder in builders:
- print "%s:" % builder.name()
- build = builder.build_for_revision(args[0], allow_failed_lookups=True)
- self._print_layout_test_results(build.layout_test_results())
-
-
-class FailureReason(Command):
- name = "failure-reason"
- help_text = "Lists revisions where individual test failures started at %s" % config_urls.buildbot_url
-
- def _blame_line_for_revision(self, revision):
- try:
- commit_info = self._tool.checkout().commit_info_for_revision(revision)
- except Exception, e:
- return "FAILED to fetch CommitInfo for r%s, exception: %s" % (revision, e)
- if not commit_info:
- return "FAILED to fetch CommitInfo for r%s, likely missing ChangeLog" % revision
- return commit_info.blame_string(self._tool.bugs)
-
- def _print_blame_information_for_transition(self, regression_window, failing_tests):
- red_build = regression_window.failing_build()
- print "SUCCESS: Build %s (r%s) was the first to show failures: %s" % (red_build._number, red_build.revision(), failing_tests)
- print "Suspect revisions:"
- for revision in regression_window.revisions():
- print self._blame_line_for_revision(revision)
-
- def _explain_failures_for_builder(self, builder, start_revision):
- print "Examining failures for \"%s\", starting at r%s" % (builder.name(), start_revision)
- revision_to_test = start_revision
- build = builder.build_for_revision(revision_to_test, allow_failed_lookups=True)
- layout_test_results = build.layout_test_results()
- if not layout_test_results:
- # FIXME: This could be made more user friendly.
- print "Failed to load layout test results from %s; can't continue. (start revision = r%s)" % (build.results_url(), start_revision)
- return 1
-
- results_to_explain = set(layout_test_results.failing_tests())
- last_build_with_results = build
- print "Starting at %s" % revision_to_test
- while results_to_explain:
- revision_to_test -= 1
- new_build = builder.build_for_revision(revision_to_test, allow_failed_lookups=True)
- if not new_build:
- print "No build for %s" % revision_to_test
- continue
- build = new_build
- latest_results = build.layout_test_results()
- if not latest_results:
- print "No results build %s (r%s)" % (build._number, build.revision())
- continue
- failures = set(latest_results.failing_tests())
- if len(failures) >= 20:
- # FIXME: We may need to move this logic into the LayoutTestResults class.
- # The buildbot stops runs after 20 failures so we don't have full results to work with here.
- print "Too many failures in build %s (r%s), ignoring." % (build._number, build.revision())
- continue
- fixed_results = results_to_explain - failures
- if not fixed_results:
- 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))
- last_build_with_results = build
- continue
- regression_window = RegressionWindow(build, last_build_with_results)
- self._print_blame_information_for_transition(regression_window, fixed_results)
- last_build_with_results = build
- results_to_explain -= fixed_results
- if results_to_explain:
- print "Failed to explain failures: %s" % results_to_explain
- return 1
- print "Explained all results for %s" % builder.name()
- return 0
-
- def _builder_to_explain(self):
- builder_statuses = self._tool.buildbot.builder_statuses()
- red_statuses = [status for status in builder_statuses if not status["is_green"]]
- print "%s failing" % (pluralize("builder", len(red_statuses)))
- builder_choices = [status["name"] for status in red_statuses]
- # We could offer an "All" choice here.
- chosen_name = self._tool.user.prompt_with_list("Which builder to diagnose:", builder_choices)
- # FIXME: prompt_with_list should really take a set of objects and a set of names and then return the object.
- for status in red_statuses:
- if status["name"] == chosen_name:
- return (self._tool.buildbot.builder_with_name(chosen_name), status["built_revision"])
-
- def execute(self, options, args, tool):
- (builder, latest_revision) = self._builder_to_explain()
- start_revision = self._tool.user.prompt("Revision to walk backwards from? [%s] " % latest_revision) or latest_revision
- if not start_revision:
- print "Revision required."
- return 1
- return self._explain_failures_for_builder(builder, start_revision=int(start_revision))
-
-
-class FindFlakyTests(Command):
- name = "find-flaky-tests"
- help_text = "Lists tests that often fail for a single build at %s" % config_urls.buildbot_url
-
- def _find_failures(self, builder, revision):
- build = builder.build_for_revision(revision, allow_failed_lookups=True)
- if not build:
- print "No build for %s" % revision
- return (None, None)
- results = build.layout_test_results()
- if not results:
- print "No results build %s (r%s)" % (build._number, build.revision())
- return (None, None)
- failures = set(results.failing_tests())
- if len(failures) >= 20:
- # FIXME: We may need to move this logic into the LayoutTestResults class.
- # The buildbot stops runs after 20 failures so we don't have full results to work with here.
- print "Too many failures in build %s (r%s), ignoring." % (build._number, build.revision())
- return (None, None)
- return (build, failures)
-
- def _increment_statistics(self, flaky_tests, flaky_test_statistics):
- for test in flaky_tests:
- count = flaky_test_statistics.get(test, 0)
- flaky_test_statistics[test] = count + 1
-
- def _print_statistics(self, statistics):
- print "=== Results ==="
- print "Occurances Test name"
- for value, key in sorted([(value, key) for key, value in statistics.items()]):
- print "%10d %s" % (value, key)
-
- def _walk_backwards_from(self, builder, start_revision, limit):
- flaky_test_statistics = {}
- all_previous_failures = set([])
- one_time_previous_failures = set([])
- previous_build = None
- for i in range(limit):
- revision = start_revision - i
- print "Analyzing %s ... " % revision,
- (build, failures) = self._find_failures(builder, revision)
- if failures == None:
- # Notice that we don't loop on the empty set!
- continue
- print "has %s failures" % len(failures)
- flaky_tests = one_time_previous_failures - failures
- if flaky_tests:
- print "Flaky tests: %s %s" % (sorted(flaky_tests),
- previous_build.results_url())
- self._increment_statistics(flaky_tests, flaky_test_statistics)
- one_time_previous_failures = failures - all_previous_failures
- all_previous_failures = failures
- previous_build = build
- self._print_statistics(flaky_test_statistics)
-
- def _builder_to_analyze(self):
- statuses = self._tool.buildbot.builder_statuses()
- choices = [status["name"] for status in statuses]
- chosen_name = self._tool.user.prompt_with_list("Which builder to analyze:", choices)
- for status in statuses:
- if status["name"] == chosen_name:
- return (self._tool.buildbot.builder_with_name(chosen_name), status["built_revision"])
-
- def execute(self, options, args, tool):
- (builder, latest_revision) = self._builder_to_analyze()
- limit = self._tool.user.prompt("How many revisions to look through? [10000] ") or 10000
- return self._walk_backwards_from(builder, latest_revision, limit=int(limit))
-
-
-class TreeStatus(Command):
- name = "tree-status"
- help_text = "Print the status of the %s buildbots" % config_urls.buildbot_url
- long_help = """Fetches build status from http://build.webkit.org/one_box_per_builder
-and displayes the status of each builder."""
-
- def execute(self, options, args, tool):
- for builder in tool.buildbot.builder_statuses():
- status_string = "ok" if builder["is_green"] else "FAIL"
- print "%s : %s" % (status_string.ljust(4), builder["name"])
-
-
-class CrashLog(Command):
- name = "crash-log"
- help_text = "Print the newest crash log for the given process"
- long_help = """Finds the newest crash log matching the given process name
-and PID and prints it to stdout."""
- argument_names = "PROCESS_NAME [PID]"
-
- def execute(self, options, args, tool):
- crash_logs = CrashLogs(tool)
- pid = None
- if len(args) > 1:
- pid = int(args[1])
- print crash_logs.find_newest_log(args[0], pid)
-
-
-class PrintExpectations(Command):
- name = 'print-expectations'
- help_text = 'Print the expected result for the given test(s) on the given port(s)'
-
- def __init__(self):
- options = [
- make_option('--all', action='store_true', default=False,
- help='display the expectations for *all* tests'),
- make_option('-x', '--exclude-keyword', action='append', default=[],
- help='limit to tests not matching the given keyword (for example, "skip", "slow", or "crash". May specify multiple times'),
- make_option('-i', '--include-keyword', action='append', default=[],
- help='limit to tests with the given keyword (for example, "skip", "slow", or "crash". May specify multiple times'),
- make_option('--csv', action='store_true', default=False,
- help='Print a CSV-style report that includes the port name, modifiers, tests, and expectations'),
- make_option('-f', '--full', action='store_true', default=False,
- help='Print a full TestExpectations-style line for every match'),
- make_option('--paths', action='store_true', default=False,
- help='display the paths for all applicable expectation files'),
- ] + platform_options(use_globs=True)
-
- Command.__init__(self, options=options)
- self._expectation_models = {}
-
- def execute(self, options, args, tool):
- if not options.paths and not args and not options.all:
- print "You must either specify one or more test paths or --all."
- return
-
- if options.platform:
- port_names = fnmatch.filter(tool.port_factory.all_port_names(), options.platform)
- if not port_names:
- default_port = tool.port_factory.get(options.platform)
- if default_port:
- port_names = [default_port.name()]
- else:
- print "No port names match '%s'" % options.platform
- return
- else:
- default_port = tool.port_factory.get(port_names[0])
- else:
- default_port = tool.port_factory.get(options=options)
- port_names = [default_port.name()]
-
- if options.paths:
- files = default_port.expectations_files()
- layout_tests_dir = default_port.layout_tests_dir()
- for file in files:
- if file.startswith(layout_tests_dir):
- file = file.replace(layout_tests_dir, 'LayoutTests')
- print file
- return
-
- tests = set(default_port.tests(args))
- for port_name in port_names:
- model = self._model(options, port_name, tests)
- tests_to_print = self._filter_tests(options, model, tests)
- lines = [model.get_expectation_line(test) for test in sorted(tests_to_print)]
- if port_name != port_names[0]:
- print
- print '\n'.join(self._format_lines(options, port_name, lines))
-
- def _filter_tests(self, options, model, tests):
- filtered_tests = set()
- if options.include_keyword:
- for keyword in options.include_keyword:
- filtered_tests.update(model.get_test_set_for_keyword(keyword))
- else:
- filtered_tests = tests
-
- for keyword in options.exclude_keyword:
- filtered_tests.difference_update(model.get_test_set_for_keyword(keyword))
- return filtered_tests
-
- def _format_lines(self, options, port_name, lines):
- output = []
- if options.csv:
- for line in lines:
- output.append("%s,%s" % (port_name, line.to_csv()))
- elif lines:
- include_modifiers = options.full
- include_expectations = options.full or len(options.include_keyword) != 1 or len(options.exclude_keyword)
- output.append("// For %s" % port_name)
- for line in lines:
- output.append("%s" % line.to_string(None, include_modifiers, include_expectations, include_comment=False))
- return output
-
- def _model(self, options, port_name, tests):
- port = self._tool.port_factory.get(port_name, options)
- return TestExpectations(port, tests).model()
-
-
-class PrintBaselines(Command):
- name = 'print-baselines'
- help_text = 'Prints the baseline locations for given test(s) on the given port(s)'
-
- def __init__(self):
- options = [
- make_option('--all', action='store_true', default=False,
- help='display the baselines for *all* tests'),
- make_option('--csv', action='store_true', default=False,
- help='Print a CSV-style report that includes the port name, test_name, test platform, baseline type, baseline location, and baseline platform'),
- make_option('--include-virtual-tests', action='store_true',
- help='Include virtual tests'),
- ] + platform_options(use_globs=True)
- Command.__init__(self, options=options)
- self._platform_regexp = re.compile('platform/([^\/]+)/(.+)')
-
- def execute(self, options, args, tool):
- if not args and not options.all:
- print "You must either specify one or more test paths or --all."
- return
-
- default_port = tool.port_factory.get()
- if options.platform:
- port_names = fnmatch.filter(tool.port_factory.all_port_names(), options.platform)
- if not port_names:
- print "No port names match '%s'" % options.platform
- else:
- port_names = [default_port.name()]
-
- if options.include_virtual_tests:
- tests = sorted(default_port.tests(args))
- else:
- # FIXME: make real_tests() a public method.
- tests = sorted(default_port._real_tests(args))
-
- for port_name in port_names:
- if port_name != port_names[0]:
- print
- if not options.csv:
- print "// For %s" % port_name
- port = tool.port_factory.get(port_name)
- for test_name in tests:
- self._print_baselines(options, port_name, test_name, port.expected_baselines_by_extension(test_name))
-
- def _print_baselines(self, options, port_name, test_name, baselines):
- for extension in sorted(baselines.keys()):
- baseline_location = baselines[extension]
- if baseline_location:
- if options.csv:
- print "%s,%s,%s,%s,%s,%s" % (port_name, test_name, self._platform_for_path(test_name),
- extension[1:], baseline_location, self._platform_for_path(baseline_location))
- else:
- print baseline_location
-
- def _platform_for_path(self, relpath):
- platform_matchobj = self._platform_regexp.match(relpath)
- if platform_matchobj:
- return platform_matchobj.group(1)
- return None
-
-
-class FindResolvedBugs(Command):
- name = "find-resolved-bugs"
- help_text = "Collect the RESOLVED bugs in the given TestExpectations file"
- argument_names = "TEST_EXPECTATIONS_FILE"
-
- def execute(self, options, args, tool):
- filename = args[0]
- if not tool.filesystem.isfile(filename):
- print "The given path is not a file, please pass a valid path."
- return
-
- ids = set()
- inputfile = tool.filesystem.open_text_file_for_reading(filename)
- for line in inputfile:
- result = re.search("(https://bugs\.webkit\.org/show_bug\.cgi\?id=|webkit\.org/b/)([0-9]+)", line)
- if result:
- ids.add(result.group(2))
- inputfile.close()
-
- resolved_ids = set()
- num_of_bugs = len(ids)
- bugzilla = Bugzilla()
- for i, bugid in enumerate(ids, start=1):
- bug = bugzilla.fetch_bug(bugid)
- print "Checking bug %s \t [%d/%d]" % (bugid, i, num_of_bugs)
- if not bug.is_open():
- resolved_ids.add(bugid)
-
- print "Resolved bugs in %s :" % (filename)
- for bugid in resolved_ids:
- print "https://bugs.webkit.org/show_bug.cgi?id=%s" % (bugid)