Clean up ChunkedUpdateDrawingAreaProxy
[WebKit-https.git] / WebKitTools / Scripts / webkitpy / tool / mocktool.py
1 # Copyright (C) 2009 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 os
30 import threading
31
32 from webkitpy.common.config.committers import CommitterList, Reviewer
33 from webkitpy.common.checkout.commitinfo import CommitInfo
34 from webkitpy.common.checkout.scm import CommitMessage
35 from webkitpy.common.net.bugzilla import Bug, Attachment
36 from webkitpy.common.system.deprecated_logging import log
37 from webkitpy.common.system.filesystem_mock import MockFileSystem
38 from webkitpy.thirdparty.mock import Mock
39
40
41 def _id_to_object_dictionary(*objects):
42     dictionary = {}
43     for thing in objects:
44         dictionary[thing["id"]] = thing
45     return dictionary
46
47 # Testing
48
49 # FIXME: The ids should be 1, 2, 3 instead of crazy numbers.
50
51
52 _patch1 = {
53     "id": 197,
54     "bug_id": 42,
55     "url": "http://example.com/197",
56     "name": "Patch1",
57     "is_obsolete": False,
58     "is_patch": True,
59     "review": "+",
60     "reviewer_email": "foo@bar.com",
61     "commit-queue": "+",
62     "committer_email": "foo@bar.com",
63     "attacher_email": "Contributer1",
64 }
65
66
67 _patch2 = {
68     "id": 128,
69     "bug_id": 42,
70     "url": "http://example.com/128",
71     "name": "Patch2",
72     "is_obsolete": False,
73     "is_patch": True,
74     "review": "+",
75     "reviewer_email": "foo@bar.com",
76     "commit-queue": "+",
77     "committer_email": "non-committer@example.com",
78     "attacher_email": "eric@webkit.org",
79 }
80
81
82 _patch3 = {
83     "id": 103,
84     "bug_id": 75,
85     "url": "http://example.com/103",
86     "name": "Patch3",
87     "is_obsolete": False,
88     "is_patch": True,
89     "review": "?",
90     "attacher_email": "eric@webkit.org",
91 }
92
93
94 _patch4 = {
95     "id": 104,
96     "bug_id": 77,
97     "url": "http://example.com/103",
98     "name": "Patch3",
99     "is_obsolete": False,
100     "is_patch": True,
101     "review": "+",
102     "commit-queue": "?",
103     "reviewer_email": "foo@bar.com",
104     "attacher_email": "Contributer2",
105 }
106
107
108 _patch5 = {
109     "id": 105,
110     "bug_id": 77,
111     "url": "http://example.com/103",
112     "name": "Patch5",
113     "is_obsolete": False,
114     "is_patch": True,
115     "review": "+",
116     "reviewer_email": "foo@bar.com",
117     "attacher_email": "eric@webkit.org",
118 }
119
120
121 _patch6 = { # Valid committer, but no reviewer.
122     "id": 106,
123     "bug_id": 77,
124     "url": "http://example.com/103",
125     "name": "ROLLOUT of r3489",
126     "is_obsolete": False,
127     "is_patch": True,
128     "commit-queue": "+",
129     "committer_email": "foo@bar.com",
130     "attacher_email": "eric@webkit.org",
131 }
132
133
134 _patch7 = { # Valid review, patch is marked obsolete.
135     "id": 107,
136     "bug_id": 76,
137     "url": "http://example.com/103",
138     "name": "Patch7",
139     "is_obsolete": True,
140     "is_patch": True,
141     "review": "+",
142     "reviewer_email": "foo@bar.com",
143     "attacher_email": "eric@webkit.org",
144 }
145
146
147 # This matches one of Bug.unassigned_emails
148 _unassigned_email = "webkit-unassigned@lists.webkit.org"
149 # This is needed for the FlakyTestReporter to believe the bug
150 # was filed by one of the webkitpy bots.
151 _commit_queue_email = "commit-queue@webkit.org"
152
153
154 # FIXME: The ids should be 1, 2, 3 instead of crazy numbers.
155
156
157 _bug1 = {
158     "id": 42,
159     "title": "Bug with two r+'d and cq+'d patches, one of which has an "
160              "invalid commit-queue setter.",
161     "reporter_email": "foo@foo.com",
162     "assigned_to_email": _unassigned_email,
163     "attachments": [_patch1, _patch2],
164     "bug_status": "UNCONFIRMED",
165 }
166
167
168 _bug2 = {
169     "id": 75,
170     "title": "Bug with a patch needing review.",
171     "reporter_email": "foo@foo.com",
172     "assigned_to_email": "foo@foo.com",
173     "attachments": [_patch3],
174     "bug_status": "ASSIGNED",
175 }
176
177
178 _bug3 = {
179     "id": 76,
180     "title": "The third bug",
181     "reporter_email": "foo@foo.com",
182     "assigned_to_email": _unassigned_email,
183     "attachments": [_patch7],
184     "bug_status": "NEW",
185 }
186
187
188 _bug4 = {
189     "id": 77,
190     "title": "The fourth bug",
191     "reporter_email": "foo@foo.com",
192     "assigned_to_email": "foo@foo.com",
193     "attachments": [_patch4, _patch5, _patch6],
194     "bug_status": "REOPENED",
195 }
196
197
198 _bug5 = {
199     "id": 78,
200     "title": "The fifth bug",
201     "reporter_email": _commit_queue_email,
202     "assigned_to_email": "foo@foo.com",
203     "attachments": [],
204     "bug_status": "RESOLVED",
205     "dup_id": 76,
206 }
207
208
209 # FIXME: This should not inherit from Mock
210 class MockBugzillaQueries(Mock):
211
212     def __init__(self, bugzilla):
213         Mock.__init__(self)
214         self._bugzilla = bugzilla
215
216     def _all_bugs(self):
217         return map(lambda bug_dictionary: Bug(bug_dictionary, self._bugzilla),
218                    self._bugzilla.bug_cache.values())
219
220     def fetch_bug_ids_from_commit_queue(self):
221         bugs_with_commit_queued_patches = filter(
222                 lambda bug: bug.commit_queued_patches(),
223                 self._all_bugs())
224         return map(lambda bug: bug.id(), bugs_with_commit_queued_patches)
225
226     def fetch_attachment_ids_from_review_queue(self):
227         unreviewed_patches = sum([bug.unreviewed_patches()
228                                   for bug in self._all_bugs()], [])
229         return map(lambda patch: patch.id(), unreviewed_patches)
230
231     def fetch_patches_from_commit_queue(self):
232         return sum([bug.commit_queued_patches()
233                     for bug in self._all_bugs()], [])
234
235     def fetch_bug_ids_from_pending_commit_list(self):
236         bugs_with_reviewed_patches = filter(lambda bug: bug.reviewed_patches(),
237                                             self._all_bugs())
238         bug_ids = map(lambda bug: bug.id(), bugs_with_reviewed_patches)
239         # NOTE: This manual hack here is to allow testing logging in
240         # test_assign_to_committer the real pending-commit query on bugzilla
241         # will return bugs with patches which have r+, but are also obsolete.
242         return bug_ids + [76]
243
244     def fetch_patches_from_pending_commit_list(self):
245         return sum([bug.reviewed_patches() for bug in self._all_bugs()], [])
246
247     def fetch_bugs_matching_search(self, search_string, author_email=None):
248         return [self._bugzilla.fetch_bug(78), self._bugzilla.fetch_bug(77)]
249
250 _mock_reviewer = Reviewer("Foo Bar", "foo@bar.com")
251
252
253 # FIXME: Bugzilla is the wrong Mock-point.  Once we have a BugzillaNetwork
254 #        class we should mock that instead.
255 # Most of this class is just copy/paste from Bugzilla.
256 # FIXME: This should not inherit from Mock
257 class MockBugzilla(Mock):
258
259     bug_server_url = "http://example.com"
260
261     bug_cache = _id_to_object_dictionary(_bug1, _bug2, _bug3, _bug4, _bug5)
262
263     attachment_cache = _id_to_object_dictionary(_patch1,
264                                                 _patch2,
265                                                 _patch3,
266                                                 _patch4,
267                                                 _patch5,
268                                                 _patch6,
269                                                 _patch7)
270
271     def __init__(self):
272         Mock.__init__(self)
273         self.queries = MockBugzillaQueries(self)
274         self.committers = CommitterList(reviewers=[_mock_reviewer])
275         self._override_patch = None
276
277     def create_bug(self,
278                    bug_title,
279                    bug_description,
280                    component=None,
281                    diff=None,
282                    patch_description=None,
283                    cc=None,
284                    blocked=None,
285                    mark_for_review=False,
286                    mark_for_commit_queue=False):
287         log("MOCK create_bug")
288         log("bug_title: %s" % bug_title)
289         log("bug_description: %s" % bug_description)
290         if component:
291             log("component: %s" % component)
292         if cc:
293             log("cc: %s" % cc)
294         if blocked:
295             log("blocked: %s" % blocked)
296         return 78
297
298     def quips(self):
299         return ["Good artists copy. Great artists steal. - Pablo Picasso"]
300
301     def fetch_bug(self, bug_id):
302         return Bug(self.bug_cache.get(bug_id), self)
303
304     def set_override_patch(self, patch):
305         self._override_patch = patch
306
307     def fetch_attachment(self, attachment_id):
308         if self._override_patch:
309             return self._override_patch
310
311         attachment_dictionary = self.attachment_cache.get(attachment_id)
312         if not attachment_dictionary:
313             print "MOCK: fetch_attachment: %s is not a known attachment id" % attachment_id
314             return None
315         bug = self.fetch_bug(attachment_dictionary["bug_id"])
316         for attachment in bug.attachments(include_obsolete=True):
317             if attachment.id() == int(attachment_id):
318                 return attachment
319
320     def bug_url_for_bug_id(self, bug_id):
321         return "%s/%s" % (self.bug_server_url, bug_id)
322
323     def fetch_bug_dictionary(self, bug_id):
324         return self.bug_cache.get(bug_id)
325
326     def attachment_url_for_id(self, attachment_id, action="view"):
327         action_param = ""
328         if action and action != "view":
329             action_param = "&action=%s" % action
330         return "%s/%s%s" % (self.bug_server_url, attachment_id, action_param)
331
332     def set_flag_on_attachment(self,
333                                attachment_id,
334                                flag_name,
335                                flag_value,
336                                comment_text=None,
337                                additional_comment_text=None):
338         log("MOCK setting flag '%s' to '%s' on attachment '%s' with comment '%s' and additional comment '%s'" % (
339             flag_name, flag_value, attachment_id, comment_text, additional_comment_text))
340
341     def post_comment_to_bug(self, bug_id, comment_text, cc=None):
342         log("MOCK bug comment: bug_id=%s, cc=%s\n--- Begin comment ---\n%s\n--- End comment ---\n" % (
343             bug_id, cc, comment_text))
344
345     def add_patch_to_bug(self,
346                          bug_id,
347                          diff,
348                          description,
349                          comment_text=None,
350                          mark_for_review=False,
351                          mark_for_commit_queue=False,
352                          mark_for_landing=False):
353         log("MOCK add_patch_to_bug: bug_id=%s, description=%s, mark_for_review=%s, mark_for_commit_queue=%s, mark_for_landing=%s" %
354             (bug_id, description, mark_for_review, mark_for_commit_queue, mark_for_landing))
355         log("-- Begin comment --")
356         log(comment_text)
357         log("-- End comment --")
358
359
360 class MockBuilder(object):
361     def __init__(self, name):
362         self._name = name
363
364     def name(self):
365         return self._name
366
367     def results_url(self):
368         return "http://example.com/builders/%s/results/" % self.name()
369
370     def force_build(self, username, comments):
371         log("MOCK: force_build: name=%s, username=%s, comments=%s" % (
372             self._name, username, comments))
373
374
375 class MockFailureMap(object):
376     def __init__(self, buildbot):
377         self._buildbot = buildbot
378
379     def is_empty(self):
380         return False
381
382     def filter_out_old_failures(self, is_old_revision):
383         pass
384
385     def failing_revisions(self):
386         return [29837]
387
388     def builders_failing_for(self, revision):
389         return [self._buildbot.builder_with_name("Builder1")]
390
391     def tests_failing_for(self, revision):
392         return ["mock-test-1"]
393
394
395 class MockBuildBot(object):
396     buildbot_host = "dummy_buildbot_host"
397     def __init__(self):
398         self._mock_builder1_status = {
399             "name": "Builder1",
400             "is_green": True,
401             "activity": "building",
402         }
403         self._mock_builder2_status = {
404             "name": "Builder2",
405             "is_green": True,
406             "activity": "idle",
407         }
408
409     def builder_with_name(self, name):
410         return MockBuilder(name)
411
412     def builder_statuses(self):
413         return [
414             self._mock_builder1_status,
415             self._mock_builder2_status,
416         ]
417
418     def red_core_builders_names(self):
419         if not self._mock_builder2_status["is_green"]:
420             return [self._mock_builder2_status["name"]]
421         return []
422
423     def red_core_builders(self):
424         if not self._mock_builder2_status["is_green"]:
425             return [self._mock_builder2_status]
426         return []
427
428     def idle_red_core_builders(self):
429         if not self._mock_builder2_status["is_green"]:
430             return [self._mock_builder2_status]
431         return []
432
433     def last_green_revision(self):
434         return 9479
435
436     def light_tree_on_fire(self):
437         self._mock_builder2_status["is_green"] = False
438
439     def failure_map(self):
440         return MockFailureMap(self)
441
442
443 # FIXME: This should not inherit from Mock
444 class MockSCM(Mock):
445
446     fake_checkout_root = os.path.realpath("/tmp") # realpath is needed to allow for Mac OS X's /private/tmp
447
448     def __init__(self):
449         Mock.__init__(self)
450         # FIXME: We should probably use real checkout-root detection logic here.
451         # os.getcwd() can't work here because other parts of the code assume that "checkout_root"
452         # will actually be the root.  Since getcwd() is wrong, use a globally fake root for now.
453         self.checkout_root = self.fake_checkout_root
454
455     def changed_files(self, git_commit=None):
456         return ["MockFile1"]
457
458     def create_patch(self, git_commit, changed_files=None):
459         return "Patch1"
460
461     def commit_ids_from_commitish_arguments(self, args):
462         return ["Commitish1", "Commitish2"]
463
464     def commit_message_for_local_commit(self, commit_id):
465         if commit_id == "Commitish1":
466             return CommitMessage("CommitMessage1\n" \
467                 "https://bugs.example.org/show_bug.cgi?id=42\n")
468         if commit_id == "Commitish2":
469             return CommitMessage("CommitMessage2\n" \
470                 "https://bugs.example.org/show_bug.cgi?id=75\n")
471         raise Exception("Bogus commit_id in commit_message_for_local_commit.")
472
473     def diff_for_revision(self, revision):
474         return "DiffForRevision%s\n" \
475                "http://bugs.webkit.org/show_bug.cgi?id=12345" % revision
476
477     def svn_revision_from_commit_text(self, commit_text):
478         return "49824"
479
480     def add(self, destination_path, return_exit_code=False):
481         if return_exit_code:
482             return 0
483
484
485 class MockCheckout(object):
486
487     _committer_list = CommitterList()
488
489     def commit_info_for_revision(self, svn_revision):
490         # The real Checkout would probably throw an exception, but this is the only way tests have to get None back at the moment.
491         if not svn_revision:
492             return None
493         return CommitInfo(svn_revision, "eric@webkit.org", {
494             "bug_id": 42,
495             "author_name": "Adam Barth",
496             "author_email": "abarth@webkit.org",
497             "author": self._committer_list.committer_by_email("abarth@webkit.org"),
498             "reviewer_text": "Darin Adler",
499             "reviewer": self._committer_list.committer_by_name("Darin Adler"),
500         })
501
502     def bug_id_for_revision(self, svn_revision):
503         return 12345
504
505     def recent_commit_infos_for_files(self, paths):
506         return [self.commit_info_for_revision(32)]
507
508     def modified_changelogs(self, git_commit, changed_files=None):
509         # Ideally we'd return something more interesting here.  The problem is
510         # that LandDiff will try to actually read the patch from disk!
511         return []
512
513     def commit_message_for_this_commit(self, git_commit, changed_files=None):
514         commit_message = Mock()
515         commit_message.message = lambda:"This is a fake commit message that is at least 50 characters."
516         return commit_message
517
518     def apply_patch(self, patch, force=False):
519         pass
520
521     def apply_reverse_diffs(self, revision):
522         pass
523
524     def suggested_reviewers(self, git_commit, changed_files=None):
525         return [_mock_reviewer]
526
527
528 class MockUser(object):
529
530     @staticmethod
531     def prompt(message, repeat=1, raw_input=raw_input):
532         return "Mock user response"
533
534     def edit(self, files):
535         pass
536
537     def edit_changelog(self, files):
538         pass
539
540     def page(self, message):
541         pass
542
543     def confirm(self, message=None, default='y'):
544         print message
545         return default == 'y'
546
547     def can_open_url(self):
548         return True
549
550     def open_url(self, url):
551         if url.startswith("file://"):
552             log("MOCK: user.open_url: file://...")
553             return
554         log("MOCK: user.open_url: %s" % url)
555
556
557 class MockIRC(object):
558
559     def post(self, message):
560         log("MOCK: irc.post: %s" % message)
561
562     def disconnect(self):
563         log("MOCK: irc.disconnect")
564
565
566 class MockStatusServer(object):
567
568     def __init__(self, bot_id=None, work_items=None):
569         self.host = "example.com"
570         self.bot_id = bot_id
571         self._work_items = work_items or []
572
573     def patch_status(self, queue_name, patch_id):
574         return None
575
576     def svn_revision(self, svn_revision):
577         return None
578
579     def next_work_item(self, queue_name):
580         if not self._work_items:
581             return None
582         return self._work_items.pop(0)
583
584     def release_work_item(self, queue_name, patch):
585         log("MOCK: release_work_item: %s %s" % (queue_name, patch.id()))
586
587     def update_work_items(self, queue_name, work_items):
588         self._work_items = work_items
589         log("MOCK: update_work_items: %s %s" % (queue_name, work_items))
590
591     def submit_to_ews(self, patch_id):
592         log("MOCK: submit_to_ews: %s" % (patch_id))
593
594     def update_status(self, queue_name, status, patch=None, results_file=None):
595         log("MOCK: update_status: %s %s" % (queue_name, status))
596         return 187
597
598     def update_svn_revision(self, svn_revision, broken_bot):
599         return 191
600
601     def results_url_for_status(self, status_id):
602         return "http://dummy_url"
603
604
605 # FIXME: This should not inherit from Mock
606 # FIXME: Unify with common.system.executive_mock.MockExecutive.
607 class MockExecutive(Mock):
608     def __init__(self, should_log):
609         self._should_log = should_log
610
611     def run_and_throw_if_fail(self, args, quiet=False):
612         if self._should_log:
613             log("MOCK run_and_throw_if_fail: %s" % args)
614         return "MOCK output of child process"
615
616     def run_command(self,
617                     args,
618                     cwd=None,
619                     input=None,
620                     error_handler=None,
621                     return_exit_code=False,
622                     return_stderr=True,
623                     decode_output=False):
624         if self._should_log:
625             log("MOCK run_command: %s" % args)
626         return "MOCK output of child process"
627
628
629 class MockOptions(object):
630     """Mock implementation of optparse.Values."""
631
632     def __init__(self, **kwargs):
633         # The caller can set option values using keyword arguments. We don't
634         # set any values by default because we don't know how this
635         # object will be used. Generally speaking unit tests should
636         # subclass this or provider wrapper functions that set a common
637         # set of options.
638         for key, value in kwargs.items():
639             self.__dict__[key] = value
640
641
642 class MockPort(Mock):
643     def name(self):
644         return "MockPort"
645
646     def layout_tests_results_path(self):
647         return "/mock/results.html"
648
649 class MockTestPort1(object):
650
651     def skips_layout_test(self, test_name):
652         return test_name in ["media/foo/bar.html", "foo"]
653
654
655 class MockTestPort2(object):
656
657     def skips_layout_test(self, test_name):
658         return test_name == "media/foo/bar.html"
659
660
661 class MockPortFactory(object):
662
663     def get_all(self, options=None):
664         return {"test_port1": MockTestPort1(), "test_port2": MockTestPort2()}
665
666
667 class MockPlatformInfo(object):
668     def display_name(self):
669         return "MockPlatform 1.0"
670
671
672 class MockTool(object):
673
674     def __init__(self, log_executive=False):
675         self.wakeup_event = threading.Event()
676         self.bugs = MockBugzilla()
677         self.buildbot = MockBuildBot()
678         self.executive = MockExecutive(should_log=log_executive)
679         self.filesystem = MockFileSystem()
680         self._irc = None
681         self.user = MockUser()
682         self._scm = MockSCM()
683         self._checkout = MockCheckout()
684         self.status_server = MockStatusServer()
685         self.irc_password = "MOCK irc password"
686         self.port_factory = MockPortFactory()
687         self.platform = MockPlatformInfo()
688
689     def scm(self):
690         return self._scm
691
692     def checkout(self):
693         return self._checkout
694
695     def ensure_irc_connected(self, delegate):
696         if not self._irc:
697             self._irc = MockIRC()
698
699     def irc(self):
700         return self._irc
701
702     def path(self):
703         return "echo"
704
705     def port(self):
706         return MockPort()
707
708
709 class MockBrowser(object):
710     params = {}
711
712     def open(self, url):
713         pass
714
715     def select_form(self, name):
716         pass
717
718     def __setitem__(self, key, value):
719         self.params[key] = value
720
721     def submit(self):
722         return Mock(file)