webkit-patch: Passing --no-review should submit patch to EWS by default
[WebKit.git] / Tools / Scripts / webkitpy / common / net / bugzilla / bugzilla_mock.py
1 # Copyright (C) 2011 Google Inc. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions are
5 # met:
6 #
7 #    * Redistributions of source code must retain the above copyright
8 # notice, this list of conditions and the following disclaimer.
9 #    * Redistributions in binary form must reproduce the above
10 # copyright notice, this list of conditions and the following disclaimer
11 # in the documentation and/or other materials provided with the
12 # distribution.
13 #    * Neither the name of Google Inc. nor the names of its
14 # contributors may be used to endorse or promote products derived from
15 # this software without specific prior written permission.
16 #
17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29 import datetime
30 import logging
31
32 from .bug import Bug
33 from .attachment import Attachment
34 from webkitpy.common.config.committers import CommitterList, Reviewer
35
36 _log = logging.getLogger(__name__)
37
38
39 def _id_to_object_dictionary(*objects):
40     dictionary = {}
41     for thing in objects:
42         dictionary[thing["id"]] = thing
43     return dictionary
44
45 # Testing
46
47
48 _patch1 = {
49     "id": 10000,
50     "bug_id": 50000,
51     "url": "http://example.com/10000",
52     "name": "Patch1",
53     "is_obsolete": False,
54     "is_patch": True,
55     "review": "+",
56     "reviewer_email": "foo@bar.com",
57     "commit-queue": "+",
58     "committer_email": "foo@bar.com",
59     "attacher_email": "Contributer1",
60 }
61
62
63 _patch2 = {
64     "id": 10001,
65     "bug_id": 50000,
66     "url": "http://example.com/10001",
67     "name": "Patch2",
68     "is_obsolete": False,
69     "is_patch": True,
70     "review": "+",
71     "reviewer_email": "reviewer2@webkit.org",
72     "commit-queue": "+",
73     "committer_email": "non-committer@example.com",
74     "attacher_email": "eric@webkit.org",
75 }
76
77
78 _patch3 = {
79     "id": 10002,
80     "bug_id": 50001,
81     "url": "http://example.com/10002",
82     "name": "Patch3",
83     "is_obsolete": False,
84     "is_patch": True,
85     "review": "?",
86     "commit-queue": "-",
87     "attacher_email": "eric@webkit.org",
88     "attach_date": datetime.datetime.today(),
89 }
90
91
92 _patch4 = {
93     "id": 10003,
94     "bug_id": 50003,
95     "url": "http://example.com/10002",
96     "name": "Patch3",
97     "is_obsolete": False,
98     "is_patch": True,
99     "review": "+",
100     "commit-queue": "?",
101     "reviewer_email": "foo@bar.com",
102     "attacher_email": "Contributer2",
103 }
104
105
106 _patch5 = {
107     "id": 10004,
108     "bug_id": 50003,
109     "url": "http://example.com/10002",
110     "name": "Patch5",
111     "is_obsolete": False,
112     "is_patch": True,
113     "review": "+",
114     "reviewer_email": "foo@bar.com",
115     "attacher_email": "eric@webkit.org",
116 }
117
118
119 _patch6 = {  # Valid committer, but no reviewer.
120     "id": 10005,
121     "bug_id": 50003,
122     "url": "http://example.com/10002",
123     "name": "ROLLOUT of r3489",
124     "is_obsolete": False,
125     "is_patch": True,
126     "commit-queue": "+",
127     "committer_email": "foo@bar.com",
128     "attacher_email": "eric@webkit.org",
129 }
130
131
132 _patch7 = {  # Valid review, patch is marked obsolete.
133     "id": 10006,
134     "bug_id": 50002,
135     "url": "http://example.com/10002",
136     "name": "Patch7",
137     "is_obsolete": True,
138     "is_patch": True,
139     "review": "+",
140     "reviewer_email": "foo@bar.com",
141     "attacher_email": "eric@webkit.org",
142 }
143
144 _patch8 = {  # Resolved bug, without review flag, not marked obsolete (maybe already landed)
145     "id": 10007,
146     "bug_id": 50005,
147     "url": "http://example.com/10002",
148     "name": "Patch8",
149     "is_obsolete": False,
150     "is_patch": True,
151     "attacher_email": "eric@webkit.org",
152 }
153
154 # This matches one of Bug.unassigned_emails
155 _unassigned_email = "webkit-unassigned@lists.webkit.org"
156 # This is needed for the FlakyTestReporter to believe the bug
157 # was filed by one of the webkitpy bots.
158 _commit_queue_email = "commit-queue@webkit.org"
159
160
161 _bug1 = {
162     "id": 50000,
163     "title": "Bug with two r+'d and cq+'d patches, one of which has an "
164              "invalid commit-queue setter.",
165     "reporter_email": "foo@foo.com",
166     "assigned_to_email": _unassigned_email,
167     "cc_emails": [],
168     "attachments": [_patch1, _patch2],
169     "bug_status": "UNCONFIRMED",
170     "comments": [],
171 }
172
173
174 _bug2 = {
175     "id": 50001,
176     "title": "Bug with a patch needing review.",
177     "reporter_email": "eric@webkit.org",
178     "assigned_to_email": "foo@foo.com",
179     "cc_emails": ["abarth@webkit.org", ],
180     "attachments": [_patch3],
181     "bug_status": "ASSIGNED",
182     "comments": [{"comment_date":  datetime.datetime(2011, 6, 11, 9, 4, 3),
183                   "comment_email": "bar@foo.com",
184                   "text": "Message1.\nCommitted r35: <http://trac.webkit.org/changeset/35>",
185                   },
186                  ],
187 }
188
189
190 _bug3 = {
191     "id": 50002,
192     "title": "The third bug",
193     "reporter_email": "foo@foo.com",
194     "assigned_to_email": _unassigned_email,
195     "cc_emails": [],
196     "attachments": [_patch7],
197     "bug_status": "NEW",
198     "comments":  [{"comment_date":  datetime.datetime(2011, 6, 11, 9, 4, 3),
199                    "comment_email": "bar@foo.com",
200                    "text": "Committed r30: <http://trac.webkit.org/changeset/30>",
201                    },
202                   {"comment_date":  datetime.datetime(2011, 6, 11, 9, 4, 3),
203                    "comment_email": "bar@foo.com",
204                    "text": "Committed r31: <http://trac.webkit.org/changeset/31>",
205                    },
206                   ],
207 }
208
209
210 _bug4 = {
211     "id": 50003,
212     "title": "The fourth bug",
213     "reporter_email": "foo@foo.com",
214     "assigned_to_email": "foo@foo.com",
215     "cc_emails": [],
216     "attachments": [_patch4, _patch5, _patch6],
217     "bug_status": "REOPENED",
218     "comments": [{"comment_date":  datetime.datetime(2011, 6, 11, 9, 4, 3),
219                   "comment_email": "bar@foo.com",
220                   "text": "Committed r25: <http://trac.webkit.org/changeset/30>",
221                   },
222                  {"comment_date":  datetime.datetime(2011, 6, 11, 9, 4, 3),
223                   "comment_email": "bar@foo.com",
224                   "text": "Rolled out in <http://trac.webkit.org/changeset/26",
225                   },
226                  ],
227 }
228
229
230 _bug5 = {
231     "id": 50004,
232     "title": "The fifth bug",
233     "reporter_email": _commit_queue_email,
234     "assigned_to_email": "foo@foo.com",
235     "cc_emails": [],
236     "attachments": [],
237     "bug_status": "RESOLVED",
238     "dup_id": 50002,
239     "comments": [{"comment_date":  datetime.datetime(2011, 6, 11, 9, 4, 3),
240                   "comment_email": "bar@foo.com",
241                   "text": "Committed r15: <http://trac.webkit.org/changeset/15>",
242                   },
243                  ],
244
245 }
246
247
248 _bug6 = {
249     "id": 50005,
250     "title": "1st resolved bug",
251     "reporter_email": _commit_queue_email,
252     "assigned_to_email": "foo@foo.com",
253     "cc_emails": [],
254     "attachments": [_patch8],
255     "bug_status": "RESOLVED",
256     "comments": [{"comment_date":  datetime.datetime(2011, 6, 11, 9, 4, 3),
257                   "comment_email": "bar@foo.com",
258                   "text": "Committed r95: <http://trac.webkit.org/changeset/95>",
259                   },
260                  ],
261
262 }
263
264
265 _bug7 = {
266     "id": 50006,
267     "title": "2nd resolved bug",
268     "reporter_email": "eric@webkit.org",
269     "assigned_to_email": "foo@foo.com",
270     "cc_emails": ["abarth@webkit.org", ],
271     "attachments": [],
272     "bug_status": "RESOLVED",
273     "comments": [{"comment_date":  datetime.datetime(2011, 6, 11, 9, 4, 3),
274                   "comment_email": "bar@foo.com",
275                   "text": "Message1.\nCommitted r105: <http://trac.webkit.org/changeset/105>",
276                   },
277                  ],
278 }
279
280
281 class MockBugzillaQueries(object):
282
283     def __init__(self, bugzilla):
284         self._bugzilla = bugzilla
285
286     def _all_bugs(self):
287         return map(lambda bug_dictionary: Bug(bug_dictionary, self._bugzilla),
288                    self._bugzilla.bug_cache.values())
289
290     def fetch_bug_ids_from_commit_queue(self):
291         bugs_with_commit_queued_patches = filter(
292                 lambda bug: bug.commit_queued_patches(),
293                 self._all_bugs())
294         return map(lambda bug: bug.id(), bugs_with_commit_queued_patches)
295
296     def fetch_attachment_ids_from_review_queue(self, since=None):
297         unreviewed_patches = sum([bug.unreviewed_patches()
298                                   for bug in self._all_bugs()], [])
299         if since:
300             unreviewed_pacthes = [patch for patch in unreviewed_patches
301                                         if patch.attach_date() >= since]
302         return map(lambda patch: patch.id(), unreviewed_patches)
303
304     def fetch_patches_from_commit_queue(self):
305         return sum([bug.commit_queued_patches()
306                     for bug in self._all_bugs()], [])
307
308     def fetch_bug_ids_from_pending_commit_list(self):
309         bugs_with_reviewed_patches = filter(lambda bug: bug.reviewed_patches(),
310                                             self._all_bugs())
311         bug_ids = map(lambda bug: bug.id(), bugs_with_reviewed_patches)
312         # NOTE: This manual hack here is to allow testing logging in
313         # test_assign_to_committer the real pending-commit query on bugzilla
314         # will return bugs with patches which have r+, but are also obsolete.
315         return bug_ids + [50002]
316
317     def fetch_bugs_from_review_queue(self, cc_email=None):
318         unreviewed_bugs = [bug for bug in self._all_bugs() if bug.unreviewed_patches()]
319
320         if cc_email:
321             return [bug for bug in unreviewed_bugs if cc_email in bug.cc_emails()]
322
323         return unreviewed_bugs
324
325     def fetch_patches_from_pending_commit_list(self):
326         return sum([bug.reviewed_patches() for bug in self._all_bugs()], [])
327
328     def fetch_bugs_matching_search(self, search_string):
329         return [self._bugzilla.fetch_bug(50004), self._bugzilla.fetch_bug(50003)]
330
331     def fetch_bugs_matching_quicksearch(self, search_string):
332         return [self._bugzilla.fetch_bug(50001), self._bugzilla.fetch_bug(50002),
333                 self._bugzilla.fetch_bug(50003), self._bugzilla.fetch_bug(50004)]
334
335
336 _mock_reviewers = [Reviewer("Foo Bar", "foo@bar.com"),
337                    Reviewer("Reviewer2", "reviewer2@webkit.org")]
338
339
340 # FIXME: Bugzilla is the wrong Mock-point.  Once we have a BugzillaNetwork
341 #        class we should mock that instead.
342 # Most of this class is just copy/paste from Bugzilla.
343 class MockBugzilla(object):
344
345     bug_server_url = "http://example.com"
346
347     bug_cache = _id_to_object_dictionary(_bug1, _bug2, _bug3, _bug4, _bug5, _bug6, _bug7)
348
349     attachment_cache = _id_to_object_dictionary(_patch1,
350                                                 _patch2,
351                                                 _patch3,
352                                                 _patch4,
353                                                 _patch5,
354                                                 _patch6,
355                                                 _patch7,
356                                                 _patch8)
357
358     def __init__(self):
359         self.queries = MockBugzillaQueries(self)
360         # FIXME: This should move onto the Host object, and we should use a MockCommitterList
361         self.committers = CommitterList(reviewers=_mock_reviewers)
362         self.username = None
363         self._override_patch = None
364
365     def authenticate(self):
366         self.username = "username@webkit.org"
367
368     def create_bug(self,
369                    bug_title,
370                    bug_description,
371                    component=None,
372                    diff=None,
373                    patch_description=None,
374                    cc=None,
375                    blocked=None,
376                    mark_for_review=False,
377                    mark_for_commit_queue=False):
378         _log.info("MOCK create_bug")
379         _log.info("bug_title: %s" % bug_title)
380         _log.info("bug_description: %s" % bug_description)
381         if component:
382             _log.info("component: %s" % component)
383         if cc:
384             _log.info("cc: %s" % cc)
385         if blocked:
386             _log.info("blocked: %s" % blocked)
387         return 60001
388
389     def quips(self):
390         return ["Good artists copy. Great artists steal. - Pablo Picasso"]
391
392     def fetch_bug(self, bug_id):
393         return Bug(self.bug_cache.get(int(bug_id)), self)
394
395     def set_override_patch(self, patch):
396         self._override_patch = patch
397
398     def fetch_attachment(self, attachment_id):
399         if self._override_patch:
400             return self._override_patch
401
402         attachment_dictionary = self.attachment_cache.get(attachment_id)
403         if not attachment_dictionary:
404             print "MOCK: fetch_attachment: %s is not a known attachment id" % attachment_id
405             return None
406         bug = self.fetch_bug(attachment_dictionary["bug_id"])
407         for attachment in bug.attachments(include_obsolete=True):
408             if attachment.id() == int(attachment_id):
409                 return attachment
410
411     def bug_url_for_bug_id(self, bug_id):
412         return "%s/%s" % (self.bug_server_url, bug_id)
413
414     def fetch_bug_dictionary(self, bug_id):
415         return self.bug_cache.get(bug_id)
416
417     def attachment_url_for_id(self, attachment_id, action="view"):
418         action_param = ""
419         if action and action != "view":
420             action_param = "&action=%s" % action
421         return "%s/%s%s" % (self.bug_server_url, attachment_id, action_param)
422
423     def reassign_bug(self, bug_id, assignee=None, comment_text=None):
424         _log.info("MOCK reassign_bug: bug_id=%s, assignee=%s" % (bug_id, assignee))
425         if comment_text:
426             _log.info("-- Begin comment --")
427             _log.info(comment_text)
428             _log.info("-- End comment --")
429
430     def set_flag_on_attachment(self,
431                                attachment_id,
432                                flag_name,
433                                flag_value,
434                                comment_text=None):
435         _log.info("MOCK setting flag '%s' to '%s' on attachment '%s' with comment '%s'" % (
436                   flag_name, flag_value, attachment_id, comment_text))
437
438     def post_comment_to_bug(self, bug_id, comment_text, cc=None):
439         _log.info("MOCK bug comment: bug_id=%s, cc=%s\n--- Begin comment ---\n%s\n--- End comment ---\n" % (
440                   bug_id, cc, comment_text))
441
442     def add_attachment_to_bug(self, bug_id, file_or_string, description, filename=None, comment_text=None, mimetype=None):
443         _log.info("MOCK add_attachment_to_bug: bug_id=%s, description=%s filename=%s mimetype=%s" %
444                   (bug_id, description, filename, mimetype))
445         if comment_text:
446             _log.info("-- Begin comment --")
447             _log.info(comment_text)
448             _log.info("-- End comment --")
449
450     def add_patch_to_bug(self,
451                          bug_id,
452                          diff,
453                          description,
454                          comment_text=None,
455                          mark_for_review=False,
456                          mark_for_commit_queue=False,
457                          mark_for_landing=False):
458         _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" %
459                   (bug_id, description, mark_for_review, mark_for_commit_queue, mark_for_landing))
460         if comment_text:
461             _log.info("-- Begin comment --")
462             _log.info(comment_text)
463             _log.info("-- End comment --")
464         return '10001'
465
466     def add_cc_to_bug(self, bug_id, ccs):
467         pass
468
469     def obsolete_attachment(self, attachment_id, message=None):
470         pass
471
472     def reopen_bug(self, bug_id, message):
473         _log.info("MOCK reopen_bug %s with comment '%s'" % (bug_id, message))
474
475     def close_bug_as_fixed(self, bug_id, message):
476         pass
477
478     def clear_attachment_flags(self, attachment_id, message):
479         pass