| # Copyright (C) 2011 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. |
| |
| import datetime |
| import logging |
| |
| from .bug import Bug |
| from .attachment import Attachment |
| from webkitpy.common.config.committers import CommitterList, Reviewer |
| |
| _log = logging.getLogger(__name__) |
| |
| |
| def _id_to_object_dictionary(*objects): |
| dictionary = {} |
| for thing in objects: |
| dictionary[thing["id"]] = thing |
| return dictionary |
| |
| # Testing |
| |
| |
| _patch1 = { |
| "id": 10000, |
| "bug_id": 50000, |
| "url": "http://example.com/10000", |
| "name": "Patch1", |
| "is_obsolete": False, |
| "is_patch": True, |
| "review": "+", |
| "reviewer_email": "foo@bar.com", |
| "commit-queue": "+", |
| "committer_email": "foo@bar.com", |
| "attacher_email": "Contributer1", |
| } |
| |
| |
| _patch2 = { |
| "id": 10001, |
| "bug_id": 50000, |
| "url": "http://example.com/10001", |
| "name": "Patch2", |
| "is_obsolete": False, |
| "is_patch": True, |
| "review": "+", |
| "reviewer_email": "reviewer2@webkit.org", |
| "commit-queue": "+", |
| "committer_email": "non-committer@example.com", |
| "attacher_email": "eric@webkit.org", |
| } |
| |
| |
| _patch3 = { |
| "id": 10002, |
| "bug_id": 50001, |
| "url": "http://example.com/10002", |
| "name": "Patch3", |
| "is_obsolete": False, |
| "is_patch": True, |
| "review": "?", |
| "commit-queue": "-", |
| "attacher_email": "eric@webkit.org", |
| "attach_date": datetime.datetime.today(), |
| } |
| |
| |
| _patch4 = { |
| "id": 10003, |
| "bug_id": 50003, |
| "url": "http://example.com/10002", |
| "name": "Patch3", |
| "is_obsolete": False, |
| "is_patch": True, |
| "review": "+", |
| "commit-queue": "?", |
| "reviewer_email": "foo@bar.com", |
| "attacher_email": "Contributer2", |
| } |
| |
| |
| _patch5 = { |
| "id": 10004, |
| "bug_id": 50003, |
| "url": "http://example.com/10002", |
| "name": "Patch5", |
| "is_obsolete": False, |
| "is_patch": True, |
| "review": "+", |
| "reviewer_email": "foo@bar.com", |
| "attacher_email": "eric@webkit.org", |
| } |
| |
| |
| _patch6 = { # Valid committer, but no reviewer. |
| "id": 10005, |
| "bug_id": 50003, |
| "url": "http://example.com/10002", |
| "name": "ROLLOUT of r3489", |
| "is_obsolete": False, |
| "is_patch": True, |
| "commit-queue": "+", |
| "committer_email": "foo@bar.com", |
| "attacher_email": "eric@webkit.org", |
| } |
| |
| |
| _patch7 = { # Valid review, patch is marked obsolete. |
| "id": 10006, |
| "bug_id": 50002, |
| "url": "http://example.com/10002", |
| "name": "Patch7", |
| "is_obsolete": True, |
| "is_patch": True, |
| "review": "+", |
| "reviewer_email": "foo@bar.com", |
| "attacher_email": "eric@webkit.org", |
| } |
| |
| _patch8 = { # Resolved bug, without review flag, not marked obsolete (maybe already landed) |
| "id": 10007, |
| "bug_id": 50005, |
| "url": "http://example.com/10002", |
| "name": "Patch8", |
| "is_obsolete": False, |
| "is_patch": True, |
| "attacher_email": "eric@webkit.org", |
| } |
| |
| # This matches one of Bug.unassigned_emails |
| _unassigned_email = "webkit-unassigned@lists.webkit.org" |
| # This is needed for the FlakyTestReporter to believe the bug |
| # was filed by one of the webkitpy bots. |
| _commit_queue_email = "commit-queue@webkit.org" |
| |
| |
| _bug1 = { |
| "id": 50000, |
| "title": "Bug with two r+'d and cq+'d patches, one of which has an " |
| "invalid commit-queue setter.", |
| "reporter_email": "foo@foo.com", |
| "assigned_to_email": _unassigned_email, |
| "cc_emails": [], |
| "attachments": [_patch1, _patch2], |
| "bug_status": "UNCONFIRMED", |
| "comments": [], |
| } |
| |
| |
| _bug2 = { |
| "id": 50001, |
| "title": "Bug with a patch needing review.", |
| "reporter_email": "eric@webkit.org", |
| "assigned_to_email": "foo@foo.com", |
| "cc_emails": ["abarth@webkit.org", ], |
| "attachments": [_patch3], |
| "bug_status": "ASSIGNED", |
| "comments": [{"comment_date": datetime.datetime(2011, 6, 11, 9, 4, 3), |
| "comment_email": "bar@foo.com", |
| "text": "Message1.\nCommitted r35: <https://trac.webkit.org/changeset/35>", |
| }, |
| ], |
| } |
| |
| |
| _bug3 = { |
| "id": 50002, |
| "title": "The third bug", |
| "reporter_email": "foo@foo.com", |
| "assigned_to_email": _unassigned_email, |
| "cc_emails": [], |
| "attachments": [_patch7], |
| "bug_status": "NEW", |
| "comments": [{"comment_date": datetime.datetime(2011, 6, 11, 9, 4, 3), |
| "comment_email": "bar@foo.com", |
| "text": "Committed r30: <https://trac.webkit.org/changeset/30>", |
| }, |
| {"comment_date": datetime.datetime(2011, 6, 11, 9, 4, 3), |
| "comment_email": "bar@foo.com", |
| "text": "Committed r31: <https://trac.webkit.org/changeset/31>", |
| }, |
| ], |
| } |
| |
| |
| _bug4 = { |
| "id": 50003, |
| "title": "The fourth bug", |
| "reporter_email": "foo@foo.com", |
| "assigned_to_email": "foo@foo.com", |
| "cc_emails": [], |
| "attachments": [_patch4, _patch5, _patch6], |
| "bug_status": "REOPENED", |
| "comments": [{"comment_date": datetime.datetime(2011, 6, 11, 9, 4, 3), |
| "comment_email": "bar@foo.com", |
| "text": "Committed r25: <https://trac.webkit.org/changeset/30>", |
| }, |
| {"comment_date": datetime.datetime(2011, 6, 11, 9, 4, 3), |
| "comment_email": "bar@foo.com", |
| "text": "Rolled out in <https://trac.webkit.org/changeset/26", |
| }, |
| ], |
| } |
| |
| |
| _bug5 = { |
| "id": 50004, |
| "title": "The fifth bug", |
| "reporter_email": _commit_queue_email, |
| "assigned_to_email": "foo@foo.com", |
| "cc_emails": [], |
| "attachments": [], |
| "bug_status": "RESOLVED", |
| "dup_id": 50002, |
| "comments": [{"comment_date": datetime.datetime(2011, 6, 11, 9, 4, 3), |
| "comment_email": "bar@foo.com", |
| "text": "Committed r15: <https://trac.webkit.org/changeset/15>", |
| }, |
| ], |
| |
| } |
| |
| |
| _bug6 = { |
| "id": 50005, |
| "title": "1st resolved bug", |
| "reporter_email": _commit_queue_email, |
| "assigned_to_email": "foo@foo.com", |
| "cc_emails": [], |
| "attachments": [_patch8], |
| "bug_status": "RESOLVED", |
| "comments": [{"comment_date": datetime.datetime(2011, 6, 11, 9, 4, 3), |
| "comment_email": "bar@foo.com", |
| "text": "Committed r95: <https://trac.webkit.org/changeset/95>", |
| }, |
| ], |
| |
| } |
| |
| |
| _bug7 = { |
| "id": 50006, |
| "title": "2nd resolved bug", |
| "reporter_email": "eric@webkit.org", |
| "assigned_to_email": "foo@foo.com", |
| "cc_emails": ["abarth@webkit.org", ], |
| "attachments": [], |
| "bug_status": "RESOLVED", |
| "comments": [{"comment_date": datetime.datetime(2011, 6, 11, 9, 4, 3), |
| "comment_email": "bar@foo.com", |
| "text": "Message1.\nCommitted r105: <https://trac.webkit.org/changeset/105>", |
| }, |
| ], |
| } |
| |
| |
| class MockBugzillaQueries(object): |
| |
| def __init__(self, bugzilla): |
| self._bugzilla = bugzilla |
| |
| def _all_bugs(self): |
| return map(lambda bug_dictionary: Bug(bug_dictionary, self._bugzilla), |
| self._bugzilla.bug_cache.values()) |
| |
| def fetch_bug_ids_from_commit_queue(self): |
| bugs_with_commit_queued_patches = filter( |
| lambda bug: bug.commit_queued_patches(), |
| self._all_bugs()) |
| return map(lambda bug: bug.id(), bugs_with_commit_queued_patches) |
| |
| def fetch_attachment_ids_from_review_queue(self, since=None): |
| unreviewed_patches = sum([bug.unreviewed_patches() |
| for bug in self._all_bugs()], []) |
| if since: |
| unreviewed_pacthes = [patch for patch in unreviewed_patches |
| if patch.attach_date() >= since] |
| return map(lambda patch: patch.id(), unreviewed_patches) |
| |
| def fetch_patches_from_commit_queue(self): |
| return sum([bug.commit_queued_patches() |
| for bug in self._all_bugs()], []) |
| |
| def fetch_bug_ids_from_pending_commit_list(self): |
| bugs_with_reviewed_patches = filter(lambda bug: bug.reviewed_patches(), |
| self._all_bugs()) |
| bug_ids = map(lambda bug: bug.id(), bugs_with_reviewed_patches) |
| # NOTE: This manual hack here is to allow testing logging in |
| # test_assign_to_committer the real pending-commit query on bugzilla |
| # will return bugs with patches which have r+, but are also obsolete. |
| return bug_ids + [50002] |
| |
| def fetch_bugs_from_review_queue(self, cc_email=None): |
| unreviewed_bugs = [bug for bug in self._all_bugs() if bug.unreviewed_patches()] |
| |
| if cc_email: |
| return [bug for bug in unreviewed_bugs if cc_email in bug.cc_emails()] |
| |
| return unreviewed_bugs |
| |
| def fetch_patches_from_pending_commit_list(self): |
| return sum([bug.reviewed_patches() for bug in self._all_bugs()], []) |
| |
| def fetch_bugs_matching_search(self, search_string): |
| return [self._bugzilla.fetch_bug(50004), self._bugzilla.fetch_bug(50003)] |
| |
| def fetch_bugs_matching_quicksearch(self, search_string): |
| return [self._bugzilla.fetch_bug(50001), self._bugzilla.fetch_bug(50002), |
| self._bugzilla.fetch_bug(50003), self._bugzilla.fetch_bug(50004)] |
| |
| |
| _mock_reviewers = [Reviewer("Foo Bar", "foo@bar.com"), |
| Reviewer("Reviewer2", "reviewer2@webkit.org")] |
| |
| |
| # FIXME: Bugzilla is the wrong Mock-point. Once we have a BugzillaNetwork |
| # class we should mock that instead. |
| # Most of this class is just copy/paste from Bugzilla. |
| class MockBugzilla(object): |
| |
| bug_server_url = "http://example.com" |
| |
| bug_cache = _id_to_object_dictionary(_bug1, _bug2, _bug3, _bug4, _bug5, _bug6, _bug7) |
| |
| attachment_cache = _id_to_object_dictionary(_patch1, |
| _patch2, |
| _patch3, |
| _patch4, |
| _patch5, |
| _patch6, |
| _patch7, |
| _patch8) |
| |
| def __init__(self): |
| self.queries = MockBugzillaQueries(self) |
| # FIXME: This should move onto the Host object, and we should use a MockCommitterList |
| self.committers = CommitterList(reviewers=_mock_reviewers) |
| self.username = None |
| self._override_patch = None |
| |
| def authenticate(self): |
| self.username = "username@webkit.org" |
| |
| def create_bug(self, |
| bug_title, |
| bug_description, |
| component=None, |
| diff=None, |
| patch_description=None, |
| cc=None, |
| blocked=None, |
| mark_for_review=False, |
| mark_for_commit_queue=False): |
| _log.info("MOCK create_bug") |
| _log.info("bug_title: %s" % bug_title) |
| _log.info("bug_description: %s" % bug_description) |
| if component: |
| _log.info("component: %s" % component) |
| if cc: |
| _log.info("cc: %s" % cc) |
| if blocked: |
| _log.info("blocked: %s" % blocked) |
| return 60001 |
| |
| def quips(self): |
| return ["Good artists copy. Great artists steal. - Pablo Picasso"] |
| |
| def fetch_bug(self, bug_id): |
| return Bug(self.bug_cache.get(int(bug_id)), self) |
| |
| def set_override_patch(self, patch): |
| self._override_patch = patch |
| |
| def fetch_attachment(self, attachment_id): |
| if self._override_patch: |
| return self._override_patch |
| |
| attachment_dictionary = self.attachment_cache.get(attachment_id) |
| if not attachment_dictionary: |
| print("MOCK: fetch_attachment: %s is not a known attachment id" % attachment_id) |
| return None |
| bug = self.fetch_bug(attachment_dictionary["bug_id"]) |
| for attachment in bug.attachments(include_obsolete=True): |
| if attachment.id() == int(attachment_id): |
| return attachment |
| |
| def bug_url_for_bug_id(self, bug_id): |
| return "%s/%s" % (self.bug_server_url, bug_id) |
| |
| def fetch_bug_dictionary(self, bug_id): |
| return self.bug_cache.get(bug_id) |
| |
| def attachment_url_for_id(self, attachment_id, action="view"): |
| action_param = "" |
| if action and action != "view": |
| action_param = "&action=%s" % action |
| return "%s/%s%s" % (self.bug_server_url, attachment_id, action_param) |
| |
| def reassign_bug(self, bug_id, assignee=None, comment_text=None): |
| _log.info("MOCK reassign_bug: bug_id=%s, assignee=%s" % (bug_id, assignee)) |
| if comment_text: |
| _log.info("-- Begin comment --") |
| _log.info(comment_text) |
| _log.info("-- End comment --") |
| |
| def set_flag_on_attachment(self, |
| attachment_id, |
| flag_name, |
| flag_value, |
| comment_text=None): |
| _log.info("MOCK setting flag '%s' to '%s' on attachment '%s' with comment '%s'" % ( |
| flag_name, flag_value, attachment_id, comment_text)) |
| |
| def post_comment_to_bug(self, bug_id, comment_text, cc=None): |
| _log.info("MOCK bug comment: bug_id=%s, cc=%s\n--- Begin comment ---\n%s\n--- End comment ---\n" % ( |
| bug_id, cc, comment_text)) |
| |
| def add_attachment_to_bug(self, bug_id, file_or_string, description, filename=None, comment_text=None, mimetype=None): |
| _log.info("MOCK add_attachment_to_bug: bug_id=%s, description=%s filename=%s mimetype=%s" % |
| (bug_id, description, filename, mimetype)) |
| if comment_text: |
| _log.info("-- Begin comment --") |
| _log.info(comment_text) |
| _log.info("-- End comment --") |
| |
| def add_patch_to_bug(self, |
| bug_id, |
| diff, |
| description, |
| comment_text=None, |
| mark_for_review=False, |
| mark_for_commit_queue=False, |
| mark_for_landing=False): |
| _log.info("MOCK add_patch_to_bug: bug_id=%s, description=%s, mark_for_review=%s, mark_for_commit_queue=%s, mark_for_landing=%s" % |
| (bug_id, description, mark_for_review, mark_for_commit_queue, mark_for_landing)) |
| if comment_text: |
| _log.info("-- Begin comment --") |
| _log.info(comment_text) |
| _log.info("-- End comment --") |
| return '10001' |
| |
| def add_cc_to_bug(self, bug_id, ccs): |
| pass |
| |
| def obsolete_attachment(self, attachment_id, message=None): |
| pass |
| |
| def reopen_bug(self, bug_id, message): |
| _log.info("MOCK reopen_bug %s with comment '%s'" % (bug_id, message)) |
| |
| def close_bug_as_fixed(self, bug_id, message): |
| pass |
| |
| def clear_attachment_flags(self, attachment_id, message): |
| pass |