Home | History | Annotate | Download | only in tool
      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 (at] bar.com",
     61     "commit-queue": "+",
     62     "committer_email": "foo (at] 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 (at] bar.com",
     76     "commit-queue": "+",
     77     "committer_email": "non-committer (at] example.com",
     78     "attacher_email": "eric (at] 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 (at] 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 (at] 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 (at] bar.com",
    117     "attacher_email": "eric (at] 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 (at] bar.com",
    130     "attacher_email": "eric (at] 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 (at] bar.com",
    143     "attacher_email": "eric (at] webkit.org",
    144 }
    145 
    146 
    147 # This matches one of Bug.unassigned_emails
    148 _unassigned_email = "webkit-unassigned (at] 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 (at] 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 (at] 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 (at] foo.com",
    172     "assigned_to_email": "foo (at] 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 (at] 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 (at] foo.com",
    192     "assigned_to_email": "foo (at] 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 (at] 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 (at] 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_attachment_to_bug(self,
    346                               bug_id,
    347                               file_or_string,
    348                               description,
    349                               filename=None,
    350                               comment_text=None):
    351         log("MOCK add_attachment_to_bug: bug_id=%s, description=%s filename=%s" % (bug_id, description, filename))
    352         if comment_text:
    353             log("-- Begin comment --")
    354             log(comment_text)
    355             log("-- End comment --")
    356 
    357     def add_patch_to_bug(self,
    358                          bug_id,
    359                          diff,
    360                          description,
    361                          comment_text=None,
    362                          mark_for_review=False,
    363                          mark_for_commit_queue=False,
    364                          mark_for_landing=False):
    365         log("MOCK add_patch_to_bug: bug_id=%s, description=%s, mark_for_review=%s, mark_for_commit_queue=%s, mark_for_landing=%s" %
    366             (bug_id, description, mark_for_review, mark_for_commit_queue, mark_for_landing))
    367         if comment_text:
    368             log("-- Begin comment --")
    369             log(comment_text)
    370             log("-- End comment --")
    371 
    372 
    373 class MockBuilder(object):
    374     def __init__(self, name):
    375         self._name = name
    376 
    377     def name(self):
    378         return self._name
    379 
    380     def results_url(self):
    381         return "http://example.com/builders/%s/results/" % self.name()
    382 
    383     def force_build(self, username, comments):
    384         log("MOCK: force_build: name=%s, username=%s, comments=%s" % (
    385             self._name, username, comments))
    386 
    387 
    388 class MockFailureMap(object):
    389     def __init__(self, buildbot):
    390         self._buildbot = buildbot
    391 
    392     def is_empty(self):
    393         return False
    394 
    395     def filter_out_old_failures(self, is_old_revision):
    396         pass
    397 
    398     def failing_revisions(self):
    399         return [29837]
    400 
    401     def builders_failing_for(self, revision):
    402         return [self._buildbot.builder_with_name("Builder1")]
    403 
    404     def tests_failing_for(self, revision):
    405         return ["mock-test-1"]
    406 
    407 
    408 class MockBuildBot(object):
    409     buildbot_host = "dummy_buildbot_host"
    410     def __init__(self):
    411         self._mock_builder1_status = {
    412             "name": "Builder1",
    413             "is_green": True,
    414             "activity": "building",
    415         }
    416         self._mock_builder2_status = {
    417             "name": "Builder2",
    418             "is_green": True,
    419             "activity": "idle",
    420         }
    421 
    422     def builder_with_name(self, name):
    423         return MockBuilder(name)
    424 
    425     def builder_statuses(self):
    426         return [
    427             self._mock_builder1_status,
    428             self._mock_builder2_status,
    429         ]
    430 
    431     def red_core_builders_names(self):
    432         if not self._mock_builder2_status["is_green"]:
    433             return [self._mock_builder2_status["name"]]
    434         return []
    435 
    436     def red_core_builders(self):
    437         if not self._mock_builder2_status["is_green"]:
    438             return [self._mock_builder2_status]
    439         return []
    440 
    441     def idle_red_core_builders(self):
    442         if not self._mock_builder2_status["is_green"]:
    443             return [self._mock_builder2_status]
    444         return []
    445 
    446     def last_green_revision(self):
    447         return 9479
    448 
    449     def light_tree_on_fire(self):
    450         self._mock_builder2_status["is_green"] = False
    451 
    452     def failure_map(self):
    453         return MockFailureMap(self)
    454 
    455 
    456 # FIXME: This should not inherit from Mock
    457 class MockSCM(Mock):
    458 
    459     fake_checkout_root = os.path.realpath("/tmp") # realpath is needed to allow for Mac OS X's /private/tmp
    460 
    461     def __init__(self, filesystem=None):
    462         Mock.__init__(self)
    463         # FIXME: We should probably use real checkout-root detection logic here.
    464         # os.getcwd() can't work here because other parts of the code assume that "checkout_root"
    465         # will actually be the root.  Since getcwd() is wrong, use a globally fake root for now.
    466         self.checkout_root = self.fake_checkout_root
    467         self.added_paths = set()
    468         self._filesystem = filesystem
    469 
    470     def add(self, destination_path, return_exit_code=False):
    471         self.added_paths.add(destination_path)
    472         if return_exit_code:
    473             return 0
    474 
    475     def changed_files(self, git_commit=None):
    476         return ["MockFile1"]
    477 
    478     def create_patch(self, git_commit, changed_files=None):
    479         return "Patch1"
    480 
    481     def commit_ids_from_commitish_arguments(self, args):
    482         return ["Commitish1", "Commitish2"]
    483 
    484     def commit_message_for_local_commit(self, commit_id):
    485         if commit_id == "Commitish1":
    486             return CommitMessage("CommitMessage1\n" \
    487                 "https://bugs.example.org/show_bug.cgi?id=42\n")
    488         if commit_id == "Commitish2":
    489             return CommitMessage("CommitMessage2\n" \
    490                 "https://bugs.example.org/show_bug.cgi?id=75\n")
    491         raise Exception("Bogus commit_id in commit_message_for_local_commit.")
    492 
    493     def diff_for_file(self, path, log=None):
    494         return path + '-diff'
    495 
    496     def diff_for_revision(self, revision):
    497         return "DiffForRevision%s\n" \
    498                "http://bugs.webkit.org/show_bug.cgi?id=12345" % revision
    499 
    500     def show_head(self, path):
    501         return path
    502 
    503     def svn_revision_from_commit_text(self, commit_text):
    504         return "49824"
    505 
    506     def delete(self, path):
    507         if not self._filesystem:
    508             return
    509         if self._filesystem.exists(path):
    510             self._filesystem.remove(path)
    511 
    512 
    513 class MockDEPS(object):
    514     def read_variable(self, name):
    515         return 6564
    516 
    517     def write_variable(self, name, value):
    518         log("MOCK: MockDEPS.write_variable(%s, %s)" % (name, value))
    519 
    520 
    521 class MockCheckout(object):
    522 
    523     _committer_list = CommitterList()
    524 
    525     def commit_info_for_revision(self, svn_revision):
    526         # The real Checkout would probably throw an exception, but this is the only way tests have to get None back at the moment.
    527         if not svn_revision:
    528             return None
    529         return CommitInfo(svn_revision, "eric (at] webkit.org", {
    530             "bug_id": 42,
    531             "author_name": "Adam Barth",
    532             "author_email": "abarth (at] webkit.org",
    533             "author": self._committer_list.committer_by_email("abarth (at] webkit.org"),
    534             "reviewer_text": "Darin Adler",
    535             "reviewer": self._committer_list.committer_by_name("Darin Adler"),
    536         })
    537 
    538     def bug_id_for_revision(self, svn_revision):
    539         return 12345
    540 
    541     def recent_commit_infos_for_files(self, paths):
    542         return [self.commit_info_for_revision(32)]
    543 
    544     def modified_changelogs(self, git_commit, changed_files=None):
    545         # Ideally we'd return something more interesting here.  The problem is
    546         # that LandDiff will try to actually read the patch from disk!
    547         return []
    548 
    549     def commit_message_for_this_commit(self, git_commit, changed_files=None):
    550         commit_message = Mock()
    551         commit_message.message = lambda:"This is a fake commit message that is at least 50 characters."
    552         return commit_message
    553 
    554     def chromium_deps(self):
    555         return MockDEPS()
    556 
    557     def apply_patch(self, patch, force=False):
    558         pass
    559 
    560     def apply_reverse_diffs(self, revision):
    561         pass
    562 
    563     def suggested_reviewers(self, git_commit, changed_files=None):
    564         return [_mock_reviewer]
    565 
    566 
    567 class MockUser(object):
    568 
    569     @classmethod
    570     def prompt(cls, message, repeat=1, raw_input=raw_input):
    571         return "Mock user response"
    572 
    573     @classmethod
    574     def prompt_with_list(cls, list_title, list_items, can_choose_multiple=False, raw_input=raw_input):
    575         pass
    576 
    577     def __init__(self):
    578         self.opened_urls = []
    579 
    580     def edit(self, files):
    581         pass
    582 
    583     def edit_changelog(self, files):
    584         pass
    585 
    586     def page(self, message):
    587         pass
    588 
    589     def confirm(self, message=None, default='y'):
    590         log(message)
    591         return default == 'y'
    592 
    593     def can_open_url(self):
    594         return True
    595 
    596     def open_url(self, url):
    597         self.opened_urls.append(url)
    598         if url.startswith("file://"):
    599             log("MOCK: user.open_url: file://...")
    600             return
    601         log("MOCK: user.open_url: %s" % url)
    602 
    603 
    604 class MockIRC(object):
    605 
    606     def post(self, message):
    607         log("MOCK: irc.post: %s" % message)
    608 
    609     def disconnect(self):
    610         log("MOCK: irc.disconnect")
    611 
    612 
    613 class MockStatusServer(object):
    614 
    615     def __init__(self, bot_id=None, work_items=None):
    616         self.host = "example.com"
    617         self.bot_id = bot_id
    618         self._work_items = work_items or []
    619 
    620     def patch_status(self, queue_name, patch_id):
    621         return None
    622 
    623     def svn_revision(self, svn_revision):
    624         return None
    625 
    626     def next_work_item(self, queue_name):
    627         if not self._work_items:
    628             return None
    629         return self._work_items.pop(0)
    630 
    631     def release_work_item(self, queue_name, patch):
    632         log("MOCK: release_work_item: %s %s" % (queue_name, patch.id()))
    633 
    634     def update_work_items(self, queue_name, work_items):
    635         self._work_items = work_items
    636         log("MOCK: update_work_items: %s %s" % (queue_name, work_items))
    637 
    638     def submit_to_ews(self, patch_id):
    639         log("MOCK: submit_to_ews: %s" % (patch_id))
    640 
    641     def update_status(self, queue_name, status, patch=None, results_file=None):
    642         log("MOCK: update_status: %s %s" % (queue_name, status))
    643         return 187
    644 
    645     def update_svn_revision(self, svn_revision, broken_bot):
    646         return 191
    647 
    648     def results_url_for_status(self, status_id):
    649         return "http://dummy_url"
    650 
    651 
    652 # FIXME: This should not inherit from Mock
    653 # FIXME: Unify with common.system.executive_mock.MockExecutive.
    654 class MockExecutive(Mock):
    655     def __init__(self, should_log):
    656         self.should_log = should_log
    657 
    658     def run_and_throw_if_fail(self, args, quiet=False):
    659         if self.should_log:
    660             log("MOCK run_and_throw_if_fail: %s" % args)
    661         return "MOCK output of child process"
    662 
    663     def run_command(self,
    664                     args,
    665                     cwd=None,
    666                     input=None,
    667                     error_handler=None,
    668                     return_exit_code=False,
    669                     return_stderr=True,
    670                     decode_output=False):
    671         if self.should_log:
    672             log("MOCK run_command: %s" % args)
    673         return "MOCK output of child process"
    674 
    675 
    676 class MockOptions(object):
    677     """Mock implementation of optparse.Values."""
    678 
    679     def __init__(self, **kwargs):
    680         # The caller can set option values using keyword arguments. We don't
    681         # set any values by default because we don't know how this
    682         # object will be used. Generally speaking unit tests should
    683         # subclass this or provider wrapper functions that set a common
    684         # set of options.
    685         for key, value in kwargs.items():
    686             self.__dict__[key] = value
    687 
    688 
    689 class MockPort(Mock):
    690     def name(self):
    691         return "MockPort"
    692 
    693     def layout_tests_results_path(self):
    694         return "/mock/results.html"
    695 
    696     def check_webkit_style_command(self):
    697         return ["mock-check-webkit-style"]
    698 
    699     def update_webkit_command(self):
    700         return ["mock-update-webkit"]
    701 
    702 
    703 class MockTestPort1(object):
    704 
    705     def skips_layout_test(self, test_name):
    706         return test_name in ["media/foo/bar.html", "foo"]
    707 
    708 
    709 class MockTestPort2(object):
    710 
    711     def skips_layout_test(self, test_name):
    712         return test_name == "media/foo/bar.html"
    713 
    714 
    715 class MockPortFactory(object):
    716 
    717     def get_all(self, options=None):
    718         return {"test_port1": MockTestPort1(), "test_port2": MockTestPort2()}
    719 
    720 
    721 class MockPlatformInfo(object):
    722     def display_name(self):
    723         return "MockPlatform 1.0"
    724 
    725 
    726 class MockWorkspace(object):
    727     def find_unused_filename(self, directory, name, extension, search_limit=10):
    728         return "%s/%s.%s" % (directory, name, extension)
    729 
    730     def create_zip(self, zip_path, source_path):
    731         pass
    732 
    733 
    734 class MockTool(object):
    735 
    736     def __init__(self, log_executive=False):
    737         self.wakeup_event = threading.Event()
    738         self.bugs = MockBugzilla()
    739         self.buildbot = MockBuildBot()
    740         self.executive = MockExecutive(should_log=log_executive)
    741         self.filesystem = MockFileSystem()
    742         self.workspace = MockWorkspace()
    743         self._irc = None
    744         self.user = MockUser()
    745         self._scm = MockSCM()
    746         self._checkout = MockCheckout()
    747         self.status_server = MockStatusServer()
    748         self.irc_password = "MOCK irc password"
    749         self.port_factory = MockPortFactory()
    750         self.platform = MockPlatformInfo()
    751 
    752     def scm(self):
    753         return self._scm
    754 
    755     def checkout(self):
    756         return self._checkout
    757 
    758     def ensure_irc_connected(self, delegate):
    759         if not self._irc:
    760             self._irc = MockIRC()
    761 
    762     def irc(self):
    763         return self._irc
    764 
    765     def path(self):
    766         return "echo"
    767 
    768     def port(self):
    769         return MockPort()
    770 
    771 
    772 class MockBrowser(object):
    773     params = {}
    774 
    775     def open(self, url):
    776         pass
    777 
    778     def select_form(self, name):
    779         pass
    780 
    781     def __setitem__(self, key, value):
    782         self.params[key] = value
    783 
    784     def submit(self):
    785         return Mock(file)
    786