1 # Copyright (C) 2009 Google Inc. All rights reserved.
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions are
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
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.
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.
33 from webkitpy.common.config.committers import CommitterList, Reviewer
34 from webkitpy.common.checkout.commitinfo import CommitInfo
35 from webkitpy.common.checkout.scm import CommitMessage
36 from webkitpy.common.net.bugzilla import Bug, Attachment
37 from webkitpy.common.system.deprecated_logging import log
38 from webkitpy.common.system.executive import ScriptError
39 from webkitpy.common.system.filesystem_mock import MockFileSystem
42 def _id_to_object_dictionary(*objects):
45 dictionary[thing["id"]] = thing
50 # FIXME: The ids should be 1, 2, 3 instead of crazy numbers.
56 "url": "http://example.com/197",
61 "reviewer_email": "foo@bar.com",
63 "committer_email": "foo@bar.com",
64 "attacher_email": "Contributer1",
71 "url": "http://example.com/128",
76 "reviewer_email": "foo@bar.com",
78 "committer_email": "non-committer@example.com",
79 "attacher_email": "eric@webkit.org",
86 "url": "http://example.com/103",
91 "attacher_email": "eric@webkit.org",
98 "url": "http://example.com/103",
100 "is_obsolete": False,
104 "reviewer_email": "foo@bar.com",
105 "attacher_email": "Contributer2",
112 "url": "http://example.com/103",
114 "is_obsolete": False,
117 "reviewer_email": "foo@bar.com",
118 "attacher_email": "eric@webkit.org",
122 _patch6 = { # Valid committer, but no reviewer.
125 "url": "http://example.com/103",
126 "name": "ROLLOUT of r3489",
127 "is_obsolete": False,
130 "committer_email": "foo@bar.com",
131 "attacher_email": "eric@webkit.org",
135 _patch7 = { # Valid review, patch is marked obsolete.
138 "url": "http://example.com/103",
143 "reviewer_email": "foo@bar.com",
144 "attacher_email": "eric@webkit.org",
148 # This matches one of Bug.unassigned_emails
149 _unassigned_email = "webkit-unassigned@lists.webkit.org"
150 # This is needed for the FlakyTestReporter to believe the bug
151 # was filed by one of the webkitpy bots.
152 _commit_queue_email = "commit-queue@webkit.org"
155 # FIXME: The ids should be 1, 2, 3 instead of crazy numbers.
160 "title": "Bug with two r+'d and cq+'d patches, one of which has an "
161 "invalid commit-queue setter.",
162 "reporter_email": "foo@foo.com",
163 "assigned_to_email": _unassigned_email,
164 "attachments": [_patch1, _patch2],
165 "bug_status": "UNCONFIRMED",
171 "title": "Bug with a patch needing review.",
172 "reporter_email": "foo@foo.com",
173 "assigned_to_email": "foo@foo.com",
174 "attachments": [_patch3],
175 "bug_status": "ASSIGNED",
181 "title": "The third bug",
182 "reporter_email": "foo@foo.com",
183 "assigned_to_email": _unassigned_email,
184 "attachments": [_patch7],
191 "title": "The fourth bug",
192 "reporter_email": "foo@foo.com",
193 "assigned_to_email": "foo@foo.com",
194 "attachments": [_patch4, _patch5, _patch6],
195 "bug_status": "REOPENED",
201 "title": "The fifth bug",
202 "reporter_email": _commit_queue_email,
203 "assigned_to_email": "foo@foo.com",
205 "bug_status": "RESOLVED",
210 class MockBugzillaQueries(object):
212 def __init__(self, bugzilla):
213 self._bugzilla = bugzilla
216 return map(lambda bug_dictionary: Bug(bug_dictionary, self._bugzilla),
217 self._bugzilla.bug_cache.values())
219 def fetch_bug_ids_from_commit_queue(self):
220 bugs_with_commit_queued_patches = filter(
221 lambda bug: bug.commit_queued_patches(),
223 return map(lambda bug: bug.id(), bugs_with_commit_queued_patches)
225 def fetch_attachment_ids_from_review_queue(self):
226 unreviewed_patches = sum([bug.unreviewed_patches()
227 for bug in self._all_bugs()], [])
228 return map(lambda patch: patch.id(), unreviewed_patches)
230 def fetch_patches_from_commit_queue(self):
231 return sum([bug.commit_queued_patches()
232 for bug in self._all_bugs()], [])
234 def fetch_bug_ids_from_pending_commit_list(self):
235 bugs_with_reviewed_patches = filter(lambda bug: bug.reviewed_patches(),
237 bug_ids = map(lambda bug: bug.id(), bugs_with_reviewed_patches)
238 # NOTE: This manual hack here is to allow testing logging in
239 # test_assign_to_committer the real pending-commit query on bugzilla
240 # will return bugs with patches which have r+, but are also obsolete.
241 return bug_ids + [76]
243 def fetch_patches_from_pending_commit_list(self):
244 return sum([bug.reviewed_patches() for bug in self._all_bugs()], [])
246 def fetch_bugs_matching_search(self, search_string, author_email=None):
247 return [self._bugzilla.fetch_bug(78), self._bugzilla.fetch_bug(77)]
250 _mock_reviewer = Reviewer("Foo Bar", "foo@bar.com")
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 class MockBugzilla(object):
258 bug_server_url = "http://example.com"
260 bug_cache = _id_to_object_dictionary(_bug1, _bug2, _bug3, _bug4, _bug5)
262 attachment_cache = _id_to_object_dictionary(_patch1,
271 self.queries = MockBugzillaQueries(self)
272 self.committers = CommitterList(reviewers=[_mock_reviewer])
273 self._override_patch = None
280 patch_description=None,
283 mark_for_review=False,
284 mark_for_commit_queue=False):
285 log("MOCK create_bug")
286 log("bug_title: %s" % bug_title)
287 log("bug_description: %s" % bug_description)
289 log("component: %s" % component)
293 log("blocked: %s" % blocked)
297 return ["Good artists copy. Great artists steal. - Pablo Picasso"]
299 def fetch_bug(self, bug_id):
300 return Bug(self.bug_cache.get(bug_id), self)
302 def set_override_patch(self, patch):
303 self._override_patch = patch
305 def fetch_attachment(self, attachment_id):
306 if self._override_patch:
307 return self._override_patch
309 attachment_dictionary = self.attachment_cache.get(attachment_id)
310 if not attachment_dictionary:
311 print "MOCK: fetch_attachment: %s is not a known attachment id" % attachment_id
313 bug = self.fetch_bug(attachment_dictionary["bug_id"])
314 for attachment in bug.attachments(include_obsolete=True):
315 if attachment.id() == int(attachment_id):
318 def bug_url_for_bug_id(self, bug_id):
319 return "%s/%s" % (self.bug_server_url, bug_id)
321 def fetch_bug_dictionary(self, bug_id):
322 return self.bug_cache.get(bug_id)
324 def attachment_url_for_id(self, attachment_id, action="view"):
326 if action and action != "view":
327 action_param = "&action=%s" % action
328 return "%s/%s%s" % (self.bug_server_url, attachment_id, action_param)
330 def reassign_bug(self, bug_id, assignee=None, comment_text=None):
331 log("MOCK reassign_bug: bug_id=%s, assignee=%s" % (bug_id, assignee))
333 log("-- Begin comment --")
335 log("-- End comment --")
337 def set_flag_on_attachment(self,
342 additional_comment_text=None):
343 log("MOCK setting flag '%s' to '%s' on attachment '%s' with comment '%s' and additional comment '%s'" % (
344 flag_name, flag_value, attachment_id, comment_text, additional_comment_text))
346 def post_comment_to_bug(self, bug_id, comment_text, cc=None):
347 log("MOCK bug comment: bug_id=%s, cc=%s\n--- Begin comment ---\n%s\n--- End comment ---\n" % (
348 bug_id, cc, comment_text))
350 def add_attachment_to_bug(self,
356 log("MOCK add_attachment_to_bug: bug_id=%s, description=%s filename=%s" % (bug_id, description, filename))
358 log("-- Begin comment --")
360 log("-- End comment --")
362 def add_patch_to_bug(self,
367 mark_for_review=False,
368 mark_for_commit_queue=False,
369 mark_for_landing=False):
370 log("MOCK add_patch_to_bug: bug_id=%s, description=%s, mark_for_review=%s, mark_for_commit_queue=%s, mark_for_landing=%s" %
371 (bug_id, description, mark_for_review, mark_for_commit_queue, mark_for_landing))
373 log("-- Begin comment --")
375 log("-- End comment --")
377 def add_cc_to_bug(self, bug_id, ccs):
380 def obsolete_attachment(self, attachment_id, message=None):
383 def reopen_bug(self, bug_id, message):
386 def close_bug_as_fixed(self, bug_id, message):
389 def clear_attachment_flags(self, attachment_id, message):
393 class MockBuilder(object):
394 def __init__(self, name):
400 def results_url(self):
401 return "http://example.com/builders/%s/results" % self.name()
403 def accumulated_results_url(self):
404 return "http://example.com/f/builders/%s/results/layout-test-results" % self.name()
406 def force_build(self, username, comments):
407 log("MOCK: force_build: name=%s, username=%s, comments=%s" % (
408 self._name, username, comments))
411 class MockFailureMap(object):
412 def __init__(self, buildbot):
413 self._buildbot = buildbot
418 def filter_out_old_failures(self, is_old_revision):
421 def failing_revisions(self):
424 def builders_failing_for(self, revision):
425 return [self._buildbot.builder_with_name("Builder1")]
427 def tests_failing_for(self, revision):
428 return ["mock-test-1"]
430 def failing_tests(self):
431 return set(["mock-test-1"])
434 class MockBuildBot(object):
436 self._mock_builder1_status = {
439 "activity": "building",
441 self._mock_builder2_status = {
447 def builder_with_name(self, name):
448 return MockBuilder(name)
450 def builder_statuses(self):
452 self._mock_builder1_status,
453 self._mock_builder2_status,
456 def red_core_builders_names(self):
457 if not self._mock_builder2_status["is_green"]:
458 return [self._mock_builder2_status["name"]]
461 def red_core_builders(self):
462 if not self._mock_builder2_status["is_green"]:
463 return [self._mock_builder2_status]
466 def idle_red_core_builders(self):
467 if not self._mock_builder2_status["is_green"]:
468 return [self._mock_builder2_status]
471 def last_green_revision(self):
474 def light_tree_on_fire(self):
475 self._mock_builder2_status["is_green"] = False
477 def failure_map(self):
478 return MockFailureMap(self)
481 class MockSCM(object):
482 def __init__(self, filesystem=None, executive=None):
483 self.checkout_root = "/mock-checkout"
484 self.added_paths = set()
485 self._filesystem = filesystem or MockFileSystem()
486 self._executive = executive or MockExecutive()
488 def add(self, destination_path, return_exit_code=False):
489 self.added_paths.add(destination_path)
493 def ensure_clean_working_directory(self, force_clean):
496 def supports_local_commits(self):
499 def ensure_no_local_commits(self, force_clean):
502 def exists(self, path):
503 # TestRealMain.test_real_main (and several other rebaseline tests) are sensitive to this return value.
504 # We should make those tests more robust, but for now we just return True always (since no test needs otherwise).
507 def absolute_path(self, *comps):
508 return self._filesystem.join(self.checkout_root, *comps)
510 def changed_files(self, git_commit=None):
513 def changed_files_for_revision(self, revision):
516 def head_svn_revision(self):
519 def create_patch(self, git_commit, changed_files=None):
522 def commit_ids_from_commitish_arguments(self, args):
523 return ["Commitish1", "Commitish2"]
525 def committer_email_for_revision(self, revision):
526 return "mock@webkit.org"
528 def commit_locally_with_message(self, message):
531 def commit_with_message(self, message, username=None, password=None, git_commit=None, force_squash=False, changed_files=None):
534 def merge_base(self, git_commit):
537 def commit_message_for_local_commit(self, commit_id):
538 if commit_id == "Commitish1":
539 return CommitMessage("CommitMessage1\n" \
540 "https://bugs.example.org/show_bug.cgi?id=42\n")
541 if commit_id == "Commitish2":
542 return CommitMessage("CommitMessage2\n" \
543 "https://bugs.example.org/show_bug.cgi?id=75\n")
544 raise Exception("Bogus commit_id in commit_message_for_local_commit.")
546 def diff_for_file(self, path, log=None):
547 return path + '-diff'
549 def diff_for_revision(self, revision):
550 return "DiffForRevision%s\nhttp://bugs.webkit.org/show_bug.cgi?id=12345" % revision
552 def show_head(self, path):
555 def svn_revision_from_commit_text(self, commit_text):
558 def delete(self, path):
559 if not self._filesystem:
561 if self._filesystem.exists(path):
562 self._filesystem.remove(path)
565 class MockDEPS(object):
566 def read_variable(self, name):
569 def write_variable(self, name, value):
570 log("MOCK: MockDEPS.write_variable(%s, %s)" % (name, value))
573 class MockCommitMessage(object):
575 return "This is a fake commit message that is at least 50 characters."
577 class MockCheckout(object):
579 _committer_list = CommitterList()
581 def commit_info_for_revision(self, svn_revision):
582 # The real Checkout would probably throw an exception, but this is the only way tests have to get None back at the moment.
585 return CommitInfo(svn_revision, "eric@webkit.org", {
587 "author_name": "Adam Barth",
588 "author_email": "abarth@webkit.org",
589 "author": self._committer_list.contributor_by_email("abarth@webkit.org"),
590 "reviewer_text": "Darin Adler",
591 "reviewer": self._committer_list.committer_by_name("Darin Adler"),
598 def is_path_to_changelog(self, path):
599 return os.path.basename(path) == "ChangeLog"
601 def bug_id_for_revision(self, svn_revision):
604 def recent_commit_infos_for_files(self, paths):
605 return [self.commit_info_for_revision(32)]
607 def modified_changelogs(self, git_commit, changed_files=None):
608 # Ideally we'd return something more interesting here. The problem is
609 # that LandDiff will try to actually read the patch from disk!
612 def commit_message_for_this_commit(self, git_commit, changed_files=None):
613 return MockCommitMessage()
615 def chromium_deps(self):
618 def apply_patch(self, patch, force=False):
621 def apply_reverse_diffs(self, revision):
624 def suggested_reviewers(self, git_commit, changed_files=None):
625 return [_mock_reviewer]
628 class MockUser(object):
631 def prompt(cls, message, repeat=1, raw_input=raw_input):
632 return "Mock user response"
635 def prompt_with_list(cls, list_title, list_items, can_choose_multiple=False, raw_input=raw_input):
639 self.opened_urls = []
641 def edit(self, files):
644 def edit_changelog(self, files):
647 def page(self, message):
650 def confirm(self, message=None, default='y'):
652 return default == 'y'
654 def can_open_url(self):
657 def open_url(self, url):
658 self.opened_urls.append(url)
659 if url.startswith("file://"):
660 log("MOCK: user.open_url: file://...")
662 log("MOCK: user.open_url: %s" % url)
665 class MockIRC(object):
667 def post(self, message):
668 log("MOCK: irc.post: %s" % message)
670 def disconnect(self):
671 log("MOCK: irc.disconnect")
674 class MockStatusServer(object):
676 def __init__(self, bot_id=None, work_items=None):
677 self.host = "example.com"
679 self._work_items = work_items or []
681 def patch_status(self, queue_name, patch_id):
684 def svn_revision(self, svn_revision):
687 def next_work_item(self, queue_name):
688 if not self._work_items:
690 return self._work_items.pop(0)
692 def release_work_item(self, queue_name, patch):
693 log("MOCK: release_work_item: %s %s" % (queue_name, patch.id()))
695 def update_work_items(self, queue_name, work_items):
696 self._work_items = work_items
697 log("MOCK: update_work_items: %s %s" % (queue_name, work_items))
699 def submit_to_ews(self, patch_id):
700 log("MOCK: submit_to_ews: %s" % (patch_id))
702 def update_status(self, queue_name, status, patch=None, results_file=None):
703 log("MOCK: update_status: %s %s" % (queue_name, status))
706 def update_svn_revision(self, svn_revision, broken_bot):
709 def results_url_for_status(self, status_id):
710 return "http://dummy_url"
713 # FIXME: Unify with common.system.executive_mock.MockExecutive.
714 class MockExecutive(object):
715 def __init__(self, should_log=False, should_throw=False):
716 self._should_log = should_log
717 self._should_throw = should_throw
718 # FIXME: Once executive wraps os.getpid() we can just use a static pid for "this" process.
719 self._running_pids = [os.getpid()]
721 def check_running_pid(self, pid):
722 return pid in self._running_pids
724 def run_and_throw_if_fail(self, args, quiet=False, cwd=None):
726 log("MOCK run_and_throw_if_fail: %s, cwd=%s" % (args, cwd))
727 return "MOCK output of child process"
729 def run_command(self,
734 return_exit_code=False,
736 decode_output=False):
738 log("MOCK run_command: %s, cwd=%s" % (args, cwd))
739 if self._should_throw:
740 raise ScriptError("MOCK ScriptError")
741 return "MOCK output of child process"
744 class MockOptions(object):
745 """Mock implementation of optparse.Values."""
747 def __init__(self, **kwargs):
748 # The caller can set option values using keyword arguments. We don't
749 # set any values by default because we don't know how this
750 # object will be used. Generally speaking unit tests should
751 # subclass this or provider wrapper functions that set a common
753 for key, value in kwargs.items():
754 self.__dict__[key] = value
757 class MockPort(object):
761 def layout_tests_results_path(self):
762 return "/mock-results/results.html"
764 def check_webkit_style_command(self):
765 return ["mock-check-webkit-style"]
767 def update_webkit_command(self):
768 return ["mock-update-webkit"]
770 def build_webkit_command(self, build_style=None):
771 return ["mock-build-webkit"]
773 def run_bindings_tests_command(self):
774 return ["mock-run-bindings-tests"]
776 def prepare_changelog_command(self):
777 return ['mock-prepare-ChangeLog']
779 def run_python_unittests_command(self):
780 return ['mock-test-webkitpy']
782 def run_perl_unittests_command(self):
783 return ['mock-test-webkitperl']
785 def run_javascriptcore_tests_command(self):
786 return ['mock-run-javacriptcore-tests']
788 def run_webkit_tests_command(self):
789 return ['mock-run-webkit-tests']
792 class MockTestPort1(object):
793 def skips_layout_test(self, test_name):
794 return test_name in ["media/foo/bar.html", "foo"]
797 class MockTestPort2(object):
798 def skips_layout_test(self, test_name):
799 return test_name == "media/foo/bar.html"
802 class MockPortFactory(object):
803 def get_all(self, options=None):
804 return {"test_port1": MockTestPort1(), "test_port2": MockTestPort2()}
807 class MockPlatformInfo(object):
808 def display_name(self):
809 return "MockPlatform 1.0"
812 class MockWorkspace(object):
813 def find_unused_filename(self, directory, name, extension, search_limit=10):
814 return "%s/%s.%s" % (directory, name, extension)
816 def create_zip(self, zip_path, source_path):
817 return object() # Something that is not None
820 class MockWeb(object):
821 def get_binary(self, url, convert_404_to_None=False):
822 return "MOCK Web result, convert 404 to None=%s" % convert_404_to_None
825 class MockTool(object):
826 def __init__(self, log_executive=False):
827 self.wakeup_event = threading.Event()
828 self.bugs = MockBugzilla()
829 self.buildbot = MockBuildBot()
830 self.executive = MockExecutive(should_log=log_executive)
832 self.workspace = MockWorkspace()
834 self.user = MockUser()
835 self._scm = MockSCM()
836 self._chromium_buildbot = MockBuildBot()
837 # Various pieces of code (wrongly) call filesystem.chdir(checkout_root).
838 # Making the checkout_root exist in the mock filesystem makes that chdir not raise.
839 self.filesystem = MockFileSystem(dirs=set([self._scm.checkout_root]))
840 self._port = MockPort()
841 self._checkout = MockCheckout()
842 self.status_server = MockStatusServer()
843 self.irc_password = "MOCK irc password"
844 self.port_factory = MockPortFactory()
845 self.platform = MockPlatformInfo()
851 return self._checkout
853 def chromium_buildbot(self):
854 return self._chromium_buildbot
856 def ensure_irc_connected(self, delegate):
858 self._irc = MockIRC()
870 class MockBrowser(object):
876 def select_form(self, name):
879 def __setitem__(self, key, value):
880 self.params[key] = value
883 return StringIO.StringIO()