Commit f1eeac35 authored by eric@webkit.org's avatar eric@webkit.org
Browse files

2010-12-09 Eric Seidel <eric@webkit.org>

        Reviewed by Adam Barth.

        Teach webkit-patch how to search bugzilla
        https://bugs.webkit.org/show_bug.cgi?id=50500

        This is a step towards teaching webkitpy how to file
        new bugs for flaky tests and update them when new flakes occur.

        * Scripts/webkitpy/common/net/bugzilla/bugzilla.py:
        * Scripts/webkitpy/common/net/bugzilla/bugzilla_unittest.py:
        * Scripts/webkitpy/tool/commands/__init__.py:
        * Scripts/webkitpy/tool/commands/bugsearch.py: Added.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@73688 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent e9f5304a
2010-12-09 Eric Seidel <eric@webkit.org>
Reviewed by Adam Barth.
Teach webkit-patch how to search bugzilla
https://bugs.webkit.org/show_bug.cgi?id=50500
This is a step towards teaching webkitpy how to file
new bugs for flaky tests and update them when new flakes occur.
* Scripts/webkitpy/common/net/bugzilla/bugzilla.py:
* Scripts/webkitpy/common/net/bugzilla/bugzilla_unittest.py:
* Scripts/webkitpy/tool/commands/__init__.py:
* Scripts/webkitpy/tool/commands/bugsearch.py: Added.
2010-12-09 Adam Barth <abarth@webkit.org>
 
