WebKitTools/Scripts/webkitpy/tool/mocktool.py
changeset 0 4f2f89ce4247
equal deleted inserted replaced
-1:000000000000 0:4f2f89ce4247
       
     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.net.rietveld import Rietveld
       
    37 from webkitpy.thirdparty.mock import Mock
       
    38 from webkitpy.common.system.deprecated_logging import log
       
    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     "in-rietveld": "?",
       
    90     "review": "?",
       
    91     "attacher_email": "eric@webkit.org",
       
    92 }
       
    93 
       
    94 
       
    95 _patch4 = {
       
    96     "id": 104,
       
    97     "bug_id": 77,
       
    98     "url": "http://example.com/103",
       
    99     "name": "Patch3",
       
   100     "is_obsolete": False,
       
   101     "is_patch": True,
       
   102     "review": "+",
       
   103     "commit-queue": "?",
       
   104     "reviewer_email": "foo@bar.com",
       
   105     "attacher_email": "Contributer2",
       
   106 }
       
   107 
       
   108 
       
   109 _patch5 = {
       
   110     "id": 105,
       
   111     "bug_id": 77,
       
   112     "url": "http://example.com/103",
       
   113     "name": "Patch5",
       
   114     "is_obsolete": False,
       
   115     "is_patch": True,
       
   116     "in-rietveld": "?",
       
   117     "review": "+",
       
   118     "reviewer_email": "foo@bar.com",
       
   119     "attacher_email": "eric@webkit.org",
       
   120 }
       
   121 
       
   122 
       
   123 _patch6 = { # Valid committer, but no reviewer.
       
   124     "id": 106,
       
   125     "bug_id": 77,
       
   126     "url": "http://example.com/103",
       
   127     "name": "ROLLOUT of r3489",
       
   128     "is_obsolete": False,
       
   129     "is_patch": True,
       
   130     "in-rietveld": "-",
       
   131     "commit-queue": "+",
       
   132     "committer_email": "foo@bar.com",
       
   133     "attacher_email": "eric@webkit.org",
       
   134 }
       
   135 
       
   136 
       
   137 _patch7 = { # Valid review, patch is marked obsolete.
       
   138     "id": 107,
       
   139     "bug_id": 76,
       
   140     "url": "http://example.com/103",
       
   141     "name": "Patch7",
       
   142     "is_obsolete": True,
       
   143     "is_patch": True,
       
   144     "in-rietveld": "+",
       
   145     "review": "+",
       
   146     "reviewer_email": "foo@bar.com",
       
   147     "attacher_email": "eric@webkit.org",
       
   148 }
       
   149 
       
   150 
       
   151 # This matches one of Bug.unassigned_emails
       
   152 _unassigned_email = "webkit-unassigned@lists.webkit.org"
       
   153 
       
   154 
       
   155 # FIXME: The ids should be 1, 2, 3 instead of crazy numbers.
       
   156 
       
   157 
       
   158 _bug1 = {
       
   159     "id": 42,
       
   160     "title": "Bug with two r+'d and cq+'d patches, one of which has an "
       
   161              "invalid commit-queue setter.",
       
   162     "assigned_to_email": _unassigned_email,
       
   163     "attachments": [_patch1, _patch2],
       
   164 }
       
   165 
       
   166 
       
   167 _bug2 = {
       
   168     "id": 75,
       
   169     "title": "Bug with a patch needing review.",
       
   170     "assigned_to_email": "foo@foo.com",
       
   171     "attachments": [_patch3],
       
   172 }
       
   173 
       
   174 
       
   175 _bug3 = {
       
   176     "id": 76,
       
   177     "title": "The third bug",
       
   178     "assigned_to_email": _unassigned_email,
       
   179     "attachments": [_patch7],
       
   180 }
       
   181 
       
   182 
       
   183 _bug4 = {
       
   184     "id": 77,
       
   185     "title": "The fourth bug",
       
   186     "assigned_to_email": "foo@foo.com",
       
   187     "attachments": [_patch4, _patch5, _patch6],
       
   188 }
       
   189 
       
   190 
       
   191 class MockBugzillaQueries(Mock):
       
   192 
       
   193     def __init__(self, bugzilla):
       
   194         Mock.__init__(self)
       
   195         self._bugzilla = bugzilla
       
   196 
       
   197     def _all_bugs(self):
       
   198         return map(lambda bug_dictionary: Bug(bug_dictionary, self._bugzilla),
       
   199                    self._bugzilla.bug_cache.values())
       
   200 
       
   201     def fetch_bug_ids_from_commit_queue(self):
       
   202         bugs_with_commit_queued_patches = filter(
       
   203                 lambda bug: bug.commit_queued_patches(),
       
   204                 self._all_bugs())
       
   205         return map(lambda bug: bug.id(), bugs_with_commit_queued_patches)
       
   206 
       
   207     def fetch_attachment_ids_from_review_queue(self):
       
   208         unreviewed_patches = sum([bug.unreviewed_patches()
       
   209                                   for bug in self._all_bugs()], [])
       
   210         return map(lambda patch: patch.id(), unreviewed_patches)
       
   211 
       
   212     def fetch_patches_from_commit_queue(self):
       
   213         return sum([bug.commit_queued_patches()
       
   214                     for bug in self._all_bugs()], [])
       
   215 
       
   216     def fetch_bug_ids_from_pending_commit_list(self):
       
   217         bugs_with_reviewed_patches = filter(lambda bug: bug.reviewed_patches(),
       
   218                                             self._all_bugs())
       
   219         bug_ids = map(lambda bug: bug.id(), bugs_with_reviewed_patches)
       
   220         # NOTE: This manual hack here is to allow testing logging in
       
   221         # test_assign_to_committer the real pending-commit query on bugzilla
       
   222         # will return bugs with patches which have r+, but are also obsolete.
       
   223         return bug_ids + [76]
       
   224 
       
   225     def fetch_patches_from_pending_commit_list(self):
       
   226         return sum([bug.reviewed_patches() for bug in self._all_bugs()], [])
       
   227 
       
   228     def fetch_first_patch_from_rietveld_queue(self):
       
   229         for bug in self._all_bugs():
       
   230             patches = bug.in_rietveld_queue_patches()
       
   231             if len(patches):
       
   232                 return patches[0]
       
   233         raise Exception('No patches in the rietveld queue')
       
   234 
       
   235 # FIXME: Bugzilla is the wrong Mock-point.  Once we have a BugzillaNetwork
       
   236 #        class we should mock that instead.
       
   237 # Most of this class is just copy/paste from Bugzilla.
       
   238 
       
   239 
       
   240 class MockBugzilla(Mock):
       
   241 
       
   242     bug_server_url = "http://example.com"
       
   243 
       
   244     bug_cache = _id_to_object_dictionary(_bug1, _bug2, _bug3, _bug4)
       
   245 
       
   246     attachment_cache = _id_to_object_dictionary(_patch1,
       
   247                                                 _patch2,
       
   248                                                 _patch3,
       
   249                                                 _patch4,
       
   250                                                 _patch5,
       
   251                                                 _patch6,
       
   252                                                 _patch7)
       
   253 
       
   254     def __init__(self):
       
   255         Mock.__init__(self)
       
   256         self.queries = MockBugzillaQueries(self)
       
   257         self.committers = CommitterList(reviewers=[Reviewer("Foo Bar",
       
   258                                                             "foo@bar.com")])
       
   259 
       
   260     def create_bug(self,
       
   261                    bug_title,
       
   262                    bug_description,
       
   263                    component=None,
       
   264                    diff=None,
       
   265                    patch_description=None,
       
   266                    cc=None,
       
   267                    blocked=None,
       
   268                    mark_for_review=False,
       
   269                    mark_for_commit_queue=False):
       
   270         log("MOCK create_bug")
       
   271         log("bug_title: %s" % bug_title)
       
   272         log("bug_description: %s" % bug_description)
       
   273 
       
   274     def quips(self):
       
   275         return ["Good artists copy. Great artists steal. - Pablo Picasso"]
       
   276 
       
   277     def fetch_bug(self, bug_id):
       
   278         return Bug(self.bug_cache.get(bug_id), self)
       
   279 
       
   280     def fetch_attachment(self, attachment_id):
       
   281         # This could be changed to .get() if we wish to allow failed lookups.
       
   282         attachment_dictionary = self.attachment_cache[attachment_id]
       
   283         bug = self.fetch_bug(attachment_dictionary["bug_id"])
       
   284         for attachment in bug.attachments(include_obsolete=True):
       
   285             if attachment.id() == int(attachment_id):
       
   286                 return attachment
       
   287 
       
   288     def bug_url_for_bug_id(self, bug_id):
       
   289         return "%s/%s" % (self.bug_server_url, bug_id)
       
   290 
       
   291     def fetch_bug_dictionary(self, bug_id):
       
   292         return self.bug_cache.get(bug_id)
       
   293 
       
   294     def attachment_url_for_id(self, attachment_id, action="view"):
       
   295         action_param = ""
       
   296         if action and action != "view":
       
   297             action_param = "&action=%s" % action
       
   298         return "%s/%s%s" % (self.bug_server_url, attachment_id, action_param)
       
   299 
       
   300     def set_flag_on_attachment(self,
       
   301                                attachment_id,
       
   302                                flag_name,
       
   303                                flag_value,
       
   304                                comment_text=None,
       
   305                                additional_comment_text=None):
       
   306         log("MOCK setting flag '%s' to '%s' on attachment '%s' with comment '%s' and additional comment '%s'" % (
       
   307             flag_name, flag_value, attachment_id, comment_text, additional_comment_text))
       
   308 
       
   309     def post_comment_to_bug(self, bug_id, comment_text, cc=None):
       
   310         log("MOCK bug comment: bug_id=%s, cc=%s\n--- Begin comment ---\%s\n--- End comment ---\n" % (
       
   311             bug_id, cc, comment_text))
       
   312 
       
   313     def add_patch_to_bug(self,
       
   314                          bug_id,
       
   315                          diff,
       
   316                          description,
       
   317                          comment_text=None,
       
   318                          mark_for_review=False,
       
   319                          mark_for_commit_queue=False,
       
   320                          mark_for_landing=False):
       
   321         log("MOCK add_patch_to_bug: bug_id=%s, description=%s, mark_for_review=%s, mark_for_commit_queue=%s, mark_for_landing=%s" %
       
   322             (bug_id, description, mark_for_review, mark_for_commit_queue, mark_for_landing))
       
   323         log("-- Begin comment --")
       
   324         log(comment_text)
       
   325         log("-- End comment --")
       
   326 
       
   327 
       
   328 class MockBuilder(object):
       
   329     def __init__(self, name):
       
   330         self._name = name
       
   331 
       
   332     def name(self):
       
   333         return self._name
       
   334 
       
   335     def results_url(self):
       
   336         return "http://example.com/builders/%s/results/" % self.name()
       
   337 
       
   338     def force_build(self, username, comments):
       
   339         log("MOCK: force_build: name=%s, username=%s, comments=%s" % (
       
   340             self._name, username, comments))
       
   341 
       
   342 
       
   343 class MockBuildBot(object):
       
   344     def __init__(self):
       
   345         self._mock_builder1_status = {
       
   346             "name": "Builder1",
       
   347             "is_green": True,
       
   348             "activity": "building",
       
   349         }
       
   350         self._mock_builder2_status = {
       
   351             "name": "Builder2",
       
   352             "is_green": True,
       
   353             "activity": "idle",
       
   354         }
       
   355 
       
   356     def builder_with_name(self, name):
       
   357         return MockBuilder(name)
       
   358 
       
   359     def builder_statuses(self):
       
   360         return [
       
   361             self._mock_builder1_status,
       
   362             self._mock_builder2_status,
       
   363         ]
       
   364 
       
   365     def red_core_builders_names(self):
       
   366         if not self._mock_builder2_status["is_green"]:
       
   367             return [self._mock_builder2_status["name"]]
       
   368         return []
       
   369 
       
   370     def red_core_builders(self):
       
   371         if not self._mock_builder2_status["is_green"]:
       
   372             return [self._mock_builder2_status]
       
   373         return []
       
   374 
       
   375     def idle_red_core_builders(self):
       
   376         if not self._mock_builder2_status["is_green"]:
       
   377             return [self._mock_builder2_status]
       
   378         return []
       
   379 
       
   380     def last_green_revision(self):
       
   381         return 9479
       
   382 
       
   383     def light_tree_on_fire(self):
       
   384         self._mock_builder2_status["is_green"] = False
       
   385 
       
   386     def revisions_causing_failures(self):
       
   387         return {
       
   388             "29837": [self.builder_with_name("Builder1")],
       
   389         }
       
   390 
       
   391 
       
   392 class MockSCM(Mock):
       
   393 
       
   394     fake_checkout_root = os.path.realpath("/tmp") # realpath is needed to allow for Mac OS X's /private/tmp
       
   395 
       
   396     def __init__(self):
       
   397         Mock.__init__(self)
       
   398         # FIXME: We should probably use real checkout-root detection logic here.
       
   399         # os.getcwd() can't work here because other parts of the code assume that "checkout_root"
       
   400         # will actually be the root.  Since getcwd() is wrong, use a globally fake root for now.
       
   401         self.checkout_root = self.fake_checkout_root
       
   402 
       
   403     def create_patch(self, git_commit):
       
   404         return "Patch1"
       
   405 
       
   406     def commit_ids_from_commitish_arguments(self, args):
       
   407         return ["Commitish1", "Commitish2"]
       
   408 
       
   409     def commit_message_for_local_commit(self, commit_id):
       
   410         if commit_id == "Commitish1":
       
   411             return CommitMessage("CommitMessage1\n" \
       
   412                 "https://bugs.example.org/show_bug.cgi?id=42\n")
       
   413         if commit_id == "Commitish2":
       
   414             return CommitMessage("CommitMessage2\n" \
       
   415                 "https://bugs.example.org/show_bug.cgi?id=75\n")
       
   416         raise Exception("Bogus commit_id in commit_message_for_local_commit.")
       
   417 
       
   418     def diff_for_revision(self, revision):
       
   419         return "DiffForRevision%s\n" \
       
   420                "http://bugs.webkit.org/show_bug.cgi?id=12345" % revision
       
   421 
       
   422     def svn_revision_from_commit_text(self, commit_text):
       
   423         return "49824"
       
   424 
       
   425 
       
   426 class MockCheckout(object):
       
   427 
       
   428     _committer_list = CommitterList()
       
   429 
       
   430     def commit_info_for_revision(self, svn_revision):
       
   431         return CommitInfo(svn_revision, "eric@webkit.org", {
       
   432             "bug_id": 42,
       
   433             "author_name": "Adam Barth",
       
   434             "author_email": "abarth@webkit.org",
       
   435             "author": self._committer_list.committer_by_email("abarth@webkit.org"),
       
   436             "reviewer_text": "Darin Adler",
       
   437             "reviewer": self._committer_list.committer_by_name("Darin Adler"),
       
   438         })
       
   439 
       
   440     def bug_id_for_revision(self, svn_revision):
       
   441         return 12345
       
   442 
       
   443     def modified_changelogs(self, git_commit):
       
   444         # Ideally we'd return something more interesting here.  The problem is
       
   445         # that LandDiff will try to actually read the patch from disk!
       
   446         return []
       
   447 
       
   448     def commit_message_for_this_commit(self, git_commit):
       
   449         commit_message = Mock()
       
   450         commit_message.message = lambda:"This is a fake commit message that is at least 50 characters."
       
   451         return commit_message
       
   452 
       
   453     def apply_patch(self, patch, force=False):
       
   454         pass
       
   455 
       
   456     def apply_reverse_diff(self, revision):
       
   457         pass
       
   458 
       
   459 
       
   460 class MockUser(object):
       
   461 
       
   462     @staticmethod
       
   463     def prompt(message, repeat=1, raw_input=raw_input):
       
   464         return "Mock user response"
       
   465 
       
   466     def edit(self, files):
       
   467         pass
       
   468 
       
   469     def edit_changelog(self, files):
       
   470         pass
       
   471 
       
   472     def page(self, message):
       
   473         pass
       
   474 
       
   475     def confirm(self, message=None):
       
   476         return True
       
   477 
       
   478     def can_open_url(self):
       
   479         return True
       
   480 
       
   481     def open_url(self, url):
       
   482         if url.startswith("file://"):
       
   483             log("MOCK: user.open_url: file://...")
       
   484             return
       
   485         log("MOCK: user.open_url: %s" % url)
       
   486 
       
   487 
       
   488 class MockIRC(object):
       
   489 
       
   490     def post(self, message):
       
   491         log("MOCK: irc.post: %s" % message)
       
   492 
       
   493     def disconnect(self):
       
   494         log("MOCK: irc.disconnect")
       
   495 
       
   496 
       
   497 class MockStatusServer(object):
       
   498 
       
   499     def __init__(self):
       
   500         self.host = "example.com"
       
   501 
       
   502     def patch_status(self, queue_name, patch_id):
       
   503         return None
       
   504 
       
   505     def svn_revision(self, svn_revision):
       
   506         return None
       
   507 
       
   508     def update_work_items(self, queue_name, work_items):
       
   509         log("MOCK: update_work_items: %s %s" % (queue_name, work_items))
       
   510 
       
   511     def update_status(self, queue_name, status, patch=None, results_file=None):
       
   512         log("MOCK: update_status: %s %s" % (queue_name, status))
       
   513         return 187
       
   514 
       
   515     def update_svn_revision(self, svn_revision, broken_bot):
       
   516         return 191
       
   517 
       
   518     def results_url_for_status(self, status_id):
       
   519         return "http://dummy_url"
       
   520 
       
   521 
       
   522 class MockExecute(Mock):
       
   523     def __init__(self, should_log):
       
   524         self._should_log = should_log
       
   525 
       
   526     def run_and_throw_if_fail(self, args, quiet=False):
       
   527         if self._should_log:
       
   528             log("MOCK run_and_throw_if_fail: %s" % args)
       
   529         return "MOCK output of child process"
       
   530 
       
   531     def run_command(self,
       
   532                     args,
       
   533                     cwd=None,
       
   534                     input=None,
       
   535                     error_handler=None,
       
   536                     return_exit_code=False,
       
   537                     return_stderr=True,
       
   538                     decode_output=False):
       
   539         if self._should_log:
       
   540             log("MOCK run_command: %s" % args)
       
   541         return "MOCK output of child process"
       
   542 
       
   543 
       
   544 class MockOptions(Mock):
       
   545     no_squash = False
       
   546     squash = False
       
   547 
       
   548 
       
   549 class MockRietveld():
       
   550 
       
   551     def __init__(self, executive, dryrun=False):
       
   552         pass
       
   553 
       
   554     def post(self, diff, message=None, codereview_issue=None, cc=None):
       
   555         log("MOCK: Uploading patch to rietveld")
       
   556 
       
   557 
       
   558 class MockTool():
       
   559 
       
   560     def __init__(self, log_executive=False):
       
   561         self.wakeup_event = threading.Event()
       
   562         self.bugs = MockBugzilla()
       
   563         self.buildbot = MockBuildBot()
       
   564         self.executive = MockExecute(should_log=log_executive)
       
   565         self._irc = None
       
   566         self.user = MockUser()
       
   567         self._scm = MockSCM()
       
   568         self._checkout = MockCheckout()
       
   569         self.status_server = MockStatusServer()
       
   570         self.irc_password = "MOCK irc password"
       
   571         self.codereview = MockRietveld(self.executive)
       
   572 
       
   573     def scm(self):
       
   574         return self._scm
       
   575 
       
   576     def checkout(self):
       
   577         return self._checkout
       
   578 
       
   579     def ensure_irc_connected(self, delegate):
       
   580         if not self._irc:
       
   581             self._irc = MockIRC()
       
   582 
       
   583     def irc(self):
       
   584         return self._irc
       
   585 
       
   586     def path(self):
       
   587         return "echo"