Reviewed by Ojan Vafai.
......
......@@ -33,6 +33,7 @@
import os.path
import re
import StringIO
import urllib
from datetime import datetime # used in timestamp()
......@@ -72,15 +73,39 @@ class BugzillaQueries(object):
def __init__(self, bugzilla):
self._bugzilla = bugzilla
# Note: _load_query and _fetch_bug are the only two methods which access
# self._bugzilla.
def _is_xml_bugs_form(self, form):
# ClientForm.HTMLForm.find_control throws if the control is not found,
# so we do a manual search instead:
return "xml" in [control.id for control in form.controls]
# This is kinda a hack. There is probably a better way to get this information from bugzilla.
def _parse_result_count(self, results_page):
result_count_text = BeautifulSoup(results_page).find(attrs={'class': 'bz_result_count'}).string
result_count_parts = result_count_text.split(" ")
if result_count_parts[0] == "Zarro":
return 0
return int(result_count_parts[0])
# Note: _load_query, _fetch_bug and _fetch_bugs_from_advanced_query
# are the only methods which access self._bugzilla.
def _load_query(self, query):
self._bugzilla.authenticate()
full_url = "%s%s" % (self._bugzilla.bug_server_url, query)
return self._bugzilla.browser.open(full_url)
def _fetch_bugs_from_advanced_query(self, query):
results_page = self._load_query(query)
if not self._parse_result_count(results_page):
return []
# Bugzilla results pages have an "XML" submit button at the bottom
# which can be used to get an XML page containing all of the <bug> elements.
# This is slighty lame that this assumes that _load_query used
# self._bugzilla.browser and that it's in an acceptable state.
self._bugzilla.browser.select_form(predicate=self._is_xml_bugs_form)
bugs_xml = self._bugzilla.browser.submit()
return self._bugzilla._parse_bugs_from_xml(bugs_xml)
def _fetch_bug(self, bug_id):
return self._bugzilla.fetch_bug(bug_id)
......@@ -114,6 +139,13 @@ class BugzillaQueries(object):
needs_commit_query_url = "buglist.cgi?query_format=advanced&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&field0-0-0=flagtypes.name&type0-0-0=equals&value0-0-0=review%2B"
return self._fetch_bug_ids_advanced_query(needs_commit_query_url)
def fetch_bugs_matching_quicksearch(self, search_string):
# We may want to use a more explicit query than "quicksearch".
# If quicksearch changes we should probably change to use
# a normal buglist.cgi?query_format=advanced query.
quicksearch_url = "buglist.cgi?quicksearch=%s" % urllib.quote(search_string)
return self._fetch_bugs_from_advanced_query(quicksearch_url)
def fetch_patches_from_pending_commit_list(self):
return sum([self._fetch_bug(bug_id).reviewed_patches()
for bug_id in self.fetch_bug_ids_from_pending_commit_list()], [])
......@@ -250,7 +282,13 @@ class Bugzilla(object):
element, 'commit-queue', attachment, 'committer_email')
return attachment
def _parse_bug_page(self, page):
def _parse_bugs_from_xml(self, page):
soup = BeautifulSoup(page)
# Without the unicode() call, BeautifulSoup occasionally complains of being
# passed None for no apparent reason.
return [Bug(self._parse_bug_dictionary_from_xml(unicode(bug_xml)), self) for bug_xml in soup('bug')]
def _parse_bug_dictionary_from_xml(self, page):
soup = BeautifulSoup(page)
bug = {}
bug["id"] = int(soup.find("bug_id").string)
......@@ -273,12 +311,12 @@ class Bugzilla(object):
def fetch_bug_dictionary(self, bug_id):
try:
return self._parse_bug_page(self._fetch_bug_page(bug_id))
return self._parse_bug_dictionary_from_xml(self._fetch_bug_page(bug_id))
except KeyboardInterrupt:
raise
except:
self.authenticate()
return self._parse_bug_page(self._fetch_bug_page(bug_id))
return self._parse_bug_dictionary_from_xml(self._fetch_bug_page(bug_id))
# FIXME: A BugzillaCache object should provide all these fetch_ methods.
......
......@@ -99,14 +99,7 @@ class BugzillaTest(unittest.TestCase):
self.assertEquals(None, parse_bug_id("http://www.webkit.org/b/12345"))
self.assertEquals(None, parse_bug_id("http://bugs.webkit.org/show_bug.cgi?ctype=xml&id=12345"))
_example_bug = """
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE bugzilla SYSTEM "https://bugs.webkit.org/bugzilla.dtd">
<bugzilla version="3.2.3"
urlbase="https://bugs.webkit.org/"
maintainer="admin@webkit.org"
exporter="eric@webkit.org"
>
_bug_xml = """
<bug>
<bug_id>32585</bug_id>
<creation_ts>2009-12-15 15:17 PST</creation_ts>
......@@ -149,13 +142,13 @@ Ignore this bug. Just for testing failure modes of webkit-patch and the commit-
<type>text/plain</type>
<size>10882</size>
<attacher>mjs@apple.com</attacher>
<token>1261988248-dc51409e9c421a4358f365fa8bec8357</token>
<data encoding="base64">SW5kZXg6IFdlYktpdC9tYWMvQ2hhbmdlTG9nCj09PT09PT09PT09PT09PT09PT09PT09PT09PT09
removed-because-it-was-really-long
ZEZpbmlzaExvYWRXaXRoUmVhc29uOnJlYXNvbl07Cit9CisKIEBlbmQKIAogI2VuZGlmCg==
</data>
<flag name="review"
id="27602"
status="?"
......@@ -163,8 +156,20 @@ ZEZpbmlzaExvYWRXaXRoUmVhc29uOnJlYXNvbl07Cit9CisKIEBlbmQKIAogI2VuZGlmCg==
/>
</attachment>
</bug>
</bugzilla>
"""
_single_bug_xml = """
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<!DOCTYPE bugzilla SYSTEM "https://bugs.webkit.org/bugzilla.dtd">
<bugzilla version="3.2.3"
urlbase="https://bugs.webkit.org/"
maintainer="admin@webkit.org"
exporter="eric@webkit.org"
>
%s
</bugzilla>
""" % _bug_xml
_expected_example_bug_parsing = {
"id" : 32585,
"title" : u"bug to test webkit-patch and commit-queue failures",
......@@ -194,10 +199,25 @@ ZEZpbmlzaExvYWRXaXRoUmVhc29uOnJlYXNvbl07Cit9CisKIEBlbmQKIAogI2VuZGlmCg==
for key, expected_value in expected.items():
self.assertEquals(actual[key], expected_value, ("Failure for key: %s: Actual='%s' Expected='%s'" % (key, actual[key], expected_value)))
def test_bug_parsing(self):
bug = Bugzilla()._parse_bug_page(self._example_bug)
def test_parse_bug_dictionary_from_xml(self):
bug = Bugzilla()._parse_bug_dictionary_from_xml(self._single_bug_xml)
self._assert_dictionaries_equal(bug, self._expected_example_bug_parsing)
_sample_multi_bug_xml = """
<bugzilla version="3.2.3" urlbase="https://bugs.webkit.org/" maintainer="admin@webkit.org" exporter="eric@webkit.org">
%s
%s
</bugzilla>
""" % (_bug_xml, _bug_xml)
def test_parse_bugs_from_xml(self):
bugzilla = Bugzilla()
bugs = bugzilla._parse_bugs_from_xml(self._sample_multi_bug_xml)
self.assertEquals(len(bugs), 2)
self.assertEquals(bugs[0].id(), self._expected_example_bug_parsing['id'])
bugs = bugzilla._parse_bugs_from_xml("")
self.assertEquals(len(bugs), 0)
# This could be combined into test_bug_parsing later if desired.
def test_attachment_parsing(self):
bugzilla = Bugzilla()
......@@ -328,6 +348,16 @@ class BugzillaQueriesTest(unittest.TestCase):
</html>
"""
def _assert_result_count(self, queries, html, count):
self.assertEquals(queries._parse_result_count(html), count)
def test_parse_result_count(self):
queries = BugzillaQueries(None)
# Pages with results, always list the count at least twice.
self._assert_result_count(queries, '<span class="bz_result_count">314 bugs found.</span><span class="bz_result_count">314 bugs found.</span>', 314)
self._assert_result_count(queries, '<span class="bz_result_count">Zarro Boogs found.</span>', 0)
self.assertRaises(Exception, queries._parse_result_count, ['Invalid'])
def test_request_page_parsing(self):
queries = BugzillaQueries(None)
self.assertEquals([40511, 40722, 40723], queries._parse_attachment_ids_request_query(self._sample_request_page))
......
# Required for Python to search this directory for module files
from webkitpy.tool.commands.bugsearch import BugSearch
from webkitpy.tool.commands.download import *
from webkitpy.tool.commands.earlywarningsystem import *
from webkitpy.tool.commands.openbugs import OpenBugs
......
# Copyright (c) 2010 Google Inc. 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.
from webkitpy.tool.multicommandtool import AbstractDeclarativeCommand
class BugSearch(AbstractDeclarativeCommand):
name = "bug-search"
help_text = "List bugs matching a query"
def execute(self, options, args, tool):
search_string = args[0]
bugs = tool.bugs.queries.fetch_bugs_matching_quicksearch(search_string)
for bug in bugs:
print "%5s %s" % (bug.id(), bug.title())
if not bugs:
print "No bugs found matching '%s'" % search_string
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